之前我们初步认识了泛型和内部类,相关的知识点都比较简单,感兴趣的小伙伴可以点击下面的链接去看看哦
- 初识Java内部类
- 初识Java泛型
目录
泛型和内部类的关系
泛型的通配符
引例
< ? >
上限通配符
下限通配符
类型擦除
成员内部类会复用外部类的泛型参数,但静态内部类不会复用外部类的泛型参数
public class MyOutter {
//成员内部类
private class Inner{
public void test(T t1){
System.out.println(t1);
}
}
//静态内部类
private static class Inner1{
public void fun(T t2){
System.out.println(t2);
}
}
public static void main(String[] args){
MyOutter outter = new MyOutter();
MyOutter.Inner inner = outter.new Inner();
inner.test("hello");
inner.test(123);
MyOutter.Inner1 inner1 = new Inner1<>();
inner1.fun(100);
}
}
可以看到,成员内部类的泛型参数和外部类的不一致时,就会报错,而静态内部类和外部类的泛型参数无关。
何为泛型通配符?我们先看下面这引例:
public class Message{
private T msg;
public T getMsg() {
return msg;
}
public void setMsg(T msg) {
this.msg = msg;
}
public static void fun(Message msg){
System.out.println(msg.getMsg());
}
public static void main(String[] args) {
Message msg = new Message<>();
msg.setMsg("String类型");
fun(msg);
}
}
//输出:String类型
上面的代码在主方法创建泛型类的对象,确定类型为 String 类型,调用 fun 方法就输出了设置的msg。可是,当我们又创建一个不是 String 类型的对象时呢?
可以看到,当对象类型为整型时就无法调用 fun 方法了,fun方法的方法参数规定了形参的接收类型,由于泛型的强类型校验,不同的类型完全不能通过校验,要匹配不同的类型就要重载多次 fun 方法。这时就引出了通配符来解决问题。
一般用在方法参数,表示可以接收该类所有类型的泛型变量。它只能用在方法的形参上,不可以用在类定义和方法返回值上
public static void fun(Message> msg){
System.out.println(msg.getMsg());
}
public static void main(String[] args) {
Message msg = new Message<>();
msg.setMsg("String类型");
fun(msg);
Message msg1 = new Message<>();
msg1.setMsg(123);
fun(msg1);
Message msg2 = new Message<>();
msg2.setMsg(15.5);
fun(msg2);
}
//输出:String类型
123
15.5
要注意,>只能调用对象的getter方法来获取值,不能调用setter方法来设置值,因为此时无法确实传入的对象类型
表示 ? 可以指代任意类型,但该类型必须是后面类的类型或者它的子类,如图String不是继承与Number类,因此无法通过fun方法接收
设置泛型的上限依然不能调用setter方法来设置值,以上面的代码为例, ? 是接收Number及其子类,但是子类之间是不能相互转换的。
extends 可以用在泛型类的定义上,它是唯一一个可以定义在类型参数的通配符:
此时 ?表示可以指代任意类型,但是该类型必须是后面类的父类。只能用在方法参数,不能用在类的类型参数
例: super String>
此时?只能是String或者Object
此时我们就可以使用setter方法设置值,因为不论设置何种类型,规定好的下限对象可以发生天然的向上转型变为父类
我们之前已经讲过,泛型是作用在编译期间的一种机制,实际上运行期间是没有这么多类的,泛型就是典型的语法糖。那运行期间是什么类型呢?这里就是类型擦除在做的事情。
语法糖是一个术语,指计算机语言 中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
类型擦除就是所有泛型类型参数,若没有设置泛型上限,则编译之后统一擦除为Object类型,若设置了泛型上限,则编译之后统一擦除为相应的泛型上限。
我们创建几个对象,来比较一下他们的类型地址:
public class Message{
private T msg;
public T getMsg() {
return msg;
}
public void setMsg(T msg) {
this.msg = msg;
}
public static void fun(Message super String> msg){
msg.setMsg("hello");
System.out.println(msg.getMsg());
}
public static void main(String[] args) {
Message msg = new Message<>();
Message msg1 = new Message<>();
Message msg2 = new Message<>();
//.getClass()获取类型
System.out.println(msg.getClass() == msg1.getClass());
System.out.println(msg.getClass() == msg2.getClass());
}
}
//输出:true
true
这里使用到的 getClass 方法是反射中获取对象类型的方法。
我们知道 " == " 比较的是地址,可见三个对象定义时都是不同的泛型类型,编译之后的类型却是一样的,那么它们编译之后的类型是什么呢?
public static void main(String[] args) throws NoSuchFieldException {
Message msg = new Message<>();
Message msg1 = new Message<>();
Message msg2 = new Message<>();
//获取一个类的属性
Field field = msg.getClass().getDeclaredField("msg");
Field field1 = msg1.getClass().getDeclaredField("msg");
Field field2 = msg2.getClass().getDeclaredField("msg");
//获取类型
System.out.println(field.getType());
System.out.println(field1.getType());
System.out.println(field2.getType());
}
//输出:
class java.lang.Object
class java.lang.Object
class java.lang.Object
可以看到,编译之后都成了 Object 类型,我们再演示一个设置了泛型上限的例子:
public class Message{
private T msg;
public T getMsg() {
return msg;
}
public void setMsg(T msg) {
this.msg = msg;
}
public static void main(String[] args) throws NoSuchFieldException {
Message msg1 = new Message<>();
Message msg2 = new Message<>();
Field field1 = msg1.getClass().getDeclaredField("msg");
Field field2 = msg2.getClass().getDeclaredField("msg");
System.out.println(field1.getType());
System.out.println(field2.getType());
}
}
//输出:
class java.lang.Number
class java.lang.Number