[Java]Lambda表达式

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 本文主要介绍了Java中的Lambda表达式,包括其优化匿名内部类的方式、使用规范、内置函数式接口及方法引用等内容。文章详细解析了Lambda的基础语法、参数列表、方法体的简化规则,以及如何利用Lambda优化代码。此外,还探讨了Lambda的作用域和引用规则,强调了对局部变量、成员变量和常量的访问限制,旨在帮助读者全面理解和掌握Lambda表达式的应用。

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://developer.aliyun.com/article/1631535
出自【进步*于辰的博客

启发博文:《Lambda表达式超详细总结》(转发)。
这是我系统学习Lambda表达式时参考的文章。在下文中,我会引用这篇博文中的一些资源,如:图片、阐述。因为我认为那位前辈总结的很好,我再尽力做也最多如此了。如有侵权,请与我联系!
参考笔记一,P33.8、P43.5;笔记三,P33.1、P59.1。

注:掌握Lambda表达式,这两个知识是基础:

  1. 匿名内部类,帮助在于理解“变式”,因为最初开发Lambda表达式的目的之一是简化匿名内部类;
  2. 泛型,用于Lambda表达式的“扩展”。

1、Lambda优化匿名内部类

1.1 内部类

Java中内部类分为成员内部类、静态内部类、局部内部类和匿名内部类。

class TestInnerClass {
   
    // 成员内部类
    class MemberInner {
   }

    // 静态内部类
    static class StaticInner {
   }

    public static void main(String[] args) {
   
        // 局部内部类
        class LocalInner {
   }

        // 匿名内部类
        Runnable run = new Runnable() {
   
            public void run() {
   }
        };
        new Thread(run).start();
    }
}

与Lambda表达式相关的只有匿名内部类。

1.2 优化

示例:

Runnable runa = new Runnable() {
   
    public void run() {
   
        sout "Hello!! CSDN";
    }
};
new Thread(runa).start();

等同于:

class MyRunnable implements Runnable {
   
    public void run() {
   
        sout "Hello!! CSDN";
    }
}
class TestThread {
   
    public static void main(String[] args) {
   
        Runnable runa = new MyRunnable();
        new Thread(runa).start();
    }
}

匿名内部类的一大特点:一次性,故这种写法既冗余,又LOW。

使用Lambda进行优化:

Runnable runa = () -> {
   
    sout "Hello!! CSDN";
};
new Thread(runa).start();

有什么变化?是不是省去了run()的声明部分。

继续优化。

new Thread(() -> {
   
    sout "Hello!! CSDN";
}).start();

这就是“将函数如参数般传递”。大家先初步了解,继续看。

1.3 扩展说明

上面第一个示例中的引用runa,指向 Runnable 接口的匿名内部类(上转),而下文所述的() -> {}和三种引用都可以说是对匿名内部类的简化,但其已不是匿名内部类,其类型是Lambda(一种类型)。

我暂未找到相关资料,就自己验证。看下述示例。

Predicate<Integer> ser1 = new Predicate<Integer>() {
   
    @Override
    public boolean test(Integer a) {
   
        return a.equals(0);
    }
};
Class z1 = ser1.getClass();
sout z1;// class Test2$1
z1.getSimpleName();// ""
z1.getEnclosingClass();// class Test2

Supplier<Integer> ser2 = () -> {
   
    return 0;
};
z1 = ser2.getClass();
sout z1;// class Test2$$Lambda$1/1791741888
z1.getSimpleName();// Test2$$Lambda$1/1791741888
z1.getEnclosingClass();// null

具体说明简单又啰嗦,就不赘述,大家只要了解getSimpleName()getEnclosingClass()这两个方法就明白了。如果大家感兴趣,可以看看我对Class<T>类的解析。

2、Lambda优化规范

2.1 铺垫

从:

Runnable runa = new Runnable() {
   
    public void run() {
   
        sout "Hello!! CSDN";
    }
};
new Thread(runa).start();

优化为:

Runnable runa = () -> {
   
    sout "Hello!! CSDN";
};
new Thread(runa).start();

难道可以随便写?当然不是。那规范是什么?

