初学Java8 Lambda表达式

学习笔记

作为学习笔记我就不写历史啥的了,完全随心记。
首先,Java lamda的标准写法
(param1, param2) -> {expression};
param为匿名内部类中的函数参数,expression为函数里面的所有语句,常写的例子

new Thread(new Runnable() {
            @Override
            public void run() {
                // doSomething
                System.out.println("test");
            }
        }).start();

这里匿名内部类可省略,编译器会自动推断,函数没有参数就可直接使用括号,函数内部语句直接写到花括号中,lambda表达式如下

new Thread(() -> {
            System.out.print("test");
        }).start();

接下来又是很重要的一点
如果参数只有一个,小括号可以去掉 即为
param -> {expression};
比如

jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Action performed");
                e.getActionCommand();
            }
        });

这里只有ActionEvent类的一个对象e为参数,里面有两行代码,所以可以省略掉参数外面的括号,而且参数可以自己定义为任何合法的标识符,但是在函数内部引用的时候需要与你定义的参数保持一致,这里就变成了

jButton.addActionListener(w-> {
                System.out.println("Action performed");
                w.getActionCommand();
            });

好的,语法还可以进一步简化
如果表达式只有一行,大括号,可以去掉,并且这一行最后的分号结尾,要去掉
最开始写的新建线程的lambda表达式可以进一步简化成这样

new Thread(() -> System.out.println("test")).start();

那么,这是java8的新特性,什么情况下能使用lambda表达式呢?我们看到的形式都是在创建匿名内部类的时候使用lambda表达式,创建形式就是new一个某个接口类型的对象,然后在内部类中实现接口的方法。那么,这些接口都有什么特点呢,通过Idea打开Runnable接口的定义,会发现接口上面有这么一个注解
@FunctionalInterface
即函数式接口,那我们再看看这个接口的定义吧

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.
Conceptually, a functional interface has exactly one abstract method. Since {@linkplain java.lang.reflect.Method#isDefault() default methods} have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

If a type is annotated with this annotation type, compilers are required to generate an error message unless:

  • The type is an interface type and not an annotation type, >enum, or class.
  • The annotated type satisfies the requirements of a functional interface.

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a {@code FunctionalInterface} annotation is present on the interface declaration.

从这段解释里面我们可以提取到几个要点

  • 首先这个注解是定义在接口上面的
  • 第二,接口由这个注解定义的接口应该有且仅有一个抽象方法,但是由于Java8引入了接口的default方法,default方法有实现,所以不算抽象方法,如果接口覆写了object的一个public方法,也不计算在这个抽象方法的名额里面,因为所有的接口的实现都会实现object的方法。
  • 第三,函数式接口的实例可以lambda expressions, method references, or constructor references的形式出现
  • 第四,不在以上范围内的接口如果使用了FunctionalInterface注解,编译器会报错
  • 最后,虽然有些接口没有加上FunctionalInterface注解,但是编译器会把满足函数式接口的定义的接口看作FunctionalInterface,也就是说这些接口的实例也可以用lambda表达式的形式来写。

也就是说,所有满足函数式接口的接口的实例都可以变为lambda表达式,不满足的则会在创建的时候报错。
以下定义的接口就满足函数式接口的定义

public interface MyTest {
    String[] test(String str);

    default void test2() {
        System.out.println("test");
    }
    String toString();
}

虽然没有加上FunctionalInterface注解,但是在使用的时候还是可以使用Lambda表达式

public class MyTestMain {
    public static void main(String[] args) {
        // MyTest.test(str)常规写法;
        String[] test = test(new MyTest() {
            @Override
            public String[] test(String str) {
                return str.split("");
            }
        });
        //变为lambda表达式
        test(s->s.split(""));
        for (String s : test) {
            System.out.println(s);
        }
    }

    public static String[] test(MyTest test) {
        return test.test("strd");
    }
}

到这里就可以使用lambda表达式写很多有意思的代码啦,仔细观察,java集合里面好多的匿名内部类都可以使用lambda表达式来简写,我们看了函数式接口用lambda表达式来表示的,当然要介绍一下使用方法引用的形式咯
如果,lambda 表达式只有一个参数,表达式只有一个, 且表达式调用了这个的参数本身,那么这个参数可以省略,并且变成方法引用的形式
对象::方法名
对象为表达式中的对象,方法为该对象调用的方法

appleStore.forEach(apple -> System.out.println(apple));

apple为参数,表达式里面调用了apple,且表达式只要一个,可以转化为方法引用的形式

appleStore.forEach(System.out::println);

方法引用还有一种形式
当表达式为前述参数只调用一次自己的方法,也可以变为方法引用
参数类名::方法名

int redPriceSum = appleStore.stream()
                .filter(apple -> apple.getColor().equals("red"))
                .mapToInt(apple1->apple1.getPrice).sum();

可以看到mapToInt方法中的lambda表达式符合上述方法引用的形式,所以可以简化为

Apple::getPrice

其中Apple为apple对象的类名

大致就是这些,想要了解更多使用的实例的可以去翻一翻集合的源码,比如集合类的foreach方法,参数就是一个函数式接口,可以转换为lambda表达式,真的太宝藏啦!我现在才发现在Idea上面看源码是多么有意思,以后要常看常学!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章