JDK8之Comparator

Comparator

在Java8之前的版本我们应该也使用过关于Comparator吧!Comparator就是一个比较器,比较两个元素的大小。我们在对集合进行排序的时候,就需要一个比较器来对集合中的元素进行比较,才能进行排序。

 * @since 1.2
 */
@FunctionalInterface
public interface Comparator {
}

通过这段源代码就可以发现,Comparator比较器是从Java2就有啦。但是自从JDK8开始,该接口就变为一个函数式接口,并且在JKD8对Comparator进行了增强(增加了一些默认方法和静态方法),既然Comparator成为一个函数式接口,那么该接口中的唯一一个抽象方法是什么呢?

int compare(T o1, T o2);

该方法就是Comparator比较器的核心:比较。该方法接收两个参数,并返回一个整形。如果返回的数据大于0就证明o1比o2大,返回0就证明o1和o2相等,返回小于0就证明o1比o2小。

  • 例子1:创建一个字符串集合,并对集合中的元素进行排序。
public class ComparatorTest {
    public static void main(String[] args) {
        List list = Arrays.asList("hello","world","nihao","wohao","welcome");
        list.sort((item1,item2)->item2.length()-item1.length());
        System.out.println(list);
    }
}

通过这个示例代码可以知道,我们是对集合中的元素按元素的长度进行降序排列。我们通过Comparator进行改造:

public class ComparatorTest {
    public static void main(String[] args) {
        List list = Arrays.asList("hello","world","nihao","wohao","welcome");
        list.sort(Comparator.comparingInt(String::length).reversed());
        System.out.println(list);
    }
}

改成该种方式的代码也能完成相同的功能。那么我们将上面的代码中的方法引用改成lambda表达式,看能出现什么情况呢?

public class ComparatorTest {
    public static void main(String[] args) {
        List list = Arrays.asList("hello","world","nihao","wohao","welcome");
        list.sort(Comparator.comparingInt(item->item.length()).reversed());
        System.out.println(list);
    }
}

这行代码在编辑器进行编译的时候,会发现编译出错Cannot resolve method 'length()',也就是说item并没有length方法,那么为什么没有该方法呢?通过查看item类型发现,编辑器把item当成了Object类型的啦。那么为什么Java的类型推断会把item推断为Object类型呢?

public static  Comparator comparingInt(ToIntFunction keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator & Serializable)
            (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}

通过我们查看Comparator接口中的comparingInt静态方法,发现该方法是一个泛型方法,T就是需要比较的元素类型,然而我们可以发现ToIntFunction接收的参数类型也带有一个泛型,该泛型就是输入元素的类型,可以发现输入元素的类型定义了一个下界,证明输入元素类型的下界是T类型。那么为什么类型推断不能通过上下文推断出ToIntFunction输入参数是String类型,而将输入元素向上转换为Object类型呢?其实是这样的,我们在调用comparingInt方法后,还调用了reversed方法:

default Comparator reversed() {
        return Collections.reverseOrder(this);
}

通过reversed方法可以发现,该默认方法也是一个泛型方法,当中的泛型就是T类型,也就是集合中的待比较元素的类型。就可以知道reversed方法返回的Comparator类型相对上下文会比ToIntFunction相对较远,所以,编辑器不能判断出确定的类型。就会把T类型向上转型为Object最大的类型。 因为,编辑器不能推断出item的类型,我们在编程的时候,可以指定出item的类型:

public class ComparatorTest {
    public static void main(String[] args) {
        List list = Arrays.asList("hello","world","nihao","wohao","welcome");
        list.sort(Comparator.comparingInt((String item)->item.length()).reversed());
        System.out.println(list);
    }
}

那么问题来了,为什么comparingInt方法为什么会把待比较的元素类型设置为一个下界呢?其实JDK的设计思路是这样的,有些时候,可能我们比较的时候,不一定会按照当前类的特性进行比较,有可能,我们会按照待比较元素类型的父类或者接口中的一些特性进行比较。

default void sort(Comparator c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
}

通过这段代码,我们可以发现,JDK的设计者是向开发者可以使用比较器的时候,可以使用比较元素的父类或者接口进行比较,比较完了过后,把比较后的元素给强制的转换为比较元素的类型。

  • 例子2:首先对集合中的元素按照元素的长度进行升序排列,如果相同,就按照元素的ASCI码进行比较。
public class ComparatorTest {
    public static void main(String[] args) {
        List list = Arrays.asList("hello","world","nihao","wohao","welcome");
        list.sort(Comparator.comparingInt(String::length).thenComparing(String.CASE_INSENSITIVE_ORDER));
        System.out.println(list);
    }
}

这里给出一个注意点:String类给我们直接提供了一个不区分大小写的比较实现CASE_INSENSITIVE_ORDER

public static final Comparator CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator, java.io.Serializable {
        // use serialVersionUID from JDK 1.2.2 for interoperability
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }

这段代码就是String提供的一个对String类型不区分大小写进行比较的一个私有的比较器。

在这个例子中,我们使用了thenComparing方法,该方法,就是如果前面的比较结果不为0,就直接返回前面比较的结果,如果比较结果为0,就调用该方法传入的比较器进行二次比较。

default Comparator thenComparing(Comparator other) {
        Objects.requireNonNull(other);
        return (Comparator & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
}

在上面的例子中,我们使用String提供的一个不区分大小写的私有比较器CASE_INSENSITIVE_ORDER,那么我们不使用String提供的,该怎么写呢?

public class ComparatorTest {
    public static void main(String[] args) {
        List list = Arrays.asList("hello","world","nihao","wohao","welcome");
        list.sort(Comparator.comparingInt(String::length).thenComparing((item1,item2)->item1.toLowerCase().compareTo(item2.toLowerCase())));
        System.out.println(list);
    }
}

也可是这样:

public class ComparatorTest {
    public static void main(String[] args) {
        List list = Arrays.asList("hello","world","nihao","wohao","welcome");
        list.sort(Comparator.comparingInt(String::length).thenComparing(Comparator.comparing(String::toLowerCase)));
        System.out.println(list);
    }
}

你可能感兴趣的:(Javase,Java8新特性分析)