java_通用方法

通用方法

考虑编写一个方法,该方法接受一组对象和一个集合,并将数组中的所有对象放入集合中。这是第一次尝试:

static void fromArrayToCollection(Object [] a,Collection <?> c){
     for(Object o:a){
        c.add(O); // 编译时错误
    }
}

到目前为止,您将学会避免初学者错误地尝试将其Collection<Object>用作集合参数的类型。您可能已经或可能没有认识到使用Collection<?>也不会起作用。回想一下,您不能只将对象推送到未知类型的集合中。

处理这些问题的方法是使用通用方法。就像类型声明一样,方法声明可以是通用的 - 也就是说,由一个或多个类型参数参数化。

static <T> void fromArrayToCollection(T [] a,Collection <T> c){
     for(T o:a){
        c.add(O); // 正确
    }
}

我们可以使用任何类型的集合调用此方法,其元素类型是数组元素类型的超类型。

Object [] oa = new Object [100];
Collection <Object> co = new ArrayList <Object>();

// T推断为Object
fromArrayToCollection(oa,co); 

String [] sa = new String [100];
集合<String> cs = new ArrayList <String>();

// T推断为String
fromArrayToCollection(sa,cs);

// T推断为Object
fromArrayToCollection(sa,co);

Integer [] ia = new Integer [100];
Float [] fa = new Float [100];
数字[] na =新数字[100];
Collection <Number> cn = new ArrayList <Number>();

// T推断为数字
fromArrayToCollection(ia,cn);

// T推断为数字
fromArrayToCollection(fa,cn);

// T推断为数字
fromArrayToCollection(na,cn);

// T推断为Object
fromArrayToCollection(na,co);

// 编译时错误
fromArrayToCollection(na,cs);

请注意,我们不必将实际类型参数传递给泛型方法。编译器根据实际参数的类型为我们推断类型参数。它通常会推断出使调用类型更正的最具体的类型参数。

出现的一个问题是:何时应该使用泛型方法,何时应该使用通配符类型?为了理解答案,我们来看一下Collection库中的一些方法。

interface Collection <E> {
     public boolean containsAll(Collection <?> c);
    public boolean addAll(Collection <?extends E > c);
}

我们可以在这里使用泛型方法:

interface Collection <E> {
     public <T> boolean containsAll(Collection <T> c);
    public <T extends E> boolean addAll(Collection <T> c);
    // 嘿,类型变量也可以有界限!
}

然而,在这两个containsAlladdAll,类型参数T只能使用一次。返回类型不依赖于类型参数,也不依赖于方法的任何其他参数(在这种情况下,只有一个参数)。这告诉我们类型参数用于多态; 它唯一的作用是允许在不同的调用站点使用各种实际的参数类型。如果是这种情况,则应使用通配符。通配符旨在支持灵活的子类型,这是我们在此尝试表达的内容。

通用方法允许使用类型参数来表示方法和/或其返回类型的一个或多个参数的类型之间的依赖关系。如果没有这种依赖关系,则不应使用通用方法。

可以串联使用通用方法和通配符。这是方法Collections.copy()

class Collections {
     public static <T> void copy(List <T> dest,List <?extends T> src){
    ...
}

注意两个参数类型之间的依赖关系。从源列表复制的任何对象src必须可分配给T目标列表的元素类型dst。所以元素类型src可以是任何子类型 - T我们不关心哪个。签名copy使用类型参数表示依赖关系,但使用通配符作为第二个参数的元素类型。

我们可以用另一种方式为这种方法编写签名,而不使用通配符:

class Collections {
     public static <T,S extends T> void copy(List <T> dest,List <S> src){
    ...
}

这很好,但是第一个类型参数同时用于dst第二个类型参数的类型和绑定中SS它本身只使用一次,其类型为src-nothing取决于它。这是我们可以S用通配符替换的标志。使用通配符比声明显式类型参数更清晰,更简洁,因此应尽可能优先使用。

通配符还具有以下优点:它们可以在方法签名之外使用,如字段类型,局部变量和数组。这是一个例子。

回到我们的形状绘制问题,假设我们想要保留绘图请求的历史记录。我们可以保持内部类的静态变量的历史Shape,并有drawAll()其传入的参数保存到历史领域。

静态列表<List <?extends Shape>>
    history = new ArrayList <List <?extends Shape >>();

public void drawAll(List <?extends Shape> shapes){
    history.addLast(形状);
    for(Shape s:shapes){
        s.draw(this);
    }
}

最后,再次让我们注意用于类型参数的命名约定。我们使用T类型,只要没有更具体的类型来区分它。通用方法通常就是这种情况。如果有多个类型参数,我们可能会使用字母表中邻居T的字母,例如S。如果泛型方法出现在泛型类中,最好避免对方法和类的类型参数使用相同的名称,以避免混淆。这同样适用于嵌套泛型类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值