java8新特性-Lambda表达式的详解(从0开始)

简介: 这几天复习了java8的一些新特性,作为一个从java5以来最具革命性的版本,一直没有来得及总结。本系列文章主要是从《java8实战》总结的。这是第一篇文章主要介绍java8的lambda。

一、为什么要使用lambda表达式


如果之前见到的话都会觉得,lambda就是一个匿名函数,我们可以这样来理解就好了,Lambda表达式就是为了使得我们的代码更加的简洁。如何简洁呢?我们直接举个例子来看看:

public class LambdaTest1 {
    @Test
    public void test1() {
        //第一种
        Runnable runnable = new Runnable() {
            public void run() {
                System.out.println("不使用Lambda表达式");
            }
        };
        runnable.run();
        System.out.println("=======================");
        //第二种
        Runnable runnable1 = () -> System.out.println("使用Lambda表达式");
        runnable1.run();
    }
}
/*不使用Lambda表达式
=======================
使用Lambda表达式*/

之前我们新建一个线程使用5行代码,但是如果我们使用lambda表达式只需要1行代码即可,是不是很方便。既然这么牛,下面就来看看Lambda表达式如何使用。


二、lambda表达式的使用


1、基本语法

在上面的例子中我们使用了这样一行() -> System.out.println("使用Lambda表达式");下面我们对lambda的格式进行一个介绍:


(1)左边括号:lambda的形参列表,就好比是我们定义一个接口,里面有一个抽象方法,这个抽象方法的形参列表。


(2)箭头:lambda的操作符,所以你看见这个箭头心中知道这是一个lambda表达式就可以了。


(3)右边lambda体:就好比是我们实现了接口中的抽象方法。

lambda表达式的使用可以分为以下5种基本的情况。我们一个一个来介绍。


2、无参无返回值


这个是最简单的一种情况,就是刚刚我们所举的例子。为了不混淆我们再举一个例子。

//此时如果方法体比较复杂好几行代码,那么这个{}是不能省略的
Runnable runnable1 = () -> {
            System.out.println("使用Lambda表达式");
            System.out.println("使用Lambda表达式");
};

我们可以看到没有任何参数也没有任何返回值,因此可以直接写,不过lambda体如果不是一行代码,那么就需要使用{}将其括起来。


3、有参数无返回值


@Test
    public void test2() {
        //第一种:没有使用lambda表达式
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        consumer.accept("没有使用lambda:有参数,但是没有返回值");
        //第二种:使用lambda表达式
        Consumer<String> consumer1 = (String s)->{
            //此时只有一行输出代码,因此可以省去外部的{}
            System.out.println(s);
        };
        consumer.accept("使用lambda:有参数,但是没有返回值");
    }

我们看这个例子,只接受String参数,但是缺没有返回值。


4、有参数无返回值,数据类型可省略,称为类型推断


这种情况只能称之为上面的一种特例,只不过我们可以不传入类型,由编译器帮我们推断出来即可。

Consumer<String> consumer1 = (s)->{
      //此时只有一行输出代码,因此可以省去外部的{}
      System.out.println(s);
};
consumer.accept("使用lambda:有参数,但是没有返回值");

在这个例子中我们可以看到,直接把s中的String类型给去掉了,此时运行依然是正确的。这就是编译器自动为我们推断出了s的类型就是String的。可能你会对类型推断有点疑惑,不知道是啥,不过下面举出几个例子相信你应该明白了


//右边的new ArrayList<>()如果没有类型推断应该是
//new ArrayList<Integer>()
ArrayList<Integer> list = new ArrayList<>();
//{}默认就是String
String[] list2= {};
int[] arr = {};

相信现在大家应该能理解了吧。对了还有一点,那就是如果只有一个参数的时候可以直接把s的小括号去掉,有多个参数时候不可以。


5、有多个参数,有返回值


@Test
    public void test3() {
        //第一种:没有使用lambda表达式
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println("o1:"+o1);
                return o1.compareTo(o2);
            }
        };
        System.out.println(comparator.compare(1,2));
        System.out.println("======================");
        //第二种:使用lambda表达式
        Comparator<Integer> comparator2 = (o1,o2)->{
            System.out.println("o1:"+o1);
            return o1.compareTo(o2);
        };
        System.out.println(comparator2.compare(1,2));
    }

