前言

  • Properties集合
  • Lambda表达式
  • 函数式接口
  • Stream流
  • Socker网络编程
  • 枚举类
  • XML文档
  • Junit单元测试
  • 类加载器简介
  • 类加载器分类
  • 类加载器加载.class文件到内存过程
  • 反射
  • 注解

 

Properties双列集合

Properties集合本质上是1个Map双列集合;

Properties中包含I/O相关的方法可以帮助我们读取和生成程序的配置文件;

一般情况下Properties集合中只会存储字符串;

1.setProperty和getProperty方法

package com.itheima.prop;

import java.util.Properties;
import java.util.Set;

public class PropertiesDemo3 {
    public static void main(String[] args) {
        //Properties集合特有方法==> 专门用于操作字符串的方法(方法的参数/返回值都是String类型)
        Properties prop = new Properties();
        //1.setProperty():存入一组字符串类型的键和值
        prop.setProperty("username", "root");
        prop.setProperty("password", "admin");
        //2.getProperty():通过字符串类型的键或者到String类型的值(键和值必须都是String类型,才能被获取到)
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        prop.put("age", 11);
        System.out.println("通过get()获取到的值:" + prop.get("age"));
        System.out.println("通过getProperty()获取到的值:" + prop.getProperty("age"));
        System.out.println(username + password);
        //3.tringPropertyNames():获取Properties中所有的键(键和值必须都是String类型,才能被获取到)
        Set<String> names = prop.stringPropertyNames();
        for (String name : names) {
            System.out.println(name);
        }


    }
}
设置获取键值

 

2.load和store方法

package com.itheima.prop;


import java.io.*;
import java.util.Properties;

public class PropertiesDemo4 {
    public static void main(String[] args) throws IOException {
        //新的文件格式:properties文件(文本文件【具备指定格式key=value】)
        Properties prop = new Properties();
        //public void load(InputSrream is):将输入流绑定的文件按照KEY=VALUE方式读取并保存在Properties集合中。
        prop.load(new FileReader(new File("chapter-13-stream\\user.properties")));
        //public void store(Writer writer, String comments):把文件的流持久化存储到文件中
        prop.store(new FileWriter(new File("chapter-13-stream\\newuser.properties")), "MySQL  Config");
    }
}
加载和另存储配置文件

 

Lambda表达式

Java 8之后引入了函数式编程思想;

当某成员方法的参数声明为函数式接口类型时

可以使用Lambda表达式直接重写函数式接口中的定义的这1个抽象方法,然后把Lambda表达式作为参数传递给该成员方法

无需再创建实现类,让Java代码更加简洁。

 

1.面向对象和函数式编程的区别

面向对象编程思想:关注怎么做? 由哪个对象去做? 对象怎么创建?哪个对象具体做什么? (对象)

函数式编程思想:   关注于在已经明确了对象和对象的方法之后 ,不需要创建对象, 直接关注实现逻辑。  (执行逻辑)

 

2.Lambda表达式的特点

能推导出来的都可以省略,可以在IDEA中使用万能键(Ctrl+Enter)直接把匿名内部类转换成Lambda表达式

public static void main(String[] args) {
        //3.通过匿名内部类实现1个接口实现类的对象,作为参数传递
        flying(new flyable() {
            @Override
            public void fly(String something) {
                System.out.println(something + "正在飞翔" + "通过匿名内部类fly方法的逻辑");
            }
        });

        //4.通过Lambda表达式实现fly()方法的逻辑,作为参数传递
        flying((String something) -> {
            System.out.println(something + "正在飞翔" + "通过Lambda实现fly方法的逻辑");
        });
    }


    //1.定义1个flyable接口
    interface flyable {
        //接口中提供1个抽象方法 fly
        void fly(String something);
    }

    //2.将wimming接口声明为成员方法的参数(多态:传递实参的时候需要传递 flyable接口的实现类对象)
    public static void flying(flyable somethingFlyable) {
        //参数已经传递
        somethingFlyable.fly("飞机");
    }
体验Lambda表达式

 

3.匿名内部类和Lambda表达式的区别

应用范围不同

  • 匿名内部类:可以应用于接口,也可以是抽象类,还可以是具体类
  • Lambda表达式:只能应用于成员方法的参数需要传递1个函数式接口接口中有且只1个抽象方法)的对象。

实现原理不同:

  • 匿名内部类:编译之后,产生1个单独的.class字节码文件
  • Lambda表达式:编译之后,不会产生.class字节码文件,对应的  字节码会在运行期动态生成。

 

 

函数式接口

函数式接口是指:Java中有且仅有1个抽象方法的接口,即适用于函数式编程场景的接口。

1.FunctionalInterface注解

与@override注解的作用类似,Java8在专门为函数式接口引入了1个新的注解 @FunctionalInterface。

    //1.定义1个flyable函数式接口
    @FunctionalInterface
    interface flyable {
        //接口中提供1个抽象方法 fly
        void fly(String something);
    }

 一旦使用@FunctionalInterface注解定义了接口,代码编译时,编译器将会强制检查该接口是否有且只有1个抽象方法,否则将会报错,程序无法编译成功。

 即便不使用该注解,接口满足函数式接口要求,依然属于函数式接口。

 

2.函数式接口的应用场景

为什么所有的单列集合都有1个forEach()方法?

如果给forEach的方法传递1个实现了Consumer接口accept(Object o)方法逻辑的实现类对象,就可以对单列集合进行遍历?

  ArrayList strList = new ArrayList<String>();
  Collections.addAll(strList, "您", "好", "师", "姐");
  strList.forEach(o -> System.out.println((String) o));

这就是Java提供的函数式接口应用场景之一。

函数式接口用于成员方法的参数声明,一旦成员方法的参数声明为函数式接口类型,那么在调用该成员方法时。

实参可以是1个实现了该函数式接口中抽象方法的匿名内部类对象,也可以是一个直接实现了该函数式接口中抽象方法的Lambda表达式。

Java在java.util.Function包中定义好可四大类函数式接口。

 

3.供给型接口  (Supplier)

Supplier<T>供给型函数式接口:接口中定义了1个get()抽象方法,用于生产1个指定类型数据;

可用于提供1个指定类型的数据,但是提供数据的具体逻辑需要开发者指定。

 

4.消费型接口(Consumer)

Consumer<T>消费性函数式接口:接口中定义了1个accept()抽象方法,用于消费1个指定类型的数据。

所有的单列集合都可以调用1个forEach方法,forEach方法声明了一个Consumer<T>消费性函数式接口作为该方法的参数

