Суперкласс как тип appernt в списке [duplicate]

В Java, когда оператор «==» используется для сравнения двух объектов, он проверяет, ссылаются ли объекты на одно и то же место в памяти. Другими словами, он проверяет, являются ли имена двух объектов в основном ссылками на одно и то же место в памяти.

Класс Java String фактически переопределяет реализацию equals () по умолчанию в классе Object и переопределяет этот метод, чтобы он проверял только значения строк, а не их местоположения в памяти. Это означает, что если вы вызываете метод equals () для сравнения двух объектов String, то, пока действительная последовательность символов равна, оба объекта считаются равными.

Оператор == проверяет, являются ли две строки точно одним и тем же объектом.

Метод .equals() проверяет, имеют ли две строки одно и то же значение.

620
задан kevinarpe 15 December 2017 в 03:24
поделиться

16 ответов

Нет, a List<Dog> - не a List<Animal>. Подумайте, что вы можете сделать с List<Animal> - вы можете добавить к нему любое животное ... включая кошку. Теперь, можете ли вы логически добавить кошку в помет щенков? Абсолютно нет.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

Внезапно у вас есть очень спутанный кот.

Теперь вы не можете добавить Cat до List<? extends Animal>, потому что вы не знаете, что это List<Cat>. Вы можете получить значение и знать, что это будет Animal, но вы не можете добавить произвольных животных. Обратное верно для List<? super Animal> - в этом случае вы можете безопасно добавить Animal, но вы ничего не знаете о том, что можно извлечь из него, потому что это может быть List<Object>.

760
ответ дан Jon Skeet 15 August 2018 в 16:22
поделиться
  • 1
    Интересно, что каждый список собак есть действительно список животных, как говорит нам интуиция. Дело в том, что не каждый список животных - это список собак, поэтому проблема с изменением списка, добавив кошку, является проблемой. – Ingo 28 January 2013 в 21:29
  • 2
    @Ingo: Нет, не совсем: вы можете добавить кошку в список животных, но вы не можете добавить кошку в список собак. Список собак - это только список животных, если вы считаете это в смысле только для чтения. – Jon Skeet 28 January 2013 в 21:33
  • 3
    @JonSkeet - Конечно, но кто поручает, что создание нового списка у кошки и список собак на самом деле изменяет список собак? Это произвольное решение для реализации на Java. Тот, который противоречит логике и интуиции. – Ingo 28 January 2013 в 21:41
  • 4
    @Ingo: Я бы не использовал это "конечно" начать с. Если у вас есть список, который говорит в верхней части «Гостиницы, которые мы, возможно, захотим пойти», и тогда кто-то добавил к нему плавательный бассейн, вы считаете, что это действительно так? Нет - это список отелей, который не является списком зданий. И это не то, что я даже сказал: «Список собак - это не список животных». - Я поместил его в кодовые слова в шрифт кода. Я действительно не думаю, что здесь есть какая-то двусмысленность. В любом случае использование подкласса было бы некорректным - речь идет о совместимости присваивания, а не подклассификации. – Jon Skeet 28 January 2013 в 21:58
  • 5
    @ruakh: Проблема в том, что вы затем нажимаете на время выполнения что-то, что можно заблокировать во время компиляции. И я бы сказал, что ковариация массива была ошибкой дизайна. – Jon Skeet 3 July 2013 в 18:22

На самом деле вы можете использовать интерфейс для достижения желаемого.

public interface Animal {
    String getName();
    String getVoice();
}
public class Dog implements Animal{
    @Override 
    String getName(){return "Dog";}
    @Override
    String getVoice(){return "woof!";}

}

вы можете использовать коллекции, используя

List <Animal> animalGroup = new ArrayList<Animal>();
animalGroup.add(new Dog());
3
ответ дан Angel Koh 15 August 2018 в 16:22
поделиться

Давайте возьмем пример из учебника JavaSE

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

Итак, почему список собак (кругов) не следует рассматривать неявно, список животных (фигур) - это потому, что этой ситуации:

// drawAll method call
drawAll(circleList);


public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

Итак, у «архитекторов» Java было два варианта решения этой проблемы:

  1. не считают, что подтип неявно является супертипом и дает ошибка компиляции, как это происходит сейчас
  2. , считают подтип супертипом и ограничивают при компиляции метод «добавить» (поэтому в методе drawAll, если список подтипов формы окружности будет равен прошло, компилятор должен обнаружить это и ограничить вас ошибкой компиляции в этом).

