java8 的接口新特性
先看看下面的接口的默认方法和静态方法,感受一下:
public interface Java8Interface {
//默认方法
default String createName() {
//逻辑实现
return "xxxx";
}
//静态方法
public static LocalDateTime getLocalDate() {
return LocalDateTime.now();
}
}
- 默认实现方法
如果多继承状态下有默认方法,遵循 3 条规则
类胜与接口,如果在继承链中有方法体或抽象的方法声明,可以忽略接口中的定义
子类胜于父类。如果一个接口继承了另外一个接口,而且两个接口都定义了相同的默认方法,那么子类中的默认方法胜出
如上都不适用,要么子类实现该方法,要么将方法声明为抽象方法
- 静态方法
为了帮助编写类库的开发人员,Java 8 增加类接口静态方法新特性
lambda 表达式
函数式接口
函数式接口是 lambda 表达式的类型,其特点是只有一个抽象方法的接口,但是可以有 default 类型的方法实现。
JDK 提供几个常用的函数式接口
- Predicate
:通过传入一个对象T,返回是否匹配
@FunctionalInterface //这个注解是作用是为了标识该接口是函数式接口,编译时可以检查语法是否准确
public interface Predicate {
//唯一一个抽象方法
boolean test(T t);
//其他 default 的方法实现省略。。。
}
- Consumer
:传入对象 T,没有返回值,是一种消费型接口
@FunctionalInterface
public interface Consumer {
void accept(T t);
}
- Function
:一个入参T,返回值 R
@FunctionalInterface
public interface Function {
R apply(T t);
}
- Supplier
: 没入参,返回 T
@FunctionalInterface
public interface Supplier {
T get();
}
lambda 表达式语法
(参数列表) -> {执行语句}
前面说了函数式接口是 lamdba 表达式的类型,所以其满足以下要求:
lamdba 表达式的参数列表对应函数式接口方法的参数列表
lambda 的返回类型对应函数式接口方法的返回类型
例如 JDK 提供的 Predicate
Predicate predicate = (Integer t) -> {return t > 100;};
System.out.println(predicate.test(300));//true
其中为了更加简洁,以下情况可以省略一些代码:
- 参数类型可省略
Predicate predicate = (t) -> {return t > 100;};
- 入参只有一个,括号可省略
Predicate predicate = t -> {return t > 100;};
- 如果表达式只有一条语句,大括号和 'return' 都可以省略
Predicate predicate = t -> t > 100;
说明:都没有表明类型,程序是怎么知道的?其实是在编译的时候,通过前面指定的泛型类型来推断的,例如上面 Predicate
中的 T 就是 Integer,进而推断出 t 是 Integer 类型。####Lambda 另外一个表达方式
方法引用
如果 lambda 体中的内容已经有方法实现了,我们可以直接利用该方法代替。
语法如下:
使用规则:
- lamdba 体中调用方法的入参列表和返回值类型,要与函数接口抽象方法的一致!
- Lamdba 参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用类::实例方法名
- 对象:实例方法名
//1. 对象::实例方法名
@Test
public void methodRef() {
Student student = new Student();
//lambda 表达式方式
Consumer con = (x) -> student.setName(x);
//方法引用方式
Consumer con2 = student::setName;
}
- 类:静态方法名
//2. 类::静态方法名
@Test
public void test2() {
Comparator comparator = (x,y) -> Integer.compare(x,y);
//方法引用方式
Comparator comparator2 = Integer::compare;
}
- 类:实例方法名
//3. 类::实例方法
//需要满足:Lamdba 参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时
public void test3() {
BiPredicate biPredicate = (x,y) -> x.equals(y);//x 为参数列表的第一个,并且是调用者,第二个参数y为方法的参数
//方法引用方式
BiPredicate biPredicate2 = String::equals;
}
构造器引用
- 格式: Type::new
- 规则: 调用的构造器参数列表要与函数式接口抽象方法的参数列表保存一致
例如:
public void test4() {
Supplier studentSupplier = () -> new Student();\
//构造器引用:因为Supplier的抽象方法 get(),参数列表为空,所以调用Student的空参数构造器
Supplier studentSupplier2 = Student::new;
}
数组引用
格式: Type[]::new
//数组引用
public void test5() {
Function function = (x) -> new String[x];
//数组引用方式
Function function2 = String[]::new;
}
Stream
对比
举个栗子:过滤string 集合中包含 "sam" 的元素
以前的写法:
List lists = new ArrayList<>();
List result = new ArrayList<>();
for(String s : lists) {
if (s.startsWith("sam")) {
result.add(s);
}
}
Stream 的写法:是不是很爽?
List lists = new ArrayList<>();
List result = lists.stream().filter(s -> s.startsWith("sam")).collect(Collectors.toList());
Stream 两种求值
一种是惰性求值,一种是及早求值。判断是标准是:如果返回值是Stream,那么就是惰性求值,否则是及早求值。只有遇到及早求值才会执行,否则不会执行。
- 只有惰性求值不会执行的:
List students = Arrays.asList(new Student("sam", 18, "男"));
Stream stream = students.stream().map(student -> {
System.out.println("name:" + student.getName());//没有输出
return student.getName();
});
- 遇到及早求值 collect
List students = Arrays.asList(new Student("sam", 18, "男"));
long count = students.stream().map(student -> {
System.out.println("name:" + student.getName());//输出
return student.getName();
}).count();
Stream 常用操作
- map: 输入 R 类型,返回 T 类型,用于类型转换。属于惰性求值
List names = students.stream().map(student -> student.getName()).collect(Collectors.toList());
- collect: 从 stream 中生成一个集合,属于及早求值。一般结合 Collectors.xxx,如上面例子的Collectors.toList(),它会根据返回值的类型自动生成对应的类型。如果需要指定具体类型,可以通过 toCollection ,它接受一个函数作为参数,例如:
collect(Collectors.toCollection(TreeSet::new));//toCollection 参数是Supplier 函数式接口\
- filter:过滤出满足条件的数据,也是 filter 表达式返回 true 的数据走到下一步操作。属于惰性求值
List students = students.stream().filter(student -> student.getName().startsWith("sam")).collect(Collectors.toList());
- flatMap: 基于 map 的功能,并且将返回多个 stream 连接成一个 stream。属于惰性求值
List students = Arrays.asList(new Student("sam", 18, "男"));
List> lists = Arrays.asList(students);
List allStudent = lists.stream().flatMap(students1 -> students1.stream()).collect(Collectors.toList());
//students1 -> students1.stream() : flatMap 将多个 stream 合成一个
- max 和 min: 求集合中最大值和最小值。属于及早求值
Student student1 = new Student("sam", 18, "男");
Student student2 = new Student("lilian", 28, "女");
List students = Arrays.asList(student1, student2);
//求年龄最大的学生
Optional max = students.stream().max(Comparator.comparing(Student::getAge));
System.out.println(max.get());//lilian
- reduce: 将一组的值经过操作(加减乘除等)生成一个值。reduce(a,b) 其中 a 表示上次的操作结果,b 表示下次操作的数,例如:
Stream integerStream = Stream.of(1, 2, 3, 4);
//计算集合中数字总和
Integer total = integerStream.reduce(0,(a, b) -> a + b);//0 表示初始值,可选
System.out.println(total);//10
Optional
Optional 是用来替换 null 值的,用于鼓励程序猿适当检查变量是否为空,并提供更多简便的操作 API.
常见用法
Optional name = Optional.of("sam");
if (name.isPresent()) {//如果存在
System.out.println(name.get());//取元素之前,如果不能确定是否一定有值,一定要判断是否存在
} else {
//orElse 用来设置默认值
String d = name.orElse("default");
//orElseGet 用于备选值计算太繁琐而提供的
String createDefault = name.orElseGet(() -> {
return "sam";
});
}