小練習:
Измените предыдущий пример HeavenlyBody так, чтобы HeavenlyBody
В классе также есть поле bodyType. В этом поле будет храниться
тип Небесного Тела (например, ЗВЕЗДА, ПЛАНЕТА, ЛУНА и т. д.).
Вы можете включить столько типов, сколько хотите, но они должны поддерживаться на
минимум ПЛАНЕТЫ и ЛУНЫ.
Для каждого из поддерживаемых типов создайте подкласс класса HeavenlyBody.
чтобы ваша программа могла создавать объекты соответствующего типа.
Хотя астрономы могут содрогнуться, наши солнечные системы
разрешить двум телам иметь одно и то же имя, если они не являются
тот же тип тела: так что вы могли бы иметь звезду по имени «БетаМинор» и
например, астероид, также называемый «БетаМинор».
Подсказка: это намного проще реализовать для набора, чем для карты.
потому что карте понадобится ключ, который использует оба поля.
Существует ограничение, что единственные спутники, которые могут быть у планет, должны
быть лунами. Однако даже если вы не реализуете тип STAR, ваша программа
не должны препятствовать тому, чтобы один из них был добавлен в будущем (и спутники STAR
может быть почти любым видом HeavenlyBody).
Тестовые случаи:
1. Планеты и луны, которые мы добавили в предыдущем видео, должны появиться в
коллекции solarSystem и в наборах лун для соответствующих планет.
2. a.equals(b) должен возвращать тот же результат, что и b.equals(a) — equals симметричен.
3. Попытка добавить дубликат в набор не должна приводить к изменению набора (поэтому
исходное значение не заменяется новым).
4. Попытка добавить дубликат на карту приводит к замене оригинала
по новому объекту.
5. В один и тот же набор можно добавить два тела с одинаковым названием, но разными обозначениями.
6. На одну карту можно добавить два тела с одинаковым названием, но разными обозначениями,
и могут быть извлечены из карты.
參考答案:
HeavenlyBody.java
package SetChallenge;
import java.util.HashSet; import java.util.Set;
public abstract class HeavenlyBody { private final Key key; private final double orbitalPeriod; private final Set<HeavenlyBody> satellites;
public static Key makeKey(String name, BodyTypes bodyType) { return new Key(name, bodyType); }
public HeavenlyBody(String name, double orbitalPeriod, BodyTypes bodyType) { this.orbitalPeriod = orbitalPeriod; this.satellites = new HashSet<>(); this.key = new Key(name, bodyType);
}
public double getOrbitalPeriod() { return orbitalPeriod; }
public Key getKey() { return key; }
public boolean addSatellite(HeavenlyBody satellite) { return this.satellites.add(satellite); }
public Set<HeavenlyBody> getSatellites() { return new HashSet<>(this.satellites); }
@Override public final boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof HeavenlyBody) { Key objKey = ((HeavenlyBody) obj).getKey(); return this.key.equals(objKey); } return false; }
@Override public final int hashCode() { return this.key.hashCode(); }
@Override public String toString() { return getKey().getName() + ": " + getKey().getBodyType() + ", " + getOrbitalPeriod(); }
enum BodyTypes { PLANET, DWARF_PLANET, MOON }
public static final class Key { private String name; private BodyTypes bodyType;
private Key(String name, BodyTypes bodyType) { this.name = name; this.bodyType = bodyType; }
public String getName() { return name; }
public BodyTypes getBodyType() { return bodyType; }
@Override public boolean equals(Object obj) { if (this == obj) { return true; } if ((obj == null) || (obj.getClass() != this.getClass())) { System.out.println("obj is null or not equal!"); return false; }
String objName = ((HeavenlyBody.Key) obj).getName(); BodyTypes objBodyType = ((HeavenlyBody.Key) obj).getBodyType(); return this.name.equals(objName) && this.bodyType.equals(objBodyType); }
@Override public int hashCode() { return this.name.hashCode() + this.bodyType.hashCode() + 37; }
@Override public String toString() { return name + ": " + bodyType; } } }
class Planet extends HeavenlyBody { public Planet(String name, double orbitalPeriod) { super(name, orbitalPeriod, BodyTypes.PLANET); }
@Override public boolean addSatellite(HeavenlyBody moon) { if (moon.getKey().getBodyType() == BodyTypes.MOON) { return super.addSatellite(moon); } return false;
} }
class DwarfPlanet extends HeavenlyBody { public DwarfPlanet(String name, double orbitalPeriod) { super(name, orbitalPeriod, BodyTypes.DWARF_PLANET); } }
class Moon extends HeavenlyBody { public Moon(String name, double orbitalPeriod) { super(name, orbitalPeriod, BodyTypes.MOON); } }
Причина в том, что на карте не может быть повторяющихся ключей. Шестое требование этого задания – разрешить добавление тел с одинаковыми именами, но разных типов (типы – планета, луна, карликовая планета и т. п.).
Как вы, возможно, заметили, наша карта содержит только строковые ключи, поэтому попытка сохранить тела с одинаковым именем и разными типами завершится неудачей, поскольку у вас не может быть дубликатов ключей. Я пытаюсь сказать, что, допустим, у вас есть 2 ключа: Плутон (тип Планета) и Плутон (тип DwarfPlanet). Например, если вы написали [map.put("Плутон", new Planet("Плутон", 90.8);] то вы не сможете сделать [map.put("Плутон", new DwarfPlanet("Плутон" , 64.6));] потому что они оба имеют один и тот же строковый ключ «Плутон».
Поэтому вам нужно найти способ обойти это, и он хранит объекты, которые содержат как имя, так и тип в качестве ключей. Объект Key содержит name и bodyType, поэтому мы сможем добавить 2 ключа, которые содержат одно и то же имя (Плутон), но разного типа (один — Planet, а другой — DwarfPlanet). Причиной этого является метод equals() внутри класса Key, метод сравнивает имя 2-х объектов, затем сравнивает bodyType 2-х объектов, и если какие-либо из них отличаются, он возвращает false. Когда вы добавляете ключ на карту, он проверяет, есть ли уже ключ на карте, который вернет true при сравнении с ключом, который вы хотите добавить, но поскольку вы добавляете 2 элемента с тем же именем, но разные bodyType сравнение вернет false, что позволит вам добавить их обоих на карту.
Прошу прощения за многословное и длинное объяснение, надеюсь, оно вам поможет!
public static final class Key {
private String name;
private BodyTypes bodyType;
private Key(String name, BodyTypes bodyType) {
this.name = name;
this.bodyType = bodyType;
}
public String getName() {
return name;
}
public BodyTypes getBodyType() {
return bodyType;
}
@Override
public int hashCode() {
return this.name.hashCode() + 57 + this.bodyType.hashCode();
}
@Override
public boolean equals(Object obj) {
Key key = (Key) obj;
if(this.name.equals(key.getName())) {
return(this.bodyType == key.getBodyType());
}
return false;
}
@Override
public String toString() {
return this.name + ": " + this.bodyType;
}
}
В этом случае ключевой класс имеет 2 поля, имя и тип тела, поэтому, когда 2, хэш-код и равенство зависят от этих 2 полей.
Если вы решите использовать строку, вы можете либо использовать только имя, либо имя + bodyType.toString(), а второй вариант — не очень хорошая идея, так как вам нужно создать еще одну строку и поэкспериментировать с конкатенацией. беспорядок быстро, почему?
Что ж, если позже вы решите, что вам нужно еще одно поле, преобразование всего в строку не рекомендуется. Эту логику проще разделить на классы, подобные этому классу Key, чтобы вы могли решить, какие поля вы хотите использовать для ключей и хеширования.
В oop у вас есть классы, поэтому класс, который, например, используется в качестве ключа, является хорошей практикой, класс, который передает данные с сервера на клиент, является хорошей практикой и т. д.