泛型是Java最具影响力的新特性之一,Java程序员需要深入理解这一特性。
|-- 从字面上看:泛型就是泛泛的指定对象所操作的类型,而不像常规方式一样使用某种固定的类型去指定。
|-- 从本质上看:泛型就是参数化类型,在创建类、接口、方法时可以用类型参数指定他们所要操作的数据类型。
最终都是要被实现的。
/**一个泛型类的简单实例**/
public class Gen<T> { //这里的T是类型参数的名称,又叫标记,是实际类型的占位符
private T ob;
Gen(T ob){
this.ob = ob;
}
public T getOb(){
return ob;
}
public T setOb(T ob){
this.ob = ob;
return this.ob;
}
/*
* 因为Object是所有类的基类,所以这里可以使用getClass()和getName()。
* 如果使用到其他类的方法,是不行的。因为编译器并不知道T代表的具体类型。
* 如果要想使用其他类的方法,T要限定范围,也就是有界类型。
*
* */
public void showtype(){
System.out.println("ob的类型是:"+ob.getClass().getName());
}
}
/**下面是一个泛型类不合理的实现方式**/
public class GenDemo {
public static void main(String[] args) {
//Gen<Integer>将<Integer>传递给力Gen<T>的T。因为是在类上声明的所以对整个类都有效。
Gen<Integer> Ob1 = new Gen(new Integer(123));
Integer i = Ob1.getOb();
System.out.println(i);
Ob1.showtype();
//Gen没有指定要传递的数据类型,编译后会被Object替换。new Gen<String>只是在创建Gen实例时指定Gen副本的T。
7 //这时构造函数相当于 Object ob = "123456"。Jdk7以后创建实例好像已经不用重新声明了、
Gen ob2 = new Gen<String>("123456");
Object s = ob2.getOb();
System.out.println(s);
ob2.showtype();//java.lang.String。getClass()获取的是创建"123456"这个对象的类。
//ob1=ob2;/**同一种泛型可以对应多个版本(因为参数类型是不确定的),但是不同版本的泛型类实例是不兼容的。**/
}
}
需要注意的是至始至终只有一个 Gen 类。Java编译器实际上只是擦除了所有泛型类信息,将之替换成必须的类型。
/**一个加深对泛型理解的例子**/
public class Tool{
public <T> void add(Collection<? super T> a,T b){
// <?>可以引用各种参数化的类型,可以调用与参数无关的方法,不能调用与参数有关的方法
// a.addAll(a);// 编译失败。 boolean add(E e)
a.remove(b);// boolean remove(Object o)
}
}
public class Test {
public static void main(String[] args) {
Tool tool =new Tool();
//不完全确定list的类型。至少可以是Number,不管是什么类型,完全可以装下Integer
List<? super Number> list = new ArrayList<Number>();
//完全不确定list的类型。如果是Long就装不下,Integer
// List<? extends Number> list = new ArrayList<Number>();//编译失败
list.add(new Integer(123));
tool.add(list,123);
}
}
从上面可以看出泛型的一些使用规则:
|-- 泛型的类型参数只能是类类型(包括自定义类),不能是基础数据类型。
不同版本的泛型类实例是不兼容的。
|-- 泛型可以是静态的也可以是非静态的,静态内不能使用类型参数。静态方法的返回类型不能是类型参数
|-- 不能创建泛型异常类。
|-- 类型参数不能被实例化。
使用泛型的好处:
|-- 创建类型安全的代码,将运行时错误转换成编译时错误。
|-- 简化了处理过程,不再需要显示的使用 强制类型转换,所有类型转换都是自动、隐式进行的。
|-- 扩展了代码的重用能力。
泛型的语法:
|-- 声明泛型类:class class_name<type_param_list>
class<type_arg_list>(cons_arg_list);
泛型的有界类型:
|-- 告诉编译器类型参数的范围。我们在实例化对象时所传递的类型也必须在这个范围之内。
范围:Number及其子类。
范围:Number及其父类。
|-- 限制方法和可以操作对象的类型。
泛型的通配符:
|-- 在泛型中可以使用通配符参数,用?来指定。表示未知类型
|-- 解决了指定占位符操作数据类型的局限性。可以同时操作多种数据类型。
<T extends 和 <?extends Number> 的区别。从范围来看他们是相等的。但是。。。
类型参数最终都是要被实现的。
/** 一个简单的有界类 **/
/*
* 一个简单的计算数组和的类
* 实现:
* 要操作的数据类型很多,想到 泛型。
* 所有数字类型都是Number的子类。
* 在计算时要把对象转成基本数据类型进行运算。
* 所以要限定标记的范围,告诉编译器我们要使用的数据类型都是Number的子类。
*
* */
// 只有同一种数据类型才可以比较 T
public class Stats<T extends Number> {
private T[] nums;
Stats(T[] n) {
nums = n;
}
T[] get() {
return nums;
}
/*
*判断两个数组的大小
*
*这里的Stats<?>是函数的参数,是为了更安全的表述ob的类型。
*
*/
public double compareSum(Stats<?> ob) {
return add() - ob.add();
}
public double add() {
double sum = 0.0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i].doubleValue();
}
return sum;
}
}
public static void main(String[] args) {
Integer[] nums = {2,3,1,5,6,3,4};
// Integer nums[] = {2,3,1,5,6,3,4,10};//数组的另一种表现形式。
Number[] nums2 = {1.1,2.1,3.1,4};
Stats<Integer> stats = new Stats<Integer>(nums);
Stats<Number> stats2 = new Stats<Number>(nums2);
double sum = stats.add();
double sum2 = stats2.add();
System.out.println(sum + "--" +sum2);
System.out.println(stats.compareSum(stats2));
}
泛型方法:
|-- 可以将类方法泛型化,即使他们的类不是泛型类。
|-- 泛型方法可以指定参数类型,返回值类型,参数之间的关系。
/*
* 用泛型方法判断任意一个数是否在一个数组中
*
*/
public class GenFunc {
public static <T, Y extends T> boolean isIn(T t, Y[] y) {
for (int i = 0; i < y.length; i++) {
if (t.equals(y[i])) return true;
}
return false;
}
public static void main(String[] args) {
Integer[] nums = {21,32,545,87} ;
int i = 21;
if(isIn(i,nums)) System.out.println(i + "在nums中");
String[] strs = {"123","456","789"} ;
String s = "123";
if(isIn(s,strs)) System.out.println(s + "在nums中");
}
}
|-- 类型参数在方法返回前声明
泛型可以是静态的也可以是非静态的,静态方法不能使用定义在类上的类型参数。只能定义在方法上、
强制类型安全)
泛型接口:
|-- 泛型接口可以针对不同类型的数据进行实现。。。
|-- 一般而言如果实现了泛型接口,那么类也必须泛型化,至少应该实现接口的类型参数
子类都必须向上传递超类所需要的类型参数。
/*
* GenInterface功能扩展。
* 希望GenInterface可以实现计算出对象数组的最值功能。
* 想到了接口
* 对象比较想到Comparable
* Comparable<T>的子类都具有比较功能。
* 两个对象比较,必须数据类型一致,并且是Comparable<T>的子类才可以比较
*
* */
/***
* 比较大小要返回值,返回值类型不确定。 泛型。
* 只有同一种数据类型才可以比较 T
* 创建接口的时候最好限定类型范围,这符合逻辑思维
****/
interface minMax<T extends Comparable<T>> {
T min(T[] vals);
T max(T[] vals);
}
/***********
*
* 子类必须现实接口的类型 minMax<T>,把T传递个接口
* 不实现默认Object 泛型就没意义了
* 如果你觉得这里有点牵强,只要保证子类在范围内不就行了。那就错了。
* 如果没有把类型传递给接口:那么就相当于
* 接口的函数是:Object min(Object vals)
* GenInterface:T min(T[] vals) 这是不行的。。。
*
******/
public class GenInterface<T extends Comparable<T>> implements minMax<T> {
public T min(T[] vals) {
T min = vals[0];
for (int i = 1; i < vals.length; i++)
if (min.compareTo(vals[i]) > 0) min = vals[i];
return min;
}
public T max(T[] vals) {
T max = vals[0];
for (int i = 1; i < vals.length; i++)
if (max.compareTo(vals[i]) < 0) max = vals[i];
return max;
}
public static void main(String[] args) {
String[] vals = {"asdas","bsdas","esdasd","ffasd"};
GenInterface<String> demo1 = new GenInterface<String>();
String min = demo1.min(vals);
String max = demo1.max(vals);
System.out.println(min +"--"+ max);
}
}
泛型类实例的强制类型转换:
泛型类实例的类型相互兼容,类型参数也相同的时候才可以。
|-- 但是这样转换有意义?
|-- GenInterface<String> demo1 = new GenInterface<String>();
GenInterface<Integen> demo2 = new GenInterface<Integen>();
GenInterface类型实例,但是已经是两种完全不兼容的对象了、这是泛型提高代码重用带来的、
泛型工作的原理:
|-- 为了兼容以前的老版本,java使用擦拭实现泛型。怎么擦拭
|-- 使用具体类型替换类型参数,如果没有显示的指定,就使用Object。