package com.itheima.functional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Supplier;

/*
1.upplier<T>供给型函数式接口:接口中定义了1个get()抽象方法,用于生产1个指定类型数据
可用于提供1个指定类型的数据,但是提供数据的具体逻辑需要开发者指定

2.Consumer<T>消费性函数式接口:接口中定义了1个accept()抽象方法,用于消费1个指定类型的数据。
所有的单列集合都可以调用1个forEach方法,forEach方法声明了一个Consumer<T>消费性函数式接口作为该方法的参数



   用于过滤数据
*/
public class FuncationalDemo1 {
    public static void main(String[] args) {
        //调用useSupplier方法,传入接口具体实现类对象(匿名内部类)
        useSupplier(new Supplier<Integer>() {

            //重写并实现Supplier接口的get()方法
            @Override
            public Integer get() {
                //随机返回1个符合Int类型的数字
                return new Random().nextInt(10) + 10;
            }
        });
        //调用useSupplier方法,传入具体逻辑(Lambda)
        useSupplier(() -> {
            //重写并实现Supplier接口的get()方法
            return new Random().nextInt(10);
        });

        System.out.println(" -------------------------------------------------------------------------------------------");
        //单列集合的forEach()就是消费性接口的应用之一
        ArrayList strList = new ArrayList<String>();
        Collections.addAll(strList, "您", "好", "师", "姐");
        strList.forEach(new Consumer() {
            @Override
            public void accept(Object o) {
                System.out.println((String) o);
            }
        });
        useConsumer(new Consumer<String>() {
            @Override
            public void accept(String str) {
                System.out.println("本次要消费的数据是:" + str.length());
                System.out.println("本次要消费的数据长度是:" + str.length());
                System.out.println("本次要消费的数据的第一个字符是:" + str.charAt(0));
            }
        });
        useConsumer(str -> {
            System.out.println("本次要消费的数据是:" + str);
            System.out.println("本次要消费的数据的长度是:" + str.length());
            System.out.println("本次要消费的数据的第一个字符是:" + str.charAt(0));
        });


    }


    //声明useSupplier方法:将Supplier<Integer>函数式接口实现类对象,作为方法的参数
    public static void useSupplier(Supplier<Integer> supplier) {
        //使用结果引用去调用get()方法来获取提供的数据
        Integer result = supplier.get();
        System.out.println("供给的Integer数据是" + result);
    }
    //声明useConsumer方法:将Consumer<String>函数式接口的实现类对象,作为方法的参数
    public static void useConsumer(Consumer<String> consumer) {
        consumer.accept("包子");
    }
}
供给和消费性接口

 

5.谓词型接口(Predicate):

Predicate<T>谓词型接口:接口中定义了1个test()抽象方法,判断完返回ture/false判断结果,决定是否保留?具体判断逻辑由开发者指定。常用于数据过滤。

package com.itheima.functional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Predicate;
/*
 *3.Predicate<T>谓词型接口:接口中定义了1个test()抽象方法,判断完返回ture/false判断结果,决定是否保留?具体判断逻辑由开发者指定
 * 常用于数据过滤
 *
 *功能型接口: 指定将1种数据类型转换为另一种数据类型
 * */

public class FunctionalDemo2 {

    public static void main(String[] args) {
        //判断通过Predicate的test()判断传入的Integer参数是否大于50。(匿名内部类)
        usePredicate(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer > 50;
            }
        });
        //判断通过Predicate的test()参数为1个谓词型接口的实现类对象
        usePredicate(integer -> integer > 50);

        //声明usePredicate函数,参数为1个谓词型接口的实现类对象!
        usePredicate(integer -> integer > 50);

        //单列集合中removeIf(Predicate)就是把谓词型接口的对象作为方法参数。
        // 批量单列集合中姓赵的元素
        ArrayList strList = new ArrayList<String>();
        Collections.addAll(strList, "张三丰", "张无忌", "赵敏", "赵四");
        strList.removeIf(new Predicate() {
            @Override
            public boolean test(Object o) {
                String name = (String) o;
                if (name.startsWith("张")) {
                    return false;
                }
                return true;

            }
        });
        //删除后的结果
        strList.forEach((name) -> {
            System.out.println(name);
        });

    }

    //声明usePredicate法:将Predicate<Integer>函数式接口实现类对象,作为方法的参数
    public static void usePredicate(Predicate<Integer> predicate) {
        System.out.println("对100的数据判断的结果是" + predicate.test(100));
    }
}
转换性接口

 

6.转换型接口(Function)

Function<T, R>功能型接口:接口中定义了1个apply()可以将1个T类型转换为R类型。

package com.itheima.functional;

import java.util.Objects;
import java.util.function.Function;

/*
 * Function<T, R>功能型接口:接口中定义了1个apply()可以将1个T类型转换为R类型。
 *转化型接口
 */
public class FunctionalDemo3 {
    private static class User {
        private String name;

        public User() {
        }

        public User(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

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


        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof User)) return false;
            User user = (User) o;
            return Objects.equals(getName(), user.getName());
        }

        @Override
        public int hashCode() {
            return Objects.hash(getName());
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        useFunction(integer -> {
            String sNumber = String.valueOf(integer);
            return sNumber + 99;
        });

        useFunction1(new Function<String, User>() {
            @Override
            public User apply(String s) {
                return new User(s);
            }
        });

    }

    //声明useFunction法:将Function<T, R>函数式接口的实现类对象,作为方法的参数
    //功能型接口:可以将1个T类型转换为R类型
    public static void useFunction(Function<Integer, String> function) {
        String result = function.apply(100);
        System.out.println("数字100转换为String字符串的结果是" + result);

    }

    //将字符串转换成User类型
    public static void useFunction1(Function<String, User> function) {
        User user = function.apply("Kelvin");
        System.out.println("字符串Kelvin转换为User的结果是" + user.getName());

    }

}
消费性接口

 

Stream流

java.util.stream.Stream本质是Java中自带的一个接口,Java中的单列和双列集合都已经实现了该接口,都转换成数据流;

Stream流就像Python的中map和filter函数,可以对单列和双列集合进行各种高效操作;

Stream流是Java8中一种以流水线思想操作处理集合数据的API,通过这种流水线思想操作集合可以使代码更加高效、 简洁。

我们可以把任意集合数据类型转换成Stream流,Stream流经过为中间方法、终结方法、静态方法的层层处理操作,最终变成我们想要的数据。

 

1.集合获取Stream流

