Estou participando do "Programa Nuggets · Sail"
visão geral
O que são genéricos? Por que você precisa de genéricos? Como usar? Qual é o princípio? Como melhorar? Esta é basicamente a maneira correta de aprendermos uma tecnologia. Este artigo irá elaborar na ordem acima e apresentar os genéricos que conheço.
o que é genérico
A essência dos genéricos é um tipo parametrizado , ou seja, especificar um parâmetro para o tipo e, em seguida, especificar o valor específico do parâmetro ao usá-lo, para que o tipo possa ser determinado ao usá-lo. Esse tipo de parâmetro pode ser usado em classes, interfaces e métodos e é chamado de classes genéricas, interfaces genéricas e métodos genéricos, respectivamente.
Por que você precisa de genéricos
O principal objetivo da introdução de genéricos em Java é avançar o trabalho de verificação de tipo para o tempo de compilação e entregar o trabalho de **type cast (cast)** para o compilador, para que você possa obter exceções de conversão de tipo e remover o código-fonte em tempo de compilação Digite o código de coerção em . Por exemplo
objeto de efeito genérico
Existem três maneiras de usar genéricos: classes genéricas, interfaces genéricas e métodos genéricos.
classe genérica
Especificar parâmetros ao declarar uma classe constitui uma classe genérica. Por exemplo, no código a seguir, é especificado T
como um parâmetro de tipo, então esse tipo pode ser usado nesta classe. Por exemplo, declare T
a variável do tipo name
, declare T
o parâmetro formal do tipo param
e assim por diante.
public class Generic<T> {
public T name;
public Generic(T param){
name=param;
}
public T m(){
return name;
}
}
Então, ao usar uma classe, você pode passar o tipo correspondente para criar instâncias de tipos diferentes, pois o código a seguir passa em String
, Integer
, Boolean
3 tipos, respectivamente:
private static void genericClass()
{
Generic<String> str=new Generic<>("总有刁民想害朕");
Generic<Integer> integer=new Generic<>(110);
Generic<Boolean> b=new Generic<>(true);
System.out.println("传入类型:"+str.name+" "+integer.name+" "+b.name);
}
A saída é:传入类型:总有刁民想害朕 110 true
Se não houver nenhum tipo genérico, precisamos definir três classes para obter o efeito acima ou uma classe contendo três construtores e três métodos de valor.
interface genérica
A definição de interface genérica é basicamente a mesma de classe genérica
public interface Generator<T> {
public T produce();
}
método genérico
Isso é relativamente complicado. Quando entrei em contato pela primeira vez, também fiquei confuso. Depois de entender as características, não é tão difícil.
public class Generic<T> {
public T name;
public Generic(){}
public Generic(T param){
name=param;
}
public T m(){
return name;
}
public <E> void m1(E e){ }
public <T> T m2(T e){ }
}
重点看public <E> void m1(E e){ }
这就是一个泛型方法,判断一个方法是否是泛型方法关键看方法返回值前面有没有使用<>
标记的类型,有就是,没有就不是。这个<>
里面的类型参数就相当于为这个方法声明了一个类型,这个类型可以在此方法的作用块内自由使用。 上面代码中,m()
方法不是泛型方法,m1()
与m2()
都是。值得注意的是m2()
方法中声明的类型T
与类申明里面的那个参数T
不是一个,也可以说方法中的T
隐藏了类型中的T
。下面代码中类里面的T
传入的是String
类型,而方法中的T
传入的是Integer
类型。
Generic<String> str=new Generic<>("总有刁民想害朕");
str.m2(123);
通配符?
?代表任意类型,例如有如下函数:
public void m3(List<?>list){
for (Object o : list) {
System.out.println(o);
}
}
其参数类型是?
,那么我们调用的时候就可以传入任意类型的List
,如下
str.m3(Arrays.asList(1,2,3));
str.m3(Arrays.asList("总有刁民","想害","朕"));
但是说实话,单独一个?
意义不大,因为大家可以看到,从集合中获取到的对象的类型是Object
类型的,也就只有那几个默认方法可调用,几乎没什么用。如果你想要使用传入的类型那就需要强制类型转换,这是我们接受不了的,不然使用泛型干毛。其真正强大之处是可以通过设置其上下限达到类型的灵活使用,且看下面分解重点内容。
通配符上界
通配符上界使用<? extends T>
的格式,意思是需要一个T类型或者T类型的子类,一般T类型都是一个具体的类型,例如下面的代码。
public void printIntValue(List<? extends Number> list) {
for (Number number : list) {
System.out.print(number.intValue()+" ");
}
}
这个意义就非凡了,无论传入的是何种类型的集合,我们都可以使用其父类的方法统一处理。
通配符下界
通配符下界使用<? super T>
的格式,意思是需要一个T类型或者T类型的父类,一般T类型都是一个具体的类型,例如下面的代码。
public void fillNumberList(List<? super Number> list) {
list.add(new Integer(0));
list.add(new Float(1.0));
}
至于什么时候使用通配符上界,什么时候使用下界,在**《Effective Java》中有很好的指导意见:遵循PECS原则,即producer-extends,consumer-super.** 换句话说,如果参数化类型表示一个生产者,就使用 <? extends T>
;如果参数化类型表示一个消费者,就使用<? super T>
。
Java泛型原理解析
为什么人们会说Java的泛型是伪泛型呢,就是因为Java在编译时擦除了所有的泛型信息,所以Java根本不会产生新的类型到字节码或者机器码中,所有的泛型类型最终都将是一种原始类型,那样在Java运行时根本就获取不到泛型信息。
擦除
Java编译器编译泛型的步骤:
- 检查泛型的类型 ,获得目标类型
- 擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换)
- 调用相关函数,并将结果强制转换为目标类型。
如何擦除: 当擦除泛型类型后,留下的就只有原始类型了,例如上面的代码,原始类型就是ArrayList
。擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换),如下所示
擦除之前:
//泛型类型
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
擦除之后:
//原始类型
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
因为在Pair<T>
中,T
是一个无限定的类型变量,所以用Object替换。如果是Pair<T extends Number>
,擦除后,类型变量用Number类型替换。
Gson是如何把泛型转换成目标类型的
TypeToken
/**
* Represents a generic type {@code T}. Java doesn't yet provide a way to
* represent generic types, so this class does. Forces clients to create a
* subclass of this class which enables retrieval the type information even at
* runtime.
*
* <p>For example, to create a type literal for {@code List<String>}, you can
* create an empty anonymous inner class:
*
* <p>
* {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
*
* <p>This syntax cannot be used to create type literals that have wildcard
* parameters, such as {@code Class<?>} or {@code List<? extends CharSequence>}.
*
* @author Bob Lee
* @author Sven Mawson
* @author Jesse Wilson
*/
public class TypeToken<T> {
final Class<? super T> rawType;
final Type type;
final int hashCode;
/**
* Constructs a new type literal. Derives represented class from type
* parameter.
*
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
* parameter in the anonymous class's type hierarchy so we can reconstitute it
* at runtime despite erasure.
*/
@SuppressWarnings("unchecked")
protected TypeToken() {
this.type = getSuperclassTypeParameter(getClass());
this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);
this.hashCode = type.hashCode();
}
......
}
public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
return (T) fromJson(new JsonTreeReader(json), typeOfT);
}
//只要调用fromJson传入一个TypeToken的子类就能获取到泛型的类型
new Gson().fromJson(json,new TypeToken<ArrayList<String>>(){}.getType());
原理
子类如果实例化这个泛型,则编译时会保留实例化类型,可以通过反射获取,如:
Generic<String> generic = new Generic<String>(){ };
System.out.println(generic.getClass().getGenericSuperclass());
以上类型会输出:com.hencoder.layoutlayout.Generic<java.lang.String>