Java 何时使用通用方法,何时使用通配符?

Java 何时使用通用方法,何时使用通配符?,java,generics,wildcard,Java,Generics,Wildcard,我正在读有关通用方法的文章。我对何时使用通配符和何时使用泛型方法的比较感到非常困惑。 从文件中引用 接口集合{ 公共图书馆(c组); 公共布尔addAll(Collection通配符方法也是泛型的-您可以使用某些类型范围调用它 语法定义了一个类型变量名。如果一个类型变量有任何用途(例如,在方法实现中或作为其他类型的约束),那么将其命名是有意义的,否则您可以使用?作为匿名变量。因此,这看起来只是一条捷径 此外,在声明字段时,?语法是无法避免的: class NumberContainer { S

我正在读有关通用方法的文章。我对何时使用通配符和何时使用泛型方法的比较感到非常困惑。 从文件中引用

接口集合{
公共图书馆(c组);

公共布尔addAll(Collection通配符方法也是泛型的-您可以使用某些类型范围调用它

语法定义了一个类型变量名。如果一个类型变量有任何用途(例如,在方法实现中或作为其他类型的约束),那么将其命名是有意义的,否则您可以使用
作为匿名变量。因此,这看起来只是一条捷径

此外,在声明字段时,
语法是无法避免的:

class NumberContainer
{
 Set<? extends Number> numbers;
}
类号容器
{

Set在某些地方,通配符和类型参数的作用是相同的。但也有某些地方,您必须使用类型参数

  • 如果要对不同类型的方法参数强制执行某种关系,则不能使用通配符,必须使用类型参数
  • 以您的方法为例,假设您希望确保传递给
    copy()
    方法的
    src
    dest
    列表应为相同的参数化类型,您可以使用如下类型参数执行此操作:

    public static <T extends Number> void copy(List<T> dest, List<T> src)
    
    在第二种情况下,您可以将
    List
    List
    作为
    dest
    src
    传递。因此,将元素从
    src
    移动到
    dest
    将不再是类型安全的。 如果您不需要这种关系,那么您可以完全不使用类型参数

    使用通配符和类型参数之间的其他一些区别是:

    • 若您只有一个参数化类型参数,那个么您可以使用通配符,尽管类型参数也可以工作
    • 类型参数支持多个边界,而通配符不支持
    • 通配符支持上界和下界,类型参数只支持上界。因此,如果要定义一个方法,该方法采用类型为
      Integer
      List
      ,或者它是超类,则可以执行以下操作:

      public void print(List<? super Integer> list)  // OK
      

      public void print(List在您的第一个问题中:这意味着如果参数的类型和方法的返回类型之间存在关系,则使用泛型

      例如:

      public <T> T giveMeMaximum(Collection<T> items);
      public <T> Collection<T> applyFilter(Collection<T> items);
      
      在这两个示例中,无论集合中项目的类型如何,返回类型都将是
      int
      boolean

      在您的示例中:

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

      你可以

      List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
      basket.add(new Apple());
      basket.add(new Pear());
      basket.add(new Tomato());
      List<Fruit> fridge = new ArrayList<Fruit>(); 
      
      Collections.copy(fridge, basket);// works 
      

      List我会试着逐一回答你的问题


      我们不认为通配符类似于
      (Collection考虑下面James Gosling第四版Java编程中的示例,其中我们希望合并2个SingleyLinkQueue:

      public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
          // merge s element into d
      }
      
      public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
              // merge s element into d
      }
      
      公共静态无效合并(SinglyLinkQueue d,SinglyLinkQueue s){
      //将s元素合并到d元素中
      }
      
      公共静态无效合并(SinglyLinkQueue d,SinglyLinkQueue此处未列出的另一个差异

      static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
          for (T o : a) {
              c.add(o); // correct
          }
      }
      
      来自ArrayToCollection的静态void(T[]a,集合c){
      对于(TO:a){
      c、 添加(o);//正确
      }
      }
      
      但以下操作将导致编译时错误

      static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
          for (T o : a) {
              c.add(o); // compile time error
          }
      }
      
      来自ArrayToCollection的静态void(T[]a,集合c){
      对于(TO:a){
      c、 add(o);//编译时错误
      }
      }
      
      据我所知,当严格需要通配符时,只有一个用例(即,可以表示使用显式类型参数无法表示的内容)。这就是需要指定下限的时候

      除此之外,通配符还可以编写更简洁的代码,如您提到的文档中的以下语句所述:

      泛型方法允许使用类型参数来表示 方法的一个或多个参数类型之间的依赖关系 和/或其返回类型 方法不应使用

      [……]

      使用通配符比声明显式更清晰、更简洁 类型参数,因此应尽可能首选

      [……]

      通配符还有一个优点,即它们可以在外部使用 方法签名,如字段、局部变量和数组的类型


      主要->通配符在非泛型方法的参数/参数级别强制泛型。 注意。默认情况下,它也可以在genericMethod中执行,但在这里,我们可以使用T本身而不是?来执行

      包装仿制药

      public class DemoWildCard {
      
      
          public static void main(String[] args) {
              DemoWildCard obj = new DemoWildCard();
      
              obj.display(new Person<Integer>());
              obj.display(new Person<String>());
      
          }
      
          void display(Person<?> person) {
              //allows person of Integer,String or anything
              //This cannnot be done if we use T, because in that case we have to make this method itself generic
              System.out.println(person);
          }
      
      }
      
      class Person<T>{
      
      }
      
      公共类DemoWildCard{
      公共静态void main(字符串[]args){
      DemoWildCard obj=新的DemoWildCard();
      对象显示(新人物());
      对象显示(新人物());
      }
      无效显示(人){
      //允许使用整数、字符串或任何字符
      //如果我们使用T,这是不可能做到的,因为在这种情况下,我们必须使这个方法本身具有通用性
      系统输出打印项次(人);
      }
      }
      班主任{
      }
      

      所以通配符有这样的特定用例。

      表示未知

      一般规则适用于: 您可以从中读取,但不能写入

      给定简单的pojo汽车

      class Car {
          void display(){
      
          }
      }
      
      这将编译

      private static <T extends Car> void addExtractedAgain1(List<T> cars) {
          T t = cars.get(1);
          t.display();
          cars.add(t);
      }
      
      private static void addExtractedAgain1(列出车辆){
      T=cars.get(1);
      t、 显示();
      加上(t);
      }
      
      此方法无法编译

      private static void addExtractedAgain2(List<? extends Car> cars) {
          Car car = cars.get(1);
          car.display();
          cars.add(car); // will not compile
      }
      

      private static void addExtractedAgain2(列表这不应该是评论吗?@BuhakeSindi抱歉,什么不清楚?为什么-1?我认为它回答了这个问题。这是一个奇怪的答案。它根本无法解释为什么需要使用
      。您可以将其重写为“public static void copy(列表dest,列表src))在这种情况下,很明显发生了什么。@kan。这才是真正的问题。您可以使用类型参数强制执行相同的类型,但不能使用通配符。使用两个不同的
      interface Fruit{}
      
      class Apple implements Fruit{}
      class Pear implements Fruit{}
      class Tomato implements Fruit{}
      
      List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
      basket.add(new Apple());
      basket.add(new Pear());
      basket.add(new Tomato());
      List<Fruit> fridge = new ArrayList<Fruit>(); 
      
      Collections.copy(fridge, basket);// works 
      
      class Collections {
          public static <T, S extends T> void copy(List<T> dest, List<S> src) {
          ...
      }
      
      List<Apple> basket = new ArrayList<Apple>();
      basket.add(new Apple());
      basket.add(new Apple());
      basket.add(new Apple());
      List<Fruit> fridge = new ArrayList<Fruit>();
      
      Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
      
      Collections.copy(List<Number> dest, List<? extends Number> src);
      
      //For double 
      Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.
      
      //For int
      Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.
      
      public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
          // merge s element into d
      }
      
      public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
              // merge s element into d
      }
      
      static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
          for (T o : a) {
              c.add(o); // correct
          }
      }
      
      static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
          for (T o : a) {
              c.add(o); // compile time error
          }
      }
      
      public class DemoWildCard {
      
      
          public static void main(String[] args) {
              DemoWildCard obj = new DemoWildCard();
      
              obj.display(new Person<Integer>());
              obj.display(new Person<String>());
      
          }
      
          void display(Person<?> person) {
              //allows person of Integer,String or anything
              //This cannnot be done if we use T, because in that case we have to make this method itself generic
              System.out.println(person);
          }
      
      }
      
      class Person<T>{
      
      }
      
      class Car {
          void display(){
      
          }
      }
      
      private static <T extends Car> void addExtractedAgain1(List<T> cars) {
          T t = cars.get(1);
          t.display();
          cars.add(t);
      }
      
      private static void addExtractedAgain2(List<? extends Car> cars) {
          Car car = cars.get(1);
          car.display();
          cars.add(car); // will not compile
      }
      
      List<?> hi = Arrays.asList("Hi", new Exception(), 0);
      
      hi.forEach(o -> {
         o.toString() // it's ok to call Object methods and methods that don't need the contained type
      });
      
      hi.add(...) // nothing can be add here won't compile, we need to tell compiler what the data type is but we do not know