Java笔记——泛型的学习总结

泛型的定义

泛型即泛化类型,也可以称为参数化类型。一般情况下使用在集合中。如果把集合类比成瓶子,那么泛型就是瓶子上面的标签,指明瓶子中装的是什么类型的物品。泛型中的类型是在使用的时候指定的,一旦指明泛型,集合中就不能存放泛型之外的类型对象了。

泛型的好处

使用泛型后很多好处:安全、省心。安全是指确定泛型之后,就不能使用泛型之外的类型,可以避免这方面的错误;省心是指获取数据的时候,不用进行类型转换。下面看一个实例:

import java.util.ArrayList;
import java.util.List;

public class Test01 {

    public static void main(String[] args) {
        // 泛型在使用的时候指定
        // 泛型指定为String之后,该List中只能存放String类型的对象
        List<String> strList = new ArrayList<String>();

        strList.add("fengzhen");
        // strList.add(new Integer(666));   添加不是String类型的对象,编译不会通过

        // 获取List中数据的时候,不用进行强制类型转换
        String name = strList.get(0);
    }
}

自定义泛型类

我们不仅可以使用JDK中定义好的泛型,还可以自定义泛型。这包括:自定义泛型类、自定义泛型方法、自定义泛型接口等。下面我们先说一说自定义泛型类。

在定义泛型类的时候,我们只需要在类名的后面加上一对尖括号 <> ,然后在尖括号里面添加单个的大写字母或者是大写字母和数字的组合(T、T1、T2…),这个大写字母就是我们指定的类型,如果要指定多个泛型,那么加入过多个不同的单个字母即可。在类中我们也可以使用该大写字母定义变量,或者指定方法的返回类型。在添加大写字母的时候,尽量做到见名知意,一般情况下使用以下几个大写字母: T、K、V、E 。他们的含义如下:

大写字母 含义
T Type,类型
K Key,键值对中的键
V Value,键值对中的值
E Element

下面给出一个自定义类型的实例:

public class MyStudent<T> {
    // 学生的分数,在使用该泛型类的时候指定分数的类型
    // 可以使整数、小数,甚至是字符类型
    T score;

    public MyStudent() {
    }

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

    public T getScore() {
        return this.score;
    }
}

在使用自定义泛型类的时候,我们要传入具体的类型,实例如下:

  public static void main(String[] args) {
        // 使用自定义泛型的时候,指定类型
        // 注意只能使用引用类型,不能使用基本类型(int,double,short,chat等)
        MyStudent<Integer> stu1 = new MyStudent<Integer>();
        stu1.setScore(80);
        System.out.println(stu1.getScore());
        

        MyStudent<Double> stu2 = new MyStudent<Double>();
        stu2.setScore(90.5);
        System.out.println(stu2.getScore());


        MyStudent<String> stu3 = new MyStudent<String>();
        stu3.setScore("优秀");
        System.out.println(stu3.getScore());
    }

自定义泛型接口

Java中也可自定义泛型接口,使用的时候和自定义泛型类大同小异,但是要注意在自定义泛型接口中,泛型不能使用在全局变量上,实例如下:

public interface MyInterface<T> {
    // 泛型不能使用在全局变量上
    /*public static final*/ int MAX_VALUE = 666;

    T compare(T value);
}

自定义泛型方法

关于自定义泛型方法,我们需要了解的是:在非泛型类中也可以使用自定义泛型方法,自定义泛型方法可以是静态方法也可以是非静态方法。实例如下:

public class Student {

    // 静态方法
    public static <T> void showInfo(T info) {
        System.out.println(info);
    }

    // 非静态方法
    public <T> void printInfo(T info) {
        System.out.println(info);
    }

    public static void main(String[] args) {
        Student.showInfo("我是静态方法");
        new Student().printInfo("我是非静态方法");
    }
}

如果想要限制泛型的范围,比如泛型只能是小数,那么可以采用以下的做法:

public <T extends Double> void showNumber(T number) {
    System.out.println(number);
}

泛型类的继承与实现

比如现在我们有一个泛型类 Person ,当一个类要继承这个泛型类的时候,针对泛型,会出现以下四种情况:

public class Person<T1, T2> {

}

1. 全部保留泛型

// 父类中的T1和T2泛型全部都保留了
// 这个时候,子类中也可以有额外的、自定义的泛型
class Student01<T1, T2, E, K> extends Person<T1, T2> {
    @Override
    public void test(T2 name) {
    }
}

2. 部分保留泛型

// 父类中的T2泛型保留了,T1泛型指定为Integer类型
// 这个时候,子类中也可以有额外的、自定义的泛型
class Student02<T2, E, K> extends Person<Integer, T2> {
    @Override
    public void test(T2 name) {
    }
}

3. 不保留,指明具体的泛型

// 泛型T1和T2都被指定为明确的类型
// 但是这个时候,子类中也可以有额外的、自定义的泛型
class Student03<E, K> extends Person<Integer, String> {
    @Override
    public void test(String name) {
    }
}

4. 不保留,不指明具体的泛型

// 泛型T1和T2没有保留,也没有被指定为明确的类型
// 但是这个时候,子类中也可以有额外的、自定义的泛型
// 这里相当于把T1和T2都指定为类Object,但是和Object还是有区别的
class Student04<E,K> extends Person{
    @Override
    public void test(Object name) {
    }
}

以上四种情况中,子类和父类中定义的属性和方法的类型,是根据声明的位置来定的,具体可总结为以下几条规则:

  • 子类中重写父类中的方法,类型随父类而定
  • 子类中新增方法的属性,类型随子类而定
  • 子类中使用父类的属性,类型岁父类而定
  • 子类新增的属性,类型随子类而定

泛型接口的实现

规则同泛型类的继承与实现,参考上面的规则。

泛型的擦除

泛型擦除是指在继承(实现)或使用的时候,没有指定明确的类型。

泛型一旦擦除之后,会被当做类似Object来处理。实例如下:

public class Student<T> {
    T score;

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

    public T getScore() {
        return this.score;
    }

    public static void main(String[] args) {

        // 定义对象的时候,不指定泛型,这时候泛型被当做Object
        Student stu01 = new Student();
        stu01.setScore(98);
        Object stuScore = stu01.getScore();
    }
}

但是此时的泛型并不等同于Object,还是有细微的差别,先看以下的实例代码:

public class Student<T> {
    T score;

    public static void test(Student<Integer> student) {
    }

    public static void main(String[] args) {

        // 定义对象的时候,不指定泛型,这时候泛型被当做类Object
        // 当做参数传入test方法中,不会报错
        Student stu01 = new Student();
        Student.test(stu01);
        
        // 指明泛型为Object,传入test会报错: generic02.Student<java.lang.Object>无法转换为generic02.Student<java.lang.Integer>
        Student<Object> stu02 = new Student<Object>();
        Student.test(stu02);
    }
}

泛型的通配符

泛型中的通配符是 ? ,只能使用在变量和形参的定义上,不能用在创建泛型接口、泛型方法和泛型类上。表示当前状态下,变量和形参的类型不确定。实例如下:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Test01 {

    public static void main(String[] args) {
        // 通配符用在变量的声明上,表示当前变量的类型不确定
        // 后面可以赋多种类型的值
        List<?> myList;
        myList = new ArrayList<String>();
        myList = new LinkedList<Integer>();
        
        // 可以传递多种类型的实参
        Test01.test(new ArrayList<Double>());
        Test01.test(new ArrayList<String>());
    }

    // 通配符用在形参的声明上
    public static void test(List<?> newList) {
    }
}

泛型的上限

泛型的上限用来限制泛型的范围,用关键字 extends 表示。首先看下实例:

我们先自定义了一个类Fruit,然后定义两个子类Apple和Pear继承自Fruit类,然后定义一个子类FujiApple继承自Apple类:

public class Fruit {
    String name;
}

class Apple extends Fruit {
}

class Pear extends Fruit {
}

class FuJiApple extends Apple {
}

现在我们定义一个类,其泛型的上限是 Apple ,那么在指定泛型的时候,就只能在Apple及其子类的范围中,指定为其他的类型,编译会报错。实例如下:

public class Test02 {

    public static void main(String[] args) {
        // 将泛型指定为Apple及其子类的话,是没有问题的
        Test<Apple> test01 = new Test<Apple>();
        Test<FuJiApple> test02 = new Test<FuJiApple>();

        // 如果将泛型指定为Apple及其子类之外的类型,那么会报错
        // Type parameter 'Pear' is not within its bound; should extend 'Apple'
        Test<Pear> test01 = new Test<Pear>();
    }
}

// 定义一个类,其泛型上限是Apple
class Test<T extends Apple> {

}

泛型的上限可以和泛型的通配符搭配使用,这样在定义变量和方法的形参的时候,可以更加灵活地设置其范围,实例如下:

import java.util.ArrayList;
import java.util.List;

public class Test03 {

    // 定义一个方法,指定其形参的泛型范围:Apple及其子类
    public static void test(List<? extends Apple> list) {

    }

    public static void main(String[] args) {

        // 指定泛型的范围符合要求,编译不会报错
        List<Apple> list1 = new ArrayList<Apple>();
        test(list1);

        List<FuJiApple> list2 = new ArrayList<FuJiApple>();
        test(list2);

        List<? extends Apple> list3 = new ArrayList<Apple>();
        test(list3);
        
        
        // 指定泛型的范围不符合要求,编译会报错
        List<Fruit> list4 = new ArrayList<Fruit>();
        test(list4);

        List<?> list5 = new ArrayList<Apple>();     // List(?) 等同于 List(? extends Object)
        test(list5);
    }
}

泛型的上限一般用于限制操作,不能使用在数据的添加上,一般使用在数据的读取上。指定了形参或者变量的上限,在向里面添加对象的时候,会受到限制。比如下面的实例中,指定泛型的上限是Apple,但是没有指定泛型的下限,所以不能成功添加除 null 之外的对象。

// 定义一个方法,指定其形参的泛型范围:Apple及其子类
public static void test(List<? extends Apple> list) {
    list.add(new Apple());  // 报错
    list.add(new FuJiApple());  // 报错
    list.add(null); // 成功
}

泛型的下限

泛型的下限使用关键字 super 来指定,用法和泛型的上限类似,只不过表示的范围相反。下面给出一个实例即可:

import java.util.ArrayList;
import java.util.List;

public class Test04 {

    public static void test(List<? super Apple> list) {

    }

    public static void main(String[] args) {
        List<Apple> list01 = new ArrayList<Apple>();
        Test04.test(list01);

        List<Fruit> list03 = new ArrayList<Fruit>();
        Test04.test(list03);

        // 不能添加泛型类型的子类
         List<FuJiApple> list02 = new ArrayList<FuJiApple>();
         Test04.test(list02);
    }
}

其他的细节

泛型没有多态、泛型没有数组。

泛型是可以嵌套的,使用方法和单个的泛型类似。就是一个泛型嵌套另外一个泛型,没有什么新的内容。

猜你喜欢

转载自blog.csdn.net/fengzhen8023/article/details/85704554