Java8函数式编程 Functionallnterface注解、lambda表达式、方法的引用
2018-03-08 18:03
573 查看
上一篇:《函数式编程的简介》
1、并不是只有一个方法,是只有一个抽象方法。
2、java.lang.object 实现的方法,都不能视为抽象方法
符合规范的示例
定义一个动物类、马类、骡类。
动物类
马类
骡类
上述代码中,Mule同时拥有来自不同接口的实现方法。也可以说,这弥补了Java单一继承的一些不足。但是多继承下,多个父接口如果存在同样的默认run(),那么子接口就不知所措了。
修改Mule的实现。
为了同时实现IHorse和IDonkey,不得不重新实现一些run()方法,让编译器可以进行方法绑定。
接口的默认实现对于这个函数式编程的流式表达非常重要。比如java.util.comparator接口,它在JDK1.2时引入,用于排序时给出两个对象实例具体比较逻辑。在Java8中,comparator接口新增若干个默认方法。用于多个比较器的整合。一个常用的默认方法是:
有了这个默认的方法,在进行排序时,我们可以非常方便的进行多元素的多条件排序,如下代码先按照字符串长度排序,继而按照大小写敏感的字母顺序排序。
和匿名对象一样,lambda表达式也可以访问外部的final局部变量。
不过即使去掉上述的final声明,依然可以编译通过。实际上,这是Java8做的障眼法,它会自动将在lambda表达式中使用的变量视为final。
但是下面这样写就不行
静态方法引用:ClassName::methodName
实例上的实例方法引用:instanceRference::methodName
超类上的实例方法引用:super::methodName
类型上的实例方法引用:ClassName::methodName
构造方法引用:Class::new
数组构造方法引用:TypeName[]::new
首先,方法引用使用 “::” 定义,前半部分表示类名或者实例名,后半部分表示方法名。如果是构造函数,则可以用new表示。下例展示了方法引用:
对于第一个方法“User::getName”,表示User类的实例方法。在执行时,Java会自动识别流中的元素(这里指User实例)是作为调用目标还是调用方法的参数。在“User::getName”中,流内的元素都是作为调用目标,因此实际上,在这里调用每一个User对象实例的getName()方法,并将User的name作为一个新的流。同时,对于这里得到的所有name,使用方法引用System.out::println进行处理。这里的System.out为PrintStream对象实例。系统也会自动判断流内的元素此时应该作为方法的参数传入。
一般来说,如果使用的是静态方法,或者调用目标明确,俺么流内的元素会自动作为参数使用。如果函数引用表示实例方法,并且不存在调用目标,那么流内元素就会自动作为调用目标。因此,如果一个类中存在同名的实例方法和静态函数,那么编译器就不知道调用哪个方法。如下代码:
上述代码试图将所有的Double元素转为String,并将其输出,但是在Double中同时存在以下两个函数,对函数引用的处理就出现了歧义。
方法引用,也可以直接调用构造函数。
在此,UserFactory作为User的工厂类,是一个函数式接口。当使用User::new创建接口实例时,系统会根据UserFactory.create()的函数签名来选择合适的User构造函数。在这里既是public User(int no, String name)。在创建UserFactory实例后,对UserFactory.create()的调用,都委托给User的实际构造函数进行,从而创建User对象实例。
看一段代码:
可见 Stream.of()和 Arrays.stream()都是把对象转成Stream。
java函数式编程由繁到简的演化:《一步一步走入函数式编程》
Functionallnterface注解
函数式接口,就只定义了单一抽象方法的接口。例如:@FunctionalInterface public interface IntHandler { void handle(int i); }
注意几点
注解Functionallnterface用于表明IntHandler接口是一个函数式接口,该接口被定义为只包含一个抽象方法的handle()。如果一个接口满足函数式接口的定义,那么即使不标注@Functionallnterface注解,编译器依然会把它看做函数式接口,类似@Override。1、并不是只有一个方法,是只有一个抽象方法。
2、java.lang.object 实现的方法,都不能视为抽象方法
符合规范的示例
接口默认方法
Java8前,接口只能包含抽象方法,Java8后,接口也可以包含若干个实例方法。这使得类有了类似于多继承的能力,一个对象实例,将拥有来自于多个不同接口的实例方法。Java8中,default关键字可以在接口类定义实例方法。定义一个动物类、马类、骡类。
动物类
public interface IAnimal { default void breath(){ System.out.println("breath"); } }
马类
public interface IHorse { void eat(); default void run(){ System.out.println("horse run"); } }
骡类
public class Mule implements IHorse, IAnimal { @Override public void eat() { System.out.println("Mule eat"); } public static void main(String[] args) { Mule m = new Mule(); m.run(); m.breath(); } }
上述代码中,Mule同时拥有来自不同接口的实现方法。也可以说,这弥补了Java单一继承的一些不足。但是多继承下,多个父接口如果存在同样的默认run(),那么子接口就不知所措了。
public interface IDonkey { void eat(); default void run(){ System.out.println("donkey run"); } }
修改Mule的实现。
为了同时实现IHorse和IDonkey,不得不重新实现一些run()方法,让编译器可以进行方法绑定。
public class Mule implements IHorse, IAnimal, IDonkey { @Override public void run() { // IHorse.super.run(); } @Override public void eat() { System.out.println("Mule eat"); } public static void main(String[] args) { Mule m = new Mule(); m.run(); m.breath(); } }
接口的默认实现对于这个函数式编程的流式表达非常重要。比如java.util.comparator接口,它在JDK1.2时引入,用于排序时给出两个对象实例具体比较逻辑。在Java8中,comparator接口新增若干个默认方法。用于多个比较器的整合。一个常用的默认方法是:
default Comparator<T> thenComparing(Comparator<? super T> other) { Objects.requireNonNull(other); return (Comparator<T> & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0) ? res : other.compare(c1, c2); }; }
有了这个默认的方法,在进行排序时,我们可以非常方便的进行多元素的多条件排序,如下代码先按照字符串长度排序,继而按照大小写敏感的字母顺序排序。
Comparator<String> cmp = Comparator.comparingInt(String::length).thenComparing(String.CASE_INSENSITIVE_ORDER);
lambda表达式
lambda表达式可以说是函数式编程的核心,lambda表达式即匿名函数,是一段没有函数名的函数体,可以作为参数直接传递给相关的调用者。下面的forEach(),传入的就是一个lambda表达式。可以看到这段表达式并不像函数一样有名字,非常类似匿名内部类,它只是简单描述了应该执行的代码段。List<Integer> number = Arrays.asList(1,2,3,4,5,6); number.forEach((Integer value) -> System.out.print(value));
和匿名对象一样,lambda表达式也可以访问外部的final局部变量。
final int num = 2; Function<Integer, Integer> stringConverter = (from) -> from * num; System.out.print(stringConverter.apply(3));
不过即使去掉上述的final声明,依然可以编译通过。实际上,这是Java8做的障眼法,它会自动将在lambda表达式中使用的变量视为final。
但是下面这样写就不行
方法的引用
方法的引用是Java8中提出来的,用来简化lambda表达式的一种手段。它通过类名和方法名来定位到一个静态方法或者实例方法。方法的引用在Java8中的使用非常灵活。可以分为以下几种。静态方法引用:ClassName::methodName
实例上的实例方法引用:instanceRference::methodName
超类上的实例方法引用:super::methodName
类型上的实例方法引用:ClassName::methodName
构造方法引用:Class::new
数组构造方法引用:TypeName[]::new
首先,方法引用使用 “::” 定义,前半部分表示类名或者实例名,后半部分表示方法名。如果是构造函数,则可以用new表示。下例展示了方法引用:
public class User { private int no; private String name; public User(int no, String name) { this.no = no; this.name = name; } //getter、setter方法 }
import java.util.ArrayList; import java.util.List; public class InstanceMethodRef { public static void main(String[] args) { List<User> users = new ArrayList<User>(); for (int i=1;i<10;i++){ users.add(new User(i,"billy"+i)); } users.stream().map(User::getName).forEach(System.out::println); } }
对于第一个方法“User::getName”,表示User类的实例方法。在执行时,Java会自动识别流中的元素(这里指User实例)是作为调用目标还是调用方法的参数。在“User::getName”中,流内的元素都是作为调用目标,因此实际上,在这里调用每一个User对象实例的getName()方法,并将User的name作为一个新的流。同时,对于这里得到的所有name,使用方法引用System.out::println进行处理。这里的System.out为PrintStream对象实例。系统也会自动判断流内的元素此时应该作为方法的参数传入。
一般来说,如果使用的是静态方法,或者调用目标明确,俺么流内的元素会自动作为参数使用。如果函数引用表示实例方法,并且不存在调用目标,那么流内元素就会自动作为调用目标。因此,如果一个类中存在同名的实例方法和静态函数,那么编译器就不知道调用哪个方法。如下代码:
List<Double> numbers = new ArrayList<>(); for (int i= 1 ; 1< 10;i++){ numbers.add(Double.valueOf(i)); } numbers.stream().map(Double::toString).forEach(System.out::println);
上述代码试图将所有的Double元素转为String,并将其输出,但是在Double中同时存在以下两个函数,对函数引用的处理就出现了歧义。
public static String toString(double a) public String toString()
方法引用,也可以直接调用构造函数。
import java.util.ArrayList; import java.util.List; public class ConstrMethodRef { @FunctionalInterface interface UserFactory<U extends User>{ U create (int id,String name); } static UserFactory<User> uf = User::new; public static void main(String[] args) { List<User> users = new ArrayList<>(); for (int i=1;i<10;i++){ users.add(uf.create(i,"billy"+i)); } users.stream().map(User::getName).forEach(System.out::println); } }
在此,UserFactory作为User的工厂类,是一个函数式接口。当使用User::new创建接口实例时,系统会根据UserFactory.create()的函数签名来选择合适的User构造函数。在这里既是public User(int no, String name)。在创建UserFactory实例后,对UserFactory.create()的调用,都委托给User的实际构造函数进行,从而创建User对象实例。
关于stream()
通过查看源码,了解stream。看一段代码:
int[] arr = {1, 2, 3, 4, 5}; Stream.of(arr); // public static<T> Stream<T> of(T t) Arrays.stream(arr); // public static IntStream stream(int[] array)
可见 Stream.of()和 Arrays.stream()都是把对象转成Stream。
详细了解 “函数式编程” “lambda表达式”
阅读这6篇文章:http://blog.csdn.net/lsmsrc/article/details/41012909java函数式编程由繁到简的演化:《一步一步走入函数式编程》
相关文章推荐
- Java 8新特性:新语法方法引用和Lambda表达式及全新的Stream API
- Java 8新特性:新语法方法引用和Lambda表达式及全新的Stream API
- java8新特性之函数式接口、lambda表达式、接口的默认方法、方法和构造函数的引用
- Java8 Lambda表达式 函数式编程 方法引用
- Java8特性总结(二)Lambda表达式,函数式接口,方法引用
- Java8 lambda表达式、函数式接口、方法引用
- javaSE_8系列博客——Java语言的特性(三)--类和对象(20)--嵌套类(Lambda 表达式--VS--方法引用)
- Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)
- java lambda表达式和方法引用
- Java Lambda表达式及方法引用
- [java8] lambda表达式、函数式接口和方法引用
- java8新特性总结——lambda表达式之方法引用与构造器引用
- Java 8 函数式接口、lambda表达式、方法以及构造器引用
- (转) C#异步调用使用匿名方法Lambda表达式
- 委托、匿名方法、Lambda表达式的演进
- 匿名方法,Lambda表达式,高阶函数
- Spring_02 注入类型值、利用引用注入类型值、spring表达式、与类相关的注解、与依赖注入相关的注解、注解扫描
- 委托、匿名方法、Lambda表达式的演进
- jdk8 lambda的方法引用引起的编译器bug
- 匿名函数、委托、lambda表达式、扩展方法