Все время я думал, что если я использую список типа List<Thing> things = new ArrayList<>(), все элементы в этом списке относятся к типу Thing. Вчера меня научили другому.
Я создал следующие вещи и задаюсь вопросом, почему они такие.
Интерфейс Thing
public interface Thing {
String getType();
String getName();
}
Класс ObjectA
public class ObjectA implements Thing {
private static final String TYPE = "Object A";
private String name;
public ObjectA(String name) {
this.name = name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("ObjectA{");
sb.append("name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
@Override
public String getType() {
return TYPE;
}
@Override
public String getName() {
return name;
}
// equals and hashCode + getter and setter
}
Класс ObjectB
public class ObjectB implements Thing {
private static final String TYPE = "Object B";
private String name;
private int value1;
private String value2;
private boolean value3;
public ObjectB(String name, int value1, String value2, boolean value3) {
this.name = name;
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
}
@Override
public String getType() {
return TYPE;
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("ObjectB{");
sb.append("name='").append(name).append('\'');
sb.append(", value1 = ").append(value1);
sb.append(", value2='").append(value2).append('\'');
sb.append(", value3 = ").append(value3);
sb.append('}');
return sb.toString();
}
// equals and hashCode + getter and setter
}
Метод main
public static void main(String[] args) {
final List<Thing> things = new ArrayList<>();
final ObjectA objA = new ObjectA("Thing 1");
final ObjectB objB = new ObjectB("Thing 2", 123, "extra", true);
things.add(objA);
things.add(objB);
// The List doesn't contain Thing entities, it contains ObjectA and ObjectB entities
System.out.println(things);
for(final Thing thing : things) {
if (thing instanceof ObjectA) {
System.out.println("Found Object A: " + thing);
final ObjectA object = (ObjectA) thing;
}
if (thing instanceof ObjectB) {
System.out.println("Found Object B: " + thing);
}
}
}
Результатом этого метода является:
[ObjectA{name='Thing 1'}, ObjectB{name='Thing 2', value1=123, value2='extra', value3=true}]
Итак, я предполагаю, что у меня есть объекты ObjectA и объекты ObjectB в моем List<Thing>.
Вопрос: Может ли кто-нибудь предоставить ссылку (или несколько ключевых слов, которые можно использовать для поиска), которые объясняют это поведение, или могут объяснить это мне?
дополнительный вопрос: Я начал фильтровать этот List<Thing> с помощью instanceof, но я прочитал instanceof, и кастинг - плохая практика (например, плохой дизайн модели). Есть ли «хороший» способ отфильтровать этот список для всех типов ObjectA, чтобы выполнять только с этими объектами некоторые операции?
Вам не нужна фильтрация. Если вы не хотите, чтобы ObjectA и ObjectB в одной коллекции, не помещайте их туда. Если вы все же поместили их туда, убедитесь, что их можно использовать, не беспокоясь о том, какая именно реализация, т.е. просто считайте их Things.




Вам следует избегать instanceof check в дополнительном примере вопроса. При работе с элементами списка должно быть достаточно наличия доступных методов интерфейса. Если вам нужно что-то сделать только с ObjectA или ObjectB, я предлагаю использовать другой список только с ObjectA или ObjectB. Например, вы можете определить разные методы для выполнения конкретной работы Thing и конкретной задачи ObjectB:
public void processThings(List<Thing> things) {
for(final Thing thing : things) {
// we work only with methods that provided by interface Thing
System.out.println(thing.getType());
System.out.println(thing.getName());
}
}
public void processObjectsB(List<ObjectB> objectsB) {
// here we do some specific things with only B objects,
// assuming class ObjectB has an additional method doSomeSpecificB()
for(final ObjectB objectB : objectsB) {
objectB.doSomeSpecificB();
}
}
Не могли бы вы добавить к своему ответу пример исходного кода?
Can someone provide a link (or some keywords which can be used for searching), which explain this behavior, or can explain it to me?
Такое поведение называется «полиморфизмом». По сути, поскольку ObjectA и ObjectB реализуют Thing, экземпляры ObjectA и ObjectB могут использоваться как объект Thing. В вашем коде вы добавили их в список, который может содержать объекты Thing.
Обратите внимание, что даже если эти объекты теперь относятся к (во время компиляции) типу Thing, во время выполнения они все равно знают, что они собой представляют. Когда вы вызываете для них toString, будут вызываться соответствующие переопределенные методы toString в ObjectA и ObjectB. Это как если бы Thing «трансформируется» в ObjectA или ObjectB.
Is the are "good" way to filter this List for all Types of ObjectA to perform only on these objects some operations?
Причина, по которой люди говорят, что это плохая практика, заключается в том, что если вы хотите делать разные вещи в зависимости от того, является ли объект ObjectA или ObjectB, почему вы заставили их реализовать Thing и составить список Thing для их хранения? Вы могли просто использовать List<Object>. Реальным преимуществом использования List<Thing> является то, что вы избегать знаете, какие фактические объекты там находятся, когда вы работаете со списком. Все, что вам известно, это то, что элементы внутри списка реализуют Thing, и вы можете вызывать методы, объявленные в Thing.
Поэтому, если вам нужно отфильтровать список, чтобы разделить два типа, вы могли бы просто создать два списка для их хранения в первую очередь. Один для ObjectA и один для ObjectB. Очевидно, это не всегда возможно, особенно если список поступает откуда-то еще (например, из внешней библиотеки). В этом случае ваш текущий код в порядке.
У меня есть примечание к вашему предложению: «Обратите внимание, что даже если эти объекты сейчас относятся к (во время компиляции) типу Thing ...». Если я сделаю следующее в Idea IntelliJ Thing thing1 = new ObjectB("Thing 2", 123, "extra", true);, у меня будет доступ ко всем методам, таким как thing1.getValue3(). Таким образом, кажется, что даже во время компиляции это не Thing. Это ObjectB, или мне что-то не хватает?
@ SleepyX667 В этом случае у вас не будет доступа к getValue3. Вы бросили это или что-нибудь?
'@Sweeper' Мне очень жаль. Это моя ошибка. У меня есть список методов в Dropbox (так что я предполагаю, что у меня есть к ним доступ), но когда я нажимаю Enter, IntelliJ выполняет приведение.
things - это List<Thing>. Это означает, что во время компиляции Java гарантирует, что любой объект, который вы записываете в thingsявляется, будет Thing. Поскольку ObjectA и ObjectB реализуют Thing, фактический выполнение любого члена things может быть ObjectA или ObjectB. Это по дизайну, и эта функция называется полиморфизм: объекты разных классов имеют общий интерфейс и могут быть доступны через этот интерфейс независимо от их фактического типа. Например, вы можете использовать:
for(final Thing thing : things) {
System.stdout.println("Found a " + thing.getType() + " named " + thing.getName());
}
Использование instanceof и приведение типов не обязательно является плохой практикой и может иметь правильный вариант использования. Но часто это намек на то, что иерархия классов и интерфейсов не была правильно спроектирована. В идеале, если вам нужно обработать Thing, вам не следует задумываться о его фактическом классе: у вас есть Thing, и использования методов Thing должно быть достаточно.
В этом смысле instanceof находится на том же уровне, что и отражение: это инструмент низкого уровня, который позволяет увидеть, что скрыто под капотом. И всякий раз, когда вы его используете, вы должны спросить вас, может ли быть достаточно полиморфизма.
У меня есть огород, в котором выращивают картофель, морковь и брокколи. У меня очень строгое правило - я не буду сажать в саду ничего, что нельзя есть. Так что никакого ядовитого плюща здесь!
Итак, это Garden<Edible> - все, что я сажаю в саду, должно быть съедобным.
Теперь class Potato implements Edible означает, что каждый картофель съедобен. Но это также означает, что я могу посадить картофель в своем саду. Точно так же class Carrot implements Edible - вся морковь съедобна, и мне разрешено сажать морковь.
Темная ночь, и я голоден. Я выхожу в свой сад и кладу руку на что-то в саду. Я не вижу, что это, но знаю, что все в моем саду съедобно. Я вытаскиваю его из сада и беру внутрь, чтобы приготовить и поесть. Неважно, что я схватил - я знаю, что смогу съесть.
Потому что это Garden<Edible>. Он может содержать или не содержать объекты Potato. Он может содержать или не содержать объекты Broccoli. Он не содержит объектов PoisonIvy.
Теперь переведите все это на свой пример. У вас есть class ObjectA implements Thing - это означает, что каждый ObjectA является Thing. У вас есть class ObjectB implements Thing - это означает, что каждый ObjectB является Thing. И у вас есть List<Thing> - List, который может содержать объекты ObjectA, объекты ObjectB и любой другой объект любого класса, кроме implements Thing. То, что вы не можете поместить в него, - это объект любого класса, который не реализует Thing.
Хороший пример! Но вам может понадобиться переопределяемый метод cook, потому что я предполагаю, что он должен отличаться между Potato и Broccoli, не говоря уже о Tomato ...
ObjectAиObjectBЯВЛЯЮТСЯ экземплярамиThing- это полиморфизм в действии