javaSE进阶重点

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程序


多态

降低程序的耦合度,提高程序的扩展力
javaSE进阶重点_第1张图片


向上转型

父类 <----子类


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

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只是代表当前对象内部的那一块父类型的特征

  • 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

javaSE进阶重点_第2张图片


interface

接口是一种特殊的抽象类,或者说是一个完全抽象的类

接口中只有常量跟抽象方法,且接口可以多继承

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跟==

“==”判断的是两个对象的地址,equals没重写之前底层用的是: ==判断

StringDateFile包装类等都【重写】了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对象的内存地址,(或者说集合中存储的是对象的引用)

javaSE进阶重点_第3张图片

其中:List:ArraryList,LinkedList,Vector

Set:HashSet,TreeSet,其中set集合底层都是都用map,比如HashMap,TreeMap

Collection是集合接口

Collections是集合工具类

Collections可以让集合变成线程安全

Collections.synchronizedList(List)//将List转化为线程安全

使用Collections对List集合中元素进行排序,需要保证List中的元素实现了Comparable接口


迭代器

javaSE进阶重点_第4张图片

PS:集合c中存储的是对象的地址,而不是对象本身“abc”,上图只是为了方便

boolean hasNext = it.hasNext0;
这个方法返回true ,表示还元素可以迭代。这个方法返回false表示没有更多的元素可以迭代了。

Object obj = it.next0;

这个方法让迭代器前进一位,并且将指向的元素返回(拿到).


remove

最好在迭代器里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();
        }

javaSE进阶重点_第5张图片


contains底层判断

底层调用了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

javaSE进阶重点_第6张图片


List

ArrayList集合

  1. 默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为0)

  2. 集合底层是一个Object[]数组

  3. ArrayList集合的扩容:是原容量的1.5倍,因为:默认是10,二进制为:00001010,底层按位右移>>1,00000101 =5 ,10+5=15

  4. 面试问题:这么多集合中,你用哪个最多?

    ​ 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集合

LinkedList没有初始化容量


Vector集合

vector初始化容量是10,扩容是原容量的两倍

所有方法都是线程安全的,效率较低


将Set集合转换成List集合

Set<String> set = new HashSet<>();
set.add("123");
set.add("1234");
set.add("1235");
List<String> myList = new ArraryList<>(set);

Map

map集合以键值对的方式存储数据


HashMap

同一个单向链表上所有节点的hash相同,因为他们的数组下标是一样的,但同一个链表上的k和k的equals方法肯定返回都是false,都不相等

放在HashMap中key部分的元素跟HashSet中的元素,需要同时重写equals跟hash方法

javaSE进阶重点_第7张图片

遍历Map集合

   //通过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集合

Hashtable

HashMap中的key可以为null

Hashtable中的key,value都不可以为null

Hashtable的初始化容量是11,扩容是原容量*2+1

Hashtable是线程安全的


TreeSet

  1. TreeSet集合底层实际上是一个TreeMap

  2. TreeMap 集合底层是一个二叉树。

  3. 放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。

  4. TreeSet 集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序

    称为:可排序集合。

  5. 线程安全

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
    }

自定义类型需要实现Comparable接口

自定义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;
    }
}

二叉树的结构以及遍历方法

javaSE进阶重点_第8张图片


Properties

Properties是一个Map集合 ,继承Hashtable , Properties的key和value都是String类型。
Properties被称为属性类对象,用于读取java的配置文件

  1. getProperty ( String key),用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。
  2. load ( InputStream inStream),从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String key) 来搜索。
  3. setProperty ( String key, String value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。
  4. store ( OutputStream out, String comments),以适合使用 load 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。
  5. clear (),清除所有装载的 键 - 值对。该方法在基类中提供

位运算

& 按位与 两个都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);
    }

IO

javaSE进阶重点_第9张图片

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();
                }
            }
        }

FileInputStream方法:

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方法:

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);
            }

Reader方法:

跟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文件中

日志工具:Logger

记录日志的方法


对象专属流:

​ ObjectInputstream
Objectoutputstream


File

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("账号错误");
       }
   }
}

当密码不对的时候:抛出异常
javaSE进阶重点_第10张图片


