泛型的定义及使用
1. 定义泛型:
Student<T>
2. 类中使用泛型
Student<T> 这个 T 表示派生自object的任何类,比如 string、 intenger、 Double 等。 但是要注意的是, T 一定要
是派生自 object 类。
Class Student<T> {
private T name ;
public T getName(){
return name;
}
public void setName(T name){
this.name = name;
}
}
3. 使用泛型类
// 构造实例
Student<String> s = new Student<String>();
s.setName("张三");
system.out.printin(s.getName());
4. 使用泛型的优势?
I. 为什么不用 object 代替泛型?就是因为强制转换会出现意想不到的错误。
II. 在编译期就能报错。
多泛型变量的定义及字母规范
1. 多泛型变量定义
上面我们只定义了一个泛型变量,那如果需要多个变量怎么办? 只需要类似下面这样就行了。
新加的变量和上例中的 T 用法一样, 这样的变量想加几个加几个。只要用 "," 隔开就行。
class Student<T,E>{
Private T name;
Private E age;
......
}
class Student<T,E,U,K,V>{}
2. 字母规范
指定泛型的可以是任意一个大写字母,咩有特定的含义。 但是为了提高可读性,大家还是用比较有意义的字母为好。
- E, Element 常用在 collection 中,如: List<E>, iterator<E>, Set<E>
- K,V key-value 键值对
- N, Number 数字
- T, Type 类型,如 string integer 等
泛型接口的定义和使用
- 非泛型类
- 泛型类
泛型函数的定义和使用
不管是静态还是非静态函数,不管是有没有返回值,都在返回值类型前加符号<T> ,以用来标识泛型。
- 静态泛型函数
- 非静态泛型函数
public class StaticFans {
//静态函数
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函数
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
// 有返回值的泛型函数
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
}
其他用法:Class<T> 类传递及泛型数组
Class<T> 其实也是一种泛型,用来装载 class 对象的。
public final class Class<T> implements Serializable {
…………
}
泛型高级知识: 类型绑定和通配符
泛型的类型绑定: extends
- <T extends BoundingType> 就是给泛型参数加一个界限。
- 此时的 extends 不等同于 继承。和继承没有任何联系。
- BoundingType 可以是类,也可以是接口。 T 代表的类型是被包含的意思。具有 BoundingType的功能。
- 能提前调用 T 的父类或父接口中的方法。
- 绑定接口
public interface Comparable<T> { boolean compareto(T i); } public class StringCompare implements Comparable<StringCompare> { String mStr; public StringCompare(String string) { this.mStr = string; } @Override public boolean compareto(StringCompare i) { if (mStr == null) { throw new NullPointerException(); } if (mStr.length() > i.mStr.length()) { return true; } return false; } } public static <T extends Comparable> T min(T... a) { T smallest = a[0]; for (T item : a) { if (smallest.compareto(item)) { smallest = item; } } } // 调用时候 // 可以看出类型绑定有两个作用: // 1、对填充的泛型加以限定 // 2、使用泛型变量T时,可以使用BoundingType内部的函数。 public static <T extends Comparable> T min(T... a) { T smallest = a[0]; for (T item : a) { if (smallest.compareto(item)) { smallest = item; } } return smallest; } StringCompare result = min(new StringCompare("1"),new StringCompare("123")); Log.e("xyd","result = " + result.mStr);
- 绑定类
class Fruit{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public static <T extends Fruit> String getFruitName(T t){ return t.getName(); } class Banana extends Fruit{ public Banana() { setName("banana"); } } class Apple extends Fruit{ public Apple() { setName("apple"); } } // 最后使用; Log.e("xyd","FruitName = " + getFruitName(new Banana())); Log.e("xyd","FruitName = " + getFruitName(new Apple()));
- 绑定多个绑定,用 & 连接
public static <T extends Fruit&Serializable> String getFruitName(T t){ ... } 加深难度,如果有多个泛型,每个泛型都带绑定? public static <T extends Comparable & Serializable, U extends Runnable> T foo(T a, U b){ ... }
通配符
无边界通配符
- 无边界通配符初识
有没有办法,只生成一个变量,可以将不同的实例赋值给它呢?首先定义定义一个泛型类: class Point<T>{ private T x; private T y; public Point() { } public Point(T x, T y) { this.x = x; this.y = y; } public T getX() { return x; } public void setX(T x) { this.x = x; } public T getY() { return y; } public void setY(T y) { this.y = y; } } Point<?> point; point = new Point<Integer>(3,3); point = new Point<Float>(2.3f,5.6f); point = new Point<Double>(6.3d,46.5d); point = new Point<Long>(10l,58l); 这里的 ? 就是无边界通配符。就是一个任意的类,一个未知的符号。 point = new Point<String>("",""); point = new Point<Object>(); 不光能将 T 填充为数值类,任意的 Object 的子类都可以匹配给通配符。
- 通配符 ? 和 T 的区别
他们俩没有任何联系; 泛型变量T不能在代码中用于创建变量,只能在类、接口、函数中声明后使用。 无界通配符只能用于填充泛型变量T,表示通配任何类型。它是用来填充 T 的,只是填充方式的一种!! // 无边界通配符填充 Box<?> box; // 其他类型填充 Box<String> stringBox;
- 通配符只能用于填充泛型变量T,不能用于定义变量
只能用来填充变量T的位置,不能用于定义变量。通配符的使用位置只有: Box<?> box; box = new Box<String>(); 而不能用于定义变量: box = new Box<?>(); // 错误 ? x; // 错误
通配符 ? 的 extends 绑定 // 上边界限定通配符
通配符?可以代表任意类型,但跟泛型一样,如果不加以限定,在后期的使用中编译器可能不会报错。所以我们同样要对通配符加以限定。
绑定的形式,同样是通过extends 关键字,意义和使用方法都和泛型变量一致。
为了保证泛型类有意义,需要对泛型类的参数做限制,不能超出限制之外。
如有的参数只有是数字类型才有意义。
Point<? extends Number> point;
point = new Point<Number>();
point = new Point<Float>(3.4f,4.3f);
point = new Point<Double>(3.4d,4.3d);
point = new Point<Long>(3.4l,4.3l);
point = new Point<String>("",""); // 报错
point = new Point<Object>(); // 报错
//----
编译时候,最后两行会报错。
new Point<Number>(); 不会报错,说明无边界通配符的extends绑定包括边界自身。
无边界通配符只是泛型T的填充方式,给他加上限定,只是限定了赋值给它的实例类型。
如果想从根本上解决乱填充Point的问题,需要从 Point 泛型类定义时候就加上 <T extends Number>
注意: 利用<? extends Number> 定义的变量,只可取其中的值,不可修改。
Point<? extends Number> point;
point = new Point<Integer>(3,33);
Number Integer_x = point.getX();
point.setX(new Integer(222)); // 报错
// 为什么会报错???
因为 Point 的类型是由 Point<? extends Number> 决定的,并不会因为 point = new Point<Integer>(3,33)而改变类型。
即便 point = new Point<Integer>(3,3) 之后, point 的类型依然是 Point<? extends Number>(),
即派生自 Number 的未知类型!!!
怎么理解? 如果在 point = new Point<Integer>(3,3) 之后, point 就变成了 Poin<Integer> 类,那后面
point = new Point<Long>(2l,22l); 肯定会因为类型不匹配而报编译错误了。正因为 point 的类型始终是
Point<? extends Number> 因此能继续被各种类型的实例赋值。
继续正题,为什么会报错?
point 的类型为 Point<? extends Number> , 那就是说填充 point 泛型变量 T 的是 <? extends Number>,
这是一个什么类型? 未知类型。怎么可能用一个未知类型来给设置内部值! 这是不合理的。
但是取值时, 正由于泛型变量T被填充为<? extends Number> 所以编译器能确定 T 肯定是 Number 的子类。编译器就会用 Number 来填充 T。
也就是说,编译器,只要能确定通配符类型,就会允许,如果无法确定通配符的类型,就会报错。
通配符 ?的 supper 绑定 // 下边界限定通配符
总结:
通配符的使用可以对泛型参数做出某些限制,使的代码更安全,对于上边界和下边界限定的通配符总结如下:
- 使用 List<? extends C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素类型是 C 的子类型 ( 包含 C 本身)的一种。
- 使用 List<? super C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素就类型是 C 的超类型 ( 包含 C 本身 ) 的一种。
参考资料:
Java 泛型总结(一):基本用法与类型擦除
Java 泛型总结(二):泛型与数组
Java 泛型总结(三):通配符的使用
夯实JAVA基本之一 —— 泛型详解(1):基本使用
夯实JAVA基本之一——泛型详解(2):高级进阶
什么是协变?为什么数组是支持协变的?为什么泛型不支持协变?
参考知乎 - 胖胖 的答案