Java8新特性
一、Lambda表达式
-
本质:函数式接口的实例
-
格式
① 操作符 - >
② 左 边:Lambda参数列表 (其实就是接口中的抽象方法的形参列表)
③ 右 边:Lambda体 (其实就是重写的抽象方法的方法体) -
类型
方式一
@Test public void test1() { // 方式一:无参无返回值 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("我爱北京天安门"); } }; r1.run(); System.out.println("--------------------"); // Lambda表达式左侧只需要保留括号即可 Runnable r2 = () -> System.out.println("我爱你中国"); r2.run(); }
方式二
public void test2() { // 方式二:有一个参数,无返回值 Consumer con1 = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; con1.accept("谎言和誓言的区别是什么"); System.out.println("-----------------------"); // Lambda表达式左侧为数据类型+参数 Consumer<String> con2 = (String s) -> System.out.println(s); con2.accept("谎言是听的人信了,誓言是说的人信了"); }
方式三
public void test3() { // 方式三:参数的数据类型可以省略,因为编译器可以推断,称为“类型推断” Consumer<String> con1 = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; con1.accept("谎言和誓言的区别是什么"); System.out.println("-----------------------"); // Lambda表达式左侧无需加参数类型 Consumer<String> con2 = (s) -> System.out.println(s); con2.accept("谎言是听的人信了,誓言是说的人信了"); }
方式四
public void test4() { // 方式四:有一个参数,且可以“类型推断” Consumer<String> con1 = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; con1.accept("谎言和誓言的区别是什么"); System.out.println("-----------------------"); // Lambda表达式左侧无需加参数类型及括号 Consumer<String> con2 = s -> System.out.println(s); con2.accept("谎言是听的人信了,誓言是说的人信了"); }
方式五
public void test5() { // 方式五 有两个及以上参数,多条执行语句,并有返回语句 Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { System.out.println(o1); System.out.println(o2); return Integer.compare(o1,o2); } }; int compare1 = com1.compare(21, 12); System.out.println(compare1); System.out.println("--------------------"); // Lambda表达式 左侧不可以省略括号 Comparator<Integer> com2 = (o1,o2) -> { System.out.println(o1); System.out.println(o2); return Integer.compare(o1,o2); }; int compare2 = com2.compare(32, 43); System.out.println(compare2); }
方式六
@Test public void test6() { // 方式六 有两个及以上参数,一条执行语句,并且是返回语句 Comparator<Integer> com1 = new Comparator<Integer>() { @Contract(pure = true) @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }; int compare1 = com1.compare(21, 12); System.out.println(compare1); System.out.println("--------------------"); // Lambda表达式 右侧可以同时省略大括号和return关键字 Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2); int compare2 = com2.compare(32, 43); System.out.println(compare2); }
-
总结
① 左边:Lambda参数列表的参数类型可以省略;如果只有一个参数,小括号也可以省略
② 右边:Lambda体一改使用一对{ }包裹;如果Lambda体只有一条执行语句(可能是return语句),则可以省略这对{ } ,并同时省略return关键字
二、函数式接口
-
什么是函数式接口
① 只包含一个抽象方法的接口,称为函数式接口
② 可通过Lambda表达式创建该接口的对象
③ 可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口。同时javadoc也会包含一条声明,说明该接口是一个函数式接口。
④ 在java.util.function包下定义了Java8丰富的函数式接口 -
如何理解函数式接口
① Java在诞生之日起就一致倡导“一切皆对象”,面向对象(OOP)就是一切。随着python、Scala等语言的兴起和新技术的挑战,Java不得不做出调整,即Java也可以支持OOF(面向函数编程)
② 在函数式编程语言当中,函数被当成是一等公民,其Lambda表达式的类型也是函数。但在Java8中,Lambda表达式仍然是对象,只是其必须依附一类特别的对象类型 - - 函数式接口
③ 以前用的匿名实现类现在都可以用Lambda表达式书写 -
Java内置四大核心函数式接口
测试程序
@Test public void test2() { List<String> list = Arrays.asList("北京", "南京", "天津", "东京", "普京"); List<String> list2 = findList(list, new Predicate<String>() { @Override public boolean test(String s) { return s.contains("京"); } }); System.out.println(list2); System.out.println("将上述函数式接口转变为Lambda表达式"); // findList方法中的参数2已经有了泛型,所以直接转Lambda时就类型推断了 List<String> list3 = findList(list, s -> s.contains("京")); System.out.println(list3); } public List<String> findList(List<String> list, Predicate<String> predicate) { ArrayList<String> list1 = new ArrayList<>(); for (String s : list) { if (predicate.test(s)) { list1.add(s); } } return list1; }
-
其它函数式接口
三、方法引用
-
使用情境:当要传递给Lambda体的操作,已经有实现的方法了,且该实现方法和Lambda表达式所代表的函数式接口中的唯一抽象方法有相同的泛型参数列表格式,则可以使用方法的引用
-
方法的引用:本质上就是Lambda表达式,而Lambda表达式本质是函数式接口的实例。所以,方法引用也是函数式接口的实例
-
使用格式 - - 类(对象): :调用的方法名 (参数列表不需要写,因为方法的参数使用泛型,和类是统一的)
① 对象::非静态方法 情况1
② 类::静态方法 情况2
③ 类::非静态方法 情况3 -
方法引用的要求:
① 函数式接口中的抽象方法的形参列表及返回值类型与方法引用的方法的形参列表和返回值类型都使用相对应的泛型格式。(适用于情况1和情况2)
② 方法引用中的方法的的引用者是函数式接口中的抽象方法的形参列表中的参数1,则使用该参数1对应的常用类 作为 引用类 来进行方法引用(适用于情况3) -
程序示例
// 情况一:对象 :: 实例方法 //Consumer中的void accept(T t) //PrintStream中的void println(T t) @Test public void test1() { /** *当要传递给Lambda体的操作,已经有实现的方法了,且该实现方法和Lambda表达式所代表的 *函数式接口中的唯一抽象方法有相同的泛型参数列表格式,则可以使用方法的引用 */ Consumer<String> con1 = s -> System.out.println(s); con1.accept("我爱中国"); System.out.println("将上述Lambda表达式转变为方法引用"); // 1.获取该实现方法的对象或类 PrintStream ps = System.out; // 2.使用方法引用格式 Consumer<String> con2 = ps :: println; con2.accept("I love china"); } //Supplier中的T get() //Employee中的String getName() @Test public void test2() { Employee employee = new Employee(1001, "Tom"); Supplier<String> sup = () -> employee.getName(); System.out.println(sup.get()); System.out.println("将上述Lambda表达式转变为方法引用"); Supplier<String> sup1 = employee :: getName; System.out.println(sup1.get()); } // 情况二:类 :: 静态方法 //Comparator中的int compare(T t1,T t2) //Integer中的int compare(T t1,T t2) @Test public void test3() { Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2); com1.compare(12,45); System.out.println("将上述Lambda表达式转变为方法引用"); // 使用基本数据类型Integer类 Comparator<Integer> com2 = Integer :: compare; com2.compare(34,12); } //Function中的R apply(T t) //Math中的Long round(Double d) @Test public void test4() { Function<Double,Long> func = d -> Math.round(d); Long l = func.apply(23.4); System.out.println(l); System.out.println("将上述Lambda表达式转变为方法引用"); // 使用常用类Math Function<Double,Long> func1 = Math :: round; Long lo = func1.apply(101.4); System.out.println(lo); } // 情况三:类 :: 实例方法 // Comparator中的int comapre(T t1,T t2) // String中的int t1.compareTo(t2) @Test public void test5() { Comparator<String> com1 = (t1,t2) -> t1.compareTo(t2); System.out.println(com1.compare("ab","ad")); System.out.println("将上述Lambda表达式转变为方法引用"); // 使用参数1对应的常用类作为引用类 Comparator<String> com2 = String :: compareTo; System.out.println(com2.compare("ab","ac")); } // Function中的R apply(T t) // Employee中的String getName(); @Test public void test6() { Employee em1 = new Employee(1002, "Jack"); // Employee 对象作为唯一参数传入 Function<Employee,String> func1 = em -> em.getName(); System.out.println(func1.apply(em1)); System.out.println("将上述Lambda表达式转变为方法引用"); // 在使用方法引用时,引用类就是唯一参数em对应的Employee类 Function<Employee,String> func2 = Employee :: getName; System.out.println(func2.apply(em1)); }
四、构造器及数组引用
-
构造器的引用与方法引用类似,将构造器作为一个无参,有返回值的方法看待,返回值就是new 该构造器生成对应类的实例对象。
-
使用格式及引用要求与方法引用一样
-
一个类中有多个构造器,需要根据函数式接口中抽象方法的参数及返回值格式选取对应的构造器格式。
-
构造器引用程序示例
//构造器引用 //Supplier中的T get() @Test public void test1(){ // 只有一条return执行语句,所有return关键字和大括号都省略了 Supplier<Employee> sup1 = () -> new Employee(); Employee employee = sup1.get(); System.out.println(employee); System.out.println("将上面的Lambda表达式更改为构造器引用"); Supplier<Employee> sup2 = Employee :: new; System.out.println(sup2.get()); } //Function中的R apply(T t) @Test public void test2(){ // 根据该函数式接口中抽象方法的参数及返回值情况选取格式对应的构造器。 Function<Integer,Employee> func1 = id -> new Employee(id); System.out.println(func1.apply(18)); System.out.println("将上面的Lambda表达式更改为构造器引用"); Function<Integer,Employee> func2 = Employee :: new; System.out.println(func2.apply(28)); } //BiFunction中的R apply(T t,U u) @Test public void test3(){ BiFunction<Integer,String,Employee> bif = (id,name) -> new Employee(id,name); System.out.println(bif.apply(19,"Tom")); System.out.println("将上面的Lambda表达式更改为构造器引用"); BiFunction<Integer,String,Employee> bif2 = Employee :: new; System.out.println(bif2.apply(29,"Jack")); }
-
数组引用可以看作是构造器引用,
① new String[number] ,返回值是一维字符串数组对象String[]
② new Integer[num1] [num2] ,返回值是二维整型数组对象Integer[] [] -
数组引用程序示例
//数组引用 //Function中的R apply(T t) @Test public void test4(){ Function<Integer,String[]> func1 = number -> new String[number]; String[] str1 = func1.apply(5); System.out.println(Arrays.toString(str1)); System.out.println(); System.out.println("将上面的Lambda表达式更改为构造器引用"); Function<Integer,String[]> func2 = String[] :: new; String[] str2 = func2.apply(6); System.out.println(Arrays.toString(str2)); }