Java 基础(二)| 使用 lambad 表达式的正确姿势

前言

为跳槽面试做准备,今天开始进入 Java 基础的复习。希望基础不好的同学看完这篇文章,能掌握 lambda 表达式,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆。

一、什么是 lambda 表达式

Java8 是我们使用最广泛的稳定 Java 版本,lambda 就是其中最引人瞩目的新特性。lambda 是一种闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,可以使代码看起来更加简洁。是不是听得一脸懵逼?我举个栗子你就明白了。

烂掉牙的例子,在没有 lambda 时候,我们是这样写的:

// 内部类写法
public class InnerClassMain {
    public static void main(String[] args) {
        //匿名内部类写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("内部类写法");
            }
        }).start();
    }
}

有 lambda 之后,我们就用 lambda 写:

// lambda 写法
public class LambdaMain {

    public static void main(String[] args) {
        //lambda 写法
        new Thread(() -> System.out.println("lambda写法")).start();
    }

}

我们应该知道,实现线程有两种方法,一是继承 Thread 类,二是实现 Runnable 接口。那这里采用的就是后者,后者是一个函数式接口。

1.1 函数式接口

@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();
}

从 Runnable 源码可以看到,它是一个函数式接口。这类接口的特点是:用 @FunctionalInterface 注解修饰(主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错),有且只有一个抽象方法。在原生 JDk 中的这类接口就可以使用 lambda 表达式。

上面的概念提到,把函数当做参数来使用。上面的 lambda 例子中,Thread 类的参数就是一个 Runnable 接口,lambda 就是实现这个接口并把它当做参数使用。所以上面的 () -> System.out.println("lambda写法") 就是一个整个 lambda 表达式的参数(注意与后面的方法参数区分开,后面会讲)。细品加粗这句话,可以总结出,lambda 表达式就是创建某个类的函数式接口的实例对象。如:

Runnable runnable = () -> System.out.println("lambda写法");

二、为什么需要 lambda 表达式

明白了什么是 lambda 表达式,那为什么要使用它呢?注意到使用 lambda 创建线程的时候,我们并不关心接口名,方法名,参数名。我们只关注他的参数类型,参数个数,返回值。所以原因就是简化代码,提高可读性

三、如何使用 lambda表达式

3.1 lambda 语法

// 格式遵循: (接口参数)->表达式(具体实现的方法)
(paramters) -> expression 或 (parameters) ->{ expressions; }

具体解释,如上图。此外,lambda 语法注意点:

  • 可选类型声明:方法参数不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但无参数或多个参数需要定义圆括号。
  • 可选的大括号:如果具体实现方法只有一个语句,就不需要使用中括号{}。
  • 可选的返回关键字:如果具体实现方法只有一个表达式,则编译器会自动返回值,如果有多个表达式则,中括号需要指定明表达式返回了一个数值。

使用示例:

public class Example {

    // 定义函数式接口,只能有一个抽象接口,否则会报错
    // 希望在编译期检出报错,请加 @FunctionalInterface 注解
    public interface Hello {
        String hi();
    }

    public interface Hello2 {
        String hei(String hello);
    }

    public interface Hello3 {
        String greet(String hello, String name);
    }

    public static void main(String[] args) {

        // 入参为空
        Hello no_param = () -> "hi, no param";
        Hello no_param2 = () -> {
            return "hi, no param";
        };

        System.out.println(no_param.hi());
        System.out.println(no_param2.hi());

        // 单个参数,一条返回语句,可以省略大括号和 return
        Hello2 param = name -> name;
        Hello2 param2 = name -> {
            return name;
        };

        // 打印
        System.out.println(param.hei("hei, 一个优秀的废人"));
        System.out.println(param2.hei("hei, 一个优秀的废人"));

        // 多个参数
        Hello3 multiple = (String hello, String name) -> hello + " " + name;

        // 一条返回语句,可以省略大括号和 return
        Hello3 multiple2 = (hello, name) -> hello + name;

        // 多条处理语句,需要大括号和 return
        Hello3 multiple3 = (hello, name) -> {
            System.out.println(" 进入内部 ");
            return hello + name;
        };

        // 打印
        System.out.println(multiple.greet("hello,", "祝2020脱单"));
        System.out.println(multiple2.greet("hello,", "祝2020脱单"));
        System.out.println(multiple3.greet("hello,", "祝2020脱单"));
    }
}

3.3 方法引用

看一个简单的方法引用例子:

Consumer<String> sc = System.out::println;
sc.accept("一个优秀的废人");

// 等效于
Consumer<String> sc2 = (x) -> System.out.println(x);
sc2.accept("一个优秀的废人");

Consumer 函数式接口源码:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

