数据结构 | 认识泛型

1.包装类

(1)回顾数据类型

在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个类型都对应了一个包装类. 

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

(2)装箱和拆箱

装箱(装包):把基本类型 转变成 包装类型.

  int i = 10;
    //装箱操作 新建一个Integer类型对象,将i的值放入对象的某个属性中
    Integer ii = new Integer(i);
    Integer ii2 = Integer.valueOf(i);//手动装箱 底层都调用了valueOf()方法

拆箱(拆包):把包装类型 转变为 基本数据类型.

int i = 10;
//拆箱操作,将Integer对象中的值取出,放到一个基本数据类型中
Interger ii2 = new Integer(10);
int b = ii2;
int bb = ii.intValue();//调用了intValue()方法 手动显示拆箱

自动拆箱和自动装箱都可以在"反汇编"中显示. 

查看以下代码的运行结果:

 public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;

        Integer c = 128;
        Integer d = 128;
        
        System.out.println(a == b);//true
        System.out.println(c == d);//false
    }

观察Integer类的源码中的valueOf()发现:

 

当i的值属于[-128,127]范围在,会直接返回Integer缓存数组中相应对象的引用;如果i大于127或小于-128,会重新创建一个Integer实例,并返回.

所以,a和b的值都在缓存范围内,因此他们指向了同一个对象,因此返回true;而c和d的值不在范围内,都是通过new创建出来的,因此不是同一个对象,返回false.

2.什么是泛型

泛型是在JDK1.5后引入的新语法.通俗讲,泛型就是适用于许多许多类型.从代码上讲,就是对类型实现了参数化.(对类型作为参数进行传递).

泛型类的使用 

 案例代码:

//实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值

//创建泛型类
class Myarray <T> {
    public Object[] array = new Object[10];//1
    public T getPos(int pos) {
        return (T) array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}

//main方法中
 public static void main(String[] args) {
        Myarray<Integer> myarray = new Myarray<>();//2
        myarray.setVal(0,1);
        myarray.setVal(1,2);
        int ret = myarray.getPos(0);//3
        System.out.println(ret);
        mysrray.setVal(2,"hi");//4

        Myarray<String> myarray1 = new Myarray<>();
        myarray1.setVal(1,"hello");
        myarray1.setVal(2,"world");
        String ret2 = myarray1.getPos(2);
        System.out.println(ret2);
    }

代码解析:

  1. 类名后的<T> 表占位符,表示当前类是一个泛型类.

    (类型形参一般使用大写字母表示,常用的名称有:E,K,V,N,T,S,U,V...).E表示Element,K表示Key,V表示Value,这三个常用.

    <>里面的类型,不能是基本数据类型,要是引用类型;<>里面可以写多个.

  2. 不能new泛型类型的数组.第一`是因为我们实例化的数组是一个具体的类型,写成这样,我们无法知道是哪种数据类型~第二,更重要的原因是,数组是一种单独的数据类型,数组之间没有 继承关系 的说法.所以此处创建数组正确写法就是注释1处的写法,并把所有的Object用T代替.

  3. 注释2处,就是在实例化泛型类.类型后加入<Integer>指定当前类型.<>里面表示 指定当前类以什么样的类型进行参数的传递,后面的第二个<>里面可以省略不写.

  4. 注释3处,不需要1进行强制类型转换.

  5. 注释4处,代码编译报错.此时因为在注释2处指定类当前类型为Integer类型,代码中为String类型,编译器会在存放元素的时候帮助我们进行类型检查.

总结:

        泛型是将数据类型参数化,进行传递.

        使用<T>表示当前类是一个泛型类.

        泛型存在的意义就是,1在编译时检查数据类型是否正确;2在编译时帮助进行类型转换.

        需要注意的是,泛型只能接受类,所有的基本数据类型必须使用包装类!

泛型是如何编译的 

擦除机制

编译的过程当中,将所有的 T 替换成 Object 这种机制,称为 擦除机制.

因为泛型是编译时期的机制,也意味着运行时没有泛型的概念 ==> JVM当中没有泛型的概念.

class Myarray <T> {
    public T[] array = (T[])new Object[10];
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
    public T[] getArray(){
        return array;
    }
}

//main方法中
public static void main(String[] args) {
        Myarray<Integer>  myarray= new Myarray<>();
        Integer[] strings = myarray.getArray();
    }

 分析:我们可能会认为,擦除机制不是在编译的时候,会将T替换为Object吗?为什么会报类型转换异常呢?

原因:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候直接转给Integer类型的数组,编译器认为是不安全的.()

3.泛型的上界 

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束.

泛型类:

语法:

        class 泛型类名称<类型形参 extends 类型边界>{...}

    (1)示例:

        public class MyArray<E extends Number> {..}

        E:一定是Number 或 Number的子类

    (2)复杂示例

        public class MyArray<E extends Comparable<E>> {...}

        E:传入的E一定是实现了该接口的.

泛型方法:

语法:

        方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) {...}

 代码实现

泛型类

//写一个泛型类,求一个数组的最大值
class Alg<T extends Comparable<T>> {
    public T findMaxVal(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if(array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;
    }
}

public class demo1 {
    public static void main(String[] args) {
        Integer[] array = {7,2,3,6,8,1,20};//定义一个`Integer类型的数组
        Alg<Integer> alg = new Alg<>();
        System.out.println(alg.findMaxVal(array));//输出结果20
    }
}

