泛型编程
泛型编程:写的代码可以处理任何的类型。(例如,当你要新建立一个数组时,但还不确定该数组的类型
可能后面要用的是整型数组,也可能要用的是字符串型数组,甚至栈类型数组。但因为Object类是所有类的
基类,所以此时只要将该数组定义成Object类型,这样其他的每个类型的数组都可以使用该Object类型的数
组,这就是泛型编程。)
在早期Java 5版本出现之前,人们要实现泛型编程,都是将类型定义成Object类型来实现的。
1 package L14; 2 import java.util.Arrays; 3 4 public class ObjectTest { 5 private Object[] array; //定义一个Object类数组 6 private int size; //存储数据元素个数 7 8 public ObjectTest(int size){ 9 this.array= new Object[size]; //给数组初始化 //一般新new的都是对象,而T只是个类型参数 10 this.size=0; 11 } 12 public ObjectTest(){ 13 this(10); 14 } 15 public void add(Object data){ //将添加的元素定义成Object类型 16 if(this.size==this.array.length){ //当数组满了,要进行扩容 17 this.array=Arrays.copyOf(this.array,this.array.length*2); 18 } 19 this.array[this.size++]=data; 20 } 21 public Object back() { 22 if(this.size==0){ //当数组内空了,return null 23 return null; //因为引用类型的默认初始值为null 24 } 25 return this.array[this.size-1]; //因为只返回,并不从栈中取出,所以进行size-1,而不是size-- 26 } 27 public static void main(String[] args) { 28 ObjectTest o=new ObjectTest(); //Object 类的构造函数会自动给O对象数组进行初始化 29 o.add(10); //因为Object类是所有类的基类,所以任何类型都可存到Object类的数组中 30 o.add(20); 31 o.add("30"); 32 String data1=(String) o.back(); 33 System.out.println(data1); 34 } 35 }
运行结果:
这是在使用者可以确保完全输入正确的情况下的,但是如果这次该用户想用Object类型的数组来
实现整型数组,但因为手误,而错输进一个字符串,这时编译是检测不到的。
只有在运行之后才会报错。(所以用Object类型来实现泛型编程存在好多弊端)
弊端: 1.用户可以传入任何类型,因此无法检测传入的数据是否符合用户存储数据的正确类型
2.输出时,需要手动进行类型的强制转换。
3.效率低。(没有使用泛型时,系统先进行向上转型,在输出时,又进行向下转型:String->Object->String)
所以在java5版本后,为了解决上述的两个问题,就出现了真正的泛型编程。
泛型编程的本质意义: 1.检测传入的数据是否符合用户存储数据的正确类型,提高安全性。
2.自动转换类型,无需用户手动进行类型的强制转换
1 public class GenericTest<T> { 2 private T[] array; //定义一个T类型的泛型数组 3 private int size; //存储数据元素个数 4 5 public GenericTest(int size){ 6 this.array=(T[]) new Object[size]; //将接收的Object类型强置转换为T类型 7 this.size=0; 8 } 9 public GenericTest(){ 10 this(10); 11 } 12 public void add(T data){ //添加一个T类型的数组 13 if(this.size==this.array.length){ 14 this.array=Arrays.copyOf(this.array,this.array.length*2); 15 } 16 this.array[this.size++]=data; 17 } 18 public T back(){ //返回一个T类型的数 19 if(this.size==0){ //当数组为空时,返回null。 20 return null; //因为null为引用变量的初始值 21 } 22 return array[this.size-1]; 23 } 24 25 public static void main(String[] args) { 26 GenericTest<String> text=new GenericTest(12); // 27 text.add("20"); 28 text.add("30"); 29 String str=text.back(); 30 System.out.println(str); 31 } 32 }
就是在开头类名后加一个尖括号<>,在尖括号内放入类型参数,(普通类类名+< 类型参数>),需要注意的是
该类型参数必须全为大写,如T,E,R等,然后在定义数组时,也将数组定义成T类型的,在构造函数中对数组初始化
时,将新new的Object类型也强置转换为T类型(因为一般new 对象时,要放具体的类型,而T只是类型参数,)。
然后在后面写成员方法时,将传入的值,或返回的值也要定义成T类型的。最后就是在main方法中新new对象时,
将你要定义的真正的类型写上,如要定义字符串数组,GenericTest<String> text=new GenericTest(12);
注意:尖括号<>里面放的都为引用类型,因为泛型编程要进行类型擦除到上界的动作(检查,转换),而简单类型没有基类。
这样,当你输入的数据类型有错时,就会直接报错。也省略了手动对类型转换的过程。
需要注意,当确定一个泛型类型后,仍可以继续使用原始类型。但该类型还是会有之前的弊端(2个)。
此时相当于给T赋的是Object。
之所以还可以使用原始类型,是因为泛型在java5之后出现,若要强制必须给T赋类型的话,那么在5之前写的
那些代码(没有T类型)都会报错不能使用。
java泛型的类型擦除机制???
在编译阶段,Java编译器通过泛型类型进行类型检查和自动类型转换,处理完以后,就会把T类型一直往上
擦除(进行类型检查),直到上界(默认为Object类),所以在运行阶段都是以Object类型来运行。
所以在Java中认为 GenericTest <Interage> 这两个是同一类型的。(C语言中认为是两种)
GenericTest <String>
注意:< >尖括号不是类型的一部分,它只是为了对泛型类型进行类型检查,只在编译阶段存在,运行阶段就没有了。
泛型类型可用来定义三种:
1.泛型类
2.泛型方法 (在函数返回值前面加泛型类型列表)(可静态,可实例)
3.泛型接口
泛型类型要注意的问题:
1,用泛型定义数组时,不可以A[] arr =new A[10];,应该A[] arr=(A[])new Object[];,因为在之前定义数组时有规定,定义的数组的类型必须明确。
2,不能作为静态方法的传入参数类型,因为静态方法在类加载时加载,而泛型的类型只有在加载对象时才能确定。
♥当在用泛型类型定义的类中,使用compareTo()方法时,要将泛型类型的上界定义为Comparable,即为泛型类型继承Comparable。
(因为若不给定义上界则直接擦到Object类,而Object类中只有toString(),equals()等方法,没有compareTo()方法。)
当继承了Comparable后,这时候T类型进行擦除机制时,只会擦到Comparable,而Comparable中正好有要用的CompareTo()方法。
♥当用泛型来定义静态方法时,运行时要用类调用,此时可在方法前加< >,来进行类型检查。如下,
也可省略,因为jdk会根据你传进来的实参来推导。
另外若要在该方法中使用compareTo()方法时,因为java有擦除机制,所以也要给类型参数定义上界为Comparable。