По понятным причинам это выбрало первый способ.

0
ответ дан aurelius 15 August 2018 в 16:22
поделиться

То, что вы ищете, называется параметрами ковариационного типа . Проблема в том, что они не являются безопасными для типов в общем случае, в частности для изменяемых списков. Предположим, что у вас есть List<Dog>, и ему разрешено работать как List<Animal>. Что происходит, когда вы пытаетесь добавить Cat к этому List<Animal>, который действительно является List<Dog>? Автоматически разрешая ковариационные параметры типа, поэтому разбивает систему типов.

Было бы полезно добавить синтаксис, чтобы позволить параметрам типа указываться как ковариантные, что позволяет избежать ? extends Foo в объявлениях метода, но это делает добавьте дополнительную сложность.

68
ответ дан Basil Bourque 15 August 2018 в 16:22
поделиться

Мы также должны учитывать, как компилятор угрожает родовым классам: в «создает экземпляр» другого типа всякий раз, когда мы заполняем общие аргументы.

Таким образом, мы имеем ListOfAnimal, ListOfDog, ListOfCat и т. д., которые представляют собой различные классы, которые в конечном итоге «создаются» компилятором, когда мы указываем общие аргументы. И это плоская иерархия (на самом деле относительно List не является иерархией вообще).

Другой аргумент, почему ковариация не имеет смысла в случае общих классов, состоит в том, что у основания все классы одинаковы - являются List экземплярами. Специализация List, заполняя общий аргумент, не расширяет класс, он просто заставляет его работать для этого конкретного общего аргумента.

0
ответ дан Cristik 15 August 2018 в 16:22
поделиться

Я бы сказал, что весь смысл Generics заключается в том, что он этого не позволяет. Рассмотрим ситуацию с массивами, которые допускают такой тип ковариации:

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

Этот код компилируется отлично, но выдает ошибку во время выполнения (java.lang.ArrayStoreException: java.lang.Boolean во второй строке). Это не типично. Пункт Generics заключается в добавлении безопасности типа времени компиляции, иначе вы можете просто придерживаться простого класса без дженериков.

Теперь есть моменты, когда вам нужно быть более гибкими, и это то, что ? super Class и ? extends Class для. Первый - когда вам нужно вставить тип Collection (например), а последний - для того, когда вам нужно прочитать его, безопасным типом. Но единственный способ сделать это одновременно - это иметь определенный тип.

32
ответ дан David Tonhofer 15 August 2018 в 16:22
поделиться

То, что я думаю, должно быть добавлено к тому, что other отвечает , упоминается в том, что while

List<Dog> isn't-a List<Animal> в Java

также верно, что

Список собак - это список животных на английском языке (ну, при разумной интерпретации )

То, как работает интуиция OP, что вполне справедливо, - последнее предложение. Однако, если мы применим эту интуицию, мы получим язык, который не является Java-esque в своей системе типов: предположим, что наш язык не позволяет добавить кошку в наш список собак. Что это значит? Это означало бы, что список перестает быть списком собак и остается просто списком животных. И список млекопитающих, и список квадрантов.

Другими словами: A List<Dog> в Java не означает «список собак» на английском языке, это означает «список, который может имеют собак и ничего больше ».

В более общем плане, интуиция OP поддается языку, в котором операции над объектами могут изменять свой тип, или, скорее, тип (ы) объекта является (динамической) функцией его стоимости.

26
ответ дан einpoklum 15 August 2018 в 16:22
поделиться
  • 1
    Да, человеческий язык более нечеткий. Но все же, как только вы добавляете другое животное в список собак, это все еще список животных, но больше не список собак. Разница в том, что человек, с нечеткой логикой, обычно не понимает этого. – Vlasec 13 November 2017 в 13:14
  • 2
    @Vlasec: Человеческая логика действительно может быть нечеткой, но это не мое мнение. Вы можете применять очень точную логику; просто в этом контексте мы по умолчанию используем определение списка extensional . – einpoklum 20 March 2018 в 00:42

другое решение состоит в создании нового списка