在上个例子中,大家有没有注意一个问题?有没有run()的声明?没有。因此,Lambda只能用于优化只有一个抽象方法的接口的匿名内部类,这类接口称之为“函数式接口”(注解是@FunctionalInterface)。

举个例:定义java.lang.Number抽象类的匿名内部类。
这是Number抽象类的源码。
在这里插入图片描述

可见,有4个抽象方法。因此,创建 Number 类的匿名内部类,必须重写这4个抽象方法。即:

new Number() {
   
    @Override
    public int intValue() {
   
        return 0;
    }
    @Override
    public long longValue() {
   
        return 0;
    }
    ......
};

如这般,就无法使用 Lambda 进行优化。

2.2 规范

Lambda 基础语法:

() -> {
   }

->的左边是圆括号,对应匿名内部类重写的唯一抽象方法的参数列表;右边是花括号,对应方法体。

参数列表:

  1. 若无参,则必须是(),即:() -> {}
  2. 若只有1个参数xx,则可以是(xx)xx,即:(xx) -> {}xx -> {}。(xx无类型,其名称任意)
  3. 若有2个参数 a 和 b,则必须是(a, b),即:(a, b) -> {}
  4. 举一反三。

方法体:
由于方法体没有方法的声明,故只需注意一些“省略规范”。

1、若只有一条语句,可省略分号和花括号。示例:

new Thread(() -> sout "Hello!! CSDN").start();
// 等同于:
new Thread(() -> {
   // run() 无参
    sout "Hello!! CSDN";
}).start();

2、若方法有返回值,且只有一条语句时,可省略分号、花括号和return。示例:

interface SelfInterface {
   
    double getTrigonometric(double angle);
}

class TestSelf {
   
    public static void main(String[] args) {
   
        SelfInterface service = xx -> Math.sin(xx);
        // 等同于:
        SelfInterface service = xx -> {
   
            return Math.sin(xx);
        };

        double radian = service.getRadian(10);
        sout radian;// -0.5440211108893698
    }
}

2.3 补充说明

以上例为例:
在这里插入图片描述
两个问题:

  1. JVM如何知道图中红框部分是getTrigonometric()的方法体?
  2. JVM是如何知道参数xx的类型是double?(因为Math.sin()的形参类型是 double)

下图是Math.sin()的API截图:
在这里插入图片描述
解释: (以下阐述转载自博文《Lambda表达式超详细总结》)

因为JVM可以通过上下文推断出为何接口实现抽象方法,即“接口推断”推断出所实现的相应抽象方法的参数列表(包括形参类型),即“类型推断
简言之,Lambda表达式依赖于上下文环境

3、Java内置函数式接口

3.1 四大核心函数式接口

(此表格引用自启发博文。)
|函数式接口|参数类型|返回值类型|说明|
|-|-|-|-|
|Consumer<T>消费型接口|T|void|对类型为T的对象应用操作,包含方法:void accept(T t)|
|Supplier<T>供给型接口|无|T|返回类型为T的对象,包含方法:T get()|
|Function|T|R|对类型为T的对象应用操作,并返回类型为R的对象,包含方法:R apply(T t)|
|Predicate<T>断定型接口|T|boolean|断定类型为T的对象是否满足某约束,并返回boolean结果,包含方法:boolean test(T t)|

使用示例:
1:消费型接口 Consumer<T>,方法:void accept(T t)

Consumer<String> service1 = str -> sout str;
service1.accept("Hello!! CSDN");// Hello!! CSDN

2:供给型接口 Supplier<T>,方法:T get()

Supplier<Integer> service2 = () -> (int)(Math.random() * 100);// 获取0~100的随机整数
service2.get();// 66

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

Function<String, Integer> service3 = str -> str.length();
service3.apply("I love China!!");// 14

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

Integer i1 = 10;
Predicate<Integer> service4 = xx -> i1.equals(xx);
service4.test(10);// true
service4.test(20);// false

3.2 其他函数式接口