你可能有点懵,为什么可以这样写?别急我们分析一波:Consumer 是一个函数式接口,抽象方法是 void accept(T t),参数都是 T。那我们现在有这样一个需求,我想利用这个接口的抽象方法,做一下控制台打印。正常情况下,我们需要实现这个接口,实现它的抽象方法,来实现这个需求:

public class ConsumerImpl implements Consumer<String> {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
}

实现之后,这个抽象方法变具体了。作用就是控制台打印,那就意味着抽象方法刚好可以用实际方法: System.out.println(s) 来实现,所以我们可以使用方法引用。

总结:函数式接口的抽象方法实现恰好可以通过调用一个实际方法来实现时,就可以用方法引用。

方法引用的三种形式:

// 将抽象方法参数当做实际方法的参数使用
对象::实例方法 objectName::instanceMethod
// 将抽象方法参数当做实际方法的参数使用
类::静态方法 ClassName::staticMethod
// 将方法参数的第一个参数当做方法的调用者,其他的参数作为方法的参数
类::实例方法  ClassName::instanceMethod 

自定义一个方法类:

public class Method {
    // 静态方法
    public static void StaticMethod(String name) {
        System.out.println(name);
    }

    // 实例方法
    public void InstanceMethod(String name) {
        System.out.println(name);
    }

    // 无参构造方法
    public Method() {
    }

    // 有参数构造
    public Method(String methodName) {
        System.out.println(methodName);
    }
}

测试用例:

public class MethodExample {

    public static void main(String[] args) {

        // 静态方法引用--通过类名调用
        Consumer<String> consumerStatic = Method::StaticMethod;
        consumerStatic.accept("静态方法");
        // 等价于
        Consumer<String> consumerStatic2 = (x) -> Method.StaticMethod(x);
        consumerStatic2.accept("静态方法");

        System.out.println("--------------------------");
        //非静态方法引用--通过实例调用
        Method method = new Method();
        Consumer<String> consumerInstance = method::InstanceMethod;
        consumerInstance.accept("对象的实例方法");
        // 等价于
        Consumer<String> consumerInstance2 = (x) -> method.InstanceMethod(x);
        consumerInstance2.accept("对象的实例方法");

        System.out.println("--------------------------");
        //ClassName::instanceMethod  类的实例方法:把表达式的第一个参数当成 instanceMethod 的调用者,其他参数作为该方法的参数
        BiPredicate<String, String> sbp = String::equals;
        System.out.println("类的实例方法 " + sbp.test("a", "A"));
        // 等效
        BiPredicate<String, String> sbp2 = (x, y) -> x.equals(y);
        System.out.println("类的实例方法 " + sbp2.test("a", "A"));
    }
}

输出结果:

静态方法
静态方法
--------------------------
对象的实例方法
对象的实例方法
--------------------------
类的实例方法false
类的实例方法false

3.4 构造器引用

public class ConstructMethodExample {

    public static void main(String [] args) {
        // 构造方法方法引用--无参数(可以使用方法引用)
        Supplier<Method> supplier = Method::new;
        System.out.println(supplier.get());
        // 等价于
        Supplier<Method> supplier2 = () -> new Method();
        System.out.println(supplier2.get());

        // 构造方法方法引用--有参数
        Function<String, Method> uf = name -> new Method(name);
        Method method = uf.apply("一个优秀的废人");
        System.out.println(method.toString());
    }

}

3.5 变量作用域

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

public class VariableScopeTest {

    // 定义一个接口
    public interface Converter<T1, T2> {
        void convert(int i);
    }

    public static void main(String [] args) {

        // 定义为 final 强制不能修改
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        // 输出结果为 3
        s.convert(2);
    }

}

变量不声明为 final ,导致可以修改外部变量报错:

int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);

此外,在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量

String first = "";
// 同为 first 变量名,编译会出错
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  

四、十大 lambda 表达式示例

五、源码地址

如果看到这里,喜欢这篇文章的话,帮忙 " 转发 "或者点个" 在看 ",行吗?祝你们 2020 暴富。微信搜索「一个优秀的废人」,欢迎关注。

回复「1024」送你一套完整的 java、python、c++、go、前端、linux、算法、大数据、人工智能、小程序以及英语教程。

回复「电子书」送你 50+ 本 java 电子书。

原文地址:https://www.cnblogs.com/nasus/p/12204900.html

时间: 2024-11-06 03:32:53

Java 基础(二)| 使用 lambad 表达式的正确姿势的相关文章

Java 基础 (二)

