Java泛型

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。Java泛型,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

2.泛型
2.1.背景
  • 泛型:可以在类或方法中预支地使用未知的类型。

    一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

2.2.泛型的优点
  • 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
  • 避免了类型强转的麻烦。
public class Generic {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("abc");
        list.add("itcast");
        // list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
        // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            //当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是
            String类型
            System.out.println(str.length());
        }
    }
}
2.3.定义泛型
  • 泛型类
public class MyGenericClass<MVP> {
      //没有MVP类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
      private MVP mvp;
      public void setMVP(MVP mvp) {
      	this.mvp = mvp;
      }
      //使用了泛型作为返回值的方法,因为在类声明中已经声明过了,所以可以直接使用。
      //但是该方法不能泛型方法,只能称为使用了泛型的方法,因为是在类中声明过的。
      public MVP getMVP() {
      	return mvp;
      }
  }
  
  public class Class_Generic {
      public static void main(String[] args) {
          // 创建一个泛型为String的类
          MyGenericClass<String> my = new MyGenericClass<String>();
          // 调用setMVP
          my.setMVP("大胡子登登");
          // 调用getMVP
          String mvp = my.getMVP();
          System.out.println(mvp);
          //创建一个泛型为Integer的类
          MyGenericClass<Integer> my2 = new MyGenericClass<Integer>();
          my2.setMVP(123);
          Integer mvp2 = my2.getMVP();
      }
  }
  • 泛型方法
//在这里,<>仅仅作为一种泛型声明,意思是,这个方法要用到泛型,在作用域中用到尖括号里的内容的时候,编译器会将其识别为一种数据类型。
  //如果在方法声明中不进行<>尖括号的泛型声明,编译器无法识别<>中的内容是一种类型
  public class MyGenericMethod {
      //方法中用到了泛型,所以提前声明
      public <MVP> void show(MVP mvp) {
      	System.out.println(mvp.getClass());
      }
      
      //方法中用到了泛型作为返回值,也要提前声明,告诉编译器,尖括号里的内容是一种引用类型
      //因为已经超过了上一个<>的作用域范围,所以要重新声明一下<>的内容是一种引用类型的代指
      public <MVP> MVP show2(MVP mvp) {
      	return mvp;
      }
      
      //含有两个泛型的泛型方法
      //对于一个泛型方法作为返回值类型,编译器会自动识别返回的真正类型,比如传入一个数字,编译器会自动返回为Integer类型。
      public <MVP,MVP1> MVP show3(MVP mvp,MVP1 mvp1) {
      	return mvp;
      }
      
      //show3(1,"str");  //返回值为Integer类型的数字1。
  }
  
  
  
  //对于泛型类中的使用了泛型的普通方法,和泛型方法,有以下两种情况
  //    1.对于show4方法,是一个使用了泛型的普通方法,返回值为类中的泛型,因为名称是一样的。
  //    2.对于show5方法,泛型类中的泛型虽然与泛型方法中的名称一样,但是,它的本质是两种泛型,<>在方法体中重新声明了,类似于变量隐藏。
  class MyGenericMethod1<MVP> {
    
      public MVP show4(MVP mvp) {
          return mvp;
      }
      public<MVP> MVP show5(MVP mvp) {
          return mvp;
      }
  }
  //对于上边的泛型类中的泛型方法,其实可以这样理解,是等价的
  class MyGenericMethod2<MVP> {
    
      public MVP show6(MVP mvp) {
          return mvp;
      }
      public<MVP1> MVP1 show7(MVP1 mvp1) {
          return mvp1;//返回的就是<MVP1>类型的,而不是类中的<MVP>
      }
      
      //当然,这个泛型方法的参数,就和类中的一样了,因为返回值是类中的<MVP>,而该方法体中只是重新定义了<MVP1>为一个全新的泛型
      public<MVP1> MVP show8(MVP mvp) {
          return mvp;
      }
  }
  
  
  
  public class GenericMethodDemo {
      public static void main(String[] args) {
          // 创建对象
          MyGenericMethod mm = new MyGenericMethod();
          // 演示看方法提示
          mm.show("aaa");
          mm.show(123);
          mm.show(12.45);
      }
  }
  • 泛型接口