(此表格引用自启发博文。)
函数式接口 | 参数类型 | 返回值类型 | 说明
|:-:|:-:|:-:|:-:|
BiFunction|T, U|R|对类型为TU的对象应用操作,返回类型为R的对象,包含方法:R apply(T t, U u)
UnaryOperator<T>Function<T>的子接口)|T|T|对类型为T的参数进行一元运算,并返回类型为T的结果,包含方法:T aaply(T t)
BinaryOperator<T>(BiFunction的子接口)|T, T|T|对类型为T的参数进行二元运算,并返回类型为T的结果,包含方法:T apply(T t1, T t2)
BiConsumer|T, U|void|对类型为TU的对象应用操作,包含方法:void accept(T t, U u)
ToIntFunction<T>ToLongFunction<T>ToDoubleFunction<T>|T|int、long、double|分别计算 int、long、double 的函数
IntFunction<R>LongFunction<R>DoubleFunction<R>|int、long、double|R|参数分别为 int、long、double 的函数

使用示例:
1:函数型接口 BiFunction<T, U, R>,方法:R apply(T t, U u)

BiFunction<Character[], Character, Integer> service5 = (charArr, c) -> Arrays.binarySearch(charArr, c);
service5.apply(new Character[]{
   65, 66, 67}, 'B');// 1

关于binarySearch(),解析可查阅Arrays类的第2.2项。

2:函数型接口 UnaryOperator<T>,方法:T apply(T t)

UnaryOperator<String> service6 = str -> str.trim().split("!")[1].trim().toUpperCase();
service6.apply("   Hello KiTi! I am 小白 of csdn   ");// I AM 小白 OF CSDN

3:函数型接口 BinaryOperator<T>,方法:T apply(T t1, T t2)

BinaryOperator<List<Character>> service7 = (list1, list2) -> {
   
    Collections.copy(list2, list1);
    return list2;
};
service7.apply(Arrays.asList('C', 'h'), Arrays.asList('#', '#', 'i', 'n', 'a'));// [C, h, i, n, a]

关于copy(),解析可查阅Collections类的第2.10项;关于asList(),解析可查阅 Arrays 类的第2.1项。

4:消费型接口 BiConsumer<T,R>,方法:void accept(T t, U u)

BiConsumer<char[], Character>service8 = (charArr, c) -> {
   
    Arrays.fill(charArr, c);
    Arrays.toString(charArr);// [#, #, #, #, #]
};
service8.accept(new char[]{
   '进', '步', '*', '于', '辰'}, '#');

关于fill(),解析可查阅 Arrays 类的第2.8项。

5:未知型接口 To[Int/Long?Double]Function<T>,方法:int/long/double applyAs[Int/Long/Double]

// ToIntFunction<T>
ToIntFunction<List> service9= list -> list.size();
int size = service9.applyAsInt(Arrays.asList(true, 3, 2, 1, "yes", 'f', 'i', 'r', 'e'));// 9

// ToLongFunction<T>
ToLongFunction<Integer> service10 = n-> {
   
    long startTime = System.nanoTime();
    while ((n--) > 0) {
   }
    long endTime = System.nanoTime();
    return endTime - startTime;
};
long time = service10.applyAsLong(100000);// 6486400ms

// ToDoubleFunction<T>
ToDoubleFunction<Double> service11 = radian -> Math.toDegrees(radian);
double angle = service11.applyAsDouble(Math.PI);// 180.0

6:函数型接口 [Int/Long/Double]Function<R>,方法:R apply(int/long/double i)

// IntFunction<R>
IntFunction<String> service12 = length -> {
   
    StringBuffer builder = new StringBuffer(10);
    while ((length--) > 0) {
   
        int x = (int) (Math.random() * 10);// 获取0~9的随机数
        builder.append(x);
    }
    return builder.toString();
};
String code = service12.apply(6);// 695821

// LongFunction<R>
LongFunction<Date> service13 = timeStamp -> new Date(timeStamp);
Date current = service13.apply(System.currentTimeMillis());// Tue May 09 22:17:26 CST 2023

// DoubleFunction<R>
DoubleFunction<Long> service14 = originN -> Math.round(originN);
long round = service14.apply(10.5);// 11

3.3 其他函数式接口补充

