Главная страница

АБОБА. Справочник по программированию на Java Методическое пособие


Скачать 242.41 Kb.
НазваниеСправочник по программированию на Java Методическое пособие
АнкорАБОБА
Дата20.02.2022
Размер242.41 Kb.
Формат файлаdocx
Имя файла12642_java_method_1.docx
ТипСправочник
#368066
страница67 из 67
1   ...   59   60   61   62   63   64   65   66   67

Обобщенные конструкторы


Конструкторы также могут быть обобщенными, даже если их классы таковыми не являются. Например, рассмотрим следующую короткую программу:

// Использование обобщенного конструктора.

class GenCons {

private double val;

GenCons(T arg) {

val = arg.doubleValue();

}

void showval() {

System.out.println("val: " + val);

}}

class GenConsDemo {

public static void main(String args[]) {

GenCons test = new GenCons(100);

GenCons test2 = new GenCons(123.5F);

test.showval();

test2.showval();

}}

Вывод этой программы:

val: 100.0

val: 123.5

Поскольку GenCons() специфицирует параметр обобщенного типа, который может быть подклассом Number, GenCons() можно вызывать с любым числовым типом, включая Integer, Float или Double. Таким образом, даже несмотря на то, что GenCons — необобщенный класс, его конструктор обобщен.

Обобщенные интерфейсы


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

// Пример обобщенного интерфейса.

// Интерфейс Min/Max.

interface MinMax> {

T min();

T max();

}

// Реализация MinMax

class MyClass> implements MinMax {

T[] vals;

MyClass(T[] o) { vals = o; }

// Возвращает минимальное значение из vals.

public T min() {

T v = vals[0];

for(int i=1; i < vals.length; i++)

if(vals[i].compareTo(v) < 0) v = vals[i];

return v;

}

// Возвращает максимальное значение из vals.

public T max() {

T v = vals[0];

for(int i=1; i < vals.length; i++)

if(vals[i].compareTo(v) > 0) v = vals[i];

return v;

}}

class GenIFDemo {

public static void main(String args[]) {

Integer inums[] = {3, 6, 2, 8, 6 };

Character chs[] = {'b', 'r', 'p', 'w' };

MyClass iob = new MyClass(inums);

MyClass cob = new MyClass(chs);

System.out.println("Максимальное значение в inums: " + iob.max());

System.out.println("Минимальное значение в inums: " + iob.min());

System.out.println("Максимальное значение в chs: " + cob.max());

System.out.println("Минимальное значение в chs: " + cob.min());

}}

Результат работы этой программы:

Максимальное значение в inums: 8

Минимальное значение в inums: 2

Максимальное значение в chs: w

Минимальное значение в chs: b

Хотя большинство аспектов этой программы просты для понимания, некоторые ключевые моменты следует пояснить. Во-первых, обратите внимание на то, как объявлен MinMax:

interface MinMax> {

В общем случае, обобщенный интерфейс объявляется так же, как и обобщенный класс. В этом случае тип параметра T имеет верхнюю границу Comparable — интерфейс, определенный в java.lang. Класс, который реализует Comparable, определяет объекты, которые могут быть упорядочены. То есть требование, чтобы T был ограничен сверху Comparable, гарантирует, что MinMax может быть использован только с объектами, которые могут сравниваться между собой. (Более подробную информацию о Comparable можно найти в главе 16.) Отметим, что Comparable сам по себе также является обобщенным интерфейсом (он был преобразован в обобщенную форму в JDK 5). Он принимает параметр типа объектов, которые должны сравниваться.

Далее MinMax реализует класс MyClass. Вот как выглядит объявление класса MyClass:

class MyClass> implements MinMax {

Обратите особое внимание на способ, которым параметр типа T объявлен в MyClass и затем передан MinMax. Поскольку MinMax требует типа, который реализует Comparable, реализующий класс (в данном случае — MinMax) должен специфицировать то же ограничение. Более того, однажды установленное, это ограничение уже не нужно повторять в операторе implements. Фактически так поступать некорректно. Например, следующая строка неверна и не может быть скомпилирована:

// Это не правильно!

class MyClass>

implements MinMax> {

Однажды установленный, параметр типа просто передается интерфейсу без последующих модификаций. Вообще если класс реализует обобщенный интерфейс, то такой класс также должен быть обобщенным, по крайней мере, в тех пределах, где он принимает параметр типа, переданный интерфейсу. Например, следующая попытка объявления MyClass вызовет ошибку:

class MyClass implements MinMax { // Неверно!

Поскольку MyClass не объявляет параметра, нет способа передать его MinMax. В этом случае идентификатор T просто неизвестен, и компилятор выдаст ошибку. Конечно, если класс реализует специфический тип обобщенного интерфейса, как показано ниже:

class MyClass implements MinMax { // OK

, то реализующий класс не обязан быть обобщенным.

Обобщенный интерфейс представляет два преимущества. Во-первых, он может быть реализован для разных типов данных. Во-вторых, он позволяет включить ограничения на типы данных, для которых он может быть реализован. В примере MinMax вместо T могут быть подставлены только типы, совместимые с Comparable.

Вот общий синтаксис обобщенного интерфейса:

interface имя_интерфейса<список_параметров_типов> { // ...

Здесь список_параметров_типов — разделенный запятыми список параметров типов. Когда реализуется обобщенный интерфейс, вы должны специфицировать аргументы типов, как показано ниже:

class имя_класса<список_параметров_типов>

implements имя_интерфейса<список_аргументов_типов> {

Иерархии обобщенных классов


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

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

Ниже приведен пример иерархии, которая использует обобщенный суперкласс:

// Простая иерархия обобщенных классов.

class Gen {

T ob;

Gen(T o) {

ob = o;

}

// Возвращает ob.

T getob() {

return ob;

}

}

// Подкласс Gen.

class Gen2 extends Gen {

Gen2(T o) {

super(o);

}

}

В этой иерархии Gen2 расширяет обобщенный класс Gen. Обратите внимание на то, как в следующей строке объявлен Gen2:

class Gen2 extends Gen {

Параметр типа T специфицирован в Gen2 и также передается Gen в операторе extends. Это значит, что тип, переданный Gen2, будет также передан Gen. Например, следующее объявление

Gen2 num = new Gen2(100);

, передает Integer как параметр типа классу Gen. То есть ob внутри части Gen класса Gen2 будет иметь тип Integer. Отметим также, что Gen2 никак не использует параметр типа T, кроме того, что передает его суперклассу Gen. Даже если подкласс обобщенного суперкласса никак не нуждается в том, чтобы быть обобщенным, несмотря на это он все же должен специфицировать параметры типа, необходимые его обобщенному суперклассу.

Конечно, при необходимости подкласс может добавлять свои собственные параметры типа. Например, ниже показан вариант предыдущей иерархии, в котором Gen2 добавляет собственный параметр типа:

// Подкласс может добавлять свои собственные параметры типа.

class Gen {

T ob; // Объявление объекта типа T

// Передача конструктору

// ссылки на объект типа T.

Gen(T o) {

ob = o;

}

// Возвращает ob.

T getob() {

return ob;

}}

// Подкласс Gen, который определяет

// второй параметр типа по имени V.

class Gen2 extends Gen {

V ob2;

Gen2(T o, V o2) {

super(o);

ob2 = o2;

}

V getob2() {

return ob2;

}}

// Создание объекта типа Gen2.

class HierDemo {

public static void main(String args[]) {

// Создание объектов Gen2 для String и Integer.

Gen2 x =

new Gen2("Значение равно: ", 99);

System.out.print(x.getob());

System.out.println(x.getob2());

}}

Обратите внимание на объявление Gen2, показанное в следующей строке:

class Gen2 extends Gen {

Здесь T — тип, переданный Gen, а V — тип, специфичный для Gen2. V используется для объявления объекта, названного ob2, а также в качестве типа возврата метода getob2(). В main() создается объект Gen2 с типом String для параметра T и типом Integer для параметра V.

Обобщенный подкласс


Абсолютно приемлемо, когда суперклассом для обобщенного класса выступает класс необобщенный. Например, рассмотрим программу:

// Необобщенный класс может быть суперклассом

// для обобщенного подкласса.

// Необобщенный класс.

class NonGen {

int num;

NonGen(int i) {

num = i;

}

int getnum() {

return num;

}}

// Обобщенный подкласс.

class Gen extends NonGen {

T ob; // Объявление объекта типа T

// Передать конструктору объект

// типа T.

Gen(T o, int i) {

super(i);

ob = o;

}

// Возвращает ob.

T getob() {

return ob;

}}

// Создать объект Gen.

class HierDemo2 {

public static void main(String args[]) {

// Создать объект Gen для String.

Gen w = new Gen("Добро пожаловать", 47);

System.out.print(w.getob() + " ");

System.out.println(w.getnum());

}}

Результат работы этой программы показан ниже:

Добро пожаловать 47

Обратите внимание на то, как в этой программе Gen наследуется от NonGen:

class Gen extends NonGen {

Поскольку NonGen — необобщенный класс, никакие аргументы типа не указываются. То есть даже если Gen объявляет тип-параметр T, он не требуется (и не может быть использован) NonGen. То есть Gen наследуется от NonGen обычным способом. Никаких специальных условий не требуется.

Ошибки неоднозначности


Включение в язык обобщений породило новый тип ошибок, от которых вам нужно защищаться: неоднозначность (ambiquity). Ошибки неоднозначности случаются, когда очистка порождает два внешне разных обобщенных объявления, разрешаемых в виде одного очищенного типа, что вызывает конфликт. Вот пример, который включает перегрузку методов:

// Неоднозначность порождается очисткой перегруженных методов.

class MyGenClass {

T ob1;

V ob2;

// ...

// Эти два перегруженных метода неоднозначны

// и не скомпилируются.

void set(T o) {

ob1 = o;

}

void set(V o) {

ob2 = o;

}

}

Обратите внимание, что MyGenClass объявляет два обобщенных типа: T и V. Внутри MyGenClass предпринимается попытка перегрузить set() на основе параметров T и V.

Это выглядит резонным, потому что кажется, что T и V — разные типы. Однако здесь возникают две проблемы неоднозначности.

Первая — судя по тому, как написан MyGenClass, нет требования, чтобы T и V были разными типами. Например, в принципе совершенно корректно сконструировать объект MyGenClass следующим образом:

MyGenClass obj = new MyGenClass()

В этом случае T и V будут заменены String. Это делает обе версии set() идентичными, что, конечно же, представляет собой ошибку.

Вторая, более фундаментальная проблема состоит в том, что очистка типов приводит обе версии метода к следующему виду:

void set(Object o) { // ...

То есть перегрузка set(), как пытается сделать MyGenClass, в действительности неоднозначна.

Ошибки неоднозначности бывает трудно исправить. Например, если вы знаете, что V всегда будет неким подтипом String, то можете попытаться исправить MyGenClass, переписав его объявление следующим образом:

class MyGenClass { // почти OK!

Это позволит скомпилировать MyGenClass, и вы даже сможете создавать экземпляры объектов этого класса примерно так:

MyGenClass x = new MyGenClass();

Это работает, потому что Java может аккуратно определить, когда какой метод должен быть вызван. Однако неоднозначность возникнет, когда вы попытаетесь выполнить строку:

MyGenClass x = new MyGenClass();


В этом случае, поскольку и T, и V являются String, то какую версию set() нужно вызвать?

Честно говоря, в предыдущем примере было бы лучше использовать два метода с разными именами, вместо того чтобы перегружать set(). Часто разрешение неоднозначности требует реструктуризации кода, потому что неоднозначность свидетельствует о концептуальной ошибке в дизайне.

Ограничения на статические члены


Никакой static-член не может использовать тип параметра, объявленный в его классе. Например, все static-члены этого класса являются недопустимыми:

class Wrong {

// Неверно, нельзя создать статические переменные типа T.

static T ob;

// Неверно, ни один статический метод не может использовать T.

static T getob() {

return ob;

}

// Неверно, ни один статический метод не может иметь доступ

// к объекту типа T.

static void showob() {

System.out.println(ob);

}}

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

Ограничения обобщенных массивов


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

Ограничения обобщенных исключений


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

Москва — 2016
1   ...   59   60   61   62   63   64   65   66   67


написать администратору сайта