Java中泛型总结(全面)

参考资料:

书:Java编程思想

博客:
https://blog.csdn.net/wang__qin/article/details/81415223
https://blog.csdn.net/harvic880925/article/details/49883589

https://blog.csdn.net/qq_27093465/article/details/73229016

https://blog.csdn.net/harvic880925/article/details/49872903

https://blog.csdn.net/harvic880925/article/details/49883589

目录

                                   Java泛型学习总结(全面)

1.    泛型的清单及概括

1)    泛型的知识清单

A.    泛型定义位置:接口、类、方法

B.    泛型通配符?:是一个实参类型,不是形参。

C.    泛型的类型限定:extends、super

D.    泛型方法:传入Class clazz的作用?

2)    什么是泛型?为什么使用泛型?

3)    泛型类型字母规范

4)    定义泛型时:不限定个数,逗号隔开即可

5)    泛型示例及优点分析

A.    ArrayList、HashMap典型示例,v>

B.    泛型的优点

6)    泛型的擦除与补偿:泛型只在编译阶段有效

7)    注意几点:

2.    泛型通配符?

1)    引入:

2)    如何解决上面的问题?

3)    泛型通配符?

3.    泛型类型绑定

1)    简单示例

2)    泛型之类型绑定:extends绑定:限定类型范围,BoundingType

A.    示例:绑定接口

B.    示例:绑定类

C.    用&实现多个限定;同时定义多个泛型,逗号隔开即可

4.    通配符?及其extends和super的限定

1)    T与?的区别:为什么引入"?"

2)    通配符?的向下限定extends

A.    通过extends向下限定泛型类型(包括边界类型)

B.    注意:利用定义的变量,只可取其中的值,不可修改

3)    通配符?的super绑定

A.    

B.    super通配符实例内容:能存不能取

4)    泛型上限与下限的使用场景

5)    注意:

6)    集合示例:

7)    通配符?主要使用方式

5.    泛型接口(简单)

1)    泛型接口的定义

2)    泛型接口的使用

3)    示例

6.    泛型类(简单)

1)    泛型类的定义

2)    泛型类的示例

3)    问题:定义的泛型类,就一定要传入泛型类型实参么?

4)    实现泛型接口

7.    泛型方法

1)    泛型方法的简单示例

2)    泛型方法的语法

3)    示例:

A.    泛型方法判断

B.    泛型必须提前定义

C.    泛型类和泛型方法同时定义泛型T

D.    泛型可变参数

4)    静态泛型方法

5)    泛型数组T[]:不能直接创建泛型数组。

6)    Class的使用

7)    泛型方法总结

8.    Java中为什么不允许直接创建泛型数组

1)    父类类型数组可以接受子类类型数组

2)    Java中为什么不允许直接创建泛型数组? 

3)    Java中不能直接创建泛型数组,但是可以定义泛型引用

9.    最后问题总结

1)    T与?的区别

2)    泛型方法如何判断?



                                   Java泛型学习总结(全面)


1.    泛型的清单及概括

1)    泛型的知识清单

A.    泛型定义位置:接口、类、方法

       泛型可以定义在接口、类、方法上,因此分成三种:接口泛型、类泛型、方法泛型。
       在代码中,使用最多的肯定就是方法了,因此,在类或接口上定义泛型,很多情况下也是在方法上使用的,因此方法泛型至关重要。
      泛型类,是在实例化类的时候指明泛型的具体类型;
      泛型方法,是在调用方法的时候指明泛型的具体类型。

B.    泛型通配符?:是一个实参类型,不是形参。

为什么引入泛型通配符? ? 其解决了什么问题?

C.    泛型的类型限定:extends、super

extends和super为何引入?又分别解决了什么问题?
重点:熟练掌握泛型方法的使用。

D.    泛型方法:传入Class clazz的作用?

2)    什么是泛型?为什么使用泛型?

泛型:是JDK1.5 推出来的安全机制,是给编译器使用的技术,用于编译时期,保证了类型的安全。泛型在集合框架中使用最为广泛。(1.Java的每次升级都是为了三个目的:简化书写、提高效率、提高安全性。泛型的提出是为了提高安全性,基本数据类型的自动装箱和拆箱是为了简化书写。)
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
参数化类型:泛型的本质是参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

3)    泛型类型字母规范

任意一个大写字母都可以,意义是完全相同的。但为了提高可读性,大家还是用有意义的字母比较好,一般来讲,在不同的情境下使用的字母意义如下:
 E — Element,常用在java Collection里,如:List,Iterator,Set
 K,V — Key,Value,代表Map的键值对;
 N — Number,数字; T — Type,类型,如String,Integer等等;
如果这些还不够用,那就自己随便取吧,反正26个英文字母呢。
再重复一遍,使用哪个字母是没有特定意义的!只是为了提高可读性!!!!

4)    定义泛型时:不限定个数,逗号隔开即可

用逗号隔开,写上其它的任意大写字母即可。
public class Point{…}
public class Point{…}

5)    泛型示例及优点分析

A.    ArrayList、HashMap典型示例

ArrayList strList = new ArrayList();
public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
不采用泛型的情况下,List默认每个元素都是Object类型,这时候如果添加一个String类型,再获取的时候需要强转成String,类型转换在编译时不会检查,在运行时如果类型转换错误就会报错。因此引入泛型,可以避免了类型转换可能引起的错误。

B.    泛型的优点

get时不需要类型强制转换;若强制类型转换,很容易造成类转换异常:java.lang.ClassCastException;将可能出现的类转换异常提前到了编译时期,提高了安全性;同时,避免了类型的强制转换的麻烦。set时进行类型检查,若类型不一致,编译时就会报错。
可以这么说,泛型的引入就是为了避免出现类转换异常的。
在实际的编程过程中,使用泛型可以简化开发,且能很好的保证代码质量。
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

6)    泛型的擦除与补偿:泛型只在编译阶段有效

泛型的擦除:泛型只用于编译时,供编译器判断类型正确与否,而到了程序运行的时候,会将泛型擦除掉,编译时生成的class文件中是不带泛型的,这称为泛型的擦除。擦除的目的是:为了兼容旧版本的类加载器,否则类加载器还需要进行升级。
泛型的补偿:如果采用了泛型擦除后,对象仍然被提升为Object类型,还需要进行强制类型转换,为了省略掉此转换,引入了泛型的补偿,即在运行时,通过获取元素的类型完成类型的转换工作,不需要再进行强制类型转换了。 (利用Object的getClass()方法)。
Java泛型仅仅是在编译阶段有效,在编译之后,程序会采取去泛型话的措施。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
本质一句话:带有不同泛型同一类型逻辑上看成是多个不同类型,本质上是相同的类型。
如ArrayList、ArrayList编译阶段是不同类型,在运行时是相同的。

7)    注意几点:

A.    不能对确切的泛型类型使用instanceof操作;如下面是非法的

if(ex_num instanceof Generic){ }

B.    不能定义泛型数组。

C.    Integer是Number的子类,但是List不是List的子类。

D.    泛型<>可以传入引用数据类型,包括类、接口、数组,不能传入基本数据类型,但是可以传入基本数据类型数组,如int[]。


2.    泛型通配符?


1)    引入:

在Generic泛型类中,定义方法:
public void showKeyValue(Generic obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}
Generic gInteger = new Generic(10);
Generic gNumber = new Generic(10);
//gNumber = gInteger;//错误的
//showKeyValue(gInteger);//错误的
Integer是Number的子类,但是Generic不能看做是Generic的子类。

2)    如何解决上面的问题?

如何解决上面问题?总不能再定义一个新方法来处理Generic类,这显然与Java多态思想相违背。因此,实现一个方法能同时接收Generic和Generic的引用类型,由此类型通配符应运而生。
public void showKeyValue1(Generic obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}

3)    泛型通配符?

类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。


3.    泛型类型绑定


1)    简单示例

public class Point {
    private T x;
    private T y;
    public Point(T x, T y) {
        this.x = x;
        this.y = y;
    }
    getter setter…
}


