Java中的排序与内部比较器Compareable解析

 更新时间:2023年11月07日 10:12:52   作者:JFS_Study  
这篇文章主要介绍了Java中的排序与内部比较器Compareable解析,一般没有特殊要求时,直接调用(底层默认的升序排列)就可以得到想要的结果,所谓的 sort 方法排序底层都是基于这两种排序,故如果需要设计成所想要的排序就需要了解底层排序原理,需要的朋友可以参考下

一、Arrays.sort() 与 Collections.sort() 排序

Arrays.sort() 可以对数组,字符串等排序

import java.util.Arrays;
 
public class sort {
  public static void main(String[] args) {
    int[] num = new int[]{3,2,4,1,5};
    Arrays.sort(num);
    for(int i=0;i<num.length;i++) {
      System.out.print(num[i]+" ");
    }
  }
}
//1 2 3 4 5

Collections.sort() 是对list集合排序,list 也可以放数字、字符串

import java.util.ArrayList;
import java.util.Collections;
 
public class sort {
  public static void main(String[] args) {
    ArrayList<Integer> list  = new ArrayList<>();
    list.add(3);
    list.add(2);
    list.add(1);
    Collections.sort(list);
    System.out.print(list);
  }
}
//[1, 2, 3]

一般没有特殊要求时,直接调用(底层默认的升序排列)就可以得到想要的结果。所谓的 sort 方法排序底层都是基于这两种排序,故如果需要设计成所想要的排序就需要了解底层排序原理。对自定义对象数组排序,需要引入“比较器”的概念。Compareable 和 Compartor 接口就是比较器抽象接口,通过实现类重写接口方法来进行对象比较。

二、内部比较器:Compareable 接口分析

当使用 sort(Objetc[] a) 来进行对象的自然排序时,该对象必需实现 Compareable 接口,重写 compareTo 方法,并一般在此方法中定义 3 种返回值(正,零,负)来进行排序标准的确认,一般默认下面值来表示:

  • return 1 时,按照升序。
  • return 0 时,原位置不动。
  • return -1 时,按照降序。

应用:目标类实现 Comparable 接口,重写 compareTo(),定义排序规则,就可以直接调用 Collections.sort() 来排序对象数组。

@Data
public class Student implements Comparable{
    private int id;
    private int age;
    private int height;
    private String name;
    public Student(int id, String name, int age, int height) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height;
    }
    @Override
    public int compareTo(Object o) {
        Student s = (Student) o;
        if (this.age > s.age) {
            return 1;
        }else if (this.age < s.age) {
            return -1;
        }else {
            if (this.height >= s.height) {
                return 1;
            }else {
                return -1;
            }
        }
    }
}

上面是实现升序排列,如果要实现降序只需要把 1 与 -1 互换位置即可。测试:

public class Test {
	public static void printData(List<Student> list) {
		for (Student student : list) {
			System.out.println("学号:"+student.getId()+"姓名:"+student.getName()+
"年龄"+student.getAge()+"身高:"+student.getHeight());
		}
	}
	public static void main(String[] args) {
		List<Student> list = new ArrayList<>();
		list.add(new Student(1, "A", 20, 180));
		list.add(new Student(2, "B", 21, 175));
		list.add(new Student(3, "C", 22, 190));
		list.add(new Student(4, "D", 21, 170));
		list.add(new Student(5, "E", 20, 185));
		System.out.println("before sorted");
		printData(list);
		Collections.sort(list);
		System.out.println("after age and height sorted");
		printData(list);
	}
}

结果:

before sorted
学号:1姓名:A年龄20身高:180
学号:2姓名:B年龄21身高:175
学号:3姓名:C年龄22身高:190
学号:4姓名:D年龄21身高:170
学号:5姓名:E年龄20身高:185
after age and height sorted
学号:1姓名:A年龄20身高:180
学号:5姓名:E年龄20身高:185
学号:4姓名:D年龄21身高:170
学号:2姓名:B年龄21身高:175
学号:3姓名:C年龄22身高:190

三、Compartor 接口分析

①先建一个基本属性类:

@Data
public class Student {
    //创建两个基本属性
    String name = "";
    int age = 0;
    //重写构造方法用来传递数据
    public Student(String name, int age) {
       super();
       this.name = name;
       this.age = age;
    }
}

创建按照姓名升序排列的实现类 :

import java.util.Comparator;
//按照名字的升序排列。实现接口,泛型是自定义类,里面有要排序的内容   
public class NameSort implements Comparator{
    @Override  
    //两个参数是泛型的对象
    public int compare(Student o1, Student o2) {
   //按照姓名的升序排列,前面加个负号就按照降序排列
       return o1.getName().compareTo(o2.getName());
    }
}

②直接定义Java中PriorityQueue优先级队列就是利用这原理:

//传进来的数组或是字符串以及集合list
Arrays.sort(num,new Comparator<String>(){
    @Override
    public int compare(String o1,String o2){
        return o1.compareTo(o2);//升
        //return o2.compareTo(o1);//降
    }
});