接上 Java 基础(一) 大纲:(1)类的高级特性 (2)集合类 (3)异常处理 (4)输入/输出 (5)Swing程序设计 抽象类:只申明方法的存在,而不去实现它的类:抽象类不能被实例化,也就是说不能创建其对象; 语法格式如下: abstract class 类名 { 类体 } 在抽象类中创建,没有实际意义,必须在子类中重写的方法为抽象方法:抽象方法只有方法的申明,没有方法的实现: 基本定义格式如下: abstract <返回值> 方法名(参数列表) PS:抽象方法不能用Private和S

java基础二(阅读Head First Java记录)

写在前面的话 本部分是在语法基础上的一些内容,比如内部java函数库,继承多态等 “与”和“或”运算符 1.短运算符(&&,||) &&与,必须表达式两边都为true表达式才会为true,如果左侧为false了,就不会去计算右方的算式直接返回false,可以用&&来避免操作内容为null指针变量的情况if(refVar!=null&&refVar.isValidType()){} ||同理,如果左侧已经返回true了,不会再去计算右侧就直接返

6、JAVA基础-二维数组 及 面向对象理解

1:二维数组(理解) (1)元素是一维数组的数组. (2)格式: A:数据类型[][] 数组名 = new 数据类型[m][n]; B:数据类型[][] 数组名 = new 数据类型[m][]; C:数据类型[][] 数组名 = new 数据类型[][]{{...},{...},{...}}; D:数据类型[][] 数组名 = {{...},{...},{...}}; (3)案例(掌握): A:二维数组的遍历 B:二维数组的求和 C:杨辉三角形 2:两个思考题(理解) (1)Java中的参数传递

Java基础二十二

1 多线程的概述 1.1 进程 就是正在运行的程序.是系统进行资源分配和调度的独立单位.每一个进程都有其自己的内存空间和系统资源. 1.2 多进程的意义? 单进程的计算机只能做一件事情,而我们现在的计算机可以做多件事情.例如:一边玩游戏,一边听音乐. 现在的计算机都支持多进程的,就可以一个时间段内执行多个任务,并且可以提高CPU的使用率. 1.3 线程 在一个进程内又可以执行多个任务,而这每一个任务我们就可以看成是一个线程. 线程是进程的执行单元(执行路径). 单线程:如果程序有一条执行路径.

Java基础(二)继承剖析

继承剖析 1 若是要直接调用父类的构造方法,不调用子类的方法则需要使用的是super()关键字 Publicclass Child extends Parent {          Public Child()          {                    Super(1);//此方法是显示的调用父类的方法,不会再采用默认的方法机制                    System.out.println(“child!”); }   Public static void ma

java基础二 &lt;流程控制语句, 方法,数组,java内存结构&gt; 未完待续...

话不多说,直接上干货... 一:流程控制语句 break :  结束当前循环 continue:  结束本次循环,继续下次循环 return:  结束方法 二: 方法 1.方法的特点: 定义在类中的,有特定功能的函数. 方法与方法之间是平级的,不能在方法中定义方法.方法只能够调用方法. 2.方法的重载: 在同一类中,方法名相同,参数列表不同(个数不同,对应的类型不同). 与返回值类型无关. 对应的类型不同的话,与顺序有关系 与形式参数的变量名无关 3.方法的重写: 子类可继承父类中的方法,而不需

重学JAVA基础(二):Java反射

看一下百度的解释: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息    以及动态调用对象的方法的功能称为java语言的反射机制. 先看一下一个例子: 这是最简单的反射使用方法,通过反射来调用类的方法. 下面通过一个需求来做反射实验:有3种人类(黄,白,黑),分别继承于Human类,都有人类的共同操作Behaviour /** * 行为,区别于动物 * @author tomsnail *

Java基础(二)-static关键字分析

static关键字是我们在编程中经常会使用到的,但有些可能只知其然而不知其所以然.下面介绍static关键字的作用再通过例子结合说明. static关键字共有五种作用(先说明static所修饰的不会改变其(private.protected.default和public)作用域的范围): 修饰成员变量(非局部变量) 修饰成员方法 修饰代码块 修饰内部类 静态导包 怎么会有五种呢,大部分初学者对前面两种或者加上第三种还是很熟悉的,第四种情况可能一开始的人就比较少知道了,第五种就更少人知道了.下面一

Java基础二十

1 转换流 1.1 转换流出现的原因 由于字节流操作中文的时候不是很方便,所以,Java就提供了转换流. 字符流=字节流+编码表. 1.2 String类的编码和解码功能 方法:通过指定的编码,将二进制的数据转换为字符串,即解码 String(byte[] bytes, Charset charset) 方法:通过制定的编码,将字符串转换为二进制的数据,即编码 public byte[] getBytes(Charset charset) 示例: package com.xuweiwei; im