函数式接口 | 参数类型 | 返回值类型 | 说明
|:-:|:-:|:-:|:-:|
断定型接口 BiPredicate<T, U> | T, U | boolean | 确定类型分别为T/U的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t, U u)`

使用示例:
1、断定型接口 BiPredicate<T,U>,方法:boolean test(T t, U u)

BiPredicate<String, String > service1 = (t, u) -> t.equals(u);
service1.test("abc", "abc");// true
service1.test("abc", "123");// false

3.4 示例说明

大家在看上面的示例时,肯定想吐槽:“你写的那些方法体,很多都是多此一举。”
以实用性的角度来说的确是,例如:sout strstr.lengthxx > 0? true: false,直接调用相应方法不香么?还用Lambda转个弯实现。

那我为何还这样写?
因为我觉得使用Lambda的核心思想在于“灵活、扩展、通用”。因此,我写那些示例的初衷是“任意举例、简单易懂”,目的不在于实现何种功能,而是展示Lambda的使用。

4、Lambda三大引用

先行说明:
以下对于引用的示例,我会尽量用上文中【Java内置函数式接口】及其所举示例进行$“演变”$举例,从而降低大家阅读代码的成本。

4.1 方法引用

4.1.1 介绍

先说结论:

方法引用中所使用的“缺省参数列表”抽象方法的参数列表相同,返回值类型也必须相同。

何为“缺省参数列表”?

“缺省参数列表”指方法引用中已指定参数与所引用方法的参数列表相比较的缺失参数部分。

这是我自定义的概念,看着有点绕口,我会在示例中举例说明。

格式:

1、对象 :: 成员方法名
2.:: 类方法名
3.:: 成员方法名

4.1.2 说明示例

1:boolean equals(String str)
String 类的成员方法boolean equals(String str)定义方法引用,以此例为基础进行举例。

BiPredicate<String, String > service1 = (t, u) -> t.equals(u);

演变办法一:

// 格式:类 :: 成员方法名
BiPredicate<String, String> service = String::equals;
service.test("csdn", "bilibili");// false
service.test("csdn", "csdn");// true

为什么选择 BiPredicate 接口实现方法引用?

  1. equals()的返回值类型为 boolean,则此抽象方法的返回值类型也必须是 boolean;
  2. equals()是成员方法,一共需要2个变量,方法引用为String::equals,由于未指定任何变量,故缺省2个变量。而这两个变量必须由抽象方法的参数列表提供,故参数列表必须且仅有两个参数,且类型要与方法引用的类型相同;
  3. equals()所需的两个变量,类型都是 String,则抽象方法的两个参数的类型只能是 String。故<T><U>的类型实参为 String。

演变办法二:

// 格式:对象 :: 成员方法名
String str = "csdn";
Predicate<String> service = str::equals;
service.test("bilibili");// false
service.test("csdn");// true

为什么选择 Predicate接口实现方法引用?

  1. equals()的返回值类型为 boolean,则此抽象方法的返回值类型也必须是 boolean;
  2. equals()是成员方法,一共需要2个变量,方法引用为str::equals,由于已指定一个变量,故缺省1个变量。而这两个变量必须由抽象方法的参数列表提供,故参数列表必须且仅有一个参数,且类型要与方法引用的类型相同;
  3. equals()所需的两个变量,类型都是 String,则抽象方法的参数的类型只能是 String。故<T>的类型实参为 String。

2:void fill()
Arrays 类的静态方法void fill()定义方法引用,以此例为基础进行举例。

BiConsumer<char[], Character>service8 = (charArr, c) -> {
   
    Arrays.fill(charArr, c);
    Arrays.toString(charArr);// [#, #, #, #, #]
};
service8.accept(new char[]{
   '进', '步', '*', '于', '辰'}, '#');

演变:

// 格式:类 :: 静态方法名
BiConsumer<char[], Character> service8 = Arrays::fill;
char[] charArr = new char[]{
   '进', '步', '*', '于', '辰'};
service8.accept(charArr, '#');
Arrays.toString(charArr);// [#, #, #, #, #]

为什么选择 BiConsumer接口实现方法引用?

  1. fill()无返回值,accept()也无返回值,故匹配;
  2. fill()是类方法,需要两个变量,方法引用是Arrays::fill,由于未指定任何变量,故缺省2个变量。而这两个变量必须由抽象方法的参数列表提供,故参数列表必须且仅有两个参数,且类型要与方法引用的类型相同;
  3. fill()的第1个参数类型为基本数据类型数组,为char[];第2个参数类型为基本数据类型,为char。这2个参数都由accept()提供,故<T>的类型实参$为 char[]<R>的类型实参为 Character,

补充说明:泛型的类型实参只能是,为何这里可以是char[](数组)?具体原因在博文《[Java]泛型》的第7项中有所说明。

4.2 构造器引用

4.2.1 介绍

格式

:: new

顾名思义,“构造器引用”的作用就是实例化,即返回实例
例如:为实体类Users创建构造器引用,则构造器引用固定Users :: new,即返回一个 Users 实例。

约束:方法的参数列表决定了匹配哪个构造方法,即==构造器引用等同于构造方法==。

4.2.2 示例

实体类。

class Users {
   
    private Integer id;
    private String[] hobby;

    public Users() {
   
    }

    public Users(Integer id) {
   
        this.id = id;
    }

    public Users(String[] hobby) {
   
        this.hobby = hobby;
    }

    public Users(Integer id, String[] hobby) {
   
        this.id = id;
        this.hobby = hobby;
    }

    @Override
    public String toString() {
   
        return "Users{" +
                "id=" + id +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }
}

测试。

Supplier<Users> service1 = Users::new;
Users user1 = service1.get();
sout user1;// Users{id=null, hobby=null}

Function<Integer, Users> service2 = Users::new;
Users user2 = service2.apply(1001);
sout user2;// Users{id=1001, hobby=null}

Function<String[], Users> service3 = Users::new;
Users user3 = service3.apply(new String[]{
   "编程", "Game"});
sout user3;// Users{id=null, hobby=[编程, Game]}

BiFunction<Integer, String[], Users> service4 = Users::new;
Users user4 = service4.apply(1002, new String[]{
   "java", "cf"});
sout user4;// Users{id=1002, hobby=[java, cf]}

4.3 数组引用

格式

类型[] :: new

与构造器引用同理。不过,数组引用返回的是数组。(我暂不知如何使用数组引用创建非空数组)

示例:

Function<Integer, Integer[]> service1 = Integer[]::new;
Integer[] arr = service1.apply(5);
Arrays.toString(arr);// [null, null, null, null]

5、Lambda作用域

以下阐述转载自博文《Lambda表达式超详细总结》。

Lambda表达式可以看作是匿名内部类实例化的对象,Lambda表达式对变量的访问限制和匿名内部类一样。因此Lambda表达式可以访问局部变量、局部引用,静态变量和成员变量。

5.1 引用局部常量

规定在Lambda表达式中只能引用由final修饰的局部变量,即局部常量,包括局部基本类型常量和局部引用类型常量。

5.1.1 引用局部基本类型常量

double d1 = 10.2;-------------------------------------------A
// final double d1 = 10.2;----------------------------------B
UnaryOperator<Double> service = d -> Math.floor(d + d1);----C
// d1 = 5.1;------------------------------------------------D
service.apply(5.9);// 16.0

d1定义为变量(A),可当引用于 Lambda 中时(C),会隐式转为常量,但当为d1赋值时(D),这种“隐式转换”功能会失效,d1仍为变量,则C会编译报错。

若将d1显式定义为常量(B),则C可编译通过,但由于常量不可修改,D将会编译报错。

5.1.2 引用局部引用类型常量

示例1:

String subStr = "csdn";
Predicate<String> service = str -> str.contains(subStr);
service.test("csdn, bilibili, 博客园");// true
// subStr = "bili";

此示例与上文中【引用局部基本类型常量】的示例同理。

示例2:

List list = new ArrayList();
list.add(2023);
list.add("年");
list.add(5.12);
Supplier<Integer> service = () -> list.size();
service.get();// 3
list.add(true);
service.get();// 4

执行list.add(true)是对list进行了修改,按照上面的结论,这个示例是编译报错的。可实际上编译通过。为什么?难道上面的结论有纰漏??

在后面加上这么一条代码试试:

list = new ArrayList();

这样就编译报错了。大家看出来了吧。

结论:

由 Lambda 引用的局部常量不可修改,指的是不可修改引用指向

5.2 引用成员变量、类变量

public class TestReference {
   
    String originStr1 = "csdn,bilibili,博客园";
    static String originStr2 = "csdn,bilibili,博客园";

    public static void main(String[] args) {
   
        Supplier<TestReference> service1 = TestReference::new;
        TestReference t1 = service1.get();

        Supplier<String[]> service2 = () -> t1.originStr1.split(",");
        String[] arr1 = service2.get();-----------A
        Arrays.toString(arr1);// [csdn, bilibili, 博客园]
        t1.originStr1 = "";-----------------------B

        Supplier<String[]> service3 = () -> originStr2.split(",");
        String[] arr2 = service3.get();-----------C
        Arrays.toString(arr2);// [csdn, bilibili, 博客园]
        originStr2 = "";--------------------------D
    }
}

B、D处分别修改成员变量originStr1与类变量originStr2,都编译通过。可见,==Lambda 不限制对成员变量和类变量的引用==。

PS:至于Lambda有没有如上文中【局部常量】般将成员变量或类变量隐式转为常量,暂未可知。不过,我觉得没有隐式转换,因为B、D处编译通过。

5.3 引用成员常量、类常量

以上述【引用成员变量、类变量】的示例为基础,在成员变量originStr1和类变量originStr2的定义前加上final,即:

final String originStr1 = "csdn,bilibili,博客园";
final static String originStr2 = "csdn,bilibili,博客园";

则A、C处都编译通过,说明,==Lambda 不限制对成员常量和类常量的引用==;而B、D处都编译报错。这是常量本身的性质,与 Lambda 无关。

5.4 限制访问局部变量的原因

具体原因,那位前辈已经总结得很全面,我就不班门弄斧了,详述可查阅博文《Lambda表达式超详细总结》(转发)的第8.3项。

总结:限制Lambda访问局部变量的根本原因是为了规避线程安全问题。

最后

本文中的示例是为了方便大家理解、以及阐述Lambda表达式的运用而简单举出的,不一定有实用性,仅是抛砖引玉。示例很多,不过,我所举的示例都是“以简为宗旨”,重心不在于使用Lambda表达式编写多么强大的功能,而在于尽量扩展对Lambda表达式的使用,让大家能够更透彻地理解它的格式、规范和限制等。

本文完结。

相关文章
|
17天前
|
Java
探索Java中的Lambda表达式
【10月更文挑战第37天】本文将带你深入理解Java的Lambda表达式,从基础语法到高级特性,通过实例讲解其在函数式编程中的应用。我们还将探讨Lambda表达式如何简化代码、提高开发效率,并讨论其在实际项目中的应用。
|
20天前
|
Java API
Java中的Lambda表达式与函数式编程####
【10月更文挑战第29天】 本文将深入探讨Java中Lambda表达式的实现及其在函数式编程中的应用。通过对比传统方法,我们将揭示Lambda如何简化代码、提高可读性和维护性。文章还将展示一些实际案例,帮助读者更好地理解和应用Lambda表达式。 ####
|
20天前
|
JSON 自然语言处理 Java
这款轻量级 Java 表达式引擎,真不错!
AviatorScript 是一个高性能、轻量级的脚本语言,基于 JVM(包括 Android 平台)。它支持数字、字符串、正则表达式、布尔值等基本类型,以及所有 Java 运算符。主要特性包括函数式编程、大整数和高精度运算、完整的脚本语法、丰富的内置函数和自定义函数支持。适用于规则判断、公式计算、动态脚本控制等场景。
|
24天前
|
Java API 开发者
Java中的Lambda表达式与函数式编程####
在Java的演变过程中,Lambda表达式和函数式编程的引入无疑是一次重大的飞跃。本文将深入探讨Lambda表达式的定义、用法及优势,并结合实例说明如何在Java中利用Lambda表达式进行函数式编程。通过对比传统编程方式,揭示Lambda表达式如何简化代码、提高开发效率和可维护性。 ####
|
2月前
|
自然语言处理 安全 Java
Aviator Java 表达式引擎
AviatorScript 是一门高性能、轻量级寄宿于 JVM 之上的脚本语言。
49 10
|
4天前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
17 0
|
1月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
2月前
|
Java 测试技术 开发者
🌟Java零基础-Lambda运算符详解 🌟
【10月更文挑战第12天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
23 1
|
12天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####