栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Java】泛型

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Java】泛型

今天我们学习一下泛型的有关知识 

目录

1. 什么是泛型

2.泛型

2.1语法

3.使用泛型类

3.1语法

3.2 示例

 4.裸类型

5.泛型是如何编译的 

5.1 擦除机制 

5.2 为什么不能实例化泛型类型数组

6.泛型的上界

7.泛型方法

8.通配符

9.包装类 

装箱和拆箱 


 

1. 什么是泛型

  一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。

如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。

----- 来源《Java编程思想》对泛型的介绍。

通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化
 

2.泛型

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

我们用所有类的父类,Object类 来创建数组试试
代码示例
 

class MyArray {
    public Object[] array = new Object[10];
    public Object getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,Object val) {
        this.array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0,10);
        myArray.setVal(1,"hello");//字符串也可以存放
        //String ret = myArray.getPos(1);//编译报错
        String ret = (String) myArray.getPos(1);
        System.out.println(ret);
    }
}

问题:以上代码实现后 发现

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

思考:虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型。而不是同时持有这么多类型。

所以,我们引出泛型,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

2.1语法
class 泛型类名称<类型形参列表> {
   // 这里可以使用类型参数
}

class ClassName {

}
class 泛型类名称<类型形参列表> extends 继承类 {
    // 这里可以使用类型参数
}

class ClassName extends ParentClass {
    // 可以只使用部分类型参数
}


 上述代码进行改写如下:

class MyArray {
    public T[] array = (T[])new Object[10];//1
    
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        
        MyArray myArray = new MyArray<>();//2
        myArray.setVal(0,10);
        myArray.setVal(1,12);
        Integer ret = myArray.getPos(1);//3
        System.out.println(ret);
        //MyArray myArray2 = new MyArray<>();//如果没有这句就报错
        myArray2.setVal(2,"bit");//4
    }
}

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

了解:

【规范】类型形参一般使用一个大写字母表示,常用的名称有:

  • E 表示 Element
  • K 表示 Key
  • V 表示 Value
  • N 表示 Number
  • T 表示 Type

 

可以有多个 

 2. 注释1处,不能new泛型类型的数组

意味着:

T[] ts = new T[5];//是不对的

3. 注释2处,类型后加入 指定当前类型
4. 注释3处,不需要进行强制类型转换
5. 注释4处,代码编译报错,此时因为在注释2处指定类当前的类型,此时在注释4处,编译器会在存放元素的时候帮助我们进行类型检查。
 

3.使用泛型类

3.1语法
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

3.2 示例
MyArray list = new MyArray();

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

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

MyArray list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 String

 4.裸类型

裸类型是为了兼容老版本的 API 保留的机制 

裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型

MyArray list = new MyArray();

5.泛型是如何编译的
 

5.1 擦除机制
 

那么,泛型到底是怎么编译的?

通过命令:javap -c 查看字节码文件,所有的T都是Object。
 

 

 在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制

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

5.2 为什么不能实例化泛型类型数组

为什么报错? 

 

class MyArray {
    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;
    }
}
    public static void main(String[] args) {
        MyArray myArray1 = new MyArray<>();
        Integer[] strings = myArray1.getArray();
    }

 原因:替换后的方法为:将Object[]分配给Integer[]引用,程序报错。

 

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

返回的Object数组里面,可能存放的是任何的数据类型,可能是String,运行的时
候,直接转给Intefer类型的数组,编译器认为是不安全的。
 

6.泛型的上界

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

 

语法

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

 示例

public class MyArray {
    ...
}

 含义是:只接受 Number 的子类型作为 E 的类型实参

补充:如果没有指定上界, 可以视为 E extends Object

更复杂的一种情况

public class MyArray> {
     ...
}

 E必须是实现了Comparable接口的

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

 特殊:如果是静态的泛型方法  需要在static后用<>声明泛型类型参数

class Util {
    //静态的泛型方法 需要在static后用<>声明泛型类型参数
    public static  void swap(E[] array, int i, int j) {
        E t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
}

8.通配符

通配符的符号是一个 ?

 通配符是用来解决泛型无法协变的问题的

首先什么是协变呢?如果 Student 是 Person 的子类,那么 List 也应
该是 List 的子类。但是泛型是不支持这样的父子类关系的。

由于泛型 T 是确定的类型,一旦你传了我就定下来了,所以我们需要通配符,通配符更为灵活或者说是不确定,更多的是用于扩充参数的范围
 

public static void fun(Message temp){
   ...
}

 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改

它只能获取,但不能修改 

在"?"的基础上又产生了两个子通配符:

  • ? extends 类:设置泛型上限
  • ? super 类:设置泛型下限

 

通配符上界


//可以传入的实参类型是Number或者Number的子类

 通配符的上界,一般用来读取数据,不能写入数据

通配符下界


//代表 可以传入的实参的类型是Integer或者Integer的父类类型

9.包装类 

这个我们之前就已经接触过

基本数据类型对应的包装类型 

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


装箱和拆箱 

 

        int a =10;
        Integer b = a;//自动装箱
        Integer c = Integer .valueOf(a);//手动装箱

        Integer d = 10;
        int e = d;//自动拆箱
        double f = b.doubleValue();//手动拆箱

 我们补充一个面试题

 

 

 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/832105.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号