造轮子的时候不敢用不会用泛型?那你看这篇就够了!

createh51个月前 (02-11)技术教程8


阅读本文解决什么问题?

解决许多java开发 或者android开发 在平时写一些基础架构,或者是造一些轮子的时候不敢用泛型,用不好泛型的问题。 甚至有些人使用泛型的时候报错都只会用idea提示的方法来修改代码,却不知这样改的原因,也不知道强转泛型会有什么恶果。

泛型用来解决什么问题

先定义一个模仿List 的泛型list。 我们来看看这个乞丐版的list能帮我们做什么事

public class CustomList {
    Object[] array = new Object[0];

    public T get(int index) {
        return (T) array[index];
    }

    public void add(T instance) {
        array[array.length - 1] = instance;
    }
}

看看怎么使用他

 CustomList customList = new CustomList<>();
        customList.add("hahahaha");
        String c = customList.get(0);

到这,我们来看看 到底有啥好处。 首先看这个add方法,有了泛型以后,我们就不需要担心类型转换错误了。 因为我们在定义的时候 指定了泛型的类型,所以如果我们在调用add方法的时候传了一个 非string类型的 那么ide就会报错了,即使你不用ide 用记事本写,你编译起来也会报错的。 这就是静态语言的好处了, 很多bug 在编译的时候告诉你 不用像js 那么蛋疼。

然后再看看get 这个函数,想一下 如果没有泛型的话, 我们get出来的值 是一定要强转成string才能赋值给c的, 但是现在有了泛型, 所以你可以直接get出来,这个类型转换的东西 早就帮你做好了。

总结一下泛型的好处:

  1. 避免运行时出错,编译时就告诉你
  2. 方便你使用,省略强制类型转换的代码

泛型为什么不可以是静态的?



这边可以想一下,为什么泛型不能用静态的去定义?你怎么改都是无法突破这个规则,是无法编译成功的。

前面的例子我们可以知道,泛型主要用来可以初始化每个实例的。 注意是每个实例,他是动态确定的,

取决于你当时使用的时候 传的是什么参数,比如List List List

对于一个静态变量来说 你如果用泛型 那就会乱套了。 例如我们上面截图的例子你用泛型会发生什么?

static Object ins? static String ins? static Teacher ins? 大家都叫ins,那我怎么知道 这个ins

到底应该是哪个类型的? 静态变量 全局唯一啊。 所以泛型是绝对不能用static来修饰的

这个地方一定要想明白了,想明白了,对你理解泛型是有好处的。

泛型的一种错误写法



这种就是一种典型的错误写法,明明接口中有泛型的,结果实体类中 把这个泛型给抹掉了。

虽然可以编译通过,但是这种写法就毫无意义了。



这种才是正确的写法,和前面那种错误的写法相比 我们明显可以省略一次强制类型转换。

大家可以比对一下这2种写法 和 文章开头泛型的2个优点。仔细体会一下。

如何正确extends泛型



泛型限制

interface IShop {
    T buy(float price);
}


interface IPhoneShop2 extends IShop {
    void repair(T phone);
}

前面我们说道 ,泛型最大的好处就是方便使用,比如上面的代码 我们使用起来就很轻松如意,但是因为这样的写法 太随意 所以要加一层限制。 上面的代码中,我们明明是一个手机商店,但实际使用的时候 却可以随便传, 传String 传Apple 传啥都行。 这和设计者的本意是不一致的。

所以泛型还可以加限制

interface Phone {

}

interface IPhoneShop2 extends IShop {
    void repair(T phone);
}

这样一来就可以限制我们使用时的类型,限制他一定得是Phone的类型才行。 考虑到java 是支持多接口,但是不支持多继承的,泛型的限制也遵循这个规定。


泛型限制在list中的迷惑性

来讲讲泛型中一个令很多人想不通的地方

定义一个水果 然后有苹果和香蕉

interface TFruit {

}

class Apple2 implements TFruit {
}

class Banana2 implements TFruit {
}

然后我们看看他们的使用



报红的地方为什么报错 是很多人想不明白的地方吗,我们的apple 命名是fruit的子类啊,为啥报错?

换个角度来思考一下这个问题:



所以对于 list 的 泛型来说, 他的类型 是动态确定的, 是没办法在编译期 确定的, 所以你需要保证他 在= 两边 泛型都是绝对一致的 才能声明成功,否则必定失败

继续看:



这个例子其实不难理解 为什么add 方法不能编译通过。



看到这,我相信很多人 都想告辞了。。。这tmd 泛型限制真多,咋用?不用了,以后自己造轮子自动屏蔽泛型了。

