Java基础查漏补缺

面向对象高级一

权限修饰符

修饰符 在本类中 同一个包下的其他类里 任意包下的子类里 任意包下的任意类里
private
缺省
protected
public

说明: protected修饰的方法可以在任意包下的子类里访问, 但是这个子类对应的实例是不能访问的.

方法重写规则

  1. 使用@Override注解可以帮忙检查是否书写错误
  2. 重写父类方法, 子类方法访问权限大于等于父类该方法权限(public>protected>缺省)
  3. 重写方法返回值, 必须与被重写方法的返回值类型一样, 或者范围更小
  4. 私有方法, 静态方法不能被重写

个人理解这些规则的目的是为了兼容性和安全性.

子类构造器

子类的全部构造器, 都会默认先调用父类的无参构造器(super()), 再执行自己. 如果父类没有无参构造器, 必须在子类构造器第一行手写super(…) 调用父类的有参构造器.

集合框架

总结:
  1. 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?

    用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)

  2. 如果希望记住元素的添加顺序,且增删首尾数据的情况较多?

    用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。

  3. 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?

    用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)

  4. 如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?

    用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。

  5. 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?

    用TreeSet集合,基于红黑树实现。

Collection接口下有list和set接口, list有序 可重复 有索引, set无序,不重复,无索引.

Collection接口

特有方法

  • add(): 添加元素
  • clear(): 清除集合的元素
  • isEmpty(): 判断集合是否为空
  • Size(): 获取集合的大小
  • contains(Object obj): 判断是否包含
  • remove(Object obj): 删除元素,有多个删除第一个
  • toArray(): 将集合转为数组
  • addAll(Collection): 将一个集合的全部数据倒入到另一个集合中去
 1		Collection<String> c = new ArrayList<>(); // 多态写法
 2        // 1.public boolean add(E e):添加元素, 添加成功返回true。
 3        c.add("java1");
 4        c.add("java1");
 5        c.add("java2");
 6        c.add("java2");
 7        c.add("java3");
 8        System.out.println(c);
 9
10        // 2.public void clear():清空集合的元素。
11        //c.clear();
12        //System.out.println(c);
13
14        // 3.public boolean isEmpty():判断集合是否为空 是空返回true,反之。
15        System.out.println(c.isEmpty()); // false
16
17        // 4.public int size():获取集合的大小。
18        System.out.println(c.size());
19
20        // 5.public boolean contains(Object obj):判断集合中是否包含某个元素。
21        System.out.println(c.contains("java1")); // true
22        System.out.println(c.contains("Java1")); // false
23
24        // 6.public boolean remove(E e):删除某个元素:如果有多个重复元素默认删除前面的第一个!
25        System.out.println(c.remove("java1"));
26        System.out.println(c);
27
28        // 7.public Object[] toArray():把集合转换成数组
29		//泛型的原因在运行时会擦除, 不能确保集合里都是字符串, 所以是Object数组.
30        Object[] arr = c.toArray();
31        System.out.println(Arrays.toString(arr));
32
33		//这种方式必须确保集合里面都是字符串
34        String[] arr2 = c.toArray(new String[c.size()]); 
35        System.out.println(Arrays.toString(arr2));
36
37        System.out.println("--------------------------------------------");
38        // 把一个集合的全部数据倒入到另一个集合中去。
39        Collection<String> c1 = new ArrayList<>();
40        c1.add("java1");
41        c1.add("java2");
42        Collection<String> c2 = new ArrayList<>();
43        c2.add("java3");
44        c2.add("java4");
45        c1.addAll(c2); // 就是把c2集合的全部数据倒入到c1集合中去。
46        System.out.println(c1);
47        System.out.println(c2);

遍历

1.使用迭代器, iterator()方法返回一个迭代器

迭代器方法:

  • boolean hasNext() 询问当前位置是否有元素存在
  • E next() 获取当前位置的元素, 并同时将迭代器对象指向下一个元素处.
 1		Collection<String> c = new ArrayList<>();
 2        c.add("赵敏");
 3        c.add("小昭");
 4        c.add("素素");
 5        // c.add("灭绝");
 6        System.out.println(c);
 7        // c = [赵敏, 小昭, 素素]
 8        //                       it
 9
10        // 使用迭代器遍历集合
11        // 1、从集合对象中获取迭代器对象。
12        Iterator<String> it = c.iterator();
13//        System.out.println(it.next());
14//        System.out.println(it.next());
15//        System.out.println(it.next());
16//        System.out.println(it.next());
17        // System.out.println(it.next()); // 出现异常的
18
19        // 2、我们应该使用循环结合迭代器遍历集合。
20        while (it.hasNext()){
21            String ele = it.next();
22            System.out.println(ele);
23
24        }
2.使用for循环, 本质是迭代器遍历集合. 也可以用来遍历数组
 1		Collection<String> c = new ArrayList<>();
 2        c.add("赵敏");
 3        c.add("小昭");
 4        c.add("素素");
 5        c.add("灭绝");
 6        System.out.println(c);
 7        // c = [赵敏, 小昭, 素素, 灭绝]
 8        //                      ele
 9
10        // 使用增强for遍历集合或者数组。
11        for (String ele : c) {
12            System.out.println(ele);
13        }
14
15        String[] names = {"迪丽热巴", "古力娜扎", "稀奇哈哈"};
16        for (String name : names) {
17            System.out.println(name);
18        }
3.Lambda表达式遍历
 1		Collection<String> c = new ArrayList<>();
 2        c.add("赵敏");
 3        c.add("小昭");
 4        c.add("殷素素");
 5        c.add("周芷若");
 6        System.out.println(c);
 7        // [赵敏, 小昭, 殷素素, 周芷若]
 8        //                     s
 9
10        // default void forEach(Consumer<? super T> action):  结合Lambda表达式遍历集合:
11        //forEach是Iterable的方法, Collection是Iterable的子接口.
12        c.forEach(new Consumer<String>() {
13            @Override
14            public void accept(String s) {
15                System.out.println(s);
16            }
17        });
18
19        //@FunctionalInterface函数式注解, 可以将匿名内部类替换为Lambda表达式.
20        c.forEach((String s) -> {
21                System.out.println(s);
22        });
23
24        c.forEach(s  -> {
25            System.out.println(s);
26        });
27
28        //只有一行可以去掉大括号
29        c.forEach(s  -> System.out.println(s) );
30
31        //函数引用, 前后参数一样的情况下可以用方法引用. System.out这个对象调用println这个方法.
32        c.forEach(System.out::println );

List接口

