1. 什么是泛型?
泛型就是适用于许多许多类型。从代码上讲就是对类型实现了参数化。(听完这句话,是不是很懵,不着急,我们接着往下看)
2. 引出泛型
下面,我们来思考一个问题:
如何实现一个类,类中包含数组成员,使得数组中能够存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
这个问题的突破口在哪?思索一番,想起一句话:所有类的父类,默认为Object类。那我们不妨用Object来接收各种各种的数据类型:
首先,我们来实现一下这个数组:
class MyArray{
public Object []array=new Object[10];
}
接下来,我们考虑一下如何根据成员方法返回数组中某个下标的值?
class MyArray{
public Object []array=new Object[10];
public Object getValue(int pos){
return this.array[pos];
}
public void setValue(int pos,Object val){
this.array[pos]=val;
}
}
public class Test1 {
public static void main(String[] args) {
MyArray myArray=new MyArray();
myArray.setValue(0,"bit");
myArray.setValue(1,12);
myArray.setValue(2,0.987);
String a=(String)myArray.getValue(0);
int b=(int)myArray.getValue(1);
double c=(double)myArray.getValue(2);
}
}
上面的代码确实已经实现了我们想要的效果,但是在主函数中接收数组中的元素时,我们好像无法避免的去强制类型转换 myArray.getValue(pos) 的返回值,这是一种非常不可取的方式,因为当你不知道数组中某个下标的元素的元素类型时,就无法拿到想要的结果。
因此,在实际运用当中,我们还是更多的希望该数组持有一种数据类型,而不是同时持有多种数据类型。所以,泛型由此而生。
泛型的主要作用目的:就是指定当前的容器,要持有什么类型的对象。让编译器去检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。
3. 泛型的语法
先来看一个代码分析:
上面的代码: MyArray< String> myArray=new MyArray<>() 传String类型给MyArray类,MyArray类通过< T>接收,这就是泛型运用的基本语法。
几个注意事项:
- < >里面必须是引用类型。所以所有的基础数据类型必须使用包装类。
- 泛型帮我们在编译期间做了2件事情:存放元素的时候,进行类型的检查;取元素的时候,帮我进行类型的转换
4. 泛型小结:
- 泛型是将数据类型参数化,进行传递
- 使用 表示当前类是一个泛型类。
- 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
5. 泛型是如何编译的?
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制
由于知识储备有限,先不做分享。
6. 泛型的上界
示例1:
public class MyArray<E extends Number> {
...
}
只接收Number及Number的子类作为E的类型实参
示例2:
class Alg<T extends Comparable<T>>{
public T finMax(T[] array){
T max =array[0];
for(int i=1;i<array.length;i++){
if(max.compareTo(array[i])<0){
max=array[i];
}
}
return max;
}
}
class Student implements Comparable<Student>{
public int age;
public String name;
@Override
public int compareTo(Student o) {
return this.age-o.age;
}
}
public class test {
public static void main(String[] args) {
Alg<Student> alg=new Alg<>();
Student []array= new Student[10];
Student max=alg.finMax(array);
System.out.println(max);
}
}
注意:泛型是没有下界的。
7. 泛型方法
class Util {
public static <E> void swap(E[] array, int i, int j) {
E t = array[i];
array[i] = array[j];
array[j] = t;
}
}
在调用静态的泛型方法时,是不需要进行实例化的。
8. 通配符
站在fun函数的角度,他并不知道传入Message的具体类型的,所以他不能写入数据。
通配符的上界
<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
通配符的上界,不能进行写入数据,只能进行读取数据
通配符的下界
<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
通配符的下界,不能进行读取数据,只能写入数据。
9. 包装类
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。
9.1 基本数据类型和对应的包装类
9.2 装箱和拆箱
装箱:
public static void main(String[] args) {
int a=10;
Integer b=a;
Integer d=Integer.valueOf(a);
System.out.println();
}
拆箱:
public static void main(String[] args) {
Integer a=10;
int b=a;
double d=a;
double d2=a.doubleValue();
}
9.3 一道例题
为什么会发生这种情况?
在自动装箱过程中,会默认调用valueOf( ) 函数,该函数的原码分析如下: 所以,在比较包装类的大小时,不要用==,而采用equals方法。示例如下:
|