Stream流的 4种生成方式。

        //1.单列集合的流生成方式:直接通过继承自 Collection的静态方法stream()来获取到流对象。
        ArrayList<String> strList = new ArrayList<>();
        Collections.addAll(strList, "晁盖", "宋江", "高俅");
        Stream<String> stream1 = strList.stream();
        System.out.println("ArrayList单列集合获取的流是" + stream1);

        //2.双列集合流的生成方式:不可以直接生成,间接,可以通过keySet()将所有键组成Set集合
        HashMap<String, Integer> hashMap = new HashMap<String, Integer>();
        Stream<String> stream2 = hashMap.keySet().stream();
        //可以将双列集合中的所有键值对组成的Set集合生成流
        Stream<Map.Entry<String, Integer>> stream = hashMap.entrySet().stream();

        //3.数组可以借助Arrays工具类生成流
        String[] strArray = new String[5];
        Stream<String> stream3 = Arrays.stream(strArray);

        //4.相同数据类型的零散数据可以通过Stream类的of()方法直接生成流
        Stream<Integer> stream4 = Stream.of(100, 200, 3333, 22020);
        System.out.println(stream4);

 

2.Stream流的方法分类

Stream流中方法分为:

中间方法:当调用完此方法之后,会返回一个保存剩余元素的流对象,可以基于返回的流对象继续调用其他方法。

终结方法:调用完终结方法之后流关闭。

静态方法:java.util.stream.Stream流接口中定义的静态方法,对完成处理之后的流对象进行保存。

 

 

3.filter()中间方法

publict Stream<E> filter( Predicate p):将流中每一个元素传递到接口的test()方法中进行筛选判断,返回true:保留该元素过 返回false:过滤掉该元素。

package com.itheima.stream;


import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.function.Predicate;

//中间方法filter()
public class StreamDemo3 {
    private static ArrayList<String> strList = new ArrayList<>();
    static{
        strList.add("张弢");
        strList.add("张弢");
        strList.add("张启樵");
        strList.add("张君宝");
        strList.add("张翠山");
        strList.add("张无忌");
        strList.add("赵四");
        strList.add("赵玉田");
        strList.add("赵敏");
    }
    public static void main(String[] args) {
            //终结方法(1):publict Stream<E> filter( Predicate p):将流中每一个元素传递到接口的test()方法中进行筛选判断
            //返回true:保留该元素过  返回false:过滤掉该元素

        //保留集合中所有姓张的名字
        strList.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String name) {
                return name.startsWith("张");
            }
        }).forEach(new Consumer<String>() {
            @Override
            public void accept(String name) {
                System.out.println(name);
            }
        });

    }
}
filter()中间方法

 

4.distinct()和sorted()中间方法

public Stream<T> distinct():依据Stream流中元素的equals()和hashCode(),过滤掉当前流中的重复元素

public Stream<T> sorted( Comparator< ? super E > c ):根据指定规则对Stream流中元素进行排序

package com.itheima.stream;

import com.itheima.entity.Student;

import java.util.ArrayList;
import java.util.Comparator;

/*
* stream对象的中间方法
* public Stream<T> distinct():依据Stream流中元素的equals()和hashCode(),过滤掉当前流中的重复元素
* public Stream<T> sorted( Comparator< ? super E > c ):根据指定规则对Stream流中元素进行排序
*
* */
public class StreamDemo4 {
    private static ArrayList<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张涵予", 28, 100));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张予曦", 22, 87));
        studentList.add(new Student("张无忌", 25, 68));
        studentList.add(new Student("赵四", 53, 99));
        studentList.add(new Student("赵玉田", 26, 92));
        studentList.add(new Student("王钢蛋", 19, 60));
    }

    //.stream()的中间方法
    public static void main(String[] args) {
        studentList.stream()
                .distinct()
                .sorted(new Comparator<Student>() {
                    @Override
                    public int compare(Student o1, Student o2) {
                        return o1.getScore()-o2.getScore();
                    }
                })
                .forEach(student -> System.out.println(student));
    }


}
distinct和sorted中间方法

 

5.map()中间方法

public Stream<T> map( Function<E,R> function):可以将流中的元素按照指定的转换方式转换为指定类型的元素并保存到流中返回。

package com.itheima.stream;

import com.itheima.entity.Student;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Function;

/*
* stream对象的中间方法
* public Stream<T> map( Function<E,R> function):
* 可以将流中的元素按照指定的转换方式转换为指定类型的元素并保存到流中返回。
* */
public class StreamDemo5 {
    private static ArrayList<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张涵予", 28, 100));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张予曦", 22, 87));
        studentList.add(new Student("张无忌", 25, 68));
        studentList.add(new Student("赵四", 53, 99));
        studentList.add(new Student("赵玉田", 26, 92));
        studentList.add(new Student("王钢蛋", 19, 60));
    }

    //.stream()的中间方法map
    //例:将Student类型转换成String类型
    public static void main(String[] args) {
        studentList.stream().map(new Function<Student,String>() {
            @Override
            public String apply(Student student) {
                return student.getName();
            }
        }).forEach(s -> {
            System.out.println(s);
        });


}}
map中间方法

 

6.limit()和skip()中间方法

public Stream<T> limit(long maxSize);获取流中的前maxSize个元素

public Stream<T> skip(long n); 跳过流中的前n个元素

package com.itheima.stream;

import com.itheima.entity.Student;

import java.util.ArrayList;
/**
 * public Stream<T> limit(long maxSize);获取流中的前maxSize个元素
 *public  Stream<T> skip(long n);      跳过流中的前n个元素
 *
 */
public class StreamDemo6 {
    private static ArrayList<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张涵予", 28, 100));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张予曦", 22, 87));
        studentList.add(new Student("张无忌", 25, 68));
        studentList.add(new Student("赵四", 53, 99));
        studentList.add(new Student("赵玉田", 26, 92));
        studentList.add(new Student("王钢蛋", 19, 60));
    }

    public static void main(String[] args) {
        studentList.stream()
      //获取第一名
       .distinct().sorted((o1, o2) -> o1.getScore()-o2.getScore()).limit(3).skip(2).forEach(s -> System.out.println(s));
        ;
    }
}
limit和skip中间方法

 

7.count()和forEach()终结方法

public long count(); 计算流中元素的个数

package com.itheima.stream;

import com.itheima.entity.Student;

import java.util.ArrayList;

/**
 *  终结方法
 *public long count(); 计算流中元素的个数
 *
 */
