Философия Java3
Шрифт:
Solid2(T item) { super(item); } int weight О { return item.weightO; }
}
public class InheritBounds {
public static void main(String[] args) { Solid2<Bounded> solid2 =
new Solid2<Bounded>(new BoundedO); solid2 colorO: solid2 getYO; solid2 weightO,
}
} ///.-
Holdltem просто хранит объект; это поведение наследуется классом Colored2, который также требует, чтобы его параметр реализовывал HasColor. ColoredDi-mension2 и Solid2 продолжают расширение иерархии и добавляют на каждом уровне новые ограничения. Теперь методы наследуются, и их не нужно повторять в каждом классе.
Пример
//: generics/EpicBattlе.java // Demonstrating bounds in Java generics, import java util.*;
interface Superpower {} interface XRayVision extends Superpower { void seeThroughWallsO;
}
interface SuperHearing extends Superpower { void hearSubtleNoisesO;
}
interface SuperSmell extends Superpower { void trackBySmellO;
}
class SuperHero<POWER extends SuperPower> { POWER power;
SuperHero(POWER power) { this.power = power; } POWER getPower0 { return power; }
}
class SuperSleuth<POWER extends XRayVision>
extends SuperHero<POWER> {
SuperSleuth(POWER power) { super(power); } void see { power. seeThroughWallsO: }
}
class CanineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER> {
CanineHero(POWER power) { super(power); } void hearO { power.hearSubtleNoisesO; } void smell О { power.trackBySmell0; }
}
class SuperHearSmell implements SuperHearing, SuperSmell { public void hearSubtleNoisesO {} public void trackBySmell0 {}
}
class DogBoy extends CanineHero<SuperHearSmell> { DogBoyO { super(new SuperHearSmell0); }
}
public class EpicBattle {
// Ограничения в параметризованных методах: static <POWER extends SuperHearing> void useSuperHearing(SuperHero<POWER> hero) { hero, get Power hearSubtleNoisesO:
}
static <POWER extends SuperHearing & SuperSmell> void superFind(SuperHero<POWER> hero) {
hero, get Power 0 .hearSubtleNoisesO; hero.getPower .trackBySmel 10;
}
public static void main(String[] args) { DogBoy dogBoy = new DogBoyO; useSuperHearing(dogBoy); superFind(dogBoy); // Так можно:
List<? extends SuperHearing> audioBoys; // А так нельзя:
// List<? extends SuperHearing & SuperSmell> dogBoys;
}
} ///:-
Метасимволы
Мы уже встречали простые примеры использования метасимволов — вопросительных знаков в выражениях аргументов параметризации — в главах И и 13. В этом разделе тема будет рассмотрена более подробно.
Начнем с примера, демонстрирующего одну особенность массивов: массив производного типа можно присвоить ссылке на массив базового типа:
II: generics/CovariantArrays.java class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {} class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new AppleO; // OK fruit[l] = new JonathanO; // OK
// Тип времени выполнения - Applet], а не Fruit[] или Orange[]: try {
// Компилятор позволяет добавлять объекты Fruit: fruit[0] = new FruitO; // ArrayStoreException } catch(Exception e) { System.out.println(e): } try {
// Компилятор позволяет добавлять объекты Orange: fruit[0] = new OrangeO; // ArrayStoreException } catch(Exception e) { System.out.println(e); }
}
} /* Output:
java.1ang.ArrayStoreException: Fruit java.1ang.ArrayStoreExcepti on: Orange *///:-
Первая
строка main создает массив Apple и присваивает его ссылке на массив Fruit. Выглядит логично — Apple является разновидностью Fruit, поэтому массив Apple также одновременно должен быть массивом Fruit.С другой стороны, если фактическим типом массива является Арр1е[], в массиве можно разместить только Apple или субтип Apple, причем это правило должно соблюдаться как во время компиляции, так и во время выполнения. Но обратите внимание на то, что компилятор также позволит разместить в массиве ссылку на объект Fruit. Для компилятора это вполне логично, потому что он имеет дело со ссылкой Fruit[] — так почему бы не разрешить занести в массив объект Fruit или любого типа, производного от Fruit, — скажем, Orange? Во время компиляции это разрешено. Однако механизм времени выполнения знает, что он имеет дело с Apple [], и при попытке занесения постороннего типа происходит исключение.
Впрочем, для массивов это не создает особых проблем, потому что при вставке объекта неверного типа вы об этом очень быстро узнаете во время выполнения. Но одна из основных целей параметризации как раз и состоит в том, чтобы по возможности переместить выявление подобных ошибок на стадию выполнения. Итак, что же произойдет при использовании параметризованных контейнеров вместо массивов?
//: generics/NonCovariantGenerics.java // {CompileTimeError} (Won't compile) import java.util.*:
public class NonCovariantGenerics {
// Ошибка компиляции: несовместимые типы List<Fruit> flist = new ArrayList<Apple>; } ///:-
На первый взгляд это выглядит как утверждение «Контейнер с элементами Apple нельзя присвоить контейнеру с элементами Fruit», но следует вспомнить, что параметризация — это не только контейнеры. В действительности утверждение следует трактовать шире: «Параметризованный тип, в котором задействован тип Apple, нельзя присвоить параметризованному типу, в котором задействован тип Fruit». Если бы, как в случае с массивами, компилятор располагал достаточной информацией и мог понять, что речь идет о контейнерах, он мог бы проявить некоторую снисходительность. Но компилятор такой информацией не располагает, поэтому он отказывается выполнить «восходящее преобразование». Впрочем, это и не является восходящим преобразованием — List с элементами Apple не является «частным случаем» List с элементами Fruit. Первый может хранить Apple и подтипы Apple, а второй — любые разновидности Fruit... да, в том числе и Apple, но от этого он не становится List с элементами Apple, а по-прежнему остается List с элементами Fruit.
Проблема в том, что речь идет о типе контейнера, а не о типе элементов, которые в этом контейнере хранятся. В отличие от массивов, параметризованные типы не обладают встроенной ковариантностью. Это связано с тем, что массивы полностью определяются в языке и для них могут быть реализованы встроенные проверки как во время компиляции, так и во время выполнения, но с параметризованными типами компилятор и система времени выполнения не знают, что вы собираетесь делать с типами и какие правила при этом должны действовать.