百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

深入理解Java中Comparable和Comparator排序

toyiye 2024-05-25 20:11 17 浏览 0 评论

本文有牛旦教育原创,头条首发,转载注明来源。

如何为需要的排序算法选择正确的接口?通过本文的分析讲解,我们会找到答案参考答案。

程序员经常需要将数据库中的元素排序为集合、数组或映射。在Java中,我们可以实现任何类型的排序算法。使用Comparable接口和compareTo()方法,我们可以使用字母顺序、字符串长度、反字母顺序或数字进行排序。Comparator接口允许我们以更灵活的方式做同样的事情。

概括而言,无论我们想做什么,只需要知道如何为给定的接口和类型实现正确的排序逻辑就可以了

1.Comparable

1.1自定义对象列表排序

在这个示例中,我们使用实现了Comparable接口的POJO类,名为Figure,并在泛型类型中使用Figure:

public class Figure implements Comparable<Figure> {
 String name ;
 
 Figure(String name) {
 this.name = name;
 }
 @Override
 public int compareTo(Figure figure) {
 return this.name.compareTo(figure.name);
 }
	@Override
 public String toString() {
 return name ;
 }
}
class FigureCharacter implements Comparable<FigureCharacter>{
 String name;
 
 FigureCharacter(String name) {
 this.name = name;
 }
 @Override
 public int compareTo(FigureCharacter fc) {
 
 return this.name.compareTo(fc.name);
 }
 @Override
 public String toString() {
 return name ;
 } 
 
}
package com.nd.tutorial.lesson001;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class FigureSorting {
 public static void main(String[] args) {
 List<FigureCharacter> figures = new ArrayList<>();
 figures.add(new FigureCharacter ("Nazha "));
 figures.add(new FigureCharacter ("Wukong "));
 figures.add(new FigureCharacter ("Guanyin "));
 figures.add(new FigureCharacter ("Rulai "));
 Collections.sort(figures);
 figures.stream().map(s -> s.name).forEach(System.out::print);
 Collections.reverse(figures);
 figures.stream().forEach(System.out::print);
 }
 
}

注意,我们已经覆盖了compareTo()方法,并传入了另一个Figure对象。我们还覆盖了toString()方法,只是为了使示例更容易阅读。

toString方法显示了来自对象的信息。当我们打印对象时,输出为toString()中实现的任何内容。

1.2compareTo()方法

compareTo()方法将给定的对象或当前实例与指定的对象进行比较,以确定对象的顺序。下面快速看一下compareTo()是如何工作的:

我们对实现Comparable的类使用sort()方法排序。如果我们试图传递一个没有实现Comparable的Figure,我们将收到一个编译错误。

sort()方法通过传递任何可比较的对象来使用多态性。然后对象将按预期排序。

前面代码的输出为:

Guanyin Nazha Rulai Wukong

如果我们想反序显示,只要把Collections.sort(figures)换成Collections. reverse(figures)即可,显示结果如下:

Wukong Rulai Nazha Guanyin

1.3排序Java数组

在Java中,只要数组类型实现了Comparable接口,我们就可以用任何类型对数组进行排序。例如:

package com.nd.tutorial.lesson001;
import java.util.Arrays;
public class ArraySorting {
 public static void main(String[] args) {
 
 int[] mps = new int[] { 9, 8, 7, 6, 1 };
 Arrays.sort(mps);
 Arrays.stream(mps).forEach(System.out::print);
 
 Figure[] figures = new Figure[] { new Figure("Nezha"), new Figure("Rulai") };
 Arrays.sort(figures);
 System.out.println();
 Arrays.stream(figures).forEach(System.out::println);
 }
}

第一个sort()调用,数组排序成如下形式:

16789

第二个sort()调用,被排序成如下形式:

Nezha

Rulai

请记住,定制对象必须实现Comparable才能进行排序,即使作为数组也是如此。

1.4没有实现Comparable能排序吗?

如果Figure对象没有实现Comparable,IDE将提示语法错误,可以自己试试,类是如下::

Exception in thread "main" java.lang.Error: Unresolved compilation problem:

The method sort(List<T>) in the type Collections is not applicable for the arguments (List<FigureCharacter>)

at nd.tutorial/com.nd.tutorial.lesson001.FigureSorting.main(FigureSorting.java:15)

这个日志有点混乱,但是不用担心。只要记住,对于没有实现Comparable接口的任何排序对象,那样去排序都会出错。

1.5用TreeMap排序Map

Java API包含了许多用来辅助排序的类,包括TreeMap。在下面的示例中,我们使用TreeMap将键排序到Map中。

