Java教程
  • Introduction
  • Getting Started
    • The Java Technology Phenomenon
      • About the Java Technology
      • What Can Java Technology Do?
      • How Will Java Technology Change My Life?
    • The Hello World Application
    • A Closer Look at the Hello World Application
  • Learning the Java Language
    • Object-Oriented Programming Concepts
      • What Is an Object?
      • What Is a Class?
      • What Is Inheritance?
      • What Is an Interface?
      • What Is a package?
    • Language Basics
      • Java Language Keywords
    • Annotations
      • Annotations Basics
      • Declaring an Annotation Type
      • Predefined Annotation Types
      • Repeating Annotations
      • Type Annotations and Pluggable Type Systems
    • Generics
      • Why Use Generics?
      • Generic Types
        • Raw Types
      • Generic Methods
      • Bounded Type Parameters
        • Generic Methods and Bounded Type Parameters
      • Generics, Inheritance, and Subtypes
      • Type Inference
      • Wildcards
        • Upper Bounded Wildcards
        • Unbounded Wildcards
        • Lower Bounded Wildcards
        • Wildcards and Subtyping
        • Wildcard Capture and Helper Methods
        • Guidelines for Wildcard Use
      • Type Erasure
        • Erasure of Generic Types
        • Erasure of Generic Methods
        • Effects of Type Erasure and Bridge Methods
        • Non-Reifiable Types
      • Restrictions on Generics
Powered by GitBook
On this page

Was this helpful?

  1. Learning the Java Language
  2. Generics
  3. Wildcards

Wildcard Capture and Helper Methods

PreviousWildcards and SubtypingNextGuidelines for Wildcard Use

Last updated 5 years ago

Was this helpful?

有些情况下,编译器会推断统配的类型。比如,一个列表可能定义为List<?>,但是当计算表达式时,编译器通过代码推断出特定的类型。这个场景被称为wildcard capture(通配符捕获)

在大多数情况下,我们无需担心通配符捕获,除非我们看到一条错误消息,其中包含capture of

例子在编译时产生一个捕获错误:

import java.util.List;

public class WildcardError {

    void foo(List<?> i) {
        i.set(0, i.get(0));
    }
}

在上面的代码中,编译器将输入参数i处理为Object类型。当函数foo调用时。编译器无法确认插入列表对象的类型,因此生成了一个错误。当发生这种类型的错误时,通常意味着编译器认为我们正在将错误的类型分配给变量。Java语言中加入泛型为的是————在编译时确保类型安全。

当使用Oracle's JDK 7javac实现来编译上面WildcardError的例子会产生如下报错:

WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;
    i.set(0, i.get(0));
     ^
  required: int,CAP#1
  found: int,Object
  reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error
public class WildcardFixed {

    void foo(List<?> i) {
        fooHelper(i);
    }

    // Helper method created so that the wildcard can be captured
    // through type inference.
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }
}

归功于辅助函数,编译器在调用中使用推断来确定T为CAP#1(捕获变量),现在例子能顺利编译了。

按照约定,辅助方法通常命名为originalMethodNameHelper

import java.util.List;

public class WildcardErrorBad {

    void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
      Number temp = l1.get(0);
      l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
                            // got a CAP#2 extends Number;
                            // same bound, but different types
      l2.set(0, temp);        // expected a CAP#1 extends Number,
                            // got a Number
    }
}

在这个例子中,代码正进行不安全的操作。比如,看下面对于swapFirst函数的操作:

List<Integer> li = Arrays.asList(1, 2, 3);
List<Double>  ld = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(li, ld);

List <Integer>和List <Double>都满足条件List <?extends Number>,但是从Integer列表中取出一项并将其放入Double值列表中显然是不正确的。

使用Oracle's JDK javac编译器编译此段代码会产生如下报错:

WildcardErrorBad.java:7: error: method set in interface List<E> cannot be applied to given types;
      l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
        ^
  required: int,CAP#1
  found: int,Number
  reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:10: error: method set in interface List<E> cannot be applied to given types;
      l2.set(0, temp);      // expected a CAP#1 extends Number,
        ^
  required: int,CAP#1
  found: int,Number
  reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:15: error: method set in interface List<E> cannot be applied to given types;
        i.set(0, i.get(0));
         ^
  required: int,CAP#1
  found: int,Object
  reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
3 errors

没有辅助函数能修复这个问题,因为代码从根本上就是错误的。

这个例子中,代码试图进行安全操作,所以我们怎么处理编译器的报错呢。我们可通过编写一个捕获通配的private helper method(私有辅助函数)来修复。在这个例子中,我们可以通过创建一个私有辅助函数fooHelper来修复问题,代码如下:

现在来看一个更复杂的例子:

WildcardError
List.set(int, E)
WildcardFixed
WildcardErrorBad