lambda表达式
Java8引入了lambda表达式,在其他语言中,比如python、swift都支持lambda表达式,这个特性用起来也非常方便和简洁。
先来看lambda表达式的语法:
() -> {}
() : 括号就是接口方法的括号,接口方法如果有参数,也需要写参数。只有一个参数时,括号可以省略。
-> : 分割形参列表与函数体的。
{} : 如果代码体只有一行代码就可以省略掉花括号,并且如果方法需要有返回值连return关键词都可以省略,系统会自动将这一行代码的结果返回
例子:
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("Objective-C");
list.add("Swift");
list.add("Python");
list.add("Golang");
list.add("Java");
list.sort((s1, s2)->s1.compareTo(s2));
list.forEach(System.out::println);
}
}
/**Golang
Java
Objective-C
Python
Swift**/
上面的代码我们省略了形参的类型,由于只有一行我们同时省略了花括号和return语句,整个代码相比使用匿名内部类更加简洁了。
lambda怎么知道实现的是接口的哪一个方法
lambda表达式的类型也被称为目标类型 target type,该类型必须是函数式接口 Functional Interface,函数式接口代表有且只有一个抽象方法,但是可以包含多个默认方法或类方法的接口,因此使用lambda表达式系统一定知道我们实现的接口的哪一个方法,因为实现的接口有且只有一个抽象方法供我们实现。
函数式接口可以使用注释**@FunctionalInterface**来要求编译器在编译时进行检查,是否只包含一个抽象方法。Java提供了大量的函数式接口这样就能使用lambda表达式简化编程。lambda表达式的目标类型必须是函数式接口,lambda表达式也只能为函数式接口创建对象因为lambda表达式只能实现一个抽象方法。
lambda表达式提供了四种引用方法和构造器的方式:
引用对象的方法 类::实例方法
引用类方法 类::类方法
引用特定对象的方法 特定对象::实例方法
引用类的构造器 类::new
举例:
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("Objective-C");
list.add("Swift");
list.add("Python");
list.add("Golang");
list.add("Java");
//list.sort((s1, s2)->s1.compareTo(s2));
list.sort(String::compareTo);
list.forEach(System.out::println);
}
}
对比上述两行代码,第一个sort函数传入了一个lambda表达式用于实现Comparator接口的compare函数,由于该实现只有一条代码,因此可以省略花括号以及return关键字。第二个sort方法则直接引用了对象的实例方法,语法规则为类::实例方法,系统会自动将函数式接口实现的方法的所有参数中的第一个参数作为调用者,接下来的参数依次传入引用的方法中即自动进行s1.compareTo(s2)的方法调用,明显第二个sort函数调用更加简洁明了。
最后一行代码list.forEach(System.out::println);则引用了类方法,集合类的实例方法forEach接收一个Consumer接口对象,该接口是一个函数式接口,只有一个抽象方法void accept(T t);,因此可以使用lambda表达式进行调用,这里引用System.out的类方法println,引用语法类::类方法,系统会自动将实现的函数式接口方法中的所有参数都传入该类方法并进行自动调用。
总的来说lambda的本质就是为函数型接口的匿名实现进行简化与更简化。
所谓的简化就是lambda的标准形式,所谓的更简化是在标准形式的基础上进行方法引用和构造引用。
方法引用是拿已有的方法去实现此刻的接口。
构造引用是对方法体只有一句new Object()的进一步简化。
Stream API
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。简单来说,它的作用就是通过一系列操作将数据源(集合、数组)转化为想要的结果。
栗子:
public class User {
private String userID;
private boolean isVip;
private int balance;
public User(String userID, boolean isVip, int balance)
{
this.userID = userID;
this.isVip = isVip;
this.balance = balance;
}
public boolean isVip()
{
return this.isVip;
}
public String getUserID()
{
return this.userID;
}
public int getBalance()
{
return this.balance;
}
}
public class HelloWord {
public static void main(String[] args) {
ArrayList<User> users = new ArrayList<>();
users.add(new User("2017001", false, 0));
users.add(new User("2017002", true, 36));
users.add(new User("2017003", false, 98));
users.add(new User("2017004", false, 233));
users.add(new User("2017005", true, 68));
users.add(new User("2017006", true, 599));
users.add(new User("2017007", true, 1023));
users.add(new User("2017008", false, 9));
users.add(new User("2017009", false, 66));
users.add(new User("2017010", false, 88));
//普通实现方式
ArrayList<User> tempArray = new ArrayList<>();
ArrayList<String> idArray = new ArrayList<>(3);
for (User user: users)
{
if (user.isVip())
{
tempArray.add(user);
}
}
tempArray.sort(new Comparator<User>(){
public int compare(User o1, User o2) {
return o2.getBalance() - o1.getBalance();
}
});
System.out.println(tempArray);
for (int i = 0; i < 3; i++)
{
idArray.add(tempArray.get(i).getUserID());
}
for (int i = 0; i < idArray.size(); i++)
{
System.out.println(idArray.get(i));
}
//Stream API实现方式
//也可以使用parallelStream方法获取一个并发的stream,提高计算效率
Stream<User> stream = users.stream();
List<String> array = stream.filter(User::isVip).sorted((t1, t2) -> t2.getBalance() - t1.getBalance()).limit(3).map(User::getUserID).collect(Collectors.toList());
array.forEach(System.out::println);
}
}
//结果
/**
2017007
2017006
2017005**/
上述代码首先定义了一个用户类,这个类保存用户是否是VIP、用户ID以及用户的余额,假如现在有一个需求,将VIP中余额最高的三个用户的ID找出来,传统的思路一般就是创建一个临时的list,然后逐一判断,将所有的VIP用户加入到这个临时的list中,然后调用集合类的sort方法根据余额排序,最后再遍历三次获取余额最高的三个用户的ID等信息。这样的方法看似简单,但代码写出来即混乱也不好看,如果用户量非常大,有几千万甚至几个亿,这样遍历的方式效率就会特别低,如果手工加上多线程的并发操作,代码就更加复杂了
。
上述代码的第二部分使用Stream API的方式来计算,首先通过集合类获取了一个普通的stream,如果数据量大可以使用parallelStream方法获取一个并发的stream,这样接下来的计算程序员不需要编写任何多线程代码系统会自动进行多线程计算。获取了stream以后首先调用filter方法找到是否为VIP用户然后对VIP用户进行排序操作,接下来限制只获取三个用户的信息,然后将用户映射为用户ID,最后将该stream转换为集合类,两种实现方式的结果完全一样,但是明显的采用Stream API的代码更加简洁易懂。
如何使用
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。简单来说,它的作用就是通过一系列操作将数据源(集合、数组)转化为想要的结果。
当我们要使用Stream API时,首先需要创建一个Stream对象,可以通过集合类的实例方法stream或parallelStream来获取一个普通的串行stream或是并行stream。也可以使用Stream、IntStream、LongStream或DoubleStream创建一个Stream对象,Stream是一个比较通用的流,可以代表任何引用数据类型,其他的则是指特定类型的流。最常用的就是通过一个集合类型来获取相应类型的Stream。
流的操作分为中间操作 Intermediate和结束操作 Terminal:
中间操作(Intermediate):一个流可以采用链式调用的方式进行数个中间操作,主要目的就是打开流然后对这个流进行各种过滤、映射、聚集、统计操作等,如上述代码中的filter、map操作等。每一个操作结束后都会返回一个新的流,并且这些操作都是lazy的,也就是在进行结束操作时才会真正的进行计算,一次遍历就计算出所有结果。
结束操作(Terminal):一个流只能执行一个结束操作,当执行了结束操作以后这个流就不能再被执行,也就是说不能再次进行中间操作或结束操作,所以结束操作一定是流的最后一个操作,如上述代码中的collect方法。当开始执行结束操作的时候才会对流进行遍历并且只一次遍历就计算出所有结果
Stream的创建
- 通过集合类创建
通过集合创建Stream的方法是我们最常用的,集合类的实例方法stream和parallelStream可以获取相应的流。
ArrayList<User> users = new ArrayList<>();
users.add(new User("2017001", false, 0));
users.add(new User("2017002", true, 36));
users.add(new User("2017003", false, 98));
Stream<User> stream = users.stream();
- 通过数组构造
String[] str = {"Hello World", "Jiaming Chen", "Zhouhang Cheng"};
Stream<String> stream = Stream.of(str);
- 通过单个元素构造
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
- Stream与Array和Collection的转换
一般我们都会对Stream进行结束操作,用于获取一个数组或是集合类,通过数组和集合类创建Stream前面已经介绍了,这里介绍通过Stream获取数组或集合类。
String[] str = {"Hello World", "Jiaming Chen", "Zhouhang Cheng"};
Stream<String> stream = Stream.of(str);
String[] strArray = stream.toArray(String[]::new);
List<String> strList = stream.collect(Collectors.toList());
ArrayList<String> strArrayList = stream.collect(Collectors.toCollection(ArrayList::new));
Set<String> strSet = stream.collect(Collectors.toSet());
上面的代码分别将流转换为数组、List、ArrayList和Set类型
Stream 常用方法
- filter
filter的栗子前面已经举过了,filter函数需要传入一个实现Predicate函数式接口的对象,该接口的抽象方法test接收一个参数并返回一个boolean值,为true则保留,false则剔除,前文举的栗子就是判断是否为VIP用户,如果是就保留,不是就剔除
原理如图所示:
- map、flatMap
map的栗子前面已经举过了,map函数需要传入一个实现Function函数式接口的对象,该接口的抽象方法apply接收一个参数并返回一个值,可以理解为映射关系,前文举的栗子就是将每一个用户映射为一个userID。
原理如图所示:
map方法是一个一对一的映射,每输入一个数据也只会输出一个值。
flatMap方法是一对多的映射,对每一个元素映射出来的仍旧是一个Stream,然后会将这个子Stream的元素映射到父集合中,栗子如下:
Stream<List<Integer>> inputStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));
List<Integer> integerList = inputStream.flatMap((childList) -> childList.stream()).collect(Collectors.toList());
//将一个“二维数组”flat为“一维数组”
integerList.forEach(System.out::println);
-
limit、skip
limit用于限制获取多少个结果,与数据库中的limit作用类似,skip用于排除前多少个结果。 -
sorted
sorted的栗子前面也举过了,sorted函数需要传入一个实现Comparator函数式接口的对象,该接口的抽象方法compare接收两个参数并返回一个整型值,作用就是排序,与其他常见排序方法一致。 -
distinct
distinct用于剔除重复,与数据库中的distinct用法一致 -
findFirst
findFirst方法总是返回第一个元素,如果没有则返回空,它的返回值类型是Optional类型,接触过swift的同学应该知道,这是一个可选类型,如果有第一个元素则Optional类型中保存的有值,如果没有第一个元素则该类型为空。 -
min、max
min可以对整型流求最小值,返回OptionalInt。
max可以对整型流求最大值,返回OptionalInt。
这两个方法是结束操作,只能调用一次。 -
allMatch、anyMatch、noneMatch
allMatch:Stream中全部元素符合传入的predicate返回 true
anyMatch:Stream中只要有一个元素符合传入的predicate返回 true
noneMatch:Stream中没有一个元素符合传入的predicate返回 true -
reduce
reduce方法用于组合Stream元素,它可以提供一个初始值然后按照传入的计算规则依次和Stream中的元素进行计算,因此上文介绍的min、max都可以看做是reduce的一种实现。
IntStream is = IntStream.range(0, 10);
System.out.println(is.reduce(0, Integer::sum));
IntStream intStream = IntStream.range(0, 10);
System.out.println(intStream.reduce((o1, o2) -> o1 + o2));
Stream<String> stream = Stream.of("Hello", "World", "Jiaming", "Chen");
System.out.println(stream.reduce("", String::concat));
//结果
/**45
OptionalInt[45]
HelloWorldJiamingChen
**/
第一个IntStream调用的reduce方法设置了一个初始值,因此最终reduce计算的结果一定有值,该方法调用Integer的类方法sum用于计算Stream的总和。
第二个IntStream调用reduce方法时没有设置初始值,因此最终reduce计算的结果不一定有值,所以返回值类型是Optional类型,没有提供初始值时会自动将第一个和第二个元素先进行计算,但有可能不存在第一个或第二个元素,因此返回值是Optional类型。