Java8新特性(一)——Lambda表达式与函数式接口
2017-09-24 20:20
549 查看
一、Java8新特性概述
1.Lambda 表达式
2. 函数式接口
3. 方法引用与构造器引用
4. Stream API
5. 接口中的默认方法与静态方法
6. 新时间日期 API
7. 其他新特性
// 其他例如HashMap在JDK8中的提升,将会在HashMap的章节进行拓展
二、Lambda表达式
1.为什么使用LambdaLambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。
可以写出更简洁、更 灵活的代码。
2.从匿名内部类到λ表达式
回顾Java基础中匿名内部类的写法:
// 原来的匿名内部类 Runnable r = new Runnable() { @Override public void run() { System.out.println("Runnable!"); } };
实际上,上述代码中核心的只有输出语句那一句,首先这个匿名内部类没有名字,其次我们实现Runnable接口时其实就是去重写run()方法这一个方法
所以,如果Java可以自动地帮我们感应出除核心代码之外的东西,那么代码便可以大大简化简洁!
改造为Lambda表达式:
// JDK8的Lambda表达式 Runnable r8 = () -> System.out.println("Runnable!");
// 这里赞一下IDEA,像λ这样的新特性,他也是可以自动感应进行标记的
关于Lambda表达式更多更实用的案例,可以参见如下代码:
package com.atguigu.java8; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.TreeSet; import org.junit.Test; public class TestLambda1 { //原来的匿名内部类 @Test public void test1(){ Comparator<String> com = new Comparator<String>(){ @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } }; TreeSet<String> ts = new TreeSet<>(com); TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>(){ @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } }); } //现在的 Lambda 表达式 @Test public void test2(){ Comparator<String> com = (x, y) -> Integer.compare(x.length(), y.length()); TreeSet<String> ts = new TreeSet<>(com); } List<Employee> emps = Arrays.asList( new Employee(101, "张三", 18, 9999.99), new Employee(102, "李四", 59, 6666.66), new Employee(103, "王五", 28, 3333.33), new Employee(104, "赵六", 8, 7777.77), new Employee(105, "田七", 38, 5555.55) ); //需求:获取公司中年龄小于 35 的员工信息 public List<Employee> filterEmployeeAge(List<Employee> emps){ List<Employee> list = new ArrayList<>(); for (Employee emp : emps) { if(emp.getAge() <= 35){ list.add(emp); } } return list; } @Test public void test3(){ List<Employee> list = filterEmployeeAge(emps); for (Employee employee : list) { System.out.println(employee); } } //需求:获取公司中工资大于 5000 的员工信息 public List<Employee> filterEmployeeSalary(List<Employee> emps){ List<Employee> list = new ArrayList<>(); for (Employee emp : emps) { if(emp.getSalary() >= 5000){ list.add(emp); } } return list; } //优化方式一:策略设计模式 public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> mp){ List<Employee> list = new ArrayList<>(); for (Employee employee : emps) { if(mp.test(employee)){ list.add(employee); } } return list; } @Test public void test4(){ List<Employee> list = filterEmployee(emps, new FilterEmployeeForAge()); for (Employee employee : list) { System.out.println(employee); } System.out.println("------------------------------------------"); List<Employee> list2 = filterEmployee(emps, new FilterEmployeeForSalary()); for (Employee employee : list2) { System.out.println(employee); } } //优化方式二:匿名内部类 @Test public void test5(){ List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() { @Override public boolean test(Employee t) { return t.getId() <= 103; } }); for (Employee employee : list) { System.out.println(employee); } } //优化方式三:Lambda 表达式 @Test public void test6(){ List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35); list.forEach(System.out::println); System.out.println("------------------------------------------"); List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000); list2.forEach(System.out::println); } //优化方式四:Stream API @Test public void test7(){ emps.stream() .filter((e) -> e.getAge() <= 35) .forEach(System.out::println); System.out.println("----------------------------------------------"); emps.stream() .map(Employee::getName) .limit(3) .sorted() .forEach(System.out::println); } }
View Code
3.Lambda表达式语法格式
比较关键的是箭头操作符(->)将表达式拆分成两部分:
左侧——参数列表
右侧——拉姆达体(实现功能的代码)
对应到上文中示例的写法,Lambda表达式对应的是实现了接口的匿名内部类,那么左侧的参数列表就对应了它所实现的接口中抽象方法的
参数列表,而右边就是抽象方法的 实现(关于若接口中有多个抽象方法,将会在函数式接口中进行阐述)
我们通过以下一些实例结合注释来加强对拉姆达表达式格式的理解:
// 语法格式一:无参数数无返回值 Runnable r8 = () -> System.out.println("Runnable!"); // 语法格式二:有一个参数无返回值(Consumer为JDK的一个接口,具体可以参考源码) Consumer<String> con = (x) -> System.out.println(x); con.accept("Hello Lambda!"); // 语法格式三:只有一个参数时,小括号可以省略(当然不太建议省略) Consumer<String> consumer = x -> System.out.println(x); // 语法格式四:多个参数、多条语句(使用大括号)、有返回值 Comparator<Integer> c1 = (x, y) -> { System.out.println("函数式接口!"); return Integer.compare(x, y); }; // 语法格式五:多个参数、只有一条语句(可以省略大括号)、有返回值(可以省略return) Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y); // 语法格式六:Lambda表达式的参数列表的参数类型可以省略!是因为JVM编译时可以通过上下文推断出数据类型:也就是类型推断!
当然了,本质上Lambda表达式还是一个语法糖,但是如此简洁的表达一方面使得我们的代码变得简洁,但另外一方面也给调试 和维护的人员带来了不小的麻烦(尤其是还未接触JDK8新特性的人),所以即使好用,也需要慎用!
三、函数式接口
什么是函数式接口只包含一个抽象方法的接口,称为函数式接口。
由上文我们也知道Lambda表达式是依赖函数式接口的(不然它也不知道它所实现的接口对应的是哪个方法),(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)
可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口
@FunctionalInterface public interface Flyable<T> { int fly(T t); }
一个基本的示例如下:
// 接口 @FunctionalInterface public interface Oprator { Integer oprator(Integer num1); } public Integer oprator(Integer num, Oprator op){ return op.oprator(num); } oprator(10, (x) -> x + x);
四大内置核心函数式接口
通过Lambda表达式呢我们可以写出非常简洁的代码,但是也暴露出一个问题:每实现一个功能(例如上文的操作一个数,或者其他的比较两个数以及更多的需求功能),每一个我们都必须新建一个相应的函数式接口——即使它只有一个抽象方法,这就导致了接口的暴涨,于是Java8中引入了内置函数式接口来解决此问题!
像更新了函数式接口后,集合的遍历也变得更加简单(虽然底层不变),例如list.forEach(),传入一个消费式接口即可(参见源码!)
我们通过一段代码加注释来进行学习了解:
// 1.Consumer<T>——消费型接口——“有去无回” public void happy(double money, Consumer<Double> con) { // 利用Consumer接口进行消费 con.accept(money); } @Test public void test1() { // 单参数、无返回值 happy(100, (x) -> System.out.println("‘大保健消费...’")); } // 2.Supplier<T>——供给型接口——“空手套白狼” public List<Integer> getNumList(int num, Supplier<Integer> sup) { // 利用Supplier接口产生指定个数个数字放入集合并返回 List<Integer> list = new ArrayList<>(); Random random = new Random(); for (int i = 0; i<num; i++) { list.add(sup.get()); } return list; } @Test public void test2() { // 无参数,有返回值 List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100)); System.out.println(numList); } // 3.Function<T, R>——函数型接口——“礼尚往来” public String strHandler(String str, Function<String, String> fun) { // 对字符串进行处理(具体处理逻辑将在Lambda表达式中实现) return fun.apply(str); } @Test public void test3() { // 单参数、有返回值 String s = strHandler("hello", (x) -> x.toUpperCase()); System.out.println(s); } // 4.Predicate<T>——断言型接口——“明辨是非” public List<String> filterStr(List<String> list, Predicate<String> pre) { List<String> strList = new ArrayList<>(); for (String s : list) { // 对符合条件的字符串进行过滤 if (pre.test(s)) { strList.add(s); } } return strList; } @Test public void test4() { List<String> list = Arrays.asList("hello", "world", "Hello"); // 单参数,有返回值(boolean) List<String> list1 = filterStr(list, (x) -> x.equalsIgnoreCase("hello")); System.out.println(list1); }
当然如果四大内置函数接口无法满足的话,可以尝试其他的拓展接口:未全部列出,可以去java.lang.function中查看(注意JDK版本)
四、方法引用与构造器引用
方法引用当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)
方法引用是Lambda表达式的一种形式!
方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来
对象::实例方法
类::静态方法
类::实例方法
先来看一个实例:
@Test public void test1() { // Lambda的写法 Consumer<String> con1 = (x) -> System.out.println(x); // 也就是System.out.println()的返回值和参数列表与接口Consumer中抽象方法一致 // PrintStream ps = System.out; // Consumer<String> con2 = ps::println; // 方法引用的写法 对象::实例方法名 Consumer<String> con2 = System.out::println; con2.accept("hello method reference"); } @Test public void test2() { Employee emp = new Employee("小明", 18); // Lambda写法 Supplier<String> sup1 = () -> emp.getName(); // 方法引用(注意这里的方法是不需要写小括号的) Supplier<String> sup2 = emp::getName; String s = sup2.get(); System.out.println(s); }
从上文也可以看出,方法引用的条件是Lambda表达式中的方法体已经有方法实现了(该方法的参数列表与返回值一致!)
上述示例就是通过对象::实例方法的形式写出的方法引用,接下来介绍其他的几种形式:注意点见注释!
// 类::静态方法的形式 @Test public void test3() { // Lambda表达式的写法 Comparator<Integer> c1 = (x, y) -> Integer.compare(x, y); // 改造为方法引用(这里使用的是Integer的静态方法) Comparator<Integer> c2 = Integer::compare; } // 类::实例方法的形式 @Test public void test4() { // Lambda方法 BiPredicate<String, String> bp = (x, y) -> x.equalsIgnoreCase(y); // 方法引用,需要满足条件:当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引 用方法的第二个参数(或无参数)时 BiPredicate<String, String> bp2 = String::equalsIgnoreCase; }
构造器引用
语法形式:ClassName::new
// 构造器引用 @Test public void test5() { // Lambda表达式 Supplier<Employee> sup1 = () -> new Employee(); // 方法引用(这里自动匹配无参构造器),实际中是按照抽象方法中有几个参数便调用几个参数的构造器 Supplier<Employee> sup2 = Employee::new; }
相关文章推荐
- 浅析Java8新特性Lambda表达式和函数式接口
- Java 8新特性-1 函数式接口
- Java 8 新特性-菜鸟教程 (3) -Java 8 函数式接口
- java 8 新特性(1.函数式接口 - Functional Interface)
- Java8 新特性----函数式接口,以及和Lambda表达式的关系
- Java8新特性函数式接口
- Java8新特性之四函数式接口
- Java 8.0新增特性(内建函数式接口)
- JavaEE进阶知识学习-----Java8新特性知识学习-2-函数式接口
- java8新特性之函数式接口、lambda表达式、接口的默认方法、方法和构造函数的引用
- Java8 新特性之一---------Lambda表达式和函数式接口
- java8新特性lambda表达式, 函数式接口以及Steam流和新的日期时间例子代码
- Java8 新特性 函数式接口
- Java 8新特性 内建函数式接口详解
- JAVA8新特性之:函数式接口
- Java8新特性(内置的核心函数式接口)
- java8函数式接口和Lambda表达式应用在javaFX中tableView自定义点击事件
- java8新增特性(二)----函数式接口(Functional)
- java学习:java8新特性之一,函数式接口
- Java8新特性Lambda表达式、函数式接口