应用:实现比较器接口 Comparator,重写 compare 方法,直接当做参数传进 sort 中。

@Data
public class Student {
	private int id;
	private int age;
	private int height;
	private String name;
	public Student(int id, String name, int age, int height) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.height = height;
	}
}

测试:

public class Test {
	public static void main(String[] args) {
		List<Student> list = new ArrayList<>();
		list.add(new Student(1, "A", 20, 180));
		list.add(new Student(2, "B", 21, 175));
		list.add(new Student(3, "C", 22, 190));
		list.add(new Student(4, "D", 21, 170));
		list.add(new Student(5, "E", 20, 185));
		System.out.println("before sorted");
		printData(list);
		Collections.sort(list, new Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				if(o1.getAge() >= o2.getAge()) {
					return 1;
				}
				else {
					return -1;
				}
			}
		});
		System.out.println("after age sorted");
		printData(list);
		Collections.sort(list, new Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				if(o1.getAge() > o2.getAge()) {
					return 1;
				}else if (o1.getAge() < o2.getAge()){
					return -1;
				}else {
					if (o1.getHeight() >= o2.getHeight()) {
						return 1;
					}else {
						return -1;
					}
				}
			}
		});
		System.out.println("after age and height sorted");
		printData(list);
	}
	public static void printData(List<Student> list) {
		for (Student student : list) {
			System.out.println("学号:"+student.getId()+"姓名:"+student.getName()+
"年龄"+student.getAge()+"身高:"+student.getHeight());
		}
	}
}

输出结果:

before sorted
学号:1姓名:A年龄20身高:180
学号:2姓名:B年龄21身高:175
学号:3姓名:C年龄22身高:190
学号:4姓名:D年龄21身高:170
学号:5姓名:E年龄20身高:185
after age sorted
学号:1姓名:A年龄20身高:180
学号:5姓名:E年龄20身高:185
学号:2姓名:B年龄21身高:175
学号:4姓名:D年龄21身高:170
学号:3姓名:C年龄22身高:190
after age and height sorted
学号:1姓名:A年龄20身高:180
学号:5姓名:E年龄20身高:185
学号:4姓名:D年龄21身高:170
学号:2姓名:B年龄21身高:175
学号:3姓名:C年龄22身高:190

单从上面的例子可以看出排序是稳定的,查看 Java 的Collections.sort的源代码,确实是基于稳定的归并排序实现的,内部还做了优化,叫TimSort。

API 排序

public static void main(String[] args) {

    List<Student> stuList = new ArrayList<>();
    stuList.add(new Student(1, "A", 20, 180));
    stuList.add(new Student(2, "B", 21, 175));
    stuList.add(new Student(3, "C", 22, 190));
    stuList.add(new Student(4, "D", 21, 170));
    stuList.add(new Student(5, "E", 20, 185));

    System.out.println(stuList);

    System.out.println("-------------先年龄升---后身高升-------------");
    Comparator<Student> compareAge = Comparator.comparing(Student::getAge);
    Comparator<Student> compareHight = Comparator.comparing(Student::getHeight);
    Collections.sort(stuList, compareAge.thenComparing(compareHight));
    for (Student s : stuList) {
        System.out.println(s.getName() + "," + s.getAge() + "," + s.getHeight());
    }

    System.out.println("-------------先年龄升---后身高降-------------");
    Collections.sort(stuList, compareAge.thenComparing(compareHight.reversed()));
    for (Student s : stuList) {
        System.out.println(s.getName() + "," + s.getAge() + "," + s.getHeight());
    }

    System.out.println("-------------先年龄降---后身高降-------------");
    Collections.sort(stuList, compareAge.reversed().thenComparing(compareHight.reversed()));
    for (Student s : stuList) {
        System.out.println(s.getName() + "," + s.getAge() + "," + s.getHeight());
    }
}

输出结果:

[Student(id=1, age=20, height=180, name=A), Student(id=2, age=21, height=175, name=B), Student(id=3, age=22, height=190, name=C), Student(id=4, age=21, height=170, name=D), Student(id=5, age=20, height=185, name=E)]
-------------先年龄升---后身高升-------------
A,20,180
E,20,185
D,21,170
B,21,175
C,22,190
-------------先年龄升---后身高降-------------
E,20,185
A,20,180
B,21,175
D,21,170
C,22,190
-------------先年龄降---后身高降-------------
C,22,190
B,21,175
D,21,170
E,20,185
A,20,180
Process finished with exit code 0

四、compareTo 源码说明

String比较用 compareTo 方法,针对参与比较的不同的字符,从第一位开始往后比较,然后返回相应的 int 值:

  1. 字符个数相同,返回参与比较的前后两个字符串的 ASSIC 码差值。
  2. 两个字符串首字母不同,则该方法返回首字母的 ASSIC 码差值。
  3. 参与比较的两个字符串如果首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的 ASSIC 码差值。
  4. 两个字符串不一样长,可以参与比较的字符又完全一样,则返回两个字符串的长度差值。
  5. 目前 compareTo 项目中的用途是比较版本号的高低。
