解决许多java开发 或者android开发 在平时写一些基础架构,或者是造一些轮子的时候不敢用泛型,用不好泛型的问题。甚至有些人使用泛型的时候报错都只会用idea提示的方法来修改代码,却不知这样改的原因,也不知道强转泛型会有什么恶果。
泛型用来解决什么问题
先定义一个模仿List 的泛型list。我们来看看这个乞丐版的list能帮我们做什么事
public class CustomList<T> {
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<String> customList = new CustomList<>();
customList.add("hahahaha");
String c = customList.get(0);
到这,我们来看看 到底有啥好处。首先看这个add方法,有了泛型以后,我们就不需要担心类型转换错误了。
因为我们在定义的时候 指定了泛型的类型,所以如果我们在调用add方法的时候传了一个 非string类型的 那么ide就会报错了,即使你不用ide 用记事本写,你编译起来也会报错的。
这就是静态语言的好处了,很多bug 在编译的时候告诉你,不用像js 那么蛋疼。
然后再看看get 这个函数,想一下 如果没有泛型的话, 我们get出来的值 是一定要强转成string才能赋值给c的, 但是现在有了泛型, 所以你可以直接get出来,这个类型转换的东西 早就帮你做好了。
总结一下泛型的好处:
- 避免运行时出错,编译时就告诉你
- 方便你使用,省略强制类型转换的代码
泛型为什么不可以是静态的?
这边可以想一下,为什么泛型不能用静态的去定义?你怎么改都是无法突破这个规则,是无法编译成功的。
前面的例子我们可以知道,泛型主要用来可以初始化每个实例的。注意是每个实例,他是动态确定的, 取决于你当时使用的时候 传的是什么参数,比如List<Object> List<String> List<Teacher>
对于一个静态变量来说 你如果用泛型 那就会乱套了。例如我们上面截图的例子你用泛型会发生什么?
static Object ins?static String ins?static Teacher ins?大家都叫ins,那我怎么知道 这个ins 到底应该是哪个类型的?静态变量 全局唯一啊。所以泛型是绝对不能用static来修饰的。
这个地方一定要想明白了,想明白了,对你理解泛型是有好处的。
泛型的一种错误写法
这种就是一种典型的错误写法,明明接口中有泛型的,结果实体类中 把这个泛型给抹掉了。 虽然可以编译通过,但是这种写法就毫无意义了。
这种才是正确的写法,和前面那种错误的写法相比 我们明显可以省略一次强制类型转换。 大家可以比对一下这2种写法 和 文章开头泛型的2个优点。仔细体会一下。
如何正确extends泛型
泛型限制
interface IShop<T> {
T buy(float price);
}
interface IPhoneShop2<T> extends IShop<T> {
void repair(T phone);
}
前面我们说道 ,泛型最大的好处就是方便使用,比如上面的代码 我们使用起来就很轻松如意,但是因为这样的写法 太随意 所以要加一层限制。 上面的代码中,我们明明是一个手机商店,但实际使用的时候却可以随便传,传String 传Apple 传啥都行。这和设计者的本意是不一致的。
所以泛型还可以加限制
interface Phone {
}
interface IPhoneShop2<T extends Phone> extends IShop<T> {
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<TFruit> tf1 = new ArrayList<TFruit>();
//既然是个水果的list 那我 add 苹果香蕉 肯定没问题
tf1.add(new Apple2());
tf1.add(new Banana2());
// =左边:代表 我想要一个水果的list 任意的水果 =右边 :我给你一个苹果的list
//这样编译肯定不通过,因为我想要的是水果的list 你却给我一个苹果的list 这样你让我就没办法玩了
// 我想要水果 你只给我苹果 那香蕉 葡萄 西瓜 我就没办法要了,所以你肯定不行 编译不过
List<TFruit> tf2 = new ArrayList<Apple2>();
//=左边:代表 我想要一个list,这个list 必须是一个水果的类型,且只能是一种水果的类型 =右边 :我给你一个苹果的list
//符合要求 编译通过
List<? extends TFruit> tf3 = new ArrayList<Apple2>();
//我这个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就可以了。
原文链接: DK_BurNIng https://juejin.im/post/5e86a58d6fb9a03c8b4bf701 文源网络,仅供学习之用,如有侵权,联系删除。
我将优质的技术文章和经验总结都汇集在了我的公众号【Java圈子】里。
为方便大家学习,我整理了一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,免费提供给热爱Java的同学! 更有学习交流群,多交流问题才能更快进步~
来源:oschina
链接:https://my.oschina.net/u/4477286/blog/3222826