1实例变量放在堆中,静态变量放在方法区中,局部变量放在栈中
在类加载的时候执行,且在main方法执行之前执行,并且只执行一次,一个类中可以编写多个静态代码块
static{
System.out.println("1")
}
static{
System.out.println("2")
}
static{
System.out.println("3")
}
public static void main(String [] args){
System.out.println("4)
}
//输出:1234
当源码中以;结尾,且修饰列表中有native
关键字,表示 底层调用c++写的dll程序
父类 <----子类
public class cat extends animal{
public void move(){
System.out.println("cat")
}
public void catch(){
System.out.println("cat catch mouse")
}
}
public class dog extends animal{
public void move(){
System.out.println("dog")
}
}
public class animal{
public void move(){
System.out.println("animal)
}
}
public static void main (String [] args){
animal a1 = new cat();
a1.move(); //cat
animal a2 = new dog();
a2.move(); //dog
animal a3 = new cat();
a3.catch() //error 编译不通过,就不用考虑运行时了,因为catch是子类cat特有的方法,编译的时候编译器看animal中没有catch就会报错
}
子类 <—父类
PS:有风险
当需要访问子类中特有的方法,需要进行向下转型
public static void main (String [] args){
animal a3 = new cat();
cat a4 = (cat)a3;
//不会报错,因为cat animal是继承关系,所以可以强制类型转换
a4.catch;//cat catch mouse
animal a5 = new dog();
cat a6 = (cat)a5;//error,编译通过,因为(cat)跟animal有继承关系,但是运行出错,因为a5实际上是dog对象,dog跟cat没有继承关系
}
instanceof可以在运行阶段动态判断引用指向的对象的类型,运算结果为true/false
if(c instanceof cat ) 若为true 则表示:c引用指向的堆内存中的java对象是一个cat
if(a5 instanceof cat ){
cat a6 = (cat)a5;
a6.catch();
}//通过
静态方法不存在方法覆盖
方法覆盖只是针对实例方法,静态方法覆盖没有意义
class animal{
public static void do(){
System.out.println("animal")
}
}
class cat extends animal{
public static void do(){
System.out.println("cat")
}
}
public class test{
public static void main(String [] agrs){
animal a = new cat();
//虽然用a.来引用静态方法,但是实际运行的时候还是animal.do();
a.do();//animal
}
}
super
super不是引用,super也不保存内存地址,super也不指向任何对象
super只是代表当前对象内部的那一块父类型的特征
new C();
class A{
public A(){
System.out.println("1");
}
}
class B extends A{
int x = b;
public B(){
System.out.println("2");
}
public B(String name){
System.out.println("3");
}
}
class C extends B{
int x= c;
public C(){
this("zhangsan");
System.out.println("4");
}
public C(String name){
this(name,20);
System.out.println("5");
}
public C(String name , int a){
super(name);
System.out.println("6");
System.out.println(this.x); //c
System.out.println(super.x); //b
}
}
//13654
接口是一种特殊的抽象类,或者说是一个完全抽象的类
接口中只有常量跟抽象方法,且接口可以多继承
interface A{
}
interface B extends A{
}
interface C extends B,A{
//其中可以省略public static final doublue p = 3.14;
double p = 3.14;
//抽象方法
//public abstract int sum(int a,int b);
int sum(int a,int b );
}
public static void main (String []agrs){
System.out.println(C.p);
}
例子2:
interface demo1{
}
interface demo2{
}
class allDemo implements demo1,demo2{
}
main{
//可以成功运行
demo1 x = new allDemo();
demo2 y = (demo2) x; //allDemo同时继承demo1,demo2,即使编译的时候demo1,demo2之间没有继承关系,接口也能通过
}
例子3:
interface demo1{
}
interface demo2{
}
class allDemo implements demo1{
}
main{
//不可以成功运行
demo1 x = new allDemo();
demo2 y = (demo2) x; //ClassCastException
//allDemo只继承了demo1
}
“==”判断的是两个对象的地址,equals没重写之前底层用的是: ==判断
String、Date、File、包装类等都【重写】了Object类中的equals()方法
不建议使用,复用性不高
public class demo {
public static void main(String[] args) {
myMath mm = new myMath();
// computeImp ci = new computeImp();
// mm.mySum(new computeImp(),100,200);
//上面两行代码比下面的匿名内部类复用性更高,匿名内部类只能用一次
//匿名内部类
mm.mySum(new compute() {
@Override
public int sum(int a, int b) {
return a+b;
}
},100,200);
}
}
interface compute{
int sum(int a,int b);
}
class computeImp implements compute{
public int sum(int a, int b){
return a+b;
}
}
class myMath{
public void mySum(compute imp,int a,int b){
int res = imp.sum(a,b);
System.out.println("成功调用");
}
}
数组
数组在存储的时候,内存地址是连续的
enum 枚举类型名{
枚举值1,枚举值2,,
}
集合本身就是一个对象,其中存储的都是java对象的内存地址,(或者说集合中存储的是对象的引用)
其中:List:ArraryList,LinkedList,Vector
Set:HashSet,TreeSet,其中set集合底层都是都用map,比如HashMap,TreeMap
Collection是集合接口
Collections是集合工具类
Collections可以让集合变成线程安全
Collections.synchronizedList(List)//将List转化为线程安全
使用Collections对List集合中元素进行排序,需要保证List中的元素实现了Comparable接口
PS:集合c中存储的是对象的地址,而不是对象本身“abc”,上图只是为了方便
boolean hasNext = it.hasNext0;
这个方法返回true ,表示还元素可以迭代。这个方法返回false表示没有更多的元素可以迭代了。
Object obj = it.next0;
这个方法让迭代器前进一位,并且将指向的元素返回(拿到).
最好在迭代器里remove,如果在集合里remove,且存在迭代器,会导致集合里的元素跟迭代器的元素不同,会报错
在迭代器里remove,会自动将集合里的元素也一起remove
Collection c = new ArrayList();
c.add(123);
c.add(456);
c.add(789);
Iterator it = c.iterator();
while (it.hasNext()){
System.out.println(it.next());
//c.remove(123); error
it.remove();
}
底层调用了equals方法,下面的代码块中x是String类型,String自动重写了equals方法,比较类型和内容
Collection c = new ArrayList();
String s1 = new String("a");
c.add(s1);
String x = new String("a");
System.out.println(c.contains(x));//true
默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为0)
集合底层是一个Object[]数组
ArrayList集合的扩容:是原容量的1.5倍,因为:默认是10,二进制为:00001010,底层按位右移>>1,00000101 =5 ,10+5=15
面试问题:这么多集合中,你用哪个最多?
ArraryList集合,因为往数组末尾添加元素,效率不受影响,另外,我们检索元素/查找某个元素的操作比较多,检索效率比较高
List l = new ArrayList();
l.add(123);
l.add(456);
l.add(789);
//向指定位置加入元素
l.add(1,555);
Iterator it = l.iterator();
//list特有的遍历方式,因为list有下标,所以可以通过下标来遍历
for(int i = 0;i<l.size();i++){
Object obj = l.get(i);
System.out.println(obj);
}
//获取元素第一次出现的索引
System.out.println(l.indexOf(123));//0
//获取元素最后一次出现的索引
System.out.println(l.lastIndexOf(123));//0
//删除指定下标的元素
l.remove(1);
//修改指定下标的元素
l.set(1,1111);
LinkedList没有初始化容量
vector初始化容量是10,扩容是原容量的两倍
所有方法都是线程安全的,效率较低
Set<String> set = new HashSet<>();
set.add("123");
set.add("1234");
set.add("1235");
List<String> myList = new ArraryList<>(set);
map集合以键值对的方式存储数据
同一个单向链表上所有节点的hash相同,因为他们的数组下标是一样的,但同一个链表上的k和k的equals方法肯定返回都是false,都不相等
放在HashMap中key部分的元素跟HashSet中的元素,需要同时重写equals跟hash方法
//通过Map.Entry遍历,较为推荐,速度较快
Set<Map.Entry<Integer,String>> set =map.entrySet();
Iterator<Map.Entry<Integer,String >> it2 = set.iterator();
while (it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key+","+value);
}
for(Map.Entry<Integer,String > node :set){
System.out.println(node.getKey()+"-->"+node.getValue());
}
HashMap集合的默认初始化容量是16,默认加载因子是0.75
加载因子的意思是:当HashMap集合底层数组中的容量达到**75%**时,数组开始扩容
初始化容量必须是2的倍数,为了提高HashMap集合的存取效率(官方推荐)
JDK8之后,如果哈希表单向链表中元素超过8个,单链表这种数据结构会变成红黑树数据结构,当红黑树上的节点数量小于6时,会重新把红黑树变成单链表数据结构(目的提高检索效率)
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear()清空Map集合
boolean containsKey(object key)判断Map 中是否包含某Nkey
boolean containsValue(0bject value) 判断Map 中是否包含某个value
boolean isEmpty()判断Map集合中元素个数是否为e
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中鍵值对的个数。
Collection values() 获取Map集合中所有的value ,返回一个Collection
Set<>keySet() 获取Map集合所有的key(所有的键是一个set集合)
Set>entrySet()将Map集合转换成Set集合
HashMap中的key可以为null
Hashtable中的key,value都不可以为null
Hashtable的初始化容量是11,扩容是原容量*2+1
Hashtable是线程安全的
TreeSet集合底层实际上是一个TreeMap
TreeMap 集合底层是一个二叉树。
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
TreeSet 集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。
称为:可排序集合。
线程安全
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>();
ts.add("zhangsan");
ts.add("liusi");
ts.add("wangwu");
ts.add("lis");
for (String s:ts){
System.out.println(s);
}
//lis
liusi
wangwu
zhangsan
}
自定义TreeSet类型的对象的时候,需要实现comparable接口,实现方法,指定如何比较compareTo
compareTo方法的返回值很重要:
返回0表示相同,value会覆盖
返回>0,会继续在右子树上找
返回<0,会继续在左子树上找
放到TreeSet或者TreeMap集合key部分的元素要想做到排序包括两种方式:
第一种:放在集合中的元素实现java. lang. Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择呢?
当比较规则不会,发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用comparator接口。
public class methods2 {
public static void main(String[] args) {
//正常创建treeSet对象:
//TreeSet animals = new TreeSet<>();
//第二种方法:创建对象的时候传入比较器
TreeSet<animal> animals = new TreeSet<>(new animalComparetor());
//不用单独写比较器,可以用匿名内部类
/*TreeSet animals = new TreeSet<>(new Comparator() {
@Override
public int compare(animal o1, animal o2) {
return o1.age- o2.age;
}
});*/
animals.add(new animal(123));
animals.add(new animal(1234));
animals.add(new animal(124));
animals.add(new animal(24));
for (animal am :animals){
System.out.println(am);
}
}
}
class animal {
int age;
public animal(int age) {
this.age = age;
}
@Override
public String toString() {
return "animal{" +
"age=" + age +
'}';
}
}
//独立创建一个比较器
class animalComparetor implements Comparator<animal> {
@Override
public int compare(animal o1, animal o2) {
return o1.age- o2.age;
}
}
Properties是一个Map集合 ,继承Hashtable , Properties的key和value都是String类型。
Properties被称为属性类对象,用于读取java的配置文件
& 按位与 两个都1,返回1,其他都是0
**| **按位或 只要有一个是1,就返回1
~ 按位非 0变1、1变0
**^ ** 按位异或 不相同就是1、相同是0
异或运算:一种基于二进制的位运算,特点:同值取0,异值取1.
交换律: a^b = b^a;
结合律: (ab)c = a(bc)
对任意的a: a^a = 0; a^0=a; a^(-1) = ~a.
public int singleNumber(int[] nums) {
int [] nums ={1,2,3,2,1}
int ret = 0;
for (int n : nums) ret = ret ^ n;
return ret;
}
即:1^2^3^2^1 == (1^1)^(2^2)^3 ==3
JDK5.0之后的新特性
集合中存储的类型都是泛型指定的类型,不需要大规模向下转型
JDK8之后引入了:自动推断机制(又称为钻石表达式),即new对象的时候不需要继续写泛型内容,其中<>中的内容叫做:泛型限定符
List<animal> l = new ArrayList<animal>();//jdk8之前
List<animal> l = new ArrayList<>();//jdk8之后
<>中的只是一个表示符,随便写,真正看的是new对象时候的<>
class test <标识符随便写> {
void doSome(标识符随便写 o) {
System.out.println(o);
}
}
public static void main(String[] args) {
test<String> t = new test<>();
t.doSome("123");
//报错,还是得看泛型限定符,
// t.doSome(123);
}
idea默认当前路径是:当前工程的根目录
字节输入流:InputStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer
所有流都实现了Closeable接口,都是可关闭的,都有close()方法
流是内存跟硬盘之间的通道,用完之后要关闭,不然会占用很多资源
java中类名以Stream结尾的都是字节流,以Reader/Writer结尾的都是字符流
FileInputStream
FileOutputs tream
FileReader //只能读取普通文本,读取文本内容时,比较方便
FileWriter
FileInputStream fis = null;
try {
fis = new FileInputStream("D:\\a桌面\\后端\\java\\java_project\\src\\IO\\FileInputStreamTest02.java");
//使用read(byte[])方式读取数据,每次只读取4个字节的数据,二次读取的时候会覆盖上一次的数据
byte[] bytes = new byte[4];
int count ;
while((count= fis.read(bytes))!=-1){
//将byte数组转换成String,读取bytes数组,从0开始,读取到count
System.out.print(new String(bytes,0,count));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (fis !=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
int available()
返回流当中剩余没有读到的字节数量
long skip(long n )
跳过几个字节不读
FileInputStream fis = new FileInputStream("xxx") //其中xxx是abcdef
int readFis = fis.read();//读取了一个字节,一共六个字节
int availCount = fis.available();//还剩5个字节,当前available应该是5
FileOutputStream fos = new FileOutputStream("myFirstTxt");
没有这个文件的时候会创建一个新文件,有文件的时候,会将内容直接覆盖
FileOutputStream fos = new FileOutputStream("myFirstTxt",true);
路径后面加true,表示在原文件后面追加内容
byte [] bytes = {'a','b','c','d'};//abcd
//全部读取
// fos.write(bytes);
//读取部分
fos.write(bytes,0,2);
将String转换成byte类型:
byte [] bytes = {'a','b','c','d'};
//abcd
bytes = str.getBytes();
关键点:进行一边读一边写
fis = new FileInputStream("D:\\a桌面\\后端\\java\\test\\demo.txt");
fos = new FileOutputStream("D:\\a桌面\\后端\\java\\test\\demo2.txt",true);
byte[] bytes = new byte[1024 * 1024];//1MB
int count =0;
while ((count = fis.read(bytes))!=-1){
fos.write(bytes,0,count);
}
跟FileInputStream一样,只不过fis读取的是byte,Reader读取的是char字符
Reader/Writer读取普通文本文件
InputstreamReader
Outputs treamWriter
Buf feredReaders
BufferedWriter
Buf feredInputs tream
Buf feredOutputStream
带有缓冲区的流,使用这个流的时候不需要自定义char数组,或者自定义byte数组,自带缓冲
当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流
外部负责包装的流叫做:包装流/处理流
//节点流
FileReader reder = new FileReader("D:\\a桌面\\后端\\java\\test\\demo.txt");
//包装流
BufferedReader br = new BufferedReader(reder);
其中节点流不需要close()关闭流,因为关闭包装流的时候底层会自己关闭节点流
FileInputStream in = null;
//通过转换流将字节流转换成字符流,
in = new FileInputStream("xx");
InputStreamReader reader = new InputStreamReader(in);
//BufferedReader需要一个Reader字符流类型的参数
BufferedReader br = new BufferedReader(reader);
String readTxt =null;
//合并:
BufferedReader br = new BufferedReader( new InputStreamReader(new FileInputStream("xx")));
DataInputstream
DataOutputstrean
数据专属的流,这个流可以将数据连同类型一起写入文件,这个文件不能用记事本打开,不是普通文件,打开会乱码
DataOutputstrean写的文件只能用DataInputstream去读,并且提前知道写入的顺序,读的顺序跟写的顺序一样才能正常取出数据
PrintWriter
Printstream
System.out就是一个PrintStream流
当我们调用System.out.println()的时候就是用PrintStream输出到控制台
System.setOut()
改变输出方向
PrintStream p = new PrintStream(new FileOutpuStream("xx"))
System.setOut(p)
System.out.println(1)//此时不是输出到控制台了,而是输出到xx文件中
记录日志的方法
ObjectInputstream
Objectoutputstream
file对象有可能是目录,也有可能是文件
createNewFile()//创建文件
mkdir();//创建目录
mkdirs();//创建多目录
getParent()//获取父文件路径
getParentFile()//获取父文件
getAbsolutePath()//获取绝对路径
getName()//获取文件名
isDirectory()//判断是否是一个目录
isFile()//判断是不是一个文件
lastModified()//获取文件最后一次修改时间 ,返回一个毫秒数,从1970年到现在的总毫秒数,需要将它转换成日期
length()//获取文件的大小
listFiles()//获取当前目录下的所有子文件,返回一个File[]
自定义异常类
public class myException extends RuntimeException{
public myException(){
}
public myException(String s){
super(s);
}
}
main
public static void main(String[] args) {
users u = new users();
try{
u.register(123,121114);
}catch (myException e){
System.out.println(e.getMessage());
}
}
对象类
public class users {
int number;
int password;
// public users(int number,int password){
// this.number = number;
// this.password = password;
// }
public void register (int number,int password) throws myException {
if (number > 100 && number < 1000){
if (password > 100 && password < 1000 ){
System.out.println("yes");
}else {
throw new myException("密码错误");
}
}else {
throw new myException("账号错误");
}
}
}
序列化:Serialize 将java对象存储到文件中,将java对象的状态保存下来的过程
反序列化: DeSerialize 将硬盘上的数据重新恢复到内存中,恢复成java对象
参与序列化跟反序列化的对象,必须实现Serializable接口
其中,Serializable接口只是一个标志接口,这个接口当中什么都没有,只是起到一个标识作用,这个标志接口是给Java虚拟机啊参考的,JVM看到这个接口之后,就会为该类自动生成一个序列化版本号
1.首先通过类名进行比对,如果类名不一样,肯定不是一个类
2.如果类名一样,通过序列化版本号区别
一旦代码确定,就不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成一个全新的序列化版本,此时JVM会认为这个类是一个全新的类
凡是一个类实现了Serializable接口,建议给该类一个固定不变的序列化版本
transient表示游离的,不参与序列化
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a桌面\\后端 \\java\\java_project\\src\\序列化\\Student2"));
//序列化对象
oos.writeObject(s);
//刷新
oos.flush();
//关闭
oos.close();
一次序列化多个对象:将对象放在集合中
List<Student> Stus = new ArrayList<>();
Stus.add(new Student("zs",12));
Stus.add(new Student("zs2",12));
Stus.add(new Student("zs3",12));
Stus.add(new Student("zs4",12));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/a桌面/后端/java/java_project/src/序列化/Student3"));
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a桌面\\后端\\java\\java_project\\src\\序列化\\Student2"));
Object obj = ois.readObject();
System.out.println(obj);
反序列化多个对象:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a桌面/后端/java/java_project/src/序列化/Student3"));
List<Student>stuList= (List)ois.readObject();
for (Student s :stuList){
System.out.println(s);
}
ois.close();
对于多线程来说,堆内存跟方法区是共享的,栈内存独立,一个线程一个栈
静态变量 + 常量 + 类信息(构造方法/接口定义) + 运行时常量池存在方法区中 。
1.编写一个类,继承Java.lang.Thread 重写run()方法
2.编写一个类,实现java.lang.Runnable接口 (推荐)
//start()的作用是:在JVM中开辟一个新的栈空间,只要新的栈空间开出来,start()就结束了,线程就已经启动成功了,
//启动成功的线程自动调用run()方法,并且run方法在分支栈的栈底部
//run方法在分支栈底部,main在主栈底部,run跟main平级
直接调用run方法,而不是调用start方法启动线程
thread1 t1 = new thread1();
t1.run()
调用strat方法开启多线程
thread1 t1 = new thread1();
t1.start()
thread1 t1 = new thread1();
t1.start();
class thread1 extends Thread{
@Override
public void run() {
for (int i = 0 ; i < 5; i++){
System.out.println("thread1" + i);
}
}
}
Thread T = new Thread(new thread2());
T.start();
//这是一个可运行的类,还不是一个线程
class thread2 implements Runnable{
public void run() {
for (int i = 0 ; i < 5; i++){
System.out.println("thread2" + i);
}
}
}
JDK1.8新特性,可以接收到线程返回结果,因为前两种方法返回值是void,都不可以接收线程返回结果
task t = new task();
//第一步:创建一个“未来任务类”对象
//参数需要一个Callable接口的实现类
FutureTask task = new FutureTask(t);
Thread th = new Thread(task);
th.start();
//在主线程中获取th线程的执行结果
Object obj = task.get();//这行语句会导致main线程阻塞,
// 因为这行语句执行的时候需要获取th线程的执行结果,而执行结果不知道要什么时候才能得到
System.out.println(obj);
//实现类
class task implements Callable {
//相当于run方法
@Override
public Object call() throws Exception {
return 10;
}
}
取当前线程:
//public static native Thread currentThread();静态方法,可以直接调用
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());
出现在哪当前线程就是谁
获取线程名字:String str = T.getName();
默认线程名字:Thread-0
设置线程名字:T.getName()
public static void main(String []args){
Thread t = new Thread(new thread222);
t.sleep(5000);//会不会让thread222线程休眠5s
//不会,对象.sleep的方式不会生效,运行时候会默认改成Thread.sleep()
//所以是主线程main休眠5s
System.out.prtintln(2)
}
class thread222 implements Runnable{
public void run(){
System.out.println(1)
}
}
interrupt()
public class test5 {
public static void main(String[] args) throws Exception{
Thread thread = new Thread(new th());
thread.start();
Thread.sleep(1000);
System.out.println("下班");
thread.interrupt();
}
}
class th implements Runnable{
@Override
public void run() {
System.out.println("start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("强制开机");
}
System.out.println("end");
}
}
使用一个bool标志,通过判断bool的true/false来控制线程是否结束
public class test6{
public static void main(String[] args) throws Exception{
MyRunnable myRun = new MyRunnable();
Thread thread = new Thread(myRun);
thread.start();
myRun.run = false;
}
}
class MyRunnable implements Runnable{
boolean run = true;
@Override
public void run() {
if (run){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("myRunnable");
}else{
System.out.println("out");
return;
}
}
}
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程的优先级
最低优先级 1 Thread.MIN_PRIORITY
默认优先级 5
最高优先级 10 Thread.MAX_PRIORITY
优先级比较高的获取cput时间片的可能会多一点(不完全是)
void join() 合并线程
class MyThread extends Thread{
public void do(){
MyThread2 t2 = new MyThread2();
t2.join();//当前线程进入阻塞,t2线程执行,直到t2线程结束,当前线程才可以
}
}
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yiled()不是阻塞方法。是让当前线程让位,让给其他线程使用
yield()方法的执行会让当前线程从"运行状态"回到"就绪状态",是有可能重新抢到执行权的
public class test7 {
public static void main(String[] args) {
Thread t = new Thread(new myThread());
t.setName("t");
t.start();
for (int i= 0 ; i < 10 ; i++){
System.out.println("main:" + i);
}
}
}
class myThread implements Runnable{
@Override
public void run() {
for (int i = 0 ; i < 10 ; i++){
if (i % 3 ==0){
Thread.yield();
}
System.out.println("t:"+i);
}
}
}
使用线程同步机制:使用synchronized(),其中()中为线程共享对象,
如果有线程t1,t2,t3,t4,t5,需要让t1,t2,t3同步操作,t4,t5不同步,那么synchronized()中的共享对象为t1,t2,t3的共享对象
假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
假设t1 先执行了,遇到了synchronized ,这个时候自动找“后面共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
假设t1 已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
共享对象的这把锁,结果这把锁被t1占有, t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码块执行结束了, t1会归还这把锁,此时t2终于等到这把锁,然后
t2占有这把锁之后,进入同步代码块执行程序。
理解:
private double balance;
private String account;
public void withdraw(double money){
synchronized (this){//this对当前对象,只有一个所以可以
balance = balance -money;
System.out.println("余额:"+balance);
}
}
//理解:
private double balance;
private String account;
Object o = new Object; //o=0x1111
public void withdraw(double money){
synchronized (o){//也可以,因为o是实例变量,也只有一个,创建对象的时候,只要()中的共享对象只有一个,其他线程进入这个方法的时候遇到共享对象,就不能继续执行,因为只有一个共享对象,已经被占有了
balance = balance -money;
System.out.println("余额:"+balance);
}
}
**synchronized出现在实例方法上,一定锁的是this,只能是this **
synchronized有三种写法:
同步代码块
灵活
synchronized (线程共享对象) {
同步代码块;
在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
在静态方法上使用synchronized
表示找类锁。
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把。
对象锁: 1个对象1把锁,100个 对象100把锁。
类锁: 100个对象, 也可能只是1把类锁。
线程分为两大类:
用户线程
守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)
守护线程的特点:一般是一个死循环,所有的用户线程只要结束,守护线程自动结束
主线程main是一个用户线程
守护线程用在什么地方:
每天00:00的时候系统数据自动备份。这个时候需要使用到定时器,并且我们可以将定时器设置为守护线程
设置守护线程:
th.setDaemon(true);//设置守护线程
th.start();
间隔特定时间,执行特定程序
java的类库中已经写好了一个定时器:java.util.Timer
//创建定时器对象
Timer timer = new Timer();
// Timer timer = new Timer(true);//表示守护线程的方式
//指定定时任务
// timer.schedule(定时任务,第一次执行时间,间隔多久一次执行);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime=sdf.parse("2022-9-25 21:54:00");
timer.schedule(new LogTimerTask(),firstTime,1000*10);
//定时任务类
class LogTimerTask extends TimerTask {
@Override
public void run() {
//编写需要执行的任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + ":完成了一次数据备份");
}
}
Object类中的wait跟notify方法
wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
wait和notify方法都不是通过线程对象调用,比如:t.wati() X
**wati() ** :Object o = new Object
o.wati()
表示:
notify():Object o = new Object
o.notify()
表示:
public static void main(String[] args) {
//仓库
List list =new ArrayList();
//生产者
Thread th1 = new Thread(new producer(list));
th1.setName("生产者模式");
//消费者
Thread th2 = new Thread(new consumer(list));
th2.setName("消费者模式");
th1.start();
th2.start();
}
//生产者线程
class producer implements Runnable{
private List list;
public producer(List list) {
this.list = list;
}
@Override
public void run() {
for (int i = 0 ;i<100;i++){
synchronized (list){
if (list.size() > 1){ //大于0,说明仓库满了,不能继续生产
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序进入到这里,说明仓库是空的,可以生产
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName() + ":"+list.size());
list.notify();
// list.notifyAll();
}
}
}
}
//消费者线程
class consumer implements Runnable{
private List list;
public consumer(List list) {
this.list = list;
}
@Override
public void run() {
for (int i = 0 ;i<100;i++){
synchronized (list){
if (list.size() == 0){ //等于0,说明仓库空了,不能消费
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序进入到这里,说明仓库不是空的,可以消费
Object o =list.remove(0);
System.out.println(Thread.currentThread().getName() + ":"+list.size());
list.notify();
// list.notifyAll();
}
}
}
}
在代码中直接写绝对路径是有问题的,因为写死了,以后代码在其他系统中使用的时候,没有盘符,就会报错。
//这种代码不好,不通用,移植性不好,当换到其他操作系统的时候,这个绝对路径就变了,不可用了
// FileReader reader = new FileReader("D:\\myDesktop\\BE\\java\\java_project\\src\\reflectDemo\\classinfo.properties");
//Thread.currentThread() 当前线程对象
//getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象
//getResource() 这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源(Src)
String path =Thread.currentThread().getContextClassLoader().getResource("reflectDemo/classinfo.properties").getPath();
System.out.println(path);
通过反射机制可以操作字节码文件(.class文件)
反射机制的相关类在哪个包下:
Class c1 = Class.forName("java.lang.String");//代表String.class文件,或者说c1代表String类
String s = "ab";
Class x = s.getClass();
System.out.println(x == c1);
//true,c1跟x两个变量保存的内存地址是一样的,因为都指向方法区中的字节码文件String.class
静态方法
方法中的参数是一个字符串
字符串需要的是一个完整类名
完整类名必须带有包名,java.lang包也不能省略
Class.forName()会导致类加载,类加载时,会执行静态代码块(只执行一次)
//代表String.class文件,或者说c1代表String类
Class c1 = Class.forName("java.lang.String");
public class reflect04 {
public static void main(String[] args) throws Exception{
Class.forName("reflectDemo.myclass");
//静态代码块加载
}
}
class myclass {
static {
System.out.println("静态代码块加载");
}
}
String s = "abc";
Class S = s.getClass();
Class i =String.class
//c1跟x两个变量保存的内存地址是一样的,因为都指向方法区中的字节码文件String.class
System.out.println(x == c1);//true,
System.out.println(i ==c1);//true,
第一种方法:先获取路径,再通过路径用输入流读取文件
/* String path = Thread.currentThread().getContextClassLoader().getResource("reflectDemo/classinfo.properties").getPath();
FileReader read = new FileReader(path);*/
第二种方法:getResourceAsStream直接返回一个流,结合起来了
InputStream read = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflectDemo/classinfo.properties");
第三种方法:资源绑定器,配置文件必须放在类路径下
//资源绑定器,只能绑定xxx.properties文件,并且这个文件必须在类路径下,并且在写路径的时候,不能写后缀properties
ResourceBundle bundle = ResourceBundle.getBundle("reflectDemo/classinfo");
String ClassName = bundle.getString("ClassName");
newInstance()底层调用的是该类型的无参构造方法
如果没有这个无参构造方法会出现异常
Class c = Class.forName("reflectDemo.user");
Object o = c.newInstance();//通过反射机制创建对象,JDK9之后就不能用了
创建对象
Constructor> notParFun = aClass.getDeclaredConstructor();
Object o2 = notParFun.newInstance();
public static void main(String[] args) throws Exception{
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflectDemo/classinfo.properties");
Properties pro = new Properties();
pro.load(is);
String className2 = pro.getProperty("ClassName2");
Class<?> aClass = Class.forName(className2);
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor constructor:declaredConstructors){
String s = Modifier.toString(constructor.getModifiers());
System.out.println(s);
String name = aClass.getSimpleName();
System.out.println(name);
Class[] parameterTypes = constructor.getParameterTypes();
for (Class p:parameterTypes){
String simpleName = p.getSimpleName();
System.out.println(simpleName);
}
}
//调用构造方法
Object o = aClass.newInstance();//调用无参构造方法创建对象
System.out.println(o);
//获取无参构造方法
Constructor<?> notParFun = aClass.getDeclaredConstructor();
Object o2 = notParFun.newInstance();
System.out.println(o2);
//先获取有参构造方法,
Constructor<?> ParFun = aClass.getDeclaredConstructor(String.class);
//调用构造方法
Object o1 = ParFun.newInstance("588");//调用有参构造方法
System.out.println(o1);
}
在下列代码中:classinfo.properties: ClassName=reflectDemo.user
main{
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflectDemo/classinfo.properties");
Properties pro = new Properties();
pro.load(is);
String ClassName = pro.getProperty("ClassName");
System.out.println(ClassName);
Class c =Class.forName(ClassName);
Field[] fields = c.getFields();//获取公共属性
String name = fields[0].getName(); //name=no,其中no为public
//获取到了所有属性,所有
Field[] declaredFields = c.getDeclaredFields();
System.out.println(declaredFields.length);//5
for (Field field : declaredFields){
//获取属性名字:name no age sex id
String fName = field.getName();
// System.out.print(fName +" ");
//获取属性类型:(完整类型,String不是基本类型,所以连包名也一起获取) class java.lang.String int int boolean int
Class fieldType = field.getType();
// System.out.print(fieldType+" ");
//获取属性类型简单形式:String int int boolean int
String simpleName = fieldType.getSimpleName();
// System.out.print(simpleName+" ");
//获取属性修饰符,getModifiers()返回值是int,每一个修饰符都有自己对应的数字编号
int modifiers = field.getModifiers();
//private public protected public static final
String s = Modifier.toString(modifiers);//Modifier是一个修饰符类,其中的toString专门用来将getModifiers()获取的编号转化成String
System.out.print(s +" ");
}
}
public static void main(String[] args) throws Exception{
InputStream is = Thread.currentThread().getContextClassLoader(),getResourceAsStream("reflectDemo/classinfo.properties");
Properties pro = new Properties();
pro.load(is);
String className = pro.getProperty("ClassName");
Class aClass = Class.forName(className);
Object o = aClass.newinstance();
no.set(o,22);
/*访问私有属性*/
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);//打破封装
name.set(o,"zs");
System.out.println(name.get(o));//获取失败,can not access "private",打破封装后可以获取
}
public static void main(String[] args) throws Exception{
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflectDemo/classinfo.properties");
Properties pro =new Properties();
pro.load(is);
String className2 = pro.getProperty("ClassName2");
Class<?> aClass = Class.forName(className2);
//获取所有的方法(包括私有)
Method[] declaredMethod = aClass.getDeclaredMethods();
System.out.println(declaredMethod.length);
for (Method method:declaredMethod){
//获取方法的访问权限 public
System.out.println(Modifier.toString(method.getModifiers()));
//获取返回值类型 String
System.out.println(method.getReturnType().getSimpleName());
//获取方法名 test
System.out.println(method.getName());
//获取参数列表 String
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class p:parameterTypes){
System.out.println(p.getSimpleName());
}
}
public static void main(String[] args) throws Exception{
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflectDemo/classinfo.properties");
Properties pro =new Properties();
pro.load(is);
String className2 = pro.getProperty("ClassName2");
Class<?> aClass = Class.forName(className2);
Method[] declaredMethod = aClass.getDeclaredMethods();
//创建对象
Object o = aClass.newInstance();
//获取方法,需要方法名+参数类型(避免重载)
Method test = aClass.getDeclaredMethod("test",String.class,String.class);
//调用方法:第一步:对象,第二步:方法名,第三步:参数,第四步:返回值
Object invokeTest = test.invoke(o,"123","13");//invoke是调用的意思,返回值用Object接收
public static void main(String[] args) throws Exception{
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflectDemo/classinfo.properties");
Properties pro = new Properties();
pro.load(is);
String className2 = pro.getProperty("ClassName2");
Class<?> aClass = Class.forName(className2);
//获取父类
Class<?> superclass = aClass.getSuperclass();//class reflectDemo.demoFather
String simpleName = superclass.getSimpleName();//demoFather
//获取实现的所有接口
Class<?>[] interfaces = aClass.getInterfaces();
}
注解annotation是一种引用数据类型,编译之后也是生成.class文件
自定义注解语法格式:
[修饰符列表] @annotation 注解类型名{
}
注解使用语法:@注解类型名
注解可以用在:类上,方法上,变量上,注解上
在java.lang包下的注释类型:
元注解:
用来标注“注释类型”的注解,被称为元注解
常见的元注解:Target Retention
Target :
Retention:
public @interface myAnnotation2 {
String name();
int id();
String sex() default "男";
String value();
}
public class annotation2 {
//当注解有属性的时候,必须给属性赋值,(当属性有default指定了默认值)
@myAnnotation2(name="1",id=2,value = "3")
public void test(){}
//如果一个注解中只有一个属性叫做value,可以省略属性名直接给值
@myAnnotation3("heihei")
public void test2(){}
}
属性的类型可以是:
public @interface myAnnotation {
int age();
String [] interests();
}
@myAnnotation(age=1,interests = {"1","2"})
public annotation(){}
//如果数组中只有一个元素:大括号可以省略
@myAnnotation(age=2,interests = "3")
public void test(){}
//只允许该注解标注类,方法
@Target({ElementType.TYPE,ElementType.METHOD})
//希望这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface myAnnotation {
int age();
String [] interests();
String value() default "yes";
}