public class StreamDemo7 {
    private static ArrayList<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张涵予", 28, 100));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张予曦", 22, 87));
        studentList.add(new Student("张无忌", 25, 68));
        studentList.add(new Student("赵四", 53, 99));
        studentList.add(new Student("赵玉田", 26, 92));
        studentList.add(new Student("王钢蛋", 19, 60));
    }

    public static void main(String[] args) {
        studentList.stream().count();
    }
}
count终结方法

publict void foreache (Consumer<T> consumer):按照指定的消费逻辑,对流中每个元素进行消费。

package com.itheima.stream;


import java.util.ArrayList;

//终结方法forEach
public class StreamDemo2 {
    private static ArrayList<String> strList = new ArrayList<>();
    static{
        strList.add("张弢");
        strList.add("张弢");
        strList.add("张启樵");
        strList.add("张君宝");
        strList.add("张翠山");
        strList.add("张无忌");
        strList.add("赵四");
        strList.add("赵玉田");
        strList.add("赵敏");
    }
    public static void main(String[] args) {
            //终结方法(1):publict void foreache (Consumer<T>  consumer):按照指定的消费逻辑,对流中每个元素进行消费。
        strList.stream().forEach(name -> System.out.println(name));

    }
}
forEach终结方法

 

8.collect()终结方法

public R collect(Collector collector):将流中剩余的元素收集到一个新的单列集合中。

package com.itheima.stream;

import com.itheima.entity.Student;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 终结方法:
     *public R collect(Collector collector):将流中剩余的元素收集到一个新的单列集合中
 *
 */
public class StreamDemo8 {
    private static ArrayList<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张涵予", 28, 100));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张予曦", 22, 87));
        studentList.add(new Student("张无忌", 25, 68));
        studentList.add(new Student("赵四", 53, 99));
        studentList.add(new Student("赵玉田", 26, 92));
        studentList.add(new Student("王钢蛋", 19, 60));
    }

    public static void main(String[] args) {
        //将流中剩余的元素收集到一个新的List 集合中
        List<Student> newStudentList = studentList.stream().distinct().collect(Collectors.toList());
        System.out.println(newStudentList);
        //将流中剩余的元素收集到一个新的Set集合中
        Set<Student> newStudentSet = newStudentList.stream().distinct().collect(Collectors.toSet());
        System.out.println(newStudentSet);

    }
}
collect终结方法

public R collect(Collector collector):将流中剩余的元素收集到一个新的双列集合中

package com.itheima.stream;

import com.itheima.entity.Student;

import java.util.ArrayList;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 终结方法:
 *public R collect(Collector collector):将流中剩余的元素收集到一个新的双列集合中
 *
 */
public class StreamDemo9 {
    private static ArrayList<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张涵予", 28, 100));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张予曦", 22, 87));
        studentList.add(new Student("张无忌", 25, 68));
        studentList.add(new Student("赵四", 53, 99));
        studentList.add(new Student("赵玉田", 26, 92));
        studentList.add(new Student("王钢蛋", 19, 60));
    }

    public static void main(String[] args) {
        //1.将流中剩余的元素收集到一个新的双列集合中 (匿名内部类)
        studentList.stream().distinct().collect(
                //Key
                Collectors.toMap(
                        new Function<Student, String>() {
                            @Override
                            public String apply(Student student) {
                                return student.getName();
                            }
                        },
                        //Value
                        new Function<Student, Integer>() {
                            @Override
                            public Integer apply(Student student) {
                                return student.getAge();
                            }
                        }
        ));

        //2.将流中剩余的元素收集到一个新的双列集合中(Lambada表达式)
        Map<String, Integer> studentMap = studentList.stream().filter(student -> student.getName().startsWith("张")).distinct()
                .collect(Collectors.toMap(student -> student.getName(), student -> student.getAge()));
        System.out.println(studentMap);
    }
}
collect收集到hashMap容器中

 

9.concat静态方法

public Stream<E> concat(Stream<E> sOne,Stream<E> sTwo) : 将多个流合并为一个流并返回.

package com.itheima.stream;

import com.itheima.entity.Student;

import java.util.ArrayList;
import java.util.stream.Stream;

/**
 * 静态方法: java.util.stream.Stream流接口中定义的静态方法
 *
 */
public class StreamDemo10 {
    private static ArrayList<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张涵予", 28, 100));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张含韵", 24, 78));
        studentList.add(new Student("张予曦", 22, 87));
        studentList.add(new Student("张无忌", 25, 68));
        studentList.add(new Student("赵四", 53, 99));
        studentList.add(new Student("赵玉田", 26, 92));
        studentList.add(new Student("王钢蛋", 19, 60));
    }

    public static void main(String[] args) {
        //当我们拥有了多个流,想将多个流合并为1个流的时候,可以进行Stream流的拼接。
        ArrayList<Student> studentList1=new ArrayList();
        studentList1.add(new Student("曹操",56,100));
        studentList1.add(new Student("孙权",83,100));
        studentList1.add(new Student("诸葛亮",53,120));

        //Stream接口中的静态方法:public Stream<E> concat(Stream<E> sOne,Stream<E> sTwo) : 将多个流合并为一个流并返回.
        //Stream.concat(streamOne, streamTwo).forEach(student -> System.out.println(student));
        Stream<Student> concatedStream = Stream.concat(studentList.stream(), studentList1.stream());
        concatedStream.forEach(student -> System.out.println("拼接之后的流元素"+student.getName()));

    }
}
concat静态方法

 

10.总结Stream流中间/终结方法、函数式编程接口、Lambda表达式的关系

Stream流的处理方法是Java中函数式编程思想的最终体现。

Stream流的中间/终结方法都需要1个函数式编程接口的实现类对象作为参数。

  //2.将流中剩余的元素收集到一个新的双列集合中(Lambada表达式)
        Map<String, Integer> studentMap = studentList.stream()
.filter(student -> student.getName().startsWith("张"))
          .distinct() .collect(Collectors.toMap(student
-> student.getName(), student -> student.getAge()));

Lambda表达式可以直接重写函数式编程接口中定义那1个抽象方法

Lambda表达式可以直接取代1个函数式编程接口的实现类对象

Lambda表达式可以直接作为作为Strem流对象中间或终结方法的实参传递

 

Socket网络编程

Socket是对Java对网络协议TCP/UDP协议实现和封装;

 

1.Java中Socket有以下特点

Socket对象的输入流(SocketInputStream)的特点一:当read()方法无法从通道中read()到数据时就会一直阻塞;

