泛型的本质
Java泛型是JDK1.5引入的新特性,泛型提供了编译时类型安全检查。该机制允许程序在编译时检测到非法的类型。
泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个参数的类型就可以在使用时决定了,这种参数类型可以用在类、接口、方法上
为什么需要泛型?
在Java推出泛型之前,可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用集合的过程中,程序员需要明确的知道存储每个元素的数据类型,不然很容易发生ClassCastException,泛型的好处是在编译期间进行类型检查,并且所有强制转化都是自动和隐式的。
- 保证了类型的安全:编译器在编译期间就会做类型检查,告知是否插入错误的类型对象,使得程序更安全
- 消除强制转换:没有设置泛型的类型默认为 Object,当我们获取元素时需要进行一个强转,当使用泛型时则不需要强转操作。
- 避免不必要的装箱拆箱操作,提高程序的性能:非泛型时,将简单类型作为 Object 类型传递时会引起装箱和拆箱操作,这两个过程都会有极大的消耗
- 提高代码的重用性
使用泛型
泛型有三种使用方式:泛型类、泛型接口和泛型方法
泛型类
- 如果没有指定具体的数据类型,则默认是 Object。
- 泛型的参数类型只能是类类型,不能是基本类型。
- 泛型类在逻辑上可以看作是多个不同的类型,但实际上都是相同的类型
public class Generic<T> {
}
泛型类型必须是引用类型,即非基本数据类型,泛型类型可以有多个,每个参数使用逗号分隔
public class Generic<T> {
private T key;
public Generic() {
}
public Generic(T key) {
this.key = key;
}
// get set toString...
}
Generic<Integer> generic = new Generic<>(123);
Generic<String> generic1 = new Generic<>("admin");
System.out.println(generic.getClass());
System.out.println(generic1.getClass());
System.out.println(generic.getClass() == generic1.getClass());
// conslog result
class Generic
class Generic
true
泛型类派生子类
创建子类先要创建父类对象,如果子类也是泛型类,那么子类的泛型标识就要和父类的泛型一致
//父类
public class Parent<E> {
private E value;
public E getValue() {
return value;
}
public void setValue(E value) {
this.value = value;
}
}
/**
* 泛型类派生子类,子类也是泛型类,那么子类的泛型标识要和父类一致。
* @param <T>
*/
public class ChildFirst<T> extends Parent<T> {
@Override
public T getValue() {
return super.getValue();
}
}
若子类不是泛型类,那么父类就要明确泛型的数据类型
/**
* 泛型类派生子类,如果子类不是泛型类,那么父类要明确数据类型
*/
public class ChildSecond extends Parent<Integer> {
@Override
public Integer getValue() {
return super.getValue();
}
@Override
public void setValue(Integer value) {
super.setValue(value);
}
}
泛型接口
public interface GenericInterface<T> {
void show(T t);
T getValue(T t);
}
实现类也是泛型类,实现类和接口的泛型类型要一致
/**
* 泛型接口
* @param <T>
*/
public interface Generator<T> {
T getKey();
}
/**
* 泛型接口的实现类,是一个泛型类,
* 那么要保证实现接口的泛型类泛型标识包含泛型接口的泛型标识
* @param <T>
* @param <E>
*/
public class Pair<T,E> implements Generator<T> {
private T key;
private E value;
public Pair(T key, E value) {
this.key = key;
this.value = value;
}
@Override
public T getKey() {
return key;
}
public E getValue() {
return value;
}
}
实现类不是泛型类,接口要明确数据类型
/**
* 实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型。
*/
public class Apple implements Generator<String> {
@Override
public String getKey() {
return "hello generic";
}
}
泛型方法
泛型方法是在调用方法的时候指明泛型的具体类型。
// 只有 public 与 返回值之间有 <T> 才表示该方法为泛型方法,泛型类中使用的泛型成员方法并不是泛型方法
public <T> void showGeneric(T t){
}
- 泛型方法能使方法独立于类而产生变化,该方法的泛型与类上面的泛型没有任何关系
- 如果static方法要使用泛型能力,就必须使其成为泛型方法
/**
* 静态的泛型方法,采用多个泛型类型
* @param t
* @param e
* @param k
* @param <T>
* @param <E>
* @param <K>
*/
public static <T,E,K> void printType(T t, E e, K k) {
System.out.println(t + "\t" + t.getClass().getSimpleName());
System.out.println(e + "\t" + e.getClass().getSimpleName());
System.out.println(k + "\t" + k.getClass().getSimpleName());
}
类型通配符
类型通配符一般是使用”?”代替具体的类型实参。所以,类型通配符是类型实参,而不是类型形参
类型通配符的上限
要求该泛型的类型,只能是实参类型,或实参类型的子类类型。
要声明使用该类通配符, 采用的形式, 这里的E就是该泛型的上边界。
public class GrandFather {
}
public class Father extends GrandFather{
}
public class Son extends Father{
}
类型通配符下限
要求泛型的类型只能是实参类型或者实参类型的父类型
类型擦除
泛型是1.5引入的,在这之前是没有泛型的,但是泛型能够与之前的代码兼容,是因为泛型信息只处在编译阶段,在进入JVM之前,与泛型相关的信息都会被擦除。
无限制泛型擦除
public class Generic<T> {
private T key;
// 省略 get set 等方法,下面同样
}
Class<Generic> clazz = Generic.class;
Field[] fields = clazz.getDeclaredFields();
Arrays.stream(fields).forEach(field -> {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
});
// conslog result
key : Object
有限制类型擦除
public class Generic<T extends Number> {
private T key;
}
Class<Generic> clazz = Generic.class;
Field[] fields = clazz.getDeclaredFields();
Arrays.stream(fields).forEach(field -> {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
});
// conslog result
key : Number
泛型方法
桥接方法
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/95899.html