特有方法

  • add(int index, E element): 指定位置插入元素, 如果位置已有元素也不会覆盖, 就是中间插, 别的挤旁边了.
  • E remove(int index): 删除指定位置的元素, 返回被删除的值
  • E set(int index, E element): 修改指定索引处的元素, 返回被修改的元素
  • E get(int index): 返回指定索引处的元素
 1		// 1.创建一个ArrayList集合对象(有序、可重复、有索引)
 2        List<String> list = new ArrayList<>();  // 一行经典代码
 3        list.add("蜘蛛精");
 4        list.add("至尊宝");
 5        list.add("至尊宝");
 6        list.add("牛夫人");
 7        System.out.println(list); // [蜘蛛精, 至尊宝, 至尊宝, 牛夫人]
 8
 9        // 2.public void add(int index, E element): 在某个索引位置插入元素。
10        list.add(2, "紫霞仙子");
11        System.out.println(list);
12
13        // 3.public E remove(int index): 根据索引删除元素,返回被删除元素
14        System.out.println(list.remove(2));
15        System.out.println(list);
16
17        // 4.public E get(int index): 返回集合中指定位置的元素。
18        System.out.println(list.get(3));
19
20        // 5.public E set(int index, E element): 修改索引位置处的元素,修改成功后,会返回原来的数据
21        System.out.println(list.set(3, "牛魔王"));
22        System.out.println(list);

遍历方式

1.Collection支持的它也支持
2.for循环遍历(索引遍历)
 1		List<String> list = new ArrayList<>();
 2        list.add("糖宝宝");
 3        list.add("蜘蛛精");
 4        list.add("至尊宝");
 5
 6        //(1)for循环
 7        for (int i = 0; i < list.size(); i++) {
 8            // i = 0 1 2
 9            String s = list.get(i);
10            System.out.println(s);
11        }
12
13        //(2)迭代器。
14        Iterator<String> it = list.iterator();
15        while (it.hasNext()) {
16            System.out.println(it.next());
17        }
18
19        //(3)增强for循环(foreach遍历)
20        for (String s : list) {
21            System.out.println(s);
22        }
23
24        //(4)JDK 1.8开始之后的Lambda表达式
25        list.forEach(s -> {
26            System.out.println(s);
27        });

ArrayList的底层原理

特点: 基于数组实现
  • 查询速度快(是根据索引查询数据快): 查询数据通过地址值和索引定位, 查询任意数据耗时相同
  • 删除效率低: 可能需要把后面很多的数据进行前移
  • 添加效率极低:可能需要把后面很多数据后移, 或者需要对数组扩容
底层原理
  1. 利用无参构造创建ArrayList时, 底层创建一个长度为0的数组
  2. 添加第一个元素时, 底层创建一个新的长度为10的数组
  3. 存满时, 扩容至1.5倍
  4. 如果一次添加多个数据, 同时即使扩容1.5被也放不下, 那么新创建数组的长度就以刚刚装满为准.
实用场景

适合: 查询数据较多, 或者数据量不是很大时

不适合: 数据量大的同时又需要频繁进行增删操作.

LinkedList底层原理

特点: 基于双链表实现

查询慢, 增删相对较快. 对首尾元素进行增删改查的速度是极快的.

新增方法
  • addFirst(E e): 开头插入元素, push一样
  • addLast(E e): 追加元素至末尾, pop一样
  • E get First(): 返回第一个元素
  • E get Last(): 返回最后一个元素
  • E removeFirst(): 从列表中删除并返回第一个元素
  • E removeLast(): 从列表中删除并返回最后一个元素
应用场景

设计队列:

 1		// 1、创建一个队列。
 2        LinkedList<String> queue = new LinkedList<>();
 3        // 入队
 4        queue.addLast("第1号人");
 5        queue.addLast("第2号人");
 6        queue.addLast("第3号人");
 7        queue.addLast("第4号人");
 8        System.out.println(queue);
 9        // 出队
10        System.out.println(queue.removeFirst());
11        System.out.println(queue.removeFirst());
12        System.out.println(queue.removeFirst());
13        System.out.println(queue);
14        System.out.println("--------------------------------------------------");

设计栈:

 1        // 2、创建一个栈对象。
 2        LinkedList<String> stack = new LinkedList<>();
 3        // 压栈(push)
 4        stack.push("第1颗子弹");
 5        stack.push("第2颗子弹");
 6        stack.push("第3颗子弹");
 7        stack.push("第4颗子弹");
 8        System.out.println(stack);
 9        // 出栈(pop)
10        System.out.println(stack.pop());
11        System.out.println(stack.pop());
12        System.out.println(stack);

Set接口

set下的实现类增删改查都快.

  • HashSet: 无序, 不重复, 无索引
  • LinkedHashSet: 有序(按添加顺序), 不重复, 无索引
  • TreeSet: 排序(默认升序), 不重复, 无索引

set几乎没有新增的功能, 都是用Collection的.

 1		// 1、创建一个Set集合的对象
 2        //Set<Integer> set = new HashSet<>(); // 创建了一个HashSet的集合对象 一行经典代码  HashSet: 无序 不重复 无索引
 3        Set<Integer> set = new LinkedHashSet<>(); //  有序 不重复 无索引
 4//         Set<Integer> set = new TreeSet<>(); //  可排序(升序) 不重复 无索引
 5        set.add(666);
 6        set.add(555);
 7        set.add(555);
 8        set.add(888);
 9        set.add(888);
10        set.add(777);
11        set.add(777);
12        System.out.println(set);

HashSet底层原理

Java中每个对象都有个哈希值, 使用hashCode方法返回一个哈希值, 范围是-21亿多到+21亿多. 不同的对象哈希值有可能会相同(哈希碰撞)

HashSet是基于哈希表实现的, 哈希表是一种增删改查性能都较好的数据结构

在Jdk8之前哈希表=数组+链表; 在Jdk8开始, 哈希表=数组+链表+红黑树.

jdk8之前:

  1. 创建一个默认长度16的数组, 默认加载因子0.75, 数组名table
  2. 使用元素的哈希值对数组长度求余, 计算存入位置
  3. 如果该位置为null, 直接存入
  4. 如果该位置不为null, 调用equals方法比较是否相等, 不相等则存入数组(jdk8之前新元素存入数组老元素挂下面; jdk8开始新元素挂老元素下面)
  5. 当占满了16(数组长度)*0.75(加载因子)个位置, 数组就开始扩容, 扩大约两倍, 防止链表过长影响查询效率.

jdk8开始, 当链表长度超过8, 同时数组长度>=64时, 自动将链表转成红黑树.

