黑马程序员——Comparable接口和Comparactor接口的使用以及分析

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

大纲:本篇博客主要对Comparable接口、Comparactor接口和Arrays.sort()、Collections.sort()方法的学习使用进行介绍,是因为做基础测试题和答题才知道这两个接口,题目已经提交,但是对两个接口的详细使用和区别却是一知半解,只是感觉它们有点类似,就抽出时间搞清楚,下面是学习的记录。
10、声明类Student,包含3个成员变量:name、age、score,创建5个对象装入TreeSet, 按照成绩排序输出结果(考虑成绩相同的问题)。
看到这个题目,自然而然很简单的 想到就是创建类并包含三个成员变量,加入TreeSet很简单,就开始写代码,下面是代码和运行结果。
import java.util.Iterator;
import java.util.TreeSet;

public class Test10 {

	/**
	 * 10、声明类Student,包含3个成员变量:name、age、score,创建5个对象装入TreeSet,
	 * 按照成绩排序输出结果(考虑成绩相同的问题)。
	 * 
	 * 思路:TreeSet要指定自然顺序并且不能重复,自定义的Student没有自然顺序所以要
	 * 实现Comparable接口对student进行,自然方法排序的增加,
	 * 如果不实现此接口将student对象加入数组就会报 com.itheima.
 	 * Student cannot be cast to java.lang.Comparable异常
	 */
	public static void main(String[] args) {
		//声明创建TreeSet集合泛型为Student
		TreeSet<Student> set = new TreeSet<Student>();
		//声明创建五个student并加入set集合
		Student stu1 = new Student("小明", 23, 98.5);
		Student stu2 = new Student("小红", 24, 45.6);
		Student stu3 = new Student("小强", 25, 84.2);
		Student stu4 = new Student("小刚", 26, 84.2);
		Student stu5 = new Student("小松", 23, 98);
		set.add(stu1);
		set.add(stu2);
		set.add(stu3);
		set.add(stu4);
		set.add(stu5);
		System.out.println("set大小:" + set.size());
		//调用所定义的对set集合的输出函数
		Myout(set);
	}

	public static void Myout(TreeSet<Student> set) {
		//迭代set集合
		Iterator<Student> it = set.iterator();
		//取出迭代器中的元素
		while (it.hasNext()) {
			Student stu = (Student) it.next();
			System.out.println(stu.toString());
		}

	}

}
/*
 * 定义Student类,并继承Comparable并实现compareto()方法
 * 在此我直接将泛型加入,省去compareto()中的强转
 * 
 */
class Student  {
	private String name;
	private int age;
	private double score;

	public Student() {
	}
	//增加带参数构造器方便添加数据
	public Student(String name, int age, double score) {
		super();
		this.name = name;
		this.age = age;
		this.score = score;
	}
	//增加get set 方法
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getScore() {
		return score;
	}
	public void setScore(double score) {
		this.score = score;
	}

	//覆盖toString()方法方便调试和显示
	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + ", score=" + score
				+ "]";
	}

}
点击运行,结果出乎意料:上图

考虑TreeSet,TreeSet是基于TreeMap实现的,TreeSet是有序的,要求其中存的元素要是实现Comparable接口,或者是在使用TreeSet时使用(TreeSet(Comparator<? superE> comparator)构造一个新的空 TreeSet,它根据指定比较器进行排序。)该构造器对TreeSet传入一个比较器。这就需要程序员手动实现Comparactor接口了。下面先说Comparable接口:

public interfaceComparable<T>

此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法