我们使用了一个比较器,当然了如果只有一条return语句的话,那样式就更简单了。箭头直接指向我们要返回的结果。

@Test
    public void test3() {
        //第一种:没有使用lambda表达式
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };
        System.out.println(comparator.compare(1,2));
        System.out.println("======================");
        //第二种:使用lambda表达式
        Comparator<Integer> comparator2 = (o1,o2)-> o1.compareTo(o2);
        System.out.println(comparator2.compare(1,2));
    }

这样就把基本的语法介绍完了,如果你只是想认识一下如何使用,下面的就可以不用看了,但是学习嘛,都是往深处钻。下面就深入解析一下:


三、Lambda表达式深入解析


想要对lambda表达式有一个深入的理解,我们需要去认识另外一个知识点,那就是函数式接口。在上面我们的举得例子中比如Consumer或者是Comparator为什么能够使用lambda呢?就是因为实函数式接口,下面我们来认识一下:


1、什么是函数式接口


比如我们的Runnable就是一个函数式接口,我们可以到源码中看看:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

他主要有如下的特点:


(1)含有@FunctionalInterface注解

(2)只有一个抽象方法


也就是说只有函数式接口的变量或者是函数式接口,才能够赋值为Lambda表达式。当然了方法的类型可以任意。


2、函数式接口有什么用呢?


函数式接口能够接受匿名内部类的实例化对象,换句话说,我们可以使用匿名内部类来实例化函数式接口的对象,而Lambda表达式能够代替内部类实现代码的进一步简化。并且java为我们提供了四个比较重要的函数式接口:


  1. 消费型接口:Consumer< T> void accept(T t)有参数,无返回值的抽象方法;
  2. 供给型接口:Supplier < T> T get() 无参有返回值的抽象方法;
  3. 断定型接口: Predicate< T> boolean test(T t):有参,但是返回值类型是固定的boolean
  4. 函数型接口: Function< T,R> R apply(T t)有参有返回值的抽象方法;


这里仅仅是给出了4个,其实java提供了很多。比如java.util.function包下还有很多函数式接口可供使用。


3、自定义一个函数式接口


@FunctionalInterface
public  interface MyInterface{
    void test();
}
public class LambdaTest2 {
    public static void main(String[] args) {
        MyInterface myInterface = () -> System.out.println("test");
    }
}

现在我们定义了一个MyInterface的函数式接口,里面定义了一个test方法,如果我们定义了两个就不能使用lambda表达式了,为什么呢?因为lambda是一个接口方法,如果有两个方法,应该指定哪一个呢?就搞混了。


4、类型推导


在第二部分介绍lambda语法的时候曾经说过,lambda本身具有类型推导,那么这个类型推导可以做到什么程度呢?编译器负责推导lambda的类型,它利用上下文被期待的类型当做推导的目标类型,当满足下面条件时,就会被赋予目标类型:


(1)被期待的目标类型是一个函数式接口

(2)lambda的入参类型和数量与该接口一致

(3)返回类型一致

(4)抛出异常类型一致


其实lambda最后会由编译器生成static 方法在当前类中,利用了invokedynamic命令脱离了内部类实现的优化。这一部分可以参考汪文军大佬的视频。

OK,现在相信你对lambda有了一个更加深刻的认识。我在网上搜索了一下关于lambda表达式的面试题基本上都是基于其语法使用和上面的这几个点。


相关文章
|
23天前
|
存储 Java 开发者
什么是java的Compact Strings特性,什么情况下使用
Java 9引入了紧凑字符串特性,优化了字符串的内存使用。它通过将字符串从UTF-16字符数组改为字节数组存储,根据内容选择更节省内存的编码方式,通常能节省10%至15%的内存。
|
1月前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
56 6
|
1月前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
2月前
|
Java API 开发者
Java中的Lambda表达式:简洁代码的利器####
本文探讨了Java中Lambda表达式的概念、用途及其在简化代码和提高开发效率方面的显著作用。通过具体实例,展示了Lambda表达式如何在Java 8及更高版本中替代传统的匿名内部类,使代码更加简洁易读。文章还简要介绍了Lambda表达式的语法和常见用法,帮助开发者更好地理解和应用这一强大的工具。 ####
|
1月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
2月前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
28 0
|
存储 并行计算 Java
Java8特性大全(最新版)
Java8特性大全(最新版)
|
8天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
10天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
10天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。