java泛型的理解

java中随处可见泛型的使用,下面作为个人笔记,做一些案例来理解泛型;
首先理解一下:
形参:T (可以理解为Object下的一个类)
实参:就是具体的类型,用来作为参数,一般叫这个过程为类型参数化
类型通配符:?(可以理解为是一切实参的父类,包括String,引用类型等)

案例测试一:


package Demo2_fanxing;

import java.util.ArrayList;
import java.util.List;

/**
 * 使用List来测试泛型
 * 泛型就是类型参数化:就是将类型作为参数,目的就是避免使用同一集合容器时,返回值时 类型转变报错
 * @author Administrator
 *
 */
public class demo1 {
    public static void main(String[] args) {    
        /*
         *什么都不使用时 
         */
        List list = new ArrayList();
         list.add("1");
         list.add("2");
         list.add(123);
         String a = (String) list.get(0);//此时必须强转,因为此时存放在list中的是Object类
         int b = (int) list.get(2);
         System.out.println(a+" "+b);



        /*
         * T的使用,这是泛型的基本使用,其实List接口就是使用List<T>这样定义,其方法的实现也是使用T
         * 在这里T为形参,String为实参(实际的类型参数)
         */
        List<String> list1 = new ArrayList<String>();
           list1.add("nihao");
           list1.add("hello");
        String model1 = list1.get(0);//根据指定的实参String 来规定形参T的类型
        System.out.println(model1);//nihao



        /*
         * 类型通配符?的理解
         */
        List<?> list2 = new ArrayList<>();
        /*
         * 在Java集合框架中,对于参数值是未知类型的容器类,只能读取其中元素,不能向其中添加元素,
         *  因为,其类型是未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是NULL
         */
        //list2.add("rock");//报错,不能添加元素
        //既然不能给它值,那么我们让它成为一个对象
        list2 = list1;
        //list2 = list;
        /*
         * 个人理解 : ?是一切实参的父类,(包括String,double,引用类型);
         * 所以上面的这行代码是 下级向上级 赋值,自动实现; 
         * 而下面的取值用String来承接,是上级向下级转换,必须得强转。
         * 其实在运用时,个人觉得这个用法基本用不到,在这就是表明 ? 是所有实参的父类
         */
        //String model2 = list2.get(0);//报错,集合list2不能判断返回值的具体类型
        String model3 = (String) list2.get(0);

    }

}

案例测试二:

/**
 * 泛型--类的认识
 * @author Administrator
 *
 */
public class demo2 {
    public static void main(String[] args) {
        Box<Integer> box = new Box(22);
        Box<String> box1 = new Box("nihao");

        Bag<Integer> bag = new Bag<Integer>();
          bag.setNum(123);

        box.test();//22
        box1.test();//nihao
        bag.test1();//123
        System.out.println((box.getClass()==box1.getClass()));//true
        /*
         * 由此可以看出,虽然给Box设定了不同的实参(一个是Integer,一个是String),但是他们都是共用的同一个对象
         *由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,
         *传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),
         *当然,在逻辑上我们可以理解成多个不同的泛型类型。
         *究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,
         *对于正确检验泛型结果后,会将泛型的相关信息擦除,并向上转型为 Objec,也就是说成功编译过后的class文件中是不包含任何泛型信息的。
         *泛型信息不会进入到运行时阶段。
         */

    }


}

 class Box<T>{
     T num;
     public Box(){

     }
     public Box(T num){
         this.num = num;
     }
     public void test(){
         System.out.println(num);
     }
 }

 class Bag<T>{
     T num;

    public T getNum() {
        return num;
    }

    public void setNum(T num) {
        this.num = num;
    }

     public void test1(){
         System.out.println(num);
     }

 }

案例测试三:

//:泛型用在数组上
class Aox<T>{
     T[] arr;    
     public Aox(int num){

         //this.arr = new T[num];//编译错误 可以理解为,
             //当new出对象后,编译器有一个擦除功能,就是将泛型的信息全部擦除掉,
             //T不等同于Object,可以理解为T是Object下面的类型,
             //所以说当new出来新对象后,那么这个数组的T信息被擦除掉了,那么这个数组里面的元素到底是什么类型?编译器无法
             //确定,所以编译不通过;

         this.arr = (T[]) new Object[num];//这里需要强转   
     }   
}

案例测试四:

public class demo4 {

    public static void main(String[] args) {

        Phone<String> pp1 = new Phone("nihao");
        Phone<Integer> pp2 = new Phone(222);
        demo4.getData(pp1);
        demo4.getData(pp2);//报错

        demo4.getDataQ(pp1);//报错
        demo4.getDataQ(pp2);
    }
    //下面两者的写法都行
    public static <T> void getData(Phone<T> pp){    
        String str = (String) pp.getNum();//返回的是Object类
        System.out.println(str);
    }
    public static void getDataQ(Phone<?> pp){   
        Integer intg = (Integer) pp.getNum();//返回的是?类型
        System.out.println(intg);
    }

}

  class Phone<T>{
      T num;

     public Phone(){

     }
     public Phone(T num){
         this.num = num;
     }

    public T getNum() {
        return num;
    }

    public void setNum(T num) {
        this.num = num;
    }



  }

