Skip to main content

Command Palette

Search for a command to run...

Comparator 为例,Java Lambda 表达式和函数式接口

Published
2 min read
Comparator 为例,Java Lambda 表达式和函数式接口

Lambda 格式

起因是在多线程编程中看到这种写法:

Thread thread = new Thread(()->{
           System.out.println("test!");
       });

其中 Thread() 传入的参数就是一个 Lambda 表达式

可以将 Lambda 表达式看作一个方法的简写,Lambda 表达式由三部分组成,() 中的是方法中的传入参数,{} 是方法体,-> 表示将参数传入方法体中。

()->{}

比如:

(String first,String second) -> {
            return  first.length()-second.length();
        }

当参数的类型可以推断,比如 first.length() 需要一个 String 类型,那 first 的类型就可以被忽略。

(first,second) -> {
            return  first.length()-second.length();
        }

当只有一个参数的时候,() 甚至可以被省略:

event -> {
            System.out.println(event);
        }

为什么引入 Lambda 表达式

在 Java 中,一切皆对象,如果不采用 Lambda 表达式的话,我们需要先 new 一个对象出来,然后实现这个对象中的方法,再将这个对象作为参数传入,我们以 Comparator 接口作为例子:

我们要将 words 数组中的字符串按照从小到大排序,Arrays.sort 方法需要一个传入一个 Comparator 类型的对象,不使用 Lambda 表达式时:

String[] words =new String[]{"abc","bc","c"};

Comparator<String> comparator = new Comparator<String>() {
    @Override
    public int compare(String first, String second) {
        return first.length() -second.length();
    }
};
Arrays.sort(words,comparator);

for (int i = -1; i < words.length; i++) {
    System.out.println(words[i]);
}

注意,接口是不能被实现的,即接口是不能被 new 的,被 new 的是实现接口的实现类。

使用 Lambda 表达式之后:

String[] words =new String[]{"abc","bc","c"};

Arrays.sort(words,(String first,String second) -> {
    return  first.length()-second.length();
});

for (int i = -1; i < words.length; i++) {
    System.out.println(words[i]);
}

所以 Lambda 表达式存在的主要意义就是忽略了 new 操作,节省了不少代码。

那么什么时候可以用 Lambda 表达式来表示对象呢?

当接口是函数式接口的时候可以用 Lambda 表达式来代替实现类。

函数式接口

对于有且只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个 lambda 表达式。这种接口称为函数式接口(functional interface)。

比如并发编程中经常见到的 Runnale 接口:

public interface Runnable {
    abstract void run();
}

在 Java 接口,所有的抽象方法都是 public 和 abstract 的,所以这两个关键字可以省略

Java 接口中的方法不都是抽象的吗?难道还有不是抽象的方法?

我们以 Comparator 接口为例:

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

       public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }
    ......

在上面的示例中,我们可以用 Lambda 表达式来代替 Comparator 类型的对象,Comparator 也是一个函数式接口,不是说有且只能有一个抽象方法吗?

Comparator 确实只有一个抽象方法 compare(T o1, T o2) 。equals 方法是 Comparator 接口重声明 Object 类中的 equals 方法,并不是一个抽象方法。

在下面的示例中,class B 并没有被强制重写 equals 方法,所以 equals 并不是一个抽象方法。

interface A{
    boolean equals(Object obj);
}
class B implements A{

}
class C{
    public static void main(String[] args) {
        A a =new B();
        System.out.println(a.equals(a));
    }
}
// 打印结果为 true

在接口中重写 Object 类方法主要是出于文档的目的,为了细化 equals 方法的定义。Java API中的一些接口会重新声明Object方法来附加javadoc注释。Comparator API就是这样一个例子。

在 Java1.8 之后,接口中的方法可以有方法体,比如 Comparator 接口中带有 default 和 static 关键字的方法,default 是方法的默认实现,static 是接口中的静态方法,有了 static 方法就可以直接使用而不用再new 一个对象出来了,虽然不违背 Java 语法,但不符合接口的设计初衷。

接口相关

  • 接口绝不能被实例化,即绝不能 new 一个接口出来。

  • 接口绝不能包含变量。

  • 在接口中可以定义常量,常量被自动设为 public static final 类型。

  • 在 Java8 之后,在接口中可以实现方法(default,static 方法)。

  • 接口中的所有方法自动地属于 public。因此,在接口中声明方法时,不必提供关键字 public。也不需要abstract 关键字

  • 在实现接口时,必须把方法声明为public;否则,编译器将认为这个方法的访问属性是包可见性,即类的默认访问属性,之后编译器就会给出试图提供更严格的访问权限的警告信息。

More from this blog

根据前、中、后序数组构造二叉树

根据两个遍历数组生成二叉树,主要是固定住一个根节点,然后去另一个数组查找下标,划分数组做左右子树,再递归执行左子树和右子树。 这里主要讨论的是使用切片的过程中如何确定切片的起始点,即切片的区间,利用的是左子树的长度。 前序和中序构造二叉树 105. 从前序与中序遍历序列构造二叉树 递归加切片, python 中可以使用 index 函数直接获取值的下标。 注意:切片是左闭右开区间,最后一个值取不到 切片的下标如何思考:利用左子树的长度来辅助思考。idx 是中序数组中的当前节点下标,所以左子...

Apr 3, 20242 min read
根据前、中、后序数组构造二叉树

二叉树的遍历

掌握两种方法进行二叉树的遍历,这里重点看迭代法是怎么写,迭代法使用栈来模拟递归中的栈,也可以使用一种通用方式进行前、中、后序遍历。 递归法 def dfs(root) { // 前序遍历 dfs(root.left) // 中序遍历 dfs(root.right) // 后序遍历 } 迭代法:迭代法是用 stack 栈来模拟递归栈 下面这种写法可以统一前序、中序、后序遍历方式的写法,只需要改变入栈顺序 前序遍历:中,左,右中序遍历:左,中,右后序遍历:左...

Apr 3, 20242 min read
二叉树的遍历

函数式编程在 Java 和 Go 中的应用

函数式编程是一种 "编程范式"(programming paradigm),就是如何编写程序的方法论。 函数式编程特点: 函数是"第一等公民" 只用"表达式",不用"语句" "表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。 没有"副作用" 所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就...

Jun 26, 20237 min read
函数式编程在 Java 和 Go 中的应用

Untitled Publication

13 posts