Java泛型

描述

当需要操作特定对象的时候,如果不设置特定的对象类型,那么取出来都是Object类,这时就有可能诱发类转换异常的操作,所以需要使用泛型来确定当前容器操作的类型。

比方说:我们的一个集合,可以存储数字,又可以存储字符串、自定义对象,他返回的都是Object。

泛型就是相当于给容器贴标签,说明该容器只能存储什么样类型的对象。

泛型是再JDK5.0之后出现的,就是因为之前会出现大量的 类转换异常。

语法格式

1.集合类

集合类<泛型的类型,如Integer> 变量名 = new 集合类<泛型的类型,前后要一致>();

——将运行时异常提前至编译时异常,可以让开发人员提前预知。

——获取元素时无需强制类型转换,就避免了类转换异常问题。

——泛型的类型必须是引用类型,不能是基本数据类型。

2.泛型方法

可以作为泛型方法,也就是说,调用者调用该方法时,返回的就是此固定的类型。

格式:public void demo(T name) / piblic T demo(T t){ return t}

(其中,T代表任意的引用数据类型)

Integer[] i = {1, 2, 34, 5, 8};
        Double[] d = {1.1, 2.2, 3.4, 5.0, 8.1};
        String[] str = {"你", "好", "!"};
//使用泛型,这么多类型写一个就行了
    public static <T> void show(T[] t) {
        for (T j : t) {
            System.out.print(j + " ");
        }
    }

3.泛型类

可以作为泛型类,也就是说,可以在使用时指定未知的类型,可以在使用时固定特有的类型格式:public class Demo{}

package package02;

/*
泛型
        泛型类

 * @Author WENG Jun
 * @Date 2021/7/17 - 11:03
 */
public class GenericClassTest {
    public static void main(String[] args) {

        Demo<Integer> d1 = new Demo<Integer>();
        d1.setName(123);

        Demo<String> d2 = new Demo<String>();
        d2.setName("张三");
    }
}

class Demo<T>{
    T name;

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

4.泛型接口

可以作为泛型接口,和定义泛型类相似,可以将其看成是一个特殊的类

格式:public interface lDemo<T,V>{}
——便于接口扩展

package package02;

/*
泛型
     泛型接口

 * @Author WENG Jun
 * @Date 2021/7/17 - 11:10
 */
public class GenericInterfaceTest implements IDemo<Integer> {
    public static void main(String[] args) {
        GenericInterfaceTest test = new GenericInterfaceTest();
        test.run(100);

    }


    @Override
    public void run(Integer value) {
        System.out.println(value);
        System.out.println("重写了泛型接口的方法:Integer类型参数");
    }
}

//泛型接口
interface IDemo<T> {
    public void run(T value);
}

泛型的好处

1、利于代码的重用性,增加其通用性,使得我们的代码更加的简洁,便于维护

2、避免了强制类型转换,也不用担心会丢失精度

3、编译时确定类型,保证类型安全,使异常再开发时变成预知

泛型的限制和规则

1、泛型的类型参数只能是引用类型,不能是值类型

2、泛型类不是真正存在的类,所以不能使用instanceo运算符比较

3、如果定义泛型,但是又不指定具体的类型,泛型默认指定Object类型

4、泛型类的类型参数不能在静态中声明

5、泛型的类型参数可以有多个

6、泛型使用?作为通配符,表示未知类型,可以匹配任意类型,因为是未知,所以无法添加元素

——<?extends > :?代表的是T类型本身或者T类型的子类型,常用于泛型方法,避免类型转换。

7、泛型类中不能定义泛型静态方法

——是因为类中的泛型需要在对象初始化的时候制定具体的类型,而静态优先于对象存在,那么类中的方法就需要单独进行泛型声明,声明泛型时需注意这一点。

8.限定泛型通配符边界

8.1限定泛型通配符的上边界

——接收本类型和本类型的子类类型

正确:Demo <? extends Number> d=new Demo ();

错误:Demo <? extends Number> d=new Demo ();

8.2限定通配符的下边界

——接收本类型或者本类型的父类类型

正确:Demo <? super Integer> d=new Demo ();

错误:Demo <? super Integer> d=new Demo ();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fINXu5RL-1626698235455)(images/32.jpg)]

涉及上下边界时使用add和get方法的问题?

(类A、B、C,其中B继承自A、C继承自B)

上边界时:

表示所有继承了A的子类,也包括A,但是具体是哪个子类,无法确定,所以调用add时,要add什么类型,编译器不知道,所以编译不通过,但是get的时候,不管是什么子类,不管追溯到多少层级,肯定都有一个父类A,所以我们都可以使用父类A接着,也就是把所有的子类向上转型成为A类。

下边界时:

表示B的所有父类,包括A,也包括追溯最上层的Object,那么当我们做add的操作时,我们不能add父类,因为不能确定list里面存放的到底是哪个父类,但是我们可以add B类及它的子类,因为我不管我的子类是什么类型,它都可以向上转型位B类以及所有的父类甚至包括Object,但是当使用get时,B类的父类不止一个,所以无法接着,除了Object,其他的都接不住,那么编译也不通过,这就是下界。

概括:

使用编译器可以支持向上转型,但不支持向下转型,如果必须要向下转型的话,就需要cast。简而言之,不能add父类,不能get父类。

注意事项

泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,既可以挡住集合中插入非法的数据,当编译完带有泛型的java程序后,生成的class文件将不再带有泛型信息,以此程序运行效率不受影响,这个过程我们称之为“擦除”。

**[练习]**上下边界

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

        //上边界
        List<? extends Number> list1 = new ArrayList<Integer>();
        //String不是Number类型或者其子类类型。如下:
//        List<? extends Number> list2 = new ArrayList<String>();

        //下边界
        List<? super Integer> list3 = new ArrayList<Number>();
        //Double不是Integer类型或者其父类类型。如下:
//        List<? super Integer> list4 = new ArrayList<Double>();
    }
}