实现此接口的对象列表(和数组)可以通过 Collections.sort(和Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器

这段话的意思就是,自己编写的类要实现排序的功能就必须重写CompareTo方法定义自定义对象的自然比较方法,然会就能对其排序。

要重写compareTo方法:

compareTo

int compareTo(T o)
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

实现类必须确保对于所有的xy都存在sgn(x.compareTo(y)) == -sgn(y.compareTo(x))的关系。(这意味着如果y.compareTo(x)抛出一个异常,则x.compareTo(y)也要抛出一个异常。)

实现类还必须确保关系是可传递的:(x.compareTo(y)>0 && y.compareTo(z)>0)意味着x.compareTo(z)>0

最后,实现者必须确保x.compareTo(y)==0意味着对于所有的z,都存在sgn(x.compareTo(z)) == sgn(y.compareTo(z))。 强烈推荐(x.compareTo(y)==0) == (x.equals(y))这种做法,但并不是 严格要求这样做。一般来说,任何实现Comparable接口和违背此条件的类都应该清楚地指出这一事实。推荐如此阐述:“注意:此类具有与 equals 不一致的自然排序。”

在前面的描述中,符号sgn(expression)指定 signum 数学函数,该函数根据expression 的值是负数、零还是正数,分别返回-101中的一个值。

参数:
o - 要比较的对象。
返回:
负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
抛出:
ClassCastException - 如果指定对象的类型不允许它与此对象进行比较。
上面是JDK给出的方法解释阐述了该方法的使用详情,其实最重要的就是返回值了,返回-1、0、1这三个值分别代表的是比较此对象与指定对象的大小。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。下面就上代码:解决题目的要求,该代码是使用了Comparable接口

package com.itheima;

import java.util.Iterator;
import java.util.TreeSet;

public class Test10 {

	/**
	 * 10、声明类Student,包含3个成员变量:name、age、score,创建5个对象装入TreeSet,
	 * 按照成绩排序输出结果(考虑成绩相同的问题)。
	 * 
	 * 思路:TreeSet要指定自然顺序并且不能重复,自定义的Student没有自然顺序所以要
	 * 实现Comparable接口对student进行,自然方法排序的增加,
	 * 如果不实现此接口将student对象加入数组就会报 com.itheima.
 	 * Student cannot be cast to java.lang.Comparable异常
	 */
	public static void main(String[] args) {
		//声明创建TreeSet集合泛型为Student
		TreeSet<Student> set = new TreeSet<Student>();
		//声明创建五个student并加入set集合
		Student stu1 = new Student("小明", 23, 98.5);
		Student stu2 = new Student("小红", 24, 45.6);
		Student stu3 = new Student("小强", 25, 84.2);
		Student stu4 = new Student("小刚", 26, 84.2);
		Student stu5 = new Student("小松", 23, 98);
		set.add(stu1);
		set.add(stu2);
		set.add(stu3);
		set.add(stu4);
		set.add(stu5);
		System.out.println("set大小:" + set.size());
		//调用所定义的对set集合的输出函数
		Myout(set);
	}

	public static void Myout(TreeSet<Student> set) {
		//迭代set集合
		Iterator<Student> it = set.iterator();
		//取出迭代器中的元素
		while (it.hasNext()) {
			Student stu = (Student) it.next();
			System.out.println(stu.toString());
		}

	}

}
/*
 * 定义Student类,并继承Comparable并实现compareto()方法
 * 在此我直接将泛型加入,省去compareto()中的强转
 * 
 */
class Student implements Comparable<Student>{
	private String name;
	private int age;
	private double score;

	public Student() {
	}
	//增加带参数构造器方便添加数据
	public Student(String name, int age, double score) {
		super();
		this.name = name;
		this.age = age;
		this.score = score;
	}
	//增加get set 方法
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getScore() {
		return score;
	}
	public void setScore(double score) {
		this.score = score;
	}
	//覆盖compareTo()方法,定义出自己所需的情况
	@Override
	public int compareTo(Student o) {
		Student stu = o;
		//下面几个if else 语句实现了先通过分数进行排序和年龄进行排序
		//如果两者都相同则通过姓名进行排序。
		//返回的-1、1和0也可以是其它的证书或者负数
		if (this.score < stu.score)
			return (int) -1;
		else if (this.score > stu.score)
			return 1;
		else if (this.age > stu.age)
			return 1;
		else if (this.age < stu.age)
			return -1;
		else if (this.score == stu.score && this.age == stu.age)
			//分数年龄相同则返回姓名的字典顺序差。
			return this.name.compareTo(stu.name);
		return 0;
	}
	//覆盖toString()方法方便调试和显示
	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + ", score=" + score
				+ "]";
	}

}

上面已经描述了,要对TreeSet中的元素进行排序,需要对象具备自然顺序,要么就是使用TreeSet时候指定构造器,下面就展示通过实现Comparator 接口实现的方式

package com.itheima;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class Test10 {

	/**
	 * 10、声明类Student,包含3个成员变量:name、age、score,创建5个对象装入TreeSet,
	 * 按照成绩排序输出结果(考虑成绩相同的问题)。
	 * 
	 * 思路:TreeSet要指定自然顺序并且不能重复,自定义的Student没有自然顺序所以要
	 * 实现Comparable接口对student进行,自然方法排序的增加,
	 * 如果不实现此接口将student对象加入数组就会报 com.itheima.
 	 * Student cannot be cast to java.lang.Comparable异常
	 */
	public static void main(String[] args) {
		//声明创建TreeSet集合泛型为Student
		TreeSet<Student> set = new TreeSet<Student>(new myStudentComparactor());
		//声明创建五个student并加入set集合
		Student stu1 = new Student("小明", 23, 98.5);
		Student stu2 = new Student("小红", 24, 45.6);
		Student stu3 = new Student("小强", 25, 84.2);
		Student stu4 = new Student("小刚", 26, 84.2);
		Student stu5 = new Student("小松", 23, 98);
		set.add(stu1);
		set.add(stu2);
		set.add(stu3);
		set.add(stu4);
		set.add(stu5);
		System.out.println("set大小:" + set.size());
		//调用所定义的对set集合的输出函数
		Myout(set);
	}

	public static void Myout(TreeSet<Student> set) {
		//迭代set集合
		Iterator<Student> it = set.iterator();
		//取出迭代器中的元素
		while (it.hasNext()) {
			Student stu = (Student) it.next();
			System.out.println(stu.toString());
		}

	}

}

class Student {
	private String name;
	private int age;
	private double score;

	public Student() {
	}
	//增加带参数构造器方便添加数据
	public Student(String name, int age, double score) {
		super();
		this.name = name;
		this.age = age;
		this.score = score;
	}
	//增加get set 方法
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getScore() {
		return score;
	}
	public void setScore(double score) {
		this.score = score;
	}
	
	
	//覆盖toString()方法方便调试和显示
	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + ", score=" + score
				+ "]";
	}

}
//自定义排序器
class myStudentComparactor implements Comparator<Student>{


	@Override
	public int compare(Student o1, Student o2) {
		if (o1.getScore() > o2.getScore())
			return (int) -1;
		else if (o1.getScore() < o2.getScore())
			return 1;
		else if (o1.getAge() > o2.getAge())
			return 1;
		else if (o1.getAge() < o2.getAge())
			return -1;
		else if (o1.getScore() == o2.getScore() && o1.getAge() == o2.getAge())
			return o1.getName().compareTo(o2.getName());
		return 0;
	}
	
}

另外:在comparable接口的介绍中:也可以使用Arrays.sort()进行排序,上面已经展示了TreeSet的构造器,下面是简略的说明一下,如果对象加入的是Arrays中,也可以通过Arrays.sort()指定构造器进行元素的排序,下面是方法

static
<T> void
sort(T[] a, Comparator<? super T> c) 
根据指定比较器产生的顺序对指定对象数组进行排序。
static
<T> void
sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) 
根据指定比较器产生的顺序对指定对象数组的指定范围进行排序。
通过这样的传入比较器的方式我们也能实现对自定义对象的排序,下面是代码:

