我是靠谱客的博主 幽默蓝天,最近开发中收集的这篇文章主要介绍JavaSE笔记:泛型程序设计,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.为什么要使用泛型程序?

泛型程序设计意味着编写的代码可以对多种不同的类进行重用。

2.定义简单的泛型类

泛型类:有一个或者多个类型变量的类。

例子:

public class Pair<T>{
private T first;
private T second;
public Pair(){first=null;second=null;}
public Pair(T first,T second){this.first=first;this.second=second;}
public T getFirst(){return first;}
public T getSecond(){return second;}
public void setFirst(T newValue){first = newValue;}
public void setSecond(T newValue){second = newValue;}
}

注释:Java库使用变量E表示集合的元素类型,KV分别表示键和值的类型。T(必要时还可以用相邻的字母US)表示"任意类型".

可以使用具体的类型替换类型变量来实例化(instantiate)泛型类型如:Pair<String>

3.泛型方法

例子:

class ArrayAlg{
public static <T> T getMiddle(T...a){
return a[a.length/2];
}
}

1.泛型方法可以在普通类中定义,也可以在泛型类中定义。

2.方法调用:

String middle = ArrayAlg.<String>getMiddle("John","Q.","Psd");//拿具体类型替换

String middle = ArrayAlg,getMiddle("John","Q.","Psd");//这种情况可以省略,因为编译器可以推断你想要的方法,这里不深究

4.类型变量的限定

例子:

public static <T extends Comparable> T min(T[] a)...

记法:

<T extends BoundingType> //T应该BoundingType的子类型,它们既可以是类,也可以是借口
T extends Comparable & Serializable //可以根据需要拥有多个接口超类型,但最多只有一个可以是类,并且必须是限定列表中的第一个限定

5.泛型代码和虚拟机

虚拟机没有泛型类型对象----所有对象都属于普通类(所以有了"擦除"概念)

5.1 类型擦除

无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(其名字就是去掉类型参数后的泛型类型名)。类型变量会被擦除,并替换为其限定类型(对无限定类型的替换为Object)

T ---擦除后---> Object

Pair<String>或Pair<LocalDate> ---擦除后---> Pair

注释:<T extends Serializable & Comparable> 会用Serializable替换T,编译器在必要时向Comparable插入强制类型转换

5.2 转换泛型表达式

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

编译器在调用该方法时走二步:

        1.对原始方法Pair.getFirst的调用

        2.将返回的Object类型强转位Employee类型

访问泛型字段时也要插入强制类型转换

5.3 转换泛型方法

会合成桥接法来保持多态。(这里不细究)

5.4调用遗留代码

观察代码:

void setLabelTable(Dictionary table)//方法
Dictionary<Integer,Component>//对象类型
slider.setLabelTable(labelTable);
//warning 因为编译器无法确定setLabelTable会对Dictionary对象做什么操作,
//擦除后,可能会有糟糕的强制类型转换,这就打破了了键类型必须为Integer的承诺。
//实际上,JSlider只读取这个信息,因此可以忽略这个警告。
//相反的情况,由一个遗留类得到一个原始类型的对象。可以将它赋给一个类型使用了泛型的变量
Dictionary<Integer,Component> labelTable = slider.getLabelTable;//warning
//为了忽略这个警告
@SuppressWarnings("unchecked")
Dictionary<Integer,Component> labelTable = slider.getLabelTable;//no warning
//该注解会关闭对方法中所有代码的检查

6.限制与局限性

6.1 不能用基本类型实例化参数类型

例子:没Pair<double>只有Pair<Double>

6.2 运行时类型查询只适用于原始类型

例子:

    a instanceof Pair<T> // ERROR,一般会编译错误

例子:

Pair<String> stringPair = ...;Pair<Employee> employeePair = ...;
    if(stringPair.getClass() == employeePair.getClass()) // true因为getClass的返回都是Pair.Class

6.3 不能创建参数化类型的数组

Pair table = new Pair<String>[10];// ERROR

6.4 Varargs警告

public static <T> void addAll(Collection<T> coll,T...ts){
for (T t : ts) coll.add(t);
}
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll(table,pair1,pair2);

为了调用这个方法,虚拟机必须建立一个Pair<String>数组,这就违反了之前的规定。不过这种情况规则会放松。你会得到一个警告

@SuppressWarnings("unchecked")
@SafeVarargs 

这二个注解可以忽略警告

