自然排序与定制排序

1.自然排序

在Java中提供了一个Comparable接口,在接口中定义了public int compareTo(To);抽象方法
该方法返回一个整数值,通过obj1.compareTo(obj2),对两个对象进行比较,如果返回值是0则表明两数相等,如果返回正整数则表明obj1大于obj2,否则如果返回负整数则表明obj1小于obj2

TreeSet会自动调用compareTo(Object obj)方法进行元素之间的排序,然后以升序排列

在Set集合中的TreeSet章中说过,Set其实就是Map集合的特殊表现形式(value为null的集合)那么跟踪源码,下面这个add方法的注释,说明排序是通过自然排序的

    /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element {@code e} to this set if
     * the set contains no element {@code e2} such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns {@code false}.
     *
     * @param e element to be added to this set
     * @return {@code true} if this set did not already contain the specified
     *         element
     * @throws ClassCastException if the specified object cannot be compared
     *         with the elements currently in this set
     * @throws NullPointerException if the specified element is null
     *         and this set uses natural ordering, or its comparator
     *         does not permit null elements
     */
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

再次跟进到put方法

private final Comparator<? super K> comparator;
//put方法作为添加元素和修改元素的两种功能,添加元素的时候统一返回的是null,修改元素的时候统一返回的是修改之前的元素的value。作为TreeSet方法很明显只有返回null
    public V put(K key, V value) {
        Entry<K,V> t = root;//根节点
        if (t == null) {//判断根节点是否为空
            compare(key, key); // type (and possibly null) check(检查类型可能为空)
            
            root = new Entry<>(key, value, null);//将parent赋值为null并作为根节点
            size = 1;
            modCount++;
            return null;//返回null,跳过余下代码
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;//判断comparator是定制排序还是自然排序
        //如果comparator是自然排序
        if (cpr != null) {//判断 comparator 是否为null,如果是自然顺序则为null
        	//通过循环比较key的值计算将要添加的结点的位置
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);//如果发现有某个结点的key值和将要添加的key的值相等,说明这是修改操作,修改其value值返回旧value值
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);//如果根节点不是空
        }
        else {//如果在创建对象的时候并没有从外部传入comparator比较器
            if (key == null)//判断key的值是否为null
                throw new NullPointerException();//如果是就抛出空指针异常
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);//调用方法重构红黑树
        size++;
        modCount++;
        return null;
    }

通过上述源码解析我们很容易就发现,我们传入值到add方法中,会调用put方法,put方法将自然排序和定制排序进行了隔离,而源码中将接口Comparator设置为finale类型
并在put方法中进行判断,
如果是Comparator定制排序(默认排序),则可以通过key的compare排序后判断key值是否相等,如果不相等则生成数的左或右节点,相等则替换(在TreeSet中value值都为null,所以即使替换也看不出效果),
如果是Comparable自然排序(外传入排序(因为包装类或String类型中都写有自然排序方法,如下代码)),那么private final Comparator<? super K> comparator;就会是null,则cpr就是null,那么程序就会执行自然排序,排序并在不相等的情况下生成树的左或右子节点,相等则替换
最后重构红黑树

    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;
    }

看完String类中重写的Comparable接口中的compareTo方法后就会更加容易理解,为什么注释中会说是通过自然排序了
因为这是为了使对象通过一个默认的顺序进行排序,如果是已经定义好的(如:String、Integer等)
所有添加的数据都按照这个顺序进行排序,则称为自然排序

但是我们发现一个问题,即我们传入的值之间必须是需要进行比较的
那么问题来了,如果传入相同的对象确实,可以很容易的通过某种规则进行比较,但是不同的对象呢?

代码如下

package TreeSetTest;

import java.util.Date;
import java.util.TreeSet;

public class Test1 {
	public static void main(String[] args) {
		TreeSet ts=new TreeSet();
		ts.add(new String());
		ts.add(new Date());
	}
}

会出现java.lang.String cannot be cast to java.util.Date的错误,无法从String转换成Date,在String类中的compareTo方法中对应的形参是该类对象,如果传入一个Date类的对象,对象之间会发生强制转换,而我们知道,这样是并不能发生转换的

package TreeSetTest;

import java.util.Date;
import java.util.TreeSet;
//这样又是可以的了
public class Test1 {
	public static void main(String[] args) {
		TreeSet ts=new TreeSet();
		ts.add(new String());
		ts.add(new String());

	}
}
所以在TreeSet中是必须要求对象是相同的并且自然排序需要写在对象所在的类中(因为是外传入排序)

2.定制排序

定制排序是使用的java中提供的Comparator接口,自然排序就是说排序发生在封装类中,用户无法使用,但是定制排序是可以通过用户自己实现的,在创建TreeSet对象时就可以自定义Comparator接口中的int compare(T o1,To2)方法,比较两个参数的大小,如果该方法返回正整数则表明o1大于o2,如果返回0则说明o1=o2,如果返回负整数则说明o1小于o2

整数类型

package TreeSetTest;

import java.util.Comparator;
import java.util.Date;
import java.util.TreeSet;

public class Test1{
	public static void main(String[] args) {
		TreeSet<Person> ts=new TreeSet<Person>(new Comparator(){
			@Override
			public int compare(Object o1,Object o2) {
				Person p1=(Person)o1;
				Person p2=(Person)o2;
				return -(p1.getAge()-p2.getAge());
			}
		});
		ts.add(new Person(15,"小明","男"));
		ts.add(new Person(17,"小明","女"));
		System.out.println(ts);
	}
}

输出如下

[Person [age=17, name=小明, sex=女], Person [age=15, name=小明, sex=男]]

String类型

package TreeSetTest;

import java.util.Comparator;
import java.util.Date;
import java.util.TreeSet;

public class Test1{
	public static void main(String[] args) {
		TreeSet<Person> ts=new TreeSet<Person>(new Comparator(){
			@Override
			public int compare(Object o1,Object o2) {
				Person p1=(Person)o1;
				Person p2=(Person)o2;
				return p1.getName().compareTo(p2.getName());
			}
		});
		ts.add(new Person(15,"小明","男"));
		ts.add(new Person(17,"小红","女"));
		System.out.println(ts);
	}
}

输出结果:

[Person [age=15, name=小明, sex=男], Person [age=17, name=小红, sex=女]]

当字符串相同情况

说明:这里在比较值相等的情况,是添加无效的,有人会说map中为什么可以覆盖,那是因为key值并没有变,而是修改了Value值,而在TreeSet中的值都是key值,value为null值,所以TreeSet中出现相同值是不会报错也不会被替换的

如下测试:

package TreeSetTest;

import java.util.Comparator;
import java.util.Date;
import java.util.TreeSet;

public class Test1{
	public static void main(String[] args) {
		TreeSet<Person> ts=new TreeSet<Person>(new Comparator<Person>(){
			@Override
			public int compare(Person o1, Person o2) {
				Person p1=(Person)o1;
				Person p2=(Person)o2;
				return p1.getName().compareTo(p2.getName());
			}
		});
		ts.add(new Person(15,"小明","男"));
		ts.add(new Person(17,"小明","女"));
		System.out.println(ts);
	}
}

输出结果:

[Person [age=15, name=小明, sex=男]]

这里我们需要注意的是,数值是可以直接减的,但是字符串等类类型通过调用自然排序的方法是最简单有效的,至于是升序还是降序,前面加负号即可

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值