import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class SortStudent {

	public static void main(String[] args) {
		Student students[] = new Student[7];
		students [0]=new Student("Tom",24, 89);
		students [1]=new Student("Robin",32, 99);
		students [2]=new Student("Jerry",24, 99);
		students [3]=new Student("Lili",23, 87);
		students [4]=new Student("Jack",22, 87);
		students [5]=new Student("LiLei",25, 95);
		students [6]=new Student("Robin",32 ,99);
		Myout(students);
	}
	//定义输出函数在此进行排序
	public static void Myout(Student[] students) {
		Arrays.sort(students, new MysortType());
		for (Student student : students) {
			System.out.println(student.toString());
		}
	}

}
//自定义排序类定义排序方法
class MysortType implements Comparator<Student>{
	@Override
	public int compare(Student o1,Student o2) {
		
		if (o1.getScore() > o2.getScore())
			return (int) -1;
		else if (o1.getScore() < o2.getScore())
			return 1;
		else if (o1.getAge() > o2.getAge())
			return 1;
		else if (o1.getAge() < o2.getAge())
			return -1;
		else if (o1.getScore() == o2.getScore() && o1.getAge() == o2.getAge())
			return o1.getName().compareTo(o2.getName());
		return 0;
	}

}
class Student {
	private String name;
	private int age;
	private int score;

	public Student() {
	}
	//增加带参数构造器方便添加数据
	public Student(String name, int age, int score) {
		this.name = name;
		this.age = age;
		this.score = score;
	}
	//增加get set 方法
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getScore() {
		return score;
	}
	public void setScore(int score) {
		this.score = score;
	}
	//覆盖toString()方法方便调试和显示
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", score=" + score
				+ "]";
	}

}

总结:相同点:Comparable接口和comparactor接口都是对自定义类添加自然顺序的方法。
      不同点:Comparable接口实现方式是自定义类实现Comparable接口,并重写compareTo方法,自定义排序方式,使得自定义类有了自然顺序的排序方式但是这种方 式改变了自定义类的结构,使得代码不再完美。
      Comparator接口实现方式是例如本文的Student类之外定义新的类,实现Comparator接口,并实现comapre方法,自定义排序方式,形成排序器,然后将排序 器传给TreeSet,从而实现对Student的排序。这种方式相对于第一种而言,更具有灵活性。而且没有对Student类本身造成任何的影响。这种方式更加方便。
而且看一下下面是Comparable多有的实现类

Authenticator.RequestorType, BigDecimal, BigInteger, Boolean, Byte, ByteBuffer, Calendar, Character, CharBuffer, Charset, ClientInfoStatus, CollationKey, Component.BaselineResizeBehavior, CompositeName, CompoundName, Date, Date, Desktop.Action, Diagnostic.Kind, Dialog.ModalExclusionType, Dialog.ModalityType, Double, DoubleBuffer, DropMode, ElementKind, ElementType, Enum, File, Float, FloatBuffer, Formatter.BigDecimalLayoutForm, FormSubmitEvent.MethodType, GregorianCalendar, GroupLayout.Alignment, IntBuffer, Integer, JavaFileObject.Kind, JTable.PrintMode, KeyRep.Type, LayoutStyle.ComponentPlacement, LdapName, Long, LongBuffer, MappedByteBuffer, MemoryType, MessageContext.Scope, Modifier, MultipleGradientPaint.ColorSpaceType, MultipleGradientPaint.CycleMethod, NestingKind, Normalizer.Form, ObjectName, ObjectStreamField, Proxy.Type, Rdn, Resource.AuthenticationType, RetentionPolicy, RoundingMode, RowFilter.ComparisonType, RowIdLifetime, RowSorterEvent.Type, Service.Mode, Short, ShortBuffer, SOAPBinding.ParameterStyle, SOAPBinding.Style, SOAPBinding.Use, SortOrder, SourceVersion, SSLEngineResult.HandshakeStatus, SSLEngineResult.Status, StandardLocation, String, SwingWorker.StateValue, Thread.State, Time, Timestamp, TimeUnit, TrayIcon.MessageType, TypeKind, URI, UUID, WebParam.Mode, XmlAccessOrder, XmlAccessType, XmlNsForm 我们可以发现诸如String、Byte、Float等等很多常用的类都是实现了Comparable接口的,而Comparator实现类是 Collator, RuleBasedCollator所以从此我们可以发现,如 果要进行自定义排序方法诸如上面Comparable接口中的实现类,都是需要使用Comparator的方法的。所以,Comparator接口更具有灵活性,适合自定义排序顺序的使 用。


版权声明:本文为shizhitita原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。