Java--泛型

简介:

泛型是将接口的概念进一步延伸,"泛型"的字面意思是广泛的类型。类、接口、和方法代码可以应用于非常广泛的类型。代码和他们能操作的数据类型不在绑定在一起了,同一套代码可以用于多种数据类型,这样不仅可以复用代码,降低耦合,而且可以提高代码的可读性和安全性。

泛型类

**
     * GenericClass 后面的T表示类型参数,泛型就是类型参数。
     * 处理的数据类型不是固定的,可以作为参数传入
     * @param <T>
     */
    static class GenericClass<T>{
        T name;
        T schoolName;

        public GenericClass(T name, T schoolName) {
            this.name = name;
            this.schoolName = schoolName;
        }
    }

    static class GenericMultipleParameter<T,U> {
        T name;
        U secore;

        public GenericMultipleParameter(T name, U secore) {
            this.name = name;
            this.secore = secore;
        }
    }
    
//泛型类用法,String 是传递的实际类型参数,GenericClass类的代码和它处理的数据类型不是绑定的,具体类型可以变化。
        GenericClass<String> genericClass = new GenericClass<>("黄皮皮","黄小小中学");
        String name = genericClass.name;
        System.out.println(name);

       //上面是String类型的数据,下面可以变为int,通过传递类型参数,可以实现代码和数据的分离,提高代码的复用性和可读性
       GenericClass<Integer> integerGenericClass = new GenericClass<>(10,20);
       int intName = integerGenericClass.name;
        System.out.println(intName);

        //类型参数可以是多个
        GenericMultipleParameter<String,Double> genericMultipleParameter =
                new GenericMultipleParameter<>("黄皮皮",20.0d);
        System.out.println(genericMultipleParameter.name);

基本原理:

我们知道java有编译器和虚拟机,java编译器将java源码转换为.class文件,而虚拟机加载并运行.class文件。对于泛型类,java编译器会将泛型代码转换为普通的代码。会将类型参数擦除,替换为Object,在插入必要的强制类型转换。JAVA虚拟机实际执行的时候,是不知道泛型这回事的,只知道普通的类和代码。在强调一遍,Java 是通过擦除实现的,类定义的类型参数T会被替换为Object。程序运行时,不知道泛型的实际参数类型。比如上面的GenericClass,运行时只知道GenericClass,而不知道Integer。

那小伙伴的疑问就来了,为啥要用泛型呢?通过下面例子会给你答案:

static class GenericPrinciple {
        Object name;
        Object secore;

        public GenericPrinciple(Object name, Object secore) {
            this.name = name;
            this.secore = secore;
        }
    }
GenericPrinciple genericPrinciple = new GenericPrinciple(1,2);
        int objName = (int)genericPrinciple.name;
        System.out.println(objName);
        GenericPrinciple genericPrinciple1 = new GenericPrinciple("黄皮皮",20.0d);
        System.out.println(genericPrinciple1.secore);
 //既然Object也可以实现,那为啥还要用泛型呢?主要时因为泛型的两大好处:更好的   安全性和更好的可读性。
//再看下面的代码,在编译的时候是没问题的,运行时会报java.lang.ClassCastException异常    
GenericPrinciple genericPrinciple2 = new GenericPrinciple("黄皮皮",90.0d);
        int name1 = (int)genericPrinciple2.name;
        String secore = (String)genericPrinciple2.secore;
        System.out.println(name1 + secore);
//如果用泛型就不会有上面的问题,在编译的时候就会提示类型错误,这样就能避免bug在运行时才能被发现。
        GenericMultipleParameter<String,Double> genericMultipleParameter1 =
                new GenericMultipleParameter<>("黄皮皮",99.0d);
        //这里会提示类型错误,
        int name2 = genericMultipleParameter1.name;

泛型最常用的应用:容器

下面例子是写了一个简易的动态数组:

static class DynamicArray<E> {
        private static final int DEFAULT_CAPARITY = 10;
        private int size;
        private Object[] elementData;

        public DynamicArray() {
            this.elementData = new Object[DEFAULT_CAPARITY];
        }

        public void ensureCapacity(int minCapacity){
            int oldCapacity = elementData.length;
            if(oldCapacity > minCapacity){
                return;
            }
            int newCapacity = oldCapacity*2;
            if(newCapacity < minCapacity){
                newCapacity = minCapacity;
                elementData = Arrays.copyOf(elementData,newCapacity);
            }
        }

