飞道的博客

JAVA---泛型

330人阅读  评论(0)

一、什么是泛型

        Java的泛型 (generics) 是在JDK5中推出的新概念,在泛型推出之前,程序员需要构建一个元素为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则会发生ClassCastException (类转换异常)

        而泛型提供了编译时类型安全监测机制,允许我们在编译时检测到非法的类型数据结构,它的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数 (形参),

        以ArrayList举例子来说,通过查看ArrayList源码可以看到,ArrayList中可以存放任意的类型是因为有一个泛型<E>,当new一个ArrayList并在泛型中放任意的类型之后此时这个ArrayList就只能存泛型中存放的这个类型的对象,这就是泛型的作用

        泛型的好处就在于可以让类型的存放更安全并且可以避免强制类型的转换不会报ClassCastException


二、泛型类

1、语法定义

class 类名<泛型标识, 泛型标识, ...>{

    private 泛型标识 变量名;

    .....

}

使用语法

类名<具体数据类型> 对象名 = new 类名<具体数据类型>();

java1.7之后,后面的<>中的具体的数据类型可以省略不写

类名<具体的数据类型> 对象名 = new 类名<>();

注意事项:

泛型类,如果没有指定具体的数据类型,此时操作类型是Object

 <具体数据类型> 泛型中只能存放包装类

 泛型类在逻辑上可以看成是多个不同的类型,但实际上都是相同类型

例:


  
  1. public class Test1<T> {
  2. private T key;
  3. public T getKey () {
  4. return key;
  5. }
  6. public void setKey (T key) {
  7. this.key = key;
  8. }
  9. }


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. Test1<String> test1 = new Test1<>();
  4. test1.setKey( "abc");
  5. System.out.println( "test1存的key为:" + test1.getKey());
  6. System.out.println( "test1的类型为:" + test1.getClass().getSimpleName());
  7. System.out.println( "test1存的key的类型为:" + test1.getKey().getClass().getSimpleName());
  8. }
  9. }

同一泛型类,根据不同的数据类型创建的对象,本质上是同一类型


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. Test1<String> test1 = new Test1<>();
  4. test1.setKey( "abc");
  5. System.out.println( "test1的类型为:" + test1.getClass());
  6. Test1<Integer> test2 = new Test1<>();
  7. test2.setKey( 100);
  8. System.out.println( "test2的类型为:" + test2.getClass());
  9. System.out.println(test1.getClass() == test2.getClass());
  10. }
  11. }

多个泛型


  
  1. public class Test1<T,V> {
  2. private T key;
  3. private V value;
  4. public T getKey () {
  5. return key;
  6. }
  7. public void setKey (T key) {
  8. this.key = key;
  9. }
  10. public V getValue () {
  11. return value;
  12. }
  13. public void setValue (V value) {
  14. this.value = value;
  15. }
  16. }


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. Test1<String,Integer> test1 = new Test1<>();
  4. test1.setKey( "number");
  5. test1.setValue( 100);
  6. System.out.println(test1.getKey() + ":" + test1.getValue());
  7. }
  8. }

2、常用的泛型标识: T、E、K、V、?

T (type) 表示具体的一个java类型   常用在定义单个对象或者单个方法时

E (element) 代表元素   常在集合中使用

K V  分别代表java键值中的Key和Value   常用在类似于map的集合中

?表示不确定的 java 类型   常在上下限中使用


三、泛型类派生子类

1、语法定义

子类也是泛型类,子类和父类的泛型类型要一致

 class Children<T> extends Father<T>

子类不是泛型类,父类要明确泛型的数据类型

class Children extends Father<具体类型>

2、使用场景

 

 可以看出使用instanceof之后调用的是children的属性

 要是父类.属性调用的就是父类的

 


四、泛型接口

1、语法定义

interface 接口名<泛型标识,泛型标识, ...>{

   泛型标识 方法名();

   .....

}

2、泛型接口的使用

实现类不是泛型类,接口要明确数据类型