需了解的树结构
  • 二叉树: 度小于等于2的数
  • 二叉搜索(查找)树: 小的放左边, 大的放右边, 一样的不放. 可能会出现变成链表的情况.
  • 平衡二叉树: 满足查找二叉树规则情况下, 使这棵树尽量变矮.
  • 红黑树: 可以自平衡的二叉树
HashSet去重复

要让HashSet将内容一样的两个对象认为是重复的去重的话, 需要重写hashCode和equals方法.

 1	// 只要两个对象内容一样就返回true
 2    @Override
 3    public boolean equals(Object o) {
 4        if (this == o) return true;
 5        if (o == null || getClass() != o.getClass()) return false;
 6        Student student = (Student) o;
 7        return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
 8    }
 9
10    // 只要两个对象内容一样,返回的哈希值就是一样的。
11    @Override
12    public int hashCode() {
13        // 姓名 年龄 身高计算哈希值的
14        return Objects.hash(name, age, height);
15    }

LinkedHashSet底层原理

基于哈希表(数组, 链表, 红黑树)实现. 每个元素额外多了一个双链表机制记录它前后元素的位置. 所以可以有序. 但额外占内存

TresSet

底层是基于红黑树实现的排序

  • 对于数值, 按数值本身大小排序
  • 对于字符串, 按首字母编号升序排序
  • 对于自定义类型, 默认无法直接排序
自定义排序规则

方式一: 自定义类实现Comparable接口, 重写compareTo方法

1	@Override
2    public int compareTo(Student o) {
3        // 如果认为左边对象大于右边对象返回正整数
4        // 如果认为左边对象小于右边对象返回负整数
5        // 如果认为左边对象等于右边对象返回0
6        // 需求:按照年龄升序排序、
7        return this.age - o.age;
8    }

方式二: 调用TreeSet有参构造器, 设置Comparator对象

(Double.compare比较小数, 返回整数.)

 1		Set<Integer> set1 = new TreeSet<>();
 2        set1.add(6);
 3        set1.add(5);
 4        set1.add(5);
 5        set1.add(7);
 6        System.out.println(set1);
 7
 8        // TreeSet就近选择自己自带的比较器对象进行排序
 9//        Set<Student> students = new TreeSet<>(new Comparator<Student>() {
10//            @Override
11//            public int compare(Student o1, Student o2) {
12//                // 需求:按照身高升序排序
13//                return Double.compare(o1.getHeight() , o2.getHeight());
14//            }
15//        });
16        Set<Student> students = new TreeSet<>(( o1,  o2) ->  Double.compare(o1.getHeight() , o2.getHeight()));
17        students.add(new Student("蜘蛛精",23, 169.7));
18        students.add(new Student("紫霞",22, 169.8));
19        students.add(new Student("至尊宝",26, 165.5));
20        students.add(new Student("牛魔王",22, 183.5));
21        System.out.println(students);

注意

  1. 这里既规定了大小, 也规定了相等, 而相等是不存放的.
  2. 如果两者都规定了, TreeSet就近选择自己自带的比较器对象进行排序
  3. 默认是升序, 如果想要降序只需要将返回值相应地设反即可(大就返回负数).

集合并发修改异常问题

使用集合的remove方法删除的是第一个元素, 这导致出现错位的问题.

解决方案: 1. 使用迭代器自带的删除方法

​ 2. for循环倒着删除, 或者每次删除后下标减1.

 1public static void main(String[] args) {
 2        List<String> list = new ArrayList<>();
 3        list.add("王麻子");
 4        list.add("小李子");
 5        list.add("李爱花");
 6        list.add("张全蛋");
 7        list.add("晓李");
 8        list.add("李玉刚");
 9        System.out.println(list);
10        //  [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
11
12        // 需求:找出集合中全部带“李”的名字,并从集合中删除。
13//        Iterator<String> it = list.iterator();
14//        while (it.hasNext()){
15//            String name = it.next();
16//            if(name.contains("李")){
17//                list.remove(name);
18//            }
19//        }
20//        System.out.println(list);
21
22        // 使用for循环遍历集合并删除集合中带李字的名字
23        //  [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
24        //  [王麻子, 李爱花, 张全蛋, 李玉刚]
25        //                                i
26//        for (int i = 0; i < list.size(); i++) {
27//            String name = list.get(i);
28//            if(name.contains("李")){
29//                list.remove(name);
30//            }
31//        }
32//        System.out.println(list);
33
34        System.out.println("---------------------------------------------------------");
35        // 怎么解决呢?
36        // 使用for循环遍历集合并删除集合中带李字的名字
37        //  [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
38        //  [王麻子, 张全蛋]
39        //                  i
40        for (int i = 0; i < list.size(); i++) {
41            String name = list.get(i);
42            if(name.contains("李")){
43                list.remove(name);
44                i--;
45            }
46        }
47        System.out.println(list);
48        // 倒着去删除也是可以的。
49
50        // 需求:找出集合中全部带“李”的名字,并从集合中删除。
51        Iterator<String> it = list.iterator();
52        while (it.hasNext()){
53            String name = it.next();
54            if(name.contains("李")){
55                // list.remove(name); // 并发修改异常的错误。
56                it.remove(); // 删除迭代器当前遍历到的数据,每删除一个数据后,相当于也在底层做了i--
57            }
58        }
59        System.out.println(list);
60
61        // 使用增强for循环遍历集合并删除数据,没有办法解决bug.
62//        for (String name : list) {
63//            if(name.contains("李")){
64//                list.remove(name);
65//            }
66//        }
67//        System.out.println(list);
68
69//        list.forEach(name -> {
70//            if(name.contains("李")){
71//                list.remove(name);
72//            }
73//        });
74//        System.out.println(list);
75    }

Collections工具类

可变参数, 可以不传也可以传多个(type… var), 在方法内部就是一个数组, 支持数组的方法.

关于<? super T>:理解Java泛型的复杂写法<? super T>,<? extend T> , 大概<? super T>代指T及T的父类; <? extend T>指T及T的子类.

  • 1public static <T> boolean addAll(Collection<? super T> c, T...elements):为集合批量添加数据
    
  • 1public static void shuffle(List<?> list):打乱List集合中的元素顺序。
    
  • 1public static <T> void sort(List<T> list):对List集合中的元素进行升序排序。
    
  • 1public static <T> void sort(List<T> list, Comparator<? super T> c): 对List集合中元素,按照比较器对象指定的规则进行排序
    
 1		// 1、public static <T> boolean addAll(Collection<? super T> c, T...elements):为集合批量添加数据
 2        List<String> names = new ArrayList<>();
 3        Collections.addAll(names, "张三", "王五", "李四", "张麻子");
 4        System.out.println(names);
 5
 6        // 2、public static void shuffle(List<?> list):打乱List集合中的元素顺序。
 7        Collections.shuffle(names);
 8        System.out.println(names);
 9
10        // 3、 public static <T> void sort(List<T> list):对List集合中的元素进行升序排序。
11        List<Integer> list = new ArrayList<>();
12        list.add(3);
13        list.add(5);
14        list.add(2);
15        Collections.sort(list);
16        System.out.println(list);
17
18        List<Student> students = new ArrayList<>();
19        students.add(new Student("蜘蛛精",23, 169.7));
20        students.add(new Student("紫霞",22, 169.8));
21        students.add(new Student("紫霞",22, 169.8));
22        students.add(new Student("至尊宝",26, 165.5));
23        // Collections.sort(students);
24        // System.out.println(students);
25
26        // 4、public static <T> void sort(List<T> list, Comparator<? super T> c): 对List集合中元素,按照比较器对象指定的规则进行排序
27        Collections.sort(students, new Comparator<Student>() {
28            @Override
29            public int compare(Student o1, Student o2) {
30                return Double.compare(o1.getHeight(), o2.getHeight());
31            }
32        });
33        System.out.println(students);

Map接口

特点(都是由键决定的, 对值没有要求):

  • HashMap: 无序, 不重复, 无索引
  • LinkedHashMap: 有序, 不重复, 无索引
  • TreeMap: 按照大小默认升序, 不重复, 无索引
 1	public static void main(String[] args) {
 2        // Map<String, Integer> map = new HashMap<>(); // 一行经典代码。 按照键 无序,不重复,无索引。
 3        Map<String, Integer> map = new LinkedHashMap<>(); // 有序,不重复,无索引。
 4        map.put("手表", 100);
 5        map.put("手表", 220); // 后面重复的数据会覆盖前面的数据(键)
 6        map.put("手机", 2);
 7        map.put("Java", 2);
 8        map.put(null, null);
 9        System.out.println(map);
10
11        Map<Integer, String> map1 = new TreeMap<>(); // 可排序,不重复,无索引
12        map1.put(23, "Java");
13        map1.put(23, "MySQL");
14        map1.put(19, "李四");
15        map1.put(20, "王五");
16        System.out.println(map1);
17    }

Map的常用方法

  1. put添加元素: 无序, 不重复, 无索引
  2. public int size(): 获取集合的大小
  3. public void clear(): 清空集合
  4. public boolean isEmpty(): 判断集合是否为空, 为空返回true
  5. public V get(Object key): 根据键获取对应值
  6. public V remove(Object key): 根据键删除整个元素(删除键会返回键的值)
  7. public boolean containsKey(Object key): 判断是否包含某个键 , 包含返回true
  8. public boolean containsValue(Object value): 判断是否包含某个值
  9. public Set keySet(): 获取Map集合的全部键
  10. public Collection values(): 获取Map集合的全部值
  11. putAll把其他Map集合的数据倒入到自己集合中来
 1	public static void main(String[] args) {
 2        // 1.添加元素: 无序,不重复,无索引。
 3        Map<String, Integer> map = new HashMap<>();
 4        map.put("手表", 100);
 5        map.put("手表", 220);
 6        map.put("手机", 2);
 7        map.put("Java", 2);
 8        map.put(null, null);
 9        System.out.println(map);
10        // map = {null=null, 手表=220, Java=2, 手机=2}
11
12        // 2.public int size():获取集合的大小
13        System.out.println(map.size());
14
15        // 3、public void clear():清空集合
16        //map.clear();
17        //System.out.println(map);
18
19        // 4.public boolean isEmpty(): 判断集合是否为空,为空返回true ,反之!
20        System.out.println(map.isEmpty());
21
22        // 5.public V get(Object key):根据键获取对应值
23        int v1 = map.get("手表");
24        System.out.println(v1);
25        System.out.println(map.get("手机")); // 2
26        System.out.println(map.get("张三")); // null
27
28        // 6. public V remove(Object key):根据键删除整个元素(删除键会返回键的值)
29        System.out.println(map.remove("手表"));
30        System.out.println(map);
31
32        // 7.public  boolean containsKey(Object key): 判断是否包含某个键 ,包含返回true ,反之
33        System.out.println(map.containsKey("手表")); // false
34        System.out.println(map.containsKey("手机")); // true
35        System.out.println(map.containsKey("java")); // false
36        System.out.println(map.containsKey("Java")); // true
37
38        // 8.public boolean containsValue(Object value): 判断是否包含某个值。
39        System.out.println(map.containsValue(2)); // true
40        System.out.println(map.containsValue("2")); // false
41
42        // 9.public Set<K> keySet(): 获取Map集合的全部键。
43        Set<String> keys = map.keySet();
44        System.out.println(keys);
45
46        // 10.public Collection<V> values(); 获取Map集合的全部值。
47        Collection<Integer> values = map.values();
48        System.out.println(values);
49
50        // 11.把其他Map集合的数据倒入到自己集合中来。(拓展)
51        Map<String, Integer> map1 = new HashMap<>();
52        map1.put("java1",  10);
53        map1.put("java2",  20);
54        Map<String, Integer> map2 = new HashMap<>();
55        map2.put("java3",  10);
56        map2.put("java2",  222);
57        map1.putAll(map2); // putAll:把map2集合中的元素全部倒入一份到map1集合中去。
58        System.out.println(map1);
59        System.out.println(map2);
60    }

遍历

1. 先获取全部键, 再找值
1for (String key : keys) {
2            // 根据键获取对应的值
3            double value = map.get(key);
4            System.out.println(key + "=====>" + value);
5        }
2. 将键值对看做整体遍历

将键值对封装成一个entry对象, 然后用getKey和getValue取里面的键值.

1		// 1、调用Map集合提供entrySet方法,把Map集合转换成键值对类型的Set集合
2        Set<Map.Entry<String, Double>> entries = map.entrySet();
3        for (Map.Entry<String, Double> entry : entries) {
4            String key = entry.getKey();
5            double value = entry.getValue();
6            System.out.println(key + "---->" + value);
7        }
3.Lambda遍历
1map.forEach(( k,  v) -> {
2              System.out.println(k + "---->" + v);
3        });

HashMap底层原理

HashMap和HashSet底层原理一模一样, 都是基于哈希表实现, 这是因为Set系列的底层就是基于Map实现, 只是Set只需要键数据, 不需要值数据. 算位置时都是根据键算的, 每个位置放的都是一个Entry对象.

LinkedHashMap底层原理

底层数据结构依然是基于哈希表实现的, 只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序). 实际上: 原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap.

TreeMap

TreeMap跟TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序。

