G

117 阅读28分钟

JDK8新特性

第一章.函数式编程思想和Lambda表达式定义格式

 1.面向对象思想:重点是找对象,调用对象中的方法帮我们去做事儿,侧重点在找对象
 2.函数式编程思想:对面向对象思想进一步简化,不注重过程,只注重结果
     
 3.定义格式:
   ()->{}
   
   ():重写方法的参数位置
   ->:将参数传递到方法体中
   {}:重写方法的方法体
 public class Test01 {
     public static void main(String[] args) {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("我到达朝鲜了");
             }
         }).start();
         System.out.println("=============================");
         new Thread(()-> System.out.println("我到达朝鲜了")).start();
     }
 }

第二章.Lambda表达式使用前提

 1.使用前提:
   a.必须有重写方法
   b.必须有函数式接口做方法参数传递
     函数式接口:接口中必须有且只能有一个抽象方法
     检测函数式接口:在接口上面,@FunctionalInterface

第三章.Lambda表达式省略规则

 1.重写方法的参数类型可以省略
 2.如果重写方法的参数只有一个,所在的小括号可以省略
 3.如果方法体中只有一句代码,所在的大括号可以省略
 4.如果方法体中只有一句代码,所在的大括号可以省略之外,那一句代码后面的;可以省略
   如果带return,return也可以省略
 新手怎么写lambda表达式:
 1.观察是否是函数式接口做方法参数传递
 2.如果是,可以考虑搞lambda表达式
 3.先写一个匿名内部类
 4.从new接口开始到重写方法的方法名结束,选中,删除,别忘记再删除右半个大括号
 5.在重写方法的参数后面,加上->
 6.再看看根据省略规则,能不能进一步删除
 public class Person {
     private String name;
     private int age;
 ​
     public Person() {
     }
 ​
     public Person(String name, int age) {
         this.name = name;
         this.age = age;
     }
 ​
     public String getName() {
         return name;
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public int getAge() {
         return age;
     }
 ​
     public void setAge(int age) {
         this.age = age;
     }
 ​
     @Override
     public String toString() {
         return "Person{" +
                 "name='" + name + ''' +
                 ", age=" + age +
                 '}';
     }
 }
 ​
 public class Test02 {
     public static void main(String[] args) {
         ArrayList<Person> list = new ArrayList<>();
         list.add(new Person("柳岩",36));
         list.add(new Person("涛哥",12));
         list.add(new Person("翠儿",60));
 ​
 /*
         Collections.sort(list, new Comparator<Person>() {
             @Override
             public int compare(Person o1, Person o2) {
                 return o1.getAge()-o2.getAge();
             }
         });
 */
         Collections.sort(list, (o1, o2)-> o1.getAge()-o2.getAge());
 ​
         System.out.println(list);
     }
 }
 ​

第四章.函数式接口

1.定义格式

 1.概述:接口中必须有且只能有一个抽象方法
 2.注解: @FunctionalInterface
 3.定义格式:
   
   @FunctionalInterface
   public interface 接口名{
       抽象方法
   }

2.基本使用

 @FunctionalInterface
 public interface USB {
     void open();
 }
 ​
 public class Test01 {
     public static void main(String[] args) {
         method(new USB() {
             @Override
             public void open() {
                 System.out.println("鼠标开启");
             }
         });
 ​
         System.out.println("=======Lambda表达式======");
 ​
         method(()->{
                 System.out.println("鼠标开启");
         });
 ​
         System.out.println("=======Lambda表达式最终写法======");
 ​
         method(()-> System.out.println("鼠标开启"));
     }
 ​
     public static void method(USB usb){
         usb.open();
     }
 }
 ​

3.Supplier

 1.Supplier接口
    java.util.function.Supplier<T>接口,它意味着"供给"->我们想要什么就给什么
 2.方法:
   T get()
 ​
 3.需求:
    使用Supplier接口作为方法的参数
    用Lambda表达式求出int数组中的最大值
 /*
   供给型接口
  */
 public class Test02_Supplier {
     public static void main(String[] args) {
         method(new Supplier<Integer>() {
             @Override
             public Integer get() {
                 int[] arr = {6,4,6,9,4,6};
                 Arrays.sort(arr);
                 return arr[arr.length-1];
             }
         });
         System.out.println("==========Lambda表达式===========");
         method(()-> {
                 int[] arr = {6,4,6,9,4,6};
                 Arrays.sort(arr);
                 return arr[arr.length-1];
         });
 ​
     }
 ​
     public static void method(Supplier<Integer> supplier){
         Integer result01 = supplier.get();
         System.out.println(result01);
     }
 }
 ​

image-20220314103713387

4.Consumer

 java.util.function.Consumer<T>->消费型接口
   方法:
     void accept(T t),意为消费一个指定泛型的数据
 /*
   消费型接口
  */
 public class Test03_Comsumer {
     public static void main(String[] args) {
         method(new Consumer<String>() {
             @Override
             public void accept(String s) {
                 System.out.println(s.toUpperCase());
             }
         },"abcdefg");
 ​
         System.out.println("==========Lambda表达式==============");
 ​
         method((String s)-> {
                 System.out.println(s.toUpperCase());
         },"abcdefg");
 ​
         System.out.println("==========Lambda表达式最终写法==============");
 ​
         method(s-> System.out.println(s.toUpperCase()),"abcdefg");
     }
     public static void method(Consumer<String> consumer,String s){
         consumer.accept(s);
     }
 }

image-20220314104445224

5.Function

 java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
   方法:
      R apply(T t)根据类型T参数获取类型R的结果
 public class Test04_Function {
     public static void main(String[] args) {
         method(new Function<Integer, String>() {
             @Override
             public String apply(Integer integer) {
                 return integer+"";
             }
         },100);
 ​
         System.out.println("==============Lambda表达式================");
 ​
         method((Integer integer)-> {
                 return integer+"";
         },100);
 ​
         System.out.println("==============Lambda表达式最终写法================");
 ​
         method(integer-> integer+"",100);
     }
 ​
     public static void method(Function<Integer,String> function,Integer i){
         String s = function.apply(i);
         System.out.println(s+1);
     }
 }

image-20220314111050851

6.Predicate

 java.util.function.Predicate<T>接口。->判断型接口
     boolean test(T t)->用于判断的方法,返回值为boolean
 public class Test05_Predicate {
     public static void main(String[] args) {
         method(new Predicate<String>() {
             @Override
             public boolean test(String s) {
                 return s.length()==5;
             }
         },"abcdefg");
 ​
         System.out.println("==========Lambda表达式==========");
 ​
         method((String s)-> {
                 return s.length()==5;
 ​
         },"abcdefg");
 ​
         System.out.println("==========Lambda表达式最终写法==========");
 ​
         method(s-> s.length()==5,"abcdefg");
     }
 ​
     public static void method(Predicate<String> predicate,String s){
         boolean result = predicate.test(s);
         System.out.println("result = " + result);
     }
 }
 ​

第五章.Stream流介绍,以及初体验

 public class Test01_Stream {
     public static void main(String[] args) {
         ArrayList<String> list = new ArrayList<>();
         list.add("张无忌");
         list.add("柳岩");
         list.add("张翠山");
         list.add("涛哥");
         list.add("张三丰");
         list.add("杨幂");
         list.add("张三丰子");
 ​
         //将姓"张"人名留下->过滤
         ArrayList<String> list1 = new ArrayList<>();
         for (String s : list) {
             if (s.startsWith("张")){
                 list1.add(s);
             }
         }
         System.out.println(list1);
 ​
         //获取名字为三个字的
         ArrayList<String> list2 = new ArrayList<>();
         for (String s : list1) {
             if (s.length()==3){
                 list2.add(s);
             }
         }
         System.out.println(list2);
 ​
         //输出
 ​
         for (String s : list2) {
             System.out.println(s);
         }
 ​
         System.out.println("======================");
         Stream<String> stream = list.stream();
         /*stream.filter(new Predicate<String>() {
             @Override
             public boolean test(String s) {
                 return s.startsWith("张");
             }
         }).filter(new Predicate<String>() {
             @Override
             public boolean test(String s) {
                 return s.length()==3;
             }
         }).forEach(new Consumer<String>() {
             @Override
             public void accept(String s) {
                 System.out.println(s);
             }
         });*/
 ​
         stream.filter(s -> s.startsWith("张")).filter(s -> s.length()==3).forEach(s -> System.out.println(s));
     }
 }
 ​

image-20220314113825447

第六章.Stream的获取

 1.针对数组:
   static Stream<T> of(T... values)  
 2.针对集合:
   Stream<E> stream()  
 public class Test02_Stream {
     public static void main(String[] args) {
        /* 1.针对数组:
         static Stream<T> of(T... values)*/
         Stream<String> stream1 = Stream.of("越前龙马", "不二周助", "手冢国光", "桃城武");
         
 ​
        /* 2.针对集合:
         Stream<E> stream()*/
 ​
         ArrayList<String> list = new ArrayList<>();
         list.add("樱木花道");
         list.add("流川枫");
         list.add("三井寿");
         Stream<String> stream2 = list.stream();
     }
 }

第七章.Stream的方法

1.Stream中的forEach方法:void forEach(Consumer<? super T> action);

 forEach : 逐一处理->遍历
 void forEach(Consumer<? super T> action);
 ​
 注意:forEach方法是一个终结方法
     Stream流对象一旦使用了forEach,后续Stream流不能再使用了
 public class Test03_Stream {
     public static void main(String[] args) {
         Stream<String> stream1 = Stream.of("越前龙马", "不二周助", "手冢国光", "桃城武");
        /* stream1.forEach(new Consumer<String>() {
             @Override
             public void accept(String s) {
                 System.out.println(s);
             }
         });*/
 ​
         System.out.println("===========Lambda=========");
 ​
         /*stream1.forEach((String s)->{
                 System.out.println(s);
 ​
         });*/
 ​
         System.out.println("===========Lambda最终写法=========");
 ​
         stream1.forEach(s-> System.out.println(s));
 ​
 ​
         //stream1.forEach(System.out::println);
     }
 }
 ​

2.Stream中的long count()方法

 1.作用:统计元素个数
 2注意:
  count方法是一个终结方法
 public class Test04_Stream {
     public static void main(String[] args) {
         Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "殷素素", "殷天正", "张无忌他舅");
         long count = stream.count();
         System.out.println(count);
 ​
         //stream.forEach(s -> System.out.println(s));//count()也是一个终结方法
     }
 }

3.Stream中的Stream<T> filter(Predicate<? super T> predicate)方法

 1.方法:Stream<T> filter(Predicate<? super T> predicate)方法,返回一个新的Stream流对象
 2.作用:过滤
 3.注意:过滤 不是  干掉符合条件的元素;而是留下符合条件的元素
 public class Test05_Stream {
     public static void main(String[] args) {
         Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "殷素素", "殷天正", "张无忌他舅");
 ​
        /* Stream<String> stream1 = stream.filter(new Predicate<String>() {
             @Override
             public boolean test(String s) {
                 return s.startsWith("张");
             }
         });
 ​
         stream1.forEach(new Consumer<String>() {
             @Override
             public void accept(String s) {
                 System.out.println(s);
             }
         });*/
 ​
 ​
         /*stream.filter(new Predicate<String>() {
             @Override
             public boolean test(String s) {
                 return s.startsWith("张");
             }
         }).forEach(new Consumer<String>() {
             @Override
             public void accept(String s) {
                 System.out.println(s);
             }
         });*/
 ​
         stream.filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
     }
 }

4.Stream<T> limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象

 1.Stream<T> limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象
 public class Test06_Stream {
     public static void main(String[] args) {
         Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "殷素素", "殷天正", "张无忌他舅");
         stream.limit(3).forEach(s -> System.out.println(s));
     }
 }

5.Stream<T> skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象

 Stream<T> skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象
 public class Test07_Stream {
     public static void main(String[] args) {
         Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "殷素素", "殷天正", "张无忌他舅");
         stream.skip(3).forEach(s -> System.out.println(s));
     }
 }
 ​

6.static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):两个流合成一个流

 1.方法:static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):两个流合成一个流
 public class Test08_Stream {
     public static void main(String[] args) {
         Stream<String> stream1 = Stream.of("铁甲小宝", "卡布达", "蝎子莱莱", "蜻蜓队长", "鲨鱼辣椒");
         Stream<String> stream2 = Stream.of("孙悟空", "孙悟饭", "孙悟天", "贝吉塔", "特南克斯");
 ​
         Stream.concat(stream1,stream2).forEach(s -> System.out.println(s));
     }
 }
 ​

7.将Stream流变成集合

Stream流对象转成集合对象,使用Stream接口方法collect
 public class Test09_Stream {
     public static void main(String[] args) {
         ArrayList<String> list = new ArrayList<>();
         list.add("和珅");
         list.add("丰申殷德");
         list.add("乾隆小女儿");
         Stream<String> stream = list.stream();
 ​
         //将Stream转会集合
         List<String> list1 = stream.collect(Collectors.toList());
         for (String s : list1) {
             System.out.println(s);
         }
     }
 }
 ​

8.Stream流练习

    1. 第一个队伍只要名字为3个字的成员姓名;//filter
 ​
    2. 第一个队伍筛选之后只要前3个人;//limit
 ​
    3. 第二个队伍只要姓张的成员姓名;//filter
 ​
    4. 第二个队伍筛选之后不要前2个人;//skip
 ​
    5. 将两个队伍合并为一个队伍;//concat
 ​
    6. 打印整个队伍的姓名信息。//forEach
 public class Test10_Stream {
     public static void main(String[] args) {
         List<String> one = new ArrayList<>();
         one.add("迪丽热巴");
         one.add("宋远桥");
         one.add("苏星河");
         one.add("老子");
         one.add("庄子");
         one.add("孙子");
         one.add("洪七公");
 ​
         List<String> two = new ArrayList<>();
         two.add("古力娜扎");
         two.add("张无忌");
         two.add("张三丰");
         two.add("赵丽颖");
         two.add("张二狗");
         two.add("张天爱");
         two.add("张三");
 ​
         //将两个集合变成Stream流
         Stream<String> streamA = one.stream();
         Stream<String> streamB = two.stream();
 ​
        /* Stream<String> stream1 = streamA.filter(new Predicate<String>() {
             @Override
             public boolean test(String s) {
                 return s.length() == 3;
             }
         }).limit(3);
 ​
 ​
         Stream<String> stream2 = streamB.filter(new Predicate<String>() {
             @Override
             public boolean test(String s) {
                 return s.startsWith("张");
             }
         }).skip(2);
 ​
 ​
         Stream.concat(stream1,stream2).forEach(new Consumer<String>() {
             @Override
             public void accept(String s) {
                 System.out.println(s);
             }
         });*/
 ​
         //Stream<String> stream1 = streamA.filter(s -> s.length() == 3).limit(3);
         //Stream<String> stream2 = streamB.filter(s -> s.startsWith("张")).skip(2);
         //Stream.concat(stream1,stream2).forEach(s -> System.out.println(s));
         Stream.concat(streamA.filter(s -> s.length() == 3).limit(3),streamB.filter(s -> s.startsWith("张")).skip(2)).forEach(s -> System.out.println(s));
     }
 }

第八章.方法引用

一.方法引用的介绍

1.概述:在Lambda表达式的基础上再次简化
2.注意:使用场景比较局限
3.使用场景:
  a.方法引用代码必须在重写的方法中写
  b.引用的方法要和所在的重写的方法从参数上,返回值上一样(类型 , 个数)
4.方法引用:
  在lambda表达式的基础上,干掉重写方法的参数和箭头以及引用方法的参数
  将.换成::

二.方法引入的体验

public class Test01 {
    public static void main(String[] args) {
        method(new Supplier<String>() {
            @Override
            public String get() {
                /*
                   1.我们要引用toUpperCase()方法,正好在重写的get方法中
                   2.重写的get方法,没有参数,有返回值,返回值类型为String
                   3.我们要引用的toUpperCase,没有参数,有返回值,返回值类型为String
                   4.结论:可以使用方法引用,引用toUpperCase()
                 */
                return "abcdef".toUpperCase();
            }
        });

        System.out.println("=========Lambda表达式========");
        method(()-> "abcdef".toUpperCase());

        System.out.println("=========方法引用========");
        method("abcdef"::toUpperCase);

        System.out.println("==========================================");

        method(new Supplier<String>() {

            /*
                   1.我们要引用substring()方法,正好在重写的get方法中
                   2.重写的get方法,没有参数,有返回值,返回值类型为String
                   3.我们要引用的substring,有参数,有返回值,返回值类型为String
                   4.结论:不可以使用方法引用引用subString方法
             */
            @Override
            public String get() {
                return "abcdefg".substring(0);
            }
        });

        //method("abcdefg"::substring);

    }

    public static void method(Supplier<String> supplier){
        String s = supplier.get();
        System.out.println(s);
    }
}

三.对象名--引用成员方法

1.使用对象名引用成员方法
  格式:
     对象::成员方法名
         
2.需求:
    函数式接口:Supplier
        java.util.function.Supplier<T>接口
    抽象方法:
        T get()用来获取一个泛型参数指定类型的对象数据
        Supplier接口使用什么泛型,就可以使用get方法获取一个什么类型的数据
public class Test02 {
    public static void main(String[] args) {
        method(new Supplier<String>() {
            /*
               1.要引用的trim()在重写的get方法中
               2.get()无参数,有String类型的返回值
               3.引用的trim()无参数,有String类型的返回值
               4.trim方法符合方法引用的条件
             */
            @Override
            public String get() {
                return " abcdefg ".trim();
            }
        });

        System.out.println("=========Lambda表达式==========");

        method(()->" abcd ".trim());

        System.out.println("==========方法引用===========");

        method(" abcd "::trim);
    }

    public static void method(Supplier<String> supplier){
        String s = supplier.get();
        System.out.println("s = " + s);
    }
}

四.类名--引用静态方法

类名--引用静态方法
    格式:
        类名::静态成员方法
public class Test03 {
    public static void main(String[] args) {
        method(new Supplier<Double>() {
            /*
              1.要引用的random()在重写的get方法中
              2.get方法无参数,有返回值,返回值类型为Double
              3.引用的random()方法,无参数,有返回值,返回值为Double
             */
            @Override
            public Double get() {
                return Math.random();
            }
        });

        System.out.println("===========Lambda=======");
        method(() -> Math.random());

        System.out.println("===========方法引用===========");
        method(Math::random);
    }

    public static void method(Supplier<Double> supplier){
        Double aDouble = supplier.get();
        System.out.println("integer = " + aDouble);
    }
}

五.类--构造引用

1. 类--构造方法引用
    格式:
        构造方法名称::new
2.需求:
    函数式接口:Function
        java.util.function.Function<T,R>接口
    抽象方法:
        R apply(T t),根据类型T的参数获取类型R的结果。用于数类型转换
public class Test04 {
    public static void main(String[] args) {
        method(new Function<String, Person>() {
            /*
              1.Person的有参构造在重写的apply方法中
              2.apply方法,带参数的,返回值类型为Person类型
              3.Person的有参构造,带参数,返回值类型Person类型
              4.可以做方法引用
             */
            @Override
            public Person apply(String s) {
                return new Person(s);//Person p = new Person(s)
            }
        },"涛哥");

        System.out.println("==========Lambda==========");

        method(s-> new Person(s),"涛哥");////Person p = new Person(s)
        System.out.println("=========方法引用===========");

        method(Person::new,"涛哥");
    }

    public  static void method(Function<String,Person> function,String name){
        Person person = function.apply(name);
        System.out.println(person.getName());
    }
}

六.数组--数组引用

数组--数组引用
        格式:
        数组的数据类型[]::new
        int[]::new 创建一个int类型的数组
        double[]::new 创建一个double类型的数组
public class Test05 {
    public static void main(String[] args) {
        method(new Function<Integer, int[]>() {
            /*
                1.引用的数组在重写的方法中
                2.apply有参数,返回值类型为int[]
                3.引用的数组,把长度看做是参数,类型是int[]
                4.结论:可以使用方法引用
             */
            @Override
            public int[] apply(Integer integer) {
                /*
                  [] 看做是方法的 ()
                  []中的长度看做是方法的参数
                 */
                return new int[integer];
            }
        },5);

        System.out.println("===============Lambda表达式==============");

        method(integer-> new int[integer],5);

        System.out.println("===============方法引用==============");

        method(int[]::new,5);
    }

    public static void method(Function<Integer,int[]> function,int length){
        int[] arr = function.apply(length);
        System.out.println(arr.length);
    }
}

第九章 Junit单元测试

1.Junit介绍

1.概述:Junit是一个Java语言的单元测试框架,简单理解为在一定程度上用于取代main方法,Junit属于第三方工具,需要导入jar包后再使用
2.作用:可以单独去运行一个我们想要测试的功能,不用提供main方法了
3.导jar包:
  a.在模块下创建文件夹:lib
  b.将要使用的第三方jar包,粘贴到lib下
  c.对着lib,右键->add as library->module level ->ok    

2.Junit的基本使用(重点)

1.注意:需要在方法上写,不要在类上写
       @Test
2.怎么执行单元测试的方法:
  a.选中要执行的方法名,右键->run
  b.选择方法左边的绿色小箭头->run
  c.如果想要执行当前类中所有带@Test的单元测试方法,点类名左边绿色小箭头->run
public class Test01_Junit {
    @Test
    public void add(){
        System.out.println("添加商品");
    }

    @Test
    public void delete(){
        System.out.println("删除商品");
    }
}

3.Junit的注意事项

1.@Test修饰的方法,不能有参数
2.@Test修饰的方法,不能有返回值
3.@Test修饰的方法,不能是静态的
    
一般情况下,我们都会单独定义一个方法,在此方法中使用单元测试,在方法体中调用要测试的方法

注意:使用单元测试,如果控制台是红色,证明测试的方法有问题,要是绿色的没问题

image-20220316092729088

4.Junit相关注解

@Before:@Test之前执行,有多少个@Test一起执行,@Before就会执行多少次->可以用初始化参数
@After:在@Test之后执行,有多少个@Test一起执行,@After就会执行多少次->可以用来关闭资源
public class Test02_Junit {
    @Test
    public void test01() {
        System.out.println("单元测试Test");
    }

    @Test
    public void test02() {
        System.out.println("单元测试Test");
    }

    @Before
    public void before01() {
        System.out.println("单元测试Before");
    }


    @After
    public void after01() {
        System.out.println("单元测试After");
    }
}
扩展注解:
 @BeforeClass:在@Test之前执行,用于修饰静态方法,只执行一次
 @AfterClass:在@Test之后执行,用于修饰静态方法,只执行一次
public class Test02_Junit {
    @Test
    public void test01() {
        System.out.println("单元测试Test");
    }

    @Test
    public void test02() {
        System.out.println("单元测试Test");
    }

    @BeforeClass
    public static void before01() {
        System.out.println("单元测试BeforeClass");
    }


    @AfterClass
    public static void after01() {
        System.out.println("单元测试AfterClass");
    }
}

第十章.类的加载时机

1.new对象的时候
2.new子类对象(new子类对象,先初始化父类对象)
3.执行main方法
4.调用静态成员
5.利用反射,反射这个类

image-20220316095556097

1.类加载器(了解)_ClassLoader

1.概述:
   在jvm中,负责将本地上的class文件加载到内存的对象_ClassLoader
2.分类:
   BootStrapClassLoader:根类加载器->C语言写的,我们是获取不到的
                        也称之为引导类加载器,负责Java的核心类加载的
                        比如:System,String等
                        jre/lib/rt.jar下的类都是核心类
   ExtClassLoader:扩展类加载器
                  负责jre的扩展目录中的jar包的加载
                  在jdk中jre的lib目录下的ext目录
   AppClassLoader:系统类加载器
                  负责在jvm启动时加载来自java命令的class文件(自定义类),以及classPath环境变量所指定的jar包(第三方jar包)
        
    不同的类加载器负责加载不同的类
       
3.三者的关系:AppClassLoader的父类加载器是ExtClassLoader
           ExtClassLoader的父类加载器是BootStrapClassLoader
 
  但是:他们从代码级别上来看,没有子父类继承关系->他们都有一个共同的父类->ClassLoader

4.获取类加载器对象
  类名.class.getClassLoader()
  
5.获取类加载器对象对应的父类加载器
  ClassLoader类中的方法:ClassLoader  	 
      
6.双亲委派(全盘负责委托机制)

   a.Person类中有一个String
     Person本身是AppClassLoader加载
     String是BootStrapClassLoader加载
   b.加载顺序:
     Person本身是App加载,按道理来说String也是App加载
     但是App加载String的时候,先问一问Ext,说:Ext你加载这个String吗?
     Ext说:我不加载,我负责加载的是扩展类,但是app你别着急,我问问我爹去->boot
     Ext说:boot,你加载String吗?
     boot说:正好我加载核心类,行吧,我加载吧!
         
   =======================================================================
         
    比如:
      class Test{
          new Person()
      }

    a.Test是app加载,person按理来说也是app加载,但是app先问ext要不要加载
      ext说不负责加载自定义类,我找boot去,boot一看,我不负责加载自定义类->perosn
      app一看,两个爹都不加载,我自己加载
      
    b.结论:两个双亲都不加载,app才自己加载
    
    
    比如:如果来了一个DNSNameService,我就想直接加载DNSNameService(扩展类),
         本身ext要加载,但是先问boot,如果boot不加载,ext再加载
    
    =======================================================================
7.类加载器的cache(缓存)机制(扩展):一个类加载到内存之后,缓存中也会保存一份儿,后面如果再使用此类,如果缓存中保存了这个类,就直接返回他,如果没有才加载这个类.下一次如果有其他类在使用的时候就不会重新加载了,直接去缓存中拿,所以这就是为什么每个类只加载一次,内存中只有一份儿的原因
     
8.所以:类加载器的双亲委派和缓存机制共同造就了加载类的特点:每个类只在内存中加载一次           
 public class Test01 {
     public static void main(String[] args) {
         app();
 ​
         //ext();
 ​
         //bootStrap();
     }
 ​
     /**
      * BootStrapClassLoader用于加载核心类  rt.jar
      *
      * BootStrapClassLoader是C语言写的,获取不到
      */
     private static void bootStrap() {
         ClassLoader classLoader = String.class.getClassLoader();
         System.out.println(classLoader);//null
     }
 ​
     /**
      * ExtClassLoader用于加载扩展类
      */
     private static void ext() {
         ClassLoader classLoader = DNSNameService.class.getClassLoader();
         System.out.println(classLoader);
     }
 ​
     /**
       AppClassLoader用于加载自定义类和第三方jar包
       类名.class.getClassLoader()
      */
     private static void app() {
         ClassLoader classLoader = Test01.class.getClassLoader();
         System.out.println(classLoader);
 ​
         ClassLoader parent1 = classLoader.getParent();
         System.out.println(parent1);
 ​
         ClassLoader parent2 = parent1.getParent();
         System.out.println(parent2);
     }
 }
 ​

第十一章.反射

1.class类的以及class对象的介绍以及反射介绍

image-20220316110351116

小结:

  1. 类加载过程

  2. 要知道什么是Class对象,什么是class类

    class对象:jvm会为加载到内存中的class文件在堆中创建一个对象,此对象为class对象

    class类:描述这个class对象的类就是class类

  3. 反射:

    解剖class对象

    我们要先获取class对象,才能解剖class对象

  4. 怎么解剖

    获取class对象中的成员变量,赋值

    获取class对象中的构造方法,new

    获取class对象中的成员方法,调用

2.反射之获取Class对象

1.使用Object类中的方法
  Class<?> getClass()  
    
2.在java中每个类型(基本类型,引用类型)java都为其提供了一个静态的属性:class
    
3.使用Class类中的静态方法:
  static Class<?> forName(String className) 
                         className:要获取的类的"全限定名"
                                   所谓的全限定名其实就是 包名.类名
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class Test01_GetClass {
    public static void main(String[] args)throws Exception {
        /*
          1.使用Object类中的方法
            Class<?> getClass()
         */
        Person person = new Person();
        Class aClass1 = person.getClass();
        System.out.println(aClass1);

        System.out.println("===========================");

        /*
          2.在java中每个类型(基本类型,引用类型)java都为其提供了一个静态的属性:class
         */
        Class aClass2 = Person.class;
        System.out.println(aClass2);

        System.out.println("===========================");

        /*
          3.使用Class类中的静态方法:
            static Class<?> forName(String className)
                                    className:要获取的类的"全限定名"
                                              所谓的全限定名其实就是 包名.类名
         */

        Class aClass3 = Class.forName("com.atguigu.c_reflect.Person");
        System.out.println(aClass3);

        System.out.println("===========================");

        System.out.println(aClass1==aClass2);//true
        System.out.println(aClass1==aClass3);//true
    }
}

image-20220316113126836

2.1.三种获取Class对象的方式最常用的一种

使用Class类中的静态方法:
  static Class<?> forName(String className) 
                         className:要获取的类的"全限定名"
                                   所谓的全限定名其实就是 包名.类名
                             
因为参数为String,可以结合properties文件动态获取Class对象           
className=com.atguigu.c_reflect.Person
public class Test02_GetClass {
    public static void main(String[] args)throws Exception {
        //创建Properties集合
        Properties properties = new Properties();
        //创建FileInputStream
        FileInputStream in = new FileInputStream("day23\pro.properties");
        //调用load方法,将流中的数据加载到properties集合中
        properties.load(in);
        //System.out.println(properties);

        String className = properties.getProperty("className");

        Class<?> aClass = Class.forName(className);
        System.out.println(aClass);
    }
}

3.获取Class对象中的构造方法

3.1.获取所有public的构造方法

Constructor<?>[] getConstructors()  -> 获取所有带public的构造方法
public class Test03_GetConstructor {
    public static void main(String[] args)throws Exception {
        //获取Personclass对象
        Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
        //获取所有public的构造方法
        Constructor<?>[] constructors = aClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
    }
}

3.2.获取空参构造_public

1.Constructor<T> getConstructor(Class<?>... parameterTypes) 
                               parameterTypes:可变参数,可以传递0或者多个参数
                               如果获取的是空参构造,parameterTypes不写
                               如果获取的是有参构造,parameterTypes要写,写的是参数类型的Class对象
                                   
2.Constructor类中的方法:创建对象
  T newInstance(Object... initargs)  
                initargs:可变参数,用来创建对象时为属性赋值
                如果根据无参构造创建对象,initargs不写
                如果根据有参构造创建对象,initargs传递具体的值
 public class Test04_GetConstructor {
     public static void main(String[] args)throws Exception {
         //获取Person的class对象
         Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
         Constructor<?> constructor = aClass.getConstructor();
         /*
           2.Constructor类中的方法:创建对象
             T newInstance(Object... initargs)
                           initargs:可变参数,用来创建对象时为属性赋值
                           如果根据无参构造创建对象,initargs不写
                           如果根据有参构造创建对象,initargs传递具体的值
          */
         Object o = constructor.newInstance();//相当于 Person p = new Person()
         System.out.println(o);//相当于直接输出p,默认调用toString方法
     }
 }

3.3.利用空参构造创建对象的快捷方式_public

 1.Class类中的方法
   T newInstance()-> 根据无参构造创建对象
 2.使用前提:被反射的类中必须有public的无参构造
 public class Test05_GetConstructor {
     public static void main(String[] args)throws Exception {
         //获取Person的class对象
         Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
         Object o = aClass.newInstance();//Person p = new Person()
         System.out.println(o);//直接输出p,默认调用toString方法
     }
 }

3.4.利用反射获取有参构造并创建对象_public

 1.Constructor<T> getConstructor(Class<?>... parameterTypes) 
                                parameterTypes:可变参数,可以传递0或者多个参数
                                如果获取的是空参构造,parameterTypes不写
                                如果获取的是有参构造,parameterTypes要写,写的是参数类型的Class对象
                                    
 2.Constructor类中的方法:创建对象
   T newInstance(Object... initargs)  
                 initargs:可变参数,用来创建对象时为属性赋值
                 如果根据无参构造创建对象,initargs不写
                 如果根据有参构造创建对象,initargs传递具体的值
 public class Test06_GetConstructor {
     public static void main(String[] args)throws Exception {
         //获取Person的class对象
         Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
         Constructor<?> constructor = aClass.getConstructor(String.class, int.class);
 ​
         Object o = constructor.newInstance("柳岩", 36);//Person p = new Person("柳岩",36)
         System.out.println(o);//相当于直接输出对象p.默认调用toString
     }
 }
 ​

3.5.利用反射获取私有构造(暴力反射)

Constructor<?>[] getDeclaredConstructors()  -> 获取所有构造,包括public,以及private
public class Test07_GetConstructor {
    public static void main(String[] args)throws Exception {
        //获取Person的class对象
        Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
        Constructor<?>[] dc = aClass.getDeclaredConstructors();
        for (Constructor<?> constructor : dc) {
            System.out.println(constructor);
        }
    }
}
1.Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) -> 获取有参构造,包括public,包括private
                                      parameterTypes:可变参数,可以传递0或者多个参数
                                      如果获取的是空参构造,parameterTypes不写
                                      如果获取的是有参构造,parameterTypes要写,写的是参数类型的Class对象
2.Constructor类中的方法:创建对象
  T newInstance(Object... initargs)  
                initargs:可变参数,用来创建对象时为属性赋值
                如果根据无参构造创建对象,initargs不写
                如果根据有参构造创建对象,initargs传递具体的值
                    
3.Constructor有一个父类:AccessibleObject
  AccessibleObject中有一个方法:
      void setAccessible(boolean flag)  
                         flag:true->解除私有权限->暴力反射
  public class Test07_GetConstructor {
    public static void main(String[] args)throws Exception {
        //获取Person的class对象
        Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
        Constructor<?> dc = aClass.getDeclaredConstructor(String.class);

        dc.setAccessible(true);

        Object o = dc.newInstance("柳岩");//Person p = new Person("柳岩")
        System.out.println(o);
    }
}

4.反射方法

4.1.利用反射获取所有成员方法

1.Method[] getMethods()  -> 获取所有public的成员方法->父类的也能获取出来
public class Test08_GetMethod {
    public static void main(String[] args)throws Exception {
        //获取Person的class对象
        Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

4.2.反射之获取方法(有参,无参)

1. Method getMethod(String name, Class<?>... parameterTypes) 
                    name:方法名
                    parameterTypes:可变参数,可以传递0或者多个参数
                                   如果获取的是空参成员方法,parameterTypes不写
                                   如果获取的是有参成员方法,parameterTypes要写,写的是参数类型的Class对象
2.Method类中的方法:
  Object invoke(Object obj, Object... args) -> 调用方法并执行 
                obj:传递对象-> 根据class中的构造创建的对象
                args:调用方法是传递的实参
                     如果调用的方法是无参的,args不写了
                     如果调用的方法是有参的,args就需要传递实参
                    
                Object:用于接收被调用方法的返回值
                       如果被调用的方法无返回值,Object不用接收了
  
public class Test09_GetMethod {
    public static void main(String[] args)throws Exception {
        //获取Person的class对象
        Class<?> aClass = Class.forName("com.atguigu.c_reflect.Person");
        //利用无参构造创建对象
        Object o = aClass.newInstance();//Person p = new Person()

        Method setName = aClass.getMethod("setName", String.class);
        //执行set方法
        setName.invoke(o, "柳岩");//p.setName("柳岩");
        System.out.println(o);//直接输出p.默认调用toString

        System.out.println("============================");

        Method getName = aClass.getMethod("getName");
        //执行getName
        Object o1 = getName.invoke(o);
        System.out.println(o1);
    }
}

5.反射练习(编写一个小框架)

<select id = "selectAll">
   select * from category
</select>
    
List<Category> selectAll()
利用反射,解析配置文件中的信息:properties文件
  类的全限定名:  className=包名.类名
  方法名:       methodName=方法名
  
需求:利用反射解析properties文件,获取配置信息
    根据配置信息,执行配置文件中的方法
  
步骤:
 1.创建Properties配置文件,写配置信息
   问题1:properties配置文件应该放到哪里
        a.如果将properties配置文件放到模块下, 那么当我们给用户out路径下的class文件时, out路径下就没有properties配置文件,也没有src路径,那么用户执行程序,程序所需要的配置文件中的数据就拿不到,用户那边也执行不了 
       
        b.所以我们一般都是将配置文件放到src下,那么out路径下就有这个配置文件了
       
   问题2:将配置文件放到src下,读取此文件的时候路径怎么写?
        FileInputStream in = new FileInputStream("模块名\src\文件名")
        如果源码中路径带了src了,由于给用户的out路径下没有src,那么文件到用户那边也读不到
       
   解决问题2: 使用类加载器读取配置文件
             当前类名.class.getClassLoader().getResourceAsStream("文件名")->自动扫描src下的配置文件
       
 2.解析配置文件
 3.根据配置文件中的类的全限定名,获取Class对象
 4.根据配置文件中的方法名获取对应的方法
 5.调用invoke执行
className=com.atguigu.c_reflect.Student
methodName=study
public class Test10_Exam {
    public static void main(String[] args)throws Exception {
       //1.创建Properties配置文件
        Properties properties = new Properties();
        //2.读取配置文件
        //FileInputStream in = new FileInputStream("day23\src\test.properties");
        /*
        ClassLoader classLoader = Test10_Exam.class.getClassLoader();
        InputStream in = classLoader.getResourceAsStream("test.properties");
         */

        InputStream in = Test10_Exam.class.getClassLoader().getResourceAsStream("test.properties");

        //3.调用load方法,将流中的数据加载到properties集合中
        properties.load(in);

        //4.获取集合中的value
        String className = properties.getProperty("className");//类的全限定名
        String methodName = properties.getProperty("methodName");//方法名

        //5.获取Class对象
        Class<?> aClass = Class.forName(className);
        Object o = aClass.newInstance();

        //6.根据获取出来的方法名获取方法
        Method method = aClass.getMethod(methodName);
        //7.执行方法
        method.invoke(o);
    }
}

第十二章.注解

1.注解的介绍

 1.jdk1.5版本的新特性->一个引用数据类型
       和类,接口,枚举是同一个层次的
 2.作用:
        说明:对代码进行说明,生成doc文档(API文档)(不会用)
        检查:检查代码是否符合条件   @Override(会用) @FunctionalInterface
        分析:对代码进行分析,起到了代替配置文件的作用(会用)
 3.JDK中的注解:
        @Override  ->  检测此方法是否为重写方法
           jdk1.5版本,支持父类的方法重写
           jdk1.6版本,支持接口的方法重写
        @Deprecated -> 方法已经过时,不推荐使用
                       调用方法的时候,方法上会有横线,但是能用
        @SuppressWarnings->消除警告  @SuppressWarnings("all")
public class Person {
    @Deprecated
    public void eat(){
        System.out.println("人要吃饭");
    }
}
@SuppressWarnings("all")
public class Test01 {
    public static void main(String[] args) {
        Person person = new Person();
        person.eat();

        ArrayList list = new ArrayList();
        list.add(1);
    }
}

2.注解的定义以及属性的定义格式

1.注解的定义格式:
  public @interface 注解名{}

2.定义属性:提高注解的作用
  数据类型 属性名() -> 没有默认值,需要在使用注解的时候为其赋值
  数据类型 属性名() default 值-> 属性有默认值,在使用注解的时候,无序给该属性赋值;如果需要重新赋值,那就赋呗

3.注解中能够定义什么类型的属性呢?
  a.8中基本类型(byte short int long float double boolean char)
  b.String类型,Class类型,枚举类型,注解类型
  c.以及以上类的一维数组
public @interface Book {
    //书名
    String name();
    //价格
    int price();
    //作者
    String[] author();
    //数量,有默认值
    int count() default 10;
}

3.注解的使用(重点)

1.所以的使用注解:为注解中的属性赋值
2.使用的位置:类上,方法上,成员变量上,局部变量上,参数上
3.如何使用注解:
  @注解名(属性名 = 属性值,属性名 = 属性值...)
4.如果属性是数组:
  @注解名(属性名 = {元素1,元素2...})
public @interface Book {
    //书名
    String name();
    //价格
    int price();
    //作者
    String[] author();
    //数量
    int count() default 10;
}
@Book(name = "金瓶梅",price = 9,author = {"涛哥","柳岩"},count = 100)
public class BookSelf {
}
注解注意事项:
      1.空注解可以直接使用->空注解就是注解中没有任何的属性
      2.不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解
      3.使用注解时,如果此注解中有属性,注解中的属性一定要赋值,如果有多个属性,用,隔开
        如果注解中的属性有数组,那么如果数组只有一个元素值,那么{}不用写,反之用写
      4.如果注解中的属性值有默认值,那么我们不必要写,也不用重新赋值,反之必须写上
      5.如果注解中只有一个属性,并且属性名叫value,那么使用注解的时候,属性名不用写,直接写值
        (包括单个类型,还包括数组)

4.注解解析的方法->AnnotatedElement接口

解析注解:获取注解中的属性值
    AnnotatedElement接口的实现类:Class Method Constructor等-> 解析注解离不开反射
    
    接口:AnnotatedElement->专门用于注解解析的
       boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 
        判断当前Class对象上是否有指定的注解,如果返回false,证明没注解,否则证明有注解
        参数annotationClass:注解的class对象
            
        比如:判断BookSelf上面有没有使用Book注解
            Class bookSelf = BookSelf.class;
            bookSelf.isAnnotationPresent(Book.class)
        ==========================================================================
         
        <T extends Annotation> T getAnnotation(Class<T> annotationClass) ->获取当前Class对象上的指定注解
         参数annotationClass:注解的class对象
       
         比如:
            Class bookSelf = BookSelf.class;
            boolean result = bookSelf.isAnnotationPresent(Book.class)
            如果bookSelf上使用了Book注解了,就获取
            Book book = bookSelf.getAnnotation(Book.class)
        =============================================================================

 注解的解析思想:

    解析类上的注解:
          1.反射带有注解的类,获取类的Class对象
          2.判断这类上有没有注解:isAnnotationPresent
          3.获取这个注解:getAnnotation
          4.获取注解中的属性值
          
    解析方法上的注解
          1.反射带有注解的类->Class对象
          2.获取方法:Method
          3.判断方法上有没有注解:method.isAnnotationPresent
          4.获取这个方法上的注解:method.getAnnotation
          5.获取注解中的属性值 
public @interface Book {
    //书名
    String name();
    //价格
    int price();
    //作者
    String[] author();
    //数量
    int count() default 10;
}

@Book(name = "金瓶梅",price = 9,author = {"涛哥","柳岩"},count = 100)
public class BookSelf {
}
 public class Test01 {
     public static void main(String[] args) {
         //1.获取BookSelf的Class对象
         Class<BookSelf> bookSelfClass = BookSelf.class;
         //2.判断BookSelf上面有没有注解
         boolean result = bookSelfClass.isAnnotationPresent(Book.class);
         System.out.println(result);//false
         //如果b是true,证明BookSelf上有Book注解,那么我们就获取这个注解
         if (result){
             //获取注解
             Book book = bookSelfClass.getAnnotation(Book.class);
             //获取注解中的属性值
             System.out.println(book.name());
             System.out.println(book.price());
             System.out.println(book.count());
             System.out.println(Arrays.toString(book.author()));
         }
 ​
     }
 }
 ​
 以上代码没有解析出来

涛哥猜想:如果Book注解在内存中出现了,我们是能判断出来的;但是BookSelf上明明用了Book注解了,但是就是判断不出来,也就证明Book注解有可能没有在内存中出现

第十三章.元注解

 1.概述:管理注解的注解
 2.元注解在哪些方面去管理注解呢?
   a.元注解可以控制自定义注解能写在什么位置上
   b.元注解可以控制自定义注解的生命周期
     元注解控制自定义注解是否能在源码中,是否能在class文件中,是否能在内存中
     
 3.常见的元注解:
   @Target-> 控制自定义注解可以写在什么位置上
    a.Target中的属性:ElementType[] value();
    b.ElementType是一个枚举,枚举中的属性都是静态的,我们可以类名直接调用
      TYPE:自定义注解可以用在类上
      FIELD:自定义注解可以用在成员变量上
      METHOD:自定义注解可以用在方法上
      PARAMETER:自定义注解可以用在参数上
      CONSTRUCTOR:自定义注解可以用在构造上
      LOCAL_VARIABLE:自定义注解可以用在局部变量上
          
   @Retention->元注解可以控制自定义注解的生命周期
    a.Retention属性: RetentionPolicy value();
    b.RetentionPolicy是一个枚举,枚举中的成员都是静态,可以类名直接调用
      SOURCE:自定义注解只能在源码中出现(默认)
      CLASS:自定义注解能在Class文件中
      RUNTIME:自定义注解能在内存中存在
 @Target({ElementType.METHOD,ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface Book {
     //书名
     String name();
     //价格
     int price();
     //作者
     String[] author();
     //数量
     int count() default 10;
 }
 ​

1.注解再次解析(成功了)

 public class Test01 {
     public static void main(String[] args) {
         //1.获取BookSelf的Class对象
         Class<BookSelf> bookSelfClass = BookSelf.class;
         //2.判断BookSelf上面有没有注解
         boolean result = bookSelfClass.isAnnotationPresent(Book.class);
         System.out.println(result);
         //如果b是true,证明BookSelf上有Book注解,那么我们就获取这个注解
         if (result){
             //获取注解
             Book book = bookSelfClass.getAnnotation(Book.class);
             //获取注解中的属性值
             System.out.println(book.name());
             System.out.println(book.price());
             System.out.println(book.count());
             System.out.println(Arrays.toString(book.author()));
         }
 ​
     }
 }
 ​
 public class Test02 {
     public static void main(String[] args) {
         //1.获取BookSelf的Class对象
         Class bookSelfClass = BookSelf.class;
         //2.判断BookSelf上面有没有注解
         boolean result = bookSelfClass.isAnnotationPresent(Book.class);
         System.out.println(result);
         //如果b是true,证明BookSelf上有Book注解,那么我们就获取这个注解
         if (result){
             //获取注解
             //Annotation annotation = bookSelfClass.getAnnotation(Book.class);
 ​
             /*
                强转
                
                注解的父接口是Annotation接口
              */
             Book book = (Book) bookSelfClass.getAnnotation(Book.class);
             System.out.println(book);
             //获取注解中的属性值
             System.out.println(book.name());
             System.out.println(book.price());
             System.out.println(book.count());
             System.out.println(Arrays.toString(book.author()));
         }
 ​
     }
 }

第一次解析由于我们自己的注解Book默认是SOURCE,只能在源码中,所以我们没有解析出来

第二次解析由于我们将SOURCE改成了RUNTIME,证明注解Book能在内存中出现了,所以我们就解析出来了