public interface MyGenericInterface<E>{
        public abstract void add(E e);
        public abstract E getE();
    }
    
    
    //定义类时确定泛型的类型
    //此时,泛型E的值就是String类型。
    public class MyImp1 implements MyGenericInterface<String> {
        @Override
        public void add(String e) {
        	// 省略...
        }
        @Override
        public String getE() {
        	return null;
        }
    }
    
    
    //始终不确定泛型的类型,直到创建对象的时候,确定泛型的类型
    public class MyImp2<E> implements MyGenericInterface<E> {
        @Override
        public void add(E e) {
        	// 省略...
        }
        @Override
        public E getE() {
        	return null;
        }
    }
    
    /*
     *  使用
     */
    public class GenericInterface {
        public static void main(String[] args) {
            MyImp2<String> my = new MyImp2<String>();
            my.add("aa");
        }
    }
  • 详解关于泛型类、泛型接口的继承与实现
//泛型接口的实现
    public interface InGeneric1<T> {
    
    // 泛型接口:把泛型定义在接口上
    // 格式:public interface 接口名<泛型类型1…>
        
        public T getAge();
    }
    
    //不传入类型,默认Object类型
    class ClassGeneric1 implements InGeneric1{
        @Override
        public Object getAge() {
            return null;
        }
    }
    
    //在类的实现的时候确定了为String类型
    class ClassGeneric2 implements InGeneric1<String>{
        @Override
        public String getAge() {
            return null;
        }
    }
    
    //用一个泛型类去实现接口,实现的时候确定了接口类型为String,但是泛型类的类型仍然不确定
    class ClassGeneric3<E> implements InGeneric1<String>{
        @Override
        public String getAge() {
            return null;
        }
    }
    
    //泛型类实现该接口的时候仍然不确定类型,创建对象的时候会确定。
    class ClassGeneric4<E> implements InGeneric1<E>{
        @Override
        public E getAge(){
            return null;
        }
    }
    
    
    
    //泛型类的继承
    public class ClassGeneric01<T> {}
    
    //与上一个类没有任何关系,只是两个类而已,T只是一个标记
    class ClassGeneric02<T> {
        T t;
    }
    
    class ClassGeneric03<T> extends ClassGeneric12<T> {}
    
    //报错
    //class ClassGeneric04<E> extends ClassGeneric12<T> {}
    /*
    报错原因:
    	在类的声明中,我们只声明了一个<E>是一个确定的类型,代表任何引用类型,而在后边,对<>内容的声明已经完毕了,编译器无法识别。
    	编译器不会识别<T>是什么,只能识别到声明里<E>是一种固定类型,或者说是某种自定义类型,此时对于编译器来说,<T>是一种语法错误。
    */

个人的一些理解:

  1. 对于类中声明的<>,<>内可以是任意内容,它的有效范围只有该类的{}范围加上声明时尖括号后边的继承语句或实现语句,超出这个范围需要重新声明。
  2. 同理,对于方法体中声明的<>,<>内可以是任意内容,它的有效范围只有该方法的{}内,否则编译器仍然会作为语法错误而编译异常。
  3. 可以理解为,声明的位置,只有固定的位置,只有这么一个位置可以告诉编译器,<>里的某类型是一个我们自己定义的类型的代表,编译器就可以识别。
2.4.泛型擦除
  • 对于这样一种情形:定义一个泛型类,创建该类的不同类型的对象,例如一个是String类型,另一个是Integer类型,调用两个线程分别控制它们运行,这个代码依旧编译成一份,所有带泛型的代码, 在编译完成之后, 泛型完全就不存在了, 全部替换成Object,底层会自动进行类型强转。泛型仅存在于编译之前。
2.5.泛型通配符
2.5.1.背景及使用方法
  • ?代表任意的数据类型
  • 泛型没有集成概念,不能创建对象使用,只能作为参数传递
2.5.2.高级使用
  • 泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
  • 泛型的下限限定:? super E 代表使用的泛型只能是E类型的父类/本身
