Java泛型笔记

本文详细介绍了Java中的泛型,包括泛型类、泛型方法、泛型类派生子类、泛型接口、类型通配符及其上下限、类型擦除、桥接方法、泛型与数组以及泛型与反射的相关概念和使用示例,帮助读者深入理解Java泛型的运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

泛型

泛型,即参数化类型。在类型未知的时候,可以使用泛型来代替。通俗点来说,泛型就是一个参数,用来接收数据的类型。定义泛型的符号是<>。需要注意的是泛型的参数不支持多态。注意是参数。

泛型类:

class fanclass<T>{
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

    public fanclass(T key) {
        this.key = key;
    }

    @Override
    public String toString() {
        return "fanclass{" +
                "key=" + key +
                '}';
    }
}

类名后面加上尖括号和参数就是泛型类,其中里面的T可以随便定义,只要不跟引用类型相同既可以。如果定义了引用类型,那么这个泛型类的泛型就确定了。在实例化泛型类的时候可以给泛型参数传入引用类型的数,需要注意的是不能传入基本数据类型,但可以传入基本数据类型的包装类。如果实例化的时候没有传入数据类型,那么就会默认为Object类型。
实例化:

 fanclass f =new fanclass(100);
 fanclass <String> f1 =new fanclass<>("acb");

调用方法后的返回值类型:

        Object key = f.getKey();
        System.out.println("key:"+key);

        String key1 = f1.getKey();
        System.out.println("key1"+key1);

输出结果:

key:100
key1acb

泛型方法:
以上的方法都不是泛型方法,泛型方法需要有泛型声明。

//抽奖器
class product<T>{

    Random random =new Random();
    //奖品
    public T p;
    //奖品集合
    ArrayList<T> list =new ArrayList<>();
    //添加奖品
    public void addProduct(T t){
        list.add(t);
    }
    //抽奖
    public T getProduct(){
        p =list.get(random.nextInt(list.size()));
        return p;
    }
    public <T> T getProduct(ArrayList<T> list){
        return list.get(random.nextInt(list.size()));
    }

在这里我定义了一个类,注意看最后一个方法,这个方法有个<>和参数T。这个方法就是一个泛型方法。泛型方法里面的泛型参数跟泛型类的泛型参数是没有任何关系的,哪怕同名。注意,当调用泛型方法时传入参数的类型就是泛型参数的类型。
泛型方法调用:

        System.out.println("----------调用泛型方法-------------");
 		product <Integer>p4 =new product<>();
        ArrayList<String> list =new ArrayList<>();
        list.add("苹果电脑");
        list.add("劳斯莱斯幻影");
        list.add("布加迪威龙");
        list.add("柯尼塞格");
        //泛型方法的类型是在调用方法的时候指定的
        //泛型方法的类型跟泛型类的类型毫无关系
        System.out.println(p4.getProduct(list) +"\t"+p4.getProduct(list).getClass().getSimpleName());

输出结果:

----------调用泛型方法-------------
柯尼塞格	String

泛型类派生子类:
因为当子类会继承父类的属性及方法,所以必须要先给父类的泛型进行传值。当然,如果子类泛型跟父类泛型一致,那么它们使用的类型就是一致的。
泛型类的继承需要注意两点:
1.子类跟父类的泛型要一致,如果父类没有声明,那么就是Object
2.如果子类不是泛型类,那么父类要明确数据类型

class students extends person<Integer>{}
class ad extends person{}//默认为Object类型
class ddf<T> extends person<T>{}
class dddw<T> extends person<Integer>{}

泛型接口:
泛型接口跟泛型类派送子类需要注意的点很相似。
1.接口要实现类的泛型要一致(也可以不一致但是要在实现的时候声明具体类型),如果接口没有声明。那么就是Object
2.如果实现类不是泛型类,那么接口要明确数据类型

下面我们定义一个泛型接口然后实现它

interface fans<I>{

    I getKey();
    void setKey(I i);
}

class fanff<D> implements fans{
    @Override
    public Object getKey() {
        return null;
    }

    @Override
    public void setKey(Object o) {

    }
}

class faf<D> implements fans<Integer>{
    @Override
    public Integer getKey() {
        return null;
    }

    @Override
    public void setKey(Integer integer) {

    }
}

class fanacc<D> implements fans<D>{
    private D key;
    @Override
    public D getKey() {
        return key;
    }
    @Override
    public void setKey(D d) {
        key =d;
    }

    public fanacc(D key) {
        this.key = key;
    }
}

class fancc1 implements fans<String>{
    private String key;
    @Override
    public String getKey(){
        return key;
    }
    @Override
    public void setKey(String o) {
        key =o;
    }

    public fancc1(String key) {
        this.key = key;
    }
}

其实很好理解,一个类实现一个接口就要先给泛型接口确定泛型的值,如果泛型定义一致则接口跟类用同一个类型,如果不一致那么就要先对泛型接口进行传入参数类型。如果实现接口的时候不使用接口的泛型则默认为Object类型。


类型通配符:
类型的通配符为:
类型通配符主要用于接收不确定的类型,比如定义一个泛型方法里面的参数为一个泛型类,在调用方法传入参数的时候由于传入的泛型类对象早已经确定好类型,而若传入参数的类型与泛型方法的形参不一样,那么就会报出错误。如下:

public class fanTest2 {
    public static void main(String[] args) {
        fant<Long> f1 =new fant<>();
        f1.setFf(361l);
        showfant(f1);//此处会报出错误
    }

    public static void showfant(fant<Number> fan){
        Object n1 = fan.getFf();
        System.out.println(n1);
    }
}

class fant<T>{
    private T ff;

    public T getFf() {
        return ff;
    }

    public void setFf(T ff) {
        this.ff = ff;
    }
}

若想解决这个问题,只需要把public static void showfant(fant<Number> fan)方法里面的参数换成 public static void showfant(fant<?> fan)即可。?符号代表泛型类型未知,传入任何引用类型都可以。可以类似的理解为Object类型(只是帮助理解)。但它们是不同的。


类型通配符上下限:
上限:关键字是extends,需要在<>括号里面使用。定义泛型能接收的最大类型。
public static void showan(ArrayList<?extends cat> list)参数内的泛型最大能接收cat类型。
下限:关键字是super,需要在<>括号里面使用。定义泛型能接收的最小类型。
public static void showan(ArrayList <? super cat> list2)参数内的泛型最小能接收cat类型。

上限:
定义了三个有着继承关系的类。

class animal{}
class cat extends animal{}
class minicat extends cat{}

创建集合并且利用泛型约定类型。

class fantest3{
    public static void main(String[] args) {
        ArrayList<animal> animals =new ArrayList<>();
        ArrayList<cat> cats =new ArrayList<>();
        ArrayList<minicat> minicats =new ArrayList<>();
        //showan(animals);(报错)
        showan(cats);
        showan(minicats);
        //addAll方法的参数可以是cats类集合及其子类集合
        cats.addAll(minicats);
    }
    public static void showan(ArrayList<?extends cat> list){
        //不允许在方法内填充元素
        //list.add(new cat());(报错,因为参数不确定传入的是什么类型。)
        for (int i = 0; i <list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

可以看到,当定义了上限之后,传入约定为animals类型的集合就会报错。而传入约定为cat类型及其子类的集合则不会报错。

下限:

class fanTest4{
    public static void main(String[] args) {
        ArrayList<animal> animals = new ArrayList<>();
        ArrayList<cat> cats = new ArrayList<>();
        ArrayList<minicat> minicats = new ArrayList<>();
        showan(animals);
        showan(cats);
        //showan(minicats);报错
    }

    /**
     * 类型通配符的下限,要求集合只能是cat或者cat的父类类型
     * @param list2
     */
    public static void showan(ArrayList <? super cat> list2){
        for (int i = 0; i < list2.size(); i++) {
            System.out.println(list2.get(i));
        }
        for (Object o: list2) {
            System.out.println(o);
        }
        //设定了上限就不能添加元素,设定了下限可以添加
        list2.add(new cat());
        list2.add(new minicat());
    }
}

下限跟上限类似,相信不难理解。

需要注意的是:集合使用了extends上限的通配符之所以不能使用add方法添加元素是因为只知道传入类型的上限而不知道下限,不知道要add元素的类型是否小于传入的类型。所以不能使用。无法上转型。
而使用了super下限的通配符之所以能够使用add方法是因为已经知道传入的最小类型是什么,只要add比这个类型更小的元素,就可以成功。即可以上转型。


类型擦除:
泛型只存在于编译的时候,在进入JVM的时候,与泛型相关的信息就会被擦除。
具体代码可见:

class fantest5{
    public static void main(String[] args) {
        ArrayList<Integer> arrayList =new ArrayList<>();
        ArrayList<String> arrayList1 =new ArrayList<>();
        System.out.println(arrayList.getClass().getSimpleName());
        System.out.println(arrayList1.getClass().getSimpleName());
        System.out.println(arrayList.getClass() ==arrayList1.getClass());
    }
 }

输出结果:

ArrayList
ArrayList
true
class erasure<T extends Number>{
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

    public <T extends List> T show(T t){
        return t;
    }
}
class fantest5{
    public static void main(String[] args) {
		erasure<Integer> erasure1 =new erasure<>();
        //获得反射对象
        Class<? extends erasure> c1 = erasure1.getClass();
        //获取所以成员变量
        Field[] declaredFields = c1.getDeclaredFields();
        //打印成员变量名称以及类型,打印类型为Object,因为在编译的时候就进行了类型擦除(无限制)
        //假如泛型类有上限,那么就是有限制擦除。擦除后类型为上限
        for (Field a:declaredFields) {
            System.out.println(a.getName()+"  "+a.getType().getSimpleName());
        }
        //获取所有方法
        Method[] declaredMethods = c1.getDeclaredMethods();
        for (Method a:declaredMethods) {
            System.out.println(a.getName()+"  "+a.getReturnType().getSimpleName());
        }
    }
}

输出结果:

key  Number
getKey  Number
show  List
setKey  void

从上述上面可以看到,有关泛型的信息全部都被擦除了。

桥接方法:
为了保持实现类和接口的联系,在擦除的时候会自动创建一个方法。

nterface Info<T>{
    T info(T t);
}

class infoimpl implements Info<Integer>{
    @Override
    //这里的Integer之所以没有被擦除是因为它的返回值不是泛型
    public Integer info(Integer value) {
        return value;
    }
}
class fantest5{
    public static void main(String[] args) {
        System.out.println("-------------------------------");
        Class<infoimpl> infoimplClass = infoimpl.class;
        Method[] declaredMethods1 = infoimplClass.getDeclaredMethods();
        for (Method a:declaredMethods1) {
            System.out.println(a.getName()+"  "+a.getReturnType().getSimpleName());
        }
    }
}

输出结果:

-------------------------------
info  Integer
info  Object

泛型与数组:
泛型数组只能声明引用,不能创建对象。
rrayList<String>[] listarr =new ArrayList<>[5];这种写法是错误的,因为泛型数组不能创建对象。(数组是要确定类型的,你连类型都不确定,怎么创建)
但可以通过这种方式实现:

ArrayList [] lists =new ArrayList[5];
ArrayList<String>[] listarr =lists;

但是这种方式有很大的弊端,如向lists添加了非String的对象,那么就会出错。

我们可以用原生的创建对象方法,不暴露泛型。
ArrayList<String>[] listarr =new ArrayList[5];
然后创建一个泛型集合
ArrayList<String>strList =new ArrayList<>();
给集合添加对象
strList.add("adbc");
然后把集合赋给集合数组
listarr[0] =strList;
这样就可以实现给泛型数组创建对象了

我们还可以通过类来实现这一功能
Array.newInstance(Class a,int b)方法可以返回一个对象,参数a为反射的类,b为返回数组长度。

class fruit<T>{
    private T[] array;

    public fruit(Class<T> clz,int length) {
        //通过newInstance方法创建泛型数组
        array = (T[])Array.newInstance(clz,length);
    }
    //填充数组
    public void put(int index,T item){
        array[index] =item;
    }
    //获取数组
    public T get(int index){
        return array[index];
    }

    //获取数组所以元素
    public T[] gets(){
        return array;
    }
}

class fantest6{
    public static void main(String[] args) {
        fruit <String> f1 =new fruit<>(String.class,3);
        f1.put(1,"PG");
        f1.put(2,"xg");
        f1.put(0,"xj");
        System.out.println(Arrays.toString(f1.gets()));
        System.out.println(f1.get(0));
    }
}

输出结果:

[xj, PG, xg]
xj

泛型与反射:
使用泛型可以更加方便的进行代码操作,减少类型转换。如在第一个newInstance()的时候返回值就是pers类型的对象。而没有使用泛型则直接返回了一个Object类型的对象。

//泛型与反射
class fantest7{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<pers> persClass = pers.class;
        Constructor<pers> constructor = persClass.getDeclaredConstructor();
        pers p1 =constructor.newInstance();

        Class persClass1 = pers.class;
        Constructor constructor1 = persClass1.getDeclaredConstructor();
        Object o = constructor1.newInstance();
    }
}

class pers{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值