认识lambda

前言

lambda是匿名函数,虽然Java中的匿名函数没有学过,但是C++中的匿名函数我非常的熟悉啊,而且python中也曾经接触过匿名函数,不过python中的匿名长什么样子我都记不得了。现在就来学学Java中的lambda函数吧。

初识lambda

说得跟恋爱似的,我和lambda的相识也是一个不经意间,并不是我要去故意的去了解lambda,他却是出现在了我的面前。话说第一个看到lambda是学习python的时候,不过现在python中lambda长什么锤子样我都已经忘记了。给我印象最深的C++中的lambda,毕竟C++是我最爱的语言。而认识Java的lambda确实因为Core Java Volume I这本书。这本书的前面使用了Java Swing编写了一个小程序,以此来彰显Java的魅力。(反正我是丝毫没感觉到)。

代码如下。

package com.sher.corejava.imageview;

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.io.File;

/**
 * @author SHeR
 * @time 8/21/2019 10:27 PM
 * @describe A simple java swing(GUI) ImageViewer project from "Core Java, Volume I"
 */
public class ImageViewer {
    public static void main(String[] args) {
        // What is '() ->'?? This should be Anonymous Inner Class
        // It is Lambda Function related changes in Java8
        EventQueue.invokeLater(() -> {
            var frame = new ImageViewerFrame();
            frame.setTitle("ImageViewer");
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });

        /*
        So this Lambda Function can be wrote like this -- Anonymous Inner Class
        But the compiler says that Anonymous New Runnable can be replaced by lambda
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ImageViewerFrame imageViewerFrame = new ImageViewerFrame();
                imageViewerFrame.setTitle("ImageViewer");
                imageViewerFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                imageViewerFrame.setVisible(true);
            }
        });
        */
    }
}

@SuppressWarnings("all")
class ImageViewerFrame extends JFrame {

    private static final int WIDTH = 800;
    private static final int HEIGHT = 600;

    public ImageViewerFrame() {
        setSize(WIDTH, HEIGHT);

        // Use a lable to display image
        JLabel jLabel = new JLabel();
        this.add(jLabel);

        JFileChooser jFileChooser = new JFileChooser();
        // Choose File Extension
        FileNameExtensionFilter fileNameExtensionFilter = new FileNameExtensionFilter("JPG & GIF Images", "jpg", "gif");
        jFileChooser.setFileFilter(fileNameExtensionFilter);
        // Set initial open path
        jFileChooser.setCurrentDirectory(new File("C:\\Users\\SHeR\\blog"));

        // set up the menu bar
        JMenuBar jMenuBar = new JMenuBar();
        setJMenuBar(jMenuBar);

        JMenu menu = new JMenu("File");
        jMenuBar.add(menu);

        JMenuItem open = new JMenuItem("Open");
        menu.add(open);

        // Lambda Function again!
        open.addActionListener(event -> {
            int result = jFileChooser.showOpenDialog(null);

            // if file selected, set it as icon of the label
            if (result == JFileChooser.APPROVE_OPTION) {
                String name = jFileChooser.getSelectedFile().getPath();
                jLabel.setIcon(new ImageIcon(name));
            }
        });

        JMenuItem exit = new JMenuItem("Exit");
        menu.add(exit);
        // Wow, Lambda Function! That's fucking simple and beautiful!
        exit.addActionListener(event -> System.exit(0));
    }
}

可以看到代码的17行,71行,当时我看到了是一脸懵逼的,这是个什么玩意?后来我去查阅-> in java竟然查到了这个是Java中的lambda function。这个用法是java 8中才引入的。

我观察了17行invokeLater()函数,参数是一个Runnable参数。(Runnable是一个多线程相关的接口,有一个run()方法需要实现。)所以我寻思写法应该是想我下面注释中那样哒。好歹我也是学过了一点安卓的,这里写一个匿名类还是明白的。不过他用了lambda的方式来写,好像写的东西更少了,不过也有点儿看不懂了。