TreeMap 集合同样也支持两种方式来指定排序规则

  • 让类实现Comparable接口,重写比较规则。
  • TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。

Stream流

初识Stream流

使用stream()方法获得流对象, 然后filter过滤, 其中的s表示对每个列表中的元素进行过滤. 最后使用collect方法传入Collectors.toList()将其转化为列表.

 1	public static void main(String[] args) {
 2        List<String> names = new ArrayList<>();
 3        Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
 4        System.out.println(names);
 5        // names = [张三丰, 张无忌, 周芷若, 赵敏, 张强]
 6        //          name
 7
 8        // 找出姓张,且是3个字的名字,存入到一个新集合中去。
 9        List<String> list = new ArrayList<>();
10        for (String name : names) {
11            if(name.startsWith("张") && name.length() == 3){
12                list.add(name);
13            }
14        }
15        System.out.println(list);
16
17        // 开始使用Stream流来解决这个需求。
18        List<String> list2 = names.stream().filter(s -> s.startsWith("张"))
19                .filter(a -> a.length()==3).collect(Collectors.toList());
20        System.out.println(list2);
21    }

获取Stream流

对于List和Set, 调用stream方法即可. 对于Map, 需要分开键值对, 或者直接调用entrySet得到Entry对象的集合再使用Stream

Arrays和Stream类都提供了静态方法来创建Stream: Arrays.stream(), Stream.of().