小结:个人认为泛型的出现就是解决我们在获取值时(比如说从一个集合中),不会因为不知道类型而导致运行时异常;所以他放在了编译器,如果有错就会提醒。而在正真的运行时是不会有泛型的信息;
我们可以做一个比喻:
使用泛型时,我们可以把该对象(泛型使用者)作为一个书包,该书包里放什么东西都可以,不过在取得时候就麻烦了,不知道下一个掏出来的到底是什么物类;
所以我们给书包一个T标志,谁用的话就留下自己的标志(具体实参),那么再取得时候就知道该标志下取出来的是什么物类;
对于?这个标志,我们在用的时候是建立在T的基础上的,还是那个印有T的书包,我们在用的时候突然不知道要留下什么标志(不知道具体实参),那就给一个?,到时候放东西的时候再说放什么具体的物类;

泛型使用时还有一个“类型的上限” ,也就是说配置了类型上限,这个书包不能随心所欲的放任何物类,必须是在该类型下的物类才能放进去;
案例:
在泛型中,如果不对类型参数加以限制,它就可以接受任意的数据类型,只要它是被定义过的。但是,很多时候我们只需要一部分数据类型就够了,用户传递其他数据类型可能会引起错误。例如,编写一个泛型函数用于返回不同类型数组(Integer 数组、Double 数组等)中的最大值:

public <T> T getMax(T array[]){
    T max = null;
    for(T element : array){
        max = element.doubleValue() > max.doubleValue() ? element : max;
    }
    return max;
}

上面的代码会报错,doubleValue() 是 Number 类及其子类的方法,不是所有的类都有该方法,所以我们要限制类型参数 T,让它只能接受 Number 及其子类(Integer、Double、Character 等)。

通过 extends 关键字可以限制泛型的类型的上限,改进上面的代码:

public <T extends Number> T getMax(T array[]){
    T max = null;
    for(T element : array){
        max = element.doubleValue() > max.doubleValue() ? element : max;
    }
    return max;
}

表示 T 只接受 Number 及其子类,传入其他类型的数据会报错。这里的限定使用关键字 extends,后面可以是类也可以是接口。如果是类,只能有一个;但是接口可以有多个,并以“&”分隔,例如

class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }
}

现在要求在类的外部定义一个 printPoint() 方法用于输出坐标,怎么办呢?

可以这样来定义方法:

public void printPoint(Point p){
    System.out.println("This point is: " + p.getX() + ", " + p.getY());
}

我们知道,如果在使用泛型时没有指名具体的数据类型,就会擦除泛型类型,并向上转型为 Object,这与不使用泛型没什么两样。上面的代码没有指明数据类型,相当于:

public void printPoint(Point<Object, Object> p){
    System.out.println("This point is: " + p.getX() + ", " + p.getY());
}

为了避免类型擦除,可以使用通配符(?):

public class Demo {
    public static void main(String[] args){
        Point<Integer, Integer> p1 = new Point<Integer, Integer>();
        p1.setX(10);
        p1.setY(20);
        printPoint(p1);

        Point<String, String> p2 = new Point<String, String>();
        p2.setX("东京180度");
        p2.setY("北纬210度");
        printPoint(p2);
    }

    public static void printPoint(Point<?, ?> p){  // 使用通配符
        System.out.println("This point is: " + p.getX() + ", " + p.getY());
    }
}
class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }
}
运行结果:
This point is: 10, 20
This point is: 东京180度, 北纬210

但是,数字坐标与字符串坐标又有区别:数字可以表示x轴或y轴的坐标,字符串可以表示地球经纬度。现在又要求定义两个方法分别处理不同的坐标,一个方法只能接受数字类型的坐标,另一个方法只能接受字符串类型的坐标,怎么办呢?
这个问题的关键是要限制类型参数的范围,请先看下面的代码:

public class Demo {
    public static void main(String[] args){
        Point<Integer, Integer> p1 = new Point<Integer, Integer>();
        p1.setX(10);
        p1.setY(20);
        printNumPoint(p1);

        Point<String, String> p2 = new Point<String, String>();
        p2.setX("东京180度");
        p2.setY("北纬210度");
        printStrPoint(p2);
    }

    // 借助通配符限制泛型的范围
    public static void printNumPoint(Point<? extends Number, ? extends Number> p){
        System.out.println("x: " + p.getX() + ", y: " + p.getY());
    }

    public static void printStrPoint(Point<? extends String, ? extends String> p){
        System.out.println("GPS: " + p.getX() + "," + p.getY());
    }
}
class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }
}
运行结果:
x: 10, y: 20
GPS: 东京180度,北纬210度

? extends Number 表示泛型的类型参数只能是 Number 及其子类,? extends String 也一样,这与定义泛型类或泛型方法时限制类型参数的范围类似。

不过,使用通配符(?)不但可以限制类型的上限,还可以限制下限。限制下限使用 super 关键字,例如 <? super Number> 表示只能接受 Number 及其父类。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值