//已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类,Object是根类
    public static void main(String[] args) {
        Collection<Integer> list1 = new ArrayList<Integer>();
        Collection<String> list2 = new ArrayList<String>();
        Collection<Number> list3 = new ArrayList<Number>();
        Collection<Object> list4 = new ArrayList<Object>();
        
        getElement1(list1);
        getElement1(list2);//报错
        getElement1(list3);
        getElement1(list4);//报错
        
        getElement2(list1);//报错
        getElement2(list2);//报错
        getElement2(list3);
        getElement2(list4);
    }
    
    // 泛型的上限:此时的泛型<>,必须是Number类型或者Number类型的子类
    public static void getElement1(Collection<? extends Number> coll){}
    
    // 泛型的下限:此时的泛型<>,必须是Number类型或者Number类型的父类
    public static void getElement2(Collection<? super Number> coll){}
2.6.泛型协变
2.6.1.数组协变
  • Java对于数组进行了很多特殊处理,比如foreach循环,其底层其实是fori循环,因为其并没有实现Iterator接口,依旧能进行foreach循环。
  • 关于数组的协变处理,例子如下:
Integer[] arrs = new Integer[10];
Object[] objs = new Object[10];
objs = arrs;  //可以指向

objs[0] = 1;  //可以存
objs[1] = "1";  //类型不匹配,报错
  • 对于多态,应该是父类的引用指向子类,但是,对于数组的引用,数组并不存在父子关系,因此并没有多态的情形。
  • 对于数组,JVM单独处理,数组的类型Integer是Object的子类,所以可以指向,称之为协变。
2.6.2.集合协变
  • 对于集合类型,JVM并没有像数组那样进行特殊处理,所以并不存在一般情形下的协变。
List<String> listStr = new ArrayList<>();
List<Object> listObj = new ArrayList<>();

// Java中的集合类: 不允许协变
listObj = listStr;  //报错
  • 特殊情形下的协变:通配符
//泛型通配符<?> 可以代指任意类型(除了基本类型)
//为了模拟协变 , Java提供了泛型的通配
//个人理解,?这个通配符,可以理解为植物大战僵尸里的模仿者,指向的引用是什么类型,就化身为什么类型
List<String> arrayList = new ArrayList<String>();
List<?> listObj1 = arrayList;  //可以指向

List<Object> listObjs = new ArrayList<>();
List<?> listObj2 = listObjs;  //可以指向

//为了解决数组协变的存储元素的问题,所以直接暴力解决,不允许添加元素
//即使底层的数据存储类型一样,也不能存储,如,listObj1指向的类型是String,也不允许存储String类型的元素
listObj1.add("1");  //不可以,报错
listObj2.add(new Object());  //不可以,报错

//? extends E
public class DemoGeneric {
    public static void main(String[] args) {
        //? extends E  :  可以通配E类型或者E类型的子类
        //向下限定,E及其子类
        List<B> list = new ArrayList<>();
        List<A> list1 = new ArrayList<>();
        List<Object> list2 = new ArrayList<>();

        List<? extends A> listAll = list;
        listAll = list1;
        // listAll = list2;  //报错,因为Object是A的父类

        // 因为编程人员可能并不确定底层到底是A还是A的哪一个子类,所以暴力解决,全部不允许存储,报错
        // listAll.add(new A());
        // listAll.add(new C());
        // listAll.add(new Object());
    }
}
class A{}
class B extends A{}
class C extends A{}


//? super E
public class DemoGeneric {
    public static void main(String[] args) {
        // ? super E : 仅能代指E类型, 或者E类型的父类型: 代表可以存储E及E的子类型
        // 向上限定,E及其父类

        // 这个List: 里面存储的是F类型的数据
        List<F> list1 = new ArrayList<>();
        List<? super F> listObj = list1;

        // 可以存储F的对象,所以F的子类也必定可以存储,这个是必定的,因为list1按子父类继承关系,最低也是F类型
        // 对于? super E这种通配符,可以指向E或E的父类型,但是只能存储E或E的子类型。
        listObj.add(new F());
        listObj.add(new S1());
        listObj.add(new S2());

        //错误
//        List<S1> list1 = new ArrayList<>();
//        List<? super F> listObj = list1;

    }
}
class F{}
class S1 extends  F{}
class S2 extends  F{}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181079.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!