Socket对象的输入流(SocketInputStream)的特点二:通道中永远不可能读到特殊值,和文件输入流不一样,不会返回长度为-1的字节数组/null 这种特殊值。

当客户端Socket和服务器Socket有交互关系时,当一端的Socket关闭,对端的Socket也会跟着关闭。

---------------------------------

package com.itehei.demo6;

import java.io.*;
import java.net.Socket;

//改造为客户端上传文件成功之后,服务端响应文件上传成功!信息。
//改造为使用uuid命名图片
public class Client {
    public static void main(String[] args) throws IOException {
        // (1)创建客户端Socket对象请求连接服务器端.
        Socket client = new Socket("127.0.0.1", 8001);
        //(2)创建一个字节输入流绑定要上传的文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("chapter-14-socket\\xifu.jpg")));
        // (3)获取到客户端的输出流,用于将读取到的文件字节数组写入到通道中,上传到服务器端.
        BufferedOutputStream bos = new BufferedOutputStream(client.getOutputStream());
        //(4)定义一个byte类型的数组,用于保存每次读取到的文件字节信息,定义一个int类型数据,用于保存每次读取到的有效字节数.
        int len;
        byte[] bytes = new byte[1024];
        //(5)通过while循环进行读取,并且读取到的内容写出.
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
            bos.flush();
        }
        //★:关闭掉客户端的输出流(向服务器端传递信息,停止阻塞读取,继续向下执行)
        client.shutdownOutput();
        //(6)获取到客户端的输入流(包装为字符缓冲输入流),去读取服务器端回写的文本数据.
        BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
        String info = br.readLine();
        System.out.println("接收到服务器端响应信息:" + info);
        //释放资源
        bis.close(); //流和Socket没有关系,所以要单独关闭.
        client.close();
    }
}
客户端

---------------------------------

package com.itehei.demo6;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.UUID;

//改造为客户端上传文件成功之后,服务端响应文件上传成功!信息。
//改造为使用uuid做图片名称
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8001);
        Socket currentClient = serverSocket.accept();
        String fileName = UUID.randomUUID().toString().replace("-", "") + ".jpg";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(fileName)));

        int len;
        byte[] bytes = new byte[1024];

        BufferedInputStream bis = new BufferedInputStream(currentClient.getInputStream());
        //(6)循环读取通道中的内容,将读取到的内容通过字节输出流写入到新文件中.
        //socket拥有不会得到 -1长度的字节,socket read()不到数据只会一只阻塞。
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
            bos.flush();
            System.out.println(len);
        }
        //当while循环结束的时候,代表文件已经上传成功,获取服务器的输出流,向客户端诙谐数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(currentClient.getOutputStream()));
        bw.write(new Date() + " 文件上传成功!");
        bw.newLine();
        bw.flush();
        //(8)释放资源
        bos.close();
        currentClient.close();
        serverSocket.close();


    }
}
服务器端

---------------------------------

 

2.禁用套接字的输出流

套接字的shutdownOutput()可以停止当前套接字的输出流。

当套接字调用了shutdownOutput()方法之后,任何先前写入的数据将会被立即发送,随后发送TCP的正常连接终止序列。

      //★:客户端关闭掉客户端的输出流(向服务器端传递信息,停止阻塞读取,继续向下执行)
        client.shutdownOutput();

  

枚举类

使用枚举类,可以限制该类可以实例化出哪些对象?就像单选框中的options的数量是有限的,且1次只能选1个。

例如交通灯类只能创建 红灯/绿灯/黄灯这3种对象。

枚举项的本质就是枚举类的1个全局唯一的对象

正常类的对象在类外面进行实例化,而枚举类的对象声明在枚举类中,并在枚举类内中进行实例化

 

1.枚举类的特点

        //所有枚举类的枚举项(对象)全局唯一,可用于单列模式。
        //所有枚举类都继承自 java.lang.Enum类
        Enum redLight= TrafficLight.RED;

通过枚举类的枚举项(对象)可以保证对象全局唯一,可用于单列模式

所有枚举类都继承自 java.lang.Enum父类。

 

2.定义枚举类

 在定义枚举项通过英文逗号(,)分割,通过英文分号(;)结束

package com.itheima.枚举定义;

public enum CrownPrince {
    ////1.创建当前枚举类拥有的对象,多个对象之间用","隔开,末尾对象";"结束
    PRINCE1("李建成", 32),
    PRINCE2("李世民", 26);

    //2.声明对象(PRINCE)的属性:private final修饰
    private final String princeName;
    private final Integer princeAge;

    //3.私化类的构造器,并给对象属性赋值
    CrownPrince(String princeName, Integer princeAge) {
        this.princeName = princeName;
        this.princeAge = princeAge;
    }

    public String getPrinceName() {
        return princeName;
    }

    public Integer getPrinceAge() {
        return princeAge;
    }

    //定义1个显示方法
    public void show() {
        System.out.println("当前太子人选是:" + this.getPrinceName());
    }
}
声明枚举1个类

 

3.使用枚举类

switch语句支持对枚举类型进行case判断。

package com.itheima.枚举;

import com.itheima.entity.Prince;
import com.itheima.枚举定义.CrownPrince;
import com.itheima.枚举定义.TrafficLight;

public class EnumTest2 {
    public static void main(String[] args) {
        TrafficLight.RED.show();
        TrafficLight.YELLOW.show();
        TrafficLight.GREEN.show();

        CrownPrince.PRINCE1.show();
        CrownPrince.PRINCE2.show();

        showLight(TrafficLight.RED);
        showPrince(CrownPrince.PRINCE1);
        showPrince(CrownPrince.PRINCE2);

    }


    public static void showPrince(CrownPrince prince) {
        switch (prince) {
            case PRINCE1:
                System.out.println("追封息王");
                break;
            case PRINCE2:
                System.out.println("年号唐太宗");
                break;
        }

    }

    public static void showLight(TrafficLight trafficLight) {
        //使用switch 对枚举进行判断(直接将枚举对象作为switch语句的参数即可)
        switch (trafficLight) {
            case RED:
                System.out.println("红灯停");
                break;
            case GREEN:
                System.out.println("绿灯行");
                break;
            case YELLOW:
                System.out.println("黄灯亮了等一等");
                break;
        }
    }
}
使用枚举类型

 

XML文档

XML:可扩展标记语言(Extensible Markup Language)

XML:是一种标记语言,类似于HTML,XML中的标签都是自定义的

W3C在1998年2月发布1.0版本,2004年2月又发布1.1版本,但因1.1版本无法向下兼容1.0版本,目前XML1.0版本的使用比较普遍

