java8为什么引入lambda?
在java8出现之前,如果想传递一段代码到一个方法里是很不方便的,你几乎不可能将代码块到处传递,因为Java是一个面向对象的语言,因此你要构建一个属于某个类的对象,由它的某个方法来放置你想传递的代码块。
下面看两个非常典型的例子,构造线程与比较器:
构造线程
我们要想在另一个线程中执行一些代码逻辑时,通常会将代码放在一个实现Runnable接口的run方法当中
public static void main(String[] args) {
myThread t = new myThread();
t.start();
}
class myThread implements Runnable {
@Override
public void run() {
System.out.println("放入你想执行的代码");
}
}
你写这段代码的目的就是想开启新的线程来执行你定制的代码,
为此你创建了myThread
。
接着我们看下构造比较器:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(2);
list.add(5);
list.add(1);
list.add(4);
list.add(3);
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
Collections.sort(list, comparator);
}
sort 方法会不断调用compare 方法,对顺序不对的元素进行重新排序,直到有序为止。你写比较器的目的就是给Collections的sort方法传递一段比较逻辑的代码片段,这段代码会被整合到排序排序逻辑中,
为此你写了一个类和一个方法
。
上述两个例子你会看到它们的相似点,
将一段代码传递给其他调用者
(一个线程池或者一个排序方法),这段代码会在新的方法中被调用。
但是,在Java8出现之前,如果你想传递一段代码到另一个方法里是很不方便的,因为Java是一个面向对象的语言,因此你要构建一个属于某个类的对象,由它的某个方法来放置你想传递的代码块。而在一些其他语言中可以直接传递代码块,比如JS,以及C/C++语言的函数指针,为此,Java决定加入了lambda表达式的语法糖。
lambda表达式的语法
Lambda表达式的核心就是
函数式接口
。
什么是函数式接口?
一个接口中只定义了一个抽象函数,称为函数式接口。
java8中引入了函数式接口新特性,使用@FunctionalInterface标识,表示有且只有一个抽象方法,但可以有多个非抽象方法(default,static)。
为什么需要函数式接口?
java中方法是不能作为参数传给另一个方法的,方法的调用是需要引用载体的(类、对象、接口),但为了执行一个方法就去新建一个类或者对象,是很繁琐的,因此接口就充当了这个载体。
为什么Lambda表达式是长这样的
()->
{}
?
当一个接口中只有一个函数的时候,当用到这个接口时,我们都不需要关心方法名是什么,我们关心的是参数和函数体。于是
(参数)->{函数体}
的样子就出来了,并起了个名字为Lambda表达式。
语法
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
可选类型声明:
不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:
一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:
如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:
如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
lambda表达式的例子:
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
执行以上脚本,输出结果为:
$ javac Java8Tester.java
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
public class Java8Tester {
final static String salutation = "Hello! ";
public static void main(String args[]){
GreetingService greetService1 = message ->
System.out.println(salutation + message);
greetService1.sayMessage("Runoob");
}
interface GreetingService {
void sayMessage(String message);
}
}
执行以上脚本,输出结果为:
$ javac Java8Tester.java
$ java Java8Tester
Hello! Runoob
我们也可以直接在 lambda 表达式中访问外层的局部变量:
public class Java8Tester {
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
//报错信息:Variable used in lambda expression should be final or effectively final
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
方法引用
对象::实例方法,将lambda的参数当做方法的参数使用
objectName::instanceMethod
示例:
Consumer<String> sc = System.out::println;
//等效
Consumer<String> sc2 = (x) -> System.out.println(x);
sc.accept("618, 狂欢happy");
类::静态方法
,将lambda的参数当做方法的参数使用
ClassName::staticMethod
示例:
//ClassName::staticMethod 类的静态方法:把表达式的参数值作为staticMethod方法的参数
Function<Integer, String> sf = String::valueOf;
//等效
Function<Integer, String> sf2 = (x) -> String.valueOf(x);
String apply1 = sf.apply(61888);
类::实例方法
,将lambda的第一个参数当做方法的调用者,其他的参数作为方法的参数。开发中尽量少些此类写法,减少后续维护成本。
ClassName::instanceMethod
示例:
//ClassName::instanceMethod 类的实例方法:把表达式的第一个参数当成instanceMethod的调用者,其他参数作为该方法的参数
BiPredicate<String, String> sbp = String::equals;
//等效
BiPredicate<String, String> sbp2 = (x, y) -> x.equals(y);
boolean test = sbp.test("a", "A");
构造函数
无参
的构造方法就是
类::实例方法模型,如:
Supplier<User> us = User::new;
//等效
Supplier<User> us2 = () -> new User();
//获取对象
User user = us.get();
当有参数时:
//一个参数,参数类型不同则会编译出错
Function<Integer, User> uf = id -> new User(id);
//或加括号
Function<Integer, User> uf2 = (id) -> new User(id);
//等效
Function<Integer, User> uf3 = (Integer id) -> new User(id);
User apply = uf.apply(61888);
//两个参数
BiFunction<Integer, String, User> ubf = (id, name) -> new User(id, name);
User user = ubf.apply(618, "狂欢happy");
问题1:lambda表达式中为什么要求外部变量为final?
问题2:lambda表达式是不是就是匿名内部类的一种简写方式?
参考文章:
【小家java】使用lambda表达式传参是否有性能问题?_方向盘(YourBatman)的博客-CSDN博客
lambda表达式的使用
构造线程与比较器的改进
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(2);
list.add(5);
list.add(1);
list.add(4);
list.add(3);
Collections.sort(list, (one,two)->{
return two - one;
});
}
编译器会解析出来 one 与 two 是原先接口compare方法的入参,并自动赋予Integer类型。
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("这是一个线程" + i);
}
}).start();
for (int i = 100; i < 200; i++) {
System.out.println("这是主线程" + i);
}
}
Stream API的使用
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
import org.junit.Test;
import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author shuai.ding
* @date 2022年04月04日 下午9:47
*/
public class CollectionTester {
@Test
public void test1(){
List<String> list = Arrays.asList("a12", "b2222", "c");
// 使用增强for循环实现
for (String s : list) {
if (s.length() > 3) {
System.out.println("使用增强for循环实现:" + s);
}
}
// 使用匿名内部类实现Comsumer接口
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
if (s.length() > 3) {
System.out.println("使用Consumer内部类实现:" + s);
}
}
});
// lambda写法
list.forEach(s -> {
if (s.length() > 3) {
System.out.println("使用lambda表达式实现:" + s);
}
});
}
@Test
public void test2(){
List<String> list = Arrays.asList("a", "b", "c");
List<String> uppercaseNames = new ArrayList<>();
// list.forEach(name -> uppercaseNames.add(name.toUpperCase()));
// System.out.println(uppercaseNames);
list.stream().map(name -> name.toUpperCase())
.forEach(name -> System.out.println(name + " "));
}
/**
* 寻找元素
*/
@Test
public void test3(){
List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
List<String> startWithN = list.stream().filter(name -> name.startsWith("N"))
.collect(Collectors.toList());
System.out.println(startWithN);
}
/**
* lambda表达式重用
*/
@Test
public void test4(){
List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
// final Function<String, Predicate<String>> startsWithLetter = (String letter) -> {
// Predicate<String> checkStartWith = (String name) -> name.startsWith(letter);
// return checkStartWith;
// };
//简写
// final Function<String, Predicate<String>> startsWithLetter = (String letter) -> (String name) -> name.startsWith(letter);
final Function<String, Predicate<String>> startsWithLetter = letter -> name -> name.startsWith(letter);
List<String> startWithNList = list.stream().filter(startsWithLetter.apply("N")).collect(Collectors.toList());
List<String> startWithHList = list.stream().filter(startsWithLetter.apply("H")).collect(Collectors.toList());
System.out.println(startWithNList);
System.out.println(startWithHList);
}
/**
* 挑选元素
*/
@Test
public void test5(){
List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
String startingLetter = "a";
Optional<String> foundName = list.stream().filter(name -> name.startsWith(startingLetter))
.findFirst();
System.out.println(String.format("list name starting with %s:%s",startingLetter,foundName.orElse("No name found")));
}
/**
* 集合规约
*/
@Test
public void test6(){
List<String> list = new ArrayList<>(Arrays.asList("NICE", "HELLO", "HAPPY"));
System.out.println("Total number of characters is all names: "+
list.stream().mapToInt(String::length).sum());
//获取集合中,最长的名字
Optional<String> aLongName = list.stream().reduce((name1,name2) -> name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(name -> System.out.println(String.format("A longest name:%s",name)));
}
/**
* 元素连接
*/
@Test
public void test7(){
List<String> list = Arrays.asList("NICE", "HELLO", "HAPPY");
System.out.println(String.join(",",list));
System.out.println(list.stream().map(String::toUpperCase).collect(Collectors.joining(",")));
}
/**
* list转map
*/
@Test
public void test8(){
List<Account> list = new ArrayList<>();
Account account1 = new Account(1, "zhangsan");
Account account2 = new Account(2, "lisi");
Account account3 = new Account(3, "lisi");
list.add(account1);
list.add(account2);
list.add(account3);
//常规操作
Map<Integer,String> map = list.stream().collect(Collectors.toMap(Account::getId,Account::getUserName));
System.out.println(map);
//收集实体本身map
// Map<Integer,Account> map2 = list.stream().collect(Collectors.toMap(Account::getId,account -> account));
Map<Integer,Account> map2 = list.stream().collect(Collectors.toMap(Account::getId, Function.identity()));
System.out.println(map2);
//重复key的情况
// Map<String,Account> map3 = list.stream().collect(Collectors.toMap(Account::getUserName,Function.identity()));
Map<String,Account> map3 = list.stream().collect(Collectors.toMap(Account::getUserName,Function.identity(),(key1,key2) -> key2));
System.out.println(map3);
//指定具体收集的map
Map<String,Account> map4 = list.stream().collect(Collectors.toMap(Account::getUserName,Function.identity(),(key1,key2) -> key2, LinkedHashMap::new));
System.out.println(map4);
}
/**
* 去重
*/
@Test
public void test9(){
List<String> list = Arrays.asList("123","456","789","123","666");
list.stream().distinct().forEach(System.out::println);
}
/**
* 排序
*/
@Test
public void test10(){
List<Account> list = new ArrayList<>();
Account account1 = new Account(1, "zhangsan");
Account account2 = new Account(2, "lisi");
Account account3 = new Account(2, "lisi2");
Account account4 = new Account(4, "wanger");
list.add(account1);
list.add(account2);
list.add(account3);
list.add(account4);
list.stream()
// .sorted(Comparator.comparingInt(Account::getId))
// .sorted(Comparator.comparing(Account::getUserName))
.sorted((a1,a2) ->a2.getId() - a1.getId())
.forEach(System.out::println);
}
/**
* 返回前n个元素
*/
@Test
public void test11(){
List<String> list = Arrays.asList("333","222","111");
list.stream().limit(2).forEach(System.out::println);
}
/**
* 删除前n个元素
*/
@Test
public void test12(){
List<String> list = Arrays.asList("333","222","111");
list.stream().skip(2).forEach(System.out::println);
}
/**
* 求最值
*/
@Test
public void test13(){
List<String> list = Arrays.asList("333","222","1114");
System.out.println(list.stream().min(String::compareTo).get());
System.out.println(list.stream().max(String::compareTo).get());
}
/**
* 匹配
*/
@Test
public void test14(){
List<Account> list = new ArrayList<>();
Account account1 = new Account(1, "zhangsan");
Account account2 = new Account(2, "lisi");
Account account3 = new Account(3, "wanger");
list.add(account1);
list.add(account2);
list.add(account3);
list.add(account3);
boolean anyMatch = list.stream().anyMatch(a -> a.getId() > 1);
if(anyMatch) System.out.println("有id大于1的记录");
boolean allMath = list.stream().allMatch(a -> a.getId() > 1);
if(allMath) System.out.println("所有记录id都大于1");
boolean noneMatch = list.stream().noneMatch(a -> a.getUserName().equals("ha"));
if(noneMatch) System.out.println("没有记录的userName为ha");
}
/**
* 并行流
*/
@Test
public void test15(){
//输出的顺序是随机的
List<String> list = Arrays.asList("333","222","111");
list.parallelStream().forEach(System.out::println);
//并行处理,默认是非线程安全的
List<Integer> listParallel = new ArrayList<>();
IntStream.range(0, 1000).parallel().forEach(listParallel::add);
System.out.println("listParallel size :" + listParallel.size());
}
public static class Account implements Serializable {
private static final long serialVersionUID = -2369723218842119180L;
private Integer id;
private String userName;
public Account(Integer id, String userName) {
this.id = id;
this.userName = userName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", userName='" + userName + '\'' +
'}';
}
}
}
lambda表达式的优点和缺点
优点
1、函数式编程,会是代码更加简洁,提高编程效率,也是编程语言的发展趋势
2、并行操作容易实现
缺点
1、不容易调试
2、若其他程序员没有学过lambda表达式,代码不容易让其他语言的程序员看懂
3、类型推断让代码可读性变差,例如下面的例子,我们并不能一下子看出v是什么类型的变量
mImageView.setOnClickListener(v -> {
});
参考:
Java8 parallelStream浅析:
Java8 parallelStream浅析 – 知乎
Java8 ParallelStream:
Java8 ParallelStream – GGuoLiang – 博客园