一、用sort实现自定义排序
排序功能的核心方法:sort
sort可以对数组进行排序,被排序数组是sort方法的参数(之一)。
sort有很多重写方法,比较常见的是三个参数,sort(数组,起始,结束),用于把从起始到结束的数组元素排序,且数值只能按照大小顺序排序,字符串只能按照字典顺序排序。
想要自定义排序方法必须要用到sort(数组,排序接口),自定义排序规则的主要工作是定义排序接口。
定义排序接口
interface Comparator<Object>
不能把接口直接放到参数上,所谓排序接口是实现了Comparator接口的类。
实现排序接口的类必须单独放在一个文件中,若和主方法在同一文件则会报错
Class 'XXX' is public, should be declared in a file named 'XXX.java'
根据sort方法的使用逻辑,排序接口应返回正整数、负整数或零来代表排序规则,sort会自动按照返回的正负零来排序,如图是升序排序的写法:
public class NoComparator implements Comparator<Student> {
public int compare(Student o1, Student o2) {
return o2.getNo()-o1.getNo();
}
}
前一个元素 减 后一个元素 会让数组升序排序。
当o1的No值大于o2时返回一个正数,相等时返回零,o1的No值小于o2时则返回负数,sort拿到这个返回值后操作的具体步骤可以忽略。
自定义排序接口的使用
创建有三个属性的Student类,用刚才定义的NoComparator作为sort的第二个参数,即可以编号(No)为依据进行排序。
在输出时分别获取Student类的三个属性并组成用逗号隔开的格式来打印。
public class Info {
public static void main(String[] args) {
Student sc = new Student(10000, "lzzzz", 19);
Student sc1 = new Student(10001, "wxx", 20);
Student sc2 = new Student(10003, "zh", 17);
Student sc3 = new Student(10002, "g", 15);
Student scs[] = new Student[]{sc,sc1,sc2,sc3};
Arrays.sort(scs, new NoComparator());//这里传入了一个比较器
for (int i = 0; i < scs.length; i++) {//用循环来输出数组
System.out.println(scs[i].no+","+scs[i].name+","+scs[i].age);
}
输出结果是:
10002,g,15
10003,zh,17
10001,wxx,20
10000,lzzzz,19
二、本例的一些简化的写法
使用匿名内部类定义比较器
匿名内部类指在其他方法内定义,只实现方法,无需命名的类或方法。用于定义临时使用的方法,无需再使用一个单独的文件来储存自定义比较器。
针对本例所描述的排序也可以用匿名内部类来定义排序规则。
Arrays.sort(scs, new Comparator<Student>(){
public int compare(Student s1, Student s2){
return s1.name.length() - s2.name.length();
}
});//内部类比较器
for (int i = 0; i < scs.length; i++) {//用循环来输出数组
System.out.println(scs[i].no+","+scs[i].name+","+scs[i].age);
}
这里在参数中定义了排序规则。如下,对比较器的定义直接充当第二个参数,当场定义如何排序。
new Comparator<Student>(){
public int compare(Student s1, Student s2){
return s1.name.length() - s2.name.length();
}
}
因为这个参数一定要实现Comparator接口,所以在创建(new)的时候先写Comparator<T>,再写方法的实现过程,相当于先定义了一个实现Comparator的类,再实现,不过这个类因为没有命名所以只能在本次sort中使用。由于这次定义的排序规则是name属性的长度,所以输出结果是:
10003,zh,17
10002,g,15
10001,wxx,20
10000,lzzzz,19
使用lambda表达式来简化匿名内部类的定义
lambda表达式可以极大地简化匿名内部类的定义,只需注明接收两个Student类对象,返回age的大小关系即可使sort根据age的大小来排序。
Arrays.sort(scs, (Student s1, Student s2) -> s1.age - s2.age);//这里使用lambda表达式代替比较器
for (int i = 0; i < scs.length; i++) {//用循环来输出数组
System.out.println(scs[i].no+","+scs[i].name+","+scs[i].age);
}
输出结果是
10002,g,15
10003,zh,17
10000,lzzzz,19
10001,wxx,20
除了排序规则可以简化以外,遍历输出也可以简化。
用forEach方法遍历数组,再用双冒号表达式表明使用输出语句。
Arrays.sort(scs, (Student s1, Student s2) -> s1.age - s2.age);//这里使用lambda表达式代替比较器
Arrays.stream(scs).forEach(System.out::println);//用双冒号表达式输出
其中,forEach是列表类的方法,作用是遍历列表元素,送入括号内让括号内指定的方法来操作遍历的元素。与往常的方法不同,操作对象在外,执行方法在内。
System.out::println的意思是执行System.out包中的println方法,表明来源包和方法名就行了,连括号都省了。
Arrays.stream(数组)可以把数组转化成流,相当于列表,这样就可以用forEach来遍历了。
结果是:
Student@74a14482
Student@4554617c
Student@677327b6
Student@1540e19d
普通的for循环+println可以带参数,所以能够指定对象的某属性进行操作,如s1.no表示s1的号码,才能分别输出对象的各个属性。若直接输出对象则只能得到首地址。
forEach中的打印方法无法携带参数,只能直接打印对象,所以只得到了首地址。但是想要通过forEach来遍历数组则只能直接打印对象。
如何实现直接打印对象?
其实在打印方法中都会调用toString方法,对于基本数据类型,toString将值变为字符串,然后再被打印出来。对于实例对象,变量本身存储的就是对象的首地址,toString只是先让这个地址变成字符串然后输出而已。所以可以通过重写toString方法来实现复杂对象的打印。
public String toString(){
return getNo()+","+this.name+","+this.age;
}
在需要打印的类中重写toString方法,本例中在Student类中添加toString,直接打印对象时即可按照返回值指定的格式输出结果。
10002,g,15
10003,zh,17
10000,lzzzz,19
10001,wxx,20
其中,虽然no和age都是整数型,但是name和逗号是字符串,与字符串相加即可变成一个长的整个字符串。
同理,如果在输出对象时只想输出age,可以把toString方法作如下重写。
public String toString(){
return this.age+"";
}
空字符串也是字符串,也能把返回结果变成字符串值并用打印方法输出。能得到四个年龄值。
15
17
19
20
三、集合的排序
Arrays.sort()只能排序数组,想要排序集合只能使用Collections.sort()。原理与数组相同。
先定义比较器
public class NumComparator implements Comparator<item> {
public int compare(item o1, item o2){
return o1.num - o2.num;
}
}
新的类是item,所以尖括号里写item,除此之外和上例完全一致。
public static void main(String[] args) {
List<item> items = new ArrayList<>();
items.add(new item(1, "zhangsan", 15));
items.add(new item(2,"lisi", 19));
items.add(new item(3, "wangwu", 16));//创建集合
items.forEach(System.out::println);//先打印一遍看看重写toString的效果
//第一遍根据年龄排序
Collections.sort(items, (item o1, item o2) -> o1.age - o2.age);
items.forEach(System.out::println);
//第二遍根据编号排序,用比较器
Collections.sort(items, new NumComparator());
items.forEach(System.out::println);
//第三遍根据名称长度排序,用匿名内部类
Collections.sort(items, new Comparator<item>() {
@Override
public int compare(item o1, item o2) {
return o1.name.length() - o2.name.length();
}
});
items.forEach(System.out::println);
}
这是新的toString。