标记语言不是编程语言,因为标记语言只能存储数据,不能有逻辑判断。

 

1.XML和HTML的主要差异?

XML的标签是自定义的,HTML的标签是预定义的。

XML的语法要求严格,HTML的语法要求松散。

XML用于存储数据,HTML用于展示数据

 

2.定义XML文档

<?xml version="1.0" encoding="utf-8"?>
<cars>
    <car id="A001">
        <brand>别克</brand>
        <name>昂科旗</name>
        <seatcount>7</seatcount>
        <time-to-market>2022</time-to-market>
        <engine>
            <maxpower>280</maxpower>
            <displacement>3.0</displacement>
            <horsepower>292</horsepower>
        </engine>
    </car>
    <car id="A001">
        <brand>别克</brand>
        <name>昂科威SGS豪华</name>
        <seatcount>5</seatcount>
        <time-to-market>2022</time-to-market>
        <engine>
            <maxpower>183</maxpower>
            <displacement>2.5</displacement>
            <horsepower>192</horsepower>
        </engine>
    </car>
</cars>
car.xml

 

3.解析XML文档

解析XML文档需要使用第三方jar包dom4j,解析过程为剥洋葱思想。

package com.itheima.xml解析;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.List;

//解析XML文档就是剥洋葱思想
public class ParseXmlDemo1 {
    public static void main(String[] args) throws DocumentException {
        //1.创建SaxReader对象,用于读取XML文件,将读取到的XML文档封装为1个Document对象并返回
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(new File("chapter-15-xml/car.xml"));
        //2.获取rootelemen(car标签)
        Element rootElement = document.getRootElement();
        //3.获取跟标签下所有car标签们
        List<Element> carElementList = rootElement.elements("car");
        //4.遍历carElementList获取到每一个car元素
        for (Element carElement : carElementList) {
            //获取car元素中的brand标签
            Element brand = carElement.element("brand");
            System.out.println(brand.getText());
            //获取car元素中的name标签
            Element name = carElement.element("name");
            System.out.println(name.getText());
        }


    }
}
解析XML文档

 

Junit单元测试

有时候,我们直接无法直接调用框架(spring boot)的main()方法,那么怎么来测试我们在框架基础之上写的代码呢?

Junit是Java中一个进行单元测试工具Junit可以使成员方法不依赖于main()方法单独运行,并返回执行结果。

 

1.测试的分类

测试分成黑盒测试和白盒测试

软件测试的目的:软件开发的目的是交付给用户一个正确、稳定的项目,为了保证项目的稳定性,我们就需要对代码进行测试之后再上线进而交付给用户;

测试的分类:测试分为黑盒测试和白盒测试,其中黑盒测试仅仅在软件功能层面对我们的代码进行测试(不关注代码逻辑),而白盒测试需要写代码,对项目代码进行测试。

 

2.Junit使用流程

Junit使用步骤如下:

  • (1).导入Junit的jar包到项目中并添加为依赖
  • (2).在当前项目录下创建test文件夹设置为测试源码文件夹
  • (3).在test目录下创建相关包和测试类(见名知意)明确测试单元
  • (4).在测试类中定义一个成员方法中,该方法没有参数、没有返回值。=> 如果成员方法声明了参数和返回值,测试方法会无法运行
  • (5).在测试方法上添加@Test注解。 =>单独运行(运行成功:绿色 运行失败:红色)
  • (6).@After注解:@After标记的方法可以在测试方法执行完成后自动执行一次,@Before注解:被@Before标记的方法可以在测试方法之前行自动执行一次
package com.itheima.test.junit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.math.BigDecimal;

public class JunitDemo1 {
    /*
     * 不要上来就肌肉记忆psvm,在此处声明1个 public static void main(String[] args)方法。
     * 将测试代码编写在一个成员方法中,该方法没有参数、没有返回值。=> 如果成员方法声明了参数和返回值,测试方法会无法运行。
     * 在单独的运行方法上加 @Test注解,方法左侧出现一个运行按钮!
     * */
    @Test
    public void plus() {
        //模拟两个数字相加
        int a = 10, b = 20;
        System.out.println("a+b的结果是:" + (a + b));
    }

    @Test
    public void divide() {
        //模拟两个BigDecimal的数字相除: 保留3位小数,四舍五入。
        BigDecimal result1 = new BigDecimal("10").divide(new BigDecimal("4"), 3, BigDecimal.ROUND_HALF_UP);
        System.out.println(result1);
    }

    /*
    @Before注解:被@Before标记的方法可以在测试方法之前行自动执行一次.
    一般会做一些资源初始化的工作
    */
    @Before
    public void before() {
        System.out.println("开始测试之前执行");
    }

    /*
    @After注解:@After标记的方法可以在测试方法执行完成后自动执行一次.
    一般会做一些释放资源的工作
    */
    @After
    public void after() {
        System.out.println("测试完成后执行");
    }


}
Junit单元测试

 

类加载器简介

程序员编写的java源码文件,编译之后变成.class(字节码)文件,由类加载器通 过I/O输入流加载.class(字节码)物理文件到内存中的方法区。

 

1.类加载器每次都加载哪些类的字节码文件

我们不能每次运行1个类的.class字节码文件,就把JDK中所有的类和自定义的所有类,全部加载到内存中。

类加载的原则是按需加载,当前程序执行需要用到哪个类,就加载哪个类相关的字节码文件,与该类无关类的字节码文件不加载,并不会全部加载。

 

2.类加载器的执行时机

那么什么时机类加载器才会加载JDK中预定义的.class字节码,和我们自定义类的.class文件到内存中呢?

类加载时机如下:

  1. 创建某个类的对象的时候
  2. 通过类直接访问类的静态方法的时候
  3. 访问类/接口中定义的静态成员变量
  4. 通过反射强制加载某个类的.class文件到虚拟机中
  5. 初始化某个类的子类(由于子类初始化前,需要先完成父类的初始化)
  6. .直接通过 java命令运行某个类的.class文件时(本质就是运行这个类的main()方法)
