Страницы

Поиск по вопросам

вторник, 2 октября 2018 г.

Robustness (прочность) в Java

Добрый день! Недавно попалась весьма интересная задачка на Java. Которой хотел бы поделиться. Дан следующий класс public class RobustCheck {
private final char first; private final char second;
public RobustCheck(char first, char second) { this.first = first; this.second = second; }
public boolean equals (RobustCheck b) { return this.first == b.first && this.second == b.second; }
public int hashCode() { return 31 * first + second; }
public static void main(String[] args) { Set s = new HashSet(); for(int i=0; i<10; i++) for(char ch='a'; ch<='z'; ch++) { s.add(new RobustCheck(ch, ch)); } System.out.println(s.size()); } } И вопросы: Почему программа возвращает размер 260 вместо 26? Как ее исправить? Чего не хватает в данной программе, чтобы она указала ошибку на этапе компиляции?


Ответ

Задача на знание краеугольных камней языка Java и недостатков в его дизайне. В данном случае метод RobustCheck#equals перегружает (overloading) метод Object#equals, в результате чего при сравнении объектов RobustCheck используется метод Object#equals, который вместо сравнения на эквивалентность сравнивает на идентичность. Все 260 созданных в программе объектов RobustCheck не являются идентичными, т.к. физически являются отдельными объектами, размещенными в своих собственных областях памяти. Поэтому методы HashSet "считают" их неравными друг другу и добавляют во множество. В то время как логически неравных объектов RobustCheck только 26 штук, все последующие эквивалентны одному из этих 26-ти. Поэтому нам же необходимо перекрыть (overriding) метод Object#equals, чтобы изменить семантику сравнения. Для этого сигнатура метода RobustCheck#equals должна соответствовать сигнатуре перекрываемого Object#equals; перепишем метод так: public boolean equals (Object b) { if (b instanceof RobustCheck) return this.first == ((RobustCheck)b).first && this.second == ((RobustCheck)b).second; else return false; } Чтобы такие ошибки (когда вместо перекрытия мы по ошибке используем перегрузку) смог заметить компилятор, сообщим ему наши намерения о том, что метод Object#equals мы перекрываем в производном классе RobustCheck, добавив соответствующую аннотацию: @Override public boolean equals (Object b) { if (b instanceof RobustCheck) return this.first == ((RobustCheck)b).first && this.second == ((RobustCheck)b).second; else return false; } Ну вот теперь все должно работать как надо: >java RobustCheck 26 К сожалению, это не исправляет недостатки в дизайне Java. Более интересный вопрос, в дополнение к этой задачке, заключается в том, как вообще избежать всей этой "мумба-юмбы" при определении семантики сравнения двух объектов? В случае с Java ответа, к сожалению, нет. Но можно использовать более продуманные языки. К примеру, Scala, которая самостоятельно определяет корректную семантику сравнения на эквивалентность для неизменяемых (immutable) объектов, избавляя программиста от этой чреватой ошибками задачи: object Main extends App { // Неизменяемые объекты моделируются в Scala с помощью специальных // "case"-классов. case class RobustCheck(first: Char, second: Char)
val s = collection.mutable.HashSet.empty[RobustCheck]
for (i <- 1 to 10) for (c <- 'a' to 'z') s += RobustCheck(c, c)
println(s.size) } Результат ожидаем: >scala Main 26 Без всяких заморочек.

Комментариев нет:

Отправить комментарий