泛型(全)——Java学习笔记

泛型

泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码

  • 将原来具体的类型,变成一个可变的参数类型,使用时传递什么样的类型参数它就是什么类型。类似于方法中的变量参数。
  • 泛型是一种编译时类型确认机制。它提供了编译期的类型安全。确保在泛型类型上只能使用正确类型的对象,避免了在运行时出现的ClassCastException
  • 泛型使多种数据类型都可以执行一个代码,提高了代码的复用性

泛型的引入

我们知道集合中可以存放任意类型的对象。只要把对象放入集合中,其实这时对象都被提升为一个Object类型(隐藏的向上转型),那么这个时候,我们想要使用这个对象时,就需要进行向下转型,来还原这个对象,然后再进行使用

public class Test1{
    public static void main(String[]args){
        Collection c1 = new HashSet();
		//自定义一个类,对象有三个属性:学号、姓名、年龄
        c1.add(new Student(1001,"张三",18));
        c1.add(new Student(1002,"李四",19));
        c1.add(new Student(1003,"王五",22));

        for (Object o:c1){
            //使用之前需要先进行向下转型
            Student student = (Student) o;
            System.out.println(student.getName());
        }
    }
}

在JDK5之后新增了泛型语法。在设计API时就可以指定类或者方法支持泛型,在我们使用这个类时就可以指定数据类型,这样也可以避免取出对象时发生ClassCastException。

当我们在定义集合时加上<泛型类型>,就可以给这个集合指定存入哪一种数据类型,那么放入集合中的对象也不会自动向上转型,我们也不需要先向下转型后再使用集合中的元素。

public class Test2{
    public static void main(String[] args){
        //指定集合中存入数据的类型        
        Collection<Student> c2 = new HashSet<>();
        c2.add(new Student(1001,"小明",20));
        c2.add(new Student(1002,"小红",21));
        c2.add(new Student(1003,"小王",22));

        //明确集合中的元素类型,也不需要Object向下转型
        for (Student s: c2){
            System.out.println(s.getName());
        }
    }
}

泛型类、泛型接口

定义泛型类、接口

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

  • 泛型形参的命名一般使用单个的大写字母,如果有多个类型形参,那么使用逗号隔开,如Map<K,V>
  • 常见的字母表示:
    • T:Type
    • K,V:Key,Value
    • E:Element

举例:

定义学生类,其中学生的成绩可以为整数、小数、字符串(优、良、中、差)

class Student<T>{
    private String name;
    //分数的数据类型就为T
    private T score;

