一、介绍
1、概述
随着java的发展,越来越多的企业开始使用 java8 版本。Java8 是自 java5 之后最重要的版本,这个版本包含语言、编译器、库、工具、JVM等方面的十多个新特性。
Jdk8新增的特性如下:
- Lambda 表达式
- 新的日期API Datetime
- 引入Optional 防止空指针异常
- 使用Base64
- 接口的默认方法和静态方法
- 新增方法引用格式
- 新增Stream类
- 注解相关的改变
- 支持并行(parallel)数组
- 对并发类(Concurrency)的扩展。
- JavaFX
二、接口新特性
1、接口默认方法
当我们去实现某个框架提供的一个接口时,需要实现其所有的抽象方法,当该框架更新版本,在这个接口中加入了新的抽象方法时,我们就需要对项目重新编译,并且实现其新增的方法。当实现类太多时,操作起来很麻烦。
JDK之前是使用开闭设计模式:对扩展开放,对修改关闭。即:创建一个新的接口,继承原有的接口,定义新的方法。但是这样的话,原本的那些实现类并没有新的方法。这时候可以使用接口默认方法。
使用接口默认方法:
使用default关键字进行修饰,方法需要方法体,这样的方法所有的子类会默认实现(不用自己写),如果想要覆盖重写,也可以在实现类中覆盖重写。
A、新建一个User类
package com.itan.pojo;
public class User {
private Integer id;
private String name;
private String sex;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
B、新建一个接口
package com.itan.service;
import com.itan.pojo.User;
public interface UserService {
/**
* 保存
* @param user
*/
void save(User user);
/**
* 根据id查询
* @param id
* @return
*/
User findById(Integer id);
}
C、新建一个Data类(模拟数据库)
package com.itan.data;
import com.itan.pojo.User;
import java.util.ArrayList;
import java.util.List;
public class UserData {
//模拟数据库
public static List<User> list = new ArrayList<User>(16);
}
D、新建一个实现类
package com.itan.service.impl;
import com.itan.data.UserData;
import com.itan.pojo.User;
import com.itan.service.UserService;
import java.util.List;
public class UserServiceImpl implements UserService {
@Override
public void save(User user) {
}
@Override
public User findById(Integer id) {
List<User> list = UserData.list;
for (User user : list) {
if (user.getId().equals(id)){
return user;
}
}
return null;
}
}
E、再建一个实现类
package com.itan.service.impl;
import com.itan.data.UserData;
import com.itan.pojo.User;
import com.itan.service.UserService;
import java.util.List;
public class UserServiceImpl2 implements UserService {
@Override
public void save(User user) {
UserData.list.add(user);
}
@Override
public User findById(Integer id) {
return null;
}
}
F、如果在接口中再加一个方法,发现实现类就会报错,必须要实现这个新加的抽象方法,这是就需要使用接口默认方法
/**
* 获取user,从 java8 开始,接口中允许定义 default 默认方法
* @return
*/
default User getUser(){
return new User();
}
G、测试
package com.itan.test;
import com.itan.pojo.User;
import com.itan.service.UserService;
import com.itan.service.impl.UserServiceImpl;
import org.junit.Test;
public class InterfaceTest {
@Test
public void testInterfaceDefault(){
UserService userService=new UserServiceImpl();
User user=userService.getUser();
System.out.println(user);
// 输出:User{id=null, name='null', sex='null'}
}
}
H、注意
这里的 default
是 jdk8
新增的关键字,和访问限定修饰符 default
不是同一个概念,与switch中的 default
功能完成不同。
与抽象类的不同:抽象类更多的是提供一个模板,子类之间的某个流程大致相同,仅仅是某个步骤可能不一样(模板方法设计模式),这个时候使用抽象类,该步骤定义为抽象方法。而default关键字是用于扩展。
2、接口静态方法
/**
* 从java8开始,接口中允许定义静态方法,与一般类的静态方法用法相同
* @return
*/
static User getUser1(){
return new User();
}
注意: 接口中的静态方法不会被实现类所继承
三、函数式接口
1、概念
有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
2、格式
确保接口中有且只有一个抽象方法即可。
Public interface 接口名称 {
返回值 方法名称();
}
3、@FunctionalInterface注解
有的注解是在编译期起作用,如@Override注解。而@FunctionalInterface也是在编译期起作用。该注解是java8专门为函数式接口引入的新的注解,作用于一个接口上。
一旦使用该注解来定义接口,编译期会强制检查该接口是否符合函数式接口的条件,不符合则会报错。需要注意的是:即使不使用该注解,只要满足函数式接口的定义,这就是一个函数式接口。
package com.itan.functional;
@FunctionalInterface //标识这是一个函数式接口
public interface MyFunctionalInterface {
/**
* 仅有的抽象方法
*/
void method();
/**
* 默认方法
*/
default void defaultMethod(){
System.out.println("接口默认方法");
};
}
四、Lambda表达式
1、概述
Java8 的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。
2、语法和示例
三要素: 参数、箭头、代码
(参数类型 参数1,参数类型 参数2...)->{代码}
说明:
- 如果参数有多个,那么使用逗号分隔。如果参数没有,则留空。
- 箭头是固定写法。
- 大括号相当于方法体。
- 使用前提:必须是函数式接口。
示例:
普通方式开启线程
package com.itan.lambda;
public class LambdaTest1 {
public static void main(String[] args) {
/**
* 普通开启线程(Runnable)
* 1.new Runnable()
* 2.实现run方法
* 3.new Thread(runnable).start()
*/
Runnable runnable1=new Runnable() {
@Override
public void run() {
for (int i=0;i<=10;i++){
System.out.println("线程1开启了,"+i);
}
}
};
Runnable runnable2=new Runnable() {
@Override
public void run() {
for (int i=0;i<=10;i++){
System.out.println("线程2开启了,"+i);
}
}
};
Runnable runnable3=new Runnable() {
@Override
public void run() {
for (int i=0;i<=10;i++){
System.out.println("线程3开启了,"+i);
}
}
};
new Thread(runnable1).start();
new Thread(runnable2).start();
new Thread(runnable3).start();
}
}
Lambda表达式开启线程
package com.itan.lambda;
public class LambdaTest2 {
public static void main(String[] args) {
/**
* Lambda的思想,只关注做什么,而不关注怎么实现
*/
Runnable runnable = ()->{
for (int i = 0; i < 10; i++) {
System.out.println("线程1开启了,"+i);
}
};
new Thread(runnable).start();
/**
* 简化
*/
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("线程2开启了," + i);
}
}).start();
}
}
3、Lambda省略规则
- 参数类型可以省略。但是只能同时省略所有参数的类型,或者干脆都不省略。
- 如果参数有且仅有一个,那么小括号可以省略。
- 如果大括号内的语句有且仅有一条,那么无论是否有返回值,return、大括号、分号都可以省略。
4、Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
性能浪费的案例:
package com.itan.lambda;
import org.junit.Test;
public class LambdaTest3 {
private void log(int level,String msg){
if(level==1){
System.out.println(msg);
}
}
@Test
public void testStringConcat(){
String str1="hello";
String str2="world";
String str3="Java";
log(1,str1 + str2 + str3);
}
}
存在问题: 无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
Lambda优化写法:
// 接口
package com.itan.functional;
public interface MessageBuilder {
String buildMessage();
}
// 测试
package com.itan.lambda;
import com.itan.functional.MessageBuilder;
import org.junit.Test;
public class LambdaTest3 {
private void log(int level, MessageBuilder messageBuilder){
if(level==1){
System.out.println(messageBuilder.buildMessage());
}
}
@Test
public void testStringConcat(){
String str1="hello";
String str2="world";
String str3="Java";
log(1,()->str1 + str2 + str3);
}
}
只有当满足条件的时候才会进行三个字符串的拼接。否则不会拼接。在不符合要求的情况下,lambda将不会执行。
五、常用函数式接口
1、Supplier接口
java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
package com.itan.lambda;
import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Supplier;
public class SupplierTest {
private static User getUser(Supplier<User> supplier){
return supplier.get();
}
@Test
public void getUser(){
User user=getUser(()->new User());
System.out.println(user);
}
}
示例:求数组的最小值
package com.itan.lambda;
import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Supplier;
public class SupplierTest {
@Test
public void testGetMin(){
int [] arr={1,2,55,88,44,0};
int minNum=getMain(()->{
//假设第一个是最小值
int min=arr[0];
for (int item:arr){
if (item<min){
min=item;
}
}
return min;
});
System.out.println(minNum);
}
private int getMain(Supplier<Integer> supplier){
return supplier.get();
}
}
2、Consumer接口
java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,
其数据类型由泛型决定。
抽象方法:accept,意为消费一个指定泛型的数据。
package com.itan.lambda;
import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Consumer;
public class ConsumerTest {
@Test
public void test(){
User user=new User();
setUser(u-> u.setSex("男"),user);
System.out.println(user);
}
private void setUser(Consumer<User> consumer,User user){
consumer.accept(user);
}
}
默认方法:endThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。
package com.itan.lambda;
import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Consumer;
public class ConsumerTest {
@Test
public void test(){
User user=new User();
setUserDefaultNameAndSex(u-> {
u.setName("张三");
System.out.println(u);
},u->{
u.setSex("男");
System.out.println(u);
},user);
System.out.println(user);
}
private void setUserDefaultNameAndSex(Consumer<User> one,Consumer<User> two,User user){
one.andThen(two).accept(user);
}
}
3、Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T> 接口。
抽象方法:test
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断,条件判断的标准是传入lambda表达式逻辑。
package com.itan.lambda;
import org.junit.Test;
import java.util.function.Predicate;
public class PredicateTest {
@Test
public void test(){
PredicateMethod(s->s.length()>5,"HelloWorld");
}
private void PredicateMethod(Predicate<String> predicate,String str){
boolean flag=predicate.test(str);
System.out.println("判断结果"+flag);
}
}
默认方法:add
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and。
如果要判断一个字符串既要包含大写“H”,又要包含大写“W”
package com.itan.lambda;
import org.junit.Test;
import java.util.function.Predicate;
public class PredicateTest {
@Test
public void test(){
PredicateDefaultAndMethod(s->s.contains("H"),s->s.contains("W"));
}
private void PredicateDefaultAndMethod(Predicate<String> one,Predicate<String> two){
boolean flag=one.and(two).test("HelloWorld");
System.out.println("判断结果"+flag);
}
}
**默认方法:or **
如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变
package com.itan.lambda;
import org.junit.Test;
import java.util.function.Predicate;
public class PredicateTest {
@Test
public void test(){
PredicateDefaultOrMethod(s->s.contains("H"),s->s.contains("P"));
}
private void PredicateDefaultOrMethod(Predicate<String> one,Predicate<String> two){
boolean flag=one.or(two).test("HelloWorld");
System.out.println("判断结果"+flag);
}
}
默认方法:negate
表示取相反的
package com.itan.lambda;
import org.junit.Test;
import java.util.function.Predicate;
public class PredicateTest {
@Test
public void test(){
PredicateDefaultNegateMethod(s->s.length()<5,"HelloWorld");
}
private void PredicateDefaultNegateMethod(Predicate<String> one,String str){
boolean flag=one.negate().test(str);
System.out.println("判断结果"+flag);
}
}
4、Function接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法:apply
Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。
使用的场景例如:将 String 类型转换为 Integer 类型。
package com.itan.lambda;
import org.junit.Test;
import java.util.function.Function;
public class FunctionTest {
@Test
public void test(){
Integer i = parseInteger(s -> Integer.parseInt(s), "10");
System.out.println(i);
}
private Integer parseInteger(Function<String,Integer> function,String str){
return function.apply(str);
}
}
默认方法:andThen
package com.itan.lambda;
import org.junit.Test;
import java.util.function.Function;
public class FunctionTest {
@Test
public void test(){
Integer i = defaultAndThenMethod(s -> Integer.parseInt(s)+10, s1->s1*10);
System.out.println(i);
}
private Integer defaultAndThenMethod(Function<String,Integer> one,Function<Integer,Integer> two){
return one.andThen(two).apply("10");
}
}
六、方法引用
1、方法引用符
双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
package com.itan.lambda;
import org.junit.Test;
import java.util.function.Consumer;
public class FunctionRefTest {
@Test
public void test(){
print(System.out::println);
}
private void print(Consumer<String> consumer){
consumer.accept("HelloWorld");
}
}
分析: System.out 对象中有一个重载的 println(String)方法恰好就是我们所需要的。
那么对printString 方法的函数式接口参数,对比下面两种写法,完全等效:
- Lambda表达式写法: s -> System.out.println(s);
- 方法引用写法: System.out::println
第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println 方法去处理。
第二种等效写法的语义是指:直接让System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
注意: Lambda 中传递的参数 一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
2、通过对象名引用成员方法
package com.itan.lambda;
import com.itan.pojo.User;
import org.junit.Test;
import java.util.function.Consumer;
import java.util.function.Function;
public class FunctionRefTest {
@Test
public void test(){
String userName=getUserName(User::getName);
System.out.println(userName);
}
private String getUserName(Function<User,String> function){
User user=new User();
user.setName("张三");
return function.apply(user);
}
}
3、通过类名称引用静态方法
由于在 java.lang.Math 类中已经存在了静态方法abs ,所以当我们需要通过Lambda来调用该方法时,有两种写法。
方式一、函数式接口
@FunctionalInterface
public interface Calcable {
int calc(int num);
}
public class Demo05Lambda {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(-10, n -> Math.abs(n));
}
}
方式二、使用方法引用
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(-10, Math::abs);
}
}
4、通过super引用成员方法
如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。
首先是函数式接口:
@FunctionalInterface
public interface Greetable {
void greet();
}
父类Human的内容
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
子类Man的内容
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g) {
g.greet();
}
public void show() {
//调用method方法,使用Lambda表达式
method(() -> {
//创建Human对象,调用sayHello方法
new Human().sayHello();
});
//简化Lambda
method(() -> new Human().sayHello());
//使用super关键字代替父类对象
method(() -> super.sayHello());
}
}
但是如果使用方法引用会更好
public class Woman extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
public void method(Greetable g) {
g.greet();
}
public void show() {
method(super::sayHello);
}
}
5、通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。
首先是函数式接口:
@FunctionalInterface
public interface Richable {
void buy();
}
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(this::buyHouse);
}
}
七、Stream流
1、概述
说到Stream便容易想到I/O Stream,而实际上,在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
弊端: Stream流式操作性能比传统的For循环要低,就性能而言,传统的for循环最高。
2、获取Stream流
java.util.stream.Stream<T> 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
- 所有的 Collection 集合都可以通过 stream 默认方法获取流。
- Stream 接口的静态方法 of 可以获取数组对应的流。
根据Collection获取流
首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
package com.itan.lambda;
import org.junit.Test;
import java.util.*;
import java.util.stream.Stream;
public class TestList {
@Test
public void testGetStream(){
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
}
}
根据Map获取流
java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流
需要分key、value或entry等情况:
package com.itan.lambda;
import org.junit.Test;
import java.util.*;
import java.util.stream.Stream;
public class TestList {
@Test
public void testGetStream(){
Map<String,Object> map = new HashMap<>();
Stream<String> stream1 = map.keySet().stream();
Stream<Object> stream2 = map.values().stream();
}
}
根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of
package com.itan.lambda;
import org.junit.Test;
import java.util.*;
import java.util.stream.Stream;
public class TestList {
@Test
public void testGetStream(){
String[] array = {"张无忌", "张翠山", "张三丰", "张一元"};
Stream<String> stream = Stream.of(array);
}
}
3、逐一处理:forEach
package com.itan.lambda;
import org.junit.Test;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array = {"张无忌", "张三丰", "周芷若"};
Stream<String> stream = Stream.of(array);
stream.forEach(s -> System.out.println(s));
}
}
4、过滤:filter
可以通过 filter 方法将一个流转换成另一个子集流。
package com.itan.lambda;
import org.junit.Test;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
Stream<String> stream = Stream.of(array);
//Lambda表达式来指定筛选条件
stream.filter(s -> s.length() == 3).forEach(System.out::println);
}
}
5、映射:map
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。
package com.itan.lambda;
import org.junit.Test;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
Stream<String> stream = Stream.of(array);
Stream<String> stream1 = stream.map(e -> e.substring(2));
stream1.forEach(System.out::println);
}
}
6、统计个数:count
package com.itan.lambda;
import org.junit.Test;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
Stream<String> stream = Stream.of(array);
long count = stream.count();
System.out.println(count);
}
}
7、取用前几个:limit
package com.itan.lambda;
import org.junit.Test;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
Stream<String> stream = Stream.of(array);
stream.limit(2).forEach(System.out::println);
}
}
8、跳过前几个:skip
package com.itan.lambda;
import org.junit.Test;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
Stream<String> stream = Stream.of(array);
stream.skip(2).forEach(System.out::println);
}
}
9、组合:concat
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
package com.itan.lambda;
import org.junit.Test;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array1 = {"张无忌", "张三丰"};
String[] array2 = {"周芷若","赵敏"};
Stream<String> stream1 = Stream.of(array1);
Stream<String> stream2 = Stream.of(array2);
Stream.concat(stream1,stream2).forEach(System.out::println);
}
}
10、将流转化为集合:collect
package com.itan.lambda;
import org.junit.Test;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestList {
@Test
public void test(){
String[] array = {"张无忌", "张三丰", "周芷若","赵敏"};
Stream<String> stream = Stream.of(array);
List<String> list = stream.limit(2).collect(Collectors.toList());
for (String s : list) {
System.out.println(s);
}
}
}
11、集合排序方法:sorted
1、测试数据
List<User> list = new ArrayList();
list.add(new User(1,"张三","男","1998-07-24",135));
list.add(new User(2,"李四","女","1999-10-02",186));
list.add(new User(3,"王五","男","1997-08-23",156));
2、升序排列
//按生日升序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBirthDay))
.collect(Collectors.toList());
3、降序排列
//按生日降序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBirthDay).reversed())
.collect(Collectors.toList());
4、多字段排序
//按分数升序,当分数相同时,按生日升序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBranch).thenComparing(User::getBirthDay))
.collect(Collectors.toList());
//按分数升序,当分数相同时,按生日降序
List<User> l = list.stream().sorted(Comparator.comparing(User::getBranch).thenComparing(User::getBirthDay,Comparator.reverseOrder()))
.collect(Collectors.toList());
八、Stream操作collectingAndThen根据对象属性进行去重操作
1、实体对象
@Data
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String sex;
}
2、在user集合中根据name、sex属性去重,生成一个新的集合
public class WareInfoController {
public static void main(String[] args) {
List<User> list = new ArrayList<>();
User user1 = new User(1,"张三","男");
User user2 = new User(2,"张三","男");
User user3 = new User(3,"李四","男");
User user4 = new User(4,"李四","男");
User user5 = new User(5,"王五","女");
User user6 = new User(6,"王五","女");
list.add(user1);
list.add(user2);
list.add(user3);
list.add(user4);
list.add(user5);
list.add(user6);
List<User> users = list.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(
() -> new TreeSet<>(Comparator.comparing(User::getName).thenComparing(User::getSex))
), ArrayList::new)
);
}
}
1、输出的结果为:[{1,“张三”,“男”},{3,“李四”,“男”},{5,“王五”,“女”}]
2、运行原理:首先利用java8新特性中的
stream
流对集合进行操作,collectingAndThen
用于对流中的数据进行处理,可以对流中的数据进行聚合操作,接着需要使用toCollection
收集器并提供特定集合实现,然后将结果集放到TreeSet
中,由于TreeSet
的特性是不重复,于是就可以达到去重的效果,Comparator.comparing
括号中是筛选的条件,thenComparing
方法是拼接第二个筛选条件,最后将流中的元素累积到汇总到collect
收集器中,再以ArrayList
的形式返回。
3、也可以使用以下代码操作
List<User> users = list.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(
() -> new TreeSet<>(Comparator.comparing(o -> o.getName() + ";" + o.getSex()))
), ArrayList::new)
);