您的位置:首页 > 编程语言 > Java开发

Java8函数式编程 Functionallnterface注解、lambda表达式、方法的引用

2018-03-08 18:03 573 查看
上一篇:《函数式编程的简介》

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/41012909

java函数式编程由繁到简的演化:《一步一步走入函数式编程》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: