# 泛型

back

# 常用的字母

back

常用的 T,E,K,V,?

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element
  • 但是泛型的参数只能是类类型,不能是基本的数据类型,他的类型一定是自Object的

WARNING

泛型不接受基本数据类型,换句话说,只有引用类型才能作为泛型方法的实际参数

# 无界通配符?

back

我有一个超类Animal和几个子类,如狗,猫,鹦鹉,等…现在我需要一个动物的列表,我的第一个想法是像:

List<Animal> listAnimals
1

相反,我的同事推荐像:

List<? extends Animal> listAnimals
1

通配符在你声明局部变量时没有什么意义,但是当你为一个方法声明一个参数时,它们是非常重要的。 想象一下你有一个方法:

int countLegs ( List< ? extends Animal > animals )
{
   int retVal = 0;
   for ( Animal cur : animals )
   {
      retVal += cur.countLegs( );
   }

   return retVal;
}
1
2
3
4
5
6
7
8
9
10

有了这个签名,你可以这样做:

List<Dog> dogs = ...;
countLegs( dogs );

List<Cat> cats = ...;
countLegs( cats );

List<Animal> zoo = ...;
countLegs( zoo );
1
2
3
4
5
6
7
8

然而,如果你声明countLegs像这样:

int countLegs1 ( List< Animal > animals )
1

然后在前面的示例中,只有countLegs(动物园)将编译,因为只有该调用具有正确的类型。

所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型。像 countLegs 方法中,限定了上届,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错。而 countLegs1 就不行。

# 上界通配符< ? extends E>

back

上界:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
private <K extends A, E extends B> E test(K arg1, E arg2){
    E result = arg2;
    arg2.compareTo(arg1);
    //.....
    return result;
}
1
2
3
4
5
6

类型参数列表中如果有多个类型参数上限,用逗号分开

# 下界通配符< ? super E>

back

下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object

# ? 和 T 的区别

back

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行

// 可以
T t = operate();

// 不可以
? car = operate();
1
2
3
4
5

T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

# 区别1:通过 T 来 确保 泛型参数的一致性

// 通过 T 来 确保 泛型参数的一致性
public <T extends Number> void
test(List<T> dest, List<T> src)

//通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
public void
test(List<? extends Number> dest, List<? extends Number> src)
1
2
3
4
5
6
7

# 区别2:类型参数可以多重限定而通配符不行

public static <T extends MultiA & MultiB> void test(T t){}
1

使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子类型,此时变量 t 就具有了所有限定的方法和属性。对于通配符来说,因为它不是一个确定的类型,所以不能进行多重限定。

# 区别3:通配符可以使用超类限定而类型参数不行

类型参数 T 只具有 一种 类型限定方式:

T extends A
1

但是通配符 ? 可以进行 两种限定:

? extends A
? super A
1
2

# Class<T>Class<?> 区别

back

Class<T> 在实例化的时候,T 要替换成具体类。Class<?> 它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。比如,我们可以这样做申明:

// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;
1
2
3
4

那如果也想 public Class<T> clazzT; 这样的话,就必须让当前的类也指定 T ,

public class Test3<T> {
    public Class<?> clazz;
    // 不会报错
    public Class<T> clazzT;
1
2
3
4

# 泛型类

back

泛型类的定义只要在申明类的时候,在类名后面直接加上<E>,中的E可以是任意的字母,也可以多个,多个用逗号隔开就可以。示例代码如下

public class SelfList<E> {}
public class SelfList<T> {}
1
2

# 泛型类中的实际类型的推断

那么什么时候确定这个E 的具体类型呢?其实是在new一个对象的时候指定的,请看下面代码

public class SelfList<E> {

