【Java8新特性】 1.Lambda表达式与函数式接口

目录

一、Lambda表达式

二、方法引用、构造器引用

三、函数式接口

四、接口方法的默认实现

小结


一、Lambda表达式

Lambda作为函数式编程中的基础部分,在其他编程语言(例如:Scala)中早就广为使用,但在Java领域中发展较慢,直到java8才开始支持Lambda。

Lambda表达式格式:参数->表达式 或 (参数1,参数2,...)->{表达式1; 表达式2; ...},当参数为单个时,()可以省略;同样地当表达式为单个时,{}可以省略;参数可以写类型,也可以不写(不写的话,编译器编译时可自动推断参数的类型)。

Lambda表达式本质上是一个匿名方法,其底层还是通过invokedynamic指令生成匿名类来实现的,它提供了简单的语法和写作方式,通过Lambda表达式替代函数式接口,使代码变得更紧凑简洁,举几个例子感受下:

/** 创建线程 */
Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Java8之前常用的创建线程方式");
    }
});

Thread thread2 = new Thread(() -> System.out.println("使用Lambda表达式简化线程创建"));

/** 定义集合的比较器Comparator */
List<Integer> list = Arrays.asList(new Integer[]{15, 30, 25, 56, 66, 42, 19});
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});

Collections.sort(list, ((o1, o2) -> o1.compareTo(o2)));

Lambda表达式的简洁优势从上面例子中体现的淋漓尽致,这对提升Java程序代码的优雅性是重要的一步。此外,Lambda表达式还给Java带来了闭包功能,换句话说就是Lambda表达中可以访问域外的局部变量(但要注意不能在lambda表达式内部修改定义在域外的局部变量!!),在此之前的内部类也是Java闭包功能的体现,关于函数闭包和回调有时间再专门整理分析吧。 

Java8之前,我们进行接口编程的话,大致步骤如下:定义一个接口 -- 使用new方式创建接口对象 -- 重写接口方法 -- 通过接口实例调用接口方法,举个例子:

public class XxxTest {
    /** 接口编程,实现两个int整数求和 */
    public static void main(String[] args) {
        Func func = new Func() {
            @Override
            public int add(int m, int n) {
                return m + n;
            }
        };
        System.out.println(func.add(5, 6));
    }
}
// 自定义接口
interface Func{
    int add(int a, int b);
}