    //构造方法不需要加类名中的<T>
    public Student(){

    }
    public Student(String name, T score) {
        this.name = name;
        this.score = score;
    }
    //静态方法和静态属性不能使用泛型
//    public static T abs;
//    public static void sta(T score){ }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public T getScore() {
        return score;
    }

    public void setScore(T score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

public class Test11 {
    public static void main(String[] args) {

        //泛型类型必须是引用数据类型
        Student<Integer> s1 = new Student<>("张三",100);
        System.out.println(s1);
        Student<String> s2 = new Student<>("李四","优秀");
        System.out.println(s2);
    }
}

通过上面的代码发现,泛型的使用有很多规则

泛型的约束和局限性
  • 在定义类或接口时,指定类型形参,类型形参在整个类或接口体中可以当成类型使用,几乎所有可能使用其他普通类型的地方都可以使用这种类型形参,如:属性类型、方法的形参类型,方法返回值类型
  • 但是泛型类或者泛型接口上的泛型形参(变量),不能用于声明静态变量,也不能用在静态方法中,那是因为静态成员的初始化是随着类初始化的,此时泛型实参没有指定。
  • 我们在声明变量就要指定泛型实参,
  • 泛型实参必须是引用数据类型,不能是基本数据类型
  • 无法使用instanceof关键字,或者= =判断泛型类的类型
  • 泛型数组可以声明但是无法实例化
  • 泛型类不能继承Exception或者Throwable
  • 不能捕获泛型类型限定的异常,但是可以将泛型限定的异常抛出

创建泛型类对象的写法:

  • JDK1.5引入泛型:Student<Integer> s1 = new Student<Integer>;
  • 在JDK1.7后支持:Student<Integer> s1 = new Student<>;

在继承泛型类或实现泛型接口时,子类不延续使用该泛型,那么要明确指定实际类型,此时子类就不再是泛型类了

interface MyList extends List<String>{}

如果延续使用泛型:

延续使用父类、父接口的泛型形参

如果要继承泛型类、实现泛型接口时,想要保留父类(父接口)的泛型,必须在父类(父接口)、子类(子接口)中都要保留

//继承有泛型的类,子类和父类泛型形参要一致,即使List类中泛型形参是E
interface MyList2<T> extends List<T>{ }

系统定义的集合类:

public interface List<E> extends Collection<E> {···}
···
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{···}
设定泛型类的上限

刚刚我们为学生类的成绩使用了泛型形参,但是此时成绩的类型可以是任何类型,如果我们想要成绩只为数字类型(Integer、Double、Float···)怎么办呢?

//修改<>里面的内容就可以为泛型形参设定上限,从而缩小了它的类型范围
class Student<T extends Number>{
    ···

为泛型形参设定了上限后,泛型形参只能为 他自己(Number)或者他的子类(Integer、Double、Float···)

泛型形参至多只能有一个父类上限,但是可以有多个接口上限。

在一种更加极端的情况下,程序要为泛型形参设置更多的限定条件。该泛型形参不光要在父类上限范围内,还要实现了规定的上限接口

//多个接口上限,中间使用&  ,类上限必须在前面(和类同时继承类和接口一样)
class MyNumber2<T extends  Number & java.io.Serializable>{ }// 表示这个类必须是上限类或者其子类并且实现了所有上限接口

泛型方法

  • 两个问题:
    • 如果我们定义类或接口时,没有使用泛型的类型形参,但是某个方法想要自己来定义类型形参
    • 前面讲到类和接口的类型形参不能用在静态方法中,但是我们的静态方法也想要自己来定义类型形参
  • 为了解决上面的两个问题,JDK1.5之后还提供了泛型方法
定义泛型方法

格式:

[修饰符] <泛型形参列表> 返回值类型 方法名([形参列表]) 抛出的异常列表{}

举例:

我们想要将一个数组中的元素添加到一个集合中去,我们想让所有类型的数组都可以使用这个方法,那么就要给这个数组和集合一个类型实参,并且把这个方法要声明为泛型方法。

public class Test13 {
	public static <T> void fromCollection(T[] a, Collection<T> c) {
        for(T t :a){
            c.add(t);
        }
    }
    
    public static void main(String[] args){
        Collection<Integer> c1 = new ArrayList<>();
        Integer[] ints = {1,2,4};
        fromCollection(ints,c1);
        System.out.println(c1);

        Collection<String> c2 = new ArrayList<>();
        String[] strs = {"a","aa","aaa"};
        fromCollection(strs,c2);
        System.out.println(c2);
    }
}
  • 泛型形参列表,可以是一个也可以是多个,如果是多个,使用逗号分隔,
  • <泛型形参列表> 必须在修饰符和返回值类型之间
  • 泛型方法中声明的泛型形参只能在当前方法中使用,和其他方法无关
  • 泛型方法申明中定义的泛型形参无需显示传入实际类型参数,编译器会根据实际参数的类型自动推断出形参的实际类型
设定泛型方法的上限

其实没有设置泛型形参上限的,可以把它的上限看成Object

//这样这个方法传入的实参只能是Number类型数组或者其子类数组了
public static <T extends Number> void fromCollection(T[] a, Collection<T> c) {···

和泛型类的上限规则一样

类型通配符

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

定义类型通配符

举例:

写一个方法操作List中的元素

  • 当我们不确定一个LIst接口中的形参类型是什么的时候,如果我们这个方法并不需要对List进行添加操作只做读取,就可以不使用泛型方法,而使用类型通配符来实现
    //声明泛型形参时,设置类型通配符
    public void test1(List<?> list){
        //这种设置过的泛型,使用时只能获取其中的值,但是不能为它添加值
        //我们使用泛型通配符后,我们也不知道它其中的类型是什么,所以不能向里面添加
//        list.add(100);
        list.add(null);//null可以,因为他是所有引用数据类型的实例
        for (int i=0;i<list.size();i++){
            //这里list.get()方法提示的返回值也是不确定的类型(capture of ?)
            System.out.println(list.get(i));
        }
    }
  • 如果我们这个方法需要向list中添加元素,我们可以使用泛型方法
    public <T> void test11(List<T> list,T t){
        //可以向list中添加一个T类型的元素t
        list.add(t);
        //可以遍历读取list中的元素
        for (T t1 : list) {
            System.out.println(t);
        }
    }
设定类型通配符的上限

使用类型通配符后,那么这个List接受所有元素类型的List,当我们想要这个方法只接受一定类型的List集合,同样也可以为类型通配符设定上限(<? extends Parent>

    //设置类型通配符上限
    public void test2(List<? extends Number> list){
        //(只有Number和他的子类类型的集合才能使用这个方法)
//        list.add(100);  //此时也不可以为list添加值,因为就算为传入的集合类型设置了范围,也不能确定究竟是哪一个类
        //Number类下还有Integer、Double、等,所以使用类型通配符还是不能向集合中添加元素
        //如果我们想要添加,建议就不要使用类型通配符了,要使用泛型方法来实现
    }
设定类型通配符的下限

为了约束这个不确定集合中的元素类型,Java中允许类型通配符设置下限(<? super Child>

    //设置类型通配符下限
    //设置下限的List,list中的元素只能是下限类或者下限类的父类
    public void test3(List<? super Number> list){
    }

注意:只有类型通配符才可以设置下限, 泛型形参是不可以的!

泛型擦除

在严格的带泛型声明的类里,最好要带上类型参数,但为了与老的java代码保持一致,所以带泛型的类也可以不指定泛型类型。此时的类,它的泛型属于原始类型(raw type),默认是该类型形参的上限,如果没有上限就为Object

java中泛型在运行期是不可见的,会被擦除为它的上级类型,如果没有没有限定的泛型参数类型,就被替换为Object

        //其中设置的泛型String被擦除
        ArrayList list = new ArrayList<String>();

解释:

解释上面提到的泛型的约束和局限性,结合着来看:

并不存在泛型类型的Class对象

我们通过泛型使得一个类型的功能增强了,好像扩展出好多子类一样,比如一个集合可以使存储Integer的类,也可以是存储String的类,但系统并没有为ArrayList、ArrayList生成一个新的class文件,也不会把它们当成一个新的类,他们就是ArrayList类

        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        System.out.println(list1.getClass());
        System.out.println(list2.getClass());
		//并不能判断这个泛型类的泛型,返回true
        System.out.println(list1.getClass()==list2.getClass());

        if (list1 instanceof ArrayList){
            System.out.println("list1属于ArrayList");
        }
        //系统中并不会真正生成泛型类,所以
        //这种写法编译就会报错
//        if (list1 instanceof ArrayList<String>){
//
//        }
泛型与数组

java中是“不能创建一个确切的泛型类的数组“的

也就是说数组的元素不能包含类型变量或类型形参,除非是无上限的类型通配符

        //报错
//        List<String>[] ls1 = new ArrayList<String>[10];
        //只能声明这样是数组,但是不能创建这样的对象
        List<String>[] ls3 = new ArrayList[10];
        //可以使用类型通配符
        List<?>[] ls2 = new ArrayList<?>[10];

使用通配符的方式,最后取出的数据要做显示的类型转换(向下转型)

        List<?>[] arr = new List<?>[2];
        arr[0] = Arrays.asList("hello","java");
        arr[1] = Arrays.asList(1,2,3);
        for (List<?> list : arr) {
            for (Object object : list) {
                System.out.println(object);
            }
        }
泛型与异常
    /**
     * 泛型类不能继承Exception或者Throwable
     * Generic class may not extend 'java.lang.Throwable'
     */
//    private class MyGenericException<T> extends Exception {
//    }
//    private class MyGenericThrowable<T> extends Throwable {
//    }

    /**
     * 不能捕获泛型类型限定的异常
     * Cannot catch type parameters
     */
    public <T extends Exception> void getException(T t) {
//        try {
//
//        } catch (T e) {
//
//        }
    }

    /**
     *可以将泛型限定的异常抛出
     */
    public <T extends Throwable> void throwsException(T t) throws T {
        try {

        } catch (Exception e) {
            throw t;
        }
    }
泛型的嵌套
        Map<Integer,String> map  = Map.of(1,"a",2,"b");
        Set<Map.Entry<Integer,String>> entrySet = map.entrySet();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值