造轮子的时候不敢用不会用泛型?那你看这篇就够了!
阅读本文解决什么问题?
解决许多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出来,这个类型转换的东西 早就帮你做好了。
总结一下泛型的好处:
- 避免运行时出错,编译时就告诉你
- 方便你使用,省略强制类型转换的代码
泛型为什么不可以是静态的?
这边可以想一下,为什么泛型不能用静态的去定义?你怎么改都是无法突破这个规则,是无法编译成功的。
前面的例子我们可以知道,泛型主要用来可以初始化每个实例的。 注意是每个实例,他是动态确定的,
取决于你当时使用的时候 传的是什么参数,比如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 extends TFruit> 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就可以了。