日常生活中,如果想比较两个数的大小,可采用做差的方式,做差结果的正负可用来判断两个数的大小。假设A - B = C
- 若整数C > 0,说明 A > B ;
- 若整数C = 0,说明 A = B;
- 若整数C < 0,说明 A < B;
java的两种比较器均基于以上判断逻辑,将两个待比较的Object经过某种处理,返回一个整数值,然后根据整个整数值的正负判断大小。类似地,自定义实现比较器时,也是同样道理,经过逻辑处理之后,返回一个整数。
内部比较器(基于Comparable接口)当某一个业务类A需要具备可比较的特点时,直接实现Comparable接口,重写compareTo(T o)方法,意味着类A支持排序。对于存储类A的列表或数组,就可以使用Collections.sort(List l)或Arrays.sort(E[] es)进行排序。
使用这种方式实现的比较器的比较逻辑代码在业务类内部,因此也被称为内部比较器。java的基本数据类型均采用这种方式。
-
Comparable接口源码
public interface Comparable
{ public int compareTo(T o); } -
Student类
业务类直接继承Comparabe接口,实现compareTo方法
public class Student implements Comparable
{ private int age; private double height; ...省略geter setter... public Student( int age,double height) { this.age = age; this.height = height; } @Override public int compareTo(Student o) { //按照年龄升序排序,int不需要转化 //return this.age - o.getAge(); //按照身高排序, double类型需要转化。 //用到了基本数据类型的比较器 return ((Double)(this.height)).compareTo((Double)(o.getHeight())) ; } public static void main(String[] args) { Student[] ss = new Student[5]; ss[0] = new Student(15,1.9); ss[1] = new Student(12,1.8); ss[2] = new Student(10,1.7); ss[3] = new Student(16,1.6); ss[4] = new Student(13,2.1); //工具类对数组排序 Arrays.sort(ss); for (int i = 0; i < 5; i++) { System.out.println(ss[i].getHeight()); } } } -
测试代码
//两个学生比身高 public static void compare1() { Student s1 = new Student(16, 1.8); Student s2 = new Student(16, 1.67); System.out.println(s1.compareTo(s2)); // 1 } //对学生集合按照身高排序 public static void compare1() { Student[] ss = new Student[5]; ss[0] = new Student(15,1.9); ss[1] = new Student(12,1.8); ss[2] = new Student(10,1.7); ss[3] = new Student(16,1.6); ss[4] = new Student(13,2.1); //工具类对数组排序 Arrays.sort(ss); for (int i = 0; i < 5; i++) { System.out.println(ss[i].getHeight());// 升序 1.6 1.7 1.8 1.9 2.1 } }
在内部比较器中,比较逻辑的实现代码在业务类的内部。当比较逻辑发生变化时,就必须修改业务类的代码,这不符合设计模式的开闭原则。此时可使用基于策略模式的外部比较器。
其方式是新建一个实现Comparator接口的具体比较器类,并将比较逻辑写入int compare(T o1, T o2)方法。同内部比较器类似,外部比较器同样是对比较算法的实现,因此int compare(T o1, T o2)的返回值同样是整数,这就要求对于业务类的比较项做差不为整数的情况,需要将其差值转换为整数。
-
业务类Student2
对比Student的代码,可以发现Student2中只包含了业务自身所需要的代码,没有用于实现比较逻辑的代码
public class Student2 { private double height; private int age; ....省略getter setter.... public Student2( int age,double height) { this.age = age; this.height = height; } }
-
外部比较器1
根据业务比较逻辑的设计,创建一个对应的比较器类,该比较器类独立于业务类,两者不存在耦合关系。
该比较器按照年龄比较Student2的”大小“,此处年龄是int型,其差值仍未int型,因此不必转换。public class Student2Compartor1 implements Comparator
{ @Override public int compare(Student2 o1, Student2 o2) { return o1.getAge() - o2.getAge(); } } -
外部比较器2
该比较器按照身高比较Student2的”大小“,此处身高是double型,其差值是double型,因此需要转换。public class Student2Comparator2 implements Comparator
{ @Override public int compare(Student2 o1, Student2 o2) { //不推荐使用compareTo方法,compareTo底层仍是调用了compare方法 // return ((Double)(o1.getHeight())).compareTo(((Double)(o2.getHeight()))); //将两double的差值转换为int return Double.compare(o1.getHeight(),o2.getHeight()); } } -
测试代码
创建不同的比较器实例,然后可以进行两个对象的比较,也可进行集合排序public static void comparator1Test(){ //父类引用指向子类对象 //比较器1 按年龄比较 Comparator
sc1 = new Student2Compartor1(); //比较器2 按身高比较 Comparator sc2 = new Student2Comparator2(); //比较两个Student2 Student2 s1 = new Student2(11, 1.7); Student2 s2 = new Student2(12, 1.6); System.out.println(sc1.compare(s1, s2)); //-1 按年龄 s1 < s2 System.out.println(sc2.compare(s1, s2));// 1 按身高 s1 > s2 //对数组排序 Student2[] ss = new Student2[5]; ss[0] = new Student2(15,1.9); ss[1] = new Student2(12,1.8); ss[2] = new Student2(10,1.7); ss[3] = new Student2(16,1.6); ss[4] = new Student2(13,2.1); //工具类对数组排序,传入一个集合和对应的比较器 //传入比较器1 按年龄排序 Arrays.sort(ss,sc1); for (int i = 0; i < 5; i++) { System.out.println(ss[i].getAge());//升序 10 12 13 15 16 } //传入比较器2 按身高排序 Arrays.sort(ss,sc2); for (int i = 0; i < 5; i++) { System.out.println(ss[i].getHeight());//升序 1.6 1.7 1.8 1.9 2.1 } }
在测试代码中可以看到,Student2的集合ss即可以接收按年龄排序的比较器sc1,也可以接收按身高排序的比较器sc2。查看其方法签名public static
在实际编码实践中,用户基于不同的比较逻辑,创建不同的外部比较器,这些比较器出现的位置完全可以互换,这实际上就是策略模式的使用。外部比较器基于策略模式组合业务类和比较器类,避免了内部比较器因使用继承造成了耦合,便于修改和扩展。其中,
- 业务类Student2 对应 策略模式中的环境角色
- 外部比较器基类Comparator 对应 策略模式中的抽象策略角色
- 各种自定义的外部比较器(Student2Comparator1、Student2Comparator2)对应 策略模式中的具体策略类
-
新建外部类
这种方式是显示创建一个独立的外部比较器,使用时创建对应的比较器实例,如上文的测试代码描述。
-
匿名内部类
在外部比较器时,一般只需实现Comparator的核心方法compare,因此在创建比较器时,可以使用匿名内部类。
//给排序数组传入一个匿名内部类的比较器 Arrays.sort(ss, new Comparator
() { @Override public int compare(Student2 o1, Student2 o2) { return o1.getAge() - o2.getAge(); } };);
不管是jdk自带比较器还是自定义比较器,使对应类具备可比较的特性之后,多用于对类的集合进行排序。其中,排序结果和做差的顺序有关。
-
在内部比较器中,待比较双方是 1)当前所在类的this实例 ;2)外部传入的同类型实例,即int compareTo(T o)方法的参数o
-
在外部比较器中,待比较双方是int compare(T o1, T o2)的两个参数o1、o2;
为方便记忆,将内部比较器的this 和外部比较器的o1作为基准,如果他们作为被减数,即位于减号左边,那排序结果为升序排序,否则为降序排序。
-
升学排序
//内部比较器 基准作为被减数 public int compareTo(Test o) { return this.i - o.i; } //外部比较器 o1作为被减数 public int compare(int o1, int o2) { return o1 - o2; }
-
降序排序
//内部比较器 this作为减数 public int compareTo(Test o) { return o.i - this.i ; } //外部比较器 o1作为减数 public int compare(int o1, int o2) { return o2 - o1; }
上文描述的两种比较器都是基于类实现,因此对于基本数据类型,默认是指基本数据类型装箱之后的的比较器。
-
int类型
由于两个int类型数值的差值已经是一个整数,不必再进行转换,因此其比较大小的实现不必使用上文的两种比较器,而是直接做差即可.
public int compareTest(int a, int b){ return a - b; }
-
String类型
字符串不是数字,不能直接做差返回一个整数,因此需要借助比较器实现字符串的比较。jdk默认使用内部比较器,即实现Comparable接口。其比较逻辑基于字符串的字符的Unicode值,按照顺序逐个对比字符串的字符unicode值大小。以下为String类的比较逻辑实现
// anotherString为待比较的字符串 public int compareTo(String anotherString) { //value为当前字符串对应的字符数组 int len1 = value.length; //获取另一个字符串的字符数组 int len2 = anotherString.value.length; //获取较短字符串的长度 int lim = Math.min(len1, len2); //另存字符数组引用 char v1[] = value; //另存字符数组引用 char v2[] = anotherString.value; //遍历两个字符数组 //由于字符和int可以互换,因此将两个字符做差等同于int型做差, //返回差值 int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { //使用this作为被减数,因此排序是升序 return c1 - c2; } k++; } //若已遍历完较短的字符串后,仍无法判断大小,则根据长度判断,较长的更大。 //使用this作为被减数,因此排序是升序 return len1 - len2; } static void stringTest(){ String a = "aaaa"; String b = "bbbb"; String d = "dddd"; //两个字符串比较 System.out.println(a.compareTo(b)); //-1. 底层执行'a' - 'b',等价于97 - 98 System.out.println(a.compareTo(d));//-3. 底层执行‘a’ - 'd',等价于97 - 100 //字符串集合排序 String[] ss = new String[5]; ss[0] = "abc"; ss[1] = "xxxxx"; ss[2] = "bcdeeee"; ss[3] = "bc"; ss[4] = "dfff"; Arrays.sort(ss); for(String s : ss){ System.out.println(s); // abc bc bcdeeee dfff xxxxx } }
-
double类型
两double数据做差,其结果仍是double类型。若将结果强制类型转换为int型,会丢失小数部分,其结果不再适用于判断算法。此时可以借助其包装类型Double。
在排序时。jdk默认会为基本数据类型装箱,并使用升序排序。特别地,包装类提供了两个比较方法1)静态方法static int compare(double d1, double d2) ;2)重写comparable接口方法Double.compareTo(Double d),该方法底层仍是调用静态方法compare,在使用时建议使用compare()方法。
static void doubleTest(){ double a = 1.2; double b = 1.8; int c = (int)(a - b); //0.按照算法逻辑可得 a 等于 b,实际上是 a < b System.out.println(c); //两个doule比较 //Double的内部比较器 System.out.println(((Double) a).compareTo((Double) b));//-1 //Double的静态方法 System.out.println(Double.compare((Double) a, (Double) b));//-1 //double集合排序 double[] ds = new double[5]; ds[0] = 1.7; ds[1] = 1.3; ds[2] = 1.9; ds[3] = 1.5; ds[4] = 1.6; Arrays.sort(ds); for(double d : ds){ System.out.println(d);// 1.3 1.5 1.6 1.7 1.9 } }
-
其他基本数据类型同double类似
- 将double类型强转为Double类型,然后调用Double的方法,需要三个括号。((Double)(this.height))
- Unicode,ASCII,UTF-8的区别
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)