JavaSe学习笔记(十四)(jdk新特性(AutoCloseable接口Stream流))

JavaSe学习笔记(十四)(jdk新特性(AutoCloseable接口Stream流)),第1张

JavaSe学习笔记(十四)(jdk新特性(AutoCloseable接口 / Stream流))

JavaSe学习笔记(十四)
  • Java7新特性 - `AutoCloseable`
    • 1. 概述
    • 2. 应用场景
      • 示例
      • 如何解决
      • 改进
      • 注意
  • Java8新特性 - `Stream流`
    • 1. 概述
    • 2. Stream的API
    • 3. Stream的认识
      • (1)Stream-流的常用创建方法
        • 面试题
        • 准备工作
      • (2)中间 *** 作
        • 1. 过滤:filter
        • 2. 映射改造:peek / map
          • peek和map的区别是什么?
        • 3. 排序:sorted
        • 4. ==distanct==
        • 5. 集合分页:skip / limit
          • 匿名内部类作用
      • (3)终止 *** 作
          • findFirst& findAny
      • 总结
    • 4. 使用场景

Java7新特性 - AutoCloseable 1. 概述

JDK在1.7之后出现了自动关闭类的功能,该功能的出现为各种关闭资源提供了相当大的帮助,这里我们谈一谈自动关闭类。

JDK1.7之后出现了一个重要的接口,以及改造了一个重要的方法结构:

  • AutoCloseable自动关闭接口
  • try(){}–catch{}–finally{}

相应的 一些资源也实现了该接口,如PreparedStatement、Connection、InputStream、OutputStream等等资源接口。

2. 应用场景 示例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyLockDemo {
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        try {
            lock.lock();
            System.out.println("1-----加锁成功!!!");
            System.out.println("2-----开始执行业务逻辑");
            Thread.sleep(3000);
            System.out.println("3-----业务执行完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println("4----释放锁资源");
        }
    }

}

如上,在开发中锁是释放必须要做得事情,所以就把放在finally中来执行。但是在开发中往往很多的开发中,都会忘记释放锁或者忘记把锁的释放放入finally中,就造成死锁现象,这个很危险的 *** 作和行为。

如何解决
  • 使用AutoCloseable接口覆盖close方法

实现AutoCloseable接口,
覆盖close方法,
把原来要写finally中释放资源的动作,放入到close方法中去执行,
而这个执行是jvm自己去执行.

在有try(){}–catch{}–finally{}的时候去实现

  • 接口的实现类要重写close()方法,
  • 将要关闭的资源定义在try()中,这样当程序执行完毕之后,资源将会自动关闭。
  • 自定义类如果要进行自动关闭,只需要实现AutoCloseable接口重写close()方法即可,

同时也只有实现了AutoCloseable接口才能将自定义类放入到try()块中,否则编译不能通过

改进
  1. 定义类MyLock类
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyLock implements AutoCloseable{

    Lock lock = new ReentrantLock();

    // 加锁
    public void lock() {
        lock.lock();
    }

    // 释放锁
    public void unlock() {
        lock.unlock();
    }


    @Override
    public void close() throws Exception {
        unlock();
        System.out.println("4----释放锁资源");
    }
}

改进

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyLockDemo {
    public static void main(String[] args) {
        try (MyLock lock = new MyLock();){
            lock.lock();
            System.out.println("1-----加锁成功!!!");
            System.out.println("2-----开始执行业务逻辑");
            Thread.sleep(3000);
            System.out.println("3-----业务执行完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }//        finally {
//            // 不需要定义了.因为会自动去释放资源
//            lock.unlock();
//        }
    }
}

1-----加锁成功!!!
2-----开始执行业务逻辑
3-----业务执行完毕
4----释放锁资源  -- 这里的执行就是AutoCloseable中的close方法自动执行的
注意

若是有多个类需要上锁释放锁
在try{}中用“;”隔开
释放锁有顺序,先上锁的最后放

Java8新特性 - Stream流 1. 概述

概念:Stream 是Java8 提出的一个新概念,不是输入输出的 Stream
流,而是一种用函数式编程方式在集合类上进行复杂 *** 作的工具。简而言之,是以内部迭代的方式处理集合数据的 *** 作,内部迭代可以将更多的控制权交给集合类。Stream和 Iterator 的功能类似,只是 Iterator 是以外部迭代的形式处理集合数据的 *** 作。

总结:Stream 是Java8 的新特性,它是基于Lambda表达式在集合数组等进行一系列的优化和 *** 作的工具集。解决集合中一些像过滤,排序,分组,聚合,改造等一系列的问题。

在Java8以前,对集合的 *** 作需要写出处理的过程,如在集合中筛选出满足条件的数据,需要一 一遍历集合中的每个元素,再把每个元素逐一判断是否满足条件,最后将满足条件的元素保存返回。而Stream 对集合筛选的 *** 作提供了一种更为便捷的 *** 作,只需将实现函数接口的筛选条件作为参数传递进来,Stream会自行 *** 作并将合适的元素同样以Stream 的方式返回,最后进行接收即可。

  • 集合和数组在遍历元素的时候十分冗余,受到函数式编程以及流水线思想的启发,我们可以将常见的 *** 作封装成比较简单的方法,比如遍历的时候直接调用一个方法即可,而不用写冗余的循环程序。这就是Stream流对象的由来。
  • Stream是一个接口,有两种方式来进行创建流对象。
    一是调用 Stream.接口中的of方法。
    二是调用集合或者数组中的strain方法来获取 Stream.流对象
  • Stream对象中常用的方法有:遍历元素,筛选元素,跳过元素,截取元素,计数,把流对象拼接,对流对象中的数据元素进行转换。
2. Stream的API

流是javaAPI中的新的成员,它可以让你用声明式的方式处理集合,简单点说,可以看成遍历数据的一个高级点的迭代器,也可以看做一个工厂,数据处理的工厂,当然,流还天然的支持并行 *** 作;也就不用去写复杂的多线程的代码,下面是Stream的接口定义

在jdk1.8的java.util.stream.Stream接口,里面定义流的API的方法,jdk集合Collection.java顶级集合,利用默认方法进行了实现,具体的实现是通过:StreamSupport.java实现的。

Stream是处理集合的一套高级的API的解决方案

public interface Stream extends baseStream> {
 
	Stream filter(Predicate predicate);
 
	 Stream map(Function mapper);
 
	IntStream mapToInt(ToIntFunction mapper);
 
	LongStream mapToLong(ToLongFunction mapper);
 
	DoubleStream mapToDouble(ToDoubleFunction mapper);
 
	 Stream flatMap(Function> mapper);
 
	IntStream flatMapToInt(Function mapper);
 
	LongStream flatMapToLong(Function mapper);
 
	DoubleStream flatMapToDouble(Function mapper);
 
	Stream distinct();
 
	Stream sorted();
 
	Stream sorted(Comparator comparator);
 
	Stream peek(Consumer action);
 
	Stream limit(long maxSize);
 
	Stream skip(long n);
 
	void forEach(Consumer action);
 
	void forEachOrdered(Consumer action);
 
	Object[] toArray();
 
	 A[] toArray(IntFunction generator);
 
	T reduce(T identity, BinaryOperator accumulator);
 
	Optional reduce(BinaryOperator accumulator);
 
	 U reduce(U identity, BiFunction accumulator, BinaryOperator combiner);
 
	 R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner);
 
	 R collect(Collector collector);
 
	Optional min(Comparator comparator);
 
	Optional max(Comparator comparator);
 
	long count();
 
	boolean anyMatch(Predicate predicate);
 
	boolean allMatch(Predicate predicate);
 
	boolean noneMatch(Predicate predicate);
 
	Optional findFirst();
 
	Optional findAny();
 
	public static  Builder builder() {
		return new Streams.streamBuilderImpl<>();
	}
 
	public static  Stream empty() {
		return StreamSupport.stream(Spliterators. emptySpliterator(), false);
	}
 
	public static  Stream of(T t) {
		return StreamSupport.stream(new Streams.streamBuilderImpl<>(t), false);
	}
 
	@SafeVarargs
	@SuppressWarnings("varargs") // Creating a Stream from an array is safe
	public static  Stream of(T... values) {
		return Arrays.stream(values);
	}
 
	public static  Stream iterate(final T seed, final UnaryOperator f) {
		Objects.requireNonNull(f);
		final Iterator iterator = new Iterator() {
			@SuppressWarnings("unchecked")
			T t = (T) Streams.NONE;
 
			@Override
			public boolean hasNext() {
				return true;
			}
 
			@Override
			public T next() {
				return t = (t == Streams.NONE) ? seed : f.apply(t);
			}
		};
		return StreamSupport.stream(
				Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
	}
 
	public static  Stream generate(Supplier s) {
		Objects.requireNonNull(s);
		return StreamSupport.stream(new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s),
				false);
	}
 
	public static  Stream concat(Stream a, Stream b) {
		Objects.requireNonNull(a);
		Objects.requireNonNull(b);
 
		@SuppressWarnings("unchecked")
		Spliterator split = new Streams.ConcatSpliterator.OfRef<>((Spliterator) a.spliterator(),
				(Spliterator) b.spliterator());
		Stream Stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
		return Stream.onClose(Streams.composedClose(a, b));
	}
 
	public interface Builder extends Consumer {
		@Override
		void accept(T t);
 
		default Builder add(T t) {
			accept(t);
			return this;
		}
 
		Stream build();
 
	}
}
3. Stream的认识

通过上述接口定义,可以看到,抽象方法,有30多个方法,里面还有一些其他的接口;
上述方法可分为三类:

(1)Stream-流的常用创建方法 面试题

stream()和parallelStream()两种流化方法的区别

准备工作
   //1.创建集合
        List users = new ArrayList<>();
        users.add(new User("月色",18,0));
        users.add(new User("夜雨",16,1));
        users.add(new User("摇光",18,0));
        users.add(new User("流苏",19,1));
   //2.流化
	    Stream stream = users.stream();
	    Stream userStream = users.parallelStream();
	    List collect = Stream.of(new User("月色", 18, 0), new User("夜雨", 16, 1)).collect(Collectors.toList());

1.1 集合转流:使用Collection下的 stream() 和 parallelStream() 方法

List list = new ArrayList<>();
Stream Stream = list.stream(); //获取一个顺序流
Stream parallelStream = list.parallelStream(); //获取一个并行流

1.2 数组转流:使用Arrays 中的 stream() 方法

Integer[] nums = new Integer[10];
Stream Stream = Arrays.stream(nums);

1.3 使用Stream中的静态方法:of()、iterate()、generate()

  List collect = Stream.of(new User("月色", 18, 0), new User("夜雨", 16, 1)).collect(Collectors.toList());

Stream.of:把多个对象进行流化处理

Stream Stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
Stream2.forEach(System.out::println); // 0 2 4 6 8 10
 
Stream Stream3 = Stream.generate(Math::random).limit(2);
Stream3.forEach(System.out::println);

1.4 使用 BufferedReader.lines() 方法,将每行内容转成流

BufferedReader reader = new BufferedReader(new FileReader("F:\test_Stream.txt"));
Stream lineStream = reader.lines();
lineStream.forEach(System.out::println);

1.5 使用 Pattern.splitAsStream() 方法,将字符串分隔成流

Pattern pattern = Pattern.compile(",");
Stream stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
(2)中间 *** 作

intermediate operation 中间 *** 作:中间 *** 作的结果是刻画、描述了一个Stream,并没有产生一个新集合,这种 *** 作也叫做惰性求值方法。

对应的方法如下:

这是所有Stream中间 *** 作的列表:
过滤()==>filter()//将满足条件的过滤出来
地图==>map()//映射结果,改造数据,有返回值
转换()==>flatMap()
不同()==>distinct() 
排序()==>sorted()
窥视()==>peek()//映射结果,改造数据,无返回值
限制()==>limit()
跳跃()==>skip()
叠加()==>reduce()
1. 过滤:filter

语法:

Stream filter(Predicate predicate);

写法

userList1.stream().filter(new Predicate() {
    @Override
    public boolean test(User user) {
        System.out.println("11111");
        return false;
    }
});
userList1.stream().filter(u-> {
	return u.getAge() > 16;
});

 userList1.filter(u -> u.getAge() > 16)

是过滤,把条件满足的过滤匹配处理,如下

public class StreamDemo01 {

    public static void main(String[] args) {
        // 1:创建一个集合
        List userList1 = new ArrayList<>();
        userList1 .add(new User("月色",18,0));
        userList1 .add(new User("夜雨",16,1));
        userList1 .add(new User("摇光",18,0));
        userList1 .add(new User("流苏",19,1));
  
        // 3: 结束 *** 作
        List collect = userList1.stream()
                .filter(u -> u.getAge() > 16) // 把满足条件的筛选出来
                .collect(Collectors.toList());
        for (User user : collect) {
            System.out.println(user);
        }

    }
}

2. 映射改造:peek / map

属于:映射 ,它可以改变集合中对象的属性值

 Stream peek(Consumer action);
  Stream map(Function mapper);
  • peek *** 作案例:

把集合中年龄大于16岁的用户的年龄+1岁,如下

List collect = userList1.stream() // 1: stream流化 *** 作
    .filter(u -> u.getAge() > 16) // // 2:中间 *** 作 filter 把满足条件的筛选出来
    .peek(u->u.setAge(u.getAge() + 1))// 3:中间 *** 作 peek 把每个用户的年龄加1岁
    .collect(Collectors.toList());  // 4: 结束 *** 作


for (User user : collect) {
    System.out.println(user);
}
  • map *** 作案例:
List collect = userList1.stream() // 1: stream流化 *** 作
.filter(u -> u.getAge() > 16) // // 2:中间 *** 作 filter 把满足条件的筛选出来
.map(u -> {
    u.setAge(u.getAge() + 1);
    return u;
})// 3:中间 *** 作 peek 把每个用户的年龄加1岁
.collect(Collectors.toList());  // 4: 结束 *** 作
peek和map的区别是什么?
  • peek和map区别:peek无返回值,map有返回值(可以改变返回值)
  • peek你前面集合流化对象返回的是什么就是什么?
    map可以把stream返回值进行改变。我可以把集合中的对象改成map在返回。或者我只需要集合中某个一列的值。
public class StreamDemo02 {

    public static void main(String[] args) {
        // 1:创建一个集合
        List userList1 = new ArrayList<>();
        userList1 .add(new User("月色",18,0));
        userList1 .add(new User("夜雨",16,1));
        userList1 .add(new User("摇光",18,0));
        userList1 .add(new User("流苏",19,1));


        // user--nickname
        List nicknames = userList1.stream().map(u -> {
            return u.getUsername();
        }).collect(Collectors.toList());

        for (String nickname : nicknames) {
            System.out.println(nickname);
        }

        // user---map
        List> collect = userList1.stream().map(u -> {
            Map map = new HashMap<>();
            map.put("id", u.getId());
            map.put("username", u.getUsername());
            map.put("sex", u.getSex()); 
            return map;
        }).collect(Collectors.toList());

        for (Map stringObjectMap : collect) {
            System.out.println(stringObjectMap);
        }


        // user--->uservo
        List collect1 = userList1.stream().map(u -> {
            UserVo userVo = new UserVo();
            userVo.setId(u.getId());
            userVo.setUsername(u.getUsername());
            userVo.setSex(u.getSex() == 1 ? "男" : "女");
            return userVo;
        }).collect(Collectors.toList());

        for (UserVo userVo : collect1) {
            System.out.println(userVo);
        }


    }


}
3. 排序:sorted
public class StreamDemo03 {

    public static void main(String[] args) {
        // 1:创建一个集合
        List userList1 = new ArrayList<>();
         userList1 .add(new User("月色",18,0));
        userList1 .add(new User("夜雨",16,1));
        userList1 .add(new User("摇光",18,0));
        userList1 .add(new User("流苏",19,1));

//        List sortUserList = userList1.stream().sorted(new Comparator() {
//            @Override
//            public int compare(User o1, User o2) {
//                return o2.getAge() - o1.getAge();
//            }
//        }).collect(Collectors.toList());

//        List sortUserList = userList1.stream().sorted((User o1, User o2) ->{
//                return o2.getAge() - o1.getAge();
//        }).collect(Collectors.toList());

      
        List sortUserList = userList1.stream().sorted((o1, o2) -> o2.getAge() - o1.getAge()).collect(Collectors.toList());
        for (User user : sortUserList) {
            System.out.println(user);
        }

    }

}
4. distanct

去重这里有个坑:去重是通过equal来比较两者值是否相等来实现去重。但是问题来了:

类似Integer这种类型源码已经重写了equal方法,但是像我们自定义的一些类型,对象,它并没有重写equal方法,所以它依旧在比较地址以至于不能完成去重功能。所以我们要使用distabct的时候注意是否需要重写equal

  • 如果是对象,必须满足eqauls是true ,并且hashcode要相同
public class User {
    // 身份id
    private Integer id;
    // 姓名
    private String username;
    // 密码
    private String password;
    // 芳龄
    private Integer age;
    //  性别 0 女 1 男
    private Integer sex;
    // 身家
    private Double money;

    public User(){

    }

    public User(Integer id, String username, String password, Integer age, Integer sex, Double money) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
        this.sex = sex;
        this.money = money;
    }

    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;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", password='" + password + ''' +
                ", age=" + age +
                ", sex=" + sex +
                ", money=" + money +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) && Objects.equals(username, user.username) && Objects.equals(password, user.password) && Objects.equals(age, user.age) && Objects.equals(sex, user.sex) && Objects.equals(money, user.money);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, username, password, age, sex, money);
    }
}

public class StreamDemo04 {

    public static void main(String[] args) {
        // 1:创建一个集合
        List userList1 = new ArrayList<>();
        User user1 =  new User(1, "小文", "123456", 16, 1, 20000d);
        User user2 =  new User(1, "小文", "123456", 16, 1, 20000d);
        userList1.add(user1);
        userList1.add(user2);
        userList1.add(new User(2, "老季", "123456", 22, 1, 100000d));
        userList1.add(new User(3, "怪咖", "123456", 13, 1, 89557d));
        userList1.add(new User(4, "小六", "123456", 26, 1, 78000d));
        userList1.add(new User(5, "小刘", "123456", 46, 1, 58000d));


        System.out.println(user1.equals(user2));

        List userList = userList1.stream().distinct().collect(Collectors.toList());
        for (User user : userList) {
            System.out.println(user);
        }

//        List userList1 = new ArrayList<>();
//        userList1.add(1);
//        userList1.add(2);
//        userList1.add(3);
//        userList1.add(1);
//
//        List collect = userList1.stream().distinct().collect(Collectors.toList());
//        System.out.println(collect);
    }


}

转换()==>flatMap()
叠加()==>reduce()
5. 集合分页:skip / limit
public class StreamDemo03 {
    public static void main(String[] args) {
        // 1:创建一个集合
        List userList1 = new ArrayList<>();
        userList1 .add(new User("月色",18,0));
        userList1 .add(new User("夜雨",16,1));
        userList1 .add(new User("摇光",18,0));
        userList1 .add(new User("流苏",19,1));
        
        int pageSize =2;// 每页显示多少条
        int pageno = 1; // 当前页

        List userpage = pageUser(pageNo,pageSize);


    }
    
    public static List pageUser(int pageNo,int pageSize){
          int skip = (pageNo - 1) * pageSize;
          return  userList1.stream().sorted((o1, o2) -> o2.getAge() - o1.getAge())
                .skip(skip) // (pageno-1) * pageSize
                .limit(pageSize) // pageSize
                .collect(Collectors.toList());
		  //类似分页
          for(User user : sortUserList){
				system.out.println(user);
		 }
    
    }


}

匿名内部类作用

中间 *** 作经常用Lambda表达式简化代码,使用lambda表达式会派生一个接口类,好处是:
把匿名内部类(接口,抽象类)当做方法的参数传递以后,它可以达到一个效果就是可以把方法中的一部分逻辑抽离到匿名内部类的方法中去处理

userList1.stream().filter(new Predicate() {
    @Override
    public boolean test(User user) {
        System.out.println("11111");
        return false;
    }
});
userList1.stream().filter(u-> {
	return u.getAge() > 16;
});

 userList1.filter(u -> u.getAge() > 16)
(3)终止 *** 作

terminal operation 终止 *** 作:最终会从Stream中得到值。说白了:就是可以直接得到结果

循环()==>foreach()
count():返回流中元总个数素
收集()==>collect()---放
andMatch():接受一个`predicate`函数,只要流中有一个元素符合该断言返回true,否则返回false
noneMatch():接受一个`predicate`函数,当流中每个元素都不符合该断言返回true,否则返回false
allMatch():接受一个`predicate`函数,当流中每个元素都符合该断言返回true,否则返回false
min():返回流中元素最小值
max():返回流中元素最大值
findFirst& findAny
  • 两者在stream(串行)的情况下,两者其实都返回的第一个元素
  • 如果是parallelStream(并行)情况下findAny就造成随机返回

findAny()返回的元素是不确定的,对于同一个列表多次调用findAny()有可能会返回不同的值。使用findAny()是为了更高效的性能。
如果是数据较少,串行地情况下,一般会返回第一个结果,
如果是并行的情况,那就不能确保是第一个。

总结

steram流一旦调用终止方法就代表流已经 *** 作完毕,不能进行其他 *** 作了
若要重新进行 *** 作
将结果再次转换为流即可

  • 如何区分这2种 *** 作

可以根据 *** 作的返回值类型判断:
返回值是Stream,则该 *** 作是中间 *** 作
返回值是其他值或者为空,则该 *** 作是终止 *** 作。

如下图的前2个 *** 作是中间 *** 作,只有最后一个 *** 作是终止 *** 作。

可以形象地理解Stream的 *** 作是对一组粗糙的工艺品原型(即对应的 Stream
数据源)进行加工成颜色统一的工艺品(即最终得到的结果),第一步筛选出合适的原型(即对应Stream的 filter
的方法),第二步将这些筛选出来的原型工艺品上色(对应Stream的map方法),第三步取下这些上好色的工艺品(即对应Stream的
collect(toLis t())方法)。在取下工艺品之前进行的 *** 作都是中间 *** 作,可以有多个或者0个中间 *** 作,但每个Stream数据源只能有一次终止 *** 作,否则程序会报错

4. 使用场景

场景:查询用户信息的时候为了安全起见,需要把集合中每个用户对象的敏感信息赋值null,在进行返回。

  • 版本8之前:新建list
List userList = new ArrayList();
List newList = new ArrayList();
for(User user : userList){
	user.setPassword = null;
	newList.add(user);
}
  • 版本8之后:stream流化

  • peek解决:

 List userList = userList1.stream().peek(u -> u.setPassword("")).collect(Collectors.toList());
 userList.forEach(System.out::println);
  • map解决
List userList2 = userList1.stream().map(u -> {
    u.setPassword("");
    return u;
}).collect(Collectors.toList());
userList2.forEach(System.out::println);

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5676973.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)