这里还使用了Stream的foreach方法对stream进行遍历.

 1	public static void main(String[] args) {
 2        // 1、如何获取List集合的Stream流?
 3        List<String> names = new ArrayList<>();
 4        Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
 5        Stream<String> stream = names.stream();
 6
 7        // 2、如何获取Set集合的Stream流?
 8        Set<String> set = new HashSet<>();
 9        Collections.addAll(set, "刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
10        Stream<String> stream1 = set.stream();
11        stream1.filter(s -> s.contains("德")).forEach(s -> System.out.println(s));
12
13        // 3、如何获取Map集合的Stream流?
14        Map<String, Double> map = new HashMap<>();
15        map.put("古力娜扎", 172.3);
16        map.put("迪丽热巴", 168.3);
17        map.put("马尔扎哈", 166.3);
18        map.put("卡尔扎巴", 168.3);
19
20        Set<String> keys = map.keySet();
21        Stream<String> ks = keys.stream();
22
23        Collection<Double> values = map.values();
24        Stream<Double> vs = values.stream();
25
26        Set<Map.Entry<String, Double>> entries = map.entrySet();
27        Stream<Map.Entry<String, Double>> kvs = entries.stream();
28        kvs.filter(e -> e.getKey().contains("巴"))
29                .forEach(e -> System.out.println(e.getKey()+ "-->" + e.getValue()));
30
31        // 4、如何获取数组的Stream流?
32        String[] names2 = {"张翠山", "东方不败", "唐大山", "独孤求败"};
33        Stream<String> s1 = Arrays.stream(names2);
34        Stream<String> s2 = Stream.of(names2);
35    }

Stream流的中间方法

返回的也是stream, 所以支持链式编程.

  • filter: 过滤, 滤出满足条件的元素

  • sorted: 默认升序. 如果是复杂的对象需要提供比较器, 第一个大于第二个返回正数就是升序

    (对于小数创建比较器, 由于比较器返回的必须是整数, 因此使用Double.compare方法很便利)

  • limit(long maxSize): 取出前面的maxSize个元素

  • skip(long n): 跳过前面n个元素, 还剩size()-n个元素.

  • map: 映射, 将一个东西用另一个东西来表示.

  • distinct: 去重复, 内部使用hashCode和equals比较.

  • concat: 合并两个流为一个, 两个流里面的元素可以不一样, 返回object流.

 1	public static void main(String[] args) {
 2        List<Double> scores = new ArrayList<>();
 3        Collections.addAll(scores, 88.5, 100.0, 60.0, 99.0, 9.5, 99.6, 25.0);
 4        // 需求1:找出成绩大于等于60分的数据,并升序后,再输出。
 5        scores.stream().filter(s -> s >= 60).sorted().forEach(s -> System.out.println(s));
 6
 7        List<Student> students = new ArrayList<>();
 8        Student s1 = new Student("蜘蛛精", 26, 172.5);
 9        Student s2 = new Student("蜘蛛精", 26, 172.5);
10        Student s3 = new Student("紫霞", 23, 167.6);
11        Student s4 = new Student("白晶晶", 25, 169.0);
12        Student s5 = new Student("牛魔王", 35, 183.3);
13        Student s6 = new Student("牛夫人", 34, 168.5);
14        Collections.addAll(students, s1, s2, s3, s4, s5, s6);
15        // 需求2:找出年龄大于等于23,且年龄小于等于30岁的学生,并按照年龄降序输出.
16        students.stream().filter(s -> s.getAge() >= 23 && s.getAge() <= 30)
17                .sorted((o1, o2) -> o2.getAge() - o1.getAge())
18                .forEach(s -> System.out.println(s));
19
20        // 需求3:取出身高最高的前3名学生,并输出。
21        students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
22                .limit(3).forEach(System.out::println);
23        System.out.println("----------------------------------------------------------------");
24
25        // 需求4:取出身高倒数的2名学生,并输出。   s1 s2 s3 s4 s5 s6
26        students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
27                .skip(students.size() - 2).forEach(System.out::println);
28
29        // 需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出。
30        students.stream().filter(s -> s.getHeight() > 168).map(Student::getName)
31               .distinct().forEach(System.out::println);
32
33        // distinct去重复,自定义类型的对象(希望内容一样就认为重复,重写hashCode,equals)
34        students.stream().filter(s -> s.getHeight() > 168)
35                .distinct().forEach(System.out::println);
36
37        Stream<String> st1 = Stream.of("张三", "李四");
38        Stream<String> st2 = Stream.of("张三2", "李四2", "王五");
39        Stream<String> allSt = Stream.concat(st1, st2);
40        allSt.forEach(System.out::println);
41    }

Stream流的终结方法

  • forEach: 每个进行遍历.
  • count: 返回stream里面的元素个数.
  • max: 返回装有最大值的一个容(Optional), 必要时提供比较器. 最后使用get()取出
  • min: 找最小值. 其余同上
收集Stream流
  • collect: 将流处理后的结果收集到一个指定的集合中
  • toArray: 收集到一个数组中. 默认是Object数组, 如果希望是其他类型需要显式提供

收集方式:

  1. Collections.toList()
  2. Collections.toSet(): 自动去重复, 去重依赖于hashCode和equals.
  3. Collections.toMap(): 需要提供键和值.

注意, 流只能使用一个, 在被收集之后就关闭了.

 1public static void main(String[] args) {
 2        List<Student> students = new ArrayList<>();
 3        Student s1 = new Student("蜘蛛精", 26, 172.5);
 4        Student s2 = new Student("蜘蛛精", 26, 172.5);
 5        Student s3 = new Student("紫霞", 23, 167.6);
 6        Student s4 = new Student("白晶晶", 25, 169.0);
 7        Student s5 = new Student("牛魔王", 35, 183.3);
 8        Student s6 = new Student("牛夫人", 34, 168.5);
 9        Collections.addAll(students, s1, s2, s3, s4, s5, s6);
10        // 需求1:请计算出身高超过168的学生有几人。
11        long size = students.stream().filter(s -> s.getHeight() > 168).count();
12        System.out.println(size);
13
14        // 需求2:请找出身高最高的学生对象,并输出。
15        Student s = students.stream().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
16        System.out.println(s);
17
18        // 需求3:请找出身高最矮的学生对象,并输出。
19        Student ss = students.stream().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
20        System.out.println(ss);
21
22        // 需求4:请找出身高超过170的学生对象,并放到一个新集合中去返回。
23        // 流只能收集一次。
24        List<Student> students1 = students
25                .stream()
26                .filter(a -> a.getHeight() > 170)
27                .collect(Collectors.toList());
28        System.out.println(students1);
29
30        Set<Student> students2 = students
31                .stream()
32                .filter(a -> a.getHeight() > 170)
33                .collect(Collectors.toSet());
34        System.out.println(students2);
35
36        // 需求5:请找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回。
37//        Map<String, Double> map = students
38//                .stream()
39//                .filter(a -> a.getHeight() > 170)
40//                .distinct()
41//                .collect(Collectors.toMap(a -> a.getName(), a -> a.getHeight()));
42
43        Map<String, Student> map = students
44                .stream()
45                .filter(a -> a.getHeight() > 170)
46                .distinct()
47                .collect(Collectors.toMap(a -> a.getName(), a -> a));
48        System.out.println(map);
49
50        // Object[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray();
51        // Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(len -> new Student[len]);
52        Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(Student[]::new);
53        System.out.println(Arrays.toString(arr));
54    }

我感觉stream编程里面很多都可以使用方法引用, 这时Lambda表达式都多余了. Lambda表达式里面的变量随便设置.

Java高级

反射

获取类的字节码: Class对象

  • 静态方法: 类名.class
  • 调用Class的方法forName, 填入类的名称
  • 调用Object类的方法getClass, 需要现有这个类的对象, 然后: 对象.getClass()

一个类的Class对象只有一个, 不同方式获取的Class对象指向的是同一地址.

 1	public static void main(String[] args) throws Exception {
 2        Class c1 = Student.class;
 3        System.out.println(c1.getName()); // 全类名
 4        System.out.println(c1.getSimpleName()); // 简名:Student
 5
 6        Class c2 = Class.forName("com.itheima.d2_reflect.Student");
 7        System.out.println(c1 == c2);
 8
 9        Student s = new Student();
10        Class c3 = s.getClass();
11        System.out.println(c3 == c2);
12    }

由Class获取类的构造器并初始化对象

属于Class类的方法:

  • getConstructors: 获取全部构造器(只能获取public修饰的)
  • getDeclaredConstructors: 获取全部构造器(只要存在就能拿到)
  • getConstructor(Class<?>): 获取某个构造器(只能获取public修饰的), 需要时填入对应方法对应参数的字节码.
  • getDeclaredConstructor(Class<?>): 获取某个构造器(只要存在就能拿到), 需要时填入对应方法对应参数的字节码.

一般就用getDeclaredConstructor(s), 都可以得到.

获取类的构造器的作用是初始化一个对象返回, 下面的Constructor对象的方法:

  • T newInstance(Object… initargs): 调用构造器, 传入对应需要的参数, 完成对象初始化并返回.
  • setAccessible(boolean): 设置为true, 表示禁止检查访问控制(暴力反射)

构造器有个泛型, 不指定的话返回的都是Object类, 需要强转.

 1@Test
 2    public void testGetConstructors(){
 3        // 1、反射第一步:必须先得到这个类的Class对象
 4        Class c = Cat.class;
 5        // 2、获取类的全部构造器
 6        // Constructor[] constructors = c.getConstructors();
 7        Constructor[] constructors = c.getDeclaredConstructors();
 8        // 3、遍历数组中的每个构造器对象
 9        for (Constructor constructor : constructors) {
10            System.out.println(constructor.getName() + "--->"
11            + constructor.getParameterCount());
12        }
13    }
14
15    @Test
16    public void testGetConstructor() throws Exception {
17        // 1、反射第一步:必须先得到这个类的Class对象
18        Class c = Cat.class;
19        // 2、获取类的某个构造器:无参数构造器
20        Constructor constructor1 = c.getDeclaredConstructor();
21        System.out.println(constructor1.getName() + "--->"
22                + constructor1.getParameterCount());
23        constructor1.setAccessible(true); // 禁止检查访问权限
24        Cat cat = (Cat) constructor1.newInstance();
25        System.out.println(cat);
26
27        AtomicInteger a;
28
29
30        // 3、获取有参数构造器
31        Constructor constructor2 =
32                c.getDeclaredConstructor(String.class, int.class);
33        System.out.println(constructor2.getName() + "--->"
34                + constructor2.getParameterCount());
35        constructor2.setAccessible(true); // 禁止检查访问权限
36        Cat cat2 = (Cat) constructor2.newInstance("叮当猫", 3);
37        System.out.println(cat2);
38
39    }

由Class获取类的成员变量并赋值和取值

属于Class类的方法:

  • Field[] getFields(): 获取类的全部成员变量(只能获取public修饰的)
  • Field[] getDeclaredFields(): 获取类的全部成员变量(只要存在就能拿到)
  • Field getField(String name): 获取类的某个成员变量(只能获取public修饰的)
  • Field getDeclaredField(String name): 获取类的某个成员变量(只要存在就能拿到)

获取成员变量是用来赋值和取值, 下面的方法Field对象的

  • set(Object obj, Object value): 赋值, 第一个参数是对象实例, 第二个是赋值的内容
  • get(Object obj): 取值, 传入对应对象.
  • setAccessible: 设置禁止检查访问控制
 1	@Test
 2    public void testGetFields() throws Exception {
 3        // 1、反射第一步:必须是先得到类的Class对象
 4        Class c = Cat.class;
 5        // 2、获取类的全部成员变量。
 6        Field[] fields = c.getDeclaredFields();
 7        // 3、遍历这个成员变量数组
 8        for (Field field : fields) {
 9            System.out.println(field.getName() +  "---> "+ field.getType());
10        }
11        // 4、定位某个成员变量
12        Field fName = c.getDeclaredField("name");
13        System.out.println(fName.getName() + "--->" + fName.getType());
14
15        Field fAge = c.getDeclaredField("age");
16        System.out.println(fAge.getName() + "--->" + fAge.getType());
17
18        // 赋值
19        Cat cat = new Cat();
20        fName.setAccessible(true); // 禁止访问控制权限
21        fName.set(cat, "卡菲猫");
22        System.out.println(cat);
23
24        // 取值
25        String name = (String) fName.get(cat);
26        System.out.println(name);
27    }

由Class获取类的成员方法并

属于Class类的方法:

  • Method[] getMethods(): 获取类的全部成员方法(只能获取public修饰的)
  • Method[] getDeclaredMethods(): 获取类的全部成员方法(只要存在)
  • Method[] getMethod(String name, Class<?>… ): 获取类的某个成员方法(只能获取public修饰的), 第一个参数是方法名. 对于某些方法重载, 需要填入参数的字节码对象才能定位.
  • Method[] getDeclaredMethod(String name, Class<?>… ): 获取类的某个成员方法(只要存在), 第一个参数是方法名. 对于某些方法重载, 需要填入参数的字节码对象才能定位.

获取类的成员方法是执行方法, 下面是Method类的方法:

  • Object invoke(Object, Object… args): 触发某个对象该方法的执行. 第一个参数是对象实例.
  • setAccessible: 设置禁止检查访问控制
 1	@Test
 2    public void testGetMethods() throws Exception {
 3        //  1、反射第一步:先得到Class对象。
 4        Class c = Cat.class;
 5        // 2、获取类的全部成员方法。
 6        Method[] methods = c.getDeclaredMethods();
 7        // 3、遍历这个数组中的每个方法对象
 8        for (Method method : methods) {
 9            System.out.println(method.getName() + "--->"
10                    + method.getParameterCount() + "---->"
11                    + method.getReturnType());
12        }
13        //  4、获取某个方法对象
14        Method run = c.getDeclaredMethod("run"); // 拿run方法,无参数的
15        System.out.println(run.getName() + "--->"
16                + run.getParameterCount() + "---->"
17                + run.getReturnType());
18
19        Method eat = c.getDeclaredMethod("eat", String.class);
20        System.out.println(eat.getName() + "--->"
21                + eat.getParameterCount() + "---->"
22                + eat.getReturnType());
23
24        Cat cat = new Cat();
25        run.setAccessible(true); // 禁止检查访问权限
26        Object rs = run.invoke(cat); // 调用无参数的run方法,用cat对象触发调用的。
27        System.out.println(rs);
28
29        eat.setAccessible(true); // 禁止检查访问权限
30        String rs2 = (String) eat.invoke(cat, "鱼儿");
31        System.out.println(rs2);
32    }

都有setAccessible是因为Constructor, Field, Method类的父类都有AccessibleObject, 里面就有setAccessible.

作用, 应用场景

  1. 得到一个类的全部成分然后操作
  2. 可以破坏封装性
  3. 适合做Java的框架, 基于反射设计通用的功能.

需求: 将对象的字段名和对应的值都保存到文件中

 1	// 目标:保存任意对象的字段和其数据到文件中去
 2    public static void saveObject(Object obj) throws Exception {
 3        PrintStream ps = new PrintStream(new FileOutputStream("junit-reflect-annotation-proxy-app\\src\\data.txt", true));
 4        // obj是任意对象,到底有多少个字段要保存。
 5        Class c = obj.getClass();
 6        String cName = c.getSimpleName();
 7        ps.println("---------------" + cName + "------------------------");
 8        // 2、从这个类中提取它的全部成员变量
 9        Field[] fields = c.getDeclaredFields();
10        // 3、遍历每个成员变量。
11        for (Field field : fields) {
12            // 4、拿到成员变量的名字
13            String name = field.getName();
14            // 5、拿到这个成员变量在对象中的数据。
15            field.setAccessible(true); // 禁止检查访问控制
16            String value = field.get(obj) + "";
17            ps.println(name + "=" + value);
18        }
19        ps.close();
20    }

注解

让别的程序根据注解信息决定怎么执行该程序

自定义注解

1public @interface 注解名称{
2	public 属性类型 属性名() default 默认值;
3}

比如:

1public @interface MyTest1 {
2    String aaa();
3    boolean bbb() default true;
4    String[] ccc();
5}

在其他类使用注解时, 需要添加上这些属性的值:

 1@MyTest1(aaa="牛魔王", ccc={"HTML", "Java"})
 2// @MyTest2(value = "孙悟空")
 3//@MyTest2("孙悟空")
 4//@MyTest2(value = "孙悟空", age = 1000)
 5@MyTest2("孙悟空")
 6public class AnnotationTest1 {
 7    @MyTest1(aaa="铁扇公主", bbb=false, ccc={"Python", "前端", "Java"})
 8    public void test1(){
 9
10    }
11
12    public static void main(String[] args) {
13
14    }
15}

注意: value属性, 如果只有一个value属性的情况下, 使用value属性的时候可以省略value名称不写!! l但是如果有多个属性, 且多个属性没有默认值, 那么value名称是不能省略的.

1public @interface MyTest2 {
2    String value(); // 特殊属性
3    int age() default 23;
4}

注解的原理

注解本质是一个接口, Java中所有注解都是继承了Annotation接口的, 而@注解其实就是一个实现类对象, 实现了该注解以及Annotaion接口.

上面的MyTest1注解经过编译后变成了:

1public interface MyTest1 extends Annotaion{
2	public abstract String aaa();
3	public abstract boolean bbb();
4	public abstract String[] ccc();
5}

元注解

修饰注解的注解

@Target注解: 声明被修饰的注解只能在哪些位置使用

  1. TYPE: 类, 接口
  2. FIELD: 成员变量
  3. METHOD: 成员方法
  4. PARAMETER: 方法参数
  5. CONSTRUCTOR: 构造器
  6. LOCAL_VARIABLE: 局部变量

@Target(ElementType.TYPE)就只能用在类或接口上, 其余的都不行.

@Retention: 声明注解的保留周期

  1. SOURCE: 只作用在源码阶段, 字节码文件中不存在
  2. CLASS(默认): 保留到字节码文件阶段, 运行阶段不存在
  3. RUNTIME(开发常用): 一直保留到运行阶段

@Retention(RetentionPolicy.RUNTIME)代码修饰的这个注解运行时也能看到.

1@Target({ElementType.TYPE, ElementType.METHOD}) // 当前被修饰的注解只能用在类上,方法上。
2@Retention(RetentionPolicy.RUNTIME) // 控制下面的注解一直保留到运行时
3public @interface MyTest3 {
4}
 1@MyTest3
 2public class AnnotationTest2 {
 3
 4    // @MyTest3
 5    private String name;
 6
 7    @MyTest3
 8    public void test(){
 9
10    }
11}

注解的解析

判断类上, 方法上, 成员变量上等地方是否存在注解, 并将注解的内容解析出来.

要解析谁上面的注解, 就应先把谁拿到; 解析类上的先拿Class对象, 解析方法上的拿Method对象.

Class, Method, Field, Constructor都实现了AnnotatedElement接口, 它们都有解析注解的能力.

  • Annotaion[] getDeclaredAnnotaions(): 获取当前对象上的注解
  • T getDeclaredAnnotaion(Class<T> annotaionClass): 获取指定的注解对象
  • boolean isAnnotaionPresent(Class<Annotaion> annotaionClass): 判断某个对象上是否存在某个注解
 1@Test
 2    public void parseClass(){
 3        // 1、先得到Class对象
 4        Class c = Demo.class;
 5        // 2、解析类上的注解
 6        // 判断类上是否包含了某个注解
 7        if(c.isAnnotationPresent(MyTest4.class)){
 8            MyTest4 myTest4 =
 9                    (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
10            System.out.println(myTest4.value());
11            System.out.println(myTest4.aaa());
12            System.out.println(Arrays.toString(myTest4.bbb()));
13        }
14    }
15
16    @Test
17    public void parseMethod() throws Exception {
18        // 1、先得到Class对象
19        Class c = Demo.class;
20        Method m = c.getDeclaredMethod("test1");
21        // 2、解析方法上的注解
22        // 判断方法对象上是否包含了某个注解
23        if(m.isAnnotationPresent(MyTest4.class)){
24            MyTest4 myTest4 =
25                    (MyTest4) m.getDeclaredAnnotation(MyTest4.class);
26            System.out.println(myTest4.value());
27            System.out.println(myTest4.aaa());
28            System.out.println(Arrays.toString(myTest4.bbb()));
29        }
30    }

模拟junit

注解就是标记程序, 让其他程序根据注解信息决定如何对待它们.

 1public class AnnotationTest4 {
 2    // @MyTest
 3    public void test1(){
 4        System.out.println("===test1====");
 5    }
 6
 7    @MyTest
 8    public void test2(){
 9        System.out.println("===test2====");
10    }
11
12    @MyTest
13    public void test3(){
14        System.out.println("===test3====");
15    }
16
17    @MyTest
18    public void test4(){
19        System.out.println("===test4====");
20    }
21
22    public static void main(String[] args) throws Exception {
23        AnnotationTest4 a = new AnnotationTest4();
24        // 启动程序!
25        // 1、得到Class对象
26        Class c = AnnotationTest4.class;
27        // 2、提取这个类中的全部成员方法
28        Method[] methods = c.getDeclaredMethods();
29        // 3、遍历这个数组中的每个方法,看方法上是否存在@MyTest注解,存在
30        // 触发该方法执行。
31        for (Method method : methods) {
32            if(method.isAnnotationPresent(MyTest.class)){
33                // 说明当前方法上是存在@MyTest,触发当前方法执行。
34                method.invoke(a);
35            }
36        }
37    }
38}

动态代理

对象嫌身上干的事太多的话, 可以通过代理转义部分职责

对象有什么方法想被代理. 代理一定要有对应的方法.

规范: 对象想被代理需创建一个对应的接口, 并实现这个接口. 之后代理方法返回的就是这个接口

首先通过Proxy.newProxyInstance创建一个代理对象, 该方法有三个参数. 第三个参数是一个回调函数, 之后之后代理对象完成原本对象方法时会先进入这个回调方法, 我们就在这个回调方法里面做功能添加. 原本方法会返回值就return出去.

创建代理对象的方法:

 1public class ProxyUtil {
 2    public static Star createProxy(BigStar bigStar){
 3       /* newProxyInstance(ClassLoader loader,
 4                Class<?>[] interfaces,
 5                InvocationHandler h)
 6                参数1:用于指定一个类加载器
 7                参数2:指定生成的代理长什么样子,也就是有哪些方法
 8                参数3:用来指定生成的代理对象要干什么事情
 9                */
10        // Star starProxy = ProxyUtil.createProxy(s);
11        // starProxy.sing("好日子") starProxy.dance()
12        Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
13                new Class[]{Star.class}, new InvocationHandler() {
14                    @Override // 回调方法
15                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16                        // 代理对象要做的事情,会在这里写代码
17                        if(method.getName().equals("sing")){
18                            System.out.println("准备话筒,收钱20万");
19                        }else if(method.getName().equals("dance")){
20                            System.out.println("准备场地,收钱1000万");
21                        }
22                        return method.invoke(bigStar, args);
23                    }
24                });
25        return starProxy;
26    }
27}

