Java t extends number

Ограничения типов, метасимвольные аргументы, обобщенные методы и конструкторы

Продолжаем тему Generics (обобщения) и начнем с вопроса ограничений обобщенных типов. О чем здесь речь? Представьте, что нам нужно реализовать метод с некоторым типом данных T, который бы возвращал максимальное из двух значений:

class PointT> { public T x, y; Point(T x, T y) { this.x = x; this.y = y; } double getMax() { double xd = x.doubleValue(); double yd = y.doubleValue(); return (xd  yd) ? yd : xd; } }

Здесь я воспользовался классом Point, который мы создали на предыдущем занятии и добавил метод getMax(), возвращающий максимальную координату. Но если попытаться скомпилировать эту программу, то возникнет ошибка, так как не все типы данных поддерживают метод doubleValue(). Этот метод существует у типов, наследуемых от класса Number. Поэтому, если бы мы указали компилятору, что в качестве T будем использовать только числовые типы: Integer, Short, Double, Float, то проблем с вызовом doubleValue() не было бы, т.к. все эти типы реализуют этот метод. Так вот, чтобы сделать такой трюк и ограничить T числовыми типами, следует использовать такую запись:

class PointT extends Number> { . }

Здесь мы говорим, что в качестве T можно передавать любой тип данных, у которого базовый класс является Number. А это, как раз, все числовые типы. Теперь, при компиляции программы никаких ошибок не появляется благодаря введенному ограничению на типы данных.

Создадим в методе main объект класса Point и вызовем метод getMax():

public class Main { public static void main(String[] args) { PointInteger> pt = new PointInteger>(1, 2); double max = pt.getMax(); System.out.println( max ); } }

В консоли увидим значение 2.0. Обратите внимание, несмотря на то, что указан тип Integer, метод doubleValue() для него возвратит вещественное значение, т.е. целое число будет приведено к типу double и возвращено этим методом. Это в данном случае удобно, т.к. double можно использовать как универсальный тип представления разных действительных чисел.

А что будет, если мы попробуем создать объект с нечисловым типом, например, строковым:

PointString> ptS = new PointString>("1", "2");

В этом случае возникнет ошибка в момент компиляции программы, т.к. тип String не наследуется от класса Number и не подходит под наши ограничения.

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

class NumbersT> { } class PointT extends NumbersInteger>> { . }

В этом случае в качестве типов можно использовать любые типы данных, унаследованных от класса Number и с указанным в нем типом Integer.

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

interface I1 {} interface I2 {}

И, затем, у типа класса Point укажем их после имени базового класса:

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

Также можно указывать только интерфейсы без базового класса:

class PointT extends I1, I2> { . }

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

Метасимвольные аргументы

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

class PointT extends Number> { public T x, y; . boolean equalsPoint(PointT> pt) { return (this.x.doubleValue() == pt.x.doubleValue() && this.y.doubleValue() == pt.y.doubleValue()); } . }

И, далее в методе main() создаем два объекта и сравниваем их с помощью нашего метода equalsPoint():

public class Main { public static void main(String[] args) { PointInteger> pt = new PointInteger>(1, 2); PointDouble> pt2 = new PointDouble>(1.0, 2.0); System.out.println( pt.equalsPoint(pt2) ); } }

Но при компиляции возникнет ошибка в строчке

Дело в том, что при вызове метода pt.equalsPoint() в качестве типа T будет подставлен класс Integer и ожидается аргумент типа Point, а мы передаем аргумент с типом Point. Получается, что метод equalsPoint() можно использовать с теми же типами данных, что и объект pt и у нас перестает работать механизм обобщения. Как поправить эту ситуацию? Для этого в Java существует специальный метасимвольный аргумент, который задается с помощью символа ‘?’. И если вместо T записать знак вопроса:

boolean equalsPoint(Point pt) { . }

то проблема будет решена. Теперь, метод equalsPoint() принимает любой тип данных класса Point.

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

boolean equalsPoint(Point extends Number> pt) { . }

или, добавляя интерфейсы. То есть, ограничения прописываются и работают абсолютно также, как и с обобщенными типами T.

