java8 stream
学习 Stream 之前必须先学习 lambda 的相关知识,学习lambda又涉及到匿名内部类,所以这里先介绍匿名内部类,在介绍lambda表达式,最后介绍java8 新特性 Stream
1.匿名内部类
new 实现接口() |父类构造器(实参列表)
{
//匿名内部类的类体部分
}
1.1 父类构造器方式实现匿名内部类
public class A
{
public A() {
super();
}
public A(String name) {
super();
this.name = name;
}
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
public class B extends A
{
public String getName()
{
return "B";
}
}
public class Test2
{
public void print(A a)
{
System.out.println(a.getName());
}
public static void main(String[] args)
{
//1.
Test2 test = new Test2();
test.print(new A()
{
public String getName()
{
return "ttt";
}
});
//2.
test.print(new B());
}
}
对比main 方法里面的1处和2 处,显然1处匿名内部类写法更加简洁,而2处还要去定义一个子类B
1.2 实现接口方式实现匿名内部类
public interface Product
{
public String getName();
}
public interface Product
{
public String getName();
}
public class Iphone implements Product{
@Override
public String getName()
{
return "iphone手机";
}
}
public class Test
{
public void print(Product p)
{
System.out.println(p.getName());
}
public static void main(String[] args)
{
//1.
Test test = new Test();
test.print(new Product()
{
public String getName()
{
return "huawei手机";
}
});
//2.
test.print(new Iphone());
}
}
对比main 方法里面的1处和2 处,显然1处匿名内部类写法更加简洁,而2处还要去定义一个实现类Iphone
2.lambda表达式
2.1 lambad表达式和匿名内部类之间的联系
lambad表达式 和匿名内部类有什么联系?这里我们先通过一个例子来看下:
public interface Calculable
{
int calculateInt(int a, int b);
}
public class Test
{
public int add (int a , int b)
{
Calculable c = new Calculable()
{
@Override
public int calculateInt(int a, int b) {
// TODO Auto-generated method stub
return a+b;
}
};
return c.calculateInt(a, b);
}
public int sub (int a, int b)
{
Calculable c = new Calculable()
{
@Override
public int calculateInt(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
};
return c.calculateInt(a, b);
}
public static void main(String[] args)
{
Test test = new Test();
int result = test.add(3, 4);
int result2 = test.sub(13, 4);
System.out.println(result);
System.out.println(result2);
}
}
上述代码以匿名内部类的形式实现加法,减法运算
public class Test2
{
public int add (int x , int y)
{
Calculable c = (int a,int b) -> {return a+b;};
return c.calculateInt(x, y);
}
public int sub (int x, int y)
{
Calculable c = (int a , int b) -> {return a-b;};
return c.calculateInt(x, y);
}
public static void main(String[] args)
{
Test2 test = new Test2();
int result = test.add(3, 4);
int result2 = test.sub(13, 4);
System.out.println(result);
System.out.println(result2);
}
}
用 Lambda 表达式替代匿名内部类,可见代码变得简洁。通过以上示例我们发现,Lambda 表达式是实现接口的匿名内部类对象,可以作为表达式、方法参数和方法返回值。
如果我们在 Calculable 接口在增加一个抽象方法如下:
会出现如下报错:
因为Lambda 表达式实现的接口不是普通的接口,而是函数式接口。所以这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda 表达式会发生编译错误:
The target type of this expression must be a functional interface
函数式接口就是只显式声明一个抽象方法的接口。为保证方法数量不多不少,java8提供了一个专用注解@FunctionalInterface,这样,当接口中声明的抽象方法多于或少于一个时就会报错。如下图所示:
图一
图二
2.2 lambad表达式语法
lambad表达式主要主要就是替代匿名内部的繁琐语法,它由3部分组成:
1.形参类别。 允许省略形参类型
2.箭头( ->)。
3.代码块。只有一条语句,可以省略花括号,只有一条return语句,可以省略return关键字,lambda会自动返回这条语句的值。
(int x, int y) -> x + y;
参数类型也可以省略,Java编译器会根据上下文推断出来:
(x, y) -> x + y; //返回两数之和
或者
(x, y) -> { return x + y; }
3.java8 stream
Stream有如下三个操作步骤:
一、创建Stream
从一个数据源,如集合、数组中获取流。
二、中间操作
一个操作的中间链,对数据源的数据进行操作。
三、终止操作
一个终止操作,执行中间操作链,并产生结果。
无状态:指元素的处理不受之前元素的影响;
有状态:指该操作只有拿到所有元素之后才能继续下去。
非短路操作:指必须处理所有元素才能得到最终结果;
短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
3.1 创建Stream
3.1.1 Arrays.stream()
当在日常编程中面对的是一个数组,也可以使用Arrays.stream()方法来使用Stream
Integer [] intArrays = new Integer [] {1,2,3};
Stream<Integer> intArrayStream = Arrays.stream(intArrays);
intArrayStream.forEach(e->System.out.println(e));
3.1.2 Stream.of()
当面对数组时除了可以使用Arrays.stream()方法外,还可以使用Stream将需要的数组转成Stream。这个方法不但支持传入数组,将数组转成Stream,也支持传入多个参数,将参数最终转成Stream
Integer [] intArrays = new Integer [] {1,2,3};
Stream<Integer> intStream = Stream.of(1,2,3,4);
Stream<Integer> intStream2 = Stream.of(intArrays);
3.1.3 Stream.generate()
Stream接口有两个用来创建无限Stream的静态方法。一个是generate()方法接受一个参数函数,可以使用类似如下代码来创建一个你需要的Stream。
Stream<String> generateStream = Stream.generate(()->{return "test";}).limit(5);
generateStream.forEach(e->System.out.println(e));
3.1.4 Stream.iterate()
stream接口的另一个用来创建无限Stream的静态方法就是iterate()方法。iterate()方法也是接受一个参数函数,可以用类似如下代码来创建一个你需要的Stream。
Stream<Integer> iterateStream = Stream.iterate(10, a->a+2).limit(6);
iterateStream.forEach(e->System.out.println(e));
3.1.5 使用Collection下的 stream() 和 parallelStream() 方法
Stream可以分为串行与并行两种,串行流和并行流差别就是单线程和多线程的执行。
default Stream stream() : 返回串行流
default Stream parallelStream() : 返回并行流
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
stream.forEach(e->System.out.println(e));
parallelStream.forEach(e->System.out.println(e));
3.2 创建Stream
3.2.1 筛选与切片
filter:Stream的过滤转换,此方法会生成一个新的流,其中包含符合某个特定条件的所有元素。
Integer [] intArrays = new Integer [] {1,2,3};
Stream<Integer> intArrayStream = Arrays.stream(intArrays).filter(e->e>2);
intArrayStream.forEach(e->System.out.println(e));
limit(n):该方法会返回一个包含n个元素的新的流(若总长小于n则返回原始流)
String [] stringArrays = new String [] {"a","b","c","d","e"};
Stream<String> stringStream = Arrays.stream(stringArrays).limit(2);
stringStream.forEach(e->System.out.println(e));
skip(n):方法与limit(n)正好相反,它会丢弃掉前面的n个元素。
String [] stringArrays = new String [] {"a","b","c","d","e"};
Stream<String> skipStream = Arrays.stream(stringArrays).skip(2);
skipStream.forEach(e->System.out.println(e));
distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
Integer [] intArrays = new Integer [] {1,2,3,3,2,1};
Stream<Integer> distinctStream = Arrays.stream(intArrays).distinct();
distinctStream.forEach(e->System.out.println(e));
3.2.2 映射
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> stringList = Arrays.asList("a,b,c", "1,2,3");
Stream<String> mapStream = stringList.stream().map(e->e.replace(",", ""));
mapStream.forEach(e->System.out.println(e));//abc 123
Stream<String> flatMapStream = stringList.stream().flatMap((e)->{String [] arrs = e.split(",");return Arrays.stream(arrs);});
flatMapStream.forEach(e->System.out.println(e));//a b c 1 2 3
3.2.3 排序
sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):定制排序,自定义Comparator排序器
public class Product implements Comparable
{
private double price;
public Product() {
super();
}
public Product(double price) {
super();
this.price = price;
}
@Override
public int compareTo(Object o)
{
Product other = (Product)o;
//由低到高排序
if (this.price > other.price)
{
return 1;
}
else if (this.price == other.price)
{
return 0;
}
else
{
return -1;
}
}
@Override
public String toString() {
return "Product [price=" + price + "]";
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
Product a = new Product(3.14);
Product b = new Product(5.67);
Product c = new Product(2.11);
List<Product> productList = Arrays.asList(a, b, c);
Stream <Product> productStream = productList.stream().sorted();
productStream.forEach(e->System.out.println(e));// Product [price=2.11] Product [price=3.14] Product [price=5.67]
Stream <Product> comparatorStream = productList.stream().sorted((o1,o2)->{
Product p1 = (Product)o1;
Product p2 = (Product)o2;
if (p1.getPrice() > p2.getPrice())
{
return 1;
}
else if (p1.getPrice() == p2.getPrice())
{
return 0;
}
else
{
return -1;
}
});
comparatorStream.forEach(e->System.out.println(e)); // Product [price=2.11] Product [price=3.14] Product [price=5.67]
3.2.4 消费
peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
Product a = new Product(3.14);
Product b = new Product(5.67);
Product c = new Product(2.11);
List<Product> productList = Arrays.asList(a, b, c);
Stream <Product> peekStream = productList.stream().peek(e->e.setPrice(1.1));
peekStream.forEach(e->System.out.println(e));//Product [price=1.1] Product [price=1.1] Product [price=1.1]
3.3 流的终止操作
3.3.1 匹配、聚合操作
allMatch:接收一个 Predicate 函数,当流中所有元素都符合该断言时才返回true,否则返回false
noneMatch:接收一个 Predicate 函数,当流中所有元素都不符合该断言时才返回true,否则返回false
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:返回流中第一个元素
findAny:返回流中的任意元素
count:返回流中元素的总个数
max:返回流中元素最大值
min:返回流中元素最小值
Integer x = Arrays.stream(intArrays).findFirst().get();
Integer y = Arrays.stream(intArrays).findAny().get();
long count = Arrays.stream(intArrays).count();
Integer maxInteger = Arrays.stream(intArrays).max((o1,o2)->{return o1.compareTo(o2);}).get();
Integer minInteger = Arrays.stream(intArrays).min( Integer::compareTo).get();
System.out.println(x);//1
System.out.println(y);//1
System.out.println(count);//6
System.out.println(maxInteger);//3
System.out.println(minInteger);//1
List<Integer> intList3 = Arrays.asList(4,1,10,100,2,5,19,3,3);
Boolean b = intList3.stream().allMatch(e->e >= 1);
System.out.println(b);//true
Boolean b2 = intList3.stream().allMatch(e->e >= 2);
System.out.println(b2);//false
Boolean b3 = intList3.stream().anyMatch(e->e >= 1);
System.out.println(b3);//true
Boolean b4 = intList3.stream().anyMatch(e->e >= 101);
System.out.println(b4);//false
Boolean b5 = intList3.stream().noneMatch(e->e >= 1);
System.out.println(b5);//false
Boolean b6 = intList3.stream().noneMatch(e->e >= 101);
System.out.println(b6);//true
3.3.2 规约操作操作
Optional reduce(BinaryOperator accumulator):第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
T reduce(T identity, BinaryOperator accumulator):流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner):在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行规约。
List<Integer> list = Arrays.asList(1,2,3,4,5);
Integer reduce = list.stream().reduce((x1,x2)->{return x1+x2;}).get();
System.out.println(reduce);//15
Integer reduce2 = list.stream().reduce(3, (x1,x2)->{return x1+x2;});
System.out.println(reduce2);//18
Integer reduce3 = list.stream().reduce(3, (x1,x2)->{return x1+x2;}, (x1,x2)->{return x1*x2;});
System.out.println(reduce3);//18
Integer reduce4 = list.parallelStream().reduce(3, (x1,x2)->{return x1*x2;});
System.out.println(reduce4);//29160
3.3.3 收集操作
3.3.3.1 收集到集合
//收集到一个List
List<Integer> list = Arrays.asList(4,1,10,100,2,5,19,3,3);
List<Integer> newList = list.stream().collect(Collectors.toList());
System.out.println(newList);//[4, 1, 10, 100, 2, 5, 19, 3, 3]
//收集到一个set
Set<Integer> sets = list.stream().collect(Collectors.toSet());
System.out.println(sets );//[1, 2, 19, 3, 4, 100, 5, 10]
//如果想控制返回set的类型
TreeSet<Integer> sets2 = list.stream().collect(Collectors.toCollection(TreeSet::new));
System.out.println(sets2);//[1, 2, 3, 4, 5, 10, 19, 100]
//收集到一个map
List<Room> lsitRoom = Arrays.asList( new Room(2,1),new Room(17,1), new Room(10,1),new Room(1,3));
List<Room> lsitRoom2 = Arrays.asList( new Room(2,1),new Room(17,1), new Room(10,1),new Room(1,3), new Room(1,2));
//当我们希望将集合中的元素收集到Map中时,可以使用Collectors.toMap方法。这个方法有两个参数,用来生成Map的key和value。
Map<Integer,Integer> maps = lsitRoom.stream().collect(Collectors.toMap(Room::getHeigh, Room::getWidth));
System.out.println(maps);//{1=2, 17=1, 2=1, 10=1}
//如果多个元素拥有相同的键,在收集结果时会抛出java.lang.IllegalStateException异常。可以使用第三个参数来解决,第三个参数用来确定当出现键冲突时,该如何处理结果,如果当出现键冲突时只保留一个并且是保留已经存在的值时,就是如下方式。
Map<Integer,Integer> maps2 = lsitRoom2.stream().collect(Collectors.toMap(Room::getHeigh, Room::getWidth, (nowkey,newKey) -> nowkey));
System.out.println(maps2);//{1=2, 17=1, 2=1, 10=1}
//但是通常还是以具体元素作为值的情况多,可以使用Function.identity()来获取实际元素。
Map<Integer,Room> roomMaps1 = lsitRoom.stream().collect(Collectors.toMap(Room::getHeigh, Function.identity()));
System.out.println(roomMaps1);//{1=Room [heigh=1, width=3], 17=Room [heigh=17, width=1], 2=Room [heigh=2, width=1], 10=Room [heigh=10, width=1]}
//如果想指定生成的Map类型,则还需要第三个参数
TreeMap<Integer,Room> roomMaps2 = lsitRoom.stream().collect(Collectors.toMap(Room::getHeigh, Function.identity(), (nowkey,newKey) -> nowkey,TreeMap::new));
System.out.println(roomMaps2);//{1=Room [heigh=1, width=3], 2=Room [heigh=2, width=1], 10=Room [heigh=10, width=1], 17=Room [heigh=17, width=1]}
3.3.3.2 拼接字符串
List<Integer> intList = Arrays.asList(4,1,10,100,2,5,19,3,3);
String s = intList.stream().map(String::valueOf).collect(Collectors.joining(","));
System.out.println(s);
3.3.3.2 收集聚合
分别收集流的总和、平均值、最大值或者最小值
List<Integer> intList2 = Arrays.asList(4,1,10,100,2,5,19,3,3);
Double x = intList2.stream().collect(Collectors.averagingInt(Integer::intValue));
System.out.println(x);
int sum = intList2.stream().collect(Collectors.summingInt(Integer::intValue));
System.out.println(sum);
int max = intList2.stream().collect(Collectors.maxBy(Integer::compare)).get();
System.out.println(max);
3.3.3.3 分组
在一个集合中,对具有相同特性的值进行分组是一个很常见的功能
//分组
List<Room> newListRoom = Arrays.asList( new Room(2,1),new Room(2,3), new Room(17,1), new Room(17,3), new Room(10,1),new Room(1,3));
//按高度分组
Map<Integer,List<Room>> mapRoom = newListRoom.stream().collect(Collectors.groupingBy(Room::getHeigh));
System.out.println(mapRoom);//{1=[Room [heigh=1, width=3]], 17=[Room [heigh=17, width=1], Room [heigh=17, width=3]], 2=[Room [heigh=2, width=1], Room [heigh=2, width=3]], 10=[Room [heigh=10, width=1]]}
//多级分组
//第一级我们将房间按照高度分组,第二级按照宽度分组。
List<Room> newListRoom2 = Arrays.asList( new Room(2,1),new Room(2,3), new Room(17,1), new Room(17,3), new Room(17,4), new Room(17,3), new Room(10,1),new Room(1,3));
Map<Integer,Map<Integer,List<Room>>> mapsss = newListRoom2.stream().collect(Collectors.groupingBy(Room::getHeigh,Collectors.groupingBy(Room::getWidth)));
System.out.println(JSON.toJSONString(mapsss));
/*
{
1: {
3: [{
"heigh": 1,
"width": 3
}]
},
17: {
1: [{
"heigh": 17,
"width": 1
}],
3: [{
"heigh": 17,
"width": 3
}, {
"heigh": 17,
"width": 3
}],
4: [{
"heigh": 17,
"width": 4
}]
},
2: {
1: [{
"heigh": 2,
"width": 1
}],
3: [{
"heigh": 2,
"width": 3
}]
},
10: {
1: [{
"heigh": 10,
"width": 1
}]
}
}
*/
3.3.3.4 分片
当分类函数是一个返回布尔值的函数时,流元素会被分为两组列表:一组是返回true的元素集合,另一组是返回false的元素集合。这种情况适用partitoningBy方法会比groupingBy更有效率
//分片
Map<Boolean,List<Room>> mapRoom2 = newListRoom.stream().collect(Collectors.partitioningBy(e->e.getHeigh() > 10));
System.out.println(mapRoom2);//{false=[Room [heigh=2, width=1], Room [heigh=2, width=3], Room [heigh=10, width=1], Room [heigh=1, width=3]], true=[Room [heigh=17, width=1], Room [heigh=17, width=3]]}
3.3.3.5 扩展功能
counting方法会返回收集元素的总个数
//counting
Long longx = newListRoom.stream().filter(e->e.getHeigh() > 2).collect(Collectors.counting());
System.out.println(longx);//3
Map<Integer,Long> mapRoomN = newListRoom.stream().collect(Collectors.groupingBy(Room::getHeigh,Collectors.counting()));
System.out.println(mapRoomN);//{1=1, 17=2, 2=2, 10=1}
mapping方法会将结果应用到另一个收集器上
//mapping
//按高度分组 取出房间里最大的宽度
Map<Integer, Optional<Integer>> mapt = newListRoom.stream().collect(Collectors.groupingBy(Room::getHeigh,Collectors.mapping(Room::getWidth, Collectors.maxBy(Integer::compare))));
System.out.println(mapt);//{1=Optional[3], 17=Optional[3], 2=Optional[3], 10=Optional[1]}