认识泛型和通配符

一:泛型

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

上面就是泛型的全部类容。

猜你喜欢

转载自blog.csdn.net/yahid/article/details/125346830
今日推荐