微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JAVA8新特性-Lambda表达式

Java8 是自 Java1.0 以来最具革命性的版本,在语法特点、编译器、类 库、开发工具以及 JVM 等方面都带来了不少新特性 , 其中最为核心的为 Lambda 表达式与 Stream API
Lambda 表达式是 Java8 语法新增最大的卖点,将函数式编程引入了
Java ,允许把行为 ( 代码 ) 看成数据,作为一个方法的实参。

其他优势

1.Lambda表达式代码简洁,意图明确,比如使用stream接口代替for循环,只需要书写一行代码就能解决

2.多核友好:Java 函数式编程自带编写并行程序的方法,只需调用parallel方法即可使程序并行

面向对象的束缚

在创建一个线程时需要new一个可以执行类的对象传入到线程中,但我们只想要这个线程做一件事情,把关注点从怎么做,回归到我们实际要做什么的本质上,实现目的的过程并不重要

 如上,为了完成需求,需创建一个 Runnable 接口的匿名内部类对象来给 Thread 对象指定任务内容,再启动该线程。创建匿名内部类对象不是我们的本意,只是面向对象的思想让我们不得不创建一个匿名内部类对象来完成咱们的需求。

所以如果咱们将关注点从 怎么做 回归到 做什么 的本质上,会更加的
简单,只要能够达到目的,如果有更简单的方式,何乐而不为

 这段代码和刚才的执行效果是完全一样的。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以 一种更加简洁的形式被指定。瞬间感觉得到了解放!不再有“不得不创建 Runnable接口对象的束缚,不再有“覆盖重写抽象方法的负担,就是这么简单!

结论
函数式编程思想可以简单快速的满足需求、达到目标效果。而随着技术 的发展,在某些场景下,这种思想更能得到程序员的青睐,所以 Java8中就加入该思想,并在 Lambda 表达式和 Stream 中发挥的淋漓尽致!

Java8 新特性体验

如果是一个函数式接口(接口中有且只有一个抽象方法),那么一些确定的东西即可不必表现出来,只需写出不确定的代码

根据用户提出的不同条件筛选出满足相应条件的商品
需求 1: 筛选出所有名称包含手机的商品
需求 2: 筛选出所有价格大于 1000 的商品
//工具类静态方法
private static List<Product> findPhoneByCondition(List<Product> products, MyPredicate predicate){ 

    List<Product> productList = new ArrayList<>();

    for (Product product : products) { 

        if (predicate.test(product)) { 
            productList.add(product); 
        } 
    }

    return productList; 
}

//函数接口
public interface MyPredicate { 
    boolean test(Product product);
}

//使用
需求1: 工具类.findphoneByCondition(products,p -> p.getName().contains("手机"))
需求2: 工具类.findphoneByCondition(products,p -> p.getPrice() > 1000)

Lambda语法

1.可选声明类型:不需要声明参数类型,编译器可以统一识别参数值

        s -> System.out.println(s)

2.可选的参数圆括号():一个参数无需定义圆括号,但多个参数需要定义圆括号,让这些参数成为一个整体的参数列表

        s -> System.out.println(s)

        (x,y) -> System.out.println(x+y)

3.可选的大括号{}: 如果语句体只有一句语句,则可以不用{}

4.可选的返回关键字return: 如果语句体只有一句语句并且是要返回的结果,则可以省略return关键字和{}

函数式接口

函数式接口:只要确保接口中有且仅有一个抽象方法即可
为了便于区分并方便编译器进行语法校验 , JDK8 中还引入了一个新的注解: @FunctionalInterface
该注解可用于一个接口的定义上 , 一旦使用该注解来定义接口,编译器 将会强制检查该接口是否确
实有且仅有一个抽象方法,否则将会报错。 需要注意的是,即使不使用该注解,只要满足函数
接口的定义,该接口仍然是一个函数式接口,使用起来都一样。( 可以类比下 @Override注解的作
用)

常见的函数式接口

JDK 提供了大量常用的函数式接口以丰富 Lambda 的典型使用场景,它们主要在java.util.function 包中