A.    注意:任何的泛型变量都是派生自Object,所以在填充泛型变量时,只能使用派生自Object的类,比如String, Integer, Double,等而不能使用原始的变量类型,比如int,double,float等。
B.    问题:在泛型类Point内部,利用泛型定义的变量T x能调用哪些方法呢?
答案:只能调用Object所具有的方法,因为编译器根本不知道T具体是什么类型,只有在运行时,用户给什么类型,他才知道是什么类型。编译器唯一能确定的是,无论什么类型,都是派生自Object的,所以T肯定是Object的子类,所以T是可以调用Object的方法的。
C.    问题:问题又来了,如果我想写一个可以找到最小值的泛型类;由于不知道用户会传什么类型,所以要写一个接口,让用户实现这个接口来自已对比他所传递的类型的大小。因此引入extends关键字。

2)    泛型之类型绑定:extends绑定:限定类型范围,BoundingType

注意://T只能extends限定不能super限定
有时候,你会希望泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。

此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的”extends“表示的子类型,不等同于继承。
泛型中的extends不是类继承里的那个extends! extends:这里可以翻译为绑定。两个根本没有任何关联。在这里extends后的BoundingType可以是类,也可以是接口,意思是说,T是在BoundingType基础上创建的,具有BoundingType的功能。目测是JAVA的开发人员不想再引入一个关键字,所以用已有的extends来代替而已。

A.    示例:绑定接口

public class Point2 {
    private T x;
    private T y;
    private boolean xBigy;
    public Point2(T x, T y) {
        this.x = x;
        this.y = y;
        this.xBigy = x.compareTo(y) > 0;
    }
…
}


由于T类型是Comparable的子类型,因此T类型具有compareTo方法。
创建Point2实例:传入的T类型必须是实现了Comparable接口。
Point2 point21 = new Point2(new Integer(11),new Integer(2));
System.out.println(point21);
类型绑定有两个作用:
1、对填充的泛型加以限定;
2、使用泛型变量T时,可以使用extends后面的类型内部的方法。

B.    示例:绑定类

父类类型Fruit:
public class Fruit {
    private String name;
    public Fruit(String name) {
        this.name = name;
    }
}
子类类型Apple:
public class Apple extends Fruit {
    public Apple(String name) {
        super(name);
    }
}
泛型绑定类的使用:
public class PrintFuritName {
    private F fruit;
    public PrintFuritName(F fruit) {
        this.fruit = fruit;
    }
    public void printFruitName(){
        System.out.println(fruit.getName());
    }
}


通过构造方法传入F对象,F绑定了是Fruit类型,因此可以通过F实例调用Fruit中的所有方法。
调用:
//java泛型之类型绑定:绑定类
PrintFuritName applePrint = new PrintFuritName(new Apple("APPLE"));
applePrint.printFruitName();

C.    用&实现多个限定;同时定义多个泛型,逗号隔开即可

可以同时多个限定,用&连接;定义多个泛型,用逗号隔开。
public class Lunch {
    private F f;
    private V v;//有机蔬菜
    public Lunch(F f, V v) {
        this.f = f;
        this.v = v;
    }
public class Tomato extends Vegetable implements Organic 
public class Apple extends Fruit implements Organic 
其中Apple和Tomato都必须实现Organic接口。


4.    通配符?及其extends和super的限定


1)    T与?的区别:为什么引入"?"

在定义泛型的时候,需要用T、K、V等字符表示类型;"?"是在创建对象时通配T的。
也就是说,T、K、V等字符只能在类、接口、方法上用来定义声明泛型,不能用于创建变量;而"?"用来填充泛型T的,表示通配任何类型。
简单说:T只是当做形参的作用,而?是实参,是填充T的。
    一种错觉:List引用可以指向任何类型泛型,其实不是,List中只能接收List类型的对象,如无法接收List。但是List就可以接收任何类型泛型的对象了。(这就相当于泛型的继承一样,List变量不能接收List)
