一:泛型
1.泛型的概念 :泛型是指多种数据类型,是将数据类型参数化。例如将整形、字符型、字符串类型(引用类型)、浮点型、自定义类型等作为参数来传输。
在了解泛型之前我么先看一个案例。实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值。
package Generic;
class MyArray{
public Object[] array = new Object[4];
public Object getArray(int index) {
return this.array[index];
}
public void setArray(int index,Object val){
this.array[index] = val;
}
}
public class TestDemo {
public static void main(String[] args) {
MyArray myarray = new MyArray();
myarray.setArray(0,1);
myarray.setArray(1,"你好");
// String ret = myarray.getArray(1);
String ret = (String)myarray.getArray(1);
}
}
这里Object是任何类的父类,用它创建的数组什么都可以放。但是在拿出来的时候如果不强转成它本来的类型,可能会报错。为了在使用数组里面的数据不强转,并且数组里面只能存放我们指定的数据类型。则我们在实例化类的时候就最好将类型给确定。而泛型就是为了解决这个问题被提出来的,他可以帮助对类型的参数化。
下面通过泛型来实现案列:
package Generic;
class MyArray<T>{
public T[] array = (T[]) new Object[4];
public T getArray(int index) {
return this.array[index];
}
public void setArray(int index,T val){
this.array[index] = val;
}
}
public class TestDemo {
public static void main(String[] args) {
MyArray<Integer> myarray = new MyArray<Integer>();
myarray.setArray(0,1);
// myarray.setArray(1,"你好");
myarray.setArray(1,13);
System.out.println(myarray.getArray(1));
MyArray<String> myarray1 = new MyArray<>();
myarray1.setArray(0,"你好");
myarray1.setArray(1,"世界");
System.out.println(myarray1.getArray(1));
}
}
注释处编译错误:所输入的数据类型不是Integer
2.泛型的作用:可以看到,泛型帮助解决了两个问题:
(1):在用户输入数据的时候,帮助进行类型检查
(2):获取结果的时候帮助进行类型转换
3.泛型的结构
(1)语法:
class 泛型类名称 <类型形参列表> {
}
class 泛型类名称 <类型形参列表> extends 继承类<类型形参列表> {
}
例如:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
}
(2)类名后的<T>代表站位符,表示当前类是一个泛型类
4.泛型的注意细节
1.泛型类不能创建泛型类型数组,即T[] e = new T[5]。因为在本列中T[] e = new T[5]相当于Object[] ts = new Object[5],又回到了最初的情形。返回的Object数组里面,可能存放的是任何的数据类型,运行的时候直接转给Integer类型的数组。编译器认为不安全。
2.泛型只能接受类,所有的基本数据类型必须使用包装类
3.当编译器能够推断出类型实参时,可以省略类型实参的填写
MyArray<Integer> list = new MyArray<>()
5.泛型的上界
通过一个实际案列来引出泛型的上界。要求实现一个泛型类,用来计算传入数据的最大值并返回。
package Generic;
class Com <T>{
public T Findmax(T[] array){
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(max<array[i])max = array[i];//这里会报错
}
return max;
}
}
public class TestDemo2 {
public static void main(String[] args) {
Com<Integer> com = new Com<>();
// Integer[] array = {1,4,2,3,10,11};
Integer[] array1 = new Integer[]{1,4,2,3,10,11};
System.out.println(com.Findmax(array1));
}
}
为什么会造成上面的原因就是应为java泛型中的擦除机制会将T替换为Object,而Object里面它仅有equals方法,但是该方法只能用来判断两个数据的地址是否相等,不能比较大小。要比较大小,只能让实现了Comparable接口的类来完成。而实现了Comparable接口的类很多,我们不可能每个都指定。所以这里就规定了泛型的上界,即只要实现了Comparable接口的类都行。
语法:class 泛型类名称<类型形参 extends 类型边界>
例如:public class MyArray<E extends Number>{
}
要求E是Number的子类型或者就是Number,如果没有指定类型边界E,可以视为E extends Object
修改以后的代码
package Generic;
class Com <T extends Comparable<T>>{
public T Findmax(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;
}
}
public class TestDemo2 {
public static void main(String[] args) {
Com<Integer> com = new Com<>();
Integer[] array = {1,4,2,3,10,11};
Integer[] array1 = new Integer[]{1,4,2,3,10,11};
System.out.println(com.Findmax(array1));
}
}
在这里如果我们使用的不是整形,而是我们自己定义的类型,要完成数据之间的比较,就必须让我们定义的类实现Comparable并重写comparTo方法
package Generic;
class Com <T extends Comparable<T>>{
public T Findmax(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 String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.age-o.age;
}
}
public class TestDemo2 {
public static void main(String[] args) {
Com<Student> alt = new Com<>();
Student[] stu = new Student[3];
stu[0] = new Student("张三",21);
stu[1] = new Student("李四",22);
stu[2] = new Student("王麻子",19);
System.out.println(alt.Findmax(stu));
}
}
6.泛型方法
语法:方法限定符<类型形参列表> 返回值类型 方法名称(形参列表){}
例如:public static <E> void swap (E[] array,int i,int j)//静态的泛型方法需要在static后面<>声明泛型类型参数
上面的案列可以修改为下面的样子
class Limei<T> {这里的<T>可写可不写,但是普通泛型类就要求写
public static<T extends Comparable<T> > T findMax(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;
}
}
public class TestDemo2 {
public static void main(String[] args) {
Integer[] array = {1,4,2,3,10,11};
Integer max = Limei.findMax(array);
System.out.println(max);
}
}
二.通配符
在了解通配符之前先看一个案例分析
package Generic;
class Message<T>{
private T message;
public T getMessage(){
return message;
}
public void setMessage(T message){
this.message = message;
}
}
public class TestDemo3 {
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setMessage("西华大学");
fun(message);
Message<Integer> message1 = new Message<>();
message1.setMessage(1);
fun(message1);//出现错误,fun函数只能接收String类型
}
public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}
}
再来看一个例子
import java.util.ArrayList;
import java.util.List;
/*
*让集合List能够接收Integer类型和Double类型数据
*/
public class TestDemo3 {
public static void main(String[] args) {
List<Number> l = new ArrayList<Integer>();//报错:需要的类型是Number,而提供的类型是Integer
l = new ArrayList<Double>();//报错:需要的类型是Number,而提供的类型是Double
}
import java.util.ArrayList;
import java.util.List;
public class TestDemo3 {
public static void main(String[] args) {
List<Integer> l = new ArrayList<Integer>();
l = new ArrayList<Double>();//报错:需要的类型是Integer,而提供的类型是Double
List<Double> l = new ArrayList<Integer>();//报错:需要的类型是Double,而提供的类型是Integer
l = new ArrayList<Double>();
}
}
可以看到虽然Integer、Double是Number的子类,按照我们的理解List<Integer>、List<Double>也应该是List<Number>的子类,这就是我们常说的协变。但是在泛型中就不支持这样的父子关系。 我们需要希望它能够接收所有的泛型类型,但是又不能让用户随意修改 。这是就要用到通配符“?”来处理。
public static void fun(Message<?> temp){
System.out.println(temp.getMessage());
}
import java.util.ArrayList;
import java.util.List;
public class TestDemo3 {
public static void main(String[] args) {
List<?> l = new ArrayList<Integer>();
l = new ArrayList<Double>();
}
}
1.通配符的概念:代表未知类型,代表不关心或无法确定实际操作的类型。
2.<? extends T> 称为上限通配符,代表T和T的子类,T为上界
public class TestDemo3 {
public static void main(String[] args) {
List<? extends Number> l = new ArrayList<Integer>();
l = new ArrayList<Double>();
l = new ArrayList<Object>();//报错:?只能接受Number和Number的子类,而Object为Number的父类
}
}
3.<? extends T> 称为下限通配符,代表T和T的父类,T为下限
public class TestDemo3 {
public static void main(String[] args) {
List<? super Number> l;
l = new ArrayList<Double>();//报错:?只能接收Number和Number的父类,而Double是Number的子类
l = new ArrayList<Object>();
}
}
注意
无限定通配符<?>只能通配泛型类型,但是无法让java确定读写的类型,通配后的变量也失去了进行类型安全检查的作用。
public class TestDemo3 {
public static void main(String[] args) {
List<?> l;
l = new ArrayList<String>();
l = new ArrayList<Integer>();
l.add("java");//报错:需要的类型是?,而提供的类型是String
l.add(123);//报错:需要的类型是?,而提供的类型是Integer
Object o = l.get(0);//这里java无法判断得到的是什么类型,因为Object是所有类型的父类,所以它只能给Object
}
}
4.协变
设o1是o2的父类,则类o1引用 = o2对象;如果泛型类<o1>引用 = 泛型对象<o2>,则称为协变。如果泛型类<o2>引用 = 泛型对象<o1>,则称为逆变。根据我们上面的认识,泛型是非协变的(不是逆变)。
虽然泛型是非协变的,但是利用通配符可以让泛型变成协变的或者逆变的,下面以有限通配符为例
public class TestDemo3 {
public static void main(String[] args) {
//泛型类父类引用,引用子类对象(协变)
List<? extends Number> l = new ArrayList<Integer>();
l = new ArrayList<Double>();
//泛型类子类引用,引用父类对象(逆变)
List<? super Integer> n = new ArrayList<Object>();
n = new ArrayList<Number>();
}
}
协变和逆变的读写效果,还是以有限通配符举例
public class TestDemo3 {
public static void main(String[] args) {
//协变
List<? extends Number> l = new ArrayList<Integer>();
l = new ArrayList<Double>();
Number number = l.get(0);//可以确定读取的是Number类型,所以读取可以
l.add(123);//因为java不确定写入的是哪个类型,所以写入不行
//逆变
List<? super Number> n = new ArrayList<Object>();
Object object = n.get(0);
n.add(123);//写入可以
n.add(12.3);//写入可以
Number number1 = new Integer(12);
n.add(number1);
n.add("你好");//报错
n.add(new Object());//报错
//允许写入Number和Number的子类对象,因为子类对象可以赋给父类的引用,所以确定写入的都是Number类型。高于Number的类不允许写入,因为读取的时候不能确定是那个类型,只能按照Object读取,所以无法读取类型
}
}
案列
class Cat{}
class Cat1 extends Cat{}
class Cat2 extends Cat1{}
class Cat3 extends Cat2{}
class Cat4 extends Cat3{}
class Cat5 extends Cat4{}
public class TestDemo4 {
public static void main(String[] args) {
List<? extends Cat3> l = new ArrayList<Cat3>();//允许左尖括号填写Cat3、Cat4、Cat5
// l.add(new Cat3());//不允许写入
Cat3 cat3 = l.get(0);//允许父类为Cat、Cat1、Cat2
List<? super Cat3> n = new ArrayList<Cat3>();//允许左尖括号填写Cat、Cat1、Cat2、Cat3
n.add(new Cat3());//允许写入Cat4、Cat5、Cat3
Object object = n.get(0);//父类只能是Object,在这里无意义
}
}
综上总结:
(1)通配符的上界,不能进行写入数据,只能读取数据。
(2)通配符的下届,不能读取数据,只能写入数据
(3)对于泛型<T>,可以使用<? extends T>进行读取,可以使用<? super T>进行写入
5.读写分离
用有限通配符实现进行读写分离操作
案列:对于集合ArrayList<T>,写一个静态方法,传入源和目标两个参数,使用有限通配符进行读写分离,并且拷贝集合对象
public class TestDemo5 {
public static void main(String[] args) {
List<String> l1 = new ArrayList<String>();
List<String> l2 = new ArrayList<String>();
l1.add("你好");
l1.add("西华大学");
copyList(l1,l2);
for (String s:
l2) {
System.out.println(s);
}
}
public static <T> void copyList(List<? extends T> src,List<? super T> dest){
Iterator<? extends T> it = src.iterator();
while(it.hasNext()){
T next = it.next();
dest.add(next);
}
}
}
我们知道集合是非协变的,但是使用有上限和有限下限的通配符可以实现协变和逆变,实现读写分离的操作。比如Collections.copy
上面就是泛型的全部类容。