深入分析几个难以理解的Comparator源码

1.分析comparing单参数方法

        网上很多帖子说实话,不咋地,讲的不细节,抄来抄去,就让我这个大二的垃圾,给大家梳理一下Comparator这几个难以理解public static方法吧。

1.1函数式接口Function

        这个函数是使用的函数式编程的典范,这里如果不理解匿名内部类和Lambda会很难受,我从字节码的细节给你们讲一下。

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

1.1.1Function - 相识

        今天你和Function函数式接口相识啦。

        可以看到以下就是函数接口Function,接收两个泛型参数,定义了一个抽象方法,接收一个T类型的参数,返回一个R类型的返回值。

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

1.1.2Function - 追求

        有时候喜欢就是这样,说不上来的一种感觉,现在你想追求她,靠近她,你就得了解前置知识和相关使用细节。

        现在你要学会怎么用!!!

        首先你得知道她的背景吧,你啥也不知道那你追毛线?那她的背景有什么呢?她最爱的就是函数式接口,匿名内部类,Lambda函数和方法引用啦。

1.1.2.1函数式接口

        函数式接口是这Function的灵魂,因为它本身就是一个函数式接口,函数式接口就是只有一个方法的接口,一般使用@FunctionalInterface标记注解(Mark Interce)进行标记,提高可读性。

        这样就可以很简单地进行定义一个函数式接口啦!

@FunctionalInterface
public interface AFunctionalInterface {
    
    void danDan();
    
}
1.1.2.2匿名内部类

        匿名内部类式啥我就不细细说了,这玩意扯远了都能单独当作一期说了,就这么简单来说,匿名内部类没有名称,就是使用new 类/接口{}进行创建一个对象,如果你只是想创建这个类的对象,一次性进行使用,不想复用这个类,就直接使用匿名内部类就可以了。

废话少说,上代码!!!

  1. 匿名内部类的简单使用

        简单说一下:如果你只是想创建一个你进行拓展了成员方法的类的对象进行使用,那么使用匿名内部类是一个好选择。

        new ArrayList() {}这个语法就是进行创建一个继承了ArrayList类的对象。

        在{}中只能进行拓展成员字段,成员代码块等,不能定义static字段,不能定义static代码块,不能进行定义构造方法(都没名字,如何构造?)

public class Test01 {

    public static void main(String[] args) {
        ArrayList<Integer> array = new ArrayList<Integer>() {

            // 不能使用匿名内部类扩展这个类的静态字段
            // public static int b = 1;
            // static {
            //     System.out.println(10086);
            // }

            // 可以使用匿名内部类去扩展这个类成员字段
            public int a = 10086;

            {
                this.add(1);
                add(a);
                // 可以调用父类中的字段
                super.add(100);
            }
        };
        System.out.println(array.size());
    }

}

        可以看到里面的初始化代码都实现了。

        直接去target的class文件,可以看到里面确实是生成了一个类文件,名称是外部类名称$序号。

        可以从这个class文件中看到确实是生成了ArrayList类的派生类。

import java.util.ArrayList;

final class Test01$1 extends ArrayList<Integer> {
    public int a = 10086;

    Test01$1() {
        this.add(1);
        this.add(this.a);
        super.add(100);
    }
}
  1. 匿名内部类如何配合FunctionalInterface使用

其实是不是函数式接口无所谓的,随便一个接口都可以使用这种语法,但是其实更多的还是在函数式接口中进行灵活运用。

public static void test01() {
    AFunctionalInterface danDan = new AFunctionalInterface() {

        @Override
        public void danDan() {
            System.out.println("你是世界上最好的人啦");
        }
    };
    danDan.danDan();
}

        接去看一下反编译的字节码文件吧。

        可以看到确实也是进行生成了一个匿名内部类,当然不是说接口可以进行实例化,接口肯定不能进行实例化的,但是匿名内部类语法是支持的,这是Java帮你进行做的事情,帮你生成了一个实现了接口的匿名内部类。

final class Test01$1 implements AFunctionalInterface {
    Test01$1() {
    }

    public void danDan() {
        System.out.println("你是世界上最好的人啦");
    }
}

1.1.2.3Lambda表达式

        说句题外话,->的写法看上去太难受了在文稿中,我直接用=>替代了,大家写代码的时候还是要记得用->哦。

        Java是强面向对象语言,是面向对象的典范,离开了类你几乎不能做什么,起初Java是反对函数式编程这种风格的,但是随着时代的发展,函数式编程的呼声越来越多,但是Java设计者也不想引入典型的函数式编程(JS => 纯函数形式的函数式编程),破坏Java的强面向对象,于是天空一声巨响,Lambda闪亮登场,Java的设计者使用函数式接口这种方式巧妙的实现了Java的函数式编程,十分Nice,保留了Java面向对象的特征,也通过函数式编程赋予开发着代码更大的灵活性。

        Lambda表达式是什么样的呢:(参数列表) => { return }

        看一下Lambda表达式怎么用吧:

