泛型

本文深入解析Java泛型的概念与应用,涵盖泛型类、接口、方法等内容,详细讲解泛型通配符及其上下界限制,并探讨泛型与静态方法、可变参数的交互。

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

泛型

  java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

概述


泛型只在编译阶段有效: 在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
总结: 泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型

使用

泛型有三种使用方式,分别为:泛型类泛型接口泛型方法

① 泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以约束传入类型。最典型的就是各种容器类,如:List、Set、Map。

//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}
//
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);

泛型可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型

定义的泛型类,就一定要传入泛型类型实参么?并不是这样,如果没指定泛型则不会对传入类型做限制,传入类型可以是任何类型;

注意:

  • 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型 (如:int ,flot…等)

  • 不能对确切的泛型类型使用 instanceof 操作。如下面的操作是非法的,编译时会出错。


if(对象 instanceof Generic<Number>){
}

② 泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

public interface Generator<T> {
    public T next();
}

实现接口,未指定泛型

接口没有指定泛型时, 实现类必须声明泛型
如果实现类不声明泛型,如:class FruitGenerator implements Generator,编译器会报错:”Unknown class”

class FruitGenerator<T> implements Generator<T>{

    @Override
    public T next() {
        return null;
    }
}

实现接口,指定泛型

接口已经指定了泛型, 实现类不必再声明泛型, 但是所有使用泛型的地方都要替换成传入的实参类型;

例如: Generator,public T next();中的的T都要替换成传入的String类型。

public class FruitGenerator implements Generator<String> {

    @Override
    public String next() {
        ....
    }
}

③ 泛型通配符

我们需要一个在逻辑上可以用来表示同时是BeanA和BeanA的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。

 public class GenericTest {

     public static void main(String[] args) {

          Box<String> name = new Box<String>("corn");
          Box<Integer> age = new Box<Integer>(712);
          Box<Number> number = new Box<Number>(314);

          getData(name);
          getData(age);
          getData(number);
     }

     public static void getData(Box<?> data) {  // ?是String、Integer、Number的父类
         System.out.println("data :" + data.getData());
     }

 }

类型通配符上限 

? extends 具体类 : 表示传入<>内的类必须是具体类的子类

public class GenericTest {

      public static void main(String[] args) {

          Box<String> name = new Box<String>("corn");
          Box<Integer> age = new Box<Integer>(712);
          Box<Number> number = new Box<Number>(314);

         getData(name);
         getData(age);
         getData(number);

         //getUpperNumberData(name); // 1处编译报错
         getUpperNumberData(age);    // 2
         getUpperNumberData(number); // 3
      }

       // Number是Integer的父类
       // ? extends Number表示泛型只能传Number或其子类
       // String不是Number的子类所以很明显会报错
     public static void getUpperNumberData(Box<? extends Number> data){ 
         System.out.println("data :" + data.getData());
     }

 }

类型通配符下限 

? super 具体类 : 表示传入<>的类必须是具体类的父类

泛型方法的上下边界限制

在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界,即在泛型声明的时候添加;
总结 : 泛型的上下边界添加,必须与泛型的声明在一起 。


//public <T> T showKeyName(Generic<T extends Number> container),编译器会报错:"Unexpected bound"
public <T extends Number> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
T test = container.getKey();
return test;
}

③ 泛型方法


泛型类: 是在实例化类的时候指明泛型的具体类型;

泛型方法: 是在调用方法的时候指明泛型的具体类型 。

class Demo<V>{  
    public <T> T fun(T t){            // 可以接收任意类型的数据  
        return t ;                    // 直接把参数返回  
    }  

    public void get(V v){  //不是泛型方法           

    } 
};  

注意:

1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。

2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如: get(V v)方法

3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。

泛型方法使用举例:

public class GenericTest {
   //这个类是个泛型类,在上面已经介绍过
   public class Generic<T>{     
        private T key;

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

        //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey(){
            return key;
        }

        /**
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
        public E setKey(E key){
             this.key = keu
        }
        */
    }

    /** 
     * 这才是一个真正的泛型方法。
     * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个 
     *    如:public <T,K> K showKeyName(Generic<T> container){
     *        ...
     *        }
     */
    public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
        T test = container.getKey();
        return test;
    }

    //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

    //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
    //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
    public void showKeyValue2(Generic<?> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

     /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
    public <T> T showKeyName(Generic<E> container){
        ...
    }  
    */

    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
    public void showkey(T genericObj){

    }
    */

    public static void main(String[] args) {


    }
}

泛型类中的泛型方法

public class GenericFruit {
    class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    class GenerateTest<T>{
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
        //apple是Fruit的子类,所以这里可以
        generateTest.show_1(apple);
        //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
        //generateTest.show_1(person);

        //使用这两个方法都可以成功
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //使用这两个方法也都可以成功
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

④ 泛型方法与可变参数


public <T> void printMsg( T... args){
    for(T t : args){
        Log.d("泛型测试","t is " + t);
    }
}
printMsg("111",222,"aaaa","2323.4",55.55);

⑤ 静态方法与泛型


静态方法无法访问类上定义的泛型;
如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

public class StaticGenerator<T> {
    ....
    ....
    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){

    }
}

泛型数组

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值