Почему Java позволяет вам добавлять в коллекцию?

У меня есть простой класс foo, и я могу передать в интерфейс коллекции (либо Map или List) без какой-либо ошибки компилятора. Обратите внимание, что класс Foo не реализует никакого интерфейса или не расширяет какой-либо другой класс.

public class Foo {

    public List<String> getCollectionCast() {
        return (List<String>) this;    // No compiler error
    }

    public Map<String, String> getCollection2Cast() {
        return (Map<String, String>) this;    // No compiler error
    }

    public Other getCast() {
        return (Other)this;     // Incompatible types. Cannot cast Foo to Other
    }

    public  static class Other {
        // Just for casting demo
    }

}

Почему компилятор Java не возвращает ошибку несовместимых типов при попытке Foo класс Foo в коллекцию?

Foo не реализует Collection. Я ожидал бы ошибку несовместимых типов, потому что, учитывая текущую подпись класса Foo, это не может быть Collection.

Ответ 1

Это не потому, что они являются классами коллекции, а потому, что они являются интерфейсами. Foo не реализует их, но подклассы этого могут. Таким образом, это не ошибка времени компиляции, поскольку эти методы могут быть действительными для подклассов. Во время выполнения, если this не класс, реализующий эти интерфейсы, естественно, это ошибка времени выполнения.

Если вы измените List<String> на ArrayList<String>, вы также получите ошибку времени компилятора, так как подкласс Foo может реализовать List, но не может расширить ArrayList (поскольку Foo не работает). Точно так же, если вы сделаете Foo final, компилятор даст вам ошибку для ваших интерфейсов, потому что он знает, что они никогда не могут быть правдой (поскольку Foo не может иметь подклассы и не реализует эти интерфейсы).

Ответ 2

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

Если целевой тип является интерфейсом, тогда это имеет смысл, потому что класс, расширяющий Foo может реализовать Map<String, String>. Однако обратите внимание, что это работает только как Foo, не является final. Если вы объявили свой класс final class Foo, этот прилив не будет работать.

Если целевым типом является класс, то в этом случае он просто терпит неудачу (попробуйте (HashMap<String, String>) this), потому что компилятор точно знает, что связь между Foo и HashMap невозможна.

Для справки эти правила описаны в JLS-5.5.1 (T = target type - Map<String, String>, S = тип источника - Foo)

Если T [тип цели] - тип интерфейса:

  • Если S не является конечным классом (§8.1.1), то, если существует супертип X из T и супертип Y из S, такой, что и X, и Y являются доказуемо различными параметризованными типами, и что стирания X и Y одинаковы, возникает ошибка времени компиляции.
    В противном случае бросок всегда легален во время компиляции (потому что даже если S не реализует T, может быть подкласс S).

  • Если S - конечный класс (§8.1.1), то S должен реализовать T, или возникает ошибка времени компиляции.

Обратите внимание на полужирный курсив в цитируемом тексте.