反序列化:DeSerialize

javaSE进阶重点_第11张图片

序列化: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()

javaSE进阶重点_第12张图片

方式1:直接继承Thread创建线程

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);
      }
    }
}

方式2:实现接口方式创建线程

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);
        }
    }
}

方法3:实现Callable接口创建线程

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()

面试题关于sleep

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的共享对象

  1. 假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。

  2. 假设t1 先执行了,遇到了synchronized ,这个时候自动找“后面共享对象”的对象锁,

    找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是

    占有这把锁的。直到同步代码块代码结束,这把锁才会释放。

  3. 假设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有三种写法:

  1. 同步代码块

    灵活
    synchronized (线程共享对象) {
    同步代码块;

  2. 在实例方法上使用synchronized
    表示共享对象一定是this
    并且同步代码块是整个方法体。

  3. 在静态方法上使用synchronized

    表示找类锁。
    类锁永远只有1把。
    就算创建了100个对象,那类锁也只有一把。
    对象锁: 1个对象1把锁,100个 对象100把锁。
    类锁: 100个对象, 也可能只是1把类锁。

javaSE进阶重点_第13张图片

守护线程

线程分为两大类:

  1. 用户线程

  2. 守护线程(后台线程)

    其中具有代表性的就是:垃圾回收线程(守护线程)

    • 守护线程的特点:一般是一个死循环,所有的用户线程只要结束,守护线程自动结束

    • 主线程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方法

  1. wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。

    wait和notify方法都不是通过线程对象调用,比如:t.wati() X

  2. **wati() ** :Object o = new Object o.wati()

    表示:

    • 让正在o对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止,o.wait()方法的调 用,会让“当前线程(正在o对象上活动的线程)”进入等待状态(无限期等待,除非用notify唤醒),并且释放之前占有的o对象的锁
  3. notify()Object o = new Object o.notify()

    表示:

    • 唤醒正在o对象上等待的线程,不会释放之前占有的o对象的锁
    • **notifyAll() **方法 :这个 方法是唤醒o对象上处于等待的所有线程
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文件)

反射机制的相关类在哪个包下:

  • java.lang.reflect.class:代表整个字节码,代表整个类
  • java.lang.reflect.Method:代表字节码中的方法字节码,代表类中的方法
  • java.lang.reflect.Constructor:代表字节码中的构造器方法字节码,代表类中的构造器方法
  • java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的成员变量(静态变量)

理解:

javaSE进阶重点_第14张图片

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

获取一个类的字节码的三种方式:

  1. Class.forName()

    • 静态方法

    • 方法中的参数是一个字符串

    • 字符串需要的是一个完整类名

    • 完整类名必须带有包名,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("静态代码块加载");
           }
       }
      
  2. 对象.getClass

    • String s = "abc";
      Class S = s.getClass();
      
  3. .class

    • 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之后就不能用了

创建对象

  • 通过有参构造方法
  • 通过无参构造方法:
    • 直接newInstance()
    • 获取无参构造方法,调用构造方法
      • 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 注解类型名{

}

注解使用语法:@注解类型名

注解可以用在:类上,方法上,变量上,注解上

JDK内置注解

在java.lang包下的注释类型:

  1. Deprecated 用@Deprecated注解的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择,已过时
  2. Override 表示一个方法生命打算重写父类中的另一个方法声明
    • @Override这个注解只能注解方法
    • 这个注解是给编译器看的,和运行阶段没有关系
    • 凡是java中方法带这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错
  3. SuppressWarnings 只是应该在注释元素(以及包含在该注释元素中所有程序元素)中取消显示指定的编译器警告

元注解

用来标注“注释类型”的注解,被称为元注解

常见的元注解:Target Retention

Target :

  • @(ElementType.METHOD)可以用来标注的“被标注的注解”最终保存在哪里

Retention:

  • Retention(RetentionPolicy.SOURCE):表示该注解最被保存在java源文件中
  • Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中
  • Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取到

注解属性

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(){}
}

属性的类型可以是:

  • byte short int long float double boolean char String Class enum 以及其中的每一种的数组形式
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";
}

你可能感兴趣的:(java,开发语言)