JDK5引入的一个新特性,泛型提供了编译时类型的安全检测机制,该机制可以在编译时检测到非法的数据类型;泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数(用List来举例说明下:)。
//比如List源码的定义是:
public interface List<E> extends Collection<E> {}
//这里的List,就是使用E来表示当前List实际的数据类型(泛型标识),但E只是一个参数,而具体的类型则是由创建 List 对象时明确定义(使用时需要将泛型标识替换为具体的类型),
//那么这里的E是一个参数且用来表示的是数据类型,类似的可以理解为方法的形参(只是这个形参表示的数据类型而非具体的参数值),
//创建 List 对象时,指定的具体类型就是E的实参(创建List时不指定类型,默认为Object),那么在操作这个List的元素时
//就会在编译期受到指定的具体类型的约束(编译期的类型安全检测机制)
泛型的优点:
1、类型安全
2、消除了强制类型转换
比如定义一个集合,如果集合不写具体的元素类型,那么Java会将元素类型当作Object处理,此时这个集合添加元素时由于是Object类型,因此可以将所有类型的元素都添加到里面;但从这个集合中读取元素操作时就不太方便处理了,因为读取出来的元素也是Object类型,如果想要进一步处理这些元素那就需要对这些元素进行类型转换,而转换类型比如强转就容易出现 java.lang.ClassCastException 异常(编译时没问题但运行时可能出现异常);而泛型可以对元素做一定的约束,这样可以在往集合中添加元素时编译期验证元素类型是否合法,防止用户在集合中加入不合法类型元素,这样通过约束添加的元素类型,从而简化后期对元素的处理(比如类型转换容易报错等)。
格式:
class 泛型类名称<泛型标识1,泛型标识2,···> {
泛型标识的方法、成员变量、构造器等等
}
常用的泛型标识:T、E、K、V(K和V一般用于键值对的泛型标记),其他的字母如A、B、C、a、b、c等等也可以用来做泛型标识,甚至是多个字母的组合也可以用于做泛型标识
定义泛型类:
package com.ceshi.jichu.generic;
/**
* @version 1.0
* @Description 自定义泛型类
* E、T只是泛型标识,具体的数据类型则是由创建泛型类对象时设置为具体的数据类型(不设置具体类型按照Object类型处理)
* 定义泛型标识之后,泛型标识可以作为类中方法、成员变量、构造器等的数据类型,但不能使用不在类上定义的泛型标识
*/
public class ConTest1<E,T> {
private E name;
private T id;
public ConTest1(){
}
public ConTest1(E name, T id) {
this.name = name;
this.id = id;
}
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
}
格式:
泛型类名称<具体的数据类型> 对象名 = new 泛型类名称<具体的数据类型>();
Java7以后,后面的<>中的具体的数据类型可以省略不写:
泛型类名称<具体的数据类型> 对象名 = new 泛型类名称<>();
注意事项:
1、使用泛型类时,如果没有指定具体的数据类型,此时数据类型是Object
2、使用泛型类时,具体的数据类型参数只能是类类型,不能是基本数据类型(也就是不能是八种基本数据类型比如:double、boolean、int会报错而String、Integer就可以;因为在运行时将类型参数替换为具体的数据类型之前将泛型标识当作是Object类型处理的,而八种基本数据类型不是类没有继承自Object类,所以不能使用八种基本数据类型作为泛型类的具体数据类型,但可以使用基本数据类型的包装类)
3、同一个泛型类创建的不同具体数据类型的对象在逻辑上可以看成多个不同的类型,但实际上都是相同类型(也就是对于不同具体数据类型创建的同一个泛型类的对象,通过getClass()方法获取的Class对象是同一个)
4、泛型类派生子类,子类也是泛型类,那么子类的泛型标识要和父类的泛型标识一致或对泛型标识进行扩展(也就是定义泛型子类时,子类和父类的泛型标识一样,但可以不和定义的父类泛型类标识一样;如:父类定义为:public class ConTest1
class Child<T> extends Father<T>{}
5、泛型类派生子类,如果子类不是泛型类,那么父类要明确数据类型不写父类的具体数据类型按照Object类型处理(也就是定义的子类不是泛型类但父类是泛型类,那么在定义子类时需要明确指定父类的具体数据类型,如:父类定义为:public class ConTest1
class Child extends Father<String>{}
使用泛型类:
/**
* 使用泛型类
* @param args
*/
public static void main(String[] args) {
//使用泛型类时将泛型标识替换为具体的数据类型
ConTest1<String,Integer> conTest11 = new ConTest1<>();
//方法参数类型及返回参数类型、成员变量、构造器都匹配具体的数据类型
conTest11.setId(1);
conTest11.setName("张三");
Integer id = conTest11.getId();
String name = conTest11.getName();
System.out.println("用户id:"+id);
System.out.println("用户名称:"+name);
ConTest1<Integer,String> conTest12 = new ConTest1<>(2,"李四");
System.out.println("用户2id:"+conTest12.getId()+" 用户2name:"+conTest12.getName());
}
格式:
interface 泛型接口名称<泛型标识1,泛型标识2,···>{
泛型标识的方法等等
}
泛型接口实现(和泛型类继承规则一致,即第一:实现类如果是泛型类则在定义实现类时要求实现类的泛型标识和泛型接口一致或扩展泛型标识;第二:实现类不是泛型类则需要在定义实现类时明确泛型接口的具体数据类型否则按照Object处理):
格式:
public class 泛型接口实现类名称<泛型标识1,泛型标识2,···> implements 泛型接口名称<泛型标识1,泛型标识2,···>{}
或
public class 泛型接口实现类名称 implements 泛型接口名称<具体数据类型1,具体数据类型2,···>{}
如:
泛型接口:
public interface ConInterface<T,E> {}
实现类也是泛型类:
public class ConImpl<A,B> implements ConInterface<A,B>{}
实现类不是泛型类:
public class ConImpl implements ConInterface<String,Integer>{}
优点:泛型方法能使方法独立于类而产生变化(泛型方法的具体数据类型可以和泛型类的具体数据类型无关、成员静态方法都可以,并且泛型方法可以定义在普通类、普通接口中,不是只能在泛型接口/类中才能定义泛型方法;当然泛型接口/类中也可以定义普通方法)
格式:
修饰符(如:public、static) <泛型标识1,泛型标识2,...> 返回值类型 方法名(形参列表){方法体...}
a、修饰符与返回值类型之间的泛型标识是泛型方法的标记即声明此方法为泛型方法。
b、只有声明了泛型标识的方法才是泛型方法,泛型类/接口中的使用了泛型类/接口定义的泛型标识的成员方法只是成员方法并不是泛型方法。
c、泛型标识表明该方法将使用的泛型类型,此时才可以在方法中使用标记的泛型类型/标识(如:方法体、形参列表、返回值类型等,这些地方也可以使用泛型方法没有定义但泛型类定义的泛型标识,那么这些参数数据类型需要和泛型类创建对象时定义的具体数据类型一致)。
d、与泛型类的定义一样,泛型标识可以随便写为任意标识,常见的如T、E、K、V等常用于表示泛型类型。
e、定义泛型方法写的泛型标识和泛型接口或泛型类的泛型标识无关,即使相同也是代表独立的数据类型(这里的前提是在泛型方法申明时定义了和泛型类/接口相同的泛型标识才会独立,如果没有申明那么使用时则和泛型类/接口的数据类型一致)
泛型方法可变参数申明:
修饰符(如:public、static) <泛型标识1,泛型标识2,...> 返回值类型 方法名(形参列表){方法体...}
形参列表和普通申明不同点:
普通形参:泛型标识 形参名称
可变形参:泛型标识... 形参名称
泛型接口及泛型方法案例:
定义泛型接口:
package com.ceshi.jichu.generic;
/**
* @version 1.0
* @Description 定义泛型接口
*/
public interface ConInterface<T,E> {
/**
* 成员方法
* @return
*/
public T getT();
/**
* 成员方法
* @return
*/
public E getE();
/**
* 泛型方法, 泛型标识,意味着该方法中(返回参数类型、形参、方法体)可以使用A这个泛型标识
* 泛型标识T 没有在泛型方法的泛型标识中定义,那么T所代表的具体数据类型和创建这个泛型接口实现类对象时定义的T代表的具体数据类型一致
* @return
*/
public <A> T getCount(A a,T t);
}
定义泛型接口实现:
package com.ceshi.jichu.generic;
/**
* @version 1.0
* @Description 泛型接口实现,如果实现类是泛型类那么泛型标识应当包含泛型接口的泛型标识,但可以不和定义泛型接口时泛型标记一致
*/
public class ConImpl<A,B> implements ConInterface<A,B>{
/**
* 可使用泛型类申明的泛型标识定义成员变量
*/
private A a;
private B b;
/**
* 构造器,也可以使用泛型类申明的泛型标识
* @param a
* @param b
*/
public ConImpl(A a,B b){
this.a = a;
this.b = b;
}
/**
* 成员方法,虽然该方法也用到了泛型标识 A ,但不是泛型方法,且使用的泛型标识只能是泛型类申明的泛型标识
* (如果是泛型接口定义的方法则和泛型接口申明的泛型标识一致)
* @return
*/
@Override
public A getT() {
return a;
}
/**
* 成员方法
* @return
*/
@Override
public B getE() {
return b;
}
/**
* 定义成员泛型方法,最主要的是需要有修饰符(public/static)和返回值类型(A)之间的<泛型标识>(这里是)
* 里面定义的泛型标识和泛型接口或泛型类定义的泛型标识无关即使两者的泛型标识一样,他们所代表的具体数据类型也没有关系
* 所以泛型标识可以不和泛型类定义的保持一致(如果是泛型接口定义的泛型方法可以不和泛型接口申明的泛型标识一致)
* 但泛型方法的形参及方法内部使用的泛型、返回值类型可使用的泛型标识必须是泛型类或泛型接口、泛型方法定义时申明的泛型标识
* 在使用泛型方法时根据方法的入参数据类型确定泛型方法的泛型标记表示的具体数据类型
* 如果泛型方法的形参及方法内部使用的泛型、返回值类型使用了泛型类或泛型接口定义的泛型标识且这个泛型标识没有在泛型方法的”<泛型标识>“中定义(这里的只定义了,没有定义A)
* 那么该泛型标识(A)的具体数据类型只能是创建这个泛型类对象时定义的(A)代表的具体数据类型,如果在泛型方法的”<泛型标识>“中有定义,那么此时和泛型类或泛型接口相同泛型标识所代表的具体数据类型就是独立的
* @param t
* @param a
* @param
* @return
*/
@Override
public <T> A getCount(T t,A a){
B b = this.b;
System.out.println("执行方法getCount打印b:"+b);
return a;
}
/**
* 定义静态泛型方法,泛型标识,由于这里定义了A和B泛型标识,虽然和泛型类申明的泛型标识一致,但这里代表的具体数据类型和泛型类上的A、B无关
* 形参中 C...c 表示可变参数(即可以通过,分割传入多个c参数)
* @param t
* @param e
* @param a
* @param b
* @param
* @param
* @param
* @param
* @return
*/
public static <T,E,A,B,C> A getTest(T t, E e, A a, B b, C... c){
System.out.println("静态方法t:"+t);
System.out.println("静态方法e:"+e);
System.out.println("静态方法a:"+a);
System.out.println("静态方法b:"+b);
for (int i = 0; i < c.length; i++) {
System.out.println("静态方法c"+i+":"+c[i]);
}
return a;
}
/**
* 定义成员泛型方法
* @param t
* @param e
* @param a
* @param b
* @param c
* @param
* @param
* @param
* @param
* @param
* @return
*/
public <T,E,A,B,C> A getTest1(T t, E e, A a, B b, C... c){
System.out.println("成员方法t:"+t);
System.out.println("成员方法e:"+e);
System.out.println("成员方法a:"+a);
System.out.println("成员方法b:"+b);
for (int i = 0; i < c.length; i++) {
System.out.println("成员方法c"+i+":"+c[i]);
}
return a;
}
}
测试:
package com.ceshi.jichu.generic;
/**
* @version 1.0
* @Description 泛型接口及泛型方法测试
*/
public class Test2 {
public static void main(String[] args) {
ConImpl<Integer,String> con = new ConImpl<>(1,"iphone");
System.out.println("调用成员方法getE:"+con.getE());
System.out.println("调用成员方法getT:"+con.getT());
//实参 2 是给形参 A a 赋值,A泛型标识未在泛型方法上申明,所以需要和创建的ConImpl泛型类对象con定义的具体数据类型一致即只能是Integer类型
Integer count = con.getCount(true, 2);
System.out.println("执行成员泛型方法getCount:"+count);
Boolean test = ConImpl.getTest(1, "xiaomi", true, 'x', 100, 101, 102, 103, 104);
System.out.println("执行静态泛型方法getTest:"+test);
//由于所有泛型标识都在泛型方法进行了申明,所以和con对象定义的具体数据类型无关
Boolean test1 = con.getTest1(1, "xiaomi", true, 'x', 100, 101, 102, 103, 104);
System.out.println("执行成员泛型方法getTest1:"+test1);
}
}
什么是类型通配符?
类型通配符一般是使用 “?” 代替具体参数的数据类型;所以类型通配符是参数类型的实参,而不是参数类型的形参(是参数类型的实参和形参而不是参数的实参和形参,也就是说定义方法形参时用到的泛型标记只是参数类型的标识可以认为是这个参数的参数类型的形参,而 ? 则是在定义方法时用来做泛型标记的替代,表示这个参数的数据类型接受不限制数据类型的入参);在定义方法时使用。
注意:类型通配符只能用于本来是泛型类/接口的参数做实参类型的匹配限制,如果参数本来不是泛型类/接口不能使用类型通配符(如:List、Set等等类原本的定义就是泛型类那么List、Set作为参数时就可以使用类型通配符;String、Integer等等这种本来定义就不是泛型类则不能使用类型通配符)
如:
//泛型类:
public class ConTest3<T> {
private T name;
public T getId() {
return name;
}
public void setId(T name) {
this.name = name;
}
}
//使用泛型类:
public class Test3 {
public static void main(String[] args) {
//分别以不同的具体数据类型测试泛型
ConTest3<Number> conTest3 = new ConTest3<>();
conTest3.setId(111);
test(conTest3);
ConTest3<Integer> conTest31 = new ConTest3<>();
conTest31.setId(1);
test(conTest31);
ConTest3<String> conTest32 = new ConTest3<>();
conTest32.setId("xnwucw");
test(conTest32);
}
/**
* 定义使用泛型的方法,此时的形参ConTest3> conTest3本来是必须明确指定具体的数据类型,如:ConTest3 conTest3
* 但这种方式则限制了这个泛型的实参只能是Number类型,即使Integer这种子类型的入参也是不行的会编译不通过,因为参数必须是ConTest3类型的,而不能是其他的具体数据类型
* 此时如果还要支持ConTest3 conTest3入参,那么即使重载方法也是不行的,因为参数个数和类型、顺序还是一样的(都是ConTest3类型)
* 此时如果想要一个方法支持不同具体数据类型的泛型类入参,那么只能使用类型通配符即?替代具体数据类型也就是写成ConTest3> conTest3,这样就可以使用不同
* 具体数据类型的泛型类入参调用方法(为什么需要类型通配符)
* @param conTest3
*/
public static void test(ConTest3<?> conTest3){
System.out.println(conTest3.getId());
}
}
类型通配符上限
语法:
类/接口<? extends 实参类型> //限制使用方法时该参数的泛型的具体数据类型,只能是这里定义的实参类型,或实参类型的子类型,其他类型包括实参类型的父类型都不行(实参类型代表的具体数据类型可以是定义方法时指定的具体数据类型或者定义方法时也是用泛型标记代替实参类型但这个泛型标记和泛型类的标记是一样的,那么这个方法的泛型标记所代表的实参类型的具体数据类型就是和使用这个泛型类时定义的具体数据类型一致;如果方法是泛型方法且这个代表实参类型的泛型标记是定义泛型方法的泛型标记包含的那么就和泛型类的泛型标记无关(和 定义泛型方法 时泛型标记的规则一致))
例如:public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
TreeSet的这个构造器可以传递一个 Collection,而这个Collection集合采用了类型通配符上限,其中的实参类型是泛型标识E,而这个泛型标识E和public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable{}
TreeSet泛型类定义的泛型标识一样,那么也就是这个E所代表的具体数据类型和创建TreeSet对象的数据类型一致,如:TreeSet<String> treeSet = new TreeSet<>();那么此时要传入一个Collection集合就只能是String或String的子类
//对于extends上限限制还有一些其他用法,比如:
package com.ceshi.jichu.generic;
/**
* @version 1.0
* @Description 测试其他extends情况
* extends本来用于类型通配符代表的方法实参的数据类型限制
* 但泛型类和泛型方法的定义也可以使用extends限制泛型的数据类型上限
*/
public class Test4<T extends Number> {
public <T extends Object> T get(T t){
return t;
}
}
拿上面的例子来说明就是:
//调用test方法时,传入的实参为ConTest3泛型类类型,具体的数据类型只能是Number或Number的子类类型(如:Integer、Long等),如果传入非Number或Number子类型的具体数据类型(包括Number的父类型都不行)则编译不通过报错,那么修改后上面例子的conTest32对象,test(conTest32)则会报错
public static void test(ConTest3<? extends Number> conTest3){
System.out.println(conTest3.getId());
}
类型通配符下限
语法:
类/接口<? super 实参类型> //限制使用方法时该参数的泛型的具体数据类型,只能是这里定义的实参类型,或实参类型的父类型,其他类型包括实参类型的子类型都不行(实参类型代表的具体类型可以是定义方法时指定的具体数据类型或者定义方法时也是用泛型标记代替实参类型但这个泛型标记和泛型类的标记是一样的,那么这个方法的泛型标记所代表的实参类型的具体数据类型就是和使用这个泛型类时定义的具体数据类型一致;如果方法是泛型方法且这个代表实参类型的泛型标记是定义泛型方法的泛型标记包含的那么就和泛型类的泛型标记无关(和 定义泛型方法 时泛型标记的规则一致))
例如:public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
TreeSet的这个构造器可以传递一个 Comparator 比较器,而这个比较器采用了类型通配符下限,其中的实参类型是泛型标识E,而这个泛型标识E和public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable{}
TreeSet泛型类定义的泛型标识一样,那么也就是这个E所代表的具体数据类型和创建TreeSet对象的数据类型一致,如:TreeSet<String> treeSet = new TreeSet<>();那么此时要传入一个 Comparator 就只能是String或String的父类
类型通配符上下限例子:
package com.ceshi.jichu.generic;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
/**
* @version 1.0
* @Description 测试类型通配符上下限
*/
public class TestWildcard {
public static void main(String[] args) {
List<Tree> treeList = new ArrayList<>();
List<Branch> branchList = new ArrayList<>();
List<Leaf> leafList = new ArrayList<>();
//测试类型通配符上限限制的入参数据类型
test1(treeList);
test1(branchList);
test1(leafList);
//测试类型通配符下限限制的入参数据类型
test2(treeList);
test2(branchList);
test2(leafList);
}
/**
* 类型通配符上限
* @param list
*/
public static void test1(List<? extends Branch> list){
for (Branch branch : list) {
//遍历元素时全部用Branch来接收,也就是使用类型通配符的上限数据类型来接收(因为真实的元素要么是子类要么就是Branch)
System.out.println("遍历上限限制泛型类型元素:"+branch);
}
Tree tree = new Tree();
Branch branch = new Branch();
Leaf leaf = new Leaf();
//最高Branch类,那么父类的元素肯定不能添加
list.add(tree);
//由于不知道具体的元素数据类型,那么添加会报错
list.add(branch);
list.add(leaf);
}
/**
* 类型通配符下限
* @param list
*/
public static void test2(List<? super Branch> list){
for (Object o : list) {
//遍历元素时全部用Object来接收,也就是使用Java最顶级的父类数据类型来接收(因为真实的元素是Branch或Branch的父类而Branch的父类不知道会定义什么类型,所以统一Object来接收)
System.out.println("遍历下限限制泛型类型元素:"+o);
}
Tree tree = new Tree();
Branch branch = new Branch();
Leaf leaf = new Leaf();
list.add(tree);
//下限约束可以新增本身及子类类型的元素,但不能新增父类类型的元素,因为真实的元素至少是Branch级别的,那么Branch及其子类是可以采用Branch及其父类接收的,但不能确定究竟是哪个父类
//所以Branch的父类类型元素不能新增,虽然可以添加本身及子类,但也只是编译不报错,运行时List里面只能是具体的某个数据类型,所以运行时可能会报错
list.add(branch);
list.add(leaf);
}
}
/**
* 树类,父类
*/
class Tree{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Tree{" +
"name='" + name + '\'' +
'}';
}
}
/**
* 树枝类,本类
*/
class Branch extends Tree {
}
/**
* 树叶类,子类
*/
class Leaf extends Branch{
}
泛型是jdk1.5之后才引入的概念,在这之前是没有泛型的,而泛型代码能够很好的和之前代码兼容则因为泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,这一特性被称之为类型擦除(也就是说写代码的时候代码中有泛型的信息,并且编译时也有这些泛型信息,但是当泛型信息所涉及的类、接口、方法等一旦被加载那么这个类在堆内存中的Class对象里面就没有泛型信息了,已经使用其他的具体数据类型做了泛型替代也就是将泛型相关信息做了擦除)。
类型擦除情况:
a、无限制类型擦除(对泛型没有约束,那么替换为Object类型)
b、有限制类型擦除(对泛型有约束,extends是上限约束,那么就将泛型替换为上限类型)
c、擦除方法中类型定义的参数(和b一样extends约束替换为上限类型,如果没有extends上限限制,那么和a一样使用Object类型替换)
d、桥接方法(也就是说生成的实现类的Class对象除了类中本来有的泛型接口的实现方法,还有一个Object返回类型的同名称实现方法)
例子:
package com.database.pool.testpool.enumtest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
/**
* 测试类型擦除,测试无限制和有限制
*/
public class TestType<T,E extends Number> implements TestBridging<Integer>{
private T t;
private E e;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
private <T extends List> T show1(T t){
return t;
}
@Override
public Integer info() {
return null;
}
}
/**
* 接口用于测试桥接方法
* @param
*/
interface TestBridging<T>{
T info();
}
/**
* 通过读取对象的Class对象信息获取相关类型进行类型擦除验证
*/
class TestClear{
public static void main(String[] args) {
TestType<String,Integer> testType = new TestType<>();
Class<? extends TestType> aClass = testType.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("字段名称:"+declaredField.getName()+" 字段类型:"+declaredField.getType());
}
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("方法名称:"+declaredMethod.getName()+" 方法返回值类型:"+declaredMethod.getReturnType());
}
}
}
约束:可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象(也就是可以定义泛型类型的数组变量,但不能直接给这个泛型类型的数组变量创建带有泛型数据类型的数组对象,不论是Java原生的泛型类型的数组还是自定义泛型类型的数组都不能直接创建泛型数据类型的数组只能创建数组变量)
以ArrayList为例:
比如:ArrayList<String>[] arrayList = new ArrayList<String>()[3]
申明arrayList这个变量是可以的,但是后面的 new ArrayList<String>()[3]会报错,不能直接创建带有泛型数据类型的数组对象,因为泛型指定的类型会在加载时做类型擦除,那么就可能会把泛型替换为Object类型,而数组的元素类型则是明确指定的数据类型,这两种情况是不兼容的
T[] t = new T[5] 这种方式会报错
但申明泛型数组变量是可以的:T[] t = null;
两种方式定义:
a、定义泛型数组变量时,直接创建泛型数组对象 (虽然也是直接创建泛型数组对象,但没有带泛型数据类型参数)
比如上面的示例正确定义方式:
ArrayList<String>[] arrayList = new ArrayList[3];在创建数组对象时不指定泛型的数据类型即可,但由于前面定义变量时为String类型,因此这个数组中添加元素时必须是ArrayList<String>类型
b、通过 java.lang.reflect.Array的newInstance(Class,int) 方法创建T[]数组(泛型数组)
两种方式示例:
package com.ceshi.jichu.generic;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 测试泛型数组,Java原生泛型类类型数组和自定义泛型类类型数组,这里以ArrayList为例
* ArrayList的定义是public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable,是泛型定义的
* 那么在定义ArrayList的时候需要指定具体的数据类型加载时会发生类型擦除,数组也是需要在定义时指定元素具体的数据类型,一旦发生类型擦除则可能是Object等类型那么和数组的定义数据类型不兼容
* 因此直接创建泛型类类型的数组报错会报错,那么以下两种方式创建泛型数组
*/
public class TestArray {
public static void main(String[] args) {
//正常数组的定义类似下面这种:
Integer[] integers = new Integer[5];
//那么如果是泛型的数组应当怎么定义呢?
//第一种方式创建变量时直接定义数组对象方式
//ArrayList[] arrayList = new ArrayList()[3];直接创建泛型类类型的数组报错
ArrayList<String>[] arrayList = new ArrayList[3];//去掉泛型数据类型创建就可以
Set<String>[] sets = new Set[5];//去掉泛型数据类型创建就可以
//创建数组元素
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("aaaa");
arrayList[0] = stringArrayList;
System.out.println("第一种方式定义数组变量时直接创建泛型类类型数组对象获取元素结果:" + arrayList[0].get(0));
//第二种方式,针对自定义泛型类创建自定义泛型类类型数组,方式具体创建方式在ArrayType类的构造器中,这是只针对泛型类内部定义泛型类型数组的创建方式
ArrayType<String> arrayType = new ArrayType<>(String.class, 5);
arrayType.set("bbbb", 0);
System.out.println("第二种方式newInstance创建泛型类内部泛型数组获取元素结果:" + arrayType.get(0));
String[] array = arrayType.getArray();
for (int i = 0; i < array.length; i++) {
System.out.println("读取泛型类内部数组每个元素:" + " 元素下标:" + i + " 元素值:" + array[i]);
}
//第一、二种方式结合,针对自定义泛型类内部有泛型类型的数组且将泛型类本身也作为泛型数组定义
ArrayType<String>[] arrayTypes = new ArrayType[5];//第一种方式直接创建泛型类类型数组对象
arrayTypes[0] = arrayType;
System.out.println("两种方式结合通过第一种方式直接创建泛型类类型数组并获取内部定义的泛型类型数组获取元素结果:" + arrayTypes[0].get(0));
//直接创建泛型数组---推荐使用泛型集合替代泛型数组
List<ArrayType<String>> list = new ArrayList<>();
list.add(arrayType);
}
}
/**
* 第二种方式创建数组
*
* @param
*/
class ArrayType<T> {
//泛型数组定义变量,另外做创建泛型数组对象操作
public T[] array;
//泛型集合可以直接定义变量并创建对象,推荐使用泛型集合替代泛型数组
private List<T> list = new ArrayList<>();
//使用正常的数组定义方式,创建泛型数组对象会报错
//T[] t = new T[5];
//tClass也就是使用ArrayType时定义的具体数据类型的Class对象,length是数组的长度
public ArrayType(Class<T> tClass, int length) {
//第二种方式创建泛型数组,Array.newInstance
array = (T[]) Array.newInstance(tClass, length);
}
public void set(T param, int index) {
array[index] = param;
}
public T get(int index) {
return array[index];
}
public T[] getArray() {
return array;
}
}
注意:一般使用泛型时如果有需要使用泛型数组那么一般用泛型集合比如List、Set、Map等根据情况来代替泛型数组,因为集合比数组拥有更多功能且可以直接创建泛型集合对象。