Java Lambda表达式(写给自己看的)

本文详细介绍了Java 8中的Lambda表达式的使用方法及优势,包括替代匿名内部类、集合迭代、实现map/reduce/filter等功能。同时,还探讨了Lambda表达式的语法特点及其在实际开发中的应用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java Lambda表达式

Lambda表达式主要是为了使得代码变得更加简洁的Java8之后的特性功能,他的常见功能有替代匿名内部类,对集合内部进行迭代(一般是forEach),实现map和reduce和filter。

lambda常见函数语法定义

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
//入参为空
TestDemo no_param = () -> "hi, no param";
TestDemo no_param2 = () -> { return "hi, no param"; };
System.out.println(no_param.hi());

//单个参数
TestDemo2 param = name -> name;
TestDemo2 param2 = name -> { return name;};
System.out.println(param.hei("hei, grils"));

//多个参数
TestDemo3 multiple = (String hello, String name) -> hello + " " + name;
//一条返回语句,可以省略大括号和return
TestDemo3 multiple2 = (hello, name) -> hello + name;
//多条处理语句,需要大括号和return
TestDemo3 multiple3 = (hello, name) -> {
    System.out.println("进入内部");
    return hello + name;
};
System.out.println(multiple.greet("hello", "lambda"));

1.替代匿名内部类

首先我们要明白一下,在java的设计中有些Java对象只是对单个函数的封装。例如下面这个典型用例:Java API中定义了一个接口(一般被称为回调接口),用户通过提供这个接口的实例来传入指定行为,例如:

public interface ActionListener {
  voidactionPerformed(ActionEvent e);
}

这里并不需要专门定义一个类来实现ActionListener接口,因为它只会在调用处被使用一次。用户一般会使用匿名类型把行为内联(inline):

button.addActionListener(new ActionListener {
 public void actionPerformed(ActionEvent e) {
   ui.dazzle(e.getModifiers());
  }
});

//等效于
button.addActionListener((ActionEvent e) -> ui.dazzle(e.getModifiers()));

随着回调模式和函数式编程风格的日益流行,我们需要在Java中提供一种尽可能轻量级的将代码封装为数据(Model code as data)的方法。匿名内部类并不是一个好的选择,因为:

  • 语法过于冗余

  • 匿名类中的this和变量名容易使人产生误解

  • 类型载入和实例创建语义不够灵活

  • 无法捕获非final的局部变量

  • 无法对控制流进行抽象

上面提到的ActionListener接口只有一个方法,大多数回调接口都拥有这个特征:比如Runnable接口和Comparator接口。我们把这些只拥有一个方法的接口称为函数式接口。(之前它们被称为SAM类型,即单抽象方法类型(Single Abstract Method))

我们并不需要额外的工作来声明一个接口是函数式接口:编译器会根据接口的结构自行判断(判断过程并非简单的对接口方法计数:一个接口可能冗余的定义了一个Object已经提供的方法,比如toString(),或者定义了静态方法或默认方法,这些都不属于函数式接口方法的范畴)。不过API作者们可以通过@FunctionalInterface注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。

双冒号:https://blog.youkuaiyun.com/zhoufanyang_china/article/details/87798829?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control

这里主要讲一下双冒号的个人理解:
首先冒号是一种比较特殊的lambda写法,他可以抽象的理解成一个传入方法的结构,而不是像以前一样,传入的是实例或者接口或者基础类型。因此像下面的代码有三种写法

public class AtomicDemo {
    public static void hellooo() {
        System.out.println("hello");
    }
     
    public static void main(String[] args){
    	//1
    	new Thread(new Runnable() {
            @Override
            public void run() {
                new AtomicDemo().hellooo();
            }
        }).start();
        //2
        new Thread(AtomicDemo::hellooo).start();
        //3. 这种写法不太好。虽然hellooo方法和你的主方法在一个主类中,
        //lamabda表达式可以识别你主类中调用的这个方法,但是很少有这么写的。
        new Thread( () -> hellooo() ).start();
    }
}

究其原因就是:在java8之后Runnable加了个注解@FunctionInterface。对于Java来说使用lambda表达式的时候对于有且仅有一个方法的接口即SAM,它可以自动识别,因此不需要注明使用了Runnable接口,也不需要去实现,jvm自己会去完成这边的操作,只需要定义在Runnable的run方法内部需要实现的函数即可。
此外对于冒号来说在使用forEach的时候里面的参数会自动默认作为传参进入到双冒号的函数内部,再做调用。
对于所有的接口函数来说唯一要求是函数式接口中的抽象方法形参个数必须和引用类方法中的形参个数相同。

2. forEach(注意这里是一个方法,而不是java8之前的循环)

这里使用的是 类.forEach

2.1 Map

1.8之前

	Map<String, Integer> items = new HashMap<>();
    items.put("A", 10);
    items.put("B", 20);
    items.put("C", 30);
    items.put("D", 40);
    items.put("E", 50);
    items.put("F", 60);

    for (Map.Entry<String, Integer> entry : items.entrySet()) {
        System.out.println("Item : " + entry.getKey() + " Count : " + entry.getValue());
    }

1.8之后

	Map<String, Integer> items = new HashMap<>();
	items.put("A", 10);
	items.put("B", 20);
	items.put("C", 30);
	items.put("D", 40);
	items.put("E", 50);
	items.put("F", 60);
	
	items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v));
	
	items.forEach((k,v)->{
		System.out.println("Item : " + k + " Count : " + v);
		if("E".equals(k)){
			System.out.println("Hello E");
		}
	});

2.2 List

之前

    List<String> items = new ArrayList<>();
    items.add("A");
    items.add("B");
    items.add("C");
    items.add("D");
    items.add("E");

    for(String item : items){
        System.out.println(item);
    }

之后

    List<String> items = new ArrayList<>();
	items.add("A");
	items.add("B");
	items.add("C");
	items.add("D");
	items.add("E");

	//lambda
	//Output : A,B,C,D,E
	items.forEach(item->System.out.println(item));
		
	//Output : C
	items.forEach(item->{
		if("C".equals(item)){
			System.out.println(item);
		}
	});
		
	//method reference
	//Output : A,B,C,D,E
	items.forEach(System.out::println);
	
	//Stream and filter
	//Output : B
	items.stream()
		.filter(s->s.contains("B"))
		.forEach(System.out::println);

加一个我在leetcode 399的一个循环List<List<String>>并加入Set的方法

	Set<String> set = new HashSet<>();
    List<List<String>> equations = new ArrayList<>();
    equations.add(new ArrayList<>(list));
    equations.forEach(item->{
        item.forEach(i->{
            set.add(i);
        });
    });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值