List<Dog> dogs = new ArrayList<Dog>(); 
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());
0
ответ дан ejaenv 15 August 2018 в 16:22
поделиться

Проблема была хорошо идентифицирована. Но есть решение; make doSomething generic:

<T extends Animal> void doSomething<List<T> animals) {
}

теперь вы можете вызвать doSomething с помощью List & lt; Dog> или List & lt; Cat> или List & lt; Animal>.

0
ответ дан gerardw 15 August 2018 в 16:22
поделиться

Ответы здесь не полностью меня убедили. Поэтому вместо этого я делаю еще один пример.

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

звучит нормально, не так ли? Но вы можете передавать только Consumer s и Supplier s для Animal s. Если у вас есть потребитель Mammal, но поставщик Duck, они не должны соответствовать, хотя оба являются животными. Чтобы отменить это, добавлены дополнительные ограничения.

Вместо вышесказанного мы должны определить отношения между используемыми нами типами.

E. g.,

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

гарантирует, что мы можем использовать только поставщика, который предоставляет нам правильный тип объекта для потребителя.

OTOH, мы могли бы также сделать

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

, где мы идем в другую сторону: мы определяем тип Supplier и ограничиваем, что его можно поместить в Consumer.

Мы даже можем сделать

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

где, имея интуитивные отношения Life -> Animal -> Mammal -> Dog, Cat и т. д., мы могли бы даже поставить Mammal в Life, но не String в потребителя Life.

4
ответ дан glglgl 15 August 2018 в 16:22
поделиться

Причина, по которой List<Dog> не является List<Animal>, заключается в том, что вы можете вставить Cat в List<Animal>, но не в List<Dog> ... вы можете использовать подстановочные знаки для по возможности расширять дженерики; например, чтение из List<Dog> похоже на чтение с List<Animal> - но не на запись.

Общие для Java-языка и Раздел «Общие» из учебников Java содержит очень хорошее, подробное объяснение того, почему некоторые вещи являются или не являются полиморфными или разрешены с помощью дженериков.

42
ответ дан JonasCz 15 August 2018 в 16:22
поделиться

Чтобы понять проблему, полезно сделать сравнение с массивами.

List<Dog> не является подклассом List<Animal>. Но Dog[] является подклассом Animal[].

Массивы являются reifiable и ковариантными. Подтверждаемый означает, что их информация о типе полностью доступна во время выполнения. Поэтому массивы обеспечивают безопасность типа времени выполнения, но не безопасность типа компиляции.

    // All compiles but throws ArrayStoreException at runtime at last line
    Dog[] dogs = new Dog[10];
    Animal[] animals = dogs; // compiles
    animals[0] = new Cat(); // throws ArrayStoreException at runtime

Это наоборот для дженериков: дженерики стерты и инвариантны. Поэтому генераторы не могут обеспечить безопасность во время выполнения, но они обеспечивают безопасность типа компиляции. В приведенном ниже коде, если дженерики были ковариантными, можно сделать загрязнение кучи в строке 3.

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
    animals.add(new Cat());
5
ответ дан outdev 15 August 2018 в 16:22
поделиться

Подтипирование является инвариантом для параметризованных типов. Даже жесткий класс Dog является подтипом Animal, параметризованный тип List<Dog> не является подтипом List<Animal>. Напротив, подтипирование covariant используется массивами, поэтому тип массива Dog[] является подтипом Animal[].

Инвариантный подтипирование гарантирует, что ограничения типа, принудительно выполняемые Java, не нарушены. Рассмотрим следующий код, приведенный @Jon Skeet:

List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);

Как указано в @Jon Skeet, этот код является незаконным, поскольку в противном случае он будет нарушать ограничения типа, возвращая кошку, когда ожидается собака.

Поучительно сравнить приведенное выше с аналогичным кодом для массивов.

Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];

Код является законным. Тем не менее, выбрасывает исключение хранилища . Массив носит свой тип во время выполнения таким образом, JVM может обеспечить безопасность типов ковариантного подтипирования.

Чтобы понять это далее, давайте посмотрим на байт-код, сгенерированный javap класса ниже:

import java.util.ArrayList;
import java.util.List;

public class Demonstration {
    public void normal() {
        List normal = new ArrayList(1);
        normal.add("lorem ipsum");
    }

    public void parameterized() {
        List<String> parameterized = new ArrayList<>(1);
        parameterized.add("lorem ipsum");
    }
}