    public void add(E e) {
        if (e instanceof String) {
            System.out.println(" I am String");
        } else if (e instanceof Integer) {
            System.out.println("I am Integer");
        }
    }
    public static void main(String[] args) {
        //这里创建的时候指定了String
        SelfList<String> a = new SelfList<>();
        a.add("123");
        //这里创建的时候指定了Integer
        SelfList<Integer> b = new SelfList<>();
        b.add(123);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

为什么实例方法可以,而静态方法却报错?

  1. 首先告诉你结论:静态方法不能使用类定义的泛型,而是应该单独定义泛型
  2. 到这里估计很多小伙伴就瞬间明白了,因为静态方法是通过类直接调用的,而普通方法必须通过实例来调用,类在调用静态方法的时候,后面的泛型类还没有被创建,所以肯定不能这么去调用的

# 泛型接口

back

泛型接口和类的使用方式一样

public interface IndicatorTypeService<T> {}

//这里定义的时候指定具体类型为ProductSale,当然也可以这里没有指定具体类型
public class ProductSaleServiceImpl implements IndicatorTypeService<ProductSale> {}
1
2
3
4

# 泛型方法

back

   //注意这是个假的泛型方法,不要以为有一个E就是泛型方法哦
    public void add(E e) {
        if (e instanceof String) {
            System.out.println(" I am String");
        } else if (e instanceof Integer) {
            System.out.println("I am Integer");
        }
    }
1
2
3
4
5
6
7
8

# 泛型方法的定义

    public <T> T get(T t) {
        return t;
    }
1
2
3
  • 返回值和public 之间的<T> 是泛型方法的必要条件,并且这个和类的定义的泛型E 是可以同名(一般设置不同名),并且他们之间是独立的。
  • <T> 可以多个,多个用逗号隔开,列如 <T,V>
  • 返回值不一定是T,可以是任意的类型,如Long
  • 方法中的参数也不一定是T,可以是任意的类型,如Long。只是泛型方法一般返回值类型和参数有其中一个是定义的泛型(全是具体类型就没意义了)
    public <T> T get(T t) {
        return t;
    }
    public static void main(String[] args) {
        //这里创建的时候指定了String
        SelfList<String> a = new SelfList<String>();
        a.add("123");
        int num = a.get(123);
    }
1
2
3
4
5
6
7
8
9

# 泛型方法中的实际泛型的推断

那么泛型方法是怎么确定这个具体类型的呢?

主要思想是在调用该泛型方法传进去的参数类型和返回值类型来确定具体类型的

  • 泛型变量在参数列表中只出现一次,调用该方法时根据传进的实参类型来确定
    public <T>  T get1(T t) {
        if (t instanceof String) {
            System.out.println(" I am String");
        } else if (t instanceof Integer) {
            System.out.println("I am Integer");
        }
        return t;
    }

    public static void main(String[] args) {
        //这里调用的时候传进去的是int 类型,所以确定了他的类型是int
        int b=a.get1(123);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 当参数列表中有多个参数使用了相同的泛型变量,返回值类型也使用了该变量,那么返回值类型由他们的公共父类来决定最终的泛型类型
    public <T> T get2(T t, T t2) {
        if (t instanceof Float) {
            System.out.println(" I am String");
        } else if (t instanceof Integer) {
            System.out.println("I am Integer");
        } else if (t instanceof Number) {
            System.out.println("I am Number");

        }
        return t;
    }

    public static void main(String[] args) {
        //这里返回值类型必须是number 类型
        Number b = a.get2(123, 12.1);
    }
//输出:I am Integer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

因为根据第一条规则,传进去的是什么类型就是什么类型,但是返回值类型候需要根据第二条规则来确定

# 反射中的泛型使用

back

上面说的都是在编译之前就可以确定的泛型
大家知道,泛型运行的时候其实是会被擦除的。不过没关系,还是提供给我们通过反射的方式来获取。首先我来认识下java中的泛型类型继承结构

ParameterizedType-->Type<--TypeVariable

# ParameterizedType

back

public interface ParameterizedType extends Type {
    /**
     * 返回一个实际类型的数组
     * 比如对于ArrayList<T>,被实例化的时候ArrayList<String>,这里返回的就是String
     *
     */
    Type[] getActualTypeArguments();
    /**
     * 这里其实就是返回去掉泛型后的真实类型
     * 对于List<T> 这里返回就是List
     */
    Type getRawType();
    /**
     * 这里针对的是内部类的情况,返回的是他的外层的类的类型
     * 例如SelfHashMap  里面有一个Entry 内部类,也就是SelfHashMap.Entry<String,String>
     * 返回的就是SelfHashMap
     */
    Type getOwnerType();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# ParameterizedType测试验证

public class TypeTest {
    public static class TestMap {
        public static class Entry<T,V extends Collection>{}
    }

    public static class Ht extends TestMap.Entry<Integer, List<String>>{}

    @Test
    public void testTypeP(){
        Class clazz = Ht.class;
        Type type = clazz.getGenericSuperclass();
        if (type instanceof ParameterizedType){
            ParameterizedType ParameterizedType = (ParameterizedType) type;
            System.out.println(Arrays.toString(ParameterizedType.getActualTypeArguments()));
            System.out.println(ParameterizedType.getOwnerType());
            System.out.println(ParameterizedType.getRawType());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//输出

[class java.lang.Integer, java.util.List<java.lang.String>]
class com.chlm.mysession.common.TypeTest$TestMap
class com.chlm.mysession.common.TypeTest$TestMap$Entry
1
2
3

# TypeVariable

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
    /**
     * 这里返回的是泛型的上界类型数组,比如 <T, V extends Collection> 这里的上界类型就是Collection
    */
    Type[] getBounds();
    /**
     * 返回的是声明该泛型变量的类,接口,方法等
     * 列如 public static class Entry<T, V extends Collection> 返回的就是Entry
     */
    D getGenericDeclaration();
    /**
     * 这里返回的就是泛型定义的变量名称,比如 <T>  返回的就是T
     *
     */
    String getName();
    /**
     * 这里返回的就是AnnotatedType 数组,jdk1.8 才加进来,本文不分析,直接跳过
     */
     AnnotatedType[] getAnnotatedBounds();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

测试类

@Test
    public void testVariable(){
        Class clazz = TestMap.Entry.class;
        TypeVariable[] typeVariables = clazz.getTypeParameters();
        Stream.of(typeVariables).forEach(typeVariable -> {
            System.out.println(typeVariable.getName());
            System.out.println(ArrayUtils.toString(typeVariable.getAnnotatedBounds()));
            System.out.println(ArrayUtils.toString(typeVariable.getBounds()));
            System.out.println(typeVariable.getGenericDeclaration());
            System.out.println("我是完美分割线");
        });
    }
1
2
3
4
5
6
7
8
9
10
11
12
T
{sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl@3fb6a447}
{class java.lang.Object}
class com.chlm.mysession.common.TypeTest$TestMap$Entry
我是完美分割线
V
{sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl@79698539}
{interface java.util.Collection}
class com.chlm.mysession.common.TypeTest$TestMap$Entry
我是完美分割线
1
2
3
4
5
6
7
8
9
10