零基础看懂什么是泛型(上)

发布于:2022-12-19 ⋅ 阅读:(647) ⋅ 点赞:(0)

目录

一、泛型

1.泛型

2.泛型的语法

二、泛型类的使用

1.语法

2.推导

三、裸类型

1.裸类型的说明

四、泛型的编译

1.擦除机制

2.实例化泛型类型数组的可取性


一、泛型

1.泛型

泛型:使用于各种类型。

一般我们在敲写代码时,通常会因为同样的代码但参与其中的类型不同而将某串代码敲上好几遍。这个时候,我们就想有没有一种东西可以让我们的代码只写一遍但是能够适用于许多的类型?

答案是有的,那就是我们的泛型

《Java编程思想》中对泛型也有详细的介绍:

一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的 代码,这种刻板的限制对代码的束缚就会很大。

 如果此时,我们去写一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值。那么,我们该如何写呐?

首先,我们会想到数组中可以存放任何类型的数据,因为,Obejct是所有类的父类。那么可以将数组定义为Obejct类型的数组。

可,这样真的行得通吗?

我们来看代码:

上面代码中,我们可以看到,字符串也能放在我们定义的数组中。但是却存在编译报错的情况。当然,我们也可通过强制类型转换来修改我们的代码已达到正常运行的目标。

但是,我们从上面的代码可以知道,如果使用Object来定义数组,那么必定产生以下问题:

1.任何类型都能放到数组里面去

2.必须进行强制类型转换

 而在大多数情况下,我们想达到的要求是:尽量少的类型放到数组里面去,而不是所有类型都可以放到数组里面去。

此时我们的泛型就派上了大用场:

泛型的主要目的:就是指定当前的容器要持有什么类型的对象。让编译器去做检查

 也就是说,我们需要将类型作为参数传递。需要什么类型,我们就传入什么类型。

2.泛型的语法

class 泛型类名称 <类型形参列表>
{
    
}

类型形参列表中可以存在一个或多个。

那么,我们现在可以将上面的代码进行以下修改:

 我们可以看到,目前的代码已经完成了将类型作为参数进行传递的工作,我们再来分析一下目前的代码:

1.<T>

当前的<T>代表一个占位符,表示当前是一个泛型类型

类型的形参一般都是大写。常用的如下所示:

          E

代表Element
K 代表Key
V 代表Value
N 代表Number
T 代表Type
S、U、V等等 代表第二、第三、第四个类型

2.public T[] array=(T[])new Object[10];

不能new泛型类型的数组。

也就是说 T[] array=new T[];    是不对的。 

当然,public T[] array=(T[])new Object[10];  也不是最好的写法

3.MyArray <Integer> myArray=new MyArray<>();

类型后加上<Integer>指定当前的类型

也就是说,在这个地方,我们new了一个myArray指定它的类型为Integer。这里做到了将类型作为参数进行传递。

4.int ret=myArray.getPos(2);

在这里,与Object类型数组不同,解决了必须要进行强制类型转换的问题

5.myArray.setVil(3,"hello");

报错

因为,当前myArray指定的类型是Integer类型,所以不能存放“hello”。

编译器会在存放元素时,帮助我们进行类型的检查。

二、泛型类的使用

1.语法

泛型类<类型实参> 变量名;     // 定义一个泛型类引用

new 泛型类<类型实参>(构造方法实参);   // 实例化一个泛型类对象

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

例如:

MyArray <Integer> myArray=new MyArray<Integer>();

2.推导

关于泛型类的类型推导,当编译器可以根据上下文来推导当前类型实参的时候,我们可以不用写类型实参。

例如:

MyArray <Integer> myArray=new MyArray<>();//可以推导出,当前的类型实参为Integer

三、裸类型

1.裸类型的说明

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

例如:

MyArray  myArray=new MyArray();

当然,我们自己不要轻易的去使用裸类型。 

四、泛型的编译

1.擦除机制

我们首先来看一张图片:

从照片上我们可以看出来,在Java的字节码中,所有的T都被Object替换。

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

Java的擦除机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。(在运行期间不存在泛型这个概念

2.实例化泛型类型数组的可取性

首先,我们要明确:不能实例化泛型类型数组

class MyArray<T>{
    public T[] array=(T[])new Object[10];
    public T getPos(int pos){
        return this.array[pos];
    }
    public void setVil(int pos,T vil){
        this.array[pos]=vil;
    }
    public T[] getArray(){
        return array;
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray <Integer> myArray=new MyArray<>();
        myArray.setVil(2,3);
        int ret=myArray.getPos(2);
        Integer[] strings=myArray.getArray();
    }
}

上面代码报错:

 Exception in thread " main " java . Lang Classcastexception Create breakpoint [ Ljava . lang Object : cannot be cast to [ Ljat Test . main ( Test java : 28 )

线程“main”java.Lang类CastException中的异常创建断点[Ljav.Lang对象:无法强制转换为[Ljat Test.main(测试java:28)

报错的原因是:将Object[]分配给Integer[]引用,程序报错。

数组泛型之间存在一个非常重要的区别是它们如何强制执行类型检查

数组运行时存储和检查类型信息。

泛型编译时检查类型错误。

也就是说,在返回的Object数组中,可能存放任意的数据类型,在运行的时候直接转给Integer类型的数组,编译器认为这样做是不安全的。 

正确的做法应该是:

class MyArray<T>{
    public T[] array;
    public MyArray(){
        
    }
    public MyArray(Class<T>clazz,int capacity){
        array=(T[])Array.newInstance(clazz,capacity);
    }
    
    public T getPos(int pos){
        return this.array[pos];
    }
    public void setVil(int pos,T vil){
        this.array[pos]=vil;
    }
    public T[] getArray(){
        return array;
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray <Integer> myArray=new MyArray<>();
        myArray.setVil(2,3);
        int ret=myArray.getPos(2);
        Integer[] integers=myArray.getArray();
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到