重点:
(1)Lambda表达式的基本使用方式(什么时候使用)。
(2)泛型在类/接口/方法确定具体类型的时机。
(3)单列集合的体系。
(4)单列集合的通用方法与通用遍历方式。
1. Lambda表达式(重要)
1.1 Lambda表达式的概念
Lambda表达式就是一种语法格式。
作用:作为一个方法的具体逻辑的载体。(函数式编程思想。)
函数式编程思想:可明确的就是可省略的,传递方法的逻辑作为参数。
1.2 Lambda表达式的格式
Lambda表达式是作为一个方法的逻辑载体而存在的。
//(方法的形式参数)-> {方法体;}
goSwim(() -> {System.out.println("游泳的方式自由泳!");});
1.3 Lambda表达式的使用前提
Lambda表达式并非可以随便使用的,Lambda表达式的使用有一定满足使用前提。
Lambda表达式只能作为一个方法的具体逻辑进行传递。
Lambda只能应用于函数式接口,当方法的参数是一个函数式接口的时候,可以作为参数传递其他的情况不可以使用!
函数式接口:
函数式接口就是有且仅有一个抽象方法的接口,因为只有一个而且肯定会有一个抽象方法,所以Lambda表达式只需要作为该抽象方法的具体逻辑存在即可。
一般而言可以基于在接口标记上一个注解@FunctionalInterface进行是否是函数式接口的校验。
1.4 Lambda表达式作为不同方式方法的逻辑体现
Lambda表达式作为(无参数无返回值)的方法逻辑体现
useInterA(() -> {
System.out.println("Lambda表达式实现了methodA方法!");
});
Lambda表达式作为(无参数有返回值)的方法逻辑体现
useInterB(() -> {
//Lambda表达式中不体现方法的返回值,但是如果有返回值还是要编写return 返回值数据;
return (int) Math.ceil(Math.random() * 100);
});
//Math.ceil()向上取整的方法
//Math.foor()向下取整的方法
Lambda表达式作为(单参数无返回值)的方法逻辑体现
useInterC((Integer a) -> {
//要实现的方法的形参列表是什么,Lambda表达式的()里面就写什么
System.out.println("Lambda表达式实现methodC的逻辑是(参数的基础上*10) : " + (a * 10));
});
Lambda表达式作为(多参数无返回值)的方法逻辑体现
useInterD((Integer a, Integer b) -> {
System.out.println("Lambda表达式实现methodD的逻辑是相减 : " + (a - b));
});
Lambda表达式作为(有参数有返回值)的方法逻辑体现
useInterE((String foodOne, String foodTwo) -> {
return foodOne + foodTwo + "汤";
});
1.5 Lambda表达式的简化方式
在满足前提条件的情况下Lambda表达式还可以进行简化。
核心的思想还是可明确的可省略。
- 方法参数类型可以省略。
全部的参数类型都可以省略,要么都省略要么都不省略。
- 参数只有一个的时候,小括号也可以省略。
如果有多个参数或者没有参数,小括号不能省略,其次省略的前提是参数类型也要省略。
- 方法体只有一行代码时,大括号也可以省略。
1.6 Lambda表达式和匿名内部类的区别
问题:Lambda表达式和匿名内部类都可以在方法的参数是函数式接口时使用,能不能认为这两者是一个东西?
不是
区别:
- 思想层面的区别
Lambda表达式是函数式思想,强调的是做什么!匿名内部类是面向对象的思想,强调是怎么做。
- 应用前提的区别
匿名内部类可以在参数是接口的时候使用,抽象父类也可以,具体类也是可以的。
Lambda表达式只可以在参数是接口时使用,抽象父类是不可以的。
- 接口的限制
匿名内部类作为接口的实现类对象,接口中有多少个抽象方法都是可以的。
Lambda表达式只能针对于函数式接口有且只有一个抽象方法。
- 底层逻辑的区别
匿名内部类实际在编译的时候,java会根据重写的方法的各种情况,动态生成一个.class文件。
Lambda表达式不会生成任何内容,会在具体使用的时候生成动态加入到方法运行中。
结论:
匿名内部类写起来方便,看起来啰嗦,Lambda表达式看起来简洁,写起来麻烦。
先写匿名内部类,基于IDEA的快捷方式转化为Lambda表达式。
不要为了使用Lambda表达式而用Lambda表达式。
2. 方法引用
2.1 方法引用的概述
Lambda表达式是在java8的时候加入的。方法引用是Lambda表达式的亲兄弟。
概述:
方法引用也是一种语法格式,是在Lambda表达式在满足一定的要求下进行的简化语法格式。
在方法引用中有一个核心符号**::方法引用符号**。
2.2 方法引用的格式—静态方法引用
静态方法引用格式:
//类名::静态方法
静态方法使用前提:
当Lambda表达式(只)调用了某个类的静态方法,并且所实现的方法的形式参数全部传递给了静态方法作为实际参数。即可使用静态方法引用。
Lambda表达式:
Arrays.sort(studentArray, (o1, o2) -> Student.sCompareStudent(o1, o2));
方法引用
Arrays.sort(studentArray,Student::sCompareStudent);
2.3 方法引用的格式-实例方法引用
实例方法引用格式:
//对象名::实例方法
实例方法使用前提:
当Lambda表达式中只是使用了一个对象调用了一个成员方法,并且Lambda表示实现的方法的形参全部传递给了成员方法作为实参。那么可以优化为实例方法引用。
Lambda表达式:
Student stu = new Student();
Arrays.sort(studentArray, (o1, o2) -> Student.sCompareStudent(o1, o2));
方法引用
Student stu = new Student();
Arrays.sort(studentArray,stu::sCompareStudent);
2.4 方法引用的格式-特殊类型方法引用
特殊类型方法引用格式:
//类名::实例方法名
实例方法使用前提:
当Lambda表达式中只是使用了一个对象调用了一个成员方法,并且Lambda表达式实现的方法的第一个形参用于调用方法,其余的参数作为方法的实参传递。那么可以优化特殊类型方法引用。
Lambda表达式:
Arrays.sort(studentArray,(o1,o2) -> o1.compareStudent2(o2));
方法引用
Arrays.sort(studentArray, Student::compareStudent2);
2.5 方法引用的格式-构造方法引用
构造方法引用格式:
//类名::new
构造方法使用前提:
当Lambda表达式中只是创建了一个对象,并且所实现的方法的所有形参传递了构造方法中作为实参,那么就可以优化为构造器引用。
Lambda表达式:
useCreate(name -> new Student(name));
方法引用
useCreate(Student::new);
虽然学习了Lambda也学习了方法引用,但实际上写代码的时候先匿名再转换。能转哪个转哪个,转不了说明不满足情况,莫强求!
3. 泛型
3.1 泛型的概念
概念:泛型是一种技术,是可以一种提前使用未知数据类型占位,然后使用时再进行明确的技术。(泛型在Java5出现的)
如果没有泛型(Java5出现的技术)
public ArrayList {
public void add(Object obj){} //只能用Object进行预先的占位
public Object get(int index){}
}
但是由于多态的限制,参数也好返回值也好,只能使用实现了Object/继承自Object的方法,不能用特有方法。
想要用特有方法就需要强转。=> 类型转换异常。
如果有泛型
public ArrayList<E> {
public void add(E obj){}
public E get(int index){}
}
如果使用的时候明确了E这个未知类型是String,所有的E都会发生改变!
public ArrayList {
public void add(String obj){}
public String get(int index){}
}
3.2 泛型的好处
好处:
- 可以预先的使用未知的数据类型进行占位,避免了使用Object需要频繁强转的弊端。
- 提供了在明确了具体类型后对数据进行约束和限制的功能。
Java将可以预先使用未知数据类型的位置进行分类。泛型类 / 泛型方法 / 泛型接口。
3.3 泛型类的声明与泛型确定时机
泛型类的声明格式:
public class 类名<泛型变量,泛型变量>{}
泛型变量一般用一个大写字母来声明,如果有多个不确定的数据类型,那么可以声明多个泛型变量。
代码示例:
public class Box<T> {
//成员变量可以使用T预先作为数据类型
private T data;
//成员方法可以使用T预先作为方法的参数类型
public void setData(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
泛型类的泛型明确时机:
在创建对象的时候手动明确(泛型的具体类型需要手动声明出来)。
类名<泛型具体类型> 变量名 = new 类名<>();
注意:
(1)发现泛型,要用一个类,得知道这个类是不是泛型类。
(2)如果是泛型类,创建对象的时候要给出具体的类型才可以使用。
(3)实际在创建对象的时候没有给出泛型的具体类型,依然可以用。默认具体类型是Object类型。
3.4 泛型方法的声明与泛型明确的时机
泛型方法的定义格式:
//修饰符 <泛型变量> 返回值类型 方法名(形式参数){}
public static <T> void addElement(ArrayList<T> list, T data) {
list.add(data);
}
泛型方法的泛型明确时机:
泛型方法的泛型的具体类型不需要手动声明,当调用方法的时候系统会根据传递的参数类型分析出泛型的类型是什么。
注意:
(1)调用泛型方法的时候,如果发现是一个泛型方法,参数还是按照逻辑传递即可。
(2)泛型方法一般的目的为了限制多个参数的类型相同,参数与返回值的类型相同。
3.5 泛型接口的声明与泛型明确的时机
泛型接口的定义格式:
public interfac 接口名<泛型变量>{}
代码示例:
public interface Machine<T> {
//机器的核心功能就是制作东西(并不能明确制作出什么东西)
T make();
}
泛型接口明确泛型具体时机:
(1)当实现类实现接口的时候明确泛型的具体类型。
public class 实现类名 implements 接口名<具体类型> {}
public class IceCreamMachine implements Machine<IceCream> {
@Override
public IceCream make() {
return new IceCream("香草");
}
}
useMachine(new Machine<IceCream>() {
@Override
public IceCream make() {
return new IceCream("巧克力");
}
});
public static void useMachine(Machine<IceCream> machine) {
IceCream iceCream = machine.make();
}
(2)当实现类实现接口的时候依然不明确泛型的具体类型。
那么实现类也必须是一个泛型类。
public class 实现类名<泛型变量> implements 接口名<泛型变量> {}
public class JuiceMachine<T> implements Machine<T> {
@Override
public T make() {
return null;
}
}
3.6 泛型通配符与上限下限
泛型支持多态吗?
method("abc");
methdo(10);
public static void method(Object obj){} //多态
ArrayList<String> strList = new ArrayList<>();
method(strList);
public static void method(ArrayList<Object> list){}
泛型不支持多态! 但是如果想要让泛型的拓展性更强一些。可以使用泛型通配符。
泛型通配符 ? 当使用?作为参数的泛型类型,接收任意类型参数。
ArrayList<String> strList = new ArrayList<>();
method(strList);
public static void method(ArrayList<?> list){}
如果使用?作为泛型,泛型太大了。既可以拓展又不要范围特别大。
<? extends 类名> : 上限(该类或者该类的子类)
<? super 类名> : 下限(该类或者该类的父类)
Person
-> Teacher
-> JavaTeacher
-> PythonTeacher
-> Student
-> JavaStudent
-> PythonStudent
声明一个泛型,可以接收所有的学生类型 ArrayList<? extends Student>
声明一个泛型,可以接收所有的老师类型 ArrayList<? extends Teacher>
声明一个泛型,可以接收所有人类型 ArrayList<?> / ArrayList<? extends Person>
声明一个泛型,可以接收所有的Java学生类型 ArrayList<JavaStudent>
泛型的具体类型必须是引用数据类型!