注释:@SafeVarargs只能用于声明为static、final或private的构造器和方法。否则有可能没意义。

 小拓展:

//可以使用@SafeVarags注解来消除创建泛型数组的有关限制
@SafeVarags static <E> E[] array(E...array) {return array;}
//现在可以调用
Pair<String>[] table = array(pair1,pair2);
//这看起来很方便,不过隐藏着危险。以下代码:
Object[] objarray = table;
obj array[0] = new Pair<Employee>();
//能顺利运行而不会报ArrayStoreException 异常(因为数组存储只会检查擦除的类型),但在处理table[0]时,你会在别处得到一个异常

6.5 不能实例化类型变量

 public Pair(){first = new T();second = new T();}//ERROR 类型擦除将T变成Object,而这肯定不是你想要的

//在java8之后,最好的解决办法就是让调用者提供一个构造器表达式。例如:
Pair<String> p = Pair.makePair(String::new);
//Supplier<T>是一个函数式接口,表示一个无参数而且返回类型为T的函数
public static <T> Pair<T> makePair(Supplier<T> constr){
return new Pair<>(constr.get(),constr.get());
}

比较传统的解决方法是通过反射调用Constructor.newInstance方法来构造对象


first = T.class.getConstructor().newInstance();//ERROR
//表达式T.class是不合法的,因为它会擦除为Object.class
//正确实现
public static <T> Pair<T> makePair(Class<T> clazz){
try {
return new Pair<>(clazz.getConstructor().newInstance(),clazz.getConstructor().newInstance());
}catch (Exception e){
return null;
}
}
Pair<String> p = Pair.makePair(String.class);

6.6 不能构造泛型数组 (略)

6.7 泛型类的静态上下文中类型变量无效

private static T singleInstance;//ERROR 编译就会报错

6.8 不能抛出或捕获泛型类的实例

public class Problem<T> extends Exception{...}//ERROR--can't extend Throwable
catch(T e)//ERROR--can't catch type variable

6.9 可以取消对检查型异常的检查

        Java异常处理的一个基本原则是,必须为所有检查型异常提供一个处理器。不过可以利用泛型取消这个机制。关键在于下面这个方法:


@SuppressWarnings("unchecked")
static <T extends Throwable> void throwAs(Throwable throwable) throws T{
throw (T)throwable;
}

假设这个方法在Task接口中,如果有一个检查型异常e,并调用

Task.<RuntimeException>throwAs(e);

编译器就会认为e是一个非检查型异常。

try{
do work
}catch(Throwable t){
Task.<RuntimeException>throwAs(e);
}//会把所有异常都转换为编译器所认为的非检查型异常

6.10 注意擦除后的冲突

考虑一个Pair<String> 从概念上说,它有二个equals方法

boolean equals(String) //defined in Pair<T>
boolean equals(Object) //inherited from Object

补救办法:重命名

注意:如果二个接口类型是同一接口的不同参数化,一个类或类型变量就不能同时作为这二个接口类的子类

class Employee implements Comparable<Employee>{...}
class Manager extends Employee implements Comparable<Manager>{...}//ERROR

7.泛型类型的继承规则

例子:

Manager继承Employee,但Pair<Employee>和Pair<Manager>没有任何关系

8.通配符

8.1 通配符概念

例子 :Pair<? extends Employee> 表示任何泛型Pair类型,它的类型参数是Employee的子类

public static void printBuddies(Pair<Employee> p){
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(".....");
}//该程序用于打印员工的信息,但不能传Pair<Manager>

解决办法:

public static void printBuddies(Pair< ? extends Employee> p)

8.2 通配符的超类型限定

?super Manager // 还可以这么搞

<T extends Comparable<? super T>> // 有兴趣可以留意,这里不深究,本意是帮助应用程序员去除对调用参数的不必要限制

8.3 无限定通配符

?// 还可以这么搞

?getFirst()

void getFirst(?)

Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始Pair类的setFirst方法(可以调用setFirst(null))

8.4 通配符捕获

通配符不是类型变量,所以 ?t = p.getFirst是非法的

通配符捕获只有在非常限定的情况下才是合法的。编译器必须能保证通配符表示单个确定的类型。例如ArrayList<Pair<T>>中的T永远都不能捕获ArrayList<Pair<?>>中的通配符。

9.反射和泛型(利用反射可以获得泛型类的哪些信息)

9.1 泛型Class类

Class类是泛型的。例如String.class实际上是一个class<String>类的对象。

java.lang.Class<T>

T newInstance() 返回无参数构造器构造的一个实例

T cast(Object obj) 如果 obj为null或者转换类型T,则返回obj;否则抛出一个BadCastException异常

T[] getEnumConstants 如果T是枚举类型,则返回所有值组成的数组,否则返回null

Class<? super T>getSuperClass() 返回这个类的超类。如果T不是一个类或Object类,则返回null

Constructor<T> getConstructor(Class...paramterTypes)

Constructor<T> getDeclaredConstructor(Class...paramterTypes)

获得公共构造器,或者有给定参数类型的构造器

java.lang.reflect.Constructor<T>

T newInstance(Object...parameters) 返回用指定参数构造的新实例

9.2 使用Class<T> 参数进行类型匹配

public static <T> Pair<T> makePair(Class<T> c) throws ReflectiveOperationException {
return new Pair<>(c.newInstance(),c.newInstance());
}

如果调用makePair(Employee.class)编译器可以推断出方法返回一个Pair<Employee>

9.3 虚拟机中的泛型类信息

 擦除的类仍保留原先泛型的微弱记忆。例如Pair类知道它源自Pair<T>,尽管无法区分类型参数。

java.lang.reflect包中的接口Type(为了表述泛型类型声明),该接口有以下子类:

Class类,描述具体类型

TypeVariable接口,描述类型变量(如 T extends Comparable<? super T>)

WildcardType接口,描述通配符(如 ? super T)

ParameterizedType接口,描述泛型类或者接口类(如 Comparable<? super T>)

GenericArrayType 接口,描述泛型数组(如 T[])

9.4 类型字面量

如果现在有一个需求:需要将Class对象与一个动作关联,但是泛型类,擦除会带来问题。比如ArrayList<Integer>和ArrayList<String>都擦除为同一个原始类型ArrayList,如何让它们有不同的动作捏?

小技巧(适用于某些情况)。可以捕获Type接口的一个实例,然后构造一个匿名子类。


class TypeLiteral{
private Type type;
public TypeLiteral(){
Type parentType = getClass().getGenericSuperclass();
if(parentType instanceof ParameterizedType){
type = ((ParameterizedType) parentType).getActualTypeArguments()[0];
}else {
throw new UnsupportedOperationException("Construct as new TypeLiteral<...>(){}");
}
}
}

如果运行时有一个泛型类型,就可以将它与TypeLiteral匹配。

9.5 其他

java.lang.Class<T>

TypeVariable getTypeParameters() 如果这个类型被声明为泛型类型,则获得泛型类型变量,否则获得一个长度为0的数组

Type getGenericSupperclass() 获得这个类型所声明超类的泛型类型;如果这个类型是Object或者不是类类型(class type),则返回null

Type[] getGenericInterfaces() 获得这个类型所声明接口的泛型类型(按照声明的次序),否则,如果这个类型没有实现接口,则返回长度为0的数组

java.lang.reflect.Method

TypeVariable getTypeParameters() 如果这个方法被声明为一个泛型方法,则获得泛型类型变量,否则返回长度为0的数组

Type getGenericReturnType() 获得这个方法声明的泛型返回类型

Type[] getGenericParameterTypes() 获得这个方法声明的泛型参数类型。如果这个方法没有参数,返回长度为0的数组

java.lang.reflect.TypeVariable

String getName() 获得这个类型变量的名字

Type[] getBounds() 获得这个类型变量的子类限定,否则,如果该变量无限定,则返回长度为0的数组

java.lang.reflect.WildcardType

Type[] getUpperBounds() 获得这个类型变量的子类限定,否则,如果没有子类限定,则返回长度为0的数组

Type[] getLowerBounds() 获得这个类型变量的超类限定,否则,如果没有子类限定,则返回长度为0的数组

java.lang.reflect.ParameterizedType

Type getRawType() 获得这个参数化类型的原本参数

Type getActualTypeArguments() 获取这个参数化类型声明的类型参数

Type getOwnerType() 如果是内部类型,则返回其他外部类类型;如果是一个顶级类型,则返回null。

java.lang.reflect.GenericArrayType

Type getGenericComponentType() 获取这个数组类型声明的泛型元素类型

最后

以上就是幽默蓝天为你收集整理的JavaSE笔记:泛型程序设计的全部内容,希望文章能够帮你解决JavaSE笔记:泛型程序设计所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部