Stream流
Stream流是JDK8中提供的一种新特性
Stream流的使用步骤:
- 先得到Stream流,把数据放到流中
- 使用中间方法对流水线上的数据进行操作
- 中间方法:过滤、转换(方法调用完毕后还可以调用其他方法)
- 使用终结方法对流水线上的数据进行操作
- 终结方法:统计、打印(最后一步操作,方法调用完毕之后不能再去调用其他方法)
如何获取流水线
获取方式 | 方法名 | 说明 |
---|---|---|
单例集合 | default Stream<E> stream() | Collection中的默认方法 |
双列集合 | 无 | 无法直接使用stream流 |
数组 | public static <T> Stream<T> stream(T[] array) | Arrays工具类中的静态方法 |
零散数据 | public static<T> Stream<T>of(T… values) | Stream接口中的静态方法,需要同种数据类型 |
单列集合获取Stream流
//集合的批量添加
List<String> list = new ArrayList<String>(){
{
add("张无忌");
add("周芷若");
add("赵敏");
add("张三丰");
add("张翠山");
add("王二麻子");
add("谢广坤");
add("马超");
add("张良");
}
};
// 获取stream流并打印
list.stream().forEach(s -> System.out.println(s));
双列集合获取Stream流
Map<Integer,String> map = new HashMap<Integer,String>(){
{
put(1,"张无忌");
put(2,"周芷若");
put(3,"赵敏");
}
};
// 直接获取map中的key
map.keySet().stream().forEach(k -> System.out.println(k));
// 获取map中的value
map.values().stream().forEach(v -> System.out.println(v));
// 获取键值对
map.entrySet().stream().forEach(e-> System.out.println(e));
数组获取Stream流
String[] str = {"张无忌","周芷若","赵敏","张三丰","张翠山","王二麻子","谢广坤","马超","张良"};
// 数组转集合
List<String> collect = Arrays.stream(str).collect(Collectors.toList());
System.out.println(collect);
零散数据获取Stream流
前提零散数据类型是一样的
Stream.of("张无忌","周芷若","赵敏","张三丰","张翠山","王二麻子","谢广坤","马超","张良").forEach(s -> System.out.println(s));
Stream流中间方法
名称 | 说明 |
---|---|
Stream<T> filter(Predicate<? super T> predicate) | 过滤 |
Stream<T> limit(long maxSize) | 获取前几个元素 |
Stream<T> skip(long n) | 跳过前几个元素 |
Stream<T> distinct() | 元素去重,依赖(hashCode和equals方法) |
static <T> Stream<T> concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream<R> map(Function<T, R> mapper) | 转换流中的数据类型 |
- 注意1:中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
- 注意2:修改Stream流中的数据,不会影响原来集合或者数组中的数据
filter方法
该方法返回true表示保留该数据,false表示过滤掉该数据
public static void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌");
add("周芷若");
add("赵敏");
add("张三丰");
add("张翠山");
add("王二麻子");
add("谢广坤");
add("马超");
add("张良");
}
};
// s表示集合里的每一条数据
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return false;
}
});
// 打印以张开头的前两条数据
list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s));
}
concat方法
concat方法是Stream的静态方法,作用是将两个Stream流合并成一个,注意如果两个流的数据类型不一致就会将类型提示为他们共同的父类。
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌");
add("周芷若");
add("赵敏");
add("张三丰");
add("张翠山");
add("王二麻子");
add("谢广坤");
add("马超");
add("张良");
}
};
Stream.concat(list.stream(),list.stream()).forEach(s -> System.out.println(s));
map方法
打印每个人的年龄
public static void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌-22");
add("周芷若-20");
add("赵敏-20");
add("张三丰-70");
add("张翠山-50");
add("王二麻子-20");
add("谢广坤-44");
add("马超-35");
add("张良-30");
}
};
// 匿名内部类写法
list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
String[] split = s.split("-");
int ret = Integer.parseInt(split[1]);
return ret;
}
}).forEach(s->System.out.println(s));
// lambda写法,在forEach中s已经编程整形
list.stream().map(s->Integer.parseInt(s.split("-")[1])).forEach(s->System.out.println(s));
}
}
Stream终结方法
名称 | 说明 |
---|---|
void forEach(Consumer action) | 遍历 |
long count() | 统计 |
toArray() | 收集流中的数据放到数组中 |
collect(Collector collertor) | 收集流中的数据,放到集合中 |
toArray()方法
public static void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌");
add("周芷若");
add("赵敏");
add("张三丰");
add("张翠山");
add("王二麻子");
add("谢广坤");
add("马超");
add("张良");
}
};
// 匿名内部类写法
String[] array = list.stream().toArray(new IntFunction<String[]>() {
/**
*
* @param value 数组的容量
* @return
*/
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(array));
// lambda写法
String[] array1 = list.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(array1));
}
collect方法
收集为List或者Set是比价简单的
public static void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌-男-22");
add("周芷若-女-20");
add("赵敏-女-20");
add("张三丰-男-66");
add("张翠山-男-55");
add("谢广坤-男-35");
add("马超-男-30");
add("张良-男-29");
}
};
// 将所有男性收集到一个List中
List<String> ret = list.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toList());
// 将素所有女性收集到一个Set中
Set<String> set = list.stream().filter(s -> "女".equals(s.split("-")[1])).collect(Collectors.toSet());
System.out.println(ret);
System.out.println(set);
}
来看一下收集到Map里,将姓名和年龄形成一个键值对。
注意:使用toMap收集流中的数据时,key是不能重复的,否则会抛出异常
public static void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌-男-22");
add("周芷若-女-20");
add("赵敏-女-20");
add("张三丰-男-66");
add("张翠山-男-55");
add("谢广坤-男-35");
add("马超-男-30");
add("张良-男-29");
}
};
// 匿名内部类写法
/**
* toMap的第一个函数接口参数:
* 泛型参数:key: 表示流的数据类型
* 泛型参数 value: 表示要转换Map的key的数据类型
* toMap的第二个函数接口参数:
* 泛型参数:key: 表示流的数据类型
* 泛型参数 value: 表示要转换Map的value的数据类型
*/
Map<String,Integer> map1 = list.stream().collect(Collectors.toMap(
new Function<String, String>() {
/**
* 该方法的参数就是流中的元素
* 该方法的返回值就是Map的key
* @param s the function argument
* @return
*/
@Override
public String apply(String s) {
return s.split("-")[0];
}
},
new Function<String, Integer>() {
/**
* 该方法的参数就是流中的元素
* 该方法的返回值就是Map的value
* @param s the function argument
* @return
*/
@Override
public Integer apply(String s) {
return Integer.parseInt(s.split("-")[2]);
}
}));
// lambda写法
Map<String ,Integer> map2 = list.stream().collect(Collectors.toMap(s -> s.split("-")[0],s->Integer.parseInt(s.split("-")[2])));
System.out.println(map1);
System.out.println(map2);
}
方法引用
方法引用就是把已经有的方法拿过来用,当做函数式接口中抽象方法的方法体
使用方法引用需要注意以下4点:
- 引用处必须是函数式接口
- 被引用的方法必须已经存在
- 被引用方法的形参和返回值需要跟抽象方法保持一致
- 被引用方法的功能要满足当前需求
举个例子,我们要对一个数组进行降序排序。
public static void main(String[] args) {
Integer[] arr = {1,6,8,3,4,6,7,5,2,9};
// 使用匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
// 使用lambda表达式
Arrays.sort(arr, (o1, o2) -> o2-o1);
System.out.println(Arrays.toString(arr));
}
我们可以通过方法引用的方式来进行
Arrays.sort的第二个参数比较规则,是函数式接口满足要求,其余3点也满足就可以使用方法引用。
public static void main(String[] args) {
Integer[] arr = {1,6,8,3,4,6,7,5,2,9};
// 使用方法引用
Arrays.sort(arr, Main::cmp);
System.out.println(Arrays.toString(arr));
}
public static int cmp(int a,int b){
return b-a;
}
::
是一个方法引用符号
引用静态方法
- 格式:
类名::静态方法
- 示例:
Integer::parseInt
比如将一组字符串数组转换成整形数字,直接将Integer的静态方法拿过来用即可
public static void main(String[] args) {
List<String> list = new ArrayList<String>(){
{
add("1");
add("2");
add("3");
add("4");
add("5");
}
};
// 匿名内部类写法
List<Integer> ret1 = list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
}).collect(Collectors.toList());
System.out.println(ret1);
// 使用方法引用写法
List<Integer> ret2 = list.stream().map(Integer::parseInt).collect(Collectors.toList());
System.out.println( ret2);
}
引用成员方法
- 格式:
对象::成员方法
- 其他类:
其他对象::方法名
- 本类:
this::方法名
(引用处不能是静态方法) - 父类:
super::方法名
(引用处不能是静态方法)
需求:打印性张且名字长度为3个字人
调用其他类的成员方法
class Test{
public boolean filter(String s) {
return s.startsWith("张") && s.length() == 3;
}
}
public class Main {
public static void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌");
add("周芷若");
add("赵敏");
add("张三丰");
add("张翠山");
add("谢广坤");
add("马超");
add("张良");
}
};
// 匿名内部类写法
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张") && s.length() == 3;
}
}).forEach(s -> System.out.println(s));
// 方法引用,引用其他类方法
Test test = new Test();
list.stream().filter(test::filter).forEach(s -> System.out.println(s));
}
}
引用本类的成员方法
需求:打印性张且名字长度为3个字人
public class Main {
public boolean filter(String s) {
return s.startsWith("张") && s.length() == 3;
}
public void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌");
add("周芷若");
add("赵敏");
add("张三丰");
add("张翠山");
add("谢广坤");
add("马超");
add("张良");
}
};
// 方法引用,引用本类类方法
list.stream().filter(new Main()::filter).forEach(s -> System.out.println(s));
}
}
引用父类的成员方法
class Test {
public boolean filter(String s) {
return s.startsWith("张") && s.length() == 3;
}
}
public class Main extends Test {
public boolean filter(String s) {
return s.startsWith("张") && s.length() == 3;
}
public void func() {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌");
add("周芷若");
add("赵敏");
add("张三丰");
add("张翠山");
add("谢广坤");
add("马超");
add("张良");
}
};
// 方法引用,引用父类方法
list.stream().filter(super::filter).forEach(System.out::println);
}
}
引用构造方法
- 格式:
类名::new
需求:将字符串张三-22
构成User对象
class User {
String username;
int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
public User(String str) {
String[] split = str.split("-");
this.username = split[0];
this.age = Integer.parseInt(split[1]);
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
public class Main {
public boolean filter(String s) {
return s.startsWith("张") && s.length() == 3;
}
public static void main(String[] args) {
//集合的批量添加
List<String> list = new ArrayList<String>() {
{
add("张无忌-22");
add("周芷若-20");
add("赵敏-20");
add("张三丰-60");
add("张翠山-35");
}
};
// 匿名内部类写法
List<User> userList1 = list.stream().map(new Function<String, User>() {
@Override
public User apply(String str) {
String[] split = str.split("-");
return new User(split[0], Integer.parseInt(split[1]));
}
}).collect(Collectors.toList());
System.out.println(userList1);
// 使用构造方法引用写法,拿User类的构造方法作为参数
List<User> userList2 = list.stream().map(User::new).collect(Collectors.toList());
System.out.println(userList2);
}
}
类名引用成员方法
- 格式:
类名::成员方法
- 示例:
String::toUpperCase
这种通过类名引用成员方法的方式有点特别,需要遵循以下规则,
方法引用的规则:
- 需要有函数式接口
- 被引用的方法必须已经存在
- 被引用方法的形参,需要跟抽象方法的第一个参数到最后一个形参保持一致,返回值需要保持一致
- 被引用方法的功能需要满足当前的需求
抽象方法形参注意事项:
- 第一个参数:表示被引用方法的调用者,决定了可以引用哪些类的方法,在Stream流当中,第一个参数一般都表示流里面的每一个数据,假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法
- 第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法
需求:将一堆字符串都转换成大写
- 首先Function是一个函数式接口,它只有一个抽象方法apply
- apply方法的第一个形参是String类型,那么就觉得了只能引用String中的方法
- apply方法没有第二个形参说明引用的方法需要是无参的
- 那么引用String中的toUpperCase是符合要求的
public static void main(String[] args) {
List<String> stringList = Arrays.asList("aa","bb","cc","dd");
// 匿名内部类写法
List<String> ret1 = stringList.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).collect(Collectors.toList());
System.out.println(ret1);
// 方法引用写法
List<String> ret2 = stringList.stream().map(String::toUpperCase).collect(Collectors.toList());
System.out.println(ret2);
}
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
局限性:
- 不能引用所有类中的成员方法。
- 是跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法
引用数组构造方法
- 格式:
数据类型[]:new
- 示例:
int[]::new
需求将List中的数据放到一个新数组中
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>(){
{
add(1);
add(2);
add(3);
add(4);
add(5);
}
};
// 匿名内部类写法
Integer[] array = list.stream().toArray(new IntFunction<Integer[]>() {
@Override
public Integer[] apply(int value) {
return new Integer[value];
}
});
System.out.println(Arrays.toString(array));
// 方法引用写法
Integer[] array2 = list.stream().toArray(Integer[]::new);
System.out.println(Arrays.toString(array2));
}