lambda

Java中lambda的基本格式是(arg1, arg2...) -> {body}

或者也可以加上参数的类型。type1 arg1, type2 arg2... -> { body }

比如写一个最简单的lambda表达式就是(int a, int b) -> {return a+b}

不过问题是这个破函数如何调用呢??我尝试着像使用匿名函数那样去调用,没想到没有任何作用。

((int a, int b) -> {return a+b;})(1, 2)像这样调用,是我太天真了,误解了Java中的lambda。Java中lambda和我在C++中学到的一点都不一样,他不像是一种独立的函数,而是像一种依赖于接口的特殊函数。

比如上面的那个函数用法应该是这样

public class lambda {
    public static void main(String[] args) {
        Test t = (a, b) -> {return a+b;};
        int c = t.func(3, 4);
        System.out.println(c);
    }
}

interface Test{
    int func(int a, int b);
}

上面的lambda中,函数的参数类型是可以推导的,所以可以省略,而函数体中只有一行代码。所以大括号都可以省略,甚至return都是可以省略的。Test t = (a, b) -> a+b;(不需要两个分号了)

更多的lambda使用例子

Comparator<String> c;
c = (String s1, String s2) -> s1.compareToIgnoreCase(s2);

public Runnable toDoLater() {
  return () -> {
    System.out.println("later");
  }
}

final List<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.forEach((a)->{
    System.out.println("hello: "+ a);
});

filterFiles(new FileFilter[] {
              f -> f.exists(), f -> f.canRead(), f -> f.getName().startsWith("q")
            });

不过如果接口中有多个函数需要实现呢? 那就只能gg了,据我所知lambda不能解决这个问题,而且就算是可以解决,那也会很复杂而且代码的可读性会下降。

lambda的目标类型

和C++中的lambda不一样,Java中的lambda没有一个固定的类型所以Java中的lambda不能够独立的存在,只可以依赖于其他的东西,比如接口。同样的(a, b) -> a+b;,我可以给很多的接口使用,这要适合lambda的形式就可以了。

所谓的适合lambda的形式就是

  • 参数在数量上和类型上意义对应。(类型上很多的时候都是自动推导)
  • 返回值相兼容。
  • 异常类型也相兼容。

lambda中的变量域以及变量捕捉

当我们使用匿名内部类的时候,内部类外面的变量,除非被声明为final,是不可以被使用的。但是如果使用lambda表达式的话,那就不一样了。lambda可以使用外界有效可读的变量。lambda和匿名内部类还有一个重大的区别是,匿名内部类中的this指的是这个内部类,而lambda内部的this指的就是所在的类。

上面说lambda可以使用外界有效可读的变量。那么什么事有效可读的变量呢?

所谓的有效可读的变量就是值,这个变量被初始化之后从来没有改变过,也就说给他加上final也不会报编译错误的变量。

那么lambda是否可以修改这些变量呢?

答案是lambda表达式对封闭,对变量开放;

int sum = 0;
list.forEach(e -> { sum += e.size(); }); // Illegal, close over values

List<Integer> aList = new List<>();
list.forEach(e -> { aList.add(e); }); // Legal, open over variables

这其实也是很好理解的,上面的那个经过lambda之后sum值都修改过了,这还能是有效可读变量吗?不过下面的那个就没有修改了吗?当然没有对象中保存的一个引用,使用add方法之后这个对象的引用并没有发生变化,所以是合法的。

final List<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);

这样都是合法的,但是进行下面的操作的时候就不再合法了。

final List<Integer> list = new ArrayList<>();
list = null; // Error: cannot assign a value to final varible

小结

Java当中lambda是十分的复杂的,是Java8当中的一个最重要的更新之一。之前我也没有学过Java8的新特性(现在已经Java13了),所以这里的lambda也就不扯那么多了。下面有一篇文章对lambda的介绍是非常的详细的,建议看看。

深入理解Java 8 lambda

Java lambda表达式入门


一枚小菜鸡