Java学习day057 泛型类型的继承规则、通配符类型(通配符概念、通配符的超类限定、无限定通配符、通配符捕获)

使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。

day057   泛型类型的继承规则、通配符类型(通配符概念、通配符的超类限定、无限定通配符、通配符捕获)

目录

day057   泛型类型的继承规则、通配符类型(通配符概念、通配符的超类限定、无限定通配符、通配符捕获)

1.泛型类型的继承规则

2.通配符概念

3.通配符的超类型限定

4.无限定通配符

5.通配符捕获


1.泛型类型的继承规则

在使用泛型类时,需要了解一些有关继承和子类型的准则。下面先从许多程序员感觉不太直观的情况开始。考虑一个类和一个子类,如 Employee 和 Manager。Pair是PaiKEmployee>的一个子类吗? 答案是“不是”,或许人们会感到奇怪。例如,下面的代码将不能编译成功:

ManagerD topHonchos=. .
Pair result=ArrayAlg.ininmax(topHonchos);//Error

minmax 方法返回 Pair,而不是 Pair,并且这样的赋值是不合法的。无论S 与 T 有什么联系(如图  所示),通常,PaiKS>与 PairS有什么联系。这一限制看起来过于严格,但对于类型安全非常必要。假设允许将 Pair转换为 Pair。考虑下面代码:

Pai r managerBuddies = new Pairo(ceo, cfo);
Pair employeeBuddies = managerBuddies; // illegal, but suppose it wasn't
employeeBuddies.setFirst(1owlyEmployee);

                                      Java学习day057 泛型类型的继承规则、通配符类型(通配符概念、通配符的超类限定、无限定通配符、通配符捕获)_第1张图片

然而,最后一句是合法的。但是 employeeBuddies和 managerBuddies引用了同样的对象。现在将 CFO和 一 个 普 通 员 工 组 成 一 对,这 对 于 Pair说 应 该 是 不 可 能 的。

永远可以将参数化类型转换为一个原始类型。例如,PaiKEmployee>是原始类型Pair的一个子类型。在与遗留代码衔接时,这个转换非常必要。转换成原始类型之后会产生类型错误吗?很遗憾,会!看一看下面这个示例:

Pair managerBuddies = new Pairo(ceo, cfo);
Pair rawBuddies = managerBuddies; //OK
rawBuddies.setFirst(new File(". . .")); //only a compile-time warning

听起来有点吓人。但是,请记住现在的状况不会再比旧版 Java的情况糟糕。虚拟机的安全性还没有到生死攸关的程度。当使用 getFirst获得外来对象并赋给 Manager 变量时,与通常一样,会抛出 ClassCastException异常。这里失去的只是泛型程序设计提供的附加安全性。

最后,泛型类可以扩展或实现其他的泛型类。就这一点而言,与普通的类没有什么区别。例如,ArrayList类实现 List接口。这意味着,一个 ArrayList可以被转换为一个 List。但是,如前面所见,一个 ArrayList不是一个ArrayList 或 List。下图展示了它们之间的联系。                 

                       Java学习day057 泛型类型的继承规则、通配符类型(通配符概念、通配符的超类限定、无限定通配符、通配符捕获)_第2张图片


2.通配符概念

通配符类型中,允许类型参数变化。例如,通配符类型

Pair

表示任何泛型 Pair 类型,它的类型参数是 Employee 的子类,如 Pair,但不是Pair。假设要编写一个打印雇员对的方法,像这样:

public static void printBuddies(Pairp)
{
    Employee first = p.getFirst();
    Employee second = p.getSecond();
    Systefn.out.println(first.getName()+ " and " +second.getName()+" are buddies.");
}

正如前面讲到的,不能将 Pair传递给这个方法,这一点很受限制。解决的方法很简单:使用通配符类型:

public static void printBuddies(Pair p)

类型 Pair 是 Pair的子类型

                            Java学习day057 泛型类型的继承规则、通配符类型(通配符概念、通配符的超类限定、无限定通配符、通配符捕获)_第3张图片

使用通配符会通过 PaiK?extendsEmployee>的引用破坏 Pair吗?

Pair managerBuddies = new PairoCceo, cfo);
Pair wildcardBuddies = managerBuddies; //OK
wi1dcardBuddies.setFirst(1owlyEnployee);//compile-time error

这可能不会引起破坏。对 setFirst 的调用有一个类型错误。要了解其中的缘由,请仔细看一看类型 Pair

? extends Employee getFirst()
void setFirst(? extends Employee)

这样将不可能调用setFirst方法。编译器只知道需要某个Employee的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。毕竟?不能用来匹配。

使用getFirst就不存在这个问题:将getFirst的返回值赋给一个Employee的引用完全合法。这就是引人有限定的通配符的关键之处。现在已经有办法区分安全的访问器方法和不安全的更改器方法了。


3.通配符的超类型限定

通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超类型限定(supertypebound), 如下所亦:

? super Manager

这个通配符限制为 Manager 的所有超类型。(已有的 super关键字十分准确地描述了这种联系,这一点令人感到非常欣慰。)为什么要这样做呢? 带有超类型限定的通配符的行为与之前介绍的相反。可以为方法提供参数,但不能使用返回值。例如,Pair有方法

void setFirst(?super Manager)
? super Manager getFirst()

这不是真正的 Java语法,但是可以看出编译器知道什么。编译器无法知道 setFirst方法的具体类型,因此调用这个方法时不能接受类型为 Employee或 Object 的参数。只能传递Manager 类型的对象,或者某个子类型(如 Executive) 对象。另外,如果调用 getFirst, 不能保证返回对象的类型。只能把它赋给一个 Object。

下面是一个典型的示例。有一个经理的数组,并且想把奖金最高和最低的经理放在一个Pair 对象中。Pair 的类型是什么? 在这里,Pair是合理的,Pair也是合理的(如图所示)下面的方法将可以接受任何适当的 Pair:

public static void minmaxBonus(Manager[] a, Pair result)
{
    if (a.length == 0) return;
    Manager rain = a[0];
    Manager max = a[0];
    for (int i* 1 ; i < a.length; i++)
    {
        if (min.getBonusO > a[i].getBonus()) rain = a[i];
        if (max.getBonusO < a[i].getBonus()) max = a[i];
    }
    result.setFirst(min);
    result.setSecond(max);
}

直观地讲,带有超类型限定的通配符可以向泛型对象写人,带有子类型限定的通配符可以从泛型对象读取。

下面是超类型限定的另一种应用。Comparable接口本身就是一个泛型类型。声明如下:

public interface Comparable
{
    public int compareTo(T other);
}

在此,类型变量指示了 other 参数的类型。例如,String类实现 Comparable,它的 compareTo方法被声明为

public int compareTo(String other)

很好,显式的参数有一个正确的类型。接口是一个泛型接口之前,other 是一个 Object,并且这个方法的实现需要强制类型转换。由于 Comparable是一个泛型类型,也许可以把 ArrayAIg类的 min方法做得更好一些 ?可以这样声明:

public static >T min(T[] a)

                                Java学习day057 泛型类型的继承规则、通配符类型(通配符概念、通配符的超类限定、无限定通配符、通配符捕获)_第4张图片

看起来,这样写比只使用T extentsComparable更彻底,并且对许多类来讲,工作得更好。例如,如果计算一个 String数组的最小值,T就是 String类型的,而 String是Comparable的子类型。但是,处理一个 LocalDate对象的数组时,会出现一个问题。LocalDate实现了 ChronoLocalDate, 而 ChronoLocalDate扩展了 Comparable。因此,LocalDate实现的是 Comparable而不是 Comparableo在这种情况下,超类型可以用来进行救助:

public static > T min(T[] a) ...

现在 compareTo方法写成

int compareTo(? super T)

有可能被声明为使用类型 T 的对象,也有可能使用 T 的超类型(如当 T 是 LocalDate, T的一个子类型)。无论如何,传递一个 T类型的对象给 compareTo方法都是安全的。对于初学者来说,


4.无限定通配符

还可以使用无限定的通配符,例如,Pair。初看起来,这好像与原始的 Pair 类型一样。实际上,有很大的不同。类型 Pair有以下方法:

? getFirst()
void setFirst(?)