其实,T就像是个类型标志,可以用某一个具体的类型替换T,也可以用"?"通配很多类型。
Point integerPoint = new Point(1,2);
Point point;
point = new Point(1,1);
point = new Point(1.1,1.1);
point = new Point("1","1");
point = new Point(new Object(),new Object());
    Point 变量与Point变量的区别:前者只能接收Point实例对象,而Point可以接收任意类型泛型的对象,如Point、Point等等。
注意: ?只是用于填充泛型T,和具体类型T区别是?可以接收任何泛型类型的实例。更通俗一些,?和Integer、String等一样用于填充泛型T,但是其表示任何类型都可以。如Point、Point、Point
?注意:?只能用于声明变量,不能用于创建对象,即只能位于前面,不能放在new 后面。
      Point point2 = new Point(1,1);
//        Point point23= new Point(1,1);//错误的

2)    通配符?的向下限定extends

A.    通过extends向下限定泛型类型(包括边界类型)

如Point只有数值才有意义,因此:
Point pointNum0 = new Point(1,1);
Point pointNum1 = new Point(1,1);
Point pointNum2 = new Point(1.1,1.1);
Number就是边界类型,显然是可以的,这里String就不可以。
注意:通配符?只是泛型T的填充方式,给?加上限定,只是限定了赋值给它的实例类型;如果想从根本上解决乱填充Point的问题,需要从Point泛型类定义时加上
class Point {
    private T x;       // 表示X坐标
private T y;       // 表示Y坐标

B.    注意:利用定义的变量,只可取其中的值,不可修改

对?设定了泛型上限,因此获取的时候,肯定是边界类型或者其子类型,用边界类型或者父类父接口接收都是可以的。但是在set的时候,无法知道泛型类型具体是什么,也就无法set操作。
Point point11 = new Point(1, 1);
        Number x = point11.getX();
        Object x2 = point11.getY();
//        point11.setX(1);//错误的
解释:point11的类型永远都是Point,不会因为后面的Point发生改变;point11可以指向Point也可以指向Point,可以指向多种泛型类型实例。
为什么只能获取不能赋值?
因为是上限绑定,所以泛型最高就是边界类型,用边界类型及父类接口就可以接收。示例:取值时,由于泛型被填充为,所以编译器能确定的是泛型是Number的子类,编译器就会用Number来填充泛型。
正因为point的类型为 Point point,那也就是说,填充Point的泛型变量T的为,这是一个什么类型?未知类型!!!怎么可能能用一个未知类型来设置内部值!这完全是不合理的。
也就是说,编译器,只要能确定通配符类型,就会允许,如果无法确定通配符的类型,就会报错。

3)    通配符?的super绑定

A.    

指填充为派生于XXX的任意子类;相对应,则表示填充为任意XXX的父类。
示例:三个类:,Employee,Manager,CEO,分别代表工人,管理者,CEO;
//        CEO extends Manager extends Employee
        AssignWork manager = new AssignWork();
        manager = new AssignWork();
//        manager = new AssignWork();//不可以

B.    super通配符实例内容:能存不能取

extends通配符,能取不能存; super通配符,能存不能取。
        List list;
        list = new ArrayList();
//        list.add(new Employee()); //编译错误
        list.add(new Manager());
        list.add(new CEO());
解释:List说明List的元素类型最低为Manager,由于不知道具体是啥,因此只能认为元素变量为Manager,因此只要实例是Manager及其子类实例,都是可以add的,(CEO extends Manager extends Employee),Manager父类实例不可以。
由于任何类型都是继承自Object,所以可以且仅仅可以用Object obj接收:
        Object obj = list.get(0);
//        Manager m = list.get(0);//错误
虽然看起来是能取的,但取出来一个Object类型,是毫无意义的。所以我们认为super通配符:能存不能取。

4)    泛型上限与下限的使用场景

(1)    泛型的上限:使用较多。
: 只可以接收E类型对象;
: 可以接收E类型及其子类对象。上限
一般在集合存储元素的时候,都使用上限,因为这样就可以用上限类型E进行接收,不会出现类型安全隐患。
其实: 通配符? 就相当于 ? extends Object
(2)    泛型的下限:
:可以接收E类型及其父类型对象。下限。
通常是在对集合中元素进行取出操作时,会使用到泛型的下限。

