Java8 优势:速度快、代码更少(增加了新的语法 Lambda 表达式)、强大的 Stream API、便于并行、最大化减少空指针异常 Optional;
一、Lambda 表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以取代大部分的匿名内部类,可以写出更简洁、更灵活的代码。尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到提升。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。 【1】从匿名类到 Lambda 的转换:虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。
jdk8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。后续有专门的介绍。
//匿名类
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.printf("Hello World!");
}
};
/**
*1.简化参数类型,可以不写参数类型,但是必须所有参数都不写
*2.简化参数小括号,如果只有一个参数则可以省略参数小括号
*3.简化方法体大括号,如果方法条只有一条语句,则可以胜率方法体大括号(如下案例)
*4.如果方法体只有一条语句,并且是 return 语句,则可以省略方法体大括号和rerun关键字:X x= a -> a+3;
*Lambda 表达式展示:
*/
Runnable runnable2 = ()-> System.out.printf("Lambda 表达式");
【2】原来使用匿名内部类作为参数传递到 Lambda 表达式:
//原来使用匿名内部类作为参数传递
TreeSet ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(),o2.length());
}
});
//Lambda 表达式作为参数传递
TreeSet<String> ts2 = new TreeSet<>((o1,o2)-> Integer.compare(o1.length(),o2.length()));
【3】Lambda 表达式语法:Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” ,该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
■ 左侧:指定了 Lambda 表达式需要的所有参数; ■ 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能;
【语法格式一】:无参,无返回值,Lambda 体只需要一条语句;
Runnable runnable2 = ()-> System.out.printf("Lambda 表达式");
【语法格式二】:Lambda 需要一个参数;
Consumer<String> fun = (args) -> System.out.printf(args);
【语法格式三】:Lambda 只需要一个参数时,参数的小括号可以省略;
Consumer<String> fun = args -> System.out.printf(args);
【语法格式四】:Lambda 需要两个参数,并且有返回值;
BinaryOperator<Long> bo = (x,y)->{ System.out.printf("实现函数接口方法"); return x+y;};
【语法格式五】:当 Lambda 体只有一条语句时,return 与大括号可以省略;
BinaryOperator<Long> bo = (x,y) -> x+y;
【语法格式六】:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”:根据上下文环境推断参数类型;
BinaryOperator<Long> bo = (Long x,Long y)->{
System.out.printf("实现函数接口方法");
return x+y;
};
【4】遍历集合: 可以调用集合的 forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。Consumer 接口是 jdk 为我们提供的一个函数式接口。
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
//lambda表达式 方法引用
list.forEach(System.out::println);
list.forEach(element -> {
if (element % 2 == 0) {
System.out.println(element);
}
});
【5】删除集合:通过removeIf(Predicate<? super E> filter) 方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。
ArrayList<Item> items = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
items.removeIf(ele -> ele.getId() == 3);
【6】集合内元素的排序:若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
ArrayList<Item> list= new ArrayList<>();
Collections.addAll(list, 6,27,7,4,2);
list.sort((o1,o2) -> o1.getId() - o2.getId());
二、函数式接口
【1】只包含一个抽象方法的接口,称为函数式接口; 【2】你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。 【3】我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,说明这个接口是一个函数式接口。
@FunctionalInterface
public interface MyLambda<T> {
public T getValue(T t);
}
public String toUpperString(MyLambda<String> mf, String str){
return mf.getValue(str);
}
//为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与 Lambda 表达式兼容的函数式接口的类型
String newStr = toUpperString((str)-> str.toUpperCase(),"abcde");
【4】Java 内置四大核心函数式接口 :
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t); |
Supplier 供给型接口 | 无 | T | 返回类型为 T 对象,包含方法:T get(); |
Function<T, R> 函数型接口 | T | R | 对类型为 T 的对象应用操作,并返回结果。结果时 R 类型的对象。包含方法:R apply(T t) |
Predicate 断定型接口 | T | boolean | 确定类型为 T 的对象是否满足某约束,并返回 Boolean 值,包含方法 Boolean test(T t) |
public class Java8Tester {
public static void main(String args[]){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// Predicate<Integer> predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true
System.out.println("输出所有偶数:");
eval(list, n-> n%2 == 0 );
}
public static void eval(List<Integer> list, Predicate<Integer> predicate) {
for(Integer n: list) {
if(predicate.test(n)) {
System.out.println(n + " ");
}
}
}
}
三、方法引用与构造器引用
【1】方法引用:当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用中方法的参数列表保持一致!)方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。如下三种主要使用情况:使用方法引用的时候需要保证引用方法的参数列表和返回值类型与我们当前所要实现的函数式接口方法的参数列表和返回值类型保持一致 ①、对象::实例方法;②、类::静态方法;③、类::实例方法;
(x) -> System.out.println(x);
//等同于
System.out::println
BinaryOperator<Double> bo = (x,y) -> Math.pow(x,y);
//等同于
BinaryOperator<Double> bo = Math::pow;
compare((x,y) -> x.equals(y),"abcd","abcd");
//等同于
compare(String::equals,"abc","abc");(x) -> System.out.println(x);//等同于System.out::printlnBinaryOp