代码改变世界

java泛型 class<T>

2023-03-17 22:30  youxin  阅读(234)  评论(0编辑  收藏  举报

泛型中通配符

我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?

常用的 T,E,K,V,?

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:

  • ?表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

 

泛型的通配符

不知道使用什么类型来接收的时候,此时可以使用??表示未知通配符。

其中泛型通配符还可以这样使用:

  • <? extends Person>:表示可以传递Person及其子类

  • <? super Person>:表示可以传递Person及其父类

注意:

  • 泛型不存在继承关系:ArrayList<Object>list并不是ArrayList<String>list1和ArrayList<Integer>list2的父类,它们三个是三个不同的类型。

  • 其中Java里的泛型是一种伪泛型。

什么叫伪泛型?

也就是泛型只存在于编译时期,在运行时期会被擦除,这个比较抽象不好说明,了解就好了。真泛型也是有的,但在Java语言里没有,C语言里有。

 

上界通配符 < ? extends E>

上界:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
private <K extends A, E extends B> E test(K arg1, E arg2){
    E result = arg2;
    arg2.compareTo(arg1);
    //.....
    return result;
}
类型参数列表中如果有多个类型参数上限,用逗号分开

下界通配符 < ? super E>

下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object

在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

private <T> void test(List<? super T> dst, List<T> src){
    for (T t : src) {
        dst.add(t);
    }
}

public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {

}

dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。

?和 T 的区别

 

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种 :

// 可以
T t = operate();

// 不可以
?car = operate();

简单总结下:

T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

https://zhuanlan.zhihu.com/p/378411807

 

如何创建一个Class<T>类型的实例?

      就像使用非泛型代码一样,有两种方式:调用方法 Class.forName() 或者使用类常量X.class。      Class.forName() 被定义为返 回 Class<?>。另一方面,类常量 X.class 被定义为具有类型 Class<X>,所 以 String.class 是Class<String> 类型的。

三、方法中为什么需要<T> T修饰呢

泛型的声明,必须在方法的修饰符(public,static,final,abstract等)之后,返回值声明之前。

public static <T> T request2Bean(HttpServletRequest request,Class<T> clazz){}

其中第一个<T>是与传入的参数Class<T>相对应的,相当于返回值的一个泛型,后面的T是返回值类型,代表方法必须返回T类型的(由传入的Class<T>决定

 

    在Java中,每个 class 都有一个相应的 Class 对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个 Class 对象,用于表示这个类的类型信息

 

获取 Class 实例的三种方式:

  • (1) :利用 对象调用 getClass()方法 获取该对象的Class实例;
  • (2) :使用 Class类的静态方法 forName(),用类的名字获取一个Class实例(staticClass forName(String className) Returns the Classobject associated with the class or interface with the given stringname. );
  • (3) :运用 .class 的方式来获取 Class 实例,对于基本数据类型的封装类,还可以采用 .TYPE 来获取相对应的基本数据类型的Class实例

        在 newInstance() 调用类中缺省的构造方法 ObjectnewInstance()(可在不知该类的名字的时候,创建这个类的实例) Creates a new instance of the class represented by this Classobject.

        在运行期间,如果要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到 .class 文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象

  

  Class对象的生成方式如下:

    1. Class.forName("类名字符串") (注意:类名字符串必须是全称,包名+类名)。
    2. 类名.class  ( Object.class )。
    3. 实例对象.getClass() (object.getClass() )。

 

 

五、Object.class 和 instanceObj.getClass() 的区别

 

两者的区别如下: 

  • 类名.class 叫做 "类字面量",因 class 是关键字, 所以 类名.class 编译时确定。
  • 而 getclass() 是某个具体的方法来调用,是运行时根据实际实例确定,getClass()是动态而且是 final 的。 

例如: 

  • String.class 是能对类名的引用取得在内存中该类型class对象的引用,
  • 而 new String().getClass() 是通过实例对象取得在内存中该实际类型class对象的引用。 

 

public class Demo<T extends Animal> {
    private T ob;
 
    public T getOb() {
        return ob;
    }
 
    public void setOb(T ob) {
        this.ob = ob;
    }
 
    public Demo(T ob) {
        super();
        this.ob = ob;
    }
 
    public void print() {
        System.out.println("T的类型是:" + ob.getClass().getName());
    }
}
public class Demotest {

    private static void take(Demo<?> a){
        a.print();
    }
    public static void main(String[] args) {
        Demo<Dog> dog=new Demo<Dog>(new Dog());
        take(dog);
        Demo<Cat> cat=new Demo<Cat>(new Cat());
        take(cat);

        Demo<Animal> animal=new Demo<Animal>(new Animal());
        take(animal);

    }
}

 

CLASS

    在Java中,每个 class 都有一个相应的 Class 对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个 Class 对象,用于表示这个类的类型信息

 

获取 Class 实例的三种方式:

  • (1) :利用 对象调用 getClass()方法 获取该对象的Class实例;
  • (2) :使用 Class类的静态方法 forName(),用类的名字获取一个Class实例(staticClass forName(String className) Returns the Classobject associated with the class or interface with the given stringname. );
  • (3) :运用 .class 的方式来获取 Class 实例,对于基本数据类型的封装类,还可以采用 .TYPE 来获取相对应的基本数据类型的Class实例

        在 newInstance() 调用类中缺省的构造方法 ObjectnewInstance()(可在不知该类的名字的时候,创建这个类的实例) Creates a new instance of the class represented by this Classobject.

        在运行期间,如果要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到 .class 文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象

  

  Class对象的生成方式如下:

    1. Class.forName("类名字符串") (注意:类名字符串必须是全称,包名+类名)。
    2. 类名.class  ( Object.class )。
    3. 实例对象.getClass() (object.getClass() )。