getFirst 的返回值只能赋给一个 Object。setFirst 方法不能被调用,甚至不能用 Object 调用。Pair和 Pair 本质的不同在于:可以用任意 Object 对象调用原始 Pair 类的 setObject方法。

为什么要使用这样脆弱的类型?它对于许多简单的操作非常有用。例如,下面这个方法将用来测试一个pair是否包含一个mill引用,它不需要实际的类型。

public static boolean hasNulls(Pairp)
{
    return p.getFirstO = null || p.getSecondO = null;
}

通过将 hasNulls 转换成泛型方法,可以避免使用通配符类型:

public static  boolean hasNulls(Pairp)

 

但是带有通配符的版本可读性更强。


5.通配符捕获

编写一个交换成对元素的方法:

public static void swap(Pairp)

通配符不是类型变量,因此,不能在编写代码中使用“?”作为一种类型。也就是说,下述代码是非法的:

? t=p.getFirst();//Error
p.setFirst(p.getSecond());
p.setSecond(t);

这是一个问题,因为在交换的时候必须临时保存第一个元素。幸运的是,这个问题有一个有趣的解决方案。我们可以写一个辅助方法swapHelper,如下所示:

public static  void swapHelper(Pair p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}

注意,swapHelper 是一个泛型方法,而 swap不是,它具有固定的 Pair类型的参数。现在可以由 swap调用 swapHelper:

public static void swap(Pair p) { swapHelper(p); }

在这种情况下,swapHelper方法的参数 T 捕获通配符。它不知道是哪种类型的通配符,但是,这是一个明确的类型,并且swapHelper 的定义只有在 T指出类型时才有明确的含义。当然,在这种情况下,并不是一定要使用通配符。我们已经直接实现了没有通配符的泛型方法 voidswap(Pairp)。然而,下面看一个通配符类型出现在计算中间的示例:

public static void maxminBonus(Manager ] a, Pair result)
{
    minmaxBonus(a, result);
    PairAlg.swap(result); //OK
    swapHel per captures wildcard type
}

在这里,通配符捕获机制是不可避免的。通配符捕获只有在有许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、确定的类型。例如,ArrayList>中的 T 永远不能捕获 ArrayList>中的通配符。数组列表可以保存两个 Pair,分别针对?的不同类型。

下面的程序将前几面讨论的各种方法综合在一起。

/**
 *@author  zzehao
 */

public class PairTest3
{
	public static void main(String[] args)
	{
		Manager ceo=new Manager("Gus Greedy",800000,2003,12,15);
		Manager cfo=new Manager("Sid Sneaky",600000,2003,12,15);
		Pair buddies=new Pair<>(ceo,cfo);
		printBuddies(buddies);
		
		ceo.setBonus(1000000);
		cfo.setBonus(500000);
		Manager[] managers={ceo,cfo};
		
		Pair result = new Pair<>();
		minmaxBonus(managers,result);
		System.out.println("first: "+result.getFirst().getName()+",second: "+result.getSecond().getName());
		maxminBonus(managers,result);
		System.out.println("first: "+result.getFirst().getName()+",second: "+result.getSecond().getName());
	}
	
	public static void printBuddies(Pairp)
	{
		Employee first = p.getFirst();
		Employee second = p.getSecond();
		System.out.println(first.getName()+ " and "+second.getName()+" are buddies.");
	}

	public static void minmaxBonus(Manager[] a, Pairresult)
	{
		if (a.length==0) return;
		Manager min = a[0];
		Manager max = a[0];
		for (int i = 1 ;i < a.length;i++)
		{
			if (min.getBonus()> a[i].getBonus()) min =a[i];
			if (max.getBonus() result)
	{
		minmaxBonus(a, result);
		PairAlg.swapHelper(result);//OK--swapHelper captures wildcard type
	}
}

class PairAlg
{
	public static boolean hasNulls(Pair p)
	{
		return p.getFirst()==null||p.getSecond()==null;
	}
	
	public static void swap(Pair p){swapHelper(p);}
	
	public static  void swapHelper(Pair p)
	{
		T t=p.getFirst();
		p.setFirst(p.getSecond());
		p.setSecond(t);
	}
}


 

你可能感兴趣的:(Java基础学习,java,编程语言)