5)    注意:

//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界,即在泛型声明的时候添加
//public T showKeyName(Generic container),编译器会报错:"Unexpected bound"
public T showKeyName(Generic container){}
泛型的上下边界添加,必须与泛型的声明在一起 。

6)    集合示例:

A.    接口Collection中的addAll方法
 
一个存放E类型对象的集合可以添加另外一个存放E类型或者其子类型的对象的集合。如:
ArrayList al1 = new ArrayList();
     ArrayList al2 = new ArrayList();
     al1.addAll(al2);//Student是Person的子类
B.    Map接口中的putAll方法
 
TreeSet的构造方法:
 
C.    TreeSet的构造方法:

 
由于比较器需要从TreeSet集合中取出对象元素进行比较,对象元素可以利用本对象类型引用或者父类型引用进行接收,所以可以利用指定本对象的类型的比较器或者指定父类型的比较器进行排序。例如: TreeSet 集合可以利用Comparator比较器也可以利用Comparator比较器。
即TreeSet(new ComparatorByName)合法,TreeSet(new ComparatorByName)也合法。

TreeSet ts3 = new TreeSet(new Comparator() {
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());}});//是合理的
TreeSet ts = new TreeSet(new Comparator() {
    public int compare(Person o1, Person o2) {
        return o1.getName().compareTo(o2.getName());}});//是合理的
TreeSet ts2 = new TreeSet(new Comparator() {
            public int compare(Student o1, Student o2) {
                return o1.getName().compareTo(o2.getName());}});//是错误的

7)    通配符?主要使用方式

总结 通配符的特征,我们可以得出以下结论:
◆ 如果你想从一个数据类型里获取数据,使用 ? extends XXX通配符(能取不能存)
◆ 如果你想把对象写入一个数据结构里,使用 ? super XXX通配符(能存不能取)
◆ 如果你既想存,又想取,那就别用通配符?。
问题:如果不指定泛型,那怎么泛型默认填充什么?
构造泛型实例时,如果省略了填充类型,则默认填充为无边界通配符! 
因此下面的是等价的:
Point point22 = new Point(2,2);//默认?匹配
Point point221 = new Point("2","2");//默认?匹配
Point point23 = new Point<>(2,3);
Point point231 = new Point<>("2","3");
通配符?的主要使用方式
泛型通配符?只用于声明变量,不能用于创建对象;所以其用途只有:
List list; //与不指定泛型一样,等同与List;取为Object类型
List extendsList;//取元素可用Number接收
List superList;//可存Integer元素,取元素只能用Object接收
取出元素:Object o = list.get(0);
Number number = extendsList.get(0);
//list.set(0,new Object());//不能set,不接收Object
//extendsList.set(0,new Object());//不能set,不接收Object
superList.set(0,new Integer(10));
Object object = superList.get(0);
List list与List extendsList;都是只能取不能存。


5.    泛型接口(简单)


1)    泛型接口的定义

在接口定义上定义泛型,与泛型类定义方法相同。
public interface Car {
     T getName(T car);
}

2)    泛型接口的使用

A.    非泛型类:类中不定义泛型;
public class BentleyCar implements Car{
    @Override
    public String getName(String car) {
        return car;
    }
}
B.    泛型类:类继承泛型接口的泛型或定义更多泛型;
用泛型类保持泛型接口中的泛型的作用是让用户在创建泛型对象时定义接口中的泛型。
public class BWMCar implements Car {
    @Override
    public T getName(T car) {
        return car;
    }
}
或者:定义更多新的泛型。
public class LexusCar implements Car {
    @Override
    public T getName(T car) {
        return car;
    }
    private U u;
    private V v;
}

3)    示例

Collection、List、Set、Map等都是典型的泛型接口。

6.    泛型类(简单)

1)    泛型类的定义

泛型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

2)    泛型类的示例

public class Generic
    //key这个成员变量的类型为T,T的类型由外部指定  