Обобщенные методы

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

class Math { public static T> boolean isIn(T val, T[] ar) { for(T v: ar) if(v.equals(val)) return true; return false; } }

Смотрите, мы здесь перед типом метода указываем обобщенный тип T, и далее используем его при определении аргументов. Затем, в методе main() можно вызвать этот метод следующим образом:

Short ar[] = {1,2,3,4}; Short val = 4; boolean flIn = Math.isIn(val, ar); System.out.println( flIn );

Или, с явным указанием обобщенного типа:

Тогда в качестве аргументов можно передавать только экземпляры классов Short.

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

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

class Digit { public double value; T extends Number>Digit(T value) { this.value = value.doubleValue(); } }

Обратите внимание, сам класс Digit не является обобщенным, только его конструктор. Далее, в методе main() можно его использовать с любыми типами числовых данных: Integer, Float, Short и т.д.

public class Main { public static void main(String[] args) { Digit d1 = new Digit(10); Digit d2 = new Digit(10.5); Digit d3 = new Digit(10.5f); System.out.println(d1.value + " " + d2.value + " " + d3.value); } }

Вот так в языке Java можно накладывать ограничения на используемые типы данных, использовать метасимвольные аргументы и обобщенные конструкторы и методы.

Видео по теме

#11 Концепция объектно-ориентированного программирования (ООП)

#12 Классы и создание объектов классов

#13 Конструкторы, ключевое слово this, инициализаторы

#14 Методы класса, сеттеры и геттеры, public, private, protected

#15 Пакеты, модификаторы конструкторов и классов

#16 Ключевые слова static и final

#17 Внутренние и вложенные классы

#18 Как делается наследование классов

#19 Ключевое слово super, оператор instanceof

#20 Модификаторы private и protected, переопределение методов, полиморфизм

#21 Абстрактные классы и методы

#22 Интерфейсы — объявление и применение

#23 Интерфейсы — приватные, статические и дефолтные методы, наследование интерфейсов

#24 Анонимные внутренние классы

#26 Обобщения классов (Generics)

#27 Ограничения типов, метасимвольные аргументы, обобщенные методы и конструкторы

#28 Обобщенные интерфейсы, наследование обобщенных классов

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

Источник

Bounded Type Parameters

There may be times when you want to restrict the types that can be used as type arguments in a parameterized type. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

To declare a bounded type parameter, list the type parameter’s name, followed by the extends keyword, followed by its upper bound, which in this example is Number . Note that, in this context, extends is used in a general sense to mean either «extends» (as in classes) or «implements» (as in interfaces).

public class Box  < private T t; public void set(T t) < this.t = t; >public T get() < return t; >public extends Number> void inspect(U u) < System.out.println("T: " + t.getClass().getName()); System.out.println("U: " + u.getClass().getName()); >public static void main(String[] args) < BoxintegerBox = new Box(); integerBox.set(new Integer(10)); integerBox.inspect("some text"); // error: this is still String! > >

By modifying our generic method to include this bounded type parameter, compilation will now fail, since our invocation of inspect still includes a String :

Box.java:21: inspect(U) in Box cannot be applied to (java.lang.String) integerBox.inspect("10"); ^ 1 error

In addition to limiting the types you can use to instantiate a generic type, bounded type parameters allow you to invoke methods defined in the bounds:

public class NaturalNumber  < private T n; public NaturalNumber(T n) < this.n = n; >public boolean isEven() < return n.intValue() % 2 == 0; > // . >

The isEven method invokes the intValue method defined in the Integer class through n.

Multiple Bounds

The preceding example illustrates the use of a type parameter with a single bound, but a type parameter can have multiple bounds:

A type variable with multiple bounds is a subtype of all the types listed in the bound. If one of the bounds is a class, it must be specified first. For example:

Class A < /* . */ >interface B < /* . */ >interface C < /* . */ >class D  < /* . */ >

If bound A is not specified first, you get a compile-time error:

class D  < /* . */ >// compile-time error

Источник

Читайте также:  Php run from cli
Оцените статью