数据结构系列二:包装类+泛型


一、包装类

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

(1)基本数据类型和对应的包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
  • 除了Integercharacter,其余基本类型的包装类都是首字母大写

(2)装箱和拆箱

  • 装箱:把一个基本数据类型转化为包装类型的过程
  • 拆箱:将一个包装类中的值取出,放到一个基本类型中

自动装箱与自动拆箱

public class demo {
    public static void main(String[] args) {
        //自动装箱
        int i = 10;
        Integer a = i;
        
        //自动拆箱
        int b = a;
    }
}

显示装箱和显示拆箱

        //显示装箱
        int c = 20;
        Integer d = Integer.valueOf(c);
        
        //显示拆箱:拆箱为自己指定的元素
        int e = d.intValue();
        double e1 = d.doubleValue();

二、泛型

(1)什么是泛型

一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类,如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。—— 《Java编程思想》
泛型是在jdk1.5引入的新语法,通俗讲,泛型:就是适用于许多许多类型,从代码上将,就是对类型实现了参数化

(2)引出泛型

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

  1. 我们以前学过的数组,只能存放指定类型的数据,例如String[]int[]
  2. 所有类的父类,默认为Object类,数组是否可以创建为Object
class MyArray{
    Object[] array = new Object[10];

    public void set(int pos,Object val){
        this.array[pos] = val;
    }
    public Object get(int pos){
        return this.array[pos];
    }
}
public class demo1 {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.set(0,10000);
        myArray.set(1,"hello");
        String ret = myArray.get(1);//编译报错
        String ret = (String) myArray.get(1);
    }
}

以上代码实现后发现

  1. 任何类型数据都可以存放
  2. 1号下标本身就是字符串,但是却编译报错,必须进行强制类型转换才可以

虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型,而不是同时持有这么多类型,所以,泛型的主要目的:指定当前的容器,要持有什么类型的对象,让编译器去做检查,此时,就需要把类型作为参数传递,需要什么类型,就传入什么类型

(3)语法

class 泛型类名称<类型形参列表>{
    //这里可以使用类型参数
}
class ClassName<T1,T2……Tn>{
    
}
class 泛型类名称<类型形参列表> extends 继承类{
    //这里可以使用类型参数
}
class ClassName<T1,T2……Tn> extends ParentClass<T1>{
   //可以只使用部分类型参数
}

上述代码进行改写如下

class Myarray<T>{
    Object[] array = new Object[10];

    public void set(int pos,T val){
        this.array[pos] = val;
    }
    public T get(int pos) {
        return (T)this.array[pos];
    }
}
public class demo2 {
    public static void main(String[] args) {
        Myarray<String> myArray = new Myarray<>();
        myArray.set(0,"sadfklsajdfs");
        myArray.set(1,"qwe");
        String ret = myArray.get(0);
        String ret1 = myArray.get(1);
        System.out.println(ret);
        System.out.println(ret1);

        Myarray<Integer> myarray = new Myarray<>();
        myarray.set(0,3);
        Integer ret2 = myarray.get(0);
        System.out.println(ret2);
    }
}
//输出结果
sadfklsajdfs
qwe
3

Process finished with exit code 0

代码解释:

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

类型形参一般使用一个大写字母来表示,常用的名称有

  • E表示Element
  • K表示Key
  • V表示Value
  • N表示Number
  • T表示Type
  1. 不能new泛型类型的数组
T[] ts = new T[5];//是不对的
  1. 类型后需要加入包装类指定当前类型
Myarray<String> myArray = new Myarray<>();
  1. 存放元素的时候会帮我们检查,如果和包装类不同,会报错提示

(4)泛型类的使用

1.语法

泛型类<类型实参> 变量名 = new 泛型类<类型实参>(构造方法实参);

2.示例

尖括号中只能是引用类型,不能是基本类型

Myarray<String> myArray = new Myarray<String>();

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

3.类型推导

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写,但是尖括号不能省略

Myarray<String> myArray = new Myarray<>();

(5)裸类型

裸类型是一个泛型类但是没有带着类型实参

Myarray list = new Myarray();

注意:我们不要自己去使用裸类型,裸类型是为了兼容老版本的API保留的机制

(6)泛型是如何编译的

1.擦除机制

那么,泛型到底是怎么编译的呢?这个问题也是曾经的一个面试问题,泛型本质是一个非常难的语法,要理解好它还是需要一定的时间打磨
我们先察看字节码文件,发现所有的T都是Object

  • 在编译的过程中,将所有的T替换为Object这种机制,我们称为擦除机制
  • 运行的时候没有泛型这样的概念,泛型的擦除机制,只存在于编译期间

2.为什么不能实例化泛型类数组?

原因:替换后的方法为:将Object[]分给Integer[]引用,程序报错
不能证明所有类型都是Integer类型
通俗讲就是:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Integer,运行的时候,直接传给Integer类型的数组,编译器认为是不安全的

(7)泛型的上界

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

1.语法

class 泛型类名称<类型实参 extends 类型边界>{

}

2.示例

class test<E extends Number>{
    
}

extends表示拓展,只接受Number的子类型作为E的类型实参

class test<E extends Number>{

}
public class demo4 {
    public static void main(String[] args) {
        test<Integer> a1;//正常,因为Integer是Number的子类型
        test<String> a2;//编译错误,因为String不是Number的子类型
    }
}

没有指定类型边界E,可以视为E extends Object

3.复杂示例

写一个泛型类,实现一个方法,这个方法是就指定类型泛型的最大值

class Alg<T extends Comparable<T>>{
    public T findMax(T[] array){
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(array[i].compareTo(max) > 0){
                max = array[i];
            }
        }
        return max;
    }
}
public class demo5 {
    public static void main(String[] args) {
        Alg<Integer> alg1 = new Alg<>();
        Integer[] array = {1,2,3,4,5};
        Integer ret = alg1.findMax(array);
        System.out.println(ret);

    }
}
//输出结果
5

Process finished with exit code 0

(8)泛型方法

1.语法

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

2.示例

class Util{
    public <T extends Comparable<T>> T findMax(T[] array){
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(array[i].compareTo(max) > 0){
                max = array[i];
            }
        }
        return max;
    }
}
public class demo6 {
    public static void main(String[] args) {
        Util util = new Util();
        Integer[] array = {1,2,3,4,5};
        Integer ret = util.findMax(array);
        System.out.println(ret);
    }
}
//输出结果
5

Process finished with exit code 0

静态方法可以直接通过类名调用

class Util{
    public static <T extends Comparable<T>> T findMax(T[] array){
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            if(array[i].compareTo(max) > 0){
                max = array[i];
            }
        }
        return max;
    }
}
public class demo6 {
    public static void main(String[] args) {
        Integer[] array = {1,2,3,4,5};
        Integer ret = Util.findMax(array);
        System.out.println(ret);
    }
}
//输出结果
5

Process finished with exit code 0

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值