实体类

 1public class BigStar implements Star{
 2    private String name;
 3
 4    public BigStar(String name) {
 5        this.name = name;
 6    }
 7
 8    public String sing(String name){
 9        System.out.println(this.name + "正在唱:" + name);
10        return "谢谢!谢谢!";
11    }
12
13    public void dance(){
14        System.out.println(this.name  + "正在优美的跳舞~~");
15    }
16}

测试

 1public class Test {
 2    public static void main(String[] args) {
 3        BigStar s = new BigStar("杨超越");
 4        Star starProxy = ProxyUtil.createProxy(s);
 5
 6        String rs = starProxy.sing("好日子");
 7        System.out.println(rs);
 8
 9        starProxy.dance();
10    }
11}

应用: 利用代理模式实现AOP, 测试方法耗时.

 1public static UserService createProxy(UserService userService){
 2        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
 3                new Class[]{UserService.class}, new InvocationHandler() {
 4                    @Override
 5                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 6
 7                        if(method.getName().equals("login") || method.getName().equals("deleteUsers")||
 8                               method.getName().equals("selectUsers")){
 9                            long startTime = System.currentTimeMillis();
10
11                            Object rs = method.invoke(userService, args);
12
13                            long endTime = System.currentTimeMillis();
14                            System.out.println(method.getName() + "方法执行耗时:" + (endTime - startTime)/ 1000.0 + "s");
15                            return rs;
16                        }else {
17                            Object rs = method.invoke(userService, args);
18                            return rs;
19                        }
20                    }
21                });
22        return userServiceProxy;
23    }