解析: 

  1.                                                                           <T>表示这是一个泛型类. <T extends Comparable<T>>表示泛型的边界.                   擦除机制会将所有的T替换成Object,但是在Object源码中并没有实现Comparable接口.那么当数组进行比较大小是无法调用compareTo()方法的,这时就要设置泛型的边界.             extends Comparable<T>代表传入的T一定是实现了Comparable接口,compareTo()方法才可以调用.
  2. 实例化一个泛型类,将Integer作为泛型类型的参数进行传递. 那么为什么Integer可以用?👇👇是因为在Integer源码里面,其本身实现了Comparable接口,重写了compareTo()方法.       
  3. (拓展)因此当我们想传递一个Person作为泛型类型的参数进行传递时,一定要实现Comparable接口,重写compareTo()方法,才不会报错.👇👇

泛型方法 

class Alg2 {
    public <T extends Comparable<T>> T findMaxVal(T[] array) {
        
            T max = array[0];
            for (int i = 0; i < array.length; i++) {
                if(array[i].compareTo(max) > 0) {
                    max = array[i];
                }
            }
            return max;

    }
}

public class demo1 {
    public static void main(String[] args) {

        Integer[] array = {66,799,88};
        
        Alg2 alg2 = new Alg2();//实例化对象 不会报错,因为Alg2类是一个普普通通的类
        alg2.<Integer>findMaxVal(array);//推导.传递的参数的类型是什么,<T>里面就是什么
        alg2.findMaxVal(array);//<Integer>也可以省略不写
    }
}

解析:

  1.  结构如右图.创建一个普通的类,在类中写一个泛型方法.
  2. 设置泛型边界,实现Comparable接口去调用compareTo()方法完成数组比较大小的操作.
  3. <T>代表alg2调用findMaxVal时传了一个Integer类型的参数,通过Integer就可以推导出<T>里面是什么类型. (划重点!!)   
静态泛型方法 
//泛型方法--静态方法
class Alg22 {
    public static <T extends Comparable<T>> T findMaxVal(T[] array) {

        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if(array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;

    }
}

public class demo1 {
    public static void main(String[] args) {
        Integer[] array = {66,799,88};

        Alg22.<Integer>findMaxVal(array);
        Alg22.findMaxVal(array);//<Integer> 可省略不写
        
    }
}

解析:

 

每次调用findMaxVal方法都会实例化一个对象,因此我们可以把上述的泛型写成静态泛型方法,用static修饰,就可以不用new对象了,直接用类名调用~~

4.总结 

一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类.如果要编写可以应用于多种类型的代码,我们就用到了泛型.

泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,让编译器去检查.此时,就需要把类型作为参数传递.需要什么类型就传入什么类型.(泛型只能接收类,所有的基本数据类型必须写成包装类!)

 


 寄语:不要忘了你希望的远方

 

 

 

  • 41
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值