Consumer(消费型接口): void accept(T t) 特点:传入一个参数,在方法体里操作完,没有返回值

supplier(供给型接口): T get() 特点:不需要参数,返回一个你要生产的对象

Function(函数型接口): R apply(T t) 特点:传入一个任意类型对象,返回任意类型对象

Predicate(断言型接口): boolean test (T t) 特点:传入一个T类型参数,返回布尔值类型结果,适合做对该参数条件判断

方法引用

在使用 Lambda 表达式的时候,我们实际上传递进去的代码就是一种解决方案,拿什么参数做什么操作。那么考虑一种情况,如果我们在 Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢,例如:
Arrays.asList("a", "b", "c").forEach((x) -> System.out.print(x));
这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明 已经有了现成的实现,那就是System.out 对象中的 println(String) 方法
既然 Lambda 希望做的事情就是调用 println(String) 方法,那何必自己手动调用呢?
能否省去 Lambda 的语法格式呢(尽管它已经相当简洁)
只要 引用 过去就好了 ! 请注意其中的双冒号:: 写法,这被称为 方法引用 ,而双冒号是一种新的语法。
Arrays.asList("a", "b", "c").forEach(System.out::println);

语法分析

双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果 Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda 的替代者。
下面两种写法的执行效果完全一样,而方法方法引用的写法复用了已有方案,更加简洁。
方法 :Lambda 表达式: s -> System.out.println(s);
拿到参数之后经 Lambda 之手,继而传递给 System.out.println 方法去处理。
方法: 方法引用: System.out::println
直接让 System.out 中的 println 方法来取代 Lambda

方法引用的语法

1 . 对象的引用 :: 实例方法
Arrays.asList("a","b","c").forEach(System.out::println ); 对象::实例方法
2. 类名 :: 静态方法
supplier<Double> suppp = Math::random;  类名::静态方法
3. 类名 :: 实例方法
Function<Product,String> funnn = Product::getName;  传入的类型类名::实例方法
4. 类名 :: new ( 构造器引用 )
supplier<User> supplier = User::new;   类名::new   调用该类无参构造器创建对象返回

Lambda表达式的延迟执行

一种典型的场景就是对参数进行有条件才使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出
public static void main(String[] args) { 
    String name = "张无忌"; String action="夜店消费"; String money = "20000元"; 
    // 立即拼接字符串
    logMethod("info",name + action + money); 
}

private static void logMethod(String info, String s) { 
    if ("info".equals(info)) { 
        System.out.println(s); 
    } 
}
这段代码存在问题:无论级别是否满足要求,作为 logMethod 方法的第二个参 数,三个字符串一定会首先被拼接并传入方 法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
public static void main(String[] args) { 
    String name = "张无忌"; String action="夜店消费";
    String money = "20000元"; 
    // 延迟拼写字符串 
    logLambdamethod("info",()->{ System.out.println("come in"); 
    return name + action+money; });

}

private static void logLambdamethod(String level, supplier<String> sup) {
      //符合条件才调用接口里的方法返回拼接的字符串
     if ("info".equals(level)){ 
        System.out.println("come in"); 
        System.out.println(sup.get()); 
     } 
}

更强大的接口

在JDK 1.7及更老的版本中,接口中只能包含常量与抽象方法两种内 容,不允许包含其他。但是这种情况在JDK 1.8中已经改变:接口中允许包含default方法static方法并指定方法体的具体实现。

default/static修饰方法

JDK 1.8开始,可以在接口被多个类实现的情况下,追加新的方法定义, 而对已有的若干实现类不产生任何影响。这种新添加方法需要使用default/static关键字进行修饰并指定方法体实现,被称为“ 方法 。相当于它的实现类都拥有了该功能且不用再自己实现

接口冲突

当同时实现的多个接口中存在签名一样的若干个方法体实现(无论方法体中内容是否相同)时,因为不知道使用哪个接口中的实现方法,所以实现类必须进行覆盖重写以解决冲突,

总结

每个接口类型,引用一个匿名实现类,实现接口的方法,再通过接口对象调用实现类的方法,Lambda表达式就是对这个匿名类及实现的简化过程
 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