//1.类加载时机:源码类的.class文件被加载到虚拟机中的时机
//核心思想用到那个类就加载用不到哪个类就不加载,不能全部加载。
public class ClassLoaderDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //(1)创建某个类的对象的时候
        Animal animal = new Animal();//Animal.class就会被加载到虚拟机内存中
        //(2)通过类直接访问类的静态方法的时候
        int maxNumber = Math.max(20, 10);
        System.out.println(maxNumber); //Math.class被加载到虚拟机内存中
        //(3)访问类/接口中定义的静态成员变量
        System.out.println(Math.PI);//Math.class被加载到虚拟机内存中
        //(4)通过反射强制加载某个类的.class文件到虚拟机中
        Class.forName("com.itheima.entity.Dog");//Dog.class文件被加载到虚拟机内存中
        //(5)初始化某个类的子类(由于子类初始化前,需要先完成父类的初始化)
        Dog dog = new Dog();  //Dog.class和nimal.class文件都会被加载到虚拟机内存中
        //(6)直接通过 java命令运行某个类的.class文件时(本质就是运行这个类的main()方法)

        //核心思想用到那个类就加载用不到哪个类就不加载,不能全部加载。
    }
}
类加载器执行时机

 

类加载器分类

以上我们明确了类加载的加载内容为:JDK源码+JDK使用者自定义的代码的.Class文件。

类加载器默认分为3类,每一类类加载器各司其职,负责加载不同的.Class文件。

每一类加载器之间没有任何物理层面的继承关系,但在逻辑上有层级关系。

触发了类加载器的执行时机之后,类加载器之间会通过双亲委派机制,选出1个最终干活的类加载器去加载对应的.Class文件。

  • 启动类加载器: 虚拟机内置的类加载器加载%JAVA_HOME/lib目录下的jar包。
  • 平台类加载器 :负责加载%JAVA_HOME/lib/ext目录下的jar包。
  • 系统类加载器 负责加载用户路径(src)下开发者编写的类,这些类不属于JDK核心类库。
package com.itheima.initialization;
//类加载器的成员方法:getParent()获取上级类加载器
public class ClassLoaderDemo2 {
    public static void main(String[] args) {
        //1.通过ClassLoader类的静态方法getSystemClassLoader可以获取到当前系统类加载器SystemClassLoader
        ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader(); //系统类加载器
        System.out.println("获取到的系统类加载器是"+systemClassLoader);
        ClassLoader platformClassLoader= systemClassLoader.getParent(); //平台类加载器
        System.out.println("获取到平台类加载器是"+platformClassLoader);
        ClassLoader botstarapClassLoader=platformClassLoader.getParent();//为保证启动类加载器的安全,使用代码无法获取启动类加载器
        System.out.println("获取到启动类加载器是null"+botstarapClassLoader);


    }
}
类加载器分类

 

1.双亲委派机制

 

 

2.双亲委派的作用:

  • 让每一个类加载器都去加载归属于自己的内容,避免类的重复加载

  • 保护程序的安全性,防止核心API随意被篡改沙箱安全机制:

 

3.沙箱安全机制

沙箱安全机制保证对Java信息源代码的保护,何为沙箱安全机制?

如果我们自己建立和源代码相同的包,例如java/lang/String.Class,

在我们去使用类加载器去加载此类时,为了防止你自定义的类对源码的破坏,默认不会使用你自定义的String类的本身的系统加载器去加载它,而是选择率先使用引导类加载器去加载。

而引导类在加载的过程中会先去加载JDK自带的文件(rt.jar包中的java/lang/String.class),而不是你自己定义的String.class,报错信息会提示没有main方法 ,

就是因为加载的是rt.jar包下的String类,这样就可以做到保证对java核心源代码的保护,这即是沙箱保护机制。

 

4.类加载器的成员方法

getParent()

可以获取上级类加载器

package com.itheima.initialization;
//类加载器的成员方法:getParent()获取上级类加载器
public class ClassLoaderDemo2 {
    public static void main(String[] args) {
        //1.通过ClassLoader类的静态方法getSystemClassLoader可以获取到当前系统类加载器SystemClassLoader
        ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader(); //系统类加载器
        System.out.println("获取到的系统类加载器是"+systemClassLoader);
        ClassLoader platformClassLoader= systemClassLoader.getParent(); //平台类加载器
        System.out.println("获取到平台类加载器是"+platformClassLoader);
        ClassLoader botstarapClassLoader=platformClassLoader.getParent();//为保证启动类加载器的安全,使用代码无法获取启动类加载器
        System.out.println("获取到启动类加载器是null"+botstarapClassLoader);


    }
}
View Code

getResourceAsStream(String)

类加载器不仅可以把硬盘上1个字节码文件加载到内存中,也可以把硬盘上其他类型的文件读取到内存中并返回1个缓冲字节输入流对象

package com.itheima.initialization;


import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

//类加载器的成员方法:.getResourceAsStream(String)获取1个【绑定了指定文件名称的【缓冲字节输入流对象】】
public class ClassLoaderDemo3 {
    public static void main(String[] args) throws IOException {
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        //注意:系统类加载器(AppClassLoader)加载范围是src目录下,读取文件(user.properties)的路径也必须在src目录下。
        //getResourceAsStream()获取1个缓冲字节输入流对象
        InputStream bis = systemClassLoader.getResourceAsStream("user.properties");
        Properties properties = new Properties();
        properties.load(bis);
        System.out.println("加载的内容" + properties);


    }
}
View Code

 

类加载器加载.class文件到内存的过程

以上我们通过双亲委派机制选择出了1个类加载器。

那么当选择好了1个类加载器, 这个类加载器加载.class文件到内存的详细过程是什么呢?

 

1.大概过程

类加载器加载字节码文件到内存有3大过程:加载、链接(验证、准备、解析)、初始化

以上过程主要做了3件大事

  1. 生成了1个封装了该类成员变量、成员方法、静态方法、构造方法的.class对象(反射的源头)
  2. 当前类关联的父类一并加载到内存
  3. 类的静态变量完成初始化、静态代码的执行一次。

 

2.详细过程

以下将对类加载器加载字节码文件到内存的3大过程加载、链接(验证、准备、解析)、初始化进行详细讲解。

2.1.加载流程

通过1个类的全限定名(包名+类名)来获取此类二进制字节流。

将这个字节流所代表的静态存储结构转换为运行时数据结构。

通过读取该类得字节码文件在内存中生成1个对应的.Class对象,.Class对象抽象表示该字节码文件,字节码中成员变量、成员方法、静态方法、构造方法都会封装保存到.class对象中,这个.Class对象就是反射的核心。

 

2.2.验证流程-链接

该环节负责验证Class文件字节流中是否包含危害虚拟机自身安全的信息.

 

 

 

 

2.3.准备流程-链接

该环节负责为类静态变量分配内存并设置默认初始化值

 

2.4.解析流程-链接