private final char value[]; 
public int compareTo(String anotherString) {
     int len1 = value.length;
     int len2 = anotherString.value.length;
     int lim = Math.min(len1, len2);
     char v1[] = value;
     char v2[] = anotherString.value;

     int k = 0;
     while (k < lim) {
         char c1 = v1[k];
         char c2 = v2[k];
         if (c1 != c2) {
             return c1 - c2;
         }
         k++;
     }
     return len1 - len2;
}

Java 中的 compareto 方法实例

public static void main(String[] args) {
      //返回参与比较的前后两个字符串的ASSIC码的差值
       String a = "a";//97
       String b = "b";//98
       System.out.println("a.compareTo(b):" + a.compareTo(b));//-1
       System.out.println("b.compareTo(a):" + b.compareTo(a));//1

       String c = "c";
       String c1 = "c";
       System.out.println("c.compareTo(c1):" + c.compareTo(c1));//0

       //两个字符串首字母不同,则该方法返回首字母的ASSIC码的差值
       String d = "abc";
       String e = "bcdfg";
       System.out.println("d.compareTo(e):" + d.compareTo(e));//-1

       //参与比较的两个字符串如果首字符相同,则比较下一个字符,
       //直到有不同的为止,返回该不同的字符的ASSIC码差值
       String g = "abedfg";
       System.out.println("d.compareTo(g):" + d.compareTo(g));//-2
       //两个字符串不一样长,可以参与比较的字符又完全一样,
       //则返回两个字符串的长度差值
       String h = "abcdefg";
       System.out.println("d.compareTo(h):" + d.compareTo(h));//-4
       String i = "ab";
       System.out.println("d.compareTo(i):" + d.compareTo(i));//1
       //目前compareTo项目中的用途是比较版本号的高低
       String num = "1.0.0";
       String val = "1.0.1";
       System.out.println("num.compareTo(val):" + num.compareTo(val));//-1
  }

结果如下:

面试

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        if(numbers.length==0) {
            return "";
        }
        int len = numbers.length;
        String[] str = new String[len];
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<len;i++) {
            str[i] = String.valueOf(numbers[i]);
        }
        Arrays.sort(str,new Comparator<String>(){
            @Override
            public int compare(String o1,String o2){
                String c1 = o1 + o2;
                String c2 = o2 + o1;
                return c1.compareTo(c2);
            }
        });
        for(int i=0;i<len;i++) {
            sb.append(str[i]);
        }
        return sb.toString();
    }
}

到此这篇关于Java中的排序与内部比较器Compareable解析的文章就介绍到这了,更多相关Java中的排序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java由浅入深细数数组的操作上

    Java由浅入深细数数组的操作上

    数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。Java 语言中提供的数组是用来存储固定大小的同类型元素
    2022-04-04
  • 关于SpringBoot的热部署方案

    关于SpringBoot的热部署方案

    这篇文章主要介绍了关于SpringBoot的热部署方案,每次修改代码就得将项目重启,重新部署,对于一些大型应用来说,重启时间需要花费大量的时间成本,本文就来详解热部署方案,需要的朋友可以参考下
    2023-05-05
  • Java并发编程之Condition源码分析(推荐)

    Java并发编程之Condition源码分析(推荐)

    这篇文章主要介绍了Java并发编程之Condition源码分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 在拦截器中读取request参数,解决在controller中无法二次读取的问题

    在拦截器中读取request参数,解决在controller中无法二次读取的问题

    这篇文章主要介绍了在拦截器中读取request参数,解决在controller中无法二次读取的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java多线程下载的实现方法

    Java多线程下载的实现方法

    复习多线程的时候,练习了下,顺便记录一下:
    2013-03-03
  • struts中动态方法调用使用通配符

    struts中动态方法调用使用通配符

    这篇文章主要介绍了struts中动态方法调用使用通配符的相关资料,非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧
    2016-09-09
  • JVM实战系列之CPU100%和内存100%排查

    JVM实战系列之CPU100%和内存100%排查

    本文主要介绍了JVM实战系列之CPU100%和内存100%排查,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • ExpressionUtil工具类的应用实例

    ExpressionUtil工具类的应用实例

    这篇文章主要给大家介绍了关于ExpressionUtil工具类的应用实例,常用的工具类有很多,这是其中一个,了解基本的API可以帮助我们更好的开发,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-04-04
  • MyBatis Plus实现时间字段自动填充的完整方案

    MyBatis Plus实现时间字段自动填充的完整方案

    在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,本文将介绍如何使用 MyBatis Plus 的自动填充功能来优雅地解决这个问题,需要的朋友可以参考下
    2025-09-09
  • MyBatis Plus Mapper CRUD接口测试方式

    MyBatis Plus Mapper CRUD接口测试方式

    在数据库管理系统中,插入记录是添加新数据条目,而删除操作包括根据主键ID单条删除和批量删除,也可以基于特定条件进行删除,删除操作的SQL语句是通过键值对在Map中拼接而成,如delete from 表 where key1=value1 AND key2=value2
    2024-09-09

最新评论