我们看下使用:
public void test() {
String str = "test";
WrapperObject wrapper = new WrapperObject(str);
Object obj = wrapper.getContent();
// 获取长度,需要强转,mmp
int length = ((String) obj).length();
// 既然强转,那么随便转,很危险
int value = ((Integer) obj).intValue();
}
我们看到,虽然用 Object 解决了问题,但是使用起来很麻烦,而且很危险。那么有没有更好的解决方案呢,有!泛型!我们直接上代码: java学习交流:737251827
// 传递泛型参数,T 只是标记,随便写。
public class WrapperObject<T> {
private T content;
public WrapperObject(T content) {
this.content = content;
}
public String prefixHello() {
return "Hello" + content;
}
public String suffixAndroid() {
return content + "Android";
}
public T getContent() {
return content;
}
}
好,我们来看用法:
public void test() {
String str = "test";
// 这里创建的时候,传入了类型
WrapperObject<String> wrapper = new WrapperObject<>(str);
// 那么返回的值 就是创建时传入的类型
String content = wrapper.getContent();
// 获取长度,不用强转了
int length = content.length();
// 这里会直接报错,不能把 String 转换为 Integer
int value = ((Integer) content).intValue();
}
修改过的代码,简单明了,而且用起来非常方便,我们直接将需要的类型传入,就像一个参数一样,获取的时候就是我们需要的类型,这就是类型参数化。
泛型的使用
我们可以将泛型理解为一个 大于一般类型,小于 Object 类型 的类型,比如,T 一定是 Object,但是 Object 不一定是 T,所以 T 小于 Object,String 或 Integer 可以是 T,但是 T 不一定是 String 或 Integer,所以 T 大于 String,所以可以简单的理解为:
一般类型 < 泛型 < Object 类型
1 泛型类和泛型接口
泛型类的使用很简单,比如上面我们创建的 WrapperObject 类就是泛型类,它有个特点,就是类名后面跟上一个用尖括号括起来的类型,当然可以有多个,比如:
public class Fuck<A, B, C> {
private A a;
private B b;
private C c;
}
泛型接口跟泛型类是一样的,因为接口也是类的一种,比如常见的 List,Map 接口:
public interface List<E> extends Collection<E> {
}
可以看到,泛型接口/类 也是可以继承的,跟一般类没啥区别。
我们常见的 ArrayList,HashMap 等,都是泛型类,而且都是容器类,所以叫泛型容器。
2 泛型方法
泛型方法也很简单,我们知道,方法就是个黑盒,入口就是参数,出口就是返回值,所以我们关注这两方面即可。
泛型作为参数
很简单,我们需要在返回类型之前添加尖括号括起来的类型,然后在参数列表就可以像一般类型一样的使用,比如:
public <T> void test(T t) {
}
public static <T> void test(T t) {
}
泛型作为返回值
跟参数一样,返回类型之前加上尖括号括起来的类型,然后返回类型改为泛型即可:
public <T> T test(T t) {
return t;
}
public static <T> T test(T t) {
return t;
}
泛型的界
我们知道,泛型是小于 Object 的,然后又是大于一般类型的,那么它肯定能表示一个范围,这个范围就是边界,简称为界。
1 泛型的上界
假如现在我们有个需求,定义一个函数,入参是两个 int 类型的值,返回两数之和,太简单了,我们直接写:
// 求两数之和
public int add(int a, int b) {
return a + b;
}
完事之后,突然来了个 float,怎么办,于是我们发现了问题: 不是只有 int 才有加法这个操作,其他的 float,double,long 等,凡是 Number 的子类都具有加法,于是我们扩大这个加法的作用对象,改为 Number,如下:
public Number add(Number a, Number b) {
return a + b; // 这是个模拟方法,实际是没有的
}
然后我们来使用它:
public void test() {
int a = 10;
int b = 20;
// 这一行报错: Number 不能赋值给 int。
int c = add(a, b);
}
那么我们直接将 Number 改成 T 可以吗,不行!因为 T 没有"加法"这操作,只有 Number 及其子类有加法,那么我们需要一个是 Number 的子类的泛型,这就等价于限制了泛型的上界,也就是指定了它爹,代码如下:
// 通过<T extends Number>来指定上界
public <T extends Number> T add(T a, T b) {
return a + b;
}
public void test() {
int a = 10;
int b = 20;
// 传入的是 int,返回的也是 int,并且因为传入的是 Number 的子类,所以能使用加法
int c = add(a, b);
}
泛型的上界可以为类,接口,甚至另一个泛型,也可以有多个泛型,比如:
public <E, T extends E> T add(T a, T b) {
}
这里需要注意一点,两个"泛型类"之间不具有继承关系,比如:Integer 是 Number 的子类,但是 List<Integer>不是 List<Number>的子类。因为 List<Integer>整体是一个类型。
通过以上例子,我们可以看到,泛型可以表示一种动态类型,也可以表示一个范围。
2 泛型的下界
泛型没有下界,没有下界,没有下界!如果要使用下界,可以使用通配符?,比如:
public void test(List<? super Integer> list) {
}
因为通配符是另一个知识点,这里不多废话。
3 泛型多边界
我们知道泛型可以指定上界,我们又知道泛型就等价于一般类型,一般类型可以继承一个父类,可以实现多个接口,那么泛型指定一个上界,可以看成是继承一个父类,那么可不可以再指定一个接口上界呢,可以!
public <T extends Number & Serializable> void test() {
}
我们可以指定一个类和多个接口,等价于 java 类的单继承和多实现。而且这里面有个规则,类一定要紧跟在 extends 后面,接口使用 &连接在后面。接口可以有多个,类只能有一个。比如:
// 错误,因为 Number 是类,需要紧跟在 extends 后面。
public <T extends Serializable & Number> void test() {
}