Подтвердить что ты не робот

Какой тип безопасности будет потерян, если Generics поддерживает подтип?

Рассмотрим фрагмент:

Number[] numbers = {1, 2.3, 4.5f, 6000000000000000000L};

Совершенно нормально делать это, Number - абстрактный класс.

Вперед,

List<Long> listLong = new ArrayList<Long>();
listLong.add(Long.valueOf(10));

List<Number> listNumbers = listLong; // compiler error    - LINE 3

listNumbers.add(Double.valueOf(1.23));

Была построена линия 3, которая была успешно скомпилирована, мы получим List of Number s, т.е.

for(Number num: listNumbers ){
    System.out.println(num);
}

// 10
// 1.23

которые являются всеми числами.

Я наткнулся на это в книге,

Generics не поддерживает подтипирование, потому что это вызовет проблемы в достижение безопасности типа. Вот почему List<T> не считается подтип List<S>, где S - супертип T

Какой тип безопасности был бы потерян в этом конкретном случае, как обсуждалось выше, должны ли были успешно компилироваться строки 3?

4b9b3361

Ответ 1

List<Long> listLong = new ArrayList<Long>();
List<Number> listNumbers = listLong;

Итак, listNumbers и listLong будут двумя ссылками на один и тот же список, если это возможно, правильно?

listNumbers.add(Double.valueOf(1.23));

Итак, вы могли бы добавить Double в этот список. listLong, типа List<Long>, таким образом, будет содержать Double. Таким образом, безопасность типа будет нарушена.

Ответ 2

Если бы это было так, то мы могли бы добавить другие различные подтипы Number в listNumbers, которые должны быть запрещены.

Представьте, что теперь вы вставляете объекты типа Double и Long, а позже вы пытаетесь использовать Long#reverse. Ваш код будет компилироваться, но, конечно, не удастся во время выполнения (плохо) первого Double, который он выйдет.

Ответ 3

Используйте пример с не-абстрактным базовым классом:

public class Human {
    public string getName() {
        // ...
    }
}

public class Student extends Human {
    public void learn(Subject subject) {
        // ...
    }
}

public class Teacher extends Human {
    public void teach(Subject subject) {
        // ...
    }
}

В любом месте, где ожидается Human, a Student или Teacher будут делать то же самое, так как они полностью реализуют интерфейс Human. (В этом случае на них можно вызвать getName().) Наследование Java гарантирует, что это так технически. Выполнение семантической работы - это задание автора класса, так что его код выполняет принцип подстановки Лискова.

Разве это не означает, что мы можем также заменить Collection<Teacher>, где ожидается a Collection<Human>? Не всегда. Рассмотрим следующий метод:

public class Human {
    // ...

    public void join(Set<Human> party) {
        party.add(this);
    }
}

Теперь, если Java разрешает передавать Set<Student> в качестве участника, любые попытки не Student Human присоединиться к этой стороне должны были сбой во время выполнения.

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

Наш метод join помещает Human в party, поэтому мы также можем разрешить Set<Object> или не общий Set или эквивалентно a Set<?>. Java позволяет нам делать это с более низкоуровневыми подстановочными знаками:

public class Human {
    // ...

    public void join(Set<? super Human> party) {
        party.add(this);
    }
}

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

public class Teacher extends Human {
    public void teach(Subject subject, Set<? extends Student> schoolClass) {
        for (Student student : class) {
            student.learn(subject);
        }
    }
}

Теперь, если мы когда-либо подклассы Student, переданный schoolClass может быть Set этого подтипа тоже.

Ответ 4

Концепция, о которой вы говорите, variance.

Иными словами, если S является супертипом T, является List<S> подтипом, супертипом, равным типом или не обращается к List<T>?

Ответ для List - и всех других Java-дженериков * - является "несвязанным", т.е. инвариантным.

class SuperType {}

class Type extends SuperType {}

class SubType extends Type {}

List<Type> list = ...

List<SuperType> superList = list;
superList.add(new SuperType());
// no, we shouldn't be able to add a SuperType to list

List<SubType> subList = list;
SubType item = subList.get(0);
// no, there not necessarily only SubType items in list

* В Java есть понятие дисперсии "use-site", с подстановочными знаками (?). Это будет ограничено, какие методы можно вызвать.

List<Type> list = ...

List<? super SubType> wildcardList = list;
wildcardList.add(new SubType());
// but...everything we get() is an Object

или

List<Type> list = ...

List<? extends SuperType> wildcardList = list;
SuperType item = wildcard.get(0);
// but...it impossible to add()

FYI, некоторые языки имеют понятие дисперсии определения-места, например. Scala. Итак, List[Int] действительно является подтипом List[Number]. Это возможно с неизменяемыми коллекциями (опять же, с ограниченным набором методов), но, очевидно, не для изменчивых.