        public void add(E e){
            ensureCapacity(size + 1);
            elementData[size++] = e;
        }

        public E get(int index){
            return (E)elementData[index];
        }

        public int size(){
            return size;
        }

        public E set(int index,E e){
            E oldValue = get(index);
            elementData[index] = e;
            return oldValue;
        }
        public  <T extends E> void addAll(DynamicArray<T> e){
            for (int i = 0; i<e.size; i++){
                add(e.get(i));
            }
        }

        public void addAllCharacter(DynamicArray<? extends E> e){
            for (int i = 0; i<e.size; i++){
                add(e.get(i));
            }
        }

        public void copyTo(DynamicArray<? super E> arr){
            for (int i = 0; i<size; i++){
                arr.add(get(i));
            }
        }

    }
//泛型最常用的就是作为容器类,上面实现一个简易的动态数组 DynamicArray<E>
        //它可以使用任意数据类型,且类型安全。传入的类型参数可以是一个泛型类,可以是String
        DynamicArray<GenericClass<String>> genericClassDynamicArray = new DynamicArray<>();
        genericClassDynamicArray.add(genericClass);
        DynamicArray<String> dynamicArray = new DynamicArray<>();
        dynamicArray.add("黄皮皮");

泛型方法:

泛型方法与它所在的类是不是泛型类没有关系。同时如果用泛型方法可以解决的问题,就不用泛型类。

public static <T> int  indexOf(T[] arr, T pro){
        for (T t:arr) {
            if(t.equals(pro)){
                return (int)t;
            }
        }
        return -1;
    }
public static <T,U> void MultipleParameter(T t,U u){
        System.out.println(""+t.toString()+u);
    }
Integer[] ints= new Integer[]{1,24,2,5,6,8,12,45,124};
         int pro = 5;
         if(GenericDemo.indexOf(ints,pro) != -1){
             System.out.println("数组中存在匹配的值");
         }
//泛型方法也是可以多个类型参数,泛型方法可以不用特意指定类型参数的实际类型,java编译器可以自动推断出来
  GenericDemo.MultipleParameter("黄皮皮",90.d);        

泛型接口:

接口也可以是泛型的,比如Comparable和Comparator 接口。但是实现接口时,应该指定接口泛型的具体的类型。比如下面这个例子:

//实现Comparable<T>泛型接口时需要指定泛型接口具体的类型参数
static class GenericInterface implements Comparable<Integer> {

        @Override
        public int compareTo(Integer integer) {
            return 0;
        }
    }

类型的参数限定:extends

无论是泛型类,泛型接口、还是泛型接口,关于类型参数,我们都 知之甚少,只能把类型参数当做是Object。但JAVA 支持限定这个参数的一个上界。也就是说,参数必须为给定上界类型或其子类型。这个限定是通过extends关键字来表示的。这个上界可以是某个类,某个接口,可以是其他的类型参数。
示例:

    //比如上界是某个类
    static class GenericLimit<T extends Number,U extends Number> {
    }
    /**
     * 比如上界是某个接口
     *
     */
    static class GenericLimintInterface<T extends Comparable<T>> {}
    
    /**
     * 比如上界是其他类型参数
     */
    public void addAllCharacter(DynamicArray<? extends E> e){
            for (int i = 0; i<e.size; i++){
                add(e.get(i));
            }
        }
   /**
     * 通配符:更简洁的参数类型限定
     * 比如 DynamicArray<E> 的方法adlAll(),可以使用通配符的方式变的更简单
     * addAll(DynamicArray<? extends E> c),?表示通配符,<? extends E>表示有限定通配符
     * 匹配E 或者E 的子类,具体子类是什么类型是未知的。但是通配符这种方式和之前的定义一个类型参数T的效果是一样的
     * 但是更简洁。
     * /**
     * <T extends E> 和 <? extends E> 同样是extends关键字,它们有啥不同呢?它们用的地方不一样:
     * 1)<T extends E> 用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面,泛型方法返回值前面
     * 2)<? extends E> 用于是实际类型参数,它用于实际化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是
     * E或E的某个子类型
     * 比如:
     * public void addAll(DynamicArray<? extends E> c)
     * public <T extends E> void addAll(DynamicArray<T> c)
     * 这两种写法可以达成同一个目标
     */
      public void addAll(DynamicArray<? extends E> c){
       for (int i = 0; i<e.size; i++){
                add(e.get(i));
            }}
      public  <T extends E> void addAll(DynamicArray<T> e){
            for (int i = 0; i<e.size; i++){
                add(e.get(i));
            }
        }
    /**
     * 理解通配符,以及通配符的限制
     * DynamicArray<?>是无限定通配符,上面的DynamicArray 成为有限通配符
     */
    public static int indexOf(DynamicArray<?> arr,Object elm){
        for (int i = 0 ; i<arr.size;i++){
            if(arr.get(i).equals(elm)){
                return i;
            }
        }
        return -1;
    }
     /**
     *上面也可以改为类型参数的方式,不过通配符的方式更加简洁,但上面通配符方法只    能读不能写。
     */
    public static  <T> int indexOf1(DynamicArray<T> arr,Object elm){
        for (int i = 0 ; i<arr.size;i++){
            if(arr.get(i).equals(elm)){
                return i;
            }
        }
        return -1;
    }
 /**
     *通配符 extends无法写入,因为类型是未知的,所有有时候类型参数方法和通配符  方法结合起来用,可以达到很不错的效果
     * 如下:两个元素交换位置
     */
    public static void swap(DynamicArray<?>arr, int i, int j){
         swapInternal(arr,i,j);
    }

    public static <T> void swapInternal(DynamicArray<T> arr,int i,int j){
        T temp = arr.get(i);
        arr.set(i,arr.get(j));
        arr.set(j,temp);
    }
 /**
     * 如果参数类型之间有依赖关系,只能使用参数类型
     *比如:D和S 有依赖关系,要么相同,要么是D的子类,否则类型不匹配,有编译错误。
     */
    public static <D,S extends D> void copy(DynamicArray<D> dest,DynamicArray<S> src){
        for (int i = 0; i<src.size();i++){
            dest.add(src.get(i));
        }
    }