package com.nd.tutorial.lesson001;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
 public static void main(String[] args) {
 Map<FigureCharacter, String> figureCharacter = new TreeMap<>();
 figureCharacter.put(new FigureCharacter ("Nezha"), "Circle");
 figureCharacter.put(new FigureCharacter ("Rulai"), "FiveMountain");
 figureCharacter.put(new FigureCharacter ("Wukong"), "goldencudgel");
 figureCharacter.put(new FigureCharacter ("Guanyin"), "Bottle");
 System.out.println(figureCharacter);
 }
}

TreeMap使用了实现Comparable接口的compareTo()方法。结果Map中的每个元素都按其键排序。在这种情况下,输出为:

{Guanyin=Bottle, Nezha=Circle, Rulai=FiveMountain, Wukong=goldencudgel}

但是请记住:如果对象没有实现Comparable,将错误。

1.6用TreeSet排序Set

Set接口负责存储唯一的值(不重复的元素值),但当我们使用TreeSet实现时,插入的元素将在我们添加它们时自动排序:

package com.nd.tutorial.lesson001;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetExample {
 public static void main(String[] args) {
 Set<FigureCharacter> figureCharacters = new TreeSet<>();
 figureCharacters.add(new FigureCharacter ("Wukong"));
 figureCharacters.add(new FigureCharacter ("Guanyin"));
 figureCharacters.add(new FigureCharacter ("Nazha"));
 figureCharacters.add(new FigureCharacter ("Rulai"));
 System.out.println(figureCharacters);
 }
}

代码输出结果为:

[Guanyin, Nazha, Rulai, Wukong]

同样,如果我们使用一个非Comparable的对象(没实现Comparable接口),将抛出一个错误。

2.用Comparator排序

如果我们不想使用POJO类中的相同compareTo()方法怎么办?我们是否可以覆盖Comparable方法来使用不同的逻辑呢?看下面例子:

 public class BadExampleOfComparable {
 public static void main(String[] args) {
 List<FigureCharacter> characters = new ArrayList<>();
 FigureCharacter guanyin = new FigureCharacter ("Guanyin") {
 @Override
 public int compareTo(FigureCharacter figure) {
 return this.name.length() - (figure.name.length());
 }
 };
 FigureCharacter nezha = new FigureCharacter ("Nazha") {
 @Override
 public int compareTo(FigureCharacter figure) {
 return this.name.length() - (figure.name.length());
 }
 };
 characters.add(guanyin);
 characters.add(nezha);
 Collections.sort(characters);
 System.out.println(characters);
 }
}

正如您所见,这段代码很复杂,包含很多重复。对于相同的逻辑,我们必须两次重写compareTo()方法。如果有更多的元素,我们将不得不为每个对象复制逻辑。

幸运的是,我们有Comparator接口,它允许我们从Java类中分离compareTo()逻辑。使用Comparator重写上面的例子:

 public class GoodExampleOfComparator {
 public static void main(String[] args) {
 List<FigureCharacter> characters = new ArrayList<>();
 FigureCharacter guanyin = new FigureCharacter ("Guanyin");
 FigureCharacter nezha = new FigureCharacter ("Nezha");
 characters.add(guanyin);
 characters.add(nezha);
 Collections.sort(characters, (Comparator.< FigureCharacter >
 comparingInt(character1 -> character1.name.length())
 .thenComparingInt(character2 -> character2.name.length())));
 System.out.println(characters);
 }
}

这些例子演示了Comparable和Comparator之间的主要区别。

当使用Comparable时,对象只有一个默认比较。当您需要处理现有的compareTo()时,或者当您需要以更灵活的方式使用特定的逻辑时,请使用Comparator。Comparator从对象中分离排序逻辑,并在sort()方法中包含compareTo()逻辑。

2.1匿名内部类方式使用Comparator

在下面示例中,我们使用一个匿名内部类来比较对象的值。在本例中,匿名内部类是实现Comparator的任何类。使用它意味着我们不必实例化实现接口的已命名类;相反,我们在匿名内部类中实现compareTo()方法。

 public class MarvelComparator {
 public static void main(String[] args) {
 List<String> marvelHeroes = new ArrayList<>();
 marvelHeroes.add("SpiderMan ");
 marvelHeroes.add("Wolverine ");
 marvelHeroes.add("Xavier ");
 marvelHeroes.add("Cyclops ");
 Collections.sort(marvelHeroes, new Comparator<String>() {
 @Override
 public int compare(String hero1, String hero2) {
 return hero1.compareTo(hero2);
 }
 });
 Collections.sort(marvelHeroes, (m1, m2) -> m1.compareTo(m2));
 Collections.sort(marvelHeroes, Comparator.naturalOrder());
 marvelHeroes.forEach(System.out::print);
 }
}

