【Java数据结构】—泛型编程,让我们一起领略泛型的魅力

#1024程序员节  |  征文#

  1. 这里有很多小伙伴的就要问啦到底什么是泛型呢?
  2. 泛型到底是怎么实现的呢?
  3. 学会了泛型对于我们来说到底有什么作用呢?

接下来就让博主带领大家一起认识学习吧~~~

1:引出泛型:

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

(而不是像Object类型一样什么都能接收,我们就是一个模版,需要具体哪一个类型就传哪一个类型,你指定了类型,后续你传类型传错了,编译器会报警告)

1.1:Object数组

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

返回数组中某个下标的值(get和set方法)

思路:

1. 我们以前学过的数组,只能存放指定类型的元素,例如:int[] array = new int[10]; String[] strs = new String[10];

2. 所有类的父类,默认为Object类。我们的数组就可以创建为Object类型的数组

class MyArray{
    Object [] myArray = new Object[10];
    public Object get(int pos){
        return myArray[pos];
    }
    public void set (Object val,int pos){
        myArray[pos] = val;
    }

}

public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.set("hello",2);
        myArray.set(10,1);
        
        String s = (String) myArray.get(2);
        int a = (int) myArray.get(1);
        System.out.println(s);

    }

这样一个类我们就建立好了,但是我们发现我们每次拿出来数据都要强制类型转化

所以我就在想我们构建了这个数组,能不能让大家都用(这个模版呢)呢?(大家用的时候都只放一种数据类型)

其实这就是泛型的思维(就像我构造一个方法,每次调用这个方法传递数据,我都能进行使用)

1.2:这里就引出泛型

class MyArray<T>{
    Object [] array = new Object[10];
    public T get(int pos){
        return (T)array[pos];
    }
    public void set( T val,int pos){
        array[pos] = val;
    }
    
}
public class Test{

 public static void main2(String[] args) {
        MyArray<String> myArray = new MyArray<>();
        myArray.set("hello",2);

        //myArray.set(10,1);
        MyArray<Integer> myArray1 = new MyArray<>();
        myArray1.set(10,1);
        myArray1.set(9,2);
        Integer a = myArray1.get(1);
}
}

这里就可以看到指定类型之后,再放其他类型的数据就会报错了;

大家新建一个对象就好了,这就是一个泛型数组

注意:

代码解释:

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

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

E 表示 Element

K 表示 Key

V 表示 Value

N 表示 Number

T 表示 Type

S, U, V 等等 - 第二、第三、第四个类型

2. 不能new泛型类型的数组

意味着:T[] ts = new T[5];//是不对的

T[] array = (T[])new Object[10];这样写其实也不好

3.我们一开始用的是裸类型(MyArray list = new MyArray();

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

下面的类型擦除部分,我们也会讲到编译器是如何使用裸类型的。

小结:

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

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

3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换

都没问题?那我可要提问了!

3:泛型如何编译的?

3.1 擦除机制

那么,泛型到底是怎么编译的?这个问题,也是曾经的一个面试问题。泛型本质是一个非常难的语法,要理解好他还是需要一定的时间打磨。

我们可以看看代码的反汇编:

输入 javap -c MyArray

大家都可以看到编译过后,所有的T都变成了Object

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

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

 

这里我再给大家提出两个问题

1、那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?

不是,一定不能new一个T(泛型)的数组,T是一个类型变量,不是一个具体的数据类型

数组的建立是需要指定的类型的

2、类型擦除,一定是把T变成Object吗?

前提是没有指定边界,如果,T extends Comparable<T>接口的话,只能接收实现了Comparable接口的类型了

下面是关于泛型编程擦除机制的文章,大家可以看一下

泛型编程的擦除机制

3.2:所以说能不能返回一个泛型的数组呢?

class MyArray<T>{
    Object [] array = new Object[10];
    public T get(int pos){
        return (T)array[pos];
    }
    public void set( T val,int pos){
        array[pos] = val;
    }
    public T[] getArray() {
        return (T[])array;
    }

}

public class Test {
    public static void main(String[] args) {
        MyArray<String> myArray = new MyArray<>();
        String []ret = myArray.getArray();
    }

我们可以看到报了类型转换的错误(运行时T已经变成Object类型了,你用String接收,你能保证数组里面的类型都是String吗?其他和String不相关的类型,你就算是强转也没有用,别这样写,尽管你一开始就指定了T为String类)(这是编译器为了防止你放裸类型)

例如:String[ ]接收就会报错

还是报错,所以说我们只能用Object类接收

代码改成如下(其实你返回T[ ] 也可以(不合理但是合法)最好还是返回Object[ ]的数组)

4:接下来我们写一个泛型类用来比较大小

不过我们先要了解一下泛型的上界

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

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

...

}

稍微举一个例子

public class MyArray<E extends Number> {

...

}

MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型

MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型

这里的extends指定的泛型的上界,传过来的T类型必须是实现了Comparable接口的类型

譬如Integer和String类型

public class Test1 {

public static void main(String[] args) {
        Alg1<Integer> alg1 = new Alg1<>();
        Alg1<String> alg2 = new Alg1<>();
     }
}
class Alg1 <T extends Comparable<T>>{  //泛型的上界
    public  T compareMax(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
           /* if (max < array[i]) {
                max = array[i];
            }*/    //我们比较两个元素的时候,一般都要实现Comparable接口,譬如两个String类型不能直接比较大小

        }
        return max;
    }
}

如果我们传递过来的类型没有实现Comparable接口,编译器就会报错

5:泛型方法实现

那我们不想每次都进行实例化对象,我们既可以实现一个泛型方法

在方法前面加上<T>代表你是一个泛型方法,要用Comparable接口就直接实现,泛型的上界

class Test1{
    public static void main(String[] args) {
        Alg alg = new Alg();
       Integer[] integers ={
    
    1,2,300,199,200};
       int ret= Alg.compareMax(integers);
        System.out.println(ret);
    }

}
class Alg {
    public static <T extends Comparable<T>> T compareMax(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
           /* if (max < array[i]) {
                max = array[i];
            }*/
        }
        return max;
    }
}

小结:

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

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

3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换

最后给大家提出一个问题:

这样的写法哪里出错了?

class Alg1 <T extends Comparable<T>>{
    public static   T compareMax(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
           /* if (max < array[i]) {
                max = array[i];
            }*/    //我们比较两个元素的时候,一般都要实现Comparable接口,譬如两个String类型不能直接比较大小

        }
        return max;
    }
}

写法哪里出错了?

解释:

compareMax是一个静态方法,我们调用它的时候根本不需要实例化对象,也就是说无法指定在类级别上的泛型类型参数是Integer或者其他(我们无法指定类上面的泛型参数,因为类上面的泛型参数需要我们实例化对象的时候传过去,我们现在传不了)所以

静态成员不依赖于类的实例,因此也不依赖于实例的泛型类型参数。

所以说:你想要静态方法使用泛型类型参数,但这些参数必须直接定义在方法上,而不是类上。(如上述的静态方法法)

 public static <T extends Comparable<T>> T compareMax(T[] array) {}

上述就是 【Java数据结构】—泛型编程的全部内容啦,能看到这里相信您一定对小编的文章有了一定的认可,泛型的出现让我们的代码的可重复利用性变的更高了,我们代码的性能又提高啦~~~

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正

您的支持就是我最大的动力​​​!!!!

猜你喜欢

转载自blog.csdn.net/2302_80639556/article/details/143221787