实现类也是泛型类,实现类和接口的泛型类型要一致

例:


  
  1. public interface InMethod<T> {
  2. T getKey (T t);
  3. }

  
  1. //如果此时实现接口后泛型中不指定具体类型那么重新接口的方法和派生子类一样是Object
  2. public class claMethod implements InMethod{
  3. @Override
  4. public Object getKey (Object o) {
  5. return null;
  6. }
  7. }


  
  1. //如果此时实现接口后泛型中指定具体的类型那么重写接口方法之后返回的就是定义的类型
  2. public class claMethod implements InMethod<Integer>{
  3. @Override
  4. public Integer getKey (Integer integer) {
  5. return null;
  6. }
  7. }

实现接口之后重写一下接口中的方法


  
  1. public class ClaMethod implements InMethod<Integer>{
  2. @Override
  3. public Integer getKey (Integer integer) {
  4. return integer;
  5. }
  6. }

  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. ClaMethod claMethod = new ClaMethod();
  4. Integer num = claMethod.getKey( 100);
  5. System.out.println(num.getClass().getSimpleName());
  6. }
  7. }

类实现接口并使用了接口中的方法后因为类没有定义泛型所以使用的类型是接口的类型


  
  1. //接口中的泛型是实现类中泛型的其中一个就可以
  2. public class ClaMethod<T,K,E> implements InMethod<K>{
  3. @Override
  4. public K getKey (K k) {
  5. return null;
  6. }
  7. }


五、泛型方法

1、语法定义

修饰符 <T, E, ...> 返回值类型 方法名(形参列表) {

        方法体 ...

}

【注意】

·   public 与 返回值中间<T> 非常重要,可以理解为声明此方法为泛型方法。

·  只有声明了<T>的方法才是泛型方法,泛型类中 使用了泛型的成员方法并不是泛型方法

·  <T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T

·  与泛型类的定义一样,此处T可以随便写为任意标识,如 T、E、K、V等形式的参数常用于表示     泛型

例:


  
  1. public class ClaMethod<T,K,E>{
  2. public <T> T getGeneric (T t){
  3. return t;
  4. }
  5. }

  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. ClaMethod claMethod = new ClaMethod();
  4. String str = "abc";
  5. System.out.println(claMethod.getGeneric(str).getClass().getSimpleName());
  6. }
  7. }


  
  1. //要是调用的不是void方法 可以传多个类型 return的和上边返回的类型要一致
  2. public <T,K> K GG (K k){
  3. return k;
  4. }

2、静态的泛型方法


  
  1. public class ClaMethod<T,K,E>{
  2. //静态的泛型方法采用多个泛型类型
  3. public static <T,K,E> void getGenericType (T t,K k){
  4. System.out.println(t+ "\t"+t.getClass().getSimpleName());
  5. System.out.println(k+ "\t"+k.getClass().getSimpleName());
  6. }
  7. }

  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. //static使用类名.方法名
  4. ClaMethod.getGenericType( "abc", 100);
  5. }
  6. }

 3、泛型方法与可变参数

public <E> void print(E... e){

  for(E date : e){

     System.out.println(date);

  }

}


  
  1. public class ClaMethod{
  2. public<E> void printE (E... e){
  3. for (E data : e) {
  4. System.out.println(data);
  5. }
  6. }
  7. }

  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. ClaMethod claMethod = new ClaMethod();
  4. claMethod.printE( "a", "b", "c");
  5. }
  6. }

4、泛型方法和泛型类的区别

泛型类,是在实例化类的时候指明泛型的具体类型

泛型方法,是在调用方法的时候指明泛型的具体类型

5、泛型方法总结

泛型方法能使方法独立于类而产生变化

泛型可以使用多个,要是没有void 就return其中一个泛型,要是有void那么就正常输出就好

静态的泛型方法和普通泛型方法的区别是 静态的泛型方法是在泛型前多加一个static


六、类型通配符

1、定义