public static void test02(AFunctionalInterface aFunctionalInterface) {
    aFunctionalInterface.danDan();
}

private static void test03() {
    test02(() -> {
        System.out.println("嘿嘿");
    });
}

        查看运行结果:

1.1.2.4Lambda的其它简化形式

        Lambda的完整形式是(参数列表) => {

                // 执行逻辑

                // return 返回值

        }

        如果参数列表仅仅有一个参数,就可以省略(),如果只有返回值,没有其它逻辑,就可以省略{}。

        看一下简化后的形式:

public class Test03 {

    public static void main(String[] args) {
        TestLambda01 testLambda01 = string -> string;
    }

}

interface TestLambda01 {
    String returnStr(String string);
}
1.1.2.5方法引用

        方法引用是什么?当你的Lambda表达式仅仅涉及到一个方法的使用的时候,就可以进行使用这个方法引用了。

        简单写一下大家看一下怎么用吧:

public class Test04 {

    public static void main(String[] args) {
        TestFunction01 testFunction01 = System.out::println;
        testFunction01.func();
        TestFunction02 testFunction02 = System.out::println;
        testFunction02.func("嘿嘿");
        TestFunction03 testFunction03 = Test04::sendStr;
        testFunction02.func(testFunction03.func("迷糊吗"));
    }

    public static String sendStr(String str) {
        return str;
    }

}

interface TestFunction01 {
    void func();
}

interface TestFunction02 {
    void func(String name);
}

interface TestFunction03 {
    String func(String name);
}

        给大家展示了,方法引用的妙处,类名::方法名,有参无参,有返回值,无返回值都能轻松识别,这就是方法引用的妙处。

        但是也要注意哦,有重载方法的是不行的,因为编译器,压根不知道解析哪个。

        运行结果:

1.1.3Function - 相爱

        两情相悦,又岂在朝朝幕幕。

        你终于了解了她的背景了,你们也终于在一起了,去和她一起做一些事情吧。

public class Test02 {

    public static void main(String[] args) {

        // 1. 匿名内部类的方式
        Function<String, String> functionC = new Function<String, String>() {
            @Override
            public String  apply(String o) {
                return o;
            }
        };
        System.out.println(functionC.apply("我终于学会啦"));

        // 2. Lambda的方式
        // Function function = (name) -> {
        //     return name;
        // };
        // Lambda可以进行简化的, 如果只有一个返回值, 没有其它逻辑的化
        Function<String, String> function = (name) -> name;
        System.out.println(function.apply("哈哈哈"));

        // 3. 方法引用的方式
        Function<String, String> functionF = Test02::sendStr;
        System.out.println(functionF.apply("加油啦"));
    }

    public static String sendStr(String str) {
        return str;
    }

}

1.2分析函数comparing

        有时候真的不是源码写的太难,是你太菜了,基础都不会,如何去看得懂大牛写的代码呢?但是当你学会刚刚我提及的基础的时候,你将战无不胜,攻无不克了。

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

1.2.1函数声明的分析

1.2.1.1泛型参数

        这个函数的泛型参数估计就能吓走一群人,啥玩意啊,这么复杂。

        且听我一步一步分析,定义了两个泛型参数,T和U,T的话无所谓,U类型必须进行去继承Comparable接口,实现可比较的功能,因为在底层进行比较的时候,会调用U类型对象的compareTo方法,以生成一个Comparator比较器对象,U的话一般就是T类型中的属性字段。

1.2.1.2返回值

        可以发现返回值是一个Comparator比较器,比较器的泛型是T类型的,返回一个Comparator,这个Comparator是进行生成的为T类型对象进行排序的比较器,比较字段是U类型的对象,字段的比较的方式是每个字段的compare()进行比较的逻辑。

1.2.1.3函数参数

        函数参数是什么呢?是一个FunctionalInterface,Function,这个函数接收A类型的参数,返回B类型的数据。

1.2.1.4总结函数声明

        泛型参数定义的是:T类型 => 对象的类型,U类型 => T类型对象中的比较字段的类型。

        返回值:关于T类型对象的Comparator,泛型是T类型。

        函数参数:使用Function这个函数式接口进行声明生成比较器的对象,以及比较逻辑。

1.2.2函数执行的整体流程

        说实话,这个函数写的真好。

        我用实例讲给你听。

1.2.2.1定义一个Student
class Student implements {

    private int id;
    private String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}
1.2.2.2定义一个函数式接口

        传入一个Student类型的对象,返回一个Integer类型的数据。

Function<Student, Integer> function = (Student::getId);
1.2.2.3调用Comparator的comparing方法

        将定义的函数式接口传进去,会返回一个Comparator => 关于Student类型的比较器。

Comparator<Student> comparator = Comparator.comparing(function);

        看内部发生了什么,使用Lambda表达式进行生成了一个Comparator实现类对象,这个比较器要接收两个T类型(Student类型)的对象,进行使用function实例化对象取出这两个对象里面的相应字段,进行调用compareTo方法进行比较。

