Java 8新特性学习之lambda与函数式接口

    |     2018年2月6日   |   java8新特性   |     0 条评论   |    2129

Java 8 把一大块新能力摊在一张图上:Lambda、流、新日期、Optional、CompletableFuture……本文只把函数式接口和 lambda 讲清楚——lambda 不能单独出现,必须装进一个 SAM 接口里。

先看全景(来自网络),再落到接口、方法引用、变量捕获和内置 Function 家族。

Java8-features

一、函数式接口

函数式接口本质仍是接口,但是 SAM(Single Abstract Method):只有一个需要实现的抽象方法。以它为参数的方法,调用时才能传入 lambda。反过来:能传入 lambda 的参数类型,一定是函数式接口,通常带 @FunctionalInterface。

  • 静态方法和 default 方法不算进 SAM 契约,可以任意加(default 多为兼容老 API)。
  • 覆盖 Object 的 equals / toString / hashCode 也不破坏函数式接口。
  • 注解不是必须的,但写上能防止同事再往接口里塞第二个抽象方法。

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();

}

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

:: 也能引用实例方法:

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"

构造函数引用要先有工厂接口,编译器按 create 的签名选合适的构造:

class Person {

    String firstName;

    String lastName;

    Person() {}

    Person(String firstName, String lastName) {

        this.firstName = firstName;

        this.lastName = lastName;

    }

}
interface PersonFactory<P extends Person> {

    P create(String firstName, String lastName);

}
PersonFactory<Person> personFactory = Person::new;

Person person = personFactory.create("Peter", "Parker");
如果是静态方法,则是ClassName::methodName。如 Object ::equals

如果是实例方法,则是Instance::methodName。如Object obj=new Object();obj::equals;

构造函数.则是ClassName::new

三、Lambda 的变量范围

对外部变量的访问粒度和匿名类很像:能用到外围的 effectively final 局部变量,以及成员/静态变量。

访问局部变量

final int num = 1;

Converter<Integer, String> stringConverter =

        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

和匿名类不同,不必显式写 final,但编译期仍当 final 用:

int num = 1;

Converter<Integer, String> stringConverter =

        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

后面再改 num,或在 lambda 里改它,都不合法:

int num = 1;

Converter<Integer, String> stringConverter =

        (from) -> String.valueOf(from + num);

num = 3;

成员变量和静态变量

局部变量只读,成员和静态变量在 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);

        };

    }

}

默认方法进不了 lambda

接口里的 default 方法能给匿名类实例用,但 lambda 内部不能直接调。下面这行编译不过:

Formula formula = (a) -> sqrt(a * 100);

四、内置函数式接口

JDK 8 内置了一组:Consumer、Supplier、Function、Predicate、BinaryOperator 等,老接口如 Comparator、Runnable 也可标 @FunctionalInterface 开 Lambda。

7a34cef4b0d56132aab24f78bc68921e

消费型:

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));

}

断言型(筛两个字的水果):

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<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 是一段可传递的代码:把「传数据」变成「传行为」。Java 8 之前写线程,匿名内部类要围六行样板:

Runnable r = new Runnable() {

    @Override

    public void run() {

        System.out.println("do something.");

    }

}

lambda 只需:

Runnable r = () -> System.out.println("do something.");

语法三块:逗号分隔的形式参数(即 SAM 方法的参数)→ 箭头 -> → 方法体(表达式或 {} 代码块;若 SAM 返回 void 可以不加大括号)。

cd90ec18cb1543b154df75fa6850a733

接口 输入 输出
Function<T,R> T R
Predicate<T> T boolean
Consumer<T> T 无
Supplier<T> 无 T
BinaryOperator<T> 两个 T 一个 T(适合 reduce)

参考:winterbe;译文 http://www.importnew.com/10360.html;跟上 Java 8;10 个示例;Optional;Optional 深度解析。

一句话总结:lambda 必须落在函数式接口上;方法引用用 :: 少写样板;捕获的局部变量要是 effectively final;日常优先用 JDK 自带的 Function / Predicate / Consumer / Supplier。

转载请注明来源:Java 8新特性学习之lambda与函数式接口
本文链接地址:https://ai.zhousir.top/?p=2092

上一篇:

下一篇:

回复 取消