泛型即参数化类型,使用时通过传入具体的类型
下面例子中 的类 关系
C–继承–>B–继承–>A
public class A<T>{
}
static class A<T>{
}
继承:
static class B extends A{
}
继承父类时不带泛型时,A类的T因为类型擦除会变为Object
static class B<T,TT,TTT> extends A<T>{
}
当继承父类时指定泛型为不明确类型时,需要子类带一个与 继承的父类的泛型类型名称相同,这样在new B指定泛型时会一并指定了A的,如何B的<>不带这个T,那么将会报错,因为没人指定A的T
可以理解为 继承父类时指定泛型为不明确类型时,具有延迟加载的性质,需要子类指定
static class B extends A<String>{
}
static class A<T extends A>{
}
static class B extends A<B(需要符合 T继承A)>{
}
问题:
为什么List不能传递给List?
泛型具有子类型化的规则,List是原生类型List的一个子类型,而不是参数化类型List的子类,那么如何才能传递呢!接下来的通配符会解释道。
interface D<T>{
}
接口的继承和实现 与泛型类继承的规则一样
static class A<T>{
public <T1> void set(T1 t1){
System.out.println(t1);
}
}
调用:
A<Object> a = new A<>();
a.<String>set("hh");
如果 T与T1名称相同 ,方法里面的T就像局部变量 和类上面的互不干涉
static class A<T>{
public <T> void set(T t1){
System.out.println(t1);
}
}
调用:
A<String> a = new A<>();
a.<Integer>set(1);
作用在返回值
static class A<T>{
public T set(T t1){
System.out.println(t1);
return t1;
}
}
调用:
A<String> a = new A<>();
String s = a.set("");
不用强转,以后走路都带风
泛型方法的类型推断
如果 泛型方法 有多少个泛型类型参数,调用时不指定泛型类型,那么返回值的类型是什么?
public static <T> T set(T x,T y){
return y;
}
String v1 = Main.set("", "");
Number v2 = Main.set(1, 1.2f);
A v3 = Main.set(new A<>(), new B<>());
Integer v4 = Main.set(1, 1);
会取同一父类中最小级的
常用泛型 | 说明 |
---|---|
E | Element 表示在集合中存放的元素 |
T | Type 表述Java类 |
K | Key 映射-键 |
V | Value 映射-值 |
N | Number 数值类型 |
? 表示未知类型
使用范围:
不能用作泛型方法调用,泛型类的类型参数
表示未知类型
关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
List<? extends A> l=new ArrayList<>();
List<B> l1=new ArrayList<>();
l=l1;
关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类
List<? super B> s=new ArrayList<>();
List<A> s1=new ArrayList<>();
s=s1;
问题:
1.为什么需要泛型上下限?
我们之前提到 泛型具有子类型化的规则,List是原生类型List的一个子类型,而不是参数化类型List的子类,那么我如何才能把List传过去呢?
List<? extends Object> e=new ArrayList<>();
List<String> e1=new ArrayList<>();
e=e1; //编译成功
就是为了解决泛型的多态!
有的好奇宝宝可能还发现了其他问题 为什么 我e.add("")不能添加了!直接编译错误! 往下看PECS原则!! 隔着探案呢…一环扣一环
Producer Extends Consumer Super extends 为生产者,super为消费者
生产者顾名思义 产出东西的 对应 List的get,消费这就是买入东西的 List的add;
问题:
1.为什么 < ? extends >只能读取 不能传入,< ? supe> 传入不需要读取?
< ? extends >
List<? extends A> l=new ArrayList<>();
List<B> l1=new ArrayList<>();
l=l1;
A的子类有B和C,如果可以添加
List<? extends A> l=new ArrayList<>();
List<B> l1=new ArrayList<>();
List<C> l2=new ArrayList<>();
l=l1;
l.add(new B());
l.add(new C());
l 引用的是 l1 我 l1 只能添加类型为B的,你确可以添加个C,那我 l1 的泛型有什么用,所以不能添加
不管你l是等于 l1 还是 l2 里面添加的都是A的子类,所以可以读取返回父类A
< ? supe >
可以添加但是只能添加当前类和其子类,因为编译器无法判断究竟是哪个超类,避免像Serializable也可以添加进去,所有编译器直接静止添加其父类,当前类和子类都是可以确定的所有可以添加!
List<? super Number> v1 = new ArrayList<Object>();
List<? super Number> v2 = new ArrayList<Serializable>();
List<? super Number> v3 = new ArrayList<Number>();
建议:
以集合为例子
如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
如果既要存又要取,那么就不要使用任何通配符。
Java中的泛型是伪泛型,即在语法上支持泛型,但在编译阶段进行类型擦除保留为原始类型
原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
ArrayList<String> objects = new ArrayList<>();
objects.add("");
objects.add(1);
报错:
java: 对于add(int), 找不到合适的方法
方法 java.util.Collection.add(java.lang.String)不适用
(参数不匹配; int无法转换为java.lang.String)
类型检查只针对它的调用者
ArrayList objects = new ArrayList<String>();
objects.add("");
objects.add(1);
编译通过
new ArrayList<String>().add(1);
编译不通过
我们看一下
ArrayList<String> objects = new ArrayList<>();
objects.add("");
String s1 = objects.get(0);
字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."":()V
7: astore_1
8: aload_1
9: ldc #4 // String
11: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
14: pop
15: aload_1
16: iconst_0
17: invokevirtual #6 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
20: checkcast #7 // class java/lang/String
23: astore_2
24: return
LineNumberTable:
line 49: 0
line 50: 8
line 51: 15
line 52: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
8 17 1 objects Ljava/util/ArrayList;
24 1 2 s1 Ljava/lang/String;
LocalVariableTypeTable:
Start Length Slot Name Signature
8 17 1 objects Ljava/util/ArrayList<Ljava/lang/String;>;
}
SourceFile: "main.java"
Process finished with exit code 0
我们看到 Code表里面 序号20 是 checkcast(类型强转) #7(#7 表示常量池中的) 也就是 class java/lang/String 将类型强转为 String
引入泛型最终的目的就是为了避免繁琐的强转
引用变量类型 | 名称 | 可以接收的类型 | 能否添加元素 | 安全性 | 便利性 | 表述性 |
---|---|---|---|---|---|---|
List | 原始类型 | 任意的List | 可以添加任何元素 | ✔ | × | × |
List | 通配符类型 | 可以接收List< E >的参数化类型 包括 原始类型List | 除了null 不可以添加任何元素 | ✔ | × | ✔ |
List< Object > | 实际类型为Object的参数化类型 | 只可以接收List和其本身类型 | 可以添加任何元素 | ✔ | ✔ | ✔ |
static class C<T> extends B{
// 编译错误 'Main.C.this' cannot be referenced from a static context 不能从静态上下文中引用
static T t1;
//编译错误 'Main.C.this' cannot be referenced from a static context
public static T get(){
return t1;
}
}
因为泛型参数实例化是在 new对象的时候指定的,而静态变量和静态方法不需要创建对象
参考:
https://docs.oracle.com/javase/tutorial/java/generics/index.html
https://www.pdai.tech/md/java/basic/java-basic-x-generic.html#%E6%B3%9B%E5%9E%8B%E7%9A%84%E4%B8%8A%E4%B8%8B%E9%99%90
https://blog.csdn.net/hanchao5272/article/details/79346471
Effective Java