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 类/接口{}进行创建一个对象,如果你只是想创建这个类的对象,一次性进行使用,不想复用这个类,就直接使用匿名内部类就可以了。
废话少说,上代码!!!
- 匿名内部类的简单使用
简单说一下:如果你只是想创建一个你进行拓展了成员方法的类的对象进行使用,那么使用匿名内部类是一个好选择。
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);}
}
- 匿名内部类如何配合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进步!!!
爱意随风其,风止意难平