likes
comments
collection
share

java-泛型

作者站长头像
站长
· 阅读数 39

泛型程序设计( Generic programming)编写的代码可以被不同类型的对象重用。

类型参数的好处

在添加泛型这个特性之前,java想要实现泛型的功能是通过 继承 来实现的。例如 ArrayList 类只维护了引用的Object 数组:

public class ArrayList {// before generic classes
	private Object [ ] elementData ;
	public Object get ( int i ) { . . , }
	public void add ( Object o ) { . . . }
}

此种方法有两个问题:

  • 获取值的时候要进行强制类型转换
  • 可以向Object数组列表中添加任何类型的对象

此时,可以通过泛型引入类型参数解决上述问题,使得程序有更好的可读性和安全性。并且编译器通过泛型可以获取其参数信息,避免添加错误类型对象。

【注】类型变量使用大写形式且比较短。 在 Java 库中 , 使用变量 E 表示集合的元素类型 , K 和 V 分别表示表的关键字与值的类型 。 T ( 需要时还可以用临近的字母 U 和 S ) 表示“任意类型”。

简单泛型类

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

泛型方法

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

调用下面代码时候会报错

double middle = TestGeneric . getMiddle( 3.14 , 1729 , 0 ) ;

error:
Incompatible types. Found: 'java.lang.Number & java.lang.Comparable<? extends java.lang.Number & java.lang.Comparable<?>>', required: 'double'

编译器将会自动打包参数为 1 个 Double 和 2 个 Integer 对象 , 而后寻找这些类的共同超类型 。 找到 2 个这样的超类 型 : Number 和 Comparable 接口 , 其本身也是一个泛型类型 。 在这种情况下 , 可以采取的补救措施是将所有的参数写为 double 值。

类型变量的限定

为了保证泛型 T 所属类实现了 compareTo 方法,可以使用 extends 关键字来限定(bound):

public static < T extends Coiparab 1 e > T min(T[] a) . . .

如果有多个限定则可以使用 & 符号来表示:

T extends Comparable & Serializable

逗号用来分隔类型变量 。

泛型代码和虚拟机

类型擦除

无论何时定义一个泛型类型 , 都自动提供了一个相应的原始类型 ( raw type )。 原始类型的名字就是删去类型参数后的泛型类型名。 擦除 ( erased )类型变 M , 并替换为限定类型 ( 无限定的变量用 Object)。

例如 , Pair < T > 的原始类型如下所示 :

public class Pair
{
private Object first ;
private Object second ;
public Pair ( Object first , Object second )
{
this , first = first ;
this . second = second ;
public Object getFirstO { return first ; }
public Object getSecondO { return second ; }
public void set First ( Object newValue ) { first = newValue ; }
public void setSecond ( Object newValue ) { second = newValue ; }
}

如果使用多个限定:

public class Interval < T extends Comparable & Serializable  implements Serializable
{
private T lower ;
private T upper ;
public Interval ( T first , T second )
{
if ( first . compareTo ( second ) < = 0 ) { lower = first ; upper = second ; }
else { lower = second ; upper = first ; }
}
}

原始类型 Interval 如下所示 :

public class Interval implements Serializable
{
private Comparable lower ;
private Comparable upper ;
public Interval ( Comparable first , Comparable second ) { . . . }
}

原始类型用 Serializable 替换 T , 而编译器在必要时要向 Comparable 插入强制类型转换 。 为了提高效率 , 应该将标签 ( tagging ) 接口 ( 即没有方 法的接口 ) 放在边界列表的末尾 。

翻译泛型表达式

下面的代码执行时候

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

因为类型擦除,getFirst 的返回类型后将返回 Object 类型 。 编译器自动插人 Employee 的强制类型转换 。 也就是说,编译器把这个方法调用翻译为两条虚拟机指令 :

  • 对原始方法 Pair.getFirst() 的调用。
  • 将返回的 Object 类型强制转换为 Employee 类型。

同样的在 存取一个泛型域时也要插人强制类型转换。 假设 Pair 类的 first 域为 public。

Employee buddy = buddies.first;

上述代码也会在字节码中插入强制类型转换。

翻译泛型方法

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

在类型擦除后只剩下一个方法:

public static Comparable T min ( Comparable[] a )

方法 在类型擦除后有两个问题:

class Datelnterval extends Pair < LocalDate >{
	public void setSecond ( LocalDate second ) {
    	if(second.compareTo(getFirst())>=0)
    		super.setSecond(second);
    }
    ...
}

这个类经过擦除后:

class Datelnterval extends Pair{//after erased
	public void setSecond ( LocalDate second ) {...}
    ...
}

此类中存在另一个继承的 setSecond 方法:

	public void setSecond ( Object second ) {...}

若使用下面代码进行调用:

Datelnterval interval = new Datelnterval ( . . . ) ;
Pair < LocalDate > pair = interval ; / / OK assignment to superclass
pair.setSecond ( aDate ) ;

上面代码希望对 setSecond 的调用具有多态性 ,并调用最合适的那个方法。 由于 pair 引用 Datelnterval 对象, 所以应该调用 Datelnterval . setSecond。问题在于类型擦除与多态发生了冲 突。编译器通过在 Datelnterval 类中增加桥方法(bridge method)方式来解决这个问题。工作流程为:

  • pair 声明为类型 Pair < LocalDate >,此类型只有一个方法为 setSecond ( Object )

  • 虚拟机用 pair 引用的对象调用这个方法。 这个对象是Datelnterval 类型的 , 因而将会调用 Datelnterval.setSecond ( Object ) 方法

  • 这个方法是合成的桥方法。 它调用 Datelnterval.setSecond (Date)

假设 Datelnterval 也覆盖了 getSecond()方法:

class Datelnterval extends Pair < LocalDate >
{
	public LocalDate getSecond ( ) { return ( Date ) super.getSecond().clone(); }
...
}

此时在 Datelnterval 类中有两个 getSecond方法:

LocalDate getSecond ( ) // defined in Datelnterval
Object getSecond() // overrides the method defined in Pair to call the first method

在java代码中是不合法的。但是在虚拟机中,使用参数类型和返回类型确定一个方法,因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确地处理这一情况 。

Java 泛型转换:

  • 虚拟机中没有泛型 , 只有普通的类和方法
  • 所有的类型参数都用它们的限定类型替换
  • 桥方法被合成来保持多态
  • 为保持类型安全性 , 必要时插人强制类型转换
转载自:https://juejin.cn/post/6923504121915899918
评论
请登录