关于匿名内部类:

匿名内部类就是任何名称无关紧要的类,它实现了我们声明的接口。在这个例子中,new Comparator实际上是一个没有名称的类的实例化,它用我们想要的逻辑实现了这个方法。

2.2lambda表达式方式用Comparator

匿名内部类比较冗长,这可能会导致代码中出现问题。在Comparator接口中,我们可以使用lambda表达式来简化代码,使代码更容易阅读。如下改变,即把:

Collections.sort(marvel, new Comparator<String>() {
 @Override
 public int compare(String hero1, String hero2) {
 return hero1.compareTo(hero2);
 }
 });

改成这样:

Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));

代码少了很多,但结果一样。

输出结果如下:

Cyclops SpiderMan Wolverine Xavier 

我们还可把代码改的更简单,把:

Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));

改成:

Collections.sort(marvel, Comparator.naturalOrder());

3.核心Java类是Comparable的吗?

许多核心Java类和对象实现了Comparable接口,这意味着我们不必为这些类实现compareTo()逻辑。下面是一些常见的例子:

String

public final class String
 implements java.io.Serializable, Comparable<String>, CharSequence { ...

Integer

public final class Integer extends Number implements Comparable<Integer> { …

Double

public final class Double extends Number implements Comparable<Double> {...

还有很多其他的。推荐你去探索Java核心类,以了解它们的重要模式和概念。

接收Comparable的挑战

通过理解以下代码的输出来检验所学内容掌握如何。记住,如果你仅仅通过学习就能自己解决这个挑战,你会学得很好。也可运行下面程序进一步理解吸收这些概念。

 public class SortComparableChallenge {
 public static void main(String[] args) {
 Set<Figure> set = new TreeSet<>();
 set.add(new Figure ("Honghaier"));
 set.add(new Figure ("Mowangniu"));
 set.add(new Figure ("Laoshujing"));
 set.add(new Figure ("Baibianjun"));
 set.add(new Figure ("Meixian"));
 List< Figure > list = new ArrayList<>();
 list.addAll(set);
 Collections.reverse(list);
 list.forEach(System.out::println);
 }
 static class Figure implements Comparable< Figure > {
 String name;
 public Figure (String name) {
 this.name = name;
 }
 public int compareTo(Figure newday) {
 return newday.name.compareTo(this.name);
 }
 public String toString() {
 return this.name;
 }
 }
}

上面程序代码的输出是哪一个:

A.

Baibianjun

Honghaier

Laoshujing

Meixian

Mowangniu

B.

Meixian

Baibianjun

Honghaier

Laoshujing

Mowangniu

C.

Mowangniu

Meixian

Laoshujing

Honghaier

Baibianjun

D.

不知道

TreeSet and reverse()

如代码所示,注意到的第一件事是我们使用了一个TreeSet,因此元素将自动排序。第二件事是比较的顺序是颠倒的,所以排序的顺序是颠倒的。

当我们第一次向列表中添加元素时,TreeSet会自动将它们排序为:

Mowangniu, Meixian, Laoshujing, Honghaier, Baibianjun

然后我们使用reverse()方法,它颠倒元素的顺序。所以最终的输出是:

Baibianjun

Honghaier

Laoshujing

Meixian

Mowangniu

使用Comparable常见错误

ü 试图在sort()方法中对不可比较的对象排序。

ü 在同一对象中对不同的排序策略使用Comparable。

ü 在compareTo()方法中反转比较,以便排序将按相反的顺序进行,如下所示:

正常排序

public int compareTo(Figure figure) {
this.name.compareTo(figure.name);
}

反转排序

public int compareTo(Simpson simpson) {
simpson.name.compareTo(this.name);
}

ü 在TreeSet或TreeMap对象中添加非可比对象(没实现Comparable的任何对象)。

小结

关于使用Java排序,需要记住的:

? 当比较是给定类的标准比较时,使用Comparable。

? 当您需要更多的灵活性时,使用Comparator。

? 可以将lambdas与Comparator一起使用。

? 许多Java核心类实现了Comparable。

? 对Map或Set排序时,使用TreeMap或TreeSet。

? compareTo()方法同时适用于Comparable和Comparator。

? 如果一个对象大于另一个对象,compareTo()方法将返回一个正数,如果小则返回一个负数,如果两个对象相同,则返回零。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码