Java泛型程序设计


要学习java泛型,我觉得首先得知道设计的目的;第一,不用强制类型转换;第二,能够编译时检查,更加安全;第三,代码可读性增加。

简单的泛型使用

泛型类的定义和使用

泛型类就是具有有个或者多个类型变量的类。

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

//泛型使用
Pair<String> pair = new Pair<>();
Pair<String> pair = new Pair<>("Hello","world");
pair.setFirst("Hello")

泛型方法

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

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

//使用
ArrayAlg.getMiddle("hello","Test","Q");
ArrayAlg.<String>getMiddle("hello","Test","Q");

通常使用第一种方式调用,能够这样调用的原因是,有足够的参数能够推断出T代表的含义,而且编译器会自动推策。

类型变量的限定

有时候我们需要对类或者方法中的类型变量加以约束;如:

class ArrayAlg{
	public static <T> T min(T[] a){
		if(a==null || a.length == 0) return null;
		T smallest = a[0];
		for(int i =1;i<a.length;i++){
			if(smallest.compareTo(a[i])>0) smallest = a[i];
		}
		return smallest;
	}
}

这里就出现了一个问题,我们必须让T实现了Compareable接口,所以可以通过类型限定T extends Conpareable如果要实现多个,就可以使用T extends Conpareable & Serializable

泛型代码和虚拟机

我们不仅仅需要知道使用泛型,我们还需要知道泛型在java虚拟机中的表现,我觉得首先我们需要知道这三点:

  1. 在虚拟机中没有泛型类型对象——所有的对象都是普通类。
  2. 无论何时定义一个泛型类型,都自动提供一个相应的原始类型,原始类型的名字就是删去类型参数的泛型类型名。
  3. 擦除类型变量,并替换成限定类型(无限定类型的变量就用Object)

例如Pair的原始类型:

public class Pair{
	private Object first;
	private Object second;
	public Pair(Object first,Object second){
		this.first = first;
		this.second = second;
	}
	public Object getFirst(){
		return first;
	}
	public void setFirst(Object first){
		this.first = first;
	}
}

如果存在两个限定类型,那么取第一个。

import java.io.Serializable;

public class Interval<T extends Comparable & Serializable> implements 
        Serializable {
    private T lower;
    private T upper;

    public Interval (T lower, T upper) {
        if(first.compareTo(second)<=0){
            this.lower = first;
            this.upper = second;
        }else {
            this.lower = second;
            this.upper = first;
        }
    }
}
//原始类
import java.io.Serializable;

public class Interval implements Serializable {
    private Comparable lower;
    private Comparable upper;

    public Interval (Comparable lower, Comparable upper) {
        if(first.compareTo(second)<=0){
            this.lower = first;
            this.upper = second;
        } else {
            this.lower = second;
            this.upper = first;
        }
    }
}

如果第一个类写的是Serializable,那么编译器在必要是插入强制转换成Comparable;为了提高效率,标签接口应该放在末尾。

翻译泛型表达式

在虚拟机中调用泛型方法,由于擦除了返回类型,所有编译器会插入强制类型转换。

Pair<Employee> buddlies = ...
Employee employee = buddlies.getFirst()

这段代码,编译器会在自动在插入强制转换类型,在getFirst返回object 对象时。
实际在虚拟机中会翻译成两条虚拟机指令:

  • 对原始方法getFirst的调用
  • 对Object对象强制转换成Employee对象

翻译泛型方法

由于在java虚拟机中没有泛型,所有的泛型都没擦除,所以对于泛型方法就容易出现多态问题。
我们从一个例子中来看这个问题

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

我们这段代码是希望复写setSecond方法,使得第二个对象始终小于第一个对象。对于DateInterval 类编译后,泛型类型被擦除,就会出现两个不同的setSecond方法。

public void setSecond(Date second)
public void setSecond(Object second)

那么当代码中出现:

DateInterval interval = new DateInterval();
Pair<Date> pair = interval;
pair.setSecond(aDate);

这时候调用的是setSecond(Object Object)方法,因为向上转换成了Pair对象,所以会重新生成一个桥方法复写setSecond(Object Object)方法,具体:

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

java 泛型的几点

  • 虚拟机内没有泛型,只有普通类和方法
  • 所有的类型都用限定类型替换
  • 桥方法合成,用来保持多态
  • 为了保持类型安全,必要时插入强制类型转换

泛型的约束与局限性

  1. 不能用基本类型来当泛型参数,比如Pair是不合法的,但是可以Pair,基本类型的包装类可以
  2. 类型查询只能查询原始类型:对于 a instanceof Pair, a instanceof Pair都不合法;
  3. 不允许实例化参数化数组,Pair[] table = new Pair[10];对于table,由于类型擦除,table[0]= new Pair()也是能够通过检查,所以还是会导致类型错误,所以不允许实例化参数化数组,但是可以声明Pair[]
  4. 不能实例化类型参数,如不能使用new T、new T[] 或者T.class;因为类型擦除后这些都是无效的
  5. 不能使用带有类型参数的静态方法静态域

泛型的继承规则

其实记住两点就行:

  1. 无论S或者T是什么关系,Pair< S >和Pair< T >都是没有关系的;
  2. 泛型类可以实现拓展其他泛型类;普通类继承泛型类不一定是泛型类。

通配符类型

对于通配符类型我觉得最重要的一点是和类型参数区分开;
类型参数是在定义泛型类或者方法是时候使用,而通配符是在使用泛型时使用;

class Pair<T>{
public void setFirst(T t){}
}
//通配符
public static void printBuddies(Pair<? extends Employee>)

通配符 Pair<? extends Employee>是 Pair<Manager> 的父类型,原因是? extends Employee是一个整体,这是给泛型类型赋的一种值,表示的是一种 Employee 子类的泛型,这不是一种具体的类型,只是对类型加了限定;因为Manage是 Employee的子类,所以是父子关系

对于代码:

Pair<Manager> managerBuddies = new Pair<>(ceo,cfo);
Pair<? extends Employee> wildCardBuddies = managerBuddies;
wildCardBuddies.setFirst(lowlyEmployee);//编译报错

编译器报错的原因:? extends Employee只能保证的是泛型是 Employee的子类,但不能确定是哪一种具体的类型,为了保证安全,不出现类型转换错误,所以编译报错。我们应该将? extends Employee当一个整体看,这是一种泛型类型参数的一种值的形式。但对于get方法不会报错,因为子类可以转化成父类

通配符还有一种限定方式? super Manager,这个表示的泛型是Manager类的父类
同上:set方法不会报错,因为子类可以转化成父类,get方法会报错,父类不能转成子类。

对于无限定的通配符可以理解为? extends Object

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值