JavaRush /Java 博客 /Random-ZH /Java 中的比较器
Viacheslav
第 3 级

Java 中的比较器

已在 Random-ZH 群组中发布
只有懒人才没有写过Java中的Comparator和比较。我并不懒惰 - 所以我请你喜欢并喜欢另一种变体。我希望这不会是多余的。是的,这篇文章就是这个问题的答案:“你能凭记忆编写一个比较器吗?” 我希望读完这篇文章后,每个人都能凭记忆编写一个比较器。
Java 中的比较器 - 1
简介 Java 被认为是一种面向对象的语言。因此,在 Java 中,对对象进行操作是很常见的。但迟早会出现根据某种原则比较对象的任务。因此,假设:我们有一些消息,由 Message 类描述:
public static class Message {
    private String message;
    private int id;

    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
让我们将此类添加到Tutorialspoint java编译器中。我们还记得添加导入:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
在 main 方法中,我们将创建几条消息:
public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
我们想一下,如果我们要比较的话,我们应该怎么做呢?例如,我们想按 id 排序。为了创建顺序,您需要以某种方式比较对象,以便了解哪个对象是前一个(即较小)和哪个对象是下一个(即较大)。让我们从java.lang.Object这样的类开始。众所周知,所有类都隐式继承自该 Object 类。这是合乎逻辑的,因为 这本质上表达了“一切都是对象”的概念,并为所有类提供了共同的行为。这个类定义每个类有两个方法: → hashCode hashCode 方法返回对象的一些数字(int)表示形式作为类的实例。这是什么意思?这意味着如果您创建了一个类的两个不同实例,那么由于实例不同,它们的 hashCode 应该不同。这就是该方法的描述中所说的:“尽可能合理实用,由类 Object 定义的 hashCode 方法确实为不同的对象返回不同的整数”也就是说,如果这是两个不同的实例,那么它们应该具有不同的值。哈希码。也就是说,这个方法不适合我们的比较。→ equals equals 方法回答“对象是否相等”的问题并返回一个布尔值。该方法有默认代码:
public boolean equals(Object obj) {
    return (this == obj);
}
也就是说,在不重写对象上的此方法的情况下,此方法本质上说明对对象的引用是否匹配。这不适合我们的消息,因为我们对对象的链接不感兴趣,我们对消息 id 感兴趣。即使我们重写 equals 方法,我们得到的最大值也是:“它们相等”或“它们不相等”。但这还不足以让我们确定顺序。

Java中的比较器和Comparable

什么适合我们?如果我们在翻译器中将“compare”这个词翻译成英文,我们就会得到“compare”的翻译。太好了,那么我们需要一个能够进行比较的人。如果你比较这个compare的话,那么比较的就是Comparator。让我们打开Java Api并找到Comparator。确实,有这样一个接口 - java.util.Comparator java.util.Comparator 和 java.lang.Comparable 正如你所看到的,有这样一个接口。实现它的类说“我正在实现一个用于比较对象的函数”。唯一真正需要记住的是比较器合约,其表达如下:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
现在让我们编写一个比较器。我们需要导入java.util.Comparator。import之后,在main中添加一个方法: Comparator<Message> comparator = new Comparator<Message>(); 自然,这样是不行的,因为 比较器是一个接口。因此,在括号后面我们将添加花括号{ }。在这些括号中,我们将编写方法:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
你甚至不必记住写这个。比较器是执行比较的人,即进行比较。为了回答比较对象的顺序问题,我们返回 int。事实上,仅此而已。简单又容易。从示例中我们可以看到,除了 Comparator 之外,还有另一个接口 - java.lang.Comparable,实现它时我们必须定义compareTo方法。该接口表示“实现接口的类允许比较该类的实例”。例如,Integer 的compareTo 实现如下所示:
(x < y) ? -1 : ((x == y) ? 0 : 1)
如何记住所有这些接口?做什么的?一切都来自英语。Compare——比较,比较的人是Comparator(例如注册员,即注册的人),形容词“比较”是Comparable。那么,“Compare with”不仅可以翻译为compare with,还可以翻译为compare to。这很简单。Java语言是由讲英语的人编写的,在Java中命名一切时,他们只是以英语为指导,并且命名中有某种逻辑。compareTo 方法描述了如何将类的实例与其他实例进行比较。例如,字符串按字典顺序比较,数字按值比较。
Java 中的比较器 - 2
Java 8 带来了一些不错的变化。如果我们仔细观察 Comparator 接口,我们会发现它上面有一个注释@FunctionalInterface。事实上,这个注解是为了提供信息,意味着这个接口是功能性的。这意味着该接口只有 1 个抽象方法,没有实现。这给我们带来了什么?我们现在可以像这样编写比较器代码:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
括号中是我们如何命名变量。Java 本身会看到这一点,因为...... 如果只有一种方法,那么需要什么输入参数、多少个、什么类型就一清二楚了。接下来,我们用箭头表示我们要将它们转移到这部分代码。另外,多亏了Java 8,接口中出现了默认方法——这些是我们实现接口时默认(默认)出现的方法。Comparator接口中有几个这样的,例如:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
还有另一种方法可以使您的代码更清晰。让我们看一下上面的例子,我们在其中描述了我们的比较器。他在做什么?这是相当原始的。它只是获取一个对象并从中提取一些可比较的值。例如,Integer 实现了可比较,因此我们能够对消息 id 值执行compareTo。这个简单的比较器函数也可以这样写:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
也就是说,从字面上看,“我们有一个像这样进行比较的 Comparator:它获取对象,使用 getId() 方法从中获取 Comparable,然后使用compareTo 进行比较。” 不再有可怕的设计。最后,我想指出另一项功能。比较器可以链接在一起。例如:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

应用

比较器声明非常符合逻辑,不是吗?现在我们需要看看如何使用它以及在什么地方使用它。→ Collections.sort (java.util.Collections) 当然,我们可以通过这种方式对集合进行排序。但不是全部,只是列出。这里没有什么不寻常的,因为...... 它是需要通过索引访问元素的列表。这允许第二号元素与第三号元素交换。因此,这种方式排序仅适用于列表:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) 数组排序也很方便。同样,出于通过索引访问元素的相同原因。→ java.util.SortedSet 和 java.util.SortedMap 的后代 我们记得,Set 和 Map 不保证存储记录的顺序。但我们有特殊的实现来保证顺序。如果集合元素没有实现 java.lang.Comparable,那么我们可以将 Comparator 传递给此类集合的构造函数:
Set<Message> msgSet = new TreeSet(comparator);
Stream API 在 Java 8 中出现的 Stream Api 中,比较器允许您简化流元素的工作。例如,我们需要一个从 0 到 999(含)的随机数序列:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
我们可以停下来,但还有更有趣的问题。例如,你需要准备一个Map,其中的key是消息id。同时,我们要对这些键进行排序,使得键按照从小到大的顺序排列。让我们从这段代码开始:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
我们在这里得到的实际上是一个HashMap。据我们所知,它不保证任何订单。因此,我们按 ID 排序的记录根本就乱了。不好。我们必须稍微改变一下我们的收集器:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
代码看起来有点令人毛骨悚然,但由于 TreeMap 的显式实现,问题现在已得到正确解决。您可以在这里阅读有关各个团体的更多信息: 您可以自己创建收集器。您可以在此处阅读更多内容:“在 Java 8 中创建自定义收集器”。阅读此处的讨论很有用:“Java 8 list to map with stream”
Java 中的比较器 - 3
比较器和可比耙子 都很好。但有一个与它们相关的细微差别值得记住。当一个类执行排序时,它会计算出它可以将您的类转换为 Comparable。如果不是这种情况,您将在执行时收到错误。让我们看一个例子:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
这里似乎并没有什么问题。但事实上,在我们的示例中,它会因错误而崩溃: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable 这一切都是因为它尝试对元素进行排序(毕竟它是一个 SortedSet)。但我不能。使用 SortedMap 和 SortedSet 时应该记住这一点。 另外 推荐观看: Yuri Tkach:HashSet 和 TreeSet - Collections #1 - Advanced Java
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION