Java泛型
1. 泛型的好处
1.1 能够适用于代码复用
<T extends Number>
指定了参数类型。
public class Test0 {
public static <T extends Number> double add(T a,T b) {
return a.doubleValue()+b.doubleValue();
}
public static void main(String[] args) {
System.out.println(Test0.add(1.0f,2l));
}
}
运行结果
3.0
1.2.泛型能够实现类型安全
无需强制转换
2. 泛型的使用
2.1 简单泛型
public class Test2 {
static class Message<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setT("abc");
System.out.println(message.getT());
}
}
运行结果
abc
2.2 多元泛型
public class Test3 {
static class Entry<K,V> {
private K key;
private V val;
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getVal() {
return val;
}
public void setVal(V val) {
this.val = val;
}
}
public static void main(String[] args) {
Entry<Integer,String> entry = new Entry<>();
entry.setKey(200);
entry.setVal("成功");
System.out.println(entry.getKey()+","+entry.getVal());
}
}
运行结果
200,成功
2.3 泛型接口
public class Test4 {
interface Response<T> {
public T getVar();
}
static class ResponseImpl<T> implements Response<T> {
private T var;
@Override
public T getVar() {
return var;
}
public void setVar(T var) {
this.var = var;
}
public ResponseImpl(T var) {
this.var = var;
}
}
public static void main(String[] args) {
Response<String> response = new ResponseImpl<>("abc");
System.out.println(response.getVar());
}
}
运行结果
abc
2.4 泛型方法一:方法声明
<T>
声明泛型方法持有T
public class Test5 {
public static <T> void print(T[] arr) {
for(T t:arr) {
System.out.print(t+"\t");
}
}
public static void main(String[] args) {
Test5.print(new String[]{"a","b","c"});
}
}
运行结果
a b c
2.4.1 泛型方法二:方法声明多参数
public class Test6 {
public static <T,V> void print(T[] arr,V[] arr2) {
for(T t:arr) {
System.out.print(t+"\t");
}
for(V v:arr2) {
System.out.print(v+"\t");
}
}
public static void main(String[] args) {
Test6.print(new String[]{"a","b","c"},new Integer[]{1,2,3,4,5});
}
}
运行结果
a b c 1 2 3 4 5
2.4.2 泛型方法三:用反射生成对象
通过泛型与反射创建对象
public class Test7 {
public static <T> T getInstance(Class<T> c) {
T t = null;
try {
t = c.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return t;
}
public static void main(String[] args) throws ClassNotFoundException {
Object instance = Test7.getInstance(Class.forName("com.generic.O"));
System.out.println(instance.getClass());
}
}
运行结果
class com.generic.O
2.5 泛型方法四:泛型方法调用
在调用泛型方法时,可以指定类型,此时编译器会校验参数类型
3. 泛型使用中需要注意的上限与下限
3.1 问题的引入:为什么需要上限与下限
- 问题一
public class Test8 {
static class A {
}
static class B extends A {
}
public void putA(A a ){
}
public void putB(B b) {
putA(b);
System.out.println("自动进行类型转换,里氏替换");
}
public static void main(String[] args) {
Test8 test8 = new Test8();
test8.putB(new B());
}
}
在这个方法可以正常调用,因为编译器会识别 putA(b);
的参数类型是B。
-
问题二
但是如果是List类型就会报错哦.因此此处是隐含的类型转换。 -
用泛型解决
<? extends A>
表示该类型参数可以是A(上边界)或者A的子类类型。编译通过。
public void putA(List<? extends A> lista ){
}
public void putB(List<B> listb) {
putA(listb);
System.out.println("自动进行类型转换,里氏替换");
}
3.2 泛型上限与下限应用
extends 关键字声明了类型的上界;super 关键字声明了类型的下界。
public class Test11 {
static class Point<T> {
}
public static void main(String[] args) {
Point<? super Number> p1 = new Point<Object>();
Point<? extends Number> p2 = new Point<Integer>();
}
}
3.3 泛型多限制:同时实现多个接口
<T extends Staff & Passenger>
要求泛型必须同时实现两个接口。
public class Test12 {
interface Staff{
int getSalary();
}
interface Passenger{
boolean isStanding();
}
static class Person implements Staff,Passenger {
@Override
public int getSalary() {
return 11110;
}
@Override
public boolean isStanding() {
return false;
}
}
//工资低于2500元的上斑族并且站立的乘客车票打8折
public static <T extends Staff & Passenger> void discount(T t){
if(t.getSalary()<2500 && t.isStanding()){
System.out.println("恭喜你!您的车票打八折!");
}
}
public static void main(String[] args) {
discount(new Person());
}
}
4. 泛型的原理——了解泛型擦除
Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
-
- 无限制类型擦除
当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如和<?>的类型参数都被替换为Object。
- 无限制类型擦除
-
- 擦除类定义中的类型参数
在类型擦除中替换为类型参数的上界或者下界,比如形如和<? extends Number>的类型参数被替换为Number,<? super Number>被替换为Object。
- 擦除类定义中的类型参数
-
- 擦除方法定义中的类型参数
擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的
- 擦除方法定义中的类型参数
4.1 泛型擦除的实验1
实验证明,l1 和 l2 的类型相同。
public class Test13 {
public static void main(String[] args) {
List<String> l1 = new ArrayList<>();
List<Integer> l2= new ArrayList<>();
System.out.println(l1.getClass());
System.out.println(l2.getClass());
}
}
运行结果
class java.util.ArrayList
class java.util.ArrayList
4.2 泛型擦除的实验2
通过反射,仍然可以向泛型类里存放字符串。
public class Test14 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList<Integer> l1 = new ArrayList<>();
l1.add(42);
l1.getClass().getMethod("add",Object.class).invoke(l1,"abc");
System.out.println(l1);
}
}
运行结果
[42, abc]
4.3 泛型擦除的实验3
由于引用类型的声明没有声明为泛型,实际上什么都可以往里存。
所以泛型的检查是针对引用的,而无关它真正引用的对象。
public class Test16 {
public static void main(String[] args) {
List ls = new ArrayList<String>();
ls.add("ba");
ls.add(1);
System.out.println(ls);
}
}
5. 泛型的其他问题
5.1 泛型中的参数不考虑继承
泛型就是需要明确关系,如果参数是可以继承的,但是仍然不允许传递。
5.2 泛型的静态变量
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。因此是错误的。
但是静态方法居然可以通过,请注意此T是方法声明,非类定义的泛型T。
public static <T> T getT(){
return null;
}
5.3 通过反射获取泛型参数类型
public class Test20<T> {
public static void main(String[] args) {
Test20<Integer> genericType = new Test20<Integer>() {
};
Type superclass = genericType.getClass().getGenericSuperclass();
Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
System.out.println(type);//class java.lang.String
}
}
结果:
class java.lang.Integer
5.4 关于泛型的一个思考题
看这个方法,D继承了C,覆写了C的方法。
public class Test18 {
static class C<T>{
private T val;
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
}
static class D extends C<String>{
@Override
public String getVal() {
return super.getVal();
}
@Override
public void setVal(String val) {
super.setVal(val);
}
}
public static void main(String[] args) {
C c = new C();
c.setVal(new Object());
D d = new D();
d.setVal(new Object());
}
}
但是注意,C方法在编译期中会被擦除泛型参数,变为Object,因此D的get、set方法由于入参和返回值不一样,就不是对C的覆写了。
真的是这样吗?
运行显示,d不允许d.setVal(new Object());
。可是我们之前的思路,应当父类的方法并没有被覆盖啊,是可以调用父类的方法的啊.
问题在于虚拟机的优化实现。
运行javap -c命令查看字节码,发现jvm帮助我们实现了子类的方法,一共有两套get和set方法,以匹配父类方法。
当然我们自己是无法这样实现的,以为编译不会通过,因为jvm违反了重载的约束,仅仅返回值不同就定义了多个方法。
6.常见问题
1.Java中的泛型是什么 ? 使用泛型的好处是什么?
泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。
泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。
2.Java的泛型是如何工作的 ? 什么是类型擦除 ?
泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀。
3.什么是泛型中的限定通配符和非限定通配符 ?
限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。
4.List<? extends T>和List <? super T>之间有什么区别 ?
List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List。
总结
泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。
泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。
java基础 系列在github上有一个开源项目,主要是本系列博客的demo代码。https://github.com/forestnlp/javabasic
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。