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; }


640.png


lambda 语法例子


具体解释,如上图。此外,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 表达式示例



五、Github 源码地址


文章出现的所有代码都上传到我的 Github 了,觉得不错。给个 Star 呗。


  • Github 源码地址


相关文章
|
14天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
1天前
|
Java API 开发者
探索Java中的Lambda表达式:简洁与强大的代码实践
本文深入探讨Java中Lambda表达式的定义、用法及优势,通过实例展示其如何简化代码、提升可读性,并强调在使用中需注意的兼容性和效率问题。Lambda作为Java 8的亮点功能,不仅优化了集合操作,还促进了函数式编程范式的应用,为开发者提供了更灵活的编码方式。
|
3天前
|
Java 测试技术
java正则表达式
java正则表达式
22 7
|
4天前
|
Java 开发者
探索Java中的Lambda表达式
【9月更文挑战第23天】本文将深入探讨Java中的Lambda表达式,从其基本概念、语法结构到实际应用案例,旨在帮助读者更好地理解并掌握这一现代编程特性。我们将通过简洁明了的代码示例,展示Lambda表达式如何简化代码、提高开发效率。无论你是Java新手还是资深开发者,这篇文章都将为你提供有价值的见解和技巧。
|
2天前
|
Java API
Java中的Lambda表达式及其应用
本文将深入探讨Java中的Lambda表达式,通过简洁易懂的语言和示例代码,帮助读者理解Lambda表达式的定义、优势以及在实际开发中的应用。同时,我们将解析一些常见的使用场景,并展示如何利用Lambda表达式简化代码,提高编程效率。
9 2
|
3天前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的探索
【9月更文挑战第24天】本文将深入浅出地介绍Java 8中的重要新特性——Lambda表达式和Stream API,通过实例解析其语法、用法及背后的设计哲学。我们将一探究竟,看看这些新特性如何让Java代码变得更加简洁、易读且富有表现力,同时提升程序的性能和开发效率。
|
9天前
|
Java
Java 正则表达式高级用法
Java 中的正则表达式是强大的文本处理工具,用于搜索、匹配、替换和分割字符串。`java.util.regex` 包提供了 `Pattern` 和 `Matcher` 类来高效处理正则表达式。本文介绍了高级用法,包括使用 `Pattern` 和 `Matcher` 进行匹配、断言(如正向和负向前瞻/后顾)、捕获组与命名组、替换操作、分割字符串、修饰符(如忽略大小写和多行模式)及 Unicode 支持。通过这些功能,可以高效地处理复杂文本数据。
|
10天前
|
Java 程序员 API
Java中的Lambda表达式:简化代码的秘密武器
在Java 8中引入的Lambda表达式是一种强大的编程工具,它可以显著简化代码,提高可读性。本文将介绍Lambda表达式的基本概念、优势以及在实际开发中的应用。通过具体示例,您将了解如何使用Lambda表达式来简化集合操作、线程编程和函数式编程。让我们一起探索这一革命性的特性,看看它是如何改变Java编程方式的。
22 4
|
10天前
|
Java 开发者
探索Java中的Lambda表达式:简化你的代码
【8月更文挑战第49天】在Java 8的发布中,Lambda表达式无疑是最令人兴奋的新特性之一。它不仅为Java开发者提供了一种更加简洁、灵活的编程方式,而且还极大地提高了代码的可读性和开发效率。本文将通过实际代码示例,展示如何利用Lambda表达式优化和重构Java代码,让你的编程之旅更加轻松愉快。
|
13天前
|
Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
【9月更文挑战第14天】本文旨在揭示Java 8中引入的Lambda表达式如何革新了我们编写和管理代码的方式。通过简洁明了的语言和直观的代码示例,我们将一起走进Lambda表达式的世界,了解其基本概念、语法结构以及在实际编程中的应用。文章不仅会展示Lambda表达式的魅力所在,还会指导读者如何在日常工作中有效利用这一特性,以提高编码效率和程序可读性。