【数据结构】泛型

简介: 本文讲解:泛型

 image.gif编辑

目录

编辑

1.泛型

1.1Object类引出泛型概念

2.泛型语法

2.1泛型编写代码

3.泛型的机制

3.1擦除机制

4.泛型的上界

4.1泛型上界的语法

4.2泛型上界的使用

5.泛型方法

5.1泛型方法语法

5.2泛型方法的使用

image.gif编辑

1.泛型

一般的类和方法中,只能使用具体的代码来实现同一种类型数据的操作。比如一个数组里面存储的是同一种类型,这种存储方式太过于死板。因此JDK1.5引入了新的语法:泛型,通俗的来讲泛型就是多种数据类型(泛滥),从代码上来说就是实现了不同类型之间的存储,因此当我们想要存储各种各样的数据时,我们会使用到泛型。


1.1Object类引出泛型概念

在泛型之前,我们在Object类中学到了,所有类的父类都是Object类,因此我们能把一个数组设置为Object类型呢,这样就能达到数组里面存放各种各样的元素。所以我们可以这样去写代码:

class MyArray {
    //Object类型数组
    Object[] arr = new Object[3];
    public void show() {
        //分别初始化三种不同类型的数据
        arr[0] = 1;
        arr[1] = 1.2;
        arr[2] = "abc";
        //遍历arr数组
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.show();
    }
}

image.gif

运行后输出:

image.gif编辑

以上代码是可以很好的运行,因为我们直接初始化了Object类中的元素。当我们使用get、set方法来实现时就会发现不同处。

class MyArray {
    //Object类型数组
    public Object[] arr = new Object[3];
    //提供get方法
    public Object getPos(int pos) {
        return this.arr[pos];
    }
    //提供set方法
    public void setArr(int pos,Object value) {
        this.arr[pos] = value;
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        //分别设置了三种不同类型的元素
        myArray.setArr(0,1);
        myArray.setArr(1,1.2);
        myArray.setArr(2,"abc");
        //分别输出了三种不同类型的元素
        System.out.println(myArray.getPos(0));
        System.out.println(myArray.getPos(1));
        //输出字符类型时,报错
        System.out.println(myArray.getPos(3));
    }
}

image.gif

运行后输出:

image.gif编辑我们发现,当我往数组中添加了一个字符串时就会出现异常。所以,Object在存储不同类型的时候

还是会出现错误。因此,我们可以想到既然不让我存不同类型的数据,那么我就存同一种类型的数据就好了,这时我们就可以用到泛型。它可以将不同类型数据存储在不同的对象中,但在不同的对象中每一个元素的类型是相同的。


2.泛型语法

(1)语法1

//泛型类语法格式
class 泛型类名称<类型形参列表> {
    //内容
}
//泛型类中形参可为多个
class ClassName<T,S,B,U> {
    //内容
}

image.gif

上述代码中,我们可以看到泛型类与普通类多了一个<>其余并无太大差异,注意<>内可写多个参数。

(2)语法2

//泛型类继承类或泛型类
class 泛型类名称<类型形参列表> extends 类名 {
}
//泛型类继承泛型类
class ClassName1<T,S,B,U> extends ClassName2<T> {
}

image.gif

上述代码,表示了泛型类可以继承一个普通类,也可以继承一个泛型类


2.1泛型编写代码

因此,我们可以这样去编写一段泛型代码:

class MyArray<T> {
    //创建一个泛型数组
    T[] arr= (T[])new Object[3];
    //get方法
    public T getPos (int pos) {
        return this.arr[pos];
    }
    //set方法
    public void setArr (int pos,T value) {
        this.arr[pos] = value;
    }
}
public class Test {
    public static void main(String[] args) {
        //myArray1对象设置int类型数据
        MyArray<Integer> myArray1 = new MyArray<>();
        myArray1.setArr(0,1);
        myArray1.setArr(1,2);
        myArray1.setArr(2,3);
        //myArray2对象设置String类型数据
        MyArray<String> myArray2 = new MyArray<>();
        myArray2.setArr(0,"a");
        myArray2.setArr(1,"b");
        myArray2.setArr(2,"c");
        //通过get方法输出各个下标元素
        System.out.print(myArray1.getPos(0)+" ");
        System.out.print(myArray1.getPos(1)+" ");
        System.out.print(myArray1.getPos(2)+" ");
        System.out.print(myArray2.getPos(0)+" ");
        System.out.print(myArray2.getPos(1)+" ");
        System.out.print(myArray2.getPos(2));
    }
}

image.gif

运行后输出:

image.gif编辑

以上代码,就是泛型的一个体现,我们要想设置什么类型的数据就在<>里面设置什么类型的包装类即可。上述代码中相信get和set方法对于大家来说不是很难理解,但很多小伙伴第一件见这种代码,可能有些问题不太清楚,因此我来做出解释:

    1. 类名后面的<T>代表着占位符,表示着当前类为一个泛型类。<>里面的内容可以任意填写,你可以输入E、K、N等等。注意应当填写见名思意内容如T代表着type,N代表着number。
    2. T[] arr = new T[3];是不可行的,因为泛型不能直接new一个数组,但是我们可以强制类型转换如T[] arr = (T[]) new Object[];。
    3. 实例化泛型类时应当在<>只能是引用类型不得是基本类型因此通常我们填写包装类,并且该对象中值的类型要一致。
    4. 实例化泛型类对象时,前面<>内内容不得省略,后面<>内容可以省略。如:Array<String> array = new Array<> ();。

    3.泛型的机制

    泛型是一种运行时的机制,它会在编译时给我们指出一些错误,也会在获取元素时帮助我们进行强制类型转换。

    (1)编译时指出错误

    public static void main(String[] args) {
            MyArray<int> myArray = new MyArray<>();
        }

    image.gif

    报错:

    image.gif编辑

    上述报错,代表着泛型类中<>内容类型不能被定义为int。在上文中我们知道了,泛型类<>里面得是一个引用类型,因此int不能作为<>内参数。再比如以下代码:

    public static void main(String[] args) {
            MyArray<Integer> myArray = new MyArray<>();
            myArray.setArr(0,"abc");
        }

    image.gif

    报错:

    image.gif编辑上述代码,我们在给set方法传参的时候传了一个String类型的数据,并不符合myArray这个对象的属性。因此造成报错现象。


    (2)帮助进行强制类型转换

    class MyArray<T> {
        //创建一个泛型数组
        public Object[] arr = new Object[3];
        //get方法
        public T getPos (int pos) {
            return (T)arr[pos];
        }
        //set方法
        public void setArr (int pos,T value) {
            arr[pos] = value;
        }
    }
    public class Test {
        public static void main(String[] args) {
            //Integer泛型类
            MyArray<Integer> myArray = new MyArray<>();
            //自动帮我们进行类型转换了
            myArray.setArr(0,23);
            //String泛型类
            MyArray<String> myArray1 = new MyArray<>();
            //自动帮我们进行类型转换了
            myArray1.setArr(0,"abc");
        }
    }

    image.gif

    以上代码中,set方法形参列表第二个参数value为泛型T类型,但是在main方法中。我在给setArr方法传参的时候,直接传了一个整型和一个字符串。编译器并没有报错,那是因为泛型自动帮助我们进行了强制类型转换,也就是把T类型分别转成了整型和字符串型。


    3.1擦除机制

    通过上述讲解我们知道了,泛型会在我们编译时显示错误会帮助我们强制类型转换。表明了泛型是一种编译时的机制。那我们的泛型在运行后会是什么样呢?其实我们的泛型在编译后会被擦除为Object类型。

    class MyArray<T> {
        //创建一个泛型数组
        T[] arr= (T[])new Object[3];
        //get方法
        public T getPos (int pos) {
            return this.arr[pos];
        }
        //set方法
        public void setArr (int pos,T value) {
            this.arr[pos] = value;
        }
    }
    public class Test {
        public static void main(String[] args) {
            //实例一个泛型为Integer类的对象
            MyArray<Integer> myArray = new MyArray<>();
            myArray.setArr(0,1);
            myArray.setArr(1,2);
            //实例一个泛型为String类的对象
            MyArray<String> myArray1 = new MyArray<>();
            myArray1.setArr(0,"abc");
            myArray1.setArr(1,"def");
        }
    }

    image.gif

    调试后:

    image.gif编辑

    我们可以发现明明我们在创建数组的时候是这样的 T[] arr= (T[])new Object[3];,但编译器后台自动给我们编程了Object类型。因此,我们可以知道运行后编译器会擦除泛型类型给我们转换为Object类型。

    所以,我们可以这样创建一个泛型数组:

    class MyArray<T> {
        //创建一个泛型数组
        public Object[] arr = new Object[3];
        //get方法
        public T getPos (int pos) {
            return (T)arr[pos];
        }
        //set方法
        public void setArr (int pos,T value) {
            arr[pos] = value;
        }
    }

    image.gif

    image.gif编辑

    上述代码对数组进行一个初始化才是地道的初始化,而原来的T[] arr = (T[]) new T[3];并不是很地道,但也能达到效果。


    4.泛型的上界

    有一程序要求通过泛型找出一个数组的最大值,因此有以下代码:

    class MaxArray<T> {
        public void findMax(T[] arr) {
            T max = arr[0];
            for (int i = 0; i < arr.length; i++) {
                if (max > arr[i]) {
                    max = arr[i];
                }
            }
            System.out.println(max);
        }
    }
    public class Test {
        public static void main(String[] args) {
            MaxArray<Integer> maxArray =new MaxArray<>();
            Integer[] integers = {1,3,4,5,10,8,9,5,20};
            maxArray.findMax(integers);
        }
    }

    image.gif

    运行后报错:

    image.gif编辑

    上述代码报错原因是if里面的判断,当基本类型之间进行判断时可以使用算术符,当基本类型与引用类型之间进行判断时我们就得使用Comparable方法来判断。但是我们发现上述泛型并没有使用Comparable接口,因此我们可以使泛型继承这个接口就可以实现该操作,那么这样一个操作就代表着泛型上界这样一个概念。


    4.1泛型上界的语法

    class 泛型类<参数列表 extems 类型边界> {
            //内容
           }

    image.gif

    以上代码就是泛型类中参数类型继承一个类型边界的创建,实例:

    class Array<T extends Number> {
        //内容
    }

    image.gif

    只接受 Number 的子类型作为 T 的类型实参,因此只有关于Number的子类型我们能使用 ,比如Integer。String类型就不能。如:

    class Num<T extends Number> {
        //以下三个都行
        Num<Integer> num1 = new Num<>();
        Num<Byte> num2 = new Num<>();
        Num<Double> num3 = new Num<>();
        //会报错
        Num<String> num4 = new Num<>();
    }

    image.gif

    报错:

    image.gif编辑

    jdk-8帮助手册中描述了以下类为Number子类。

    image.gif编辑


    4.2泛型上界的使用

    还是4.1中那段代码,我们既然不能使用<来比较一个基本类型和一个引用类型,那我们就使T类型继承Comparable接口。

    class MaxArray<T extends Comparable<T>> {
        public void findMax(T[] arr) {
            T max = arr[0];
            for (int i = 0; i < arr.length; i++) {
                //使用了Comparable接口中的compareTo方法
                if (max.compareTo(arr[i]) < 0) {
                    max = arr[i];
                }
            }
            System.out.println(max);
        }
    }
    public class Test {
        public static void main(String[] args) {
            MaxArray<Integer> maxArray =new MaxArray<>();
            Integer[] integers = {1,3,4,5,10,8,9,5,20};
            maxArray.findMax(integers);
        }
    }

    image.gif

    运行后输出:

    image.gif编辑

    以上代码中泛型类中T类型继承了Comparable接口,因此if判断里面可以使用Comparable接口中的compareTo方法。此方法返回的值小于0代表前者比后者小,返回值等于0代表前者与后者相等,返回值大于0代表前者比后者大。


    5.泛型方法

    在上面我们学习到了泛型类的使用,那么泛型也是有方法的。我们可以把一个普通方法变成泛型方法去使用,那么泛型方法具体有什么用呢?下面我就来讲解:


    5.1泛型方法语法

    泛型方法的语法格式为:方法限定修饰符<类型形参列表> 返回值类型 方法名称(参数列表){ //内容}。

    实例:

    public <T> T maxNumber(T[] arr) {
        //内容
            }

    image.gif


    5.2泛型方法的使用

    求数组中的最大数:

    class Array {
        public <T extends Comparable<T>> T maxNum(T[] num){
            T max = num[0];
            for (int i = 0; i < num.length; i++) {
                if (max.compareTo(num[i]) < 0) {
                    max = num[i];
                }
            }
            return max;
        }
    }
    public class Test {
        public static void main(String[] args) {
            Array array = new Array();
            Integer[] arr = {1,23,4,5,6,7};
            Integer max=array.<Integer>maxNum(arr);
            System.out.println(max);
        }
    }

    image.gif

    运行后输出:

    image.gif编辑

    以上代码展示了泛型方法的使用,我们可以看到泛型方法的语法比较抽象,这就是泛型方法的难点之处。


    本期博客到这里就结束了,感谢各位的阅读。

    image.gif编辑

    下期预告:ArrayList与顺序表

    相关文章
    |
    6月前
    |
    安全 Java 开发者
    【Java】<泛型>,在编译阶段约束操作的数据结构,并进行检查。
    【Java】<泛型>,在编译阶段约束操作的数据结构,并进行检查。
    39 0
    |
    Java 编译器 API
    【数据结构】 简单认识包装类与泛型
    【数据结构】 简单认识包装类与泛型
    |
    27天前
    |
    存储 Java 编译器
    数据结构第四篇【再谈泛型】
    数据结构第四篇【再谈泛型】
    19 7
    |
    27天前
    |
    Java 语音技术 容器
    java数据结构泛型
    java数据结构泛型
    26 5
    |
    27天前
    |
    存储 Java 编译器
    【用Java学习数据结构系列】初识泛型
    【用Java学习数据结构系列】初识泛型
    18 2
    |
    存储 Java
    Java数据结构之第十四章、泛型进阶
    Java数据结构之第十四章、泛型进阶
    79 0
    |
    Java 编译器 API
    Java数据结构之第二章包装类&初识泛型
    可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制int num=10;//自动装箱//手动装箱//拆箱操作:将 Integer 对象中的值取出,放到一个基本数据类型中//自动拆箱1.3.2【面试题】//内部自动调用valueOf方法为什么分别输出true和false呢?接下来我们看看内部的源码:由于a和b在-128~127的范围内,所以返回cashe[127+(-128)]=cashe[-1];
    49 0
    |
    11月前
    |
    编译器
    【数据结构】初识泛型
    【数据结构】初识泛型
    |
    Java 编译器 容器
    【数据结构】包装类&简单认识泛型
    【数据结构】包装类&简单认识泛型
    60 0
    |
    Java 编译器
    【数据结构】什么是泛型?为什么要使用泛型?泛型怎么用?那包装类呢?
    发现Integer底层维护了一个数组,这个数组值的范围为[-128,127],如果Integet对象的值在这个范围内,直接从cache数组中拿,类似于字符串常量池,就是Integer类型的引用直接指向数组对应值的地址,如果Integer对象的值超过这个范围,会创建新的对象
    【数据结构】什么是泛型?为什么要使用泛型?泛型怎么用?那包装类呢?