上面的示例中,通过"new 接口名称(){} "的方式,本质上是一种匿名内部类的实现。当有了Lambda表达式之后,如何来简化接口编程呢?演示如下:(这里顺带测试了一下变量作用域的问题

public class XxxTest {
    /** Lambda表达式简化接口编程,实现两个int整数求和 */
    public static void main(String[] args) {
        int a; // test
    // Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量
//        Func f = (a, b) -> a + b; // 编译报错!!!
        Func f = (c, b) -> c + b;
        System.out.println(f.add(1, 3));
    }
}
// 自定义接口
interface Func{
    int add(int a, int b);
}

两行代码轻松搞定,简化接口编程 —— 这就是Lambda表达式的最直接应用了。而实际中Lambda表达式最重要的应用,当属Java集合通过stream流 + Lambda表达式操作集合元素了,这一点在后面的Stream API使用中能很好的体现出来。

最后值得一提的是,Lambda表达式还可以通过方法引用、构造器引用等方式继续简化!!!

二、方法引用、构造器引用

方法引用简化Lambda表达式的规则如下:

  • objectName::instanceMethod  即 对象::实例方法
  • ClassName::staticMethod       即 类::静态方法
  • ClassName::instanceMethod  即 类::实例方法

构造器引用简化Lambda表达式的规则如下:

  • ClassName::new  即 类::new

这里简化规则很清晰,不再举例说明了。

三、函数式接口

函数式接口(Functional Interface)指的是有且仅有一个抽象方法可有多个非抽象方法的接口。上面示例定义的Func接口就是一个的函数式接口,接口里只定义了一个add()抽象方法,为了满足javadoc规范,最好在Func接口上使用@FunctionalInterface注解,来标记该接口是一个函数式接口。Java8之前,JDK有哪些函数式接口和抽象方法呢,在此盘点一下:

  • java.lang.Runnable  ——  public abstract void run();
  • java.util.concurrent.Callable  —— V call() throws Exception;
  • java.security.PrivilegedAction
  • java.util.Comparator ——  int compare(T o1, T o2);
  • java.io.FileFilter  ——  boolean accept(File pathname);
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler ——  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

本文一开始举例用的就是Runnable接口和Comparator接口!!Java8时,在java.util.function包下新增了许多函数式接口,在此也来盘点下,其中最核心的函数式接口及抽象方法有:(其余的函数式接口可参考API文档)

  • Consumer<T>:消费型接口 ——  void accept(T t)
  • Function<T,R>:函数型接口 ——  R apply(T t)
  • Predicate<T>:断定型接口 ——  boolean test(T t)
  • Supplier<T>:供应型接口 ——  T get()

下面通过demo演示下如何使用这些函数式接口:

  1、消费型接口:Consumer<T>    void accept(T t)

   属于有参数,无返回值:

@Test
void test() {
    Consumer consumer = name -> System.out.println(name + ",欢迎访问我的博客!");
    consumer.accept("weixiangxiang");
    // 指定T类型
    Consumer<String> consumer1 = (String name) -> System.out.println(name + ",欢迎访问我的CSDN博客!");
    consumer1.accept("xxwei3");
}

  2、供应型接口:Supplier<T>    T get()

 属于无参数,有返回值:

@Test
void test() {
    Supplier supplier = ()-> 6;
    System.out.println(supplier.get());
    // 指定T类型
    Supplier<Double> supplier1 = ()-> 3.14;
    System.out.println(supplier1.get());
}

  3、函数型接口:Function<T,R>    R apply(T t)

属于有参数,有返回值:

@Test
void test() {
    Function<Integer, Integer> function = (a) -> a * 2;
    System.out.println(function.apply(5));
}

  4、断定型接口:Predicate<T>    boolean test(T t)

@Test
void test() {
    List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    eval(list, n -> n % 2 == 0); // 输出全部偶数
    eval(list, n -> n >= 5); // 输出>=5的数字
}

public static void eval(List<Integer> list, Predicate<Integer> predicate) {
    for (Integer n : list) {
        if (predicate.test(n)) {
            System.out.println(n + " ");
        }
    }
}

四、接口方法的默认实现

接口是一种面向抽象的编程技术,Java8之前的接口方法是不能有任何实现逻辑的,如果需要修改该接口的方法,则实现了该接口的类也要全部同步修改,这不仅违反了面向对象里的开闭原则(对扩展开放且对修改关闭),而且涉及到JDK众多实现类的修改也很头疼,因此Java8开始引入了接口方法的默认实现,可以很好的解决接口的修改与现有的实现不兼容的问题!!!

接口方法默认实现的定义格式有:(有默认方法 和 静态方法的默认实现)

  • default  返回值  方法名(参数列表){  实现逻辑;}
  • 权限修饰符  static  返回值  方法名(参数列表){  实现逻辑;}

以函数式接口Comparator为例,compare()方法是一个抽象方法,thenComparing()方法是默认方法且带有默认实现,comparing()方法是静态方法且带有默认实现,部分源码如下:

@FunctionalInterface
public interface Comparator<T> {
    // 抽象方法
    int compare(T o1, T o2);

    // 静态方法(带有默认实现)
    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor){
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

    // 默认方法(带有默认实现)
    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.comparing(Integer::new))。

当一个类实现了多个函数式接口,且多个函数式接口存在同名的默认方法时,调用默认方法该如何区分呢,通过 接口名.super.默认方法 即可(Comparator.super.thenComparing(...);)。

小结

Java8是一个重大变化的版本,首次引入Lambda表达式替代函数式接口,极大的简化了Java程序代码,通过方法引用和构造器引用还可以继续简化Lambda表达式,这一切似乎开始改变着我们对Java代码臃肿而繁杂的印象。而函数式接口在Java8之前就已存在了,在使用时感觉很不友好和优雅,Java8之后又新增了许多重要的函数式接口,同时伴随着Lambda表达式和Stream流的到来,改变了Java代码的书写方式也提升着程序处理数据的性能。另外,接口编程中的接口方法通过引入默认实现,解决了修改接口与现有实现不兼容的问题,这也是一个令人欣喜地变化。

自从sun公司被Oracle收购后,Java迎来了诸多方面的改变,让我们感受最直接的是Java版本迭代周期明显的比以前缩短了,因此还有人调侃说Java8刚学完Java快到16了,比如Java8引入了Lambda表达式,Java10开始支持局部变量的类型推断(即var变量),在Java11中则对var变量进行了增强,支持在Lambda表达式参数声明时使用var变量!当今时代,科技日新月异,今天用的东西可能明天就是过时的了,唯有不断保持学习和进步才能让自己立于不败之地,而这一切的变化,都值得我们去学习和掌握,不积硅步无以至千里,点滴付出终将有所收获,共同进步吧 ~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值