一、泛型定义及作用
泛型是一种编程机制,允许在编写代码时使用参数化类型,以在编译时实现类型安全。 以下是泛型作用:
- 增强代码可读性和可维护性:通过在代码中使用泛型参数,可以使代码更清晰、更具有可读性和可维护性。
- 提高代码安全性:泛型可以在编译时检查类型,从而防止在运行时出现类型转换错误。
- 增加代码重用性:泛型允许在不同的数据类型上编写通用代码,从而提高代码的重用性。
- 简化代码:使用泛型可以避免重复编写类似的代码,从而简化代码。
总之,泛型是一种强大的编程机制,它可以帮助开发者编写更具可读性、可维护性、安全性和重用性的代码。
Java 泛型在编译期间会执行类型擦除(Type Erasure)。类型擦除是指编译器在编译泛型代码时,会将泛型类型擦除为其原始类型或限定类型,从而在运行时不存在泛型类型。在进行类型擦除时,编译器会将泛型类型参数替换为它们的边界(限定)类型(如果有的话),如果没有指定限定类型,则会将泛型类型参数替换为 Object 类型
例如,一个泛型类定义如下:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
在编译后,类型擦除会将泛型类型 T 擦除为 Object 类型,生成的字节码如下:
public class Box {
private Object content;
public void setContent(Object content) {
this.content = content;
}
public Object getContent() {
return content;
}
}
需要注意的是,尽管在运行时不存在泛型类型,但是在编译期间编译器仍会对泛型类型进行类型检查,从而确保类型安全。
二、泛型使用
1、泛型定义在类上
代码如下:
class CommonUtil {
public Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
在使用的时候,代码如下:
public static void main(String[] args) {
CommonUtil tool = new CommonUtil<>();
tool.setObj(2);
Integer val1 = (Integer)tool.getObj();
tool.setObj("hello java");
String val2 = (String)tool.getObj();
}
从上述代码可以看出,在获取值时每次都需要强转,稍不留神就容易发生强转错误。所以能不能通过一种方式避免类型强转呢?答案是:泛型。将泛型定义在类上,就可以避免类型转换。 改进代码如下:
class CommonUtil<T> {
public T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
在创建实例时把具体类型传入,代码如下:
public static void main(String[] args) {
CommonUtil<Integer> tool = new CommonUtil<>();
tool.setObj(2);
Integer val1 = tool.getObj();
CommonUtil<String> tool = new CommonUtil<>();
tool.setObj("hello java");
String val2 = tool.getObj();
}
2、泛型定义方法上
泛型定义在类上有个不太有好的一点,看下面例子,代码如下:
class CommonUtil<T> {
public void show(T obj){
System.out.println("obj = " + obj);
}
}
代码使用代码如下:
CommonUtil<Integer> tool1 = new CommonUtil<>();
tool1.show(value);
CommonUtil<String> tool2 = new CommonUtil<>();
tool2.show("111");
CommonUtil<Person> tool2 = new CommonUtil<>();
tool2.show(new Person());
发现没,每次调用 show() 方法,都需要在通过创建实例,因为在创建时可以指定具体使用的参数类型。但是这样创建对象太麻烦,怎么解决,可以将泛型定义在方法上,代码如下:
class CommonUtil {
public <W> void show(W obj){
System.out.println("obj = " + obj);
}
}
使用代码如下:
CommonUtil tool1 = new CommonUtil<>();
tool1.show(value);
tool1.show("111");
tool1.show(new Person());
最后在方法调用时传入具体参数类型,这样就可以避免重复创建对象,做到一个方法重复调用,和 Object 非常类似。包括 static 修饰的静态方法也是一样可以使用。代码如下:
class CommonUtil {
public static <W> void show(W obj){
System.out.println("obj = " + obj);
}
}
使用代码如下:
CommonUtil.show(value);
CommonUtil.show("111");
CommonUtil.show(new Person());
但是泛型定义在静态方法上还要注意,这个静态方法不能使用类上面的泛型。为什么?因为类是在创建时才会指定具体参数类型,而静态方法是在类实例化之前就已经被加载到 JVM,根本不知道你类在创建实例时传入的是什么具体参数类型。错误示例如下:
class CommonUtil<W> {
public static <W> void show(W obj){
System.out.println("obj = " + obj);
}
}
3、泛型定义在接口上
有时候泛型会定义在接口上,代码如下:
public interface Inter<T> {
public void print(T obj);
}
接口上的泛型那么可以在子类中指定、或不指定,子类指定具体类型如下:
public InterImpl implements Inter<String> {
// 接口上的方法
public void print(String obj){}
// 子类独有方法
protect void show(Object obj){}
}
在使用时,代码如下:
Inter<String> inter = new InterImpl();
inter.print("hello");
InterImpl impl = new InterImpl();
impl.show(new Object());
或者子类中定义泛型,接口定义具体类型,代码如下:
public InterImpl<W> implements Inter<String> {
// 接口上的方法
public void print(String obj){}
// 子类独有方法
protect void show(W obj){}
}
在使用时,代码如下:
Inter<String> inter = new InterImpl();
inter.print("hello");
InterImpl<Integer> impl = new InterImpl();
impl.show(new Integer(10));
子类中也不知道具体类型,那么也可以定义泛型,把子类泛型传给接口,代码如下:
public InterImpl<W> implements Inter<W> {
// 接口上的方法
public void print(W obj){}
// 子类独有方法
protect void show(W obj){}
}
在使用时,代码如下:
Inter<String> inter = new InterImpl();
inter.print("hello");
InterImpl<Integer> impl = new InterImpl();
impl.show(new Integer(10));
4、泛型通配符
举个例子,代码如下:
public class FanxinDemo {
public static void print(Collection<String> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
print(list1);
}
}
可以发现 print() 方法只能输出参数类型是 String 的,其他的参数都不能,怎么可以做到公用这段代码?这里介绍回个泛型通配符 ?
,当你不知道泛型是什么类型是,就可以用这个暂时表示,代码如下:
public class FanxinDemo {
public static void print(Collection<?> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
print(list1);
print(list2);
}
}
但是假设现在不想让 print() 方法被 Integer 类型参数使用,那么需要怎么做呢?这就需要泛型限定了。
5、泛型限定
泛型限定可以限制参数类型范围,?
这个级别范围太大,不安全,通过 extends、super 关键字来限定范围,但是这两个关键字只能是单继承,所以也是有局限的。限定分为两种:让所有父类可以使用,让所有的子类都可以使用
5.1、让所有父类可以使用
代码如下:
public static void print(Collection<? super Integer> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
表示让 Integer 所有父类都可以使用 print() 方法。
5.2、让所有子类可以使用
代码如下:
public static void print(Collection<? extends String>> list) {
list.forEach(e->{
System.out.println("e = " + e);
});
}
表示让所有的 String 子类可以使用 print() 方法。