Используя команду javap -c Demonstration, это показывает следующий байт-код Java:

Compiled from "Demonstration.java"
public class Demonstration {
  public Demonstration();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void normal();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return

  public void parameterized();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return
}

Обратите внимание, что переведенный код тел метода идентичен. Компилятор заменил каждый параметризованный тип на erasure . Это свойство имеет решающее значение, поскольку оно не нарушает совместимость в обратном направлении.

В заключение, безопасность во время выполнения невозможна для параметризованных типов, поскольку компилятор заменяет каждый параметризованный тип его стиранием. Это делает параметризованные типы не более чем синтаксическим сахаром.

1
ответ дан Root G 15 August 2018 в 16:22
поделиться

Ответ , а также другие ответы правильные. Я собираюсь добавить к этим ответам решение, которое, я думаю, будет полезно. Я думаю, что это часто возникает при программировании. Следует отметить, что для коллекций (списки, наборы и т. Д.) Основной проблемой является добавление в коллекцию. Вот где вещи ломаются. Даже удаление в порядке.

В большинстве случаев мы можем использовать Collection<? extends T>, а не Collection<T>, и это должен быть первый выбор. Однако я нахожу случаи, когда это непросто сделать. Он обсуждает вопрос о том, всегда ли это самое лучшее. Я представляю здесь класс DownCastCollection, который может преобразовать Collection<? extends T> в Collection<T> (мы можем определить аналогичные классы для List, Set, NavigableSet, ..), которые будут использоваться, когда использование стандартного подхода очень неудобно. Ниже приведен пример того, как его использовать (мы могли бы также использовать Collection<? extends Object> в этом случае, но я просто не могу проиллюстрировать использование DownCastCollection.

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/

public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

Теперь класс:

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;

public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}

@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}

@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;

    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }

    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }

    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }

    @Override
    public void remove() {
        delegateIterator.remove();

    }

}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}



@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}

@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();

}

}

1
ответ дан Rudy Velthuis 15 August 2018 в 16:22
поделиться
  • 1
    Это хорошая идея, настолько, что она уже существует в Java SE. ; ) Collections.unmodifiableCollection – Radiodef 8 May 2015 в 21:30
  • 2
    Правильно, но набор, который я определяю, можно изменить. – dan b 9 May 2015 в 11:40
  • 3
    Да, его можно изменить. Collection<? extends E> уже правильно обрабатывает это поведение, если вы не используете его таким образом, чтобы он не был безопасным для типа (например, отбрасывал его на что-то еще). Единственное преимущество, которое я вижу там, когда вы вызываете операцию add, оно выдает исключение, даже если вы его произвели. – Vlasec 13 November 2017 в 13:47

Если вы уверены, что элементы списка являются подклассами этого суперпорогового типа, вы можете использовать список, используя этот подход:

(List<Animal>) (List<?>) dogs

Это полезно, когда вы хотите передать список в конструкторе или перебрать его

1
ответ дан sagits 15 August 2018 в 16:22
поделиться
  • 1
    Это создаст больше проблем, чем фактически решает – Ferrybig 1 February 2016 в 16:33
  • 2
    Если вы попытаетесь добавить Cat в список, убедитесь, что он создаст проблемы, но для целей цикла я считаю, что это единственный не многословный ответ. – sagits 2 February 2016 в 13:28

Базовая логика такого поведения заключается в том, что Generics следует механизму стирания типа. Поэтому во время выполнения вы не можете определить тип collection в отличие от arrays, где нет такого процесса стирания. Итак, вернемся к вашему вопросу ...

Итак, предположим, что существует метод, приведенный ниже:

add(List<Animal>){
    //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism
}

Теперь, если java позволяет вызывающему добавить список типов Animal к этому методу то вы можете добавить неправильную вещь в коллекцию, и во время выполнения тоже она будет запущена из-за стирания типа. В то время как в случае массивов вы получите исключение времени выполнения для таких сценариев ...

Таким образом, по существу это поведение реализовано таким образом, что нельзя добавлять неправильную вещь в коллекцию. Теперь я считаю, что стирание стилей существует, чтобы обеспечить совместимость с устаревшим java без дженериков ....

4
ответ дан STaefi 15 August 2018 в 16:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: