JavaSE| 泛型

Posted kris12

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE| 泛型相关的知识,希望对你有一定的参考价值。

 

泛型

JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了“类型形参”,这个类型形参将在声明变量、创建对象时确定,即传入实际的类型,我么称为“类型实参”。我们把这个“参数化的类型”称为泛型(Generic)。

我们可以为任何类和接口增加泛型声明,并不是只有集合类才可以使用泛型声明。

ArrayList<E>的<E>就是类型形参,ArrayList<String>的<String>就是类型实参,String类型就是用来确定E的类型用的。

为了区别,我们可以将int max(int a, int b)中a,b称为数据形参,将 int max = max(3,6);中3,6称为数据实参

泛型形参的命名一般使用单个的大写字母,如果有多个类型形参,那么使用逗号分隔,例如:Map<K,V>。

常见字母(见名知意):

例如定义学生类的成绩score不止一个类型:

...    
    public static void main(String[] args) {
        
        Student<Integer> stu = new Student(1, "kris", 99);
        Student<String> stu1 = new Student(2, "smile", "优秀");
        Student<Double> stu2 = new Student(5, "aa", 89.9);
    }
}


class Student<T>{
    
    private int id;
    private String name;
    private T score;
.....
}

 

可以在定义接口、类时指定类型形参,类型形参在整个接口或类体中可以当成类型使用,几乎所有可使用其他普通类型的地方都可以使用这种类型形参,例如:属性类型、方法的形参类型、方法返回值类型等。

但是泛型类或泛型接口上的泛型形参,不能用于声明静态变量,也不能用在静态方法中,那是因为静态成员的初始化是随着类的初始化而初始化的,此时泛型实参无法指定,那么泛型形参的类型就不确定。

当创建带泛型声明的类时,为该类定义构造器时,构造器名还是原来的类名,不需要增加泛型声明。例如:Student<T>类定义的构造器依然是Student(),而不是Student<T>,但调用构造器时却可以使用Student<T>的形式,而且应该为T类型形参传入实际的类型实参。

泛型实参的要求

首先泛型实参必须是引用数据类型,不能是基本数据类型,可以指定为包装类型,因为集合中只能存储对象。

什么时候指定泛型实参?

(1)在用泛型类、接口声明变量时

(2)在继承泛型类或实现泛型接口时,如果子类不延续使用该泛型,那么必须明确指定实际类型。此时子类不再是泛型类了

(3)在创建泛型类对象时

public class EmployeeManager {
    
    private ArrayList<Employee> lis;
    public static void test(ArrayList<String> lis){
            
        ArrayList<String> list = new ArrayList<String>();
        ArrayList<String> li = new ArrayList<>();//JDK1.7之后支持如下简化写法:
    }
}
        //继承泛型类
class MyArrayList extends ArrayList<String>{//此处ArrayList<String>就不能写成ArrayList<E>
        //因为MyArrayList不再是泛型类了,因此E必须给出具体的类型
}
                        //实现泛型接口
class Employee implements Comparable<Employee>{

    @Override
    public int compareTo(Employee o) {
        // TODO Auto-generated method stub
        return 0;
    }
    
}

 

继承泛型类、实现泛型接口时,想要继续保留父类、父接口的泛型,必须在父类、父接口和子类、子接口中都要保留泛型。

设定泛型形参的上限

如果泛型形参没有设定上限,那么泛型实参可以是任意引用数据类型。如果泛型形参设定了上限(例如:T  extends  父类上限),那么只能指定为该父类本身或其各子类类型。

在一种更极端的情况下,程序需要为形参设定多个上限(至多有一个父类上限,可以有多个接口上限)表明该类型形参必须是其父类的子类(包括是父类本身也行),并且实现多个上限接口。

与类同时继承父类、实现接口类似的是:为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。

class Student<T extends Number & java.io.Serializable> {
    //...省略其他代码
}

 

....
      Student<Integer> stu = new Student(1, "kris", 99);
        //Student<String> stu1 = new Student(2, "smile", "优秀");//以下代码编译报错,因为String不是Number的子类
        Student<Double> stu2 = new Student(5, "aa", 89.9);
        
        ArrayList<Integer> list = new ArrayList<>();
        

class Student<T extends Number>{
    
    private int id;
    private String name;
    private T score;
    public Student(int id, String name, T score) {
        super();
        this.id = id;
        this.name = name;
        this.score = score;
....    
}

 

定义泛型方法

在定义类、接口时可以使用类型形参,在该类的方法和属性定义、接口的方法定义中,这些类型形参可被当成普通类型来用。但是,在另外一些情况下,(1)如果我们定义类、接口时没有使用类型形参,但是某个方法定义时,想要自己定义类型形参;(2)另外我们之前说类和接口上的类型形参是不能用于静态方法中,那么当某个静态方法想要定义类型形参。那么,JDK1.5之后,还提供了泛型方法的支持。

* 二、泛型方法
 * 1、什么时候使用泛型方法
 * (1)因为刚才说,泛型类或泛型接口上的泛型形参是不能用于静态成员的,
 * 那么当静态方法需要用到泛型时,只能用泛型方法。
 * 
 * (2)如果泛型类或泛型接口上的泛型形参,但是对于某个方法来说,不适用,或这个类或接口本身不是泛型类和泛型接口,
 * 这个方法想要单独声明泛型,那么也得用方法方法,这个方法可以是非静态的
 * 
 * 2、如何声明
 * 【修饰符】 <泛型形参列表> 返回值类型  方法名(方法的形参列表) {
 * }
 * 泛型方法中声明的泛型形参,只能用在当前方法中。
 * 
 * 需要实现这样的一个方法,该方法负责将一个数组的所有元素添加到一个Collection集合中
 * 
 * 3、泛型方法的泛型形参是什么时候指定的
 * 调用时
 * 
 * 4、可以给泛型方法的泛型形参指定上限
 * 【修饰符】 <泛型形参   extends 父类上限> 返回值类型  方法名(方法的形参列表) {
 * }
 * 
 * 需求:接收一个集合(里面都是图形),打印所有图形的面积

 

 

ArrayList<String> list = new ArrayList<String>();

..
public <T extends Graphic> void print(ArrayList<T> list){
      for (T t : graphics) {

        System.out.println(t.getArea());

            }

      }

 

String[] array = {"Hello", "java"};
        Collection<String> c = new ArrayList<String>();
        //Object[] arr =  array;//这样子是多态引用是可以的
//Collection<Object> coll = c;//但右边ArrayList<String>,左边是不接收的;报错
        fromArrayToCollection(array, c);
        //编译报错
    

    public static void fromArrayToCollection(Object[]a, Collection<Object> c){
        for(Object object : a){
            c.add(object);
        }
    }

//可以这样子写;//该方法负责将一个数组的所有元素添加到一个Collection集合中
    public static <T> void copy(T[] arr, Collection<T> coll){
        for (int i = 0; i < arr.length; i++) {
            coll.add(arr[i]);
            
        }
    }

 

泛型擦除

* 1、泛型的擦除:
 * 当我们使用泛型类或泛型接口时,如果没有指定泛型的实参,那么它会出现泛型擦除的现象,
 * 如果泛型形参有上限,就按照第一个上限处理,如果没有上限,就按照Object处理。
 * 
 * 2泛型类指定为不同泛型实参时,运行时是同一种类型
 * 
 * 3、instanceof后面不支持泛型类;// 由于系统中并不会真正生成泛型类
 * 
 * 4、泛型类不能创建数组;  ArrayList<String>[] array = new ArrayList<String>[5]; //编译错误; this.arr = new T[length];    
 * 
 * 5、try..catch的catch中不能使用泛型
技术图片
public class TestErase {

    public static void main(String[] args) {
        ArrayList list = new ArrayList<String>();//ArrayList<String>被转换为了ArrayList
        list.add("kris");
        list.add("smile");
        
        for (Object object : list) {
            System.out.println(object);
        }
        
        Object object = list.get(1); //泛型被擦除,按照默认上限Object处理
        
        Student s = new Student("kk", 99); //Student<Number>
        
        Number score = s.getScore();//泛型被擦除,按照第一个上限Number处理
        
    }

}

class Student<T extends Number>{
    private String name;
    private T score;
    public Student(String name, T score) {
        super();
        this.name = name;
        this.score = score;
    }
    public T getScore() {
        return score;
    }


}
View Code

 

 public static void main(String[] args) {
        
        ArrayList<String> list1 = new ArrayList<>(); 
        ArrayList<Integer> list2 = new ArrayList<>();
        System.out.println(list1.getClass());//class java.util.ArrayList
        System.out.println(list2.getClass());//class java.util.ArrayList
        System.out.println(list1.getClass() == list2.getClass());//true
        
        ArrayList<Object> list = new ArrayList<String>();   //错误的
    
        /*ArrayList<String>不是ArrayList<Object>的子类,
        因为他们的运行时类型都是ArrayList,因此不允许如下赋值操作。
        这点和数组不同,因为数组是要生成新的Class对象的,String[]仍然是Object[]的子类,因此允许如下赋值操作。
        Object[] arr = new String[5];*/

    }
    

 

通配符

* 通配符:?
 * 1、类型通配符只能用于方法中,不能用在泛型类或泛型接口的声明上。
 * 2、形式:
 * (1) <?> 代表的是任意类型
 * (2)<? extends 上限Upper> 代表的是Upper的类型或它的子类
 * (3)<? super 下限Lower> 代表的是Lower的类或它的父类

当我们声明一个方法时,某个形参的类型是一个泛型类或泛型接口类型,但是在声明方法时,又不确定该泛型实际类型,我们可以考虑使用类型通配符。

           
                //使用类型通配符
    public static void test(List<?> c){ //List c这种形式使用List接口时没有传入实际类型参数,这将引起泛型警告。
        for (int i = 0; i < c.size(); i++) {//List<Object> c这种太局限了,调用时,只能传List<Object>,List<Object>不能接收List<String>等其他集合
            System.out.println(c.get(i));
        }
    }
                //声明一个泛型方法,需要声明泛型形参T。
    public static <T> void test2(List<T> c){  
        for (int i = 0; i < c.size(); i++) {
            System.out.println(c.get(i));
        }
    }

 

通配符的List仅表示它可以接受指定了任意泛型实参的List,并不能把元素加入其中,例如如下代码将会引起编译错误:

public static void test(List<?> c, String str){
        c.add(str);
    }

 

不知道上面程序中c集合里元素的类型,所以不能向其中添加对象,除了null对象,因为它是所有引用数据类型的实例。

test2方法带泛型的List,表示该集合的元素类型是T,因此允许T系列的对象加入其中,例如如下代码是可行的:

public static <T> void test(List<T> c, T t){
        c.add(t);
    }

 

如果不涉及添加元素到带泛型的集合中,那么两种方式都可以,如果涉及到添加元素到带泛型的集合中,使用类型通配符<?>的不支持。

当直接使用List<?>这种形式时,即表明这个List集合接收泛型实参指定为任意类型的List。

 

有时候我们只希望接收某些类型的List。

例如:一个图形的抽象父类Graphic,两个子类Circle和Rectangle。接下来我们想定义一个方法,可以打印不同图形的面积。

但是,List<Graphic>的形参只能接收List<Graphic>的实参,如果想要接收List<Circle>,List<Rectangle>的集合,可以使用List<?>。

但是这样有两个问题,一个是List<?>可以接收任意类型,不仅仅图形,第二个是需要强制类型转换。为了解决这个问题,Java允许设定通配符的上限:<? extends Type>,这个通配符表示它必须是Type本身,或是Type的子类。

public static void printArea(List<? extends Graphic> graphic){
        for (Graphic g : graphic) {
            System.out.println(g.getArea());
        }
    }
    /*与前面的完全相同,因为我们不知道这个受限制的通配符的具体类型,所以我们不能把Graphic对象或其子类对象加入这个泛型集合中。
    如果要需要将Graphic对象或其子类对象加入这个泛型集合,那么就只能用泛型方法了,*/
    
    public static void printArea(List<? extends Graphic> graphics){
        graphics.add(new Circle());//编译错误,因为不知道?的具体类型,也可能是Rectangle
    }

 

返回的T是Object,也就是说,程序在复制集合元素的过程中,丢失了src集合元素的类型String。

对于上面的copy方法,可以这样理解两个集合参数之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素类型与src的元素类相同或是它的父类即可。为了表示这种约束关系,Java允许设定通配符的下限:<? super Type>,这个通配符表示它必须是Type本身或是Type的父类。

public static void main(String[] args) {
        ArrayList<String> src = new ArrayList<String>();
        src.add("kris");  //src -- >dest
        ArrayList<Object> dest = new ArrayList<Object>();
    
        String last = copy(dest, src);
        System.out.println(last);
        
    }
        
/*    实现将src集合里元素复制到dest集合中的功能,因为dest集合需要接受src的所有元素,
    所以dest集合元素的类型应该是src集合元素的父类。
    为了表示两个参数之间的类型依赖,考虑同时使用通配符、泛型形参类实现该方法。*/
/*    public static <T> void copy (Collection<T> dest, Collection <? extends T> src){
        for (T t : src) {
            dest.add(t);
        }
    }*/
    public static <T> T copy (Collection<? super T> dest, Collection <? extends T> src){
        T last = null;
        for (T t : src) {
            dest.add(t);
        }
        return last;
    }

 

以上是关于JavaSE| 泛型的主要内容,如果未能解决你的问题,请参考以下文章

javaSE 集合框架—— 泛型

JavaSE-泛型

JavaSE 语法基础--- 泛型(基础知识问答)

JavaSE 泛型

JavaSE| 泛型

JavaSE-泛型