泛型
问题:下面是一个简单的顺序表,我们在这里面实现的一个顺序表,是存放的数据类型只有int类型,这就会很不通用,如果我们想什么样的类型的数据都想要放进去,就要把这个数组的类型设置成Object类型
能不能啥样的类型都可以存放呢?
class MyArraylist{
private int[] arr1;
private int usedsize;
public MyArraylist()
{
this.arr1=new int[10];
}
public void add(int val)
{
this.arr1[usedSize]=val;
usedSize++;
}
public int get(int pos)
{
return this.arr1[pos];
}
}
改成下面的代码之后,还是发现有问题:
1)这个代码太通用了,什么样类型的数据都可以进行存放不能指定元素,下面的代码里面的元素既可以存放整形,又可以存放String,完全就是一个大杂烩,能不能只让他存放整型或者是字符串类型呢?
2)取出我们的Object顺序表中的元素,因为咱们返回的是Object类型,每一次取出数据的时候,还需要进行强制类型转换,这是很麻烦的;
package com.example.demo; public class MyList { public Object[] array; public int usedSize; public MyList(){ this.array=new Object[10]; this.usedSize=0; } public void add(Object data){ this.array[usedSize]=data; usedSize++; } public Object get(int index){ return array[index]; } public static void main(String[] args) { MyList list=new MyList(); //存放的时候,什么样子的数据类型都可以进行存放,就是一个大杂烩 list.add(1); list.add("abcde"); //虽然我知道1号位置的下标是一个String类型,但是取出元素的时候,还要进行强制类型转换 String str= (String) list.get(1); System.out.println(str); } }
泛型和数组检查类型和存储的时间是不一样的,数组进行强制类型转换本质上并没有针对里面的所有元素进行强制类型转换
关于数组进行向下转型的问题:
为什么我们将自定义类型的数组强制转化成一个具体的类型就会发生报错呢?
1)实际上在JAVA里面,Object是所有class的根,当然数组也是一种class类型,因此比如说String[]和Object[]都是Object的子类类型
2)但是对于Object[]和String[]类型都是数组类型,他们之间并没有什么父子类关系,而是属于平级的关系,我们可能认为String[]类型是Object[]的子类类型,所以才会这么写,也就出现了classCastException异常,再说我感觉Object[]类型的数组转化成String[]类型的数组,编译器认为不安全,因为Object类型的数组不一定类型都是String类型的呀
我们现在想要做到下面几件事:
1)能不能指定我这个顺序表的类型,只能存放一种数据类型?传递啥类型就指定存什么类型,指定顺序表的类型
2)指定类型之后,是不是就只能存放指定类型的数据呢?
3)取出数据之后,可不可以不进行数据类型转换?或者是程序帮助我们自己来实现类型转换
static class MyArraylist{
private Object[] arr1;
private int usedsize;
public MyArraylist()
{
this.arr1=new Object[10];
}
public void add(Object val)
{
this.arr1[usedsize]=val;
usedsize++;
}
public Object get(int pos)
{
return this.arr1[pos];
}
}
public static void main(String[] args) {
MyArraylist list=new MyArraylist();
list.add(1);
list.add("hello");
String ret=(String)list.get(1);
}
1)在指定类的后面写上<T>,它是代表当前的类是一个泛型类,此时的这个T就只是一个占位符而已,把类型参数化了,直接就是一个T类型的数组了,最终的结果是我们进行传递什么类型的数据,最终就存放什么类型的数据
2)private T[]=new T[10];--------private T[]=(T[])new Object[];
3)不能实例化一个泛型数组,比如说T[] array=new T[];
new一个泛型数组最好的方式就是通过反射
4)数组和泛型之间的一个重要区别就是说他们如何进行强制执行类型检查,具体来说,数组在运行的时候存储和检查类型信息,然而泛型是在编译的时候检查类型错误,并且在运行的时候没有类型信息,检查类型和存储的时机是不一样的
package com.example.demo; public class TestDemo { public <T> T[] getArray(int size){ T[] array=new T[size]; return array; } //如果上面的写法是正确的,在编译的时候就会变成这种类型,代码就会变成这样,于是在main方法就变成了这样 //就会出现下面的这种写法,因为new T[]或者是Object类型的数组,就会出现类似于下面这种Object类型的数组转化成某个具体类型的数组这样的错误 public static Object[] getArray(int size){ Object[] array=new Object[size]; return array; } public static void main(String[] args) { String[] array=(String[]) getArray(10); 1)这里面的强制类型转换是错误的,因为只对数组整体进行了强制类型转换,部分元素并没有进行强制类型转换,就会抛出classCastException异常,如果说我们本身返回的数组就是一个String类型的数组,那么是不是就不会出现上面所说的情况呢?所以说我们具体在底层创建一个数组不是创建Object或者是T类型的数组,而是创建一个具体类型的数组,比如说尖括号里面是Integer,我们就创建一个Integer类型的数组,传递一个String,就创建一个String类型的数组 2)所以说这种写法是不正确的new T[]类型的数组,new Object //类型的数组都是不正确的 } }
class<E> classzz要传入一个具体的类型,创建一个具体的对应的类型的数组
泛型的意义:
1)存放数据的时候,在编译的时候会自动地对要放进去的类型进行检查是否和你指定的类型是否匹配,当取数据的时候,编译器会自动地对取出来的数据进行强制类型转换
2)泛型中<>里面的内容是不进行参与类型的组成的,泛型中尖括号的内容是不会参与类型的组成的,运行时期想要获取这些类型是根本获取不到的
3)泛型类型的参数,只能是包装类,而不能是简单类型
咱们内部的ArrayList使用泛型的时候也是new Object数组,但是他没有通过反射的方式来进行创建一个具体带有特定类型的数组,但是他的get方法,把他的单个元素强转成(E)(array[index]),不是整体数组强转,而是单个类型强转成E类型,也就是转换成了我们指定的类型;
泛型是怎么进行编译的?
1)我们先进行反编译一下:javap -c 类名,来进行查看一下对应的字节码文件,没有T类型,全部是Object类型
2)泛型是编译时期的一种机制
泛型这个概念只在编译时期起作用,在运行时期是没有泛型这个概念的,在编译时会把我们指定的类型被擦除为Object类型,编译器生成的字节码文件并不包含泛型的类型信息
package com.example.demo;
public class MyList<T>{
public T[] array;
public int usedSize;
public MyList(){
this.array=(T[])new Object[10];
this.usedSize=0;
}
public void add(T data){
this.array[usedSize]=data;
usedSize++;
}
public T get(int index){
return array[index];
}
public static void main(String[] args) {
MyList<Integer> list=new MyList<>();
//存放的时候,会自动堆存放进去的数据进行类型检查
//只能存放整形数据 list.add("abc");
list.add(1);
list.add(2);
//取出数据的时候,会自动根据我们类型进行强制类型转换
int a=list.get(0);
System.out.println(a);
}
}
一:public class MyArray<E extends Number>{ } 这表示E可以是Number或者是Number的子类 1)MyArray<Integer> l1;//这是正常的,因为Integer是Number的子类 2)MyArray<String> l2;//这是错误的,因为String并不是Integer的子类 二:没有指定边界,默认就是Object,比如说class MyArray<T>{}
下面我们来写一个方法,来求数组中元素的最大值
class ALG<T>{ public T GetMax(T[] array){ T max=array[0]; for(int i=1;i<array.length;i++){ //这是引用类型的比较,max是T类型,array[i]也是引用类型,引用类型是不可以通过>=<来进行比较的 if(array[i]>max) { max=array[i]; } } return max; } }
既然是引用类型,我们就要重写Compareable接口,重写compareTo方法来比较两个引用类型,但是我们发现上述这个代码,compareTo方法点不出来,因为程序不知道你这个类是否继承了Compareable接口?重写了CompareTo方法
class ALG<T extends Comparable<T>> 这种写法就表示此时这个T一定要实现Compareable接口,这是泛型的上界
package Demo; class ALG<T extends Comparable<T>>{ public T GetMax(T[] array){ T max=array[0]; for(int i=1;i<array.length;i++){ //这是引用类型的比较,max是T类型,array[i]也是引用类型,引用类型是不可以通过>=<来进行比较的 if(array[i].compareTo(max)>0) { max=array[i]; } } return max; } } public class TestData { public static void main(String[] args) { ALG<String> avg=new ALG<>(); String[] strings={"abcd","abc","ab","a"}; int[] array={1,3,2,45,67,87}; String str= avg.GetMax(strings); System.out.println(str); } }
1)这时候会出现一个问题,咱们每一次调用findmax都要newALG()这样的对象吗,才可以通过这个对象的实例来进行调用findmax这样的方法,就显得太麻烦了
2)这个时候我们加上static关键字,不就可以保证通过类名来进行调用了吗?如果加上了static关键字之后,这个静态方法是不依赖于对象的,咱们的这个T参数是在new ALG中的尖括号进行传参的
class ALG<T extends Comparable<T>>{ public static<T extends Comparable<T>> T GetMax(T[] array){ T max=array[0]; for(int i=1;i<array.length;i++){ //这是引用类型的比较,max是T类型,array[i]也是引用类型,引用类型是不可以通过>=<来进行比较的 if(array[i].compareTo(max)>0) { max=array[i]; } } return max; } } 这个时候就不需要进行new对象了,这就是咱们的一个静态的泛型方法
package Demo; class ALG{ public static<T extends Comparable<T>> T GetMax(T[] array){ T max=array[0]; for(int i=1;i<array.length;i++){ //这是引用类型的比较,max是T类型,array[i]也是引用类型,引用类型是不可以通过>=<来进行比较的 if(array[i].compareTo(max)>0) { max=array[i]; } } return max; } } public class TestData { public static void main(String[] args) { Integer[] array={12,34,34,45,67}; int max=ALG.<Integer>GetMax(array); } }
理论上来说ArrayList<Integer> 不是ArrayList<Number>的父亲类型
ArrayList<Number>也不是ArrayList<Integer>的子类
通配符:
通配符是无法解决泛型无法谐变的问题的,谐变指的是Student如果是Person的子类,那么List<Student>也应该是List<Person>的子类,但是泛型是不支持这样的父子类关系的
1)泛型T是确定的类型,一旦你要是传了,我就定下来了,但是通配符可以说是更为灵活或者不稳定,更多地用于扩充参数的范围
2)或者我们可以这么理解:泛型T就像是一个变量,等待着你可以传输一个具体的类型,而通配符是一种规定,规定你可以传哪些参数
class AVL{ public static<T> void print1(ArrayList<T> list){ for(T x:list){//编译器一定知道当前传递过来的是一个T类型的数据 System.out.println(x); } } public static void print2(ArrayList<?> list){ for(Object x:list)//编译器不知道?是啥类型,具体的类型我不知道 { System.out.println(x); } } }
通配符的上界:
<? extends 上界> <? extends Number>//可传入的参数类型是Number或者是Number的子类 举例: public static void printAll(ArrayList<? extends Number > list>{} //上面表示可以传入的类型是Number的子类的任意类型的ArrayList 下面都是正确的: printAll(new ArrayList<Integer>()); printAll(new ArrayList<Double>()); printAll(new ArrayList<Number>()); 下面搜是错误的 printAll(new ArrayList<String>()); printAll(new ArrayList<Object>());
假设现在有下面的关系:
Animal
Cat extends Animal
Dog extends Animal
根据上面的关系,写一个代码,打印一个存储了Animal或者Animal子类的list
代码1:
public static void print(ArrayList<Animal> list>{}
这样是不可以进行解决问题的,因为print的参数是List<Animal>,我们就不可以进行接收List<Cat> list
代码2:
public static<T extends Animal> void print3(List<T> list){ for(T animal:list){ System.out.println(animal); } }
这时候T类型是Animal的子类或者是自己,该方法也是可以实现的,这里面的类型是一个确定的类型,编译器知道是什么类型
代码三:通配符来进行实现:
public static void print(List<? extends Animal> list> { for(Animal x:list){ System.out.println(x);//编译器此时不知道这是调用谁的ToString方法 发生了向上转型,不知道这个类型具体是啥类型,反正指定了上界,编译器就认为你传递过来的类一定是Animal或者是Animal的子类,如果没有这个通配符上界Animal这里面就应该写成Object了 } }
1)总结:ArrayList<? extends Number>是ArrayList<Integer>或者ArrayList<Double>的父类类型
2)ArrayList<?>是ArrayList<? extends Number>的父亲类型
通配符的上介是不适合用于写入对象的:
ArrayList<Integer> list1=new ArrayList<>(); list1.add(1); list1.add(2); ArrayList<Double> list2=new ArrayList<>(); List<?extends Number> list=list1; //list.add(1,9);//这里面是不适合进行写入数据的,适合于读数据,如果你进行存放的话,即可以进行存放整数,也可以存放浮点数这是不可以的,因为最最终引用的只有一种类型 //list.add(2,10.9); Number number= list.get(1);//正确 //Integer s1=list.get(0);这样的写法是错误的,因为不知道里面具体存放的是哪一种类型,万一存放的是Double类型呢
我们的通配符的上介适合读取数据,不适合写入数据,上面的list可以进行引用的对象有很多,编译器是无法确定你的具体的类型的,所以说编译器为了安全起见,此时只允许你进行读取
通配符的下界:
<? super 下界> <? super Integer>这是代表可以进行传入的实参的类型是Integer或者是Integer的父类类型
假设有下面代码:
public static void printAll(ArrayList<? super Integer> list){ } //下面表示传入的实参都是Integer的父类的任意类型的ArrayList 下面的调用都是正确的: printAll(new ArrayList<Integer>()); printAll(new ArrayList<Number>()); printAll(new ArrayList<Object>()); 下面的调用是编译错误的: printAll(new ArrayList<String>); printAll(new ArrayList<Double>);
咱们的ArrayList<? super Integer> 是ArrayList<Integer>的父类类型
ArrayList<?>是ArrayList<? super Integer>的父类类型
ArrayList<? super Person> list=new ArrayList<>(); //ArrayList<? super Person> list2=new ArrayList<Student>();这里面会出现报错,因为list2只能引用Person或者Person父类类型的list list.add(new Person());//添加元素的时候,只要添加的元素是Person或者是Person的子类就可以了 list.add(new Student()); Person person=list.get(0)//父类引用引用子类对象,应该是对的呀???? Student s=list.get(0)//错误,因为Person的子类有很多,不一定就是Student Object s=list.get(1);//正确
我们在进行添加元素的时候,我们知道list引用的对象肯定是Person或者是Person的父类的集合,此时我们可以确定此时能够储存的最小粒度比Person小就可以,你放的时候,放的都是Person或者Person的子类,但是你读取的时候,你能确定你读取的时候读取的是那一个子类吗?
回顾泛型(看懂代码)
什么是泛型?
我们说,一般的类型,只能引入或者使用具体的类型,要么是基本数据类型,要么是自己所自定义的类,如果我们想要编写可以应用于多种类型的代码,这种刻板的限制就会对代码的约束就会很大;泛型,就是适用很多很多种类型,就是对泛型作为了参数进行传递;
例如说,我们自定义一个数组,想要存放各种类型,把数组类型变成Object类型的数组即可,但是取元素的时候,要进行去进行类型转换;况且我们还可以什么类型都可以放,我们想要是实现只放字符串,只放整数,我想达到通用的效果;
全部变成Object类型的数组之后,虽然在这种情况下,当前数组的任何类型都是可以进行存放的,但是更多情况下,我们还是希望他只是可以同时持有一种数据类型,而不是同时持有这么多类型
所以说泛型的主要目的就是指定当前的容器,数据类型参数化,要放什么类型的对象,让编译器自己去进行类型检查
<T>表示是一个泛型类,T表示是一个占位符,我们是不可以实例化泛型数组的;
T[] arr1=T[](new Object[10]);
1)我们再进行编译的时候,会自动进行类型的检查;
2)在进行取元素的时候,编译器会自动进行进行强制类型的转换;
3)简单类型和基本数据类型是不可以作为泛型类型的参数的,必需使用包装类;
4)擦除机制:java的泛型机制(只存在编译的时候)是在编译级别实现的,编译器生成的字节码在运行期间并不包含泛型的基本类型信息,
5)在泛型中使用返回值是数组是,不能是T[],;接收的时候建议要用Object类型的数组去接收;
1)public class MyArray<E extends Number>那么此时E可以是Number(数字类)后者是Number的子类,此时类型只能是Integer,但是绝对不可以是String,他不是Number的子类;
2)public class MyArray<E extends Comparable<T>>表明我们传入的类型实现了这个接口;
写一个泛型类,求出数组的最大值
class Test<T extends Comparable<T>>
{
public T Findmax(T[] array)
{ T max=array[0];
for(int i=0;i<array.length;i++)
{
if(array[i].compareTo(max)>0)//不可以写成array[i]>max,所以实现Comparetor接口
//自定义类型比较,要实现Compareable接口
{
max=array[i];
}
}
return max;
}
public T[] DoubleArray(T[] array)
{
return array;
}
}
class TestEnum {
public static void main(String[] args) {
Test<Integer> test=new Test<>();
Integer[] array={12,23,13,24,89,80};
int max= test.Findmax(array);
System.out.println(max);
Integer[] array1=test.DoubleArray(array);
System.out.println(Arrays.toString(array1));
Test<String> text=new Test<>();
String[] arr1={"hello","world","lijiawei","lijiaxin"};
System.out.println(text.Findmax(arr1));
}
}
1)那么此时上面的代码也有不好的地方,每次我们想要求出我们进行自定义数组的最大值的是偶,必须先要new一个对象,创建一个实例,再去调用这里面的方法;
2)在方法名前面加上一个static关键字变成静态方法,未来在进行调用这个方法的时候,是不依赖于对象的,所以说他不知道T是啥
public static <T extends Comparable<T>> T Findmax(T[] array),变成这样之后,就不用new对象了;
直接通过类名.方法名即可;
3)我们未来再次进行调用时,直接写下面的代码
Integer[] array={12,1,3,5,8,9}; Integer max=Test.<Integer>Findmax(array); System.out.println(max);//是可以自动进行类型推导的
2.通配符的介绍(?在泛型中的使用,即为通配符)
****通配符是用来解决泛型无法进行协变的问题的,协变指的是如果Student如果是Person的子类,那么List<Student>也应该是List<Person>的子类,但是泛型是不支持这样的父子类关系的;
class Test{
public static <T> void print1(ArrayList<T> list)
{
for(T t:list)
{
System.out.println(t);
}
}
public static void print2(ArrayList<?> list)//通配符类型
{
for(Object x:list)
{
System.out.println(x);
}
}
}
class TestEnum {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
Test.print1(list);
Test.print2(list);
}
}
******它只是使用于读取数据,而不是适用于写数据,因为此时List引用的子类对象有很多,编译器是无法知道一个具体的类型的,编译器为了安全起见,只允许进行读取;
但是当我们进行读取的时候,唯一可以确定的是他一定是number的子类
ArrayList<Integer> list1=new ArrayList<>(); ArrayList<Integer> list2=new ArrayList<>(); List<?extends Number> list=list1;//这个list是list1和list2的父亲 // list.add(32);这个代码是错误的 //编译器此时就不知道了,它既可以引用list1,也可以引用List2,此时放的数据不知道是啥类型,所以就报错了 Number i=list.get(0); // Integer j=list.get(1);此时会发生报错,因为此时编译器不知道list在里面引用的是什么类型,所以我们统一用Number来进行接收
支持写,不支持读,读必须用Object接收
class A{ } class Person{ } class Student extends Person{ } class C extends Student{ } class TestEnum{ public static void main(String[] args) { ArrayList<? super Person> list=new ArrayList<>(); list.add(new Person()); list.add(new Student()); list.add(new C()); //list.add(new A());此时会发生报错,里面只可以存放Person以及Person的子类 Object person1=list.get(0);Student student=list.get(0)//产生报错
}}