В Java, когда оператор «==» используется для сравнения двух объектов, он проверяет, ссылаются ли объекты на одно и то же место в памяти. Другими словами, он проверяет, являются ли имена двух объектов в основном ссылками на одно и то же место в памяти.
Класс Java String фактически переопределяет реализацию equals () по умолчанию в классе Object и переопределяет этот метод, чтобы он проверял только значения строк, а не их местоположения в памяти. Это означает, что если вы вызываете метод equals () для сравнения двух объектов String, то, пока действительная последовательность символов равна, оба объекта считаются равными.
Оператор
==
проверяет, являются ли две строки точно одним и тем же объектом.Метод
blockquote>.equals()
проверяет, имеют ли две строки одно и то же значение.
Нет, 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>
.
На самом деле вы можете использовать интерфейс для достижения желаемого.
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());
Давайте возьмем пример из учебника 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 было два варианта решения этой проблемы:
По понятным причинам это выбрало первый способ.
То, что вы ищете, называется параметрами ковариационного типа . Проблема в том, что они не являются безопасными для типов в общем случае, в частности для изменяемых списков. Предположим, что у вас есть List<Dog>
, и ему разрешено работать как List<Animal>
. Что происходит, когда вы пытаетесь добавить Cat к этому List<Animal>
, который действительно является List<Dog>
? Автоматически разрешая ковариационные параметры типа, поэтому разбивает систему типов.
Было бы полезно добавить синтаксис, чтобы позволить параметрам типа указываться как ковариантные, что позволяет избежать ? extends Foo
в объявлениях метода, но это делает добавьте дополнительную сложность.
Мы также должны учитывать, как компилятор угрожает родовым классам: в «создает экземпляр» другого типа всякий раз, когда мы заполняем общие аргументы.
Таким образом, мы имеем ListOfAnimal
, ListOfDog
, ListOfCat
и т. д., которые представляют собой различные классы, которые в конечном итоге «создаются» компилятором, когда мы указываем общие аргументы. И это плоская иерархия (на самом деле относительно List
не является иерархией вообще).
Другой аргумент, почему ковариация не имеет смысла в случае общих классов, состоит в том, что у основания все классы одинаковы - являются List
экземплярами. Специализация List
, заполняя общий аргумент, не расширяет класс, он просто заставляет его работать для этого конкретного общего аргумента.
Я бы сказал, что весь смысл Generics заключается в том, что он этого не позволяет. Рассмотрим ситуацию с массивами, которые допускают такой тип ковариации:
Object[] objects = new String[10];
objects[0] = Boolean.FALSE;
Этот код компилируется отлично, но выдает ошибку во время выполнения (java.lang.ArrayStoreException: java.lang.Boolean
во второй строке). Это не типично. Пункт Generics заключается в добавлении безопасности типа времени компиляции, иначе вы можете просто придерживаться простого класса без дженериков.
Теперь есть моменты, когда вам нужно быть более гибкими, и это то, что ? super Class
и ? extends Class
для. Первый - когда вам нужно вставить тип Collection
(например), а последний - для того, когда вам нужно прочитать его, безопасным типом. Но единственный способ сделать это одновременно - это иметь определенный тип.
То, что я думаю, должно быть добавлено к тому, что other отвечает , упоминается в том, что while
blockquote>
List<Dog>
isn't-aList<Animal>
в Javaтакже верно, что
Список собак - это список животных на английском языке (ну, при разумной интерпретации )
blockquote>То, как работает интуиция OP, что вполне справедливо, - последнее предложение. Однако, если мы применим эту интуицию, мы получим язык, который не является Java-esque в своей системе типов: предположим, что наш язык не позволяет добавить кошку в наш список собак. Что это значит? Это означало бы, что список перестает быть списком собак и остается просто списком животных. И список млекопитающих, и список квадрантов.
Другими словами: A
List<Dog>
в Java не означает «список собак» на английском языке, это означает «список, который может имеют собак и ничего больше ».В более общем плане, интуиция OP поддается языку, в котором операции над объектами могут изменять свой тип, или, скорее, тип (ы) объекта является (динамической) функцией его стоимости.
другое решение состоит в создании нового списка
List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());
Проблема была хорошо идентифицирована. Но есть решение; make doSomething generic:
<T extends Animal> void doSomething<List<T> animals) {
}
теперь вы можете вызвать doSomething с помощью List & lt; Dog> или List & lt; Cat> или List & lt; Animal>.
Ответы здесь не полностью меня убедили. Поэтому вместо этого я делаю еще один пример.
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
.
(Consumer<Runnable>, Supplier<Dog>)
, тогда как Dog
является подтипом Animal & Runnable
– ZhongYu
27 July 2015 в 20:10
Причина, по которой List<Dog>
не является List<Animal>
, заключается в том, что вы можете вставить Cat
в List<Animal>
, но не в List<Dog>
... вы можете использовать подстановочные знаки для по возможности расширять дженерики; например, чтение из List<Dog>
похоже на чтение с List<Animal>
- но не на запись.
Общие для Java-языка и Раздел «Общие» из учебников Java содержит очень хорошее, подробное объяснение того, почему некоторые вещи являются или не являются полиморфными или разрешены с помощью дженериков.
Чтобы понять проблему, полезно сделать сравнение с массивами.
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());
Подтипирование является инвариантом для параметризованных типов. Даже жесткий класс 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 . Это свойство имеет решающее значение, поскольку оно не нарушает совместимость в обратном направлении.
В заключение, безопасность во время выполнения невозможна для параметризованных типов, поскольку компилятор заменяет каждый параметризованный тип его стиранием. Это делает параметризованные типы не более чем синтаксическим сахаром.
Ответ , а также другие ответы правильные. Я собираюсь добавить к этим ответам решение, которое, я думаю, будет полезно. Я думаю, что это часто возникает при программировании. Следует отметить, что для коллекций (списки, наборы и т. Д.) Основной проблемой является добавление в коллекцию. Вот где вещи ломаются. Даже удаление в порядке.
В большинстве случаев мы можем использовать 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();
}
}
Collections.unmodifiableCollection
– Radiodef
8 May 2015 в 21:30
Collection<? extends E>
уже правильно обрабатывает это поведение, если вы не используете его таким образом, чтобы он не был безопасным для типа (например, отбрасывал его на что-то еще). Единственное преимущество, которое я вижу там, когда вы вызываете операцию add
, оно выдает исключение, даже если вы его произвели.
– Vlasec
13 November 2017 в 13:47
Если вы уверены, что элементы списка являются подклассами этого суперпорогового типа, вы можете использовать список, используя этот подход:
(List<Animal>) (List<?>) dogs
Это полезно, когда вы хотите передать список в конструкторе или перебрать его
Базовая логика такого поведения заключается в том, что 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 без дженериков ....