先说个栗子:如何使用lambda对Object集合去重

假设有个Student1类,需要通过name给给集合去重。

@Data
class Student1 {
    private String stuNo;
    private String name;

    public Student1(String stuNo,String name){
        this.name = name;
        this.stuNo = stuNo;
    }
}

//看看最整洁版的

public class Application1 {
    public static void main(String[] args) {
        List<Student1> studentList = new ArrayList<>();
        studentList.add(new Student1("01","嘻嘻"));
        studentList.add(new Student1("02","哈哈"));
        studentList.add(new Student1("03","哒哒"));
        studentList.add(new Student1("03","哒哒"));
        studentList.stream().filter(distinctByKey(Student1::getName));
    }

    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t->seen.add(keyExtractor.apply(t));

        /*Predicate<T> tBooleanCacheLoader = (T t) -> {
            Object apply = keyExtractor.apply(t);
            return seen.add(apply);
        };
        return tBooleanCacheLoader; 这段注释和上面的方法意思是一样的*/
    }
}

虽然知道他大概意识是什么,通过map来做个去重,那么如果书写这种代码呢,别着急我们先对上面的方法拆解一下。

public static void main(String[] args) {
    List<Student1> studentList = new ArrayList<>();
    studentList.add(new Student1("01","嘻嘻"));
    studentList.add(new Student1("02","哈哈"));
    studentList.add(new Student1("03","哒哒"));
    studentList.add(new Student1("03","哒哒"));

    //这段代码原理和上面得到的结果是一样的
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    studentList.stream().filter(item->{
        return seen.add(item.getName());
    });
}

区别:ConcurrentHashMap要在Main方法里面创建。

上面涉及到的:: 和 -> 不明白怎么请看我的另一偏文章。

我们再用一种方式去改善下他们的样子,来得到相同的结果

class TestFunction implements Predicate{
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    @Override
    public boolean test(Object o) {
        return seen.add(o);
    }
}
public static void main(String[] args) {
    List<Student1> studentList = new ArrayList<>();
    studentList.add(new Student1("01","嘻嘻"));
    studentList.add(new Student1("02","哈哈"));
    studentList.add(new Student1("03","哒哒"));
    studentList.add(new Student1("03","哒哒"));

    TestFunction testFunction = new TestFunction();
    studentList.stream().filter(item -> testFunction.test(item));
}

上面这种是不是就很接近第一种方式了,区别:

ConcurrentHashMap不是在main方法里面创建,只是要多创建一个TestFunction类。

我们的方法虽然是在filter里面执行,但是是通过循环调了N遍testFunction.test()

filter(item -> testFunction.test(item))

而第一种方式定义的方法distinctByKey只执行了一次。而且distinctByKey这种代码观赏明显比前者更简洁一点。

filter(distinctByKey(Student1::getName))

那如何定义类型于distinctByKey这种函数呢。首先我们要知道Filter里面的参数是Predicate类型,所以我们定义的方法返回值是Predicate类型,其次为了去创建对象,我们直接把方法修饰符定义成静态的,
那么我们需要待定的是参数和方法内如何写。

private statis <T> Predicate<T> 方法名(参数){
    ........
}

Predicate这个函数是需要参数的,所以我们在定义方法的时候一定需要把参数传过去,只是这个参数是什么类型,前面我们提到,
如果我们用filter(item -> testFunction.test(item))这种方式,那么我们的方法就要被执行N次。如果是filter(distinctByKey(Student1::getName))这种方式,我们的方法就只需要被执行一次。如果只需要执行一次我们就需要把Student1::getName或者item -> testFunction.test(item) 作为参数传到方法里面去。那么我能就选择Function类型作为参数

这样我们的方法样子就可以确认下来了,

private <T> Predicate<T> 方法名(Function<? super T, ?> keyExtractor){
	.......
}

接下来就是写方法内的业务逻辑。
Function类型里面有一个apply方法,通过T得到R
keyExtractor.apply()所以我们通过apply就可以得到我们要执行的

那么要得到Predicate方法就很见到了,看他的方法定义boolean test(T t),那么转成Lambda就是

(T t)->{
    .....业务逻辑
	return boolean;
}

结合我么需要去重的逻辑就是,把要判断的数据放置到map中去如果,存在返回false,如果不存在返回true

(T t)->{
    Object apply = keyExtractor.apply(t);
    //得到中间类型,再把这个中间类型放到Map中,看看是否存在
    return seen.add(apply);
}

进而就变成了filter(distinctByKey(Student1::getName))或者filter(distinctByKey(item -> testFunction.test(item)))