Хотя язык Java позволяет создавать обобщенные типы и методы, которые используют параметры типа, однако на уровне виртуальной машины Java в реальности
не существует никаких обобщенных типов и методов — все объекты принадлежат обычным классам. При компиляции параметры типа удаляются и заменяются либо типом Object для параметров типа без ограничений, либо их типами-ограничениями.
Данный процесс называется type erasure (стирание типов).
Например, возьмем следующий обобщенный класс:
class Person<T>{
private T id;
private String name;
T getId(){ return id; }
String getName() { return name; }
Person(T id, String name){
this.id = id;
this.name = name;
}
}
Здесь поскольку для параметра T не установлено ограничений, то он просто заменяется на Object. То есть в итоге мы получим следующий класс:
class Person{
private Object id;
private String name;
Object getId(){ return id; }
String getName() { return name; }
Person(Object id, String name){
this.id = id;
this.name = name;
}
}
Причем без разницы, какими типами типизируется объект Person в программе:
Person tom = new Person<Integer>(456, "Tom");
Person bob = new Person<String>("qwert", "Bob");
В обоих этих случаях для среды выполнения Java параметр T будет представлять тип Object.
Если результат метода или поле класса представляет параметр типа и этот результат метода или поле присваиваются внешней переменной, то среда выполняет необходимые преобразования. Например, если мы имеет следующий код:
var tom = new Person<Integer>(456, "Tom"); int tomId = tom.getId(); System.out.println(tomId);
То среда выполнения трактует этот код наподобие следующего:
var tom = new Person(456, "Tom"); int tomId = (int) tom.getId(); System.out.println(tomId);
Теперь рассмотрим другой пример - применение ограничений типа:
class Message{
private String text; // текст сообщения
String getText(){ return text; }
Message(String text)
{
this.text = text;
}
}
class Messenger<T extends Message> {
private T message;
T getMessage() { return this.message; }
Messenger(T message) { this.message = message; }
void send() {
System.out.println("Отправляется сообщение: " + message.getText());
}
}
В данном случае класс Messenger типизируется параметром T, для которого установлено ограничение в виде типа Message, поэтому внутри класса Messenger тип T
заменяется на Message:
class Messenger {
private Message message;
Message getMessage() { return this.message; }
Messenger(Message message) { this.message = message; }
void send() {
System.out.println("Отправляется сообщение: " + message.getText());
}
}
Если для одного параметра типа установлено несколько ограничений, то параметр тпиа заменяется на тип из первого ограничения. Например:
class Messenger<T extends Message & Printable> {
void sendMessage(T message){
message.print();
}
}
interface Printable{
void print();
}
class Message{
private String text; // текст сообщения
String getText(){ return text; }
Message(String text){
this.text = text;
}
}
Здесь в классе Messenger параметр типа T должен представлять одновременно класс Message и интерфейс Printable (класс, который наследуется от Message и реализует интерфейс Printable).
В итоге для виртуальной срежы Java этот класс будет выглядеть наподобие следующего:
class Messenger {
void sendMessage(Message message){
((Printable)message).print();
}
}
То есть параметр типа T заменяется на первое ограничение - тип Message, а если используется функционал из второго ограничения, то выполняется преобразование.