private T key;
public Generic(T key) { 
        this.key = key;
}
}
定义类:
Generic genericString = new Generic("key_vlaue");
Generic genericInteger = new Generic(6);

3)    问题:定义的泛型类,就一定要传入泛型类型实参么?

并不是这样。在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
紧接着上面的示例:
Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);

4)    实现泛型接口

A.    当实现泛型接口的类,未传入泛型实参时:
与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
    * 即:class FruitGenerator implements Generator{
* 如果不声明泛型,如:class FruitGenerator implements Generator,编译器会报错:"Unknown class"。
定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator,但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
B.    当实现泛型接口的类,传入泛型实参时:
   在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
     * 即:Generator,public T next();中的的T都要替换成传入的String类型。
public class FruitGenerator implements Generator {}


7.    泛型方法


1)    泛型方法的简单示例

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。
public static T genericMethod(Class clazz) throws Exception {
    T instance = clazz.newInstance();
    return instance;
}
public static void main(String[] args) throws Exception {
    String string = genericMethod(String.class);
}

2)    泛型方法的语法

A.    泛型方法的声明:在访问修饰符和返回类型之间,必须利用声明此方法是泛型方法。
与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型;
B.    只有声明的方法才是泛型方法,方法体中使用了类中定义的泛型成员的方法不是泛型方法,因为那是类实例化时需要确定的具体类型,到方法上已经确定了。
判断是否是泛型方法,与其所在类是否是泛型类无关,只需要看返回值前是否有泛型参数列表
C.    泛型方法:可以是静态方法也可以是实例方法,泛型个数没有限制。
静态方法中不能使用泛型类中定义的泛型类型,静态方法使用泛型时,必须声明该泛型,如;字母可以与类上的泛型同字母,但是表示一种新的泛型。
public K showKeyName(Generic container){…}
D.    泛型也可以做可变参数:
public void printMsg( T... args){}
E.    声明该方法是泛型方法,且表明该方法可以使用泛型类型T;当类上已经定义泛型时,在方法参数或内部可以直接使用类泛型;
如ArrayList的add(E e)方法;
public class JavaGenericsMethod {
    /**在类上已经定义E泛型**/
    public ArrayList genericMethod2(E e1, E e2) {
        ArrayList list = new ArrayList<>();
        list.add(e1);
        list.add(e2);
        return list;
    }
    /**必须在返回类型前利用定义泛型V并声明该方法为泛型方法**/
    public ArrayList genericMethod3(V e1, V e2) {
        ArrayList list = new ArrayList<>();
        list.add(e1);
        list.add(e2);
        return list;
    }
}
F.    泛型方法调用
隐式调用:方法名前不加泛型类型;
显示调用:方法名前添加泛型类型;
JavaGenericsMethod gene = new JavaGenericsMethod<>();
ArrayList strings1 = gene.genericMethod2("AAA", "BBB");
ArrayList strings2 = gene.genericMethod2("AAA", "BBB");
示例:
public static T genericMethod(T t) {
    return t;
}
String str = JavaGenericsMethod.genericMethod("AAAA");
String str2 = JavaGenericsMethod.genericMethod("AAAA");

3)    示例:

A.    泛型方法判断

public class GenericTest {
private T key;
//该方法虽然使用了泛型,但是不是泛型方法。只是类的一个普通方法,其中的T是在类层次声明的泛型。
public T getKey(){
       return key;
}
}

B.    泛型必须提前定义

      public E setKey(E key){//错误的,因为E没有声明为泛型
         this.key = key;
}
下面就是泛型方法,在方法层面声明泛型E。
public E setKey(E key){//错误的,因为E没有声明为泛型
         this.key = key;
}

C.    泛型类和泛型方法同时定义泛型T

public class GenerateTest{
public void show(T t){
            System.out.println(t.toString());
        }
}
泛型方法中也声明一个T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。

D.    泛型可变参数

public void printMsg( T... args){
    for(T t : args){
        Log.d("泛型测试","t is " + t);
    }
}

4)    静态泛型方法

