java泛型中?和T的区别,java泛型 问号
终极管理员 知识笔记 36阅读
3.从逻辑上讲,同一个泛型类创建的不同特定数据类型的对象可以看作是多个不同的类型,但实际上是同一个类型,即不同特定数据类型创建的同一个泛型类的类对象是相同的。
子类也是泛型类那么子类的泛型标识要和父类的泛型标识一致或对泛型标识进行扩展也就是定义泛型子类时子类和父类的泛型标识一样但可以不和定义的父类泛型类标识一样如父类定义为public class ConTest1<E,T> {}子类可以定义为public class ConExtends<A,B> extends ConTest1<A,B>{}或可以比父类更多的泛型标记但这些泛型标记中至少全部包含和父类一样的泛型标记如父类定义为public class ConTest1<E,T> {}子类可以定义为public class ConExtends<A,B,C,D> extends ConTest1<A,B>{}总结就是子类泛型标识必须包含父类泛型标识
class Child<T> extends Father<T>{}
5、泛型类派生子类如果子类不是泛型类那么父类要明确数据类型不写父类的具体数据类型按照Object类型处理也就是定义的子类不是泛型类但父类是泛型类那么在定义子类时需要明确指定父类的具体数据类型如父类定义为public class ConTest1<E,T> {}子类可以定义为public class ConExtends extends ConTest1<String,Integer>{}
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(用户idid); System.out.println(用户名称name); ConTest1<Integer,String> conTest12 new ConTest1<>(2,李四); System.out.println(用户2idconTest12.getId() 用户2nameconTest12.getName()); }
3、定义泛型接口 格式
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>{}
4、定义泛型方法 优点泛型方法能使方法独立于类而产生变化泛型方法的具体数据类型可以和泛型类的具体数据类型无关、成员静态方法都可以并且泛型方法可以定义在普通类、普通接口中不是只能在泛型接口/类中才能定义泛型方法当然泛型接口/类中也可以定义普通方法
格式
修饰符如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> 泛型标识意味着该方法中返回参数类型、形参、方法体可以使用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)之间的<泛型标识>这里是<T> * <T>里面定义的泛型标识和泛型接口或泛型类定义的泛型标识无关即使两者的泛型标识一样他们所代表的具体数据类型也没有关系 * 所以泛型标识可以不和泛型类定义的保持一致(如果是泛型接口定义的泛型方法可以不和泛型接口申明的泛型标识一致) * 但泛型方法的形参及方法内部使用的泛型、返回值类型可使用的泛型标识必须是泛型类或泛型接口、泛型方法定义时申明的泛型标识 * 在使用泛型方法时根据方法的入参数据类型确定泛型方法的泛型标记表示的具体数据类型 * 如果泛型方法的形参及方法内部使用的泛型、返回值类型使用了泛型类或泛型接口定义的泛型标识且这个泛型标识没有在泛型方法的”<泛型标识>“中定义这里的只定义了<T>没有定义A * 那么该泛型标识A的具体数据类型只能是创建这个泛型类对象时定义的A代表的具体数据类型如果在泛型方法的”<泛型标识>“中有定义那么此时和泛型类或泛型接口相同泛型标识所代表的具体数据类型就是独立的 * param t * param a * param <T> * return */ Override public <T> A getCount(T t,A a){ B b this.b; System.out.println(执行方法getCount打印bb); return a; } /** * 定义静态泛型方法泛型标识<T,E,A,B,C>由于这里定义了A和B泛型标识虽然和泛型类申明的泛型标识一致但这里代表的具体数据类型和泛型类上的A、B无关 * 形参中 C...c 表示可变参数即可以通过,分割传入多个c参数 * param t * param e * param a * param b * param <T> * param <E> * param <A> * param <B> * return */ public static <T,E,A,B,C> A getTest(T t, E e, A a, B b, C... c){ System.out.println(静态方法tt); System.out.println(静态方法ee); System.out.println(静态方法aa); System.out.println(静态方法bb); for (int i 0; i < c.length; i) { System.out.println(静态方法cic[i]); } return a; } /** * 定义成员泛型方法 * param t * param e * param a * param b * param c * param <T> * param <E> * param <A> * param <B> * param <C> * return */ public <T,E,A,B,C> A getTest1(T t, E e, A a, B b, C... c){ System.out.println(成员方法tt); System.out.println(成员方法ee); System.out.println(成员方法aa); System.out.println(成员方法bb); for (int i 0; i < c.length; i) { System.out.println(成员方法cic[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(调用成员方法getEcon.getE()); System.out.println(调用成员方法getTcon.getT()); //实参 2 是给形参 A a 赋值A泛型标识未在泛型方法上申明所以需要和创建的ConImpl泛型类对象con定义的具体数据类型一致即只能是Integer类型 Integer count con.getCount(true, 2); System.out.println(执行成员泛型方法getCountcount); Boolean test ConImpl.getTest(1, xiaomi, true, x, 100, 101, 102, 103, 104); System.out.println(执行静态泛型方法getTesttest); //由于所有泛型标识都在泛型方法进行了申明所以和con对象定义的具体数据类型无关 Boolean test1 con.getTest1(1, xiaomi, true, x, 100, 101, 102, 103, 104); System.out.println(执行成员泛型方法getTest1test1); }}
什么是类型通配符
类型通配符一般是使用 “?” 代替具体参数的数据类型所以类型通配符是参数类型的实参而不是参数类型的形参是参数类型的实参和形参而不是参数的实参和形参也就是说定义方法形参时用到的泛型标记只是参数类型的标识可以认为是这个参数的参数类型的形参而 ? 则是在定义方法时用来做泛型标记的替代表示这个参数的数据类型接受不限制数据类型的入参在定义方法时使用。
注意类型通配符只能用于本来是泛型类/接口的参数做实参类型的匹配限制如果参数本来不是泛型类/接口不能使用类型通配符如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<Number> conTest3 * 但这种方式则限制了这个泛型的实参只能是Number类型即使Integer这种子类型的入参也是不行的会编译不通过因为参数必须是ConTest3<Number>类型的而不能是其他的具体数据类型 * 此时如果还要支持ConTest3<Integer> 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{}
6、类型擦除 泛型是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 <T> */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()); } }}
7、泛型数组定义 约束可以声明带泛型的数组引用但不能直接创建带泛型的数组对象也就是可以定义泛型类型的数组变量但不能直接给这个泛型类型的数组变量创建带有泛型数据类型的数组对象不论是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<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable是泛型定义的 * 那么在定义ArrayList的时候需要指定具体的数据类型加载时会发生类型擦除数组也是需要在定义时指定元素具体的数据类型一旦发生类型擦除则可能是Object等类型那么和数组的定义数据类型不兼容 * 因此直接创建泛型类类型的数组报错会报错那么以下两种方式创建泛型数组 */public class TestArray { public static void main(String[] args) { //正常数组的定义类似下面这种 Integer[] integers new Integer[5]; //那么如果是泛型的数组应当怎么定义呢 //第一种方式创建变量时直接定义数组对象方式 //ArrayList<String>[] arrayList new ArrayList<String>()[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 <T> */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等根据情况来代替泛型数组因为集合比数组拥有更多功能且可以直接创建泛型集合对象。