return (Comparator<T> & Serializable)
    (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
1.2.2.4调用生成的Comparator进行使用

        在创建TreeSet的时候将生成的Comparator对象传进去。

Student student1 = new Student(1, "蛋蛋");
Student student2 = new Student(2, "傻逼");
TreeSet<Student> set = new TreeSet<>(comparator);
set.add(student2);
set.add(student1);
for (Student student : set) {
    System.out.println(student.getId() + " : " + student.getName());
}

        运行后发现确实是按相应字段进行排序了。

2.分析comparing多参数方法

        这个多参数方法其实就是接收了一个function函数式接口去取出比较对象里面的字段数据,并且也传入了一个Comparator比较器对象进行按比较器对象的规则进行比较。

        具体流程就不分析了,就是将侵入式的Comparable接口形式,改用了无侵入式的Comparator比较器的形式去比较相应的字段。

public static <T, U> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor,
        Comparator<? super U> keyComparator)
{
    Objects.requireNonNull(keyExtractor);
    Objects.requireNonNull(keyComparator);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                          keyExtractor.apply(c2));
}

3.分析thenComparing方法

        thenComparing方法是一个default成员方法,是Comparator进行内置的一个默认方法,是由Comparator对象进行调用,用来加强自己Comparator对象的比较能力。其实就是接收一个新的Comparator对象,在生成新的比较器对象的时候,在内部先调用原先比较器的比较方法,然后再调用自身比较器定义的比较方法,进行比较对象的字段。

        先分析最原始的方法,接收了一个Comparator比较器对象。

3.1原始方法分析

        这个原始方法接收一个比较器对象,进行接收了一个比较器Comparator,先进行判断一下这个比较器是不是null,防止空指针异常。

        它是又使用Lambda函数进行新建了一个比较器对象,先调用原先比较器的逻辑,如果比较出来排名一致,就停止不继续比了,如果分别不出来差别,就继续比较(所以说是先比较前面的比较器,然后再比较后面的逻辑)。

default Comparator<T> thenComparing(Comparator<? super T> other) {
    Objects.requireNonNull(other);
    return (Comparator<T> & Serializable) (c1, c2) -> {
        int res = compare(c1, c2);
        return (res != 0) ? res : other.compare(c1, c2);
    };
}

3.2接收一个Function的方法

        这个方法进行接收了一个Funtion函数式接口,然后调用comparing方法去根据这个函数式接口去生成一个比较器对象,再进行传入到原始的thenComparing方法中去,进行生成一个新的比较器对象。

default <U extends Comparable<? super U>> Comparator<T> thenComparing(
        Function<? super T, ? extends U> keyExtractor)
{
    return thenComparing(comparing(keyExtractor));
}

3.3实战使用thenComparing方法

3.3.1定义一个Student类

        进行增加了一个card字段。

class Student {

    private int card;
    private int id;
    private String name;

    public Student(int id, int card, String name) {
        this.id = id;
        this.card = card;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getCard() {
        return card;
    }

}

3.3.2测试thenComparing

        先进性根据id升序比较,然后再使用thenComparing(使用Lambda表达式传入一个Comparator实现类对象),如果id一样区分不出来,就根据card进行降序排序。

public static void main(String[] args) {
    Function<Student, Integer> function = (Student::getId);
    Comparator<Student> comparator = Comparator.comparing(function).thenComparing(
            (student1, student2) -> student2.getCard() - student1.getCard()
    );
    Student student1 = new Student(0, 1,"蛋蛋");
    Student student2 = new Student(0, 2, "傻逼");
    TreeSet<Student> set = new TreeSet<>(comparator);
    set.add(student2);
    set.add(student1);
    for (Student student : set) {
        System.out.println(student.getId() + " : " + student.getCard() + " : " + student.getName());
    }
}

3.3.3进行测试输出结果

        可以发现当id区分不出来排名的时候,就使用card进行排序区分。

4.更多函数

4.1comparing系

4.1.1comparingInt函数

        这个函数主要是进行取出对象中的Int字段进行比较的。

public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}

        依赖的是ToIntFunction函数式接口,进行传入一个对象,返回一个int字段。

@FunctionalInterface
public interface ToIntFunction<T> {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    int applyAsInt(T value);
}

4.1.2comparingLong函数

        这个函数主要是进行取出对象中的Long字段进行比较的。

public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}

        依赖的是ToLongFunction函数式接口,进行传入一个对象,返回一个long字段。

@FunctionalInterface
public interface ToLongFunction<T> {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    long applyAsLong(T value);
}

        还有comparingDouble函数就不多介绍了...

4.2thenComparing系

4.2.1thenComparingInt函数

        主要是通过调用comparingInt实现的。

default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
    return thenComparing(comparingInt(keyExtractor));
}

4.2.2thenComparingLong函数

default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
    return thenComparing(comparingLong(keyExtractor));
}

        还有thenComparingDouble函数就不多介绍了...

5.结语

        费时很久,赶出来的一篇文章,希望大家JAVA进步!!!

        爱意随风其,风止意难平

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值