没关系 耐心一下,我们再缕一遍。

// =左边: 代表 我想要一个水果  =右边 :我给你个苹果  逻辑没问题 编译通过
        TFruit fruit = new Apple2();
        // =左边: 代表 我想要一个水果的list 任意的水果 =右边 :我给你个任意水果的list 逻辑没问题 编译通过
        List tf1 = new ArrayList();
        //既然是个水果的list 那我 add 苹果香蕉 肯定没问题
        tf1.add(new Apple2());
        tf1.add(new Banana2());


        // =左边: 代表 我想要一个水果的list 任意的水果  =右边 :我给你一个苹果的list
        //这样编译肯定不通过,因为我想要的是水果的list 你却给我一个苹果的list 这样你让我就没办法玩了
        // 我想要水果 你只给我苹果 那香蕉 葡萄 西瓜 我就没办法要了,所以你肯定不行 编译不过
        List tf2 = new ArrayList();



        //=左边: 代表 我想要一个list,这个list 必须是一个水果的类型,且只能是一种水果的类型   =右边 :我给你一个苹果的list
        //符合要求 编译通过
        List tf3 = new ArrayList();
        //我这个tf3 要求的是必须是一种水果的类型,但是我并不知道是那种类型,可能是水果 可能是葡萄 可能是香蕉
        // 所以你直接往我这塞一个确定好的水果  我肯定是不接受的,编译肯定失败
        tf3.add(new Apple2());
        tf3.add(new Banana2());

?extends 好像有点蠢?

前面的文章看完,是不是觉得 这个?extends 有点蠢了,实际上 他在某种场景下 是 十分有用的(废话 不然java为啥要这么设计)

还是上面的水果,我们增加一个方法 返回对应水果的价格

interface TFruit {
    int getPrice();
}

class Apple2 implements TFruit {
    @Override
    public int getPrice() {
        return 1;
    }
}

class Banana2 implements TFruit {
    @Override
    public int getPrice() {
        return 2;
    }
}

看看会有什么问题



看这个函数的参数, 这个函数的参数 意思是 我想要一个list ,这个list里面 可以放任何水果, 只要是水果就行,

但是在调用的时候 我们给他的 却是苹果的list 和 香蕉的list ,这就不是他想要的了,我想要任意类型的水果 你却给我 苹果或者是香蕉的,你帮我指定了具体类型 那肯定是不可以的。

所以这个时候 ? extends 就出场了

改完以后 就直接编译成功了:



回想一下上一小节的内容,这个? extends 不就是代表 想要任意一种水果吗

你既然想要的是任意 一种水果,那我给你苹果或者香蕉 肯定是ok的。

你们使用的泛型埋坑了吗?



而且是运行期间报错了。后果比较严重,不易察觉。

一个香蕉当然不能转成苹果。

平时写代码的时候一定不要这么写。

?super 又是啥,怎么用。



改成



就可以, 这里怎么理解?

加上? super就代表 等号右边的东西 你只要可以接受一个苹果的list就可以了。




相关文章

java 整型类型_Java基本类型-整型解读

java的基本类型包括以下几类:整型 byte short int long浮点型 float double字符型 char布尔型 boolean它们都有对应的包装类型(如果没有特殊说明,下面都是说包...

整型数据类型有哪些?有哪些表现形式?

整型用于表示没有小数部分的数值,它允许是负数。整型的范围与运行 Java 代码的机器无关,这正是 Java 程序具有很强移植能力的原因之一。与此相反,C 和 C++程序需要针对不同的处理器选择最有效的...

Java中的数据类型_java里面的数据类型

4.数据类型4.1 java中的数据类型分为两大类:基本数据类型和引用类型。基本数据类型:数值型 byte[1],short[2],int[4],long[8]浮点型 float[4],double[...

Java 里的基本类型和引用类型_java基本类型与引用类型

有天赋是一回事,有动力去深究细微之处却是另一回事。 ————科比·布莱恩特Java 里的数据类型分为 基础数据类型和引...

Java中的顺序语句结构:编程世界的“流水线”

在Java编程中,顺序语句结构是最基础、最常见的代码执行方式。它就像一条流水线,按照从上到下的顺序依次执行每一条语句。无论是初学者还是资深开发者,理解顺序语句结构都是掌握Java编程的关键一步。本文将...

Java 17的这些新特性,Java迈入新时代

前言2021年9月14日Java 17发布,作为新时代的农民工,有必要了解一下都有哪些新东西。Java 17是Java 11以来又一个LTS(长期支持)版本,Java 11 和Java 17之间发生了...