java的泛型

本文深入探讨了Java中的泛型概念,包括泛型的优势,如类型检查和消除强制类型转换,以及泛型在类、接口、方法中的应用。详细解释了泛型通配符、上下限及其限制,旨在提高代码的类型安全性和可读性。通过实例展示了如何使用泛型提高编程效率并减少类型转换错误。
摘要由CSDN通过智能技术生成

泛型是一个使用<>(尖括号的位置在使用泛型类的类名后面,ClassName)引起来的参数类型,既然是参数,一方面起到占位作用,另一方面只在具体使用时才会确定具体的类型。需要注意,泛型的类型只能时引用数据类型,不能是基本数据类型。并且,只有这个引用类型不确定,而其他操作时确定的。

Jdk1.5前使用Object占位(代表所有要接收或返回的数据类型),之后使用泛型,即泛型替代Object的作用。

默认情况,集合可以存储任意类型的数据元素(底层Object类型数组),但这种方式方便存储,但不利于后续处理【接收数据和查询数据】。在实际编码实践中,集合中往往存储相同数据类型的元素。基于此目的,集合类设计为支持泛型,在声明时直接指定集合中要存储的数据类型,在编译阶段可检查集合中的数据元素是否正确,同时后续处理集合元素也相对方便。因此泛型主要有以下优势

  • 类型检查,保证类型安全【接收数据】

    使用泛型替代Object接收元素时,泛型被指定后,可以在编译阶段完成对对元素类型进行检查,保证类型安全。

  • 消除强制类型转换【获取数据】

    使用Object接收不同类型数据后,取值时需要进行强制类型转换。譬如某不使用泛型的集合中主要存储字符串,但也存了一个整型,在取值时需要对这个整型强制转换成字符串。

在使用时,jdk1.7之后泛型,等号右侧的尖括号内的类型可省略。即Arraylist<String> al = new ArrayList<>();

继承中的泛型

  1. 父类指定泛型
    父类指定泛型类型时,此时子类可直接使用泛型类型。子类不必使用泛型语法。

    /**
     * 父类使用了泛型,但子类继承父类时,父类指定了泛型类型,即相当于给形参位置传入了实参
     * 此时子类不必在使用泛型语法,直接使用即可。
     */
     class GenericSon1 extends GenericTest<String>{
    
    }
    
  2. 父类不指定泛型

    /**
     * 父类使用了泛型,但子类继承父类时,父类仍未指定了具体的泛型类型,即相当于传入了实参
     * 此时子类需要使用泛型语法
     */
    class GenericSon2<E> extends GenericTest<E>{
    
    }
    

泛型类/接口

  • 同一个泛型类可以有多个泛型参数,即ClassName<A,B,C,D>

  • 泛型类的构造器同一般类的构造器使用方法一致,不必在构造器的方法名后面添加尖括号

    public ClassName(){} //正确写法
    public ClassName<>(){} //错误写法。不必加尖括号
    
  • 若多个泛型类的泛型类型不同时,这些泛型类不能相互赋值

    ArrayList<String> a1 = null;
    ArrayList<Integer> a2 = null;
    a1 = a2; //编译不通过。底层定义的数组类型就不同。
    
  • 在声明时,若泛型类不指定具体的泛型参数,则泛型会被擦除,泛型对应的类型为Object类型。

    若某个类在定义时被定义为泛型类,即ClassName<E>,但在使用该类创建对象时,没有显示指定E对应的引用类型ClassName cn = new ClassName(),那么该泛型类会被认为一般的类,即所谓泛型擦除。

    //ArrayList本身是一个泛型类,但此处未指定泛型类型,
    //底层的数组被认为存储Object类型,即可以存储任意类型
    ArrayList l = new ArrayList();
    //idea提示add方法的形参为 Object o    
    l.add(1); 
    l.add(true);
    l.add(1.23);
    
    //正确用法。执行泛型类型
    ArrayList<Integer> l = new ArrayList<Integer>();
    //idea提示add方法的形参为 Integer i    
    l.add(1); 
    l.add(2);
    l.add(true); //编译出错,只能添加Integer类型
    l.add(1.23);//编译出错,只能添加Integer类型
    
  • 泛型类中的静态方法不能使用类的泛型

    类的泛型是一个形参,只有在创建该类时,指定具体的泛型类型(实参)后,才确定具体的数据元素的数据类型。而静态方法可以直接使用类名调用,不必创建对象调用。若在静态方法中使用泛型,此时泛型的引用类型尚未明确,不可能操作一个未明确的引用类型,逻辑不通。

  • 不能使用泛型类型创建数组

    E[] e  = new E[10];//该方式错误。
    E[] e = (E)new Object[10];//正确。变相方式创建泛型数组。
    

泛型方法

泛型方法的表示同样使用尖括号包裹一个大写字符,并且它位于方法访问修饰符和返回值之间,之后表示任意类型的T可以出现在泛型方法的任意地方。

/**
*泛型方法的标识<T>
*/
public <T> String test(){
    return "泛型方法";
}

需要特别注意泛型方法和泛型类的一般方法的区别。

public class GenericTest<E>{
	//虽然使用了E,但func1不是泛型方法
    public void func1(E e){
        XXX
    }
    
    
    //泛型方法。明显的泛型方法标识<T>
    public <T> void func2(T t){
        XXX
    }
}

泛型类要在实例化的时候就指明类型,若在运行过程中需要换一种类型,需要重新创建泛型类。而泛型方法是在调用时指定泛型的具体类型。泛型方法进一步延迟指定类型的时机,减少硬编码,更加灵活。

public class GenericTest<E> {
    public void a(E e){
    };
    
     //泛型方法可以使用static
    public static <T> void b(T t){}
  
    //泛型方法
    public <T> void c(T t){}
    
    public static void main(String[] args) {
        GenericTest<String> o = new GenericTest<>();
        //泛型类的一般方法
        o.a("123");

        //泛型方法可以接收任意类型
        o.c(11);
        o.c(12.11);
        o.c(true);
    }
}

泛型通配符

Father类和Son类存在父子关系,Son类可以继承Father类,基于多态,编译器允许程序将Son类的实例赋值给Father引用。但ClassName 和 ClassName 这两个泛型类不存在继承关系,他们两个是相互独立的,没有任何关系,因此编译器不允许将 ClassName实例赋值给ClassName引用。

Father f;
Son s = new Son();
f = s; //正确

ClassName<Father> ff;
ClassName<Son> fs = new ClassName<>();
ff = fs; //编译报错

泛型参数存在继承关系,但泛型类之间不存在继承关系,即List和List是完全不同的类型。当某一个方法的操作逻辑相同,当形参是不同的泛型时,需要重载多个方法,即需要写d1 d2 d3三个方法。此时可以使用泛型通配符,只需一个方法d4即可接收所有泛型类型。其基本语法为ClassName<?>,此时可以认为 ClassName<?>是其他所有泛型类,如ClassName、ClassName等的父类。

	//该方法只能接收List<Object>泛型类    
	public void d1(List<Object> l ){
        操作逻辑
    }

	//该方法只能接收List<String>泛型类   
    public void d2(List<String> l ){
        操作逻辑
    }
	//该方法只能接收List<Integer>泛型类   
    public void d3(List<Integer> l ){
        操作逻辑
    }

	//该方法可以接收List<Object>、List<String>、List<Integer>等多种泛型类
	//减少硬编码,实现解耦
    public void d4(List<?> l ){
        操作逻辑
    }

使用泛型通配符的方法,内部遍历时使用Object接收每一个元素

 public void d4(List<?> l ){
        for(Object 0 : l){
            XXX
        }
    }

泛型通配符实现接收数据的统一,但由此导致写入和读取操作有所限制。

 public void d4(List<?> l ){
     //1 接收元素或遍历集合时使用Object对象
        for(Object 0 : l){
            XXX
        }
     
        Object oo = l.get(0);
     
     //2 无法确定通配符?代表的数据类型,不能随便添加元素
     list.add(123);//编译出错
    }

泛型受限

泛型通配符建立了泛型类(其泛型参数存在继承关系)的继承关系,但泛型通配符是全部匹配,而泛型受限进一步缩小匹配范围.

泛型上限

其语法为 ClasName<? extends ClassNamePara>,它可以认为是所有泛型参数为ClassNamePara类的子类的泛型类的父类,也是所有泛型参数是ClassNamePara类的泛型类的父类。类似数学中的 小于等于 关系。

//以下是三个独立的泛型类
//其泛型参数存在继承关系  son extends father; father extends Object
ClassName<Son> s = new ClassName<Son>();
ClassName<Father> f = new ClassName<Father>();
ClassName<Object> o = new ClassName<Object>();
//sx可以认为是所有泛型参数是Father子类的泛型类的父类
ClassName<? extends Father> sx = null;

//f实例对应的泛型类的泛型参数是Father,则sx是其父类
//基于多态,父类引用指向子类对象
sx = f; //编译通过

//s实例对应的泛型类的泛型参数是Son,Son是Father的子类,则sx可认为是s的父类
//基于多态,父类引用指向子类对象
sx = s;//编译通过

//sx 和 o 是不同的类实例
sx = o; //编译不通过。

泛型下限

其语法为 ClasName<? super ClassNamePara>,它可以认为是所有泛型参数为ClassNamePara类的父类的泛型类的父类,也是所有泛型参数是ClassNamePara类的泛型类的父类。类似数学中的 大于等于 关系。

//以下是三个独立的泛型类
//其泛型参数存在继承关系  son extends father; father extends Object
ClassName<Son> s = new ClassName<Son>();
ClassName<Father> f = new ClassName<Father>();
ClassName<Object> o = new ClassName<Object>();
//sx可以认为是所有泛型参数是Father子类的泛型类的父类
ClassName<? super Father> ss= null;

//f实例对应的泛型类的泛型参数是Father,则ss是其父类
//基于多态,父类引用指向子类对象
sx = f; //编译通过

//o实例对应的泛型类的泛型参数是Object,Object是Father的父类,则ss可认为是o的父类
//基于多态,父类引用指向子类对象
sx = o;//编译通过

//sx 和 o 是不同的类实例
sx = s; //编译不通过。

需要注意的是,泛型上下限是指泛型参数类型的上下限,extends 指向子类链路方向,此时泛型参数类型为类型上限。super指向父类链路方向,此时泛型参数类型为类型下限,其对应的泛型类始终是层级较高的父类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值