该环节负责在安全的前提下,把当前类中使用到的其他类型都一并加载到内存中。

 

2.5.初始化

静态变量赋值以及静态代码块的执行1次,当前非静态内容还没有完成,静态内容无法访问非静态内容,因为静态内容执行的时候非静态内容还没有完成初始化。

 

反射

在现实世界中镜子可以发射出1个人元数据信息即体貌。

在Java的世界中所有Java代码( 类、类的对象、 对象的属性、成员方法、注解等)信息可以通过类加载器被反射出来,封装成1个叫.class对象的元数据信息。

在类加器加载1个Java类的.Class字节码文件到内存的过程中,加载流程会生成1个.Class对象,封装了关于这个Java类的所有的元数据信息。

这些元数据信息描述了这个Java类的注解、全限定名、父类是谁? 注解有哪些?成员变量有哪些、成员方法有哪些、静态方法有哪些、构造方法有哪些等等。

 

1.反射的本质

当1个类的字节码文件被类加载器加载到内存之后,获取到这个类对应的.class对象。

通过这个.class对象的API完成该类的实例化、属性赋值和方法调用。

 

2.class对象的3种获取方式

通过反射的本质得出反射的核心是操作1个.class对象,那么我们如何获取这个.class对象呢?

通过以下三种可以在类的任一生命周期,获取到与当前类对应的.class对象。

  1. 如果当.class文件还是物理文件并未加载到内存中的时候,可以通过Class.forName("全限定名")的方式强制加载.class文件到内存中,生成对应的class对象。
  2. 如果类已经被类加载器加载到内存中了,可以通过类名.class获取到对应的class对象
  3. 如果类已经加载完成并已经创建了对象,通过类名对象.getClass获取到对应的class对象
/*
*class对象的3种获取方式:
* (1)如果当.class文件还是物理文件并未加载到内存中的时候,可以通过Class.forName("全限定名")的方式强制加载.class文件到内存中,生成对应的class对象。
* (2)如果类已经被类加载器加载到内存中了,可以通过类名.class获取到对应的class对象
* (3)如果类已经加载完成并已经创建了对象,通过类名对象.getClass获取到对应的class对象
* */
public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //(1)通过Class.forName("全限定名")可以获取到Class对象
        Class<?> personClass1 = Class.forName("com.itheima.entity.Person");
        System.out.println("获取到Person类的Class对象是" + personClass1);
        //(2)通过类名.class获取到对应的class对象
        Class<Person> personClass2 = Person.class;
        System.out.println("获取到Person类的Class对象是" + personClass2);
        //(3)通过类名对象.getClass()获取到对应的class对象
        Person person1 = new Person("张跟", 26,"男");
        Class<? extends Person>personClass3 = person1.getClass();
        System.out.println("获取到Person类的Class对象是" + personClass3);


    }
}
获取.class对象的3种方式

 

注解(Annotation)

反射的核心是操作1个.class对象;

这1个.class对象中不仅封装了这1个Java类的全限定名、父类是谁? 成员变量有哪些、成员方法有哪些、静态方法有哪些、构造方法有哪些,等元数据信息。

从Java 5开始.class对象封装的元数据信息中添加了Java类中定义的注解信息。

注解的内部不会去执行任何代码逻辑,我们只是通过注解给源码做1个标注;

程序运行期间或者编译期间,通过反射读取注解,决定程序执行逻辑;

 

1.什么是注解

注释是给开发人员看的,并不影响程序的执行;

注解是给程序看的,会影响程序的编译和程序的运行

Java的JDK提供好的注解,称为源注解;

 

2.常见注解

1.@Target元注解

JDK提供的源注解,指定注解的针对的地方

@Target注解有1个ElementType属性,该注解可以标注在类的哪一个属性

ElementType.Type:针对类、接口

ElementType.FIELD:针对类的属性

ElementType.METHOD:针对成员方法

ElementType.PARAMETER:针对方法的参数

ElementType.CONSTRUCTOR:针对类的构造方法

ElementType.PACKAGE:针对包

ElementType.ANNOTATION_TYPE:针对注解的注解

2.@Retention元注解

JDK提供的源注解,用于指定该注解的生命周期;

@Retention有1个RetentionPolicy属性

RetentionPolicy.SOURCE:该注解信息保留到源代码级别,由编译器处理,处理之后就不再保留

RetentionPolicy.CLASS:该注解信息保留到类对应的.CLASS文件中

RetentionPolicy.RUNTIME:该注解保留到类.CLASS文件中, 由JVM读取,运行时使用

 

3.注解使用流程

1.定义注解(@interface)

/使用@interface语法定义注解
public @interface MyAnnotation {
    public int age() default 10;//基本数据类型 int

    public double heigh();//基本数据类型:double

    public String name();//引用数据类型

    public Class superClass();//引用数据类型Class对象

    public Test test(); //将注解名称作为数据类型

    public Gender gender();//引用数据类型:枚举对象

    public String[] hobies();//引用数据类型: String数组
}
定义注解

2.使用注解

在类/字段/属性/方法上标注注解

public class AnnotationDemo1 {
    //在成员属性上使用注解
    @MyAnnotation(
            age = 23, heigh = 1.80, name = "Martin",
            superClass = AnnotationDemo1.class, test = @Test,
            gender = Gender.FEMALE, hobies = {"抽烟", "喝酒", "烫头"}
            )
    public String name;
}
使用注解

3.利用注解

程序运行期间利用JDK提供的反射API,获取到.class对象(代码的元数据)进而获取到该代码的注解信息;

根据代码标注的注解信息执行不同的逻辑

注解是框架的灵魂,Spring启动之后读取到配置类的@ComponentScan注解的Value属性(包名),锁定要扫描的包;

package com.zhanggen;

import com.zhanggen.config.SpringConfig;
import com.zhanggen.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
//使用配置类替代配置文件
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void Test1() {
        accountService.save(1);
        System.out.println("===========================>");
        accountService.findAll();
        System.out.println("===========================>");
        accountService.findById(1);
    }
}

Spring自动去该包下,把标注了@Component或@Service或@Repository或者@Controller的类进行实例化,自动放到Spring容器中,产生Bean对象;

package com.zhanggen.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;


//注解扫描
@ComponentScan("com.zhanggen")
//激活切面自动代理
@EnableAspectJAutoProxy
public class SpringConfig {
}

开发人员通过@Autowired获取Bean对象进行使用;

 

 

参考

posted on 2021-11-28 09:40  Martin8866  阅读(114)  评论(0编辑  收藏  举报