类型通配符一般是使用 "?" 代替具体的类型实参,所以,类型通配符是类型实参,而不是类型形参

一般和上下限进行配合使用

2、示例


  
  1. public class ListMethod {
  2. private String listKey;
  3. public ListMethod (String listKey) {
  4. this.listKey = listKey;
  5. }
  6. @Override
  7. public String toString () {
  8. return "ListMethod{" +
  9. "listKey='" + listKey + '\'' +
  10. '}';
  11. }
  12. }


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. ListMethod listMethod1 = new ListMethod( "a");
  4. ListMethod listMethod2 = new ListMethod( "b");
  5. ListMethod listMethod3 = new ListMethod( "c");
  6. ArrayList<ListMethod> list = new ArrayList<>();
  7. list.add(listMethod1);
  8. list.add(listMethod2);
  9. list.add(listMethod3);
  10. MainMethod.getMethod(list);
  11. }
  12. public static void getMethod (List<?> list){
  13. for ( int i = 0; i < list.size(); i++) {
  14. System.out.println(list.get(i));
  15. }
  16. }
  17. }

3、类型通配符的上限

语法

类/接口 <? extends 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的子类类型

首先我们定义一下继承关系 如图所示 

MaxCat类MiniCat类 分别继承了 Cat类 Cat类继承了Animal类 

上限代表的意思是  能填写的类的级别最高就到extends后边的那个实体类 (包括本身)


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. ArrayList<Animal> animalList = new ArrayList<>();
  4. ArrayList<Cat> catList = new ArrayList<>();
  5. ArrayList<MaxCat> maxList = new ArrayList<>();
  6. ArrayList<MiniCat> miniCatList = new ArrayList<>();
  7. //上限 -> 只能添加继承后边的实参对象的子类
  8. // showExtendsList(animalList);
  9. showExtendsList(catList);
  10. showExtendsList(maxList);
  11. showExtendsList(miniCatList);
  12. }
  13. public static void showExtendsList (ArrayList<? extends Cat> list){
  14. for ( int i = 0; i < list.size(); i++) {
  15. System.out.println(list.get(i));
  16. }
  17. }
  18. }

【重要】

类/接口 <? extends 实参类型> 这个东西叫泛型列表 在上限中泛型列表里边是不允许添加其他元素的

因为现在有这么一种情况,我们都知道java中要是创建一个类的话要从它的父类开始创建,一直到当前类,

以上图例子进行说明,那么假如现在我调用showExtendsList方法 在该方法中进行添加元素,main方法中我们可以看到,上限因为可以存的是Cat的子类,现在有这么一种情况在MaxCat和MiniCat下分别又有四个子类 BlackMaxCat   YellowMiniCat   BlackMiniCat   YellowMiniCat   如下图

现在四个类中都分别有一个各自类的方法依次为 A B C D

 那么当我添加BlackMaxCat的时候 万一泛型列表中存放的是其他的 YellowMaxCat这三个类 那么创建对象的时候只能创建父类,方法找不到就会报错,说白了我们无法确定最小的类是什么,为了安全考虑在上限的泛型列表中不允许添加对象

4、类型通配符的下限

语法

类/接口<? super 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的父类类型

继承关系如图所示

下限代表的意思是  能填写的类的级别最低就到super后边的那个实体类 (包括本身)


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. ArrayList<Animal> animalList = new ArrayList<>();
  4. ArrayList<Cat> catList = new ArrayList<>();
  5. ArrayList<MaxCat> maxList = new ArrayList<>();
  6. ArrayList<MiniCat> miniCatList = new ArrayList<>();
  7. //下限
  8. showSuperList(animalList);
  9. showSuperList(catList);
  10. // showSuperList(maxList);
  11. // showSuperList(miniCatList);
  12. }
  13. public static void showSuperList (ArrayList<? super Cat> list){
  14. // list.add(new Animal());
  15. list.add( new Cat());
  16. list.add( new MiniCat());
  17. list.add( new MiniCat());
  18. for (Object superList: list) {
  19. System.out.println(superList);
  20. }
  21. }
  22. }

【重要】

类/接口 <? super 实参类型> 这个东西叫泛型列表 在下限中泛型列表里边允许添加super 后边实体类的子类

因为我们知道我们添加的类都是super 后边这个类的父类,我在泛型列表里边添加子类 new对象的时候父类也会被创建出来 不会受到影响,不能存父类是因为不能排除还有其他类和当前类都继承了同一个父类的情况,存父类的时候 因为下边的子类无法创建了所以又会报错造成不安全

【总结】

对于泛型列表可以把类的继承关系想象成一个树结构

上限可以存元素存的是extends 后边类的子类 但上限的泛型列表不能存元素

下限可以存元素存的是 super 后边类的父类   下限列表可以存元素存的是 super后边类的子类


七、类型擦除

        泛型是java1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好的和之前版本的代码兼容。那是因为,泛型信息之存在于代码编译阶段,在进JVM之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除

1、类的类型擦除

无限制类型擦除

没有限制时 最后jvm运行时识别的都是Object

有限制类型擦除

有限制时,jvm运行时上限识别的是限制的类型  下限识别的是Object

2、方法的类型擦除

方法的类型擦除,jvm运行时上限识别的是限制的类型  下限识别的是Object

3、接口的类型擦除

【注意】

【桥接方法】

实现了泛型接口的方法在类型擦除之后会生成两个方法 一个是生成限制类型的方法一个是桥接方法桥接方法指的是接口中的方法进行类型擦除之后生成的Object方法要在实现类的方法中进行一个对应,换句话说,类型擦除后 接口中的方法是Object方法 实现类中的方法一个是限制的另一个还需要一个与接口的Object方法相对应的 返回值的类型是Object类型,但return的类型是限制类的类型  


八、泛型数组

1、泛型数组的创建

可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象

可以通过java.lang.reflect.Array的newInstance(Class<T> ,int) 创建T[]数组

例:

这么写是错误的 ,正确的写法如下图

或者可以是如下写法

区别在于没有把原生对象暴露出来而是采用了直接将匿名对象赋给泛型数组

【注意】new ArrayList 后边没有<> 证明这是ArrayList类型的数组而不是ArrayList集合数组


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. //错误
  4. ArrayList<String>[] arrayList = new ArrayList<String>[ 5];
  5. //正确
  6. ArrayList<String>[] arrayList1 = new ArrayList[ 5];
  7. }
  8. }

2、对象中创建泛型数组


  
  1. public class StrClass<T> {
  2. private T[] array = new T[ 5];
  3. }

这种方式是错误的因为我们不知道传的类型是什么类型,万一如上边一样传一个ArrayList<String>那么一定会报错的

正确的方式如下

使用java中Array类的newInstance方法利用反射创建一个对象放在构造器当中从而创建一个数组


  
  1. public class StrClass<T> {
  2. private T[] array;
  3. public StrClass (Class<T> strClass,int length){
  4. array = (T[])Array.newInstance(strClass,length);
  5. }
  6. public void setArray (T[] array) {
  7. this.array = array;
  8. }
  9. public T[] getArray() {
  10. return array;
  11. }
  12. }


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. StrClass<String> strClass = new StrClass<>(String.class, 10);
  4. String[] str = { "a", "b", "c"};
  5. strClass.setArray(str);
  6. //调用Arrays.toString方法输出setArray的值
  7. System.out.println(Arrays.toString(strClass.getArray()));
  8. }
  9. }


九、反射常用的泛型类

Class<T>

Constructor<T>


  
  1. public class MainMethod {
  2. public static void main (String[] args) {
  3. Class<String> stringClass = String.class;
  4. try {
  5. Constructor<String> constructor = stringClass.getConstructor();
  6. } catch (NoSuchMethodException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. }


转载:https://blog.csdn.net/weixin_56777219/article/details/128623865
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场