Java 8新特性学习之lambda与函数式接口
下图是对Java8中新特性的整理(来自网络),总的来看,大致上可以分成这么几个大块。
一、函数式接口
函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数(lambda表达式参见后面)。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口,这个类型必定会使用@FunctionalInterface进行修饰。
从SAM原则上讲,这个接口中,只能有一个函数需要被实现,但是也可以有如下例外:
1. 默认方法与静态方法并不影响函数式接口的契约,可以任意使用,即
函数式接口中可以有静态方法,一个或者多个静态方法不会影响SAM接口成为函数式接口,并且静态方法可以提供方法实现
可以由 default 修饰的默认方法方法,这个关键字是Java8中新增的,为的目的就是使得某一些接口,原则上只有一个方法被实现,但是由于历史原因,不得不加入一些方法来兼容整个JDK中的API,所以就需要使用default关键字来定义这样的方法
2. 可以有 Object 中覆盖的方法,也就是 equals,toString,hashcode等方法。
JDK中以前所有的函数式接口都已经使用 @FunctionalInterface 定义,可以通过查看JDK源码来确认,以下附JDK 8之前已有的函数式接口:
java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.nio.file.PathMatcher java.lang.reflect.InvocationHandler java.beans.PropertyChangeListener java.awt.event.ActionListener javax.swing.event.ChangeListener
如:
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
函数式接口(functional interface 也叫功能性接口,其实是同一个东西)。简单来说,函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断,但最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。
Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现
@FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
方法和构造函数引用
上面的代码实例可以通过静态方法引用,使之更加简洁:
Converter<String, Integer> converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123
Java 8 允许你通过::关键字获取方法或者构造函数的的引用。上面的例子就演示了如何引用一个静态方法。而且,我们还可以对一个对象的方法进行引用:
class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } } Something something = new Something(); Converter<String, String> converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"
让我们看看如何使用::关键字引用构造函数。首先我们定义一个示例bean,包含不同的构造方法:
class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
接下来,我们定义一个person工厂接口,用来创建新的person对象:
interface PersonFactory<P extends Person> { P create(String firstName, String lastName); }
然后我们通过构造函数引用来把所有东西拼到一起,而不是像以前一样,通过手动实现一个工厂来这么做。
PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");
我们通过Person::new来创建一个Person类构造函数的引用。Java编译器会自动地选择合适的构造函数来匹配PersonFactory.create函数的签名,并选择正确的构造函数形式。
一般方法的引用格式是
如果是静态方法,则是ClassName::methodName。如 Object ::equals 如果是实例方法,则是Instance::methodName。如Object obj=new Object();obj::equals; 构造函数.则是ClassName::new
Lambda的范围
对于lambdab表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。你能够访问局部对应的外部区域的局部final变量,以及成员变量和静态变量。
访问局部变量
我们可以访问lambda表达式外部的final局部变量:
final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
但是与匿名对象不同的是,变量num并不需要一定是final。下面的代码依然是合法的:
int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
然而,num在编译的时候被隐式地当做final变量来处理。下面的代码就不合法:
int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3;
在lambda表达式内部企图改变num的值也是不允许的。
访问成员变量和静态变量
与局部变量不同,我们在lambda表达式的内部能获取到对成员变量或静态变量的读写权。这种访问行为在匿名对象里是非常典型的。
class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
访问默认接口方法
还记得第一节里面formula的那个例子么? 接口Formula定义了一个默认的方法sqrt,该方法能够访问formula所有的对象实例,包括匿名对象。这个对lambda表达式来讲则无效。
默认方法无法在lambda表达式内部被访问。因此下面的代码是无法通过编译的:
Formula formula = (a) -> sqrt( a * 100);
内置函数式接口
JDK 1.8 API中包含了很多内置的函数式接口。有些是在以前版本的Java中大家耳熟能详的,例如Comparator接口,或者Runnable接口。对这些现成的接口进行实现,可以通过@FunctionalInterface 标注来启用Lambda功能支持。
消费型接口示例
public static void donation(Integer money, Consumer<Integer> consumer){ consumer.accept(money); } public static void main(String[] args) { donation(1000, money -> System.out.println("好心的麦乐迪为Blade捐赠了"+money+"元")) ; }
供给型接口示例
public static List<Integer> supply(Integer num, Supplier<Integer> supplier){ List<Integer> resultList = new ArrayList<Integer>() ; for(int x=0;x<num;x++) resultList.add(supplier.get()); return resultList ; } public static void main(String[] args) { List<Integer> list = supply(10,() -> (int)(Math.random()*100)); list.forEach(System.out::println); }
函数型接口示例
转换字符串为Integer
public static Integer convert(String str, Function<String, Integer> function) { return function.apply(str); } public static void main(String[] args) { Integer value = convert("28", x -> Integer.parseInt(x)); }
断言型接口示例
筛选出只有2个字的水果
public static List<String> filter(List<String> fruit, Predicate<String> predicate){ List<String> f = new ArrayList<>(); for (String s : fruit) { if(predicate.test(s)){ f.add(s); } } return f; } public static void main(String[] args) { List<String> fruit = Arrays.asList("香蕉", "哈密瓜", "榴莲", "火龙果", "水蜜桃"); List<String> newFruit = filter(fruit, (f) -> f.length() == 2); System.out.println(newFruit); }
自行设计的方法中, 如果可以接收 lambda 表达式, 则可以使用 Function 作为参数, 如下为一些已经实现的函数式接口:
// Function<T, R> -T作为输入,返回的R作为输出 Function<String,String> function = (x) -> {System.out.print(x+": ");return "Function";}; System.out.println(function.apply("hello world")); //Predicate<T> -T作为输入,返回的boolean值作为输出 Predicate<String> pre = (x) ->{System.out.print(x);return false;}; System.out.println(": "+pre.test("hello World")); //Consumer<T> - T作为输入,执行某种动作但没有返回值 Consumer<String> con = (x) -> {System.out.println(x);}; con.accept("hello world"); //Supplier<T> - 没有任何输入,返回T Supplier<String> supp = () -> {return "Supplier";}; System.out.println(supp.get()); //BinaryOperator<T> -两个T作为输入,返回一个T作为输出,对于“reduce”操作很有用 BinaryOperator<String> bina = (x,y) ->{System.out.print(x+" "+y);return "BinaryOperator";}; System.out.println(" "+bina.apply("hello ","world"));
什么是lambda?
lambda表达式是一段可以传递的代码,它的核心思想是将面向对象中的传递数据变成传递行为。 我们回顾一下在使用java8之前要做的事,之前我们编写一个线程时是这样的:
Runnable r = new Runnable() { @Override public void run() { System.out.println("do something."); } }
也有人会写一个类去实现Runnable接口,这样做没有问题,我们注意这个接口中只有一个run方法, 当把Runnable对象给Thread对象作为构造参数时创建一个线程,运行后将输出do something.。 我们使用匿名内部类的方式实现了该方法。
这实际上是一个代码即数据的例子,在run方法中是线程要执行的一个任务,但上面的代码中任务内容已经被规定死了。 当我们有多个不同的任务时,需要重复编写如上代码。
设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部 类还是不够简便。 为了执行一个简单的任务逻辑,不得不加上 6 行冗繁的样板代码。那如果是lambda该怎么做?
Runnable r = () -> System.out.println("do something.");
嗯,这代码看起来很酷,你可以看到我们用()和->的方式完成了这件事,这是一个没有名字的函数,也没有人和参数,再简单不过了。 使用->将参数和实现逻辑分离,当运行这个线程的时候执行的是->之后的代码片段,且编译器帮助我们做了类型推导; 这个代码片段可以是用{}包含的一段逻辑。
Lambda语法
包含三个部分
(1) 一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
(2) 一个箭头符号:->
(3) 方法体,可以是表达式和代码块,方法体函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}
总体看起来像这样
参考
原文链接: winterbe
译文链接: http://www.importnew.com/10360.html
转载请注明来源:Java 8新特性学习之lambda与函数式接口