com3 = Integer :: compare;
+
+ int compare3 = com3.compare(32,21);
+ System.out.println(compare3);
+ }
+```
+
+
+
+## Lambda语法规则
+
+```Java
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.function.Consumer;
+
+/**
+ * Lambda表达式的使用
+ *
+ * 1.举例: (o1,o2) -> Integer.compare(o1,o2);
+ * 2.格式:
+ * -> :lambda操作符 或 箭头操作符
+ * ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
+ * ->右边:lambda体 (其实就是重写的抽象方法的方法体)
+ *
+ * 3. Lambda表达式的使用:(分为6种情况介绍)
+ *
+ * 总结:
+ * ->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
+ * ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字
+ *
+ * 4.Lambda表达式的本质:作为函数式接口的实例
+ *
+ * 5. 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,
+ * 这样做可以检查它是否是一个函数式接口。
+ *
+ * 6. 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
+ */
+public class LambdaTest1 {
+ //语法格式一:无参,无返回值
+ @Test
+ public void test1() {
+ Runnable r1 = new Runnable() {
+ @Override
+ public void run() {
+ System.out.println("我爱北京天安门");
+ }
+ };
+
+ r1.run();
+
+ System.out.println("***********************");
+
+ Runnable r2 = () -> {
+ System.out.println("我爱北京故宫");
+ };
+
+ r2.run();
+ }
+
+ //语法格式二:Lambda 需要一个参数,但是没有返回值。
+ @Test
+ public void test2() {
+
+ Consumer con = new Consumer() {
+ @Override
+ public void accept(String s) {
+ System.out.println(s);
+ }
+ };
+ con.accept("谎言和誓言的区别是什么?");
+
+ System.out.println("*******************");
+
+ Consumer con1 = (String s) -> {
+ System.out.println(s);
+ };
+ con1.accept("一个是听得人当真了,一个是说的人当真了");
+
+ }
+
+ //语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
+ @Test
+ public void test3() {
+
+ Consumer con1 = (String s) -> {
+ System.out.println(s);
+ };
+ con1.accept("一个是听得人当真了,一个是说的人当真了");
+
+ System.out.println("*******************");
+
+ Consumer con2 = (s) -> {
+ System.out.println(s);
+ };
+ con2.accept("一个是听得人当真了,一个是说的人当真了");
+
+ }
+
+ @Test
+ public void test4() {
+
+ ArrayList list = new ArrayList<>();//类型推断
+
+ int[] arr = {1, 2, 3};//类型推断
+
+ }
+
+ //语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
+ @Test
+ public void test5() {
+ Consumer con1 = (s) -> {
+ System.out.println(s);
+ };
+ con1.accept("一个是听得人当真了,一个是说的人当真了");
+
+ System.out.println("*******************");
+
+ Consumer con2 = s -> {
+ System.out.println(s);
+ };
+ con2.accept("一个是听得人当真了,一个是说的人当真了");
+
+
+ }
+
+ //语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
+ @Test
+ public void test6() {
+
+ Comparator com1 = new Comparator() {
+ @Override
+ public int compare(Integer o1, Integer o2) {
+ System.out.println(o1);
+ System.out.println(o2);
+ return o1.compareTo(o2);
+ }
+ };
+
+ System.out.println(com1.compare(12, 21));
+
+ System.out.println("*****************************");
+ Comparator com2 = (o1, o2) -> {
+ System.out.println(o1);
+ System.out.println(o2);
+ return o1.compareTo(o2);
+ };
+
+ System.out.println(com2.compare(12, 6));
+
+
+ }
+
+ //语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
+ @Test
+ public void test7() {
+
+ Comparator com1 = (o1, o2) -> {
+ return o1.compareTo(o2);
+ };
+
+ System.out.println(com1.compare(12, 6));
+
+ System.out.println("*****************************");
+
+ Comparator com2 = (o1, o2) -> o1.compareTo(o2);
+
+ System.out.println(com2.compare(12, 21));
+
+ }
+
+ @Test
+ public void test8() {
+ Consumer con1 = s -> {
+ System.out.println(s);
+ };
+ con1.accept("一个是听得人当真了,一个是说的人当真了");
+
+ System.out.println("*****************************");
+
+ Consumer con2 = s -> System.out.println(s);
+
+ con2.accept("一个是听得人当真了,一个是说的人当真了");
+
+ }
+
+}
+```
+
+
+
+# 函数式接口
+
+
+
+## 什么是函数式(Functional)接口
+
+- 只包含一个抽象方法的接口,称为**函数式接口**。
+
+- 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
+
+- 我们可以在一个接口上使用 **@FunctionalInterface** 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
+
+- 在java.util.function包下定义了Java 8 的丰富的函数式接口
+
+
+
+## 如何理解函数式接口
+
+- Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
+- 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
+- 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
+- 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
+
+
+
+## Java内置函数式接口
+
+
+
+**核心函数式接口**
+
+
+
+
+
+**其它函数式接口**
+
+
+
+
+
+
+
+**Consumer**
+
+```java
+ @Test
+ public void test1(){
+
+ happyTime(500, new Consumer() {
+ @Override
+ public void accept(Double aDouble) {
+ System.out.println("学习太累了,去天上人间买了瓶矿泉水,价格为:" + aDouble);
+ }
+ });
+
+ System.out.println("********************");
+
+ happyTime(400,money -> System.out.println("学习太累了,去天上人间喝了口水,价格为:" + money));
+ }
+
+ public void happyTime(double money, Consumer con){
+ con.accept(money);
+ }
+```
+
+
+
+**结果:**
+
+```Java
+学习太累了,去天上人间买了瓶矿泉水,价格为:500.0
+********************
+学习太累了,去天上人间喝了口水,价格为:400.0
+
+Process finished with exit code 0
+```
+
+
+
+**Predicate**
+
+```java
+@Test
+ public void test2(){
+ List list = Arrays.asList("北京","南京","天津","东京","西京","普京");
+
+ List filterStrs = filterString(list, new Predicate() {
+ @Override
+ public boolean test(String s) {//这里是定义一个校验规则
+ return s.contains("京");
+ }
+ });
+
+ System.out.println(filterStrs);
+
+ //用lambda表达式会很简单
+ List filterStrs1 = filterString(list,s -> s.contains("京"));
+ System.out.println(filterStrs1);
+ }
+
+ //根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
+ public List filterString(List list, Predicate pre){
+
+ ArrayList filterList = new ArrayList<>();
+
+ for(String s : list){
+ if(pre.test(s)){
+ filterList.add(s);
+ }
+ }
+
+ return filterList;
+
+ }
+
+```
+
+
+
+**结果:**
+
+```
+[北京, 南京, 东京, 西京, 普京]
+[北京, 南京, 东京, 西京, 普京]
+
+Process finished with exit code 0
+```
+
+
+
+## 自定义函数式接口
+
+```java
+/**
+ * 自定义函数式接口
+ * 只是说加上@FunctionalInterface之后可以校验
+ */
+@FunctionalInterface
+public interface MyFunInterface {
+
+ public T getValue(T t);
+
+}
+```
+
+
+
+```java
+public class Test {
+
+ public static void main(String[] args) {
+ //这个方法的第一个参数是lambda表达式,相当于是实例化了那个函数式接口
+ String s = toUpperString(str -> str.toUpperCase(), "abcd");
+ System.out.println(s);
+ }
+
+ public static String toUpperString(MyFunInterface mf,String str){
+ return mf.getValue(str);
+ }
+}
+```
+
+
+
+# 方法引用
+
+- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
+
+- 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
+
+- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
+
+- 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
+
+- 如下三种主要使用情况:
+
+ - 对象 :: 实例方法名
+
+ - 类 :: 静态方法名
+
+ - 类 :: 实例方法名
+
+
+
+我们直接拿例子来说明情况,先提前准备两个类:
+
+```Java
+
+public class Employee {
+
+ private int id;
+ private String name;
+ private int age;
+ private double salary;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ 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 double getSalary() {
+ return salary;
+ }
+
+ public void setSalary(double salary) {
+ this.salary = salary;
+ }
+
+ public Employee() {
+ System.out.println("Employee().....");
+ }
+
+ public Employee(int id) {
+ this.id = id;
+ System.out.println("Employee(int id).....");
+ }
+
+ public Employee(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public Employee(int id, String name, int age, double salary) {
+
+ this.id = id;
+ this.name = name;
+ this.age = age;
+ this.salary = salary;
+ }
+
+ @Override
+ public String toString() {
+ return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ Employee employee = (Employee) o;
+
+ if (id != employee.id)
+ return false;
+ if (age != employee.age)
+ return false;
+ if (Double.compare(employee.salary, salary) != 0)
+ return false;
+ return name != null ? name.equals(employee.name) : employee.name == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ result = id;
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ result = 31 * result + age;
+ temp = Double.doubleToLongBits(salary);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+}
+
+```
+
+
+
+```Java
+/**
+ * 提供用于测试的数据
+ */
+public class EmployeeData {
+
+ public static List getEmployees(){
+ List list = new ArrayList<>();
+
+ list.add(new Employee(1001, "马化腾", 34, 6000.38));
+ list.add(new Employee(1002, "马云", 12, 9876.12));
+ list.add(new Employee(1003, "刘强东", 33, 3000.82));
+ list.add(new Employee(1004, "雷军", 26, 7657.37));
+ list.add(new Employee(1005, "李彦宏", 65, 5555.32));
+ list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
+ list.add(new Employee(1007, "任正非", 26, 4333.32));
+ list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
+
+ return list;
+ }
+
+}
+```
+
+
+
+**下面来通过实际例子讲解方法引用:**
+
+```java
+
+
+/**
+ * 方法引用的使用
+ *
+ * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
+ *
+ * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
+ * 方法引用,也是函数式接口的实例。
+ *
+ * 3. 使用格式: 类(或对象) :: 方法名
+ *
+ * 4. 具体分为如下的三种情况:
+ * 情况1 对象 :: 非静态方法
+ * 情况2 类 :: 静态方法
+ *
+ * 情况3 类 :: 非静态方法
+ *
+ * 5. 方法引用使用的要求:
+ * 接口中的抽象方法的形参列表和返回值类型
+ * 与
+ * 方法引用的方法的形参列表和返回值类型相同!(针对于情况1和情况2)
+ *
+ */
+public class MethodRefTest {
+
+ // 情况一:对象 :: 实例方法
+ //Consumer中的void accept(T t)
+ //PrintStream中的void println(T t)
+ @Test
+ public void test1() {
+ Consumer con1 = str -> System.out.println(str);
+ con1.accept("北京");
+
+ System.out.println("*******************");
+ PrintStream ps = System.out;
+ Consumer con2 = ps::println;
+ con2.accept("beijing");
+ }
+
+ //Supplier中的T get()
+ //Employee中的String getName()
+ @Test
+ public void test2() {
+ Employee emp = new Employee(1001,"Tom",23,5600);
+
+ Supplier sup1 = () -> emp.getName();
+ System.out.println(sup1.get());
+
+ System.out.println("*******************");
+ Supplier sup2 = emp::getName;
+ System.out.println(sup2.get());
+
+ }
+
+ // 情况二:类 :: 静态方法
+ //Comparator中的int compare(T t1,T t2)
+ //Integer中的int compare(T t1,T t2)
+ @Test
+ public void test3() {
+ Comparator com1 = (t1,t2) -> Integer.compare(t1,t2);
+ System.out.println(com1.compare(12,21));
+
+ System.out.println("*******************");
+
+ Comparator com2 = Integer::compare;
+ System.out.println(com2.compare(12,3));
+
+ }
+
+ //Function中的R apply(T t)
+ //Math中的Long round(Double d)
+ @Test
+ public void test4() {
+ Function func = new Function() {
+ @Override
+ public Long apply(Double d) {
+ return Math.round(d);
+ }
+ };
+
+ System.out.println("*******************");
+
+ Function func1 = d -> Math.round(d);
+ System.out.println(func1.apply(12.3));
+
+ System.out.println("*******************");
+
+ Function func2 = Math::round;
+ System.out.println(func2.apply(12.6));
+ }
+
+ // 情况三:类 :: 实例方法 (有难度)
+ // Comparator中的int comapre(T t1,T t2) 第一个参数T t1,也可以变成方法的调用者
+ // String中的int t1.compareTo(t2) 看上面说的,t1变成了调用者等价于第一个参数T t1
+ @Test
+ public void test5() {
+ Comparator com1 = (s1,s2) -> s1.compareTo(s2);
+ System.out.println(com1.compare("abc","abd"));
+
+ System.out.println("*******************");
+
+ Comparator com2 = String :: compareTo;
+ System.out.println(com2.compare("abd","abm"));
+ }
+
+ //BiPredicate中的boolean test(T t1, T t2);
+ //String中的boolean t1.equals(t2)
+ @Test
+ public void test6() {
+ BiPredicate pre1 = (s1,s2) -> s1.equals(s2);
+ System.out.println(pre1.test("abc","abc"));
+
+ System.out.println("*******************");
+ BiPredicate pre2 = String :: equals;
+ System.out.println(pre2.test("abc","abd"));
+ }
+
+ // Function中的R apply(T t)
+ // Employee中的String getName(); 第一个参数T t相当于方法调用者emp,返回值R和String对应
+ @Test
+ public void test7() {
+ Employee employee = new Employee(1001, "Jerry", 23, 6000);
+
+
+ Function func1 = e -> e.getName();
+ System.out.println(func1.apply(employee));
+
+ System.out.println("*******************");
+
+
+ Function func2 = Employee::getName;
+ System.out.println(func2.apply(employee));
+
+
+ }
+
+}
+
+```
+
+
+
+
+
+# 构造器引用
+
+格式:ClassName :: new
+
+与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
+
+
+
+```Java
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * 一、构造器引用
+ * 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
+ * 抽象方法的返回值类型即为构造器所属的类的类型
+ *
+ * 二、数组引用
+ * 大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
+ *
+ */
+public class ConstructorRefTest {
+
+ /**
+ * 构造器引用
+ * Supplier中的T get()
+ * Employee的空参构造器:Employee()
+ * 1、和方法引用一样的理解方法,你的get()方法没有参数,我的Employee()也没有参数。
+ * 2、你的get()方法有返回值T,我的Employee()方法返回值也是一个对象
+ * 3、所以刚好可以用
+ */
+ @Test
+ public void test1(){
+
+ Supplier sup = new Supplier() {
+ @Override
+ public Employee get() {
+ return new Employee();
+ }
+ };
+ System.out.println("*******************");
+
+ Supplier sup1 = () -> new Employee();
+ System.out.println(sup1.get());
+
+ System.out.println("*******************");
+
+ Supplier sup2 = Employee :: new;
+ System.out.println(sup2.get());
+ }
+
+ //Function中的R apply(T t)
+ @Test
+ public void test2(){
+ Function func1 = id -> new Employee(id);
+ Employee employee = func1.apply(1001);
+ System.out.println(employee);
+
+ System.out.println("*******************");
+
+ Function func2 = Employee :: new;
+ Employee employee1 = func2.apply(1002);
+ System.out.println(employee1);
+
+ }
+
+ //BiFunction中的R apply(T t,U u)
+ @Test
+ public void test3(){
+ BiFunction func1 = (id,name) -> new Employee(id,name);
+ System.out.println(func1.apply(1001,"Tom"));
+
+ System.out.println("*******************");
+
+ BiFunction func2 = Employee :: new;
+ System.out.println(func2.apply(1002,"Tom"));
+
+ }
+
+ //数组引用
+ //Function中的R apply(T t)
+ @Test
+ public void test4(){
+ Function func1 = length -> new String[length];
+ String[] arr1 = func1.apply(5);
+ System.out.println(Arrays.toString(arr1));
+
+ System.out.println("*******************");
+
+ Function func2 = String[] :: new;
+ String[] arr2 = func2.apply(10);
+ System.out.println(Arrays.toString(arr2));
+
+ }
+}
+
+```
+
+
+
+# 强大的Stream API
+
+## Stream API说明
+
+- Java8中有两大最为重要的改变。第一个是 **Lambda** **表达式**;另外一个则是 **Stream API**。
+
+- Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
+
+- Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 **使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。**也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式
+
+
+
+## 为什么要使用Stream API
+
+- 实际开发中,项目中多数数据源都来自于Mysql,Oracle等,很多一些复杂的数据获取可以直接在sql层面去解决。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据本身不支持一些复杂的数据计算,这个时候就需要Java层面去处理。
+
+- Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
+
+
+
+## 什么是Stream
+
+Stream到底是什么呢?
+
+是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
+
+**“集合讲的是数据,Stream讲的是计算!”**
+
+**注意:**
+
+①Stream 自己不会存储元素。
+
+②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
+
+③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
+
+
+
+
+
+**Stream** **的操作三个步骤**
+
+1、创建Stream
+
+一个数据源(如:集合、数组),获取一个流
+
+2、中间操作
+
+一个中间操作链,对数据源的数据进行处理
+
+3、终止操作(终端操作)
+
+一旦执行终止操作,就执行中间操作链,才产生结果【也就是所谓的延迟执行】。之后,不会再被使用
+
+
+
+
+
+
+
+
+
+## 创建Stream
+
+
+
+```java
+
+public class StreamAPITest {
+
+ //创建 Stream方式一:通过集合
+ @Test
+ public void test1(){
+ List employees = EmployeeData.getEmployees();
+
+// default Stream stream() : 返回一个顺序流,顺序流等会中间操作拿数据的时候按顺序拿
+ Stream stream = employees.stream();
+
+// default Stream parallelStream() : 返回一个并行流
+ Stream parallelStream = employees.parallelStream();
+
+ }
+
+ //创建 Stream方式二:通过数组
+ @Test
+ public void test2(){
+ int[] arr = new int[]{1,2,3,4,5,6};
+ //调用Arrays类的static Stream stream(T[] array): 返回一个流
+ IntStream stream = Arrays.stream(arr);
+
+ Employee e1 = new Employee(1001,"Tom");
+ Employee e2 = new Employee(1002,"Jerry");
+ Employee[] arr1 = new Employee[]{e1,e2};
+ Stream stream1 = Arrays.stream(arr1);
+
+ }
+ //创建 Stream方式三:通过Stream的of(),通过显示值创建一个流。它可以接收任意数量的参数
+ @Test
+ public void test3(){
+
+ Stream stream = Stream.of(1, 2, 3, 4, 5, 6);
+
+ }
+
+ //创建 Stream方式四:创建无限流【用的少,了解下就行】
+ @Test
+ public void test4(){
+
+// 迭代
+// public static Stream iterate(final T seed, final UnaryOperator f)
+ //遍历前10个偶数
+ Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
+
+
+// 生成
+// public static Stream generate(Supplier s)
+ Stream.generate(Math::random).limit(10).forEach(System.out::println);
+
+ }
+
+}
+
+```
+
+
+
+
+
+## 中间操作
+
+```Java
+
+/**
+ * 测试Stream的中间操作
+ */
+public class StreamAPITest1 {
+
+ //1-筛选与切片
+ @Test
+ public void test1(){
+ List list = EmployeeData.getEmployees();
+// filter(Predicate p)——过滤 接收 Lambda , 从流中排除某些元素。
+ Stream stream = list.stream();
+ //练习:查询员工表中薪资大于7000的员工信息
+ /**
+ * List filterStrs1 = filterString(list,s -> s.contains("京"));
+ * 跟之前的这个lambda表达式代码是一个意思
+ */
+ stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);
+
+ System.out.println();
+// limit(n)——截断流,使其元素不超过给定数量。
+ list.stream().limit(3).forEach(System.out::println);
+ System.out.println();
+
+// skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
+ list.stream().skip(3).forEach(System.out::println);
+
+ System.out.println();
+// distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
+
+ list.add(new Employee(1010,"刘强东",40,8000));
+ list.add(new Employee(1010,"刘强东",41,8000));
+ list.add(new Employee(1010,"刘强东",40,8000));
+ list.add(new Employee(1010,"刘强东",40,8000));
+ list.add(new Employee(1010,"刘强东",40,8000));
+
+// System.out.println(list);
+
+ list.stream().distinct().forEach(System.out::println);
+ }
+
+ //映射
+ @Test
+ public void test2(){
+// map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
+ List list = Arrays.asList("aa", "bb", "cc", "dd");
+ list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
+
+// 练习1:获取员工姓名长度大于3的员工的姓名。
+ List employees = EmployeeData.getEmployees();
+ Stream namesStream = employees.stream().map(Employee::getName);
+ namesStream.filter(name -> name.length() > 3).forEach(System.out::println);
+ System.out.println();
+ //练习2:
+ Stream> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
+ //这个还需要两层遍历
+ streamStream.forEach(s ->{
+ s.forEach(System.out::println);
+ });
+ System.out.println();
+// flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
+ //flatMap一层遍历即可拿到想要的结果
+ Stream characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
+ characterStream.forEach(System.out::println);
+
+ }
+
+ //将字符串中的多个字符构成的集合转换为对应的Stream的实例
+ public static Stream fromStringToStream(String str){//aa
+ ArrayList list = new ArrayList<>();
+ for(Character c : str.toCharArray()){
+ list.add(c);
+ }
+ return list.stream();
+
+ }
+
+
+ //3-排序
+ @Test
+ public void test4(){
+// sorted()——自然排序
+ List list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
+ list.stream().sorted().forEach(System.out::println);
+ //抛异常,原因:Employee没有实现Comparable接口
+// List employees = EmployeeData.getEmployees();
+// employees.stream().sorted().forEach(System.out::println);
+
+
+// sorted(Comparator com)——定制排序
+
+ List employees = EmployeeData.getEmployees();
+ employees.stream().sorted( (e1,e2) -> {
+
+ int ageValue = Integer.compare(e1.getAge(),e2.getAge());
+ if(ageValue != 0){
+ return ageValue;
+ }else{
+ return -Double.compare(e1.getSalary(),e2.getSalary());
+ }
+
+ }).forEach(System.out::println);
+ }
+
+}
+
+```
+
+
+
+## 终止操作
+
+
+
+```Java
+
+/**
+ * 测试Stream的终止操作
+ *
+ */
+public class StreamAPITest2 {
+
+ //1-匹配与查找
+ @Test
+ public void test1(){
+ List employees = EmployeeData.getEmployees();
+
+// allMatch(Predicate p)——检查是否匹配所有元素。
+// 练习:是否所有的员工的年龄都大于18
+ boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
+ System.out.println(allMatch);
+
+// anyMatch(Predicate p)——检查是否至少匹配一个元素。
+// 练习:是否存在员工的工资大于 10000
+ boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
+ System.out.println(anyMatch);
+
+// noneMatch(Predicate p)——检查是否没有匹配的元素。
+// 练习:是否存在员工姓“雷”
+ boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
+ System.out.println(noneMatch);
+// findFirst——返回第一个元素
+ Optional employee = employees.stream().findFirst();
+ System.out.println(employee);
+// findAny——返回当前流中的任意元素
+ Optional employee1 = employees.parallelStream().findAny();
+ System.out.println(employee1);
+
+ }
+
+ @Test
+ public void test2(){
+ List employees = EmployeeData.getEmployees();
+ // count——返回流中元素的总个数
+ long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
+ System.out.println(count);
+// max(Comparator c)——返回流中最大值
+// 练习:返回最高的工资:
+ Stream salaryStream = employees.stream().map(e -> e.getSalary());
+ Optional maxSalary = salaryStream.max(Double::compare);
+ System.out.println(maxSalary);
+// min(Comparator c)——返回流中最小值
+// 练习:返回最低工资的员工
+ Optional employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
+ System.out.println(employee);
+ System.out.println();
+// forEach(Consumer c)——内部迭代
+ employees.stream().forEach(System.out::println);
+
+ //使用集合的遍历操作
+ employees.forEach(System.out::println);
+ }
+
+ //2-归约
+ @Test
+ public void test3(){
+// reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
+// 练习1:计算1-10的自然数的和
+ List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
+ Integer sum = list.stream().reduce(0, Integer::sum);
+ System.out.println(sum);
+
+
+// reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional
+// 练习2:计算公司所有员工工资的总和
+ List employees = EmployeeData.getEmployees();
+ Stream salaryStream = employees.stream().map(Employee::getSalary);
+// Optional sumMoney = salaryStream.reduce(Double::sum);
+ Optional sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
+ System.out.println(sumMoney.get());
+
+ }
+
+ //3-收集
+ @Test
+ public void test4(){
+// collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
+// 练习1:查找工资大于6000的员工,结果返回为一个List或Set
+
+ List employees = EmployeeData.getEmployees();
+ List employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
+
+ employeeList.forEach(System.out::println);
+ System.out.println();
+ Set employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
+
+ employeeSet.forEach(System.out::println);
+
+
+
+
+ }
+}
+
+```
+
+
+
+# Optional类
+
+## 什么是Optional?
+
+
+
+- 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
+
+- Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
+
+- Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
+
+
+
+## 常用API
+
+
+
+
+
+
+
+## 举例
+
+首先准备两个类
+
+```java
+
+public class Boy {
+ private Girl girl;
+
+ @Override
+ public String toString() {
+ return "Boy{" +
+ "girl=" + girl +
+ '}';
+ }
+
+ public Girl getGirl() {
+ return girl;
+ }
+
+ public void setGirl(Girl girl) {
+ this.girl = girl;
+ }
+
+ public Boy() {
+
+ }
+
+ public Boy(Girl girl) {
+
+ this.girl = girl;
+ }
+}
+```
+
+
+
+```java
+public class Girl {
+
+ private String name;
+
+ @Override
+ public String toString() {
+ return "Girl{" +
+ "name='" + name + '\'' +
+ '}';
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Girl() {
+
+ }
+
+ public Girl(String name) {
+
+ this.name = name;
+ }
+}
+```
+
+
+
+这里只是简单的测试两个API
+
+```Java
+/**
+ * Optional类:为了在程序中避免出现空指针异常而创建的。
+ *
+ * 常用的方法:ofNullable(T t)
+ * orElse(T t)
+ *
+ */
+public class OptionalTest {
+
+/*
+Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
+Optional.empty() : 创建一个空的 Optional 实例
+Optional.ofNullable(T t):t可以为null
+
+ */
+ @Test
+ public void test1(){
+ Girl girl = new Girl();
+// girl = null;
+ //of(T t):保证t是非空的
+ Optional optionalGirl = Optional.of(girl);
+
+ }
+
+ @Test
+ public void test2(){
+ Girl girl = new Girl();
+// girl = null;
+ //ofNullable(T t):t可以为null
+ Optional optionalGirl = Optional.ofNullable(girl);
+ System.out.println(optionalGirl);
+ //orElse(T t1):如果单前的Optional内部封装的t是非空的,则返回内部的t.
+ //如果内部的t是空的,则返回orElse()方法中的参数t1.
+ Girl girl1 = optionalGirl.orElse(new Girl("赵丽颖"));
+ System.out.println(girl1);
+
+ }
+}
+```
+
+
+
+**实际场景使用**
+
+可能出现空指针的例子:
+
+```java
+public String getGirlName(Boy boy){
+ return boy.getGirl().getName();
+ }
+
+ @Test
+ public void test3(){
+ Boy boy = new Boy();
+ boy = null;
+ String girlName = getGirlName(boy);
+ System.out.println(girlName);
+
+ }
+```
+
+
+
+结果:
+
+```
+java.lang.NullPointerException
+ at com.atguigu.java4.OptionalTest.getGirlName(OptionalTest.java:47)
+ at com.atguigu.java4.OptionalTest.test3(OptionalTest.java:54)
+...
+...
+
+Process finished with exit code -1
+```
+
+
+
+没有Optional的解决办法,但是如果调用层数过多,就得一层一层判断是否为null,写起来很麻烦。
+
+```Java
+//优化以后的getGirlName():
+ public String getGirlName1(Boy boy){
+ if(boy != null){
+ Girl girl = boy.getGirl();
+ if(girl != null){
+ return girl.getName();
+ }
+ }
+
+ return null;
+
+ }
+@Test
+ public void test4(){
+ Boy boy = new Boy();
+ boy = null;
+ String girlName = getGirlName1(boy);
+ System.out.println(girlName);
+
+ }
+```
+
+
+
+使用Optional解决问题:
+
+```Java
+ //使用Optional类的getGirlName():
+ public String getGirlName2(Boy boy){
+
+ Optional boyOptional = Optional.ofNullable(boy);
+ //此时的boy1一定非空
+ Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));
+
+ Girl girl = boy1.getGirl();
+
+ Optional girlOptional = Optional.ofNullable(girl);
+ //girl1一定非空
+ Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));
+
+ return girl1.getName();
+ }
+
+ @Test
+ public void test5(){
+ Boy boy = null;
+ boy = new Boy();
+ boy = new Boy(new Girl("苍老师"));
+ String girlName = getGirlName2(boy);
+ System.out.println(girlName);
+
+ }
+```
+
+这种是绝对不会出现空指针的。
+
+
+
+# 接口的增强
+
+```
+JDK7及以前:只能定义全局常量和抽象方法
+ >全局常量:public static final的.但是书写时,可以省略不写
+ >抽象方法:public abstract的
+
+JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
+```
+
+
+
+```java
+/*
+ * JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
+ */
+public interface CompareA {
+
+ //静态方法
+ public static void method1() {
+
+ System.out.println("CompareA:北京");
+ }
+
+ //默认方法
+ public default void method2() {
+ System.out.println("CompareA:上海");
+ }
+ //接口中的public 可以省略,自动就是public
+ default void method3() {
+ System.out.println("CompareA:上海");
+ }
+}
+```
+
+
+
+```java
+public class SuperClass {
+
+ public void method3(){
+ System.out.println("SuperClass:北京");
+ }
+
+}
+```
+
+
+
+```
+public interface CompareB {
+
+ default void method3(){
+ System.out.println("CompareB:上海");
+ }
+
+}
+```
+
+
+
+
+
+```java
+public class SubClassTest {
+
+ public static void main(String[] args) {
+ SubClass s = new SubClass();
+
+// s.method1();
+// SubClass.method1();
+ //知识点1:接口中定义的静态方法,只能通过接口来调用。实现类用不了
+ CompareA.method1();
+ //知识点2:通过实现类的对象,可以调用接口中的默认方法。
+ //如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
+ s.method2();
+ //知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
+ //那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
+ //知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
+ //那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
+ //这就需要我们必须在实现类中重写此方法
+ s.method3();
+
+ }
+
+}
+
+class SubClass extends SuperClass implements CompareA,CompareB{
+
+ public void method2(){
+ System.out.println("SubClass:上海");
+ }
+
+ public void method3(){
+ System.out.println("SubClass:深圳");
+ }
+
+ //知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
+ public void myMethod(){
+ method3();//调用自己定义的重写的方法
+ super.method3();//调用的是父类中声明的
+ //调用接口中的默认方法
+ CompareA.super.method3();
+ CompareB.super.method3();
+ }
+}
+```
+
+
+
+
+
+# 日期API【TODO】
+
+
+
+
+
+# 注解【TODO】
+
+
+
diff --git a/Java/Basis/keyAndDifficultPoints/Generic/泛型.md b/Java/Basis/keyAndDifficultPoints/Generic/泛型.md
new file mode 100644
index 0000000..1e6d7d3
--- /dev/null
+++ b/Java/Basis/keyAndDifficultPoints/Generic/泛型.md
@@ -0,0 +1,2137 @@
+# 简介
+
+## 泛型的优点
+
+1、泛型的本质是为了参数化类型,也就是在在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型,很明显这种方法提高了代码的复用性。
+
+2、泛型的引入提高了安全性,泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型。。
+
+3、在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。
+
+那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
+
+```
+public class GlmapperGeneric {
+ private T t;
+ public void set(T t) { this.t = t; }
+ public T get() { return t; }
+
+ public static void main(String[] args) {
+ // do nothing
+ }
+
+ /**
+ * 不指定类型
+ */
+ public void noSpecifyType(){
+ GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
+ glmapperGeneric.set("test");
+ // 需要强制类型转换
+ String test = (String) glmapperGeneric.get();
+ System.out.println(test);
+ }
+
+ /**
+ * 指定类型
+ */
+ public void specifyType(){
+ GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
+ glmapperGeneric.set("test");
+ // 不需要强制类型转换
+ String test = glmapperGeneric.get();
+ System.out.println(test);
+ }
+}
+```
+
+
+
+## 为什么提高了安全性?
+
+再举例子说明一下
+
+**不安全举例**
+
+```Java
+package keyAndDifficultPoints.Generic;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: youthlql-吕
+ * @Date: 2020/10/15 16:09
+ *
+ * 功能描述:
+ */
+public class Test_Safe {
+
+ public static void main(String[] args) {
+ test();
+ }
+
+ public static void test() {
+ List arrayList = new ArrayList();
+ arrayList.add("aaaa");
+ arrayList.add(100);
+
+ for (int i = 0; i < arrayList.size(); i++) {
+ String s = (String) arrayList.get(i);
+ System.out.println(s);
+
+ }
+ }
+}
+```
+
+结果:
+
+```
+aaaa
+Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
+ at keyAndDifficultPoints.Generic.Test_Safe.test(Test_Safe.java:25)
+ at keyAndDifficultPoints.Generic.Test_Safe.main(Test_Safe.java:16)
+```
+
+很明显的一个类型转换错误。ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
+
+
+
+**泛型提高安全性**
+
+将上面的代码稍微改一下
+
+```Java
+ public static void test01(){
+ List arrayList = new ArrayList<>();
+ arrayList.add("aaaa");
+ //下面代码编译时就直接报错了
+ arrayList.add(100);
+
+ for (int i = 0; i < arrayList.size(); i++) {
+ String s = (String) arrayList.get(i);
+ System.out.println(s);
+
+ }
+ }
+```
+
+通过泛型来提前检测类型,编译时就通不过。
+
+
+
+## 泛型为什么很重要
+
+我们看一下比较常用的JUC包
+
+```Java
+public CompletableFuture thenComposeAsync(
+ Function super T, ? extends CompletionStage> fn) {
+ return uniComposeStage(asyncPool, fn);
+ }
+
+ public CompletableFuture thenComposeAsync(
+ Function super T, ? extends CompletionStage> fn,
+ Executor executor) {
+ return uniComposeStage(screenExecutor(executor), fn);
+ }
+
+ public CompletableFuture whenComplete(
+ BiConsumer super T, ? super Throwable> action) {
+ return uniWhenCompleteStage(null, action);
+ }
+
+ public CompletableFuture whenCompleteAsync(
+ BiConsumer super T, ? super Throwable> action) {
+ return uniWhenCompleteStage(asyncPool, action);
+ }
+```
+
+这些都大量的用到了泛型,如果不把泛型学好,想真正深入源码了解一些东西,可能就完全看不懂了。
+
+# 泛型类
+
+泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
+
+**最普通的泛型类:**
+
+```Java
+package keyAndDifficultPoints.Generic;
+
+/**
+ * @Author: youthlql-吕
+ * @Date: 2020/10/15 16:38
+ *
+ * 功能描述:
+ */
+public class Test_GenericClass {
+ public static void main(String[] args) {
+ test();
+ }
+
+ public static void test(){
+ /**
+ * 1、泛型的类型参数只能是类类型(包括自定义类),不能是简单数据类型(比如int,long这些)
+ * 2、传入的实参类型需与泛型的类型参数类型相同,即为这里的Integer。
+ * 3、new 后面的泛型参数可以省略
+ */
+ Generic genericInteger1 = new Generic(123);
+ Generic genericInteger = new Generic<>(123);
+
+ Generic genericString = new Generic("my");
+
+ System.out.println(genericInteger.getVar());
+ System.out.println(genericString.getVar());
+ }
+
+
+}
+
+/**
+ * 1、此处T虽然可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
+ * 但是为了代码的可读性一般来说:
+ * K,V用来表示键值对
+ * E是Element的缩写,常用来遍历时表示
+ * T就是Type的缩写,常用在普通泛型类上
+ * 2、还有一些不常见的U,R啥的
+ */
+class Generic {
+ //key这个成员变量的类型为T,T的类型由外部指定
+ private T var;
+
+ public Generic(T var) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
+ this.var = var;
+ }
+
+ public T getVar() { //泛型方法getKey的返回值类型为T,T的类型由外部指定
+ return var;
+ }
+}
+
+class MyMap { // 此处指定了两个泛型类型
+ private K key; // 此变量的类型由外部决定
+ private V value; // 此变量的类型由外部决定
+
+ public K getKey() {
+ return this.key;
+ }
+
+ public V getValue() {
+ return this.value;
+ }
+
+ public void setKey(K key) {
+ this.key = key;
+ }
+
+ public void setValue(V value) {
+ this.value = value;
+ }
+};
+```
+
+**结果:**
+
+```
+123
+my
+
+Process finished with exit code 0
+```
+
+
+
+- 定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
+
+
+
+还是以上面的泛型类为例进行测试
+
+```Java
+ public static void test01() {
+ Generic generic = new Generic("我是字符串");
+ Generic generic1 = new Generic(123);
+ Generic generic2 = new Generic(123.123);
+ Generic generic3 = new Generic(false);
+
+ System.out.println(generic.getVar());
+ System.out.println(generic1.getVar());
+ System.out.println(generic2.getVar());
+ System.out.println(generic3.getVar());
+ }
+```
+
+**结果:**
+
+```
+我是字符串
+123
+123.123
+false
+
+Process finished with exit code 0
+```
+
+没有报错,正确输出了。
+
+# 泛型接口
+
+泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
+
+```Java
+interface Info{ // 在接口上定义泛型
+ public T getVar() ; // 定义方法,方法的返回值就是泛型类型
+}
+```
+
+**当实现泛型接口的类,未传入泛型实参时:**
+
+```java
+/**
+ * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
+ * 即:class InfoImpl implements Info
+ * 如果不声明泛型,如:class InfoImpl implements Info,编译器会报错:"Unknown class"
+ */
+class InfoImpl implements Info { // 定义泛型接口的子类
+ private T var;
+
+ public InfoImpl(T var) {
+ this.setVar(var);
+ }
+
+ public void setVar(T var) {
+ this.var = var;
+ }
+
+ public T getVar() {
+ return this.var;
+ }
+}
+```
+
+当实现泛型接口的类,传入泛型实参时:
+
+```Java
+/**
+ * 传入泛型实参时:
+ * 定义一个是先烈实现这个接口,虽然我们只创建了一个泛型接口Info
+ * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
+ * 即:InfoImpl01,public String getVar();中的的T都要替换成传入的String类型。
+ */
+class InfoImpl01 implements Info { // 定义泛型接口的子类
+ private String var;
+
+ public InfoImpl01(String var) {
+ this.setVar(var);
+ }
+
+ public void setVar(String var) {
+ this.var = var;
+ }
+
+ public String getVar() {
+ return this.var;
+ }
+}
+```
+
+# 泛型方法
+
+在java中,泛型类和接口的定义非常简单,但是泛型方法就比较复杂了。
+
+泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。
+
+
+
+**最简单的一个泛型方法**
+
+```Java
+public class Test_GenericMethod {
+
+ public static void main(String[] args) {
+ Test_GenericMethod test_genericMethod = new Test_GenericMethod();
+ Integer integer = test_genericMethod.genericMethod(12);
+ System.out.println(integer);
+ }
+
+ /**
+ * 说明:
+ * 1、public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
+ * 2、只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
+ * 3、表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
+ * 4、 后面的这个T,代表这个方法的返回值类型
+ * 4、与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
+ */
+ public T genericMethod(T a) {
+
+ return a;
+ }
+}
+```
+
+
+
+
+
+## 基本用法(非泛型类中的泛型方法)
+
+下面来细说一下泛型方法
+
+首先说一个误区
+
+```Java
+class Generic01 {
+ private T key;
+
+ public Generic01(T key) {
+ this.key = key;
+ }
+
+
+ /**
+ * 1、这个虽然在方法中使用了泛型,但这并不是一个泛型方法。这只是类中一个普通的
+ * 成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。所以在这个方法中才
+ * 可以继续使用 T 这个泛型。
+ */
+ public T getKey() {
+ return key;
+ }
+
+ /**
+ * 1、这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
+ * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
+ */
+// public E setKey(E key) {
+// this.key = key;
+// }
+
+}
+```
+
+
+
+**基本用法**(非)
+
+```Java
+package keyAndDifficultPoints.Generic;
+
+
+/**
+ * @Author: youthlql-吕
+ * @Date: 2020/10/15 17:46
+ *
+ * 功能描述:
+ */
+public class Test_GenericMethod {
+
+ public static void main(String[] args) {
+ Test_GenericMethod test_genericMethod = new Test_GenericMethod();
+ Generic01 generic01 = new Generic01<>(123);
+
+ Generic01 generic02 = new Generic01<>("AAAAA");
+
+ test_genericMethod.genericMethod_test01(generic01);
+ test_genericMethod.genericMethod_test02(generic02, "我是T");
+
+ test_genericMethod.Method01(generic01);
+ }
+
+ /**
+ * 说明:
+ * 1、public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
+ * 2、只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
+ * 3、表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
+ * 4、 后面的这个T,代表这个方法的返回值类型
+ * 4、与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
+ */
+ public T genericMethod(T a) {
+
+ return a;
+ }
+
+
+ /**
+ * 1、这才是一个真正的泛型方法。
+ * 2、首先在public与返回值之间的必不可少,这表明这是一个泛型方法,并且声明了一个泛型T。
+ * 3、这个T可以出现在这个泛型方法的任意位置.泛型的数量也可以为任意多个
+ */
+ public T genericMethod_test01(Generic01 generic01) {
+ System.out.println("我是genericMethod_test01:" + generic01.getKey());
+ T test = generic01.getKey();
+ return test;
+ }
+
+ public T genericMethod_test02(Generic01 generic01, V value) {
+ System.out.println("我是genericMethod_test02:" + generic01.getKey() + "==> value:" + value);
+
+ T test = generic01.getKey();
+ return test;
+ }
+
+
+ //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic这个泛型类做形参而已。
+ public void Method01(Generic01 extends Number> generic01) {
+ System.out.println(generic01.getKey());
+ }
+
+
+ //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
+ //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
+ public void Method02(Generic01> generic01) {
+ System.out.println(generic01.getKey());
+ }
+
+ /**
+ * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
+ * 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。
+ * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
+ */
+// public T showKeyName(Generic01 generic01, T t) {
+// return t;
+// }
+
+}
+
+```
+
+**结果:**
+
+```
+我是genericMethod_test01:123
+我是genericMethod_test02:AAAAA==> value:我是T
+123
+
+Process finished with exit code 0
+```
+
+## 泛型类中的泛型方法
+
+当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下。
+
+```Java
+package keyAndDifficultPoints.Generic;
+
+/**
+ * @Author: youthlql-吕
+ * @Date: 2020/10/15 20:14
+ *
+ * 功能描述:
+ */
+public class Test_GenericMethod01 {
+ public static void main(String[] args) {
+ Apple apple = new Apple();
+ Person person = new Person();
+
+ GenerateTest generateTest = new GenerateTest();
+ //apple是Fruit的子类,所以这里可以
+ generateTest.show_1(apple);
+ //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
+ //generateTest.show_1(person);
+
+ //使用这两个方法都可以成功
+ generateTest.show_2(apple);
+ generateTest.show_2(person);
+
+ //使用这两个方法也都可以成功
+ generateTest.show_3(apple);
+ generateTest.show_3(person);
+ }
+}
+
+abstract class GenericFruit {
+
+}
+
+class Fruit {
+ @Override
+ public String toString() {
+ return "fruit";
+ }
+}
+
+class Apple extends Fruit {
+ @Override
+ public String toString() {
+ return "apple";
+ }
+}
+
+class Person {
+ @Override
+ public String toString() {
+ return "Person";
+ }
+}
+
+class GenerateTest {
+
+ public void show_1(T t) {
+ System.out.println(t.toString());
+ }
+
+
+ /**
+ * 1、在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
+ * 2、由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
+ */
+ public void show_3(E t) {
+ System.out.println(t.toString());
+ }
+
+
+ /**
+ * 1、在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T
+ * 不是同一种类型。也就是说main函数中使用的时候也可以是不一样的泛型类型
+ */
+ public void show_2(T t) {
+ System.out.println(t.toString());
+ }
+}
+```
+
+
+
+**结果:**
+
+```
+apple
+apple
+Person
+apple
+Person
+
+Process finished with exit code 0
+```
+
+
+
+## 泛型方法与可变参数
+
+再看一个泛型方法和可变参数的例子:
+
+```Java
+public class Test_GenericMethod02 {
+ public static void main(String[] args) {
+ print("123",753,123.12);
+ }
+
+
+ //必须是三个点
+ public static void print(T... args) {
+ for (T t : args) {
+ System.out.println(t);
+ }
+ }
+}
+```
+
+**结果:**
+
+```
+123
+753
+123.12
+
+Process finished with exit code 0
+```
+
+## 静态方法与泛型
+
+静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:**静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。**
+
+即:**如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法** 。
+
+ public class StaticGenerator {
+
+ /**
+ * 1、如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
+ * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
+ * 如:public static void show(T t){..},此时编译器会提示错误信息:
+ "StaticGenerator cannot be refrenced from static context"
+ * 2、泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。换句话说,
+ * 泛型方法所属的类是不是泛型类都没有关系。
+ * 3、泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在初始化类时确定,所以无所谓
+ */
+ public static List copyFromArrayToList(E[] arr){
+
+ ArrayList list = new ArrayList<>();
+
+ for(E e : arr){
+ list.add(e);
+ }
+ return list;
+
+ }
+ }
+
+
+
+## 细枝末节
+
+> 可能合上面的有一些重复
+
+1、泛型异常类
+
+```
+//异常类不能声明为泛型类,编译报错
+class MyException extends Exception{
+}
+```
+
+
+
+2、
+
+```Java
+package keyAndDifficultPoints.Generic.Minutiae;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: youthlql-吕
+ * @Date: 2020/10/15 22:28
+ *
+ * 功能描述:
+ */
+public class Test_Minutiae1 {
+}
+class Order {
+
+ String orderName;
+ int orderId;
+
+ //类的内部结构就可以使用类的泛型
+
+ T orderT;
+
+ public Order(){
+ //编译不通过
+// T[] arr = new T[10];
+ //编译通过
+ T[] arr = (T[]) new Object[10];
+ }
+
+ public Order(String orderName,int orderId,T orderT){
+ this.orderName = orderName;
+ this.orderId = orderId;
+ this.orderT = orderT;
+ }
+
+ //如下的三个方法都不是泛型方法
+ public T getOrderT(){
+ return orderT;
+ }
+
+ public void setOrderT(T orderT){
+ this.orderT = orderT;
+ }
+
+ @Override
+ public String toString() {
+ return "Order{" +
+ "orderName='" + orderName + '\'' +
+ ", orderId=" + orderId +
+ ", orderT=" + orderT +
+ '}';
+ }
+ //静态方法中不能使用类的泛型。
+// public static void show(T orderT){
+// System.out.println(orderT);
+// }
+
+ public void show(){
+ //编译不通过
+// try{
+//
+//
+// }catch(T t){
+//
+// }
+
+ }
+
+
+ /**
+ * 2、泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。换句话说,
+ * 泛型方法所属的类是不是泛型类都没有关系。
+ * 3、泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在初始化类时确定,所以无所谓
+ */
+ public static List copyFromArrayToList(E[] arr){
+
+ ArrayList list = new ArrayList<>();
+
+ for(E e : arr){
+ list.add(e);
+ }
+ return list;
+
+ }
+}
+class SubOrder extends Order {//SubOrder:不是泛型类
+
+
+ public static List copyFromArrayToList(E[] arr) {
+
+ ArrayList list = new ArrayList<>();
+
+ for (E e : arr) {
+ list.add(e);
+ }
+ return list;
+
+ }
+
+}
+
+class SubOrder1 extends Order {//SubOrder1:仍然是泛型类
+}
+
+```
+
+
+
+
+
+
+
+# 泛型数组
+
+```Java
+package keyAndDifficultPoints.Generic;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: youthlql-吕
+ * @Date: 2020/10/15 12:10
+ *
+ * 功能描述: 测试泛型数组
+ */
+public class Test_GenericArray {
+
+ public static void main(String[] args) {
+ test02();
+ }
+
+ public static void test() {
+ //编译错误
+// List[] ls = new ArrayList[10];
+ }
+
+
+ public static void test01() {
+ //这样声明是正确的
+ List>[] ls = new ArrayList>[10];
+ ls[1] = new ArrayList();
+
+ //这样写编译就报错了
+// ls[1].add(1);
+
+ }
+
+ /**
+ * 下面是sun官方文档里写的。其实不用太纠结,平时泛型虽然用的多,但也不会用的这么奇葩。
+ */
+ public static void test02(){
+ List>[] lsa = new List>[10]; // OK, array of unbounded wildcard type.
+ Object o = lsa;
+ Object[] oa = (Object[]) o;
+ List li = new ArrayList();
+ li.add(new Integer(3));
+ oa[1] = li; // Correct.
+ Integer i = (Integer) lsa[1].get(0); // OK
+ System.out.println(i);
+ }
+
+ //正确
+ public static void test03() {
+ List[] ls = new ArrayList[10];
+ ls[0] = new ArrayList();
+ ls[1] = new ArrayList();
+
+ ls[0].add("x");
+
+ }
+
+}
+
+```
+
+[sun文档](http://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html)
+
+
+
+# 泛型在继承方面的细节
+
+直接看代码注释
+
+```java
+ /*
+ 1. 泛型在继承方面的体现
+
+ 虽然类A是类B的父类,但是G 和G二者不具备子父类关系,二者是并列关系。
+
+ 补充:类A是类B的父类,A 是 B 的父类
+
+ */
+ @Test
+ public void test1() {
+ /**
+ * 下面是有继承关系,所以可以赋值
+ */
+ Object obj = null;
+ String str = null;
+ obj = str;
+
+ Object[] arr1 = null;
+ String[] arr2 = null;
+ arr1 = arr2;
+
+ /**
+ * 下面属于并列关系,无继承关系。无法赋值
+ */
+
+ //编译不通过
+// Date date = new Date();
+// str = date;
+ List