Параметризованные классы и методы java

Содержание
  1. Generic Types
  2. A Simple Box Class
  3. A Generic Version of the Box Class
  4. Type Parameter Naming Conventions
  5. Invoking and Instantiating a Generic Type
  6. The Diamond
  7. Multiple Type Parameters
  8. Parameterized Types
  9. Core Java. Лекция 6
  10. Стирание типов → невозможность определить параметр типа в Runtime
  11. Стирание типов до Object → невозможность использовать примитивные типы в качестве параметров
  12. Примитивы и дженерики
  13. Нельзя инстанцировать типы-параметры
  14. Решается с помощью метакласса и рефлексии (о которой речь впереди)
  15. Тем более нельзя инстанцировать массив типа-параметра
  16. Массивы и дженерики — лютые враги
  17. Забьём значения кувалдой и устроим heap pollution
  18. Varargs — всё равно массив…​
  19. Компилятор что-то предчувствует…​
  20. Зачем?!
  21. Нельзя параметризовать
  22. Параметры типов нельзя использовать в статическом контексте
  23. Нельзя реализовывать разные параметризации одного и того же интерфейса
  24. Ковариантность массивов vs инвариантность дженериков
  25. Реальная картина
  26. Как быть, если хочется такого?
  27. Так не получится…​
  28. Java Covariant Wildcard Types
  29. Что можно сделать с объектом, типизированным ? extends ?
  30. В обратную сторону (контравариантные типы)
  31. Что можно сделать с объектом, типизированным ? super ?
  32. Unbounded wildcard
  33. Мнемоническое правило
  34. Правила использования wildcard-типов
  35. Wildcard Capture
  36. Метод с type capture
  37. Recursive Generics
  38. Что почитать-посмотреть
  39. Nada Amin & Rose Tate’s example
  40. Radu Grigore’s Example

Generic Types

A generic type is a generic class or interface that is parameterized over types. The following Box class will be modified to demonstrate the concept.

A Simple Box Class

Begin by examining a non-generic Box class that operates on objects of any type. It needs only to provide two methods: set, which adds an object to the box, and get, which retrieves it:

public class Box < private Object object; public void set(Object object) < this.object = object; >public Object get() < return object; >>

Since its methods accept or return an Object, you are free to pass in whatever you want, provided that it is not one of the primitive types. There is no way to verify, at compile time, how the class is used. One part of the code may place an Integer in the box and expect to get Integers out of it, while another part of the code may mistakenly pass in a String, resulting in a runtime error.

Читайте также:  Desktop java 64 bit

A Generic Version of the Box Class

A generic class is defined with the following format:

The type parameter section, delimited by angle brackets (<>), follows the class name. It specifies the type parameters (also called type variables) T1, T2, . and Tn.

To update the Box class to use generics, you create a generic type declaration by changing the code «public class Box» to «public class Box ". This introduces the type variable, T, that can be used anywhere inside the class.

With this change, the Box class becomes:

/** * Generic version of the Box class. * @param the type of the value being boxed */ public class Box  < // T stands for "Type" private T t; public void set(T t) < this.t = t; >public T get() < return t; >>

As you can see, all occurrences of Object are replaced by T. A type variable can be any non-primitive type you specify: any class type, any interface type, any array type, or even another type variable.

This same technique can be applied to create generic interfaces.

Type Parameter Naming Conventions

By convention, type parameter names are single, uppercase letters. This stands in sharp contrast to the variable naming conventions that you already know about, and with good reason: Without this convention, it would be difficult to tell the difference between a type variable and an ordinary class or interface name.

The most commonly used type parameter names are:

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

You'll see these names used throughout the Java SE API and the rest of this lesson.

Invoking and Instantiating a Generic Type

To reference the generic Box class from within your code, you must perform a generic type invocation, which replaces T with some concrete value, such as Integer:

You can think of a generic type invocation as being similar to an ordinary method invocation, but instead of passing an argument to a method, you are passing a type argumentInteger in this case — to the Box class itself.

Type Parameter and Type Argument Terminology: Many developers use the terms "type parameter" and "type argument" interchangeably, but these terms are not the same. When coding, one provides type arguments in order to create a parameterized type. Therefore, the T in Foo is a type parameter and the String in Foo f is a type argument. This lesson observes this definition when using these terms.

Like any other variable declaration, this code does not actually create a new Box object. It simply declares that integerBox will hold a reference to a "Box of Integer", which is how Box is read.

An invocation of a generic type is generally known as a parameterized type.

To instantiate this class, use the new keyword, as usual, but place between the class name and the parenthesis:

The Diamond

In Java SE 7 and later, you can replace the type arguments required to invoke the constructor of a generic class with an empty set of type arguments (<>) as long as the compiler can determine, or infer, the type arguments from the context. This pair of angle brackets, <>, is informally called the diamond. For example, you can create an instance of Box with the following statement:

For more information on diamond notation and type inference, see Type Inference.

Multiple Type Parameters

As mentioned previously, a generic class can have multiple type parameters. For example, the generic OrderedPair class, which implements the generic Pair interface:

public interface Pair  < public K getKey(); public V getValue(); >public class OrderedPair implements Pair  < private K key; private V value; public OrderedPair(K key, V value) < this.key = key; this.value = value; >public K getKey() < return key; >public V getValue() < return value; >>

The following statements create two instantiations of the OrderedPair class:

Pair p1 = new OrderedPair("Even", 8); Pair p2 = new OrderedPair("hello", "world");

The code, new OrderedPair , instantiates K as a String and V as an Integer. Therefore, the parameter types of OrderedPair's constructor are String and Integer, respectively. Due to autoboxing, it is valid to pass a String and an int to the class.

As mentioned in The Diamond, because a Java compiler can infer the K and V types from the declaration OrderedPair , these statements can be shortened using diamond notation:

OrderedPair p1 = new OrderedPair<>("Even", 8); OrderedPair p2 = new OrderedPair<>("hello", "world");

To create a generic interface, follow the same conventions as for creating a generic class.

Parameterized Types

You can also substitute a type parameter (that is, K or V) with a parameterized type (that is, List ). For example, using the OrderedPair example:

OrderedPairBox > p = new OrderedPair<>("primes", new Box(. ));

Источник

Core Java. Лекция 6

rtvscompiletime

Стирание типов → невозможность определить параметр типа в Runtime

rawtype

//ошибка компиляции! не знаем мы в рантайме параметр типа! if (a instanceof PairString>) . //вот так -- получится. if (a instanceof Pair) . 

Стирание типов до Object → невозможность использовать примитивные типы в качестве параметров

//увы, невозможно! Listint> integers = . //ошибка компиляции! ListInteger> integers = . integers.add(42); /*под капотом будет autoboxing: integers.add(Integer.valueOf(42);*/ int v = integers.get(0); /*под капотом будет unboxing: v = integers.get(0).intValue();*/

Примитивы и дженерики

  • ArrayList → IntArrayList ,
  • HashMap → Int2ObjectMap (ВНИМАНИЕ: реальная потребность в таких библиотеках возникает редко!!)

Нельзя инстанцировать типы-параметры

class Pair < T newValue < return new T(); //увы, ошибка компиляции! > >

Решается с помощью метакласса и рефлексии (о которой речь впереди)

class Container < //bounded wildcard type, речь впереди Classextends T> clazz; Container(Classextends T> clazz) < this.clazz = clazz; > T newInstance() throws ReflectiveOperationException < //если нашёлся открытый конструктор без параметров! return clazz.newInstance(); > > Container container1 = new Container<>(Employee.class);

Тем более нельзя инстанцировать массив типа-параметра

public T[] toArray()< //Не скомпилируется return new T[size]; >

Решается передачей параметра, например, в ArrayList:

/* Если массив достаточных размеров -- используе м его, если недостаточных -- конструируем новый через рефлексию*/ public T[] toArray(T[] a)

Массивы и дженерики — лютые враги

//Не скомпилируется: Generic Array Creation. ListString>[] a = new ArrayListString>[10]; //. ведь такой массив не будет иметь //полную информацию о своих элементах!

Забьём значения кувалдой и устроим heap pollution

ListString>[] a = (ListString>[])new List[10]; Object[] objArray = a; objArray[0] = List.of("foo"); a[1] = List.of(new Manager()); //не скомпилируется! objArray[1] = List.of(new Manager()); //скомпилируется и выполнится! //Runtime error: Manager cannot be cast to String String s = a[1].get(0); //. это и называется heap pollution.

Varargs — всё равно массив…​

Тот же heap pollution, что и в прошлом примере:

static void dangerous(ListString>. stringLists)< ListInteger> intList = List.of(42); Object[] objects = stringLists; objects[0] = intList; //ClassCastException String s = stringLists[0].get(0); > dangerous(new ArrayList<>());

Компилятор что-то предчувствует…​

varargswarning

Чтобы успокоить компилятор, надо поставить аннотацию @SafeVarargs :

/*Я даю честное слово, что не буду устраивать heap pollution!*/ @SafeVarargs static void dangerous(ListString>. 

Зачем?!

  • Не записывать ничего в элементы массива,
  • Не раздавать ссылку на массив параметров наружу.

Нельзя параметризовать

  • ловля исключений — это проверка их типов,
  • дальше сырых типов мы в рантайме проверить не можем.

Параметры типов нельзя использовать в статическом контексте

public class Container < private static T value; //не скомпилируется. public static T getValue(); //не скомпилируется > //Статический контекст ОДИН НА ВСЕХ Container.getValue(); Container.getValue();

Нельзя реализовывать разные параметризации одного и того же интерфейса

Source code

class Employee implements Comparable< @Override int compareTo(Employee e) < . >> class Manager extends Employee implements Comparable < @Override int compareTo(Manager m) < . >>
class Manager extends Employee implements Comparable< //bridge method for Employee int compareTo(Object m) < return compareTo((Manager) m); > //bridge method for Manager int compareTo(Object e) < return compareTo((Employee) e); > //CLASH. >

Ковариантность массивов vs инвариантность дженериков

manemp

covariance

Manager[] managers = . Employee lowlyEmployee = . Employee[] e = managers; /*ArrayStoreException in runtime*/ e[0] = lowlyEmployee;

invariance

List managers = . Employee lowlyEmployee = . /*Тупо не скомпилируется*/ List e = managers;

Реальная картина

realpicture

Как быть, если хочется такого?

manempperson

MyList managers = . MyList employees = . //Допустимые варианты, хотим чтоб компилировалось! employees.addAllFrom(managers); managers.addAllTo(employees); //Недопустимые варианты, не хотим чтоб компилировалось! managers.addAllFrom(employees); employees.addAllTo(managers);

Так не получится…​

//можно переносить только одинаково типизированные списки class MyList implements Iterable < void add(E item) < . >void addAllFrom(MyList list) < for (E item : list) this.add(item); > void addAllTo(MyList list) < for (E item : this) list.add(item); > . > MyList managers = . ; MyList employees = . ; employees.addAllFrom(managers); managers.addAllTo(employees);

Java Covariant Wildcard Types

wildext

class MyList implements Iterable < //для MyList "пролезет" MyList!! void addAllFrom (MyListextends E> list)< for (E item: list) add(item); > >

Что можно сделать с объектом, типизированным ? extends ?

Listextends E> list = . //это понятно E e1 = list.get(. ); E e2 = . ; //не скомпилируется! ПОЧЕМУ?? list.add(e2); //скомпилируется. ПОЧЕМУ?? list.add(null);

В общем, addAllTo реализовать не получится…​

В обратную сторону (контравариантные типы)

wildsup

class MyList implements Iterable < //для MyList "пролезет" MyList!! void addAllTo (MyListsuper E> list) < for (E item: this) list.add(item); > >

Что можно сделать с объектом, типизированным ? super ?

Listsuper E> list = . E e1 = . ; //скомпилируется! list.add(e1); list.add(null); //Только Object. ПОЧЕМУ?? Object e2 = list.get(. );

Unbounded wildcard

  • List — это то же, что List . (Вопрос: почему не ?)
  • Брать элементы можем, но тип только Object .
  • Класть можем только null.

Мнемоническое правило

Producer Extends, Consumer Super

public static T max (Collectionextends T> coll, Comparatorsuper T> comp) Collections.max(ListInteger>, ComparatorNumber>) Collections.max(ListString>, ComparatorObject>)

Правила использования wildcard-типов

  • Используются в аргументах методов и локальных переменных.
  • Невидимы пользователю API, не должны возвращаться.
  • Их цель — принимать те аргументы, которые надо принимать, и отвергать те аргументы, которые надо отвергать.
  • Должны быть используемы в API, иначе API будет слишком «жёстким» и непригодным для использования.

Wildcard Capture

public static void swap(Pair p) < Object f = p.getFirst(); Object s = p.getSecond(); //УУУППС!! // (мы знаем, что они правильного типа, // но ничего не можем поделать) p.setFirst(. ); p.setSecond(. ); >

Метод с type capture

public static void swap(Pair p) < swapHelper(p); >private static void swapHelper(Pair p)

Recursive Generics

class Holderextends Holder>< E value; SELF setValue(E value)< this.value = value; return (SELF) this; > > class StringHolder extends HolderString, StringHolder> < void doSmth() ; > new StringHolder().setValue("aaa").doSmth();

Что почитать-посмотреть

Nada Amin & Rose Tate’s example

class Unsound < static class Constrainextends A> <> static class Bind < extends A> A upcast(Constrain constrain, B b) < return b; > > static U coerce(T t) < Constrainsuper T> constrain = null; Bind bind = new Bind(); return bind.upcast(constrain, t); > public static void main(String[] args) < String zero = Unsound.Integer,String>coerce(0); > >

Radu Grigore’s Example

class Sample < interface BadList extends ListListsuper BadListsuper T>>> <> public static void main(String[] args) < BadListsuper String> badList = null; Listsuper BadListsuper String>> list = badList; > >

Источник

Оцените статью