/**
     *超类型通配符,与形式<? extends T>相反,它的形式为<? super T> 称为超类 型通配符,表示T的某个父类型,
     * 有了它我们就可以更灵活的写入了
     * 比如给DynamicArray 添加一个copyTo方法
     */
     public void copyTo(DynamicArray<? super E> arr){
            for (int i = 0; i<size; i++){
                arr.add(get(i));
            }
        }
 //通配符的限制:下面例子通配符只能读不能写。看例子:
        DynamicArray<Integer> integerDynamicArray = new DynamicArray<>();
        DynamicArray<? extends Number> dynamicArray1 = integerDynamicArray;
        Integer i = 1;
        //下面是那种三种添加方式都会编译出错,为啥?因为通配符表示类型安全不知,? extends Number表示是Number的
        //某个子类型,但不知道具体类型。如果允许写入,Java 将无法保证类型安全性。所以直接禁止了。
       /* dynamicArray1.add(i);
        dynamicArray1.add((Number)i);
        dynamicArray1.add((Object)i);*/

       //通过<? super E>就可以灵活的写入了
        DynamicArray<? super  Number> dynamicArray2 = new DynamicArray<>();
        dynamicArray2.add(1);
        dynamicArray2.add(29.0d);

泛型的细节和局限性:

//泛型的细节和局限性
       //(1)基本类型不能用于实例化类型参数。如下: 只能转为使用基本类型的包装类
       /* GenericTest<int> genericTest = new GenericTest<int>(1);*/
       //(2)运行时类型信息不使用泛型。比如直接类名.class,因为类型对象只有一份,和泛型无关
       /* Class<?> cls = GenericTest<Integer>.class;*/
        //下面两个用getClass()返回的泛型类Class对象是相同的
        GenericTest<Integer> intG = new GenericTest<>(1);
        GenericTest<String> stringG = new GenericTest<String>("2");
        if(intG.getClass().equals(stringG.getClass())){
            System.out.println("This is true");
        }

        //(3)类型擦除引发的冲突,下面编译就会报错
        if(intG instanceof GenericTest<Integer>){
            System.out.println("类型擦除引发的冲突");
        }
        //但支持下面这种写法
        if(intG instanceof  GenericTest<?>){
            System.out.println("允许通配符的方式");
        }

        //(4)不能通过类型参数创建对象
         T t = new T();
        //(5)泛型类类型参数不能用于静态变量和方法
        //(6)Java 还支持多个上届,多个上届之间以&分割 T extends Base &Comparabl & Serializable

总结:

泛型使我们在编写代码时可以更加的灵活,更加的易读,同时提高了代码的安全性。使我们可以在编译的时候就可以找出代码漏洞,减轻后期系统问题。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值