当前位置: 首页 > news >正文

深入分析几个难以理解的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() {@Overridepublic 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>() {@Overridepublic 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进步!!!

        爱意随风其,风止意难平

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 心觉:小时候常听到的这些教导,正在禁锢你的人生,该翻身了
  • 小程序开发设计-第一个小程序:注册小程序开发账号②
  • C# 比较对象新思路,利用反射技术打造更灵活的比较工具
  • 【MYSQL表的增删改查(进阶)】
  • 【JS】path的使用说明
  • 105.游戏安全项目-基址的技术原理-分析技巧
  • Unity 之 【Android Unity FBO渲染】之 [Unity 渲染 Android 端播放的视频] 的一种方法简单整理
  • Centos7更换阿里云的 YUM 镜像仓库
  • 计算机网络30——Linux-gdb调试命令makefile
  • Jenkins Docker Pipeline Clone Build Deploy mysqldump
  • 企业项目中常用的表结构设计
  • 摩托罗拉大顾问ADVISOR BP机拆解评测
  • MySQL索引测试
  • 智能体时代,AI正从“神坛”走向“人间”
  • 工具集锦 论文 施工ing
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • 230. Kth Smallest Element in a BST
  • CentOS 7 防火墙操作
  • CentOS7简单部署NFS
  • django开发-定时任务的使用
  • Java IO学习笔记一
  • Java|序列化异常StreamCorruptedException的解决方法
  • npx命令介绍
  • PHP 的 SAPI 是个什么东西
  • Terraform入门 - 1. 安装Terraform
  • 安装python包到指定虚拟环境
  • 基于webpack 的 vue 多页架构
  • 记录一下第一次使用npm
  • 前端技术周刊 2019-01-14:客户端存储
  • 浅谈Golang中select的用法
  • 容器服务kubernetes弹性伸缩高级用法
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 我的业余项目总结
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • ​探讨元宇宙和VR虚拟现实之间的区别​
  • ​虚拟化系列介绍(十)
  • # 数据结构
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • (1)SpringCloud 整合Python
  • (LeetCode) T14. Longest Common Prefix
  • (二)原生js案例之数码时钟计时
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (接口封装)
  • (三)SvelteKit教程:layout 文件
  • .htaccess 强制https 单独排除某个目录
  • .NET : 在VS2008中计算代码度量值
  • .NET 8 跨平台高性能边缘采集网关
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .NET中统一的存储过程调用方法(收藏)
  • @html.ActionLink的几种参数格式
  • [145] 二叉树的后序遍历 js