静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。静态方法不能使用泛型类定义上的泛型,因为静态方法不依赖于对象。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法。
示例:
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法),即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
应该为:public static void show(T t){}

5)    泛型数组T[]:不能直接创建泛型数组。

Sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组”的。
确切类型不可以,通配符可以。
List[] ls = new ArrayList[10];//不可以
List[] ls = new ArrayList[10];//可以
List[] ls = new ArrayList[10];//不指定泛型也可以
至于为什么不可以,Sun的一篇文档说明这个问题:
List[] lsa = new List[10]; // Not really allowed.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List li = new ArrayList();    
li.add(new Integer(3));    
oa[1] = li; // Unsound, but passes run time store check    
String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。

而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。

下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
List[] lsa = new List[10]; // OK, array of unbounded wildcard type.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List li = new ArrayList();    
li.add(new Integer(3));    
oa[1] = li; // Correct.    
Integer i = (Integer) lsa[1].get(0); // OK

6)    Class的使用

泛型方法中,对于泛型T不能这样使用:
new T();//因为无法确定传入的T类型有空参的构造方法;
T.class;T.getClass();//这都是不可以的。
    如果泛型方法中需要创建泛型实例或者获取泛型的Class对象,只能通过添加Class clazz参数,调用时传入具体的泛型类型。
如JSON的parseArray方法:
public static List parseArray(String text, Class clazz) {…}
 注意:Class clazz1与Class clazz2的区别:
Class类本身就是一个泛型类,public final class Class
前者没有指定泛型,等同于Object的class对象,而后者是对应类型T的class对象。
调用newInstance时:  Object obj = clazz1.newInstance();
T t = clazz2.newInstance();

7)    泛型方法总结

泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:
无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

8.    Java中为什么不允许直接创建泛型数组


https://blog.csdn.net/x_iya/article/details/79550667

1)    父类类型数组可以接受子类类型数组

Object[] objs = new Object[5];
String[] strs = new String[5];
objs = strs;
objs[0] = new User();//编译时不报错,运行时报错ArrayStoreException

2)    Java中为什么不允许直接创建泛型数组? 

《Effective Java》第五章给出了一个解释:使用泛型的作用是使得程序在编译期可以检查出与类型相关的错误,但是如果使用了泛型数组,这种能力就会受到破坏。
父类数组可以接受子类数组,本身就会出现类型不一致的潜在错误;如果允许直接创建泛型数组,将会加大这种类型不一致的情况出现。
报出异常:
List[] strListArray = new ArrayList[5];//字符串List数组
Object[] objs2 = strListArray;
List intList = new ArrayList(5);//整型List数组
intList.add(99);
objs2[0] = intList;
String ss = strListArray[0].get(0);//ClassCastException;应该是Integer类型,但是用String接收
泛型的出现在很大程度上是为了避免运行时出现烦人的 ClassCastException,可是具有讽刺意味的是,泛型数组却又导致了 ClassCastException异常的出现。
为什么不让直接创建泛型数组了呢?
因为即使可以直接创建泛型数组,上面的类型转换异常错误在编译期仍然不能避免,因此就干脆直接不让new出泛型数组。(暂时的理解) 可以定义泛型数组引用。

3)    Java中不能直接创建泛型数组,但是可以定义泛型引用

//     List[] strListArray1 = new ArrayList[5];//错误
        List[] strListArray = new ArrayList[5];//字符串List数组
https://blog.csdn.net/qq_27093465/article/details/73229016
https://blog.csdn.net/harvic880925/article/details/49872903#
https://blog.csdn.net/harvic880925/article/details/49883589

9.    最后问题总结

1)    T与?的区别

T主要是用于声明泛型,表示具体的某个类型;?是泛型通配符,可以接收任意类型,只能做实参;List list等同于List list;但是,List或List是主要用途。

2)    泛型方法如何判断?

只要方法返回值前有泛型声明,就是泛型方法,否则不是;
泛型方法与所在类是否是泛型类无关;
静态泛型方法不能使用类上定义的泛型。
 

你可能感兴趣的:(Java中泛型总结(全面))