- Type Inference
- Type Inference and Generic Methods
- Type Inference and Instantiation of Generic Classes
- Type Inference and Generic Constructors of Generic and Non-Generic Classes
- Target Types
- Как получить информацию о generic типе в Java во время выполнения?
- Как определять, что дженерик-класс типизирован нужным конкретным типом?
Type Inference
Type inference is a Java compiler’s ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.
To illustrate this last point, in the following example, inference determines that the second argument being passed to the pick method is of type Serializable:
static T pick(T a1, T a2) < return a2; >Serializable s = pick("d", new ArrayList());
Type Inference and Generic Methods
Generic Methods introduced you to type inference, which enables you to invoke a generic method as you would an ordinary method, without specifying a type between angle brackets. Consider the following example, BoxDemo , which requires the Box class:
public class BoxDemo < public static void addBox(U u, java.util.Listboxes) < Boxbox = new Box<>(); box.set(u); boxes.add(box); > public static void outputBoxes(java.util.List boxes) < int counter = 0; for (Boxbox: boxes) < U boxContents = box.get(); System.out.println("Box #" + counter + " contains [" + boxContents.toString() + "]"); counter++; >> public static void main(String[] args) < java.util.ArrayList> listOfIntegerBoxes = new java.util.ArrayList<>(); BoxDemo.addBox(Integer.valueOf(10), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes); BoxDemo.outputBoxes(listOfIntegerBoxes); > >
The following is the output from this example:
Box #0 contains [10] Box #1 contains [20] Box #2 contains [30]
The generic method addBox defines one type parameter named U . Generally, a Java compiler can infer the type parameters of a generic method call. Consequently, in most cases, you do not have to specify them. For example, to invoke the generic method addBox , you can specify the type parameter with a type witness as follows:
BoxDemo. addBox(Integer.valueOf(10), listOfIntegerBoxes);
Alternatively, if you omit the type witness,a Java compiler automatically infers (from the method’s arguments) that the type parameter is Integer :
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
Type Inference and Instantiation of Generic Classes
You can replace the type arguments required to invoke the constructor of a generic class with an empty set of type parameters ( <> ) as long as the compiler can infer the type arguments from the context. This pair of angle brackets is informally called the diamond.
For example, consider the following variable declaration:
You can substitute the parameterized type of the constructor with an empty set of type parameters (<>):
Note that to take advantage of type inference during generic class instantiation, you must use the diamond. In the following example, the compiler generates an unchecked conversion warning because the HashMap() constructor refers to the HashMap raw type, not the Map> type:
Map> myMap = new HashMap(); // unchecked conversion warning
Type Inference and Generic Constructors of Generic and Non-Generic Classes
Note that constructors can be generic (in other words, declare their own formal type parameters) in both generic and non-generic classes. Consider the following example:
class MyClass < MyClass(T t) < // . >>
Consider the following instantiation of the class MyClass :
This statement creates an instance of the parameterized type MyClass ; the statement explicitly specifies the type Integer for the formal type parameter, X , of the generic class MyClass . Note that the constructor for this generic class contains a formal type parameter, T . The compiler infers the type String for the formal type parameter, T , of the constructor of this generic class (because the actual parameter of this constructor is a String object).
Compilers from releases prior to Java SE 7 are able to infer the actual type parameters of generic constructors, similar to generic methods. However, compilers in Java SE 7 and later can infer the actual type parameters of the generic class being instantiated if you use the diamond ( <> ). Consider the following example:
MyClass myObject = new MyClass<>("");
In this example, the compiler infers the type Integer for the formal type parameter, X , of the generic class MyClass . It infers the type String for the formal type parameter, T , of the constructor of this generic class.
Note: It is important to note that the inference algorithm uses only invocation arguments, target types, and possibly an obvious expected return type to infer types. The inference algorithm does not use results from later in the program.
Target Types
The Java compiler takes advantage of target typing to infer the type parameters of a generic method invocation. The target type of an expression is the data type that the Java compiler expects depending on where the expression appears. Consider the method Collections.emptyList , which is declared as follows:
Consider the following assignment statement:
List listOne = Collections.emptyList();
This statement is expecting an instance of List ; this data type is the target type. Because the method emptyList returns a value of type List , the compiler infers that the type argument T must be the value String . This works in both Java SE 7 and 8. Alternatively, you could use a type witness and specify the value of T as follows:
List listOne = Collections.emptyList();
However, this is not necessary in this context. It was necessary in other contexts, though. Consider the following method:
void processStringList(List stringList) < // process stringList >
Suppose you want to invoke the method processStringList with an empty list. In Java SE 7, the following statement does not compile:
processStringList(Collections.emptyList());
The Java SE 7 compiler generates an error message similar to the following:
List cannot be converted to List
The compiler requires a value for the type argument T so it starts with the value Object . Consequently, the invocation of Collections.emptyList returns a value of type List , which is incompatible with the method processStringList . Thus, in Java SE 7, you must specify the value of the value of the type argument as follows:
processStringList(Collections.emptyList());
This is no longer necessary in Java SE 8. The notion of what is a target type has been expanded to include method arguments, such as the argument to the method processStringList . In this case, processStringList requires an argument of type List . The method Collections.emptyList returns a value of List , so using the target type of List , the compiler infers that the type argument T has a value of String . Thus, in Java SE 8, the following statement compiles:
processStringList(Collections.emptyList());
Как получить информацию о generic типе в Java во время выполнения?
Работаю с библиотекой jackson под Android и столкнулся с непониманием работы с Generic’ами через рефлекцию.
В библиотеке есть костыль TypeReference:
package com.fasterxml.jackson.core.type; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * This generic abstract class is used for obtaining full generics type information * by sub-classing; it must be converted to implementation * (implemented by
JavaType
from "databind" bundle) to be used. * Class is based on ideas from * http://gafter.blogspot.com/2006/12/super-type-tokens.html, * Additional idea (from a suggestion made in comments of the article) * is to require bogus implementation ofComparable
* (any such generic interface would do, as long as it forces a method * with generic type to be implemented). * to ensure that a Type argument is indeed given. ** Usage is by sub-classing: here is one way to instantiate reference * to generic type
List<Integer>
: ** TypeReference ref = new TypeReference<List<Integer>>() < >; ** which can be passed to methods that accept TypeReference, or resolved * using
TypeFactory
to obtain . */ public abstract class TypeReference implements Comparable< protected final Type _type; protected TypeReference() < Type superClass = getClass().getGenericSuperclass(); if (superClass instanceof Class) < // sanity check, should never happen throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information"); >/* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect * it is possible to make it fail? * But let's deal with specific * case when we know an actual use case, and thereby suitable * workarounds for valid case(s) and/or error to throw * on invalid one(s). */ _type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; > public Type getType() < return _type; >/** * The only reason we define this method (and require implementation * of Comparable
) is to prevent constructing a * reference without type information. */ @Override public int compareTo(TypeReference o) < return 0; >// just need an implementation, not a good one. hence ^^^ >Когда я делаю так, все норм:
TypeReference typeReference = new TypeReference> < >;
Так не работает, теряется информация о типе:
class Bar < public Bar() < TypeReference typeReference = new TypeReference
< >; > > Bar br = new Bar;
Я в курсе про очистку и все такое. Но если посмотреть в отладчике, то внутри TypeReference в первом случае будет такое:
_type = «java.util.List»Кто-нибудь может мне объяснить, что происходит в данном примере?
Как определять, что дженерик-класс типизирован нужным конкретным типом?
Здравствуйте. Иногда возникает необходимость проверить, является ли объект представителем некоторого дженерик-класса, параметризованным конкретным типов. Пока обычно не делаю такую проверку, проверяю только, что объект является представителем самого дженерик-класса. Но дженерик-класс же может быть параметризован в каждом конкретном случае каким-то своим типом. Если типы не будет совпадать, то я получу ошибку. Как проверить, что типы совпадают?
Пример кода:public abstract class BaseComboBox extends JComboBox
< //некоторый код @Override public ComboboxItemgetSelectedItem() < Object selectedItem = super.getSelectedItem(); if (selectedItem == null) return null; else < if (selectedItem.getClass() == ComboboxItem.class) < // (1) return (ComboboxItem) selectedItem; // (2) > else < return null; >> > //некоторый код > public class ComboboxItem < private String caption; private T value; public ComboboxItem(String caption, T value) < this.caption = caption; this.value = value; >@Override public String toString() < return caption; >public String getCaption() < return caption; >public T getValue() < return value; >> В данном примере в строчке (1) я просто проверяю, что совпадают классы, но не проверяю, что совпадает тип в угловых скобках. В (2) делаю кастинг, и здесь может быть ошибка, если типы разные. (Хотя в данном случае, типы всегда будут совпадать, в других местах подобный код может вызвать эксепшн)
Во-первых, обобщения — это инструкции только для компилятора. В байткоде их вообще нет и виртуальная машина о них ничего не знает. Во-вторых, ваш код нарушает принципы ООП. Как только вам понадобилась проверка типов в рантайме, знайте, что написали плохой код.
Извините за глупые вопросы, но:
1. А где именно грубое нарушение ООП?
2. В JComboBox объекты для списка хранятся в виде объектов типа Object. Чтобы быть точно уверенным, что это объект нужно мне типа, я делаю проверку. Единственное, что объекты, которые я вставляю в комбо-бокс — параметризованы типом, и если параметрические типы не совпадают, то может быть ошибка.MaxLich, неважно, в каком виде элементы хранятся, важно то, что попасть в JComboBox> могут только ComboboxItem . Соответственно, ComboboxItem getSelectedItem() всегда будет возвращать только ComboboxItem . Дополнительные проверки просто не нужны, компилятор уже позаботился о них за вас.
Сергей Горностаев, А, ну это. Да, об этом знаю. Примерно об этом я писал под примером кода. Правда, базовый класс всё равно возвращает Object, и надо делать кастинг. IDEA, например, подчёркивает место кастинга как возможное место экспшенов.
А где именно нарушения базовых принципов ООП?MaxLich, а вы замените оперирующий сырыми типами getSelectedItem() на типизированный ComboboxItem getItemAt(getSelectedIndex()) и живите спокойно.
Ну, пожалуй, про грубое нарушение базовых принципов я загнул. Но в целом метод должны работать с одним типом, реализующим минимально необходимую для работы абстракцию и не приводить типы. Иначе в коде появляются сильные зацепления и он становится хрупким.Сергей Горностаев, А да, что-то протупил. Можно сделать так, как Вы предложили. Спасибо. Сейчас исправлю код этого метода и протестирую.