<T>列表<吗?扩展 T> f() 一个有用的签名

2022-09-01 06:55:38

是有用的签名吗?它/使用它有任何问题吗?<T> List<? extends T> f()

这是一个面试问题。我知道这个:

  1. 编译精细
  2. 像 这样使用它,然后我只能调用那些签名中不包含 TList 方法(例如,isEmpty()、size(),但不能添加(T)、remove(T)List<? extends Number> lst = obj.<Number>f()

这是否完全回答了这个问题?


答案 1

此方法签名是“有用的”,从某种意义上说,您可以使用它实现非平凡的、非退化的方法(也就是说,返回和抛出错误不是您唯一的选择)。如下面的示例所示,这样的方法对于实现某些代数结构(例如幺半群)非常有用。null

首先,观察这是具有以下属性的类型:List<? extends T>

  • 您知道此列表的所有元素都符合 类型 ,因此每当您从此列表中提取元素时,都可以在预期的位置使用它。您可以从此列表中读取TT
  • 确切的类型是未知的,因此您永远无法确定是否可以将 特定子类型的 实例添加到此列表中。也就是说,您实际上无法将新元素添加到此类列表中(除非您使用s /类型转换/利用Java类型系统的不合理性,即)。Tnull

结合使用,这意味着它类似于追加保护列表,具有类型级追加保护。List<? extends T>

您实际上可以使用此类“追加保护”列表进行有意义的计算。以下是一些示例:

  • 您可以使用单个元素创建受追加保护的列表:

    public static <T> List<? extends T> pure(T t) {
      List<T> result = new LinkedList<T>();
      result.add(t);
      return result;
    }
    
  • 您可以从普通列表创建追加保护的列表:

    public static <T> List<? extends T> toAppendProtected(List<T> original) {
      List<T> result = new LinkedList<T>();
      result.addAll(original);
      return result;
    }
    
  • 您可以合并受追加保护的列表:

    public static <T> List<? extends T> combineAppendProtected(
      List<? extends T> a,
      List<? extends T> b
    ) {
      List<T> result = new LinkedList<T>();
      result.addAll(a);
      result.addAll(b);
      return result;
    }
    
  • 而且,对于此问题,最重要的是,您可以实现一个返回给定类型的空追加保护列表的方法:

    public static <T> List<? extends T> emptyAppendProtected() {
      return new LinkedList<T>();
    }
    

一起形成一个实际的代数结构(一个幺半群),以及诸如确保它是非退化的(即它有更多的元素只是一个空列表)之类的方法。实际上,如果你有一个类似于通常的Monoid typeclass的接口:combineemptypure

  public static interface Monoid<X> {
    X empty();
    X combine(X a, X b);
  }

然后你可以使用上面的方法来实现它,如下所示:

  public static <T> Monoid<List<? extends T>> appendProtectedListsMonoid() {
    return new Monoid<List<? extends T>>() {
      public List<? extends T> empty() {
        return ReadOnlyLists.<T>emptyAppendProtected();
      }

      public List<? extends T> combine(
        List<? extends T> a,
        List<? extends T> b
      ) {
        return combineAppendProtected(a, b);
      }
    };
  }

这表明,在你的问题中给出签名的方法可以用来实现一些常见的设计模式/代数结构(幺半群)。诚然,这个例子有点人为的,你可能不想在实践中使用它,因为你不想让你的API用户感到惊讶


完整的可编译示例:

import java.util.*;

class AppendProtectedLists {

  public static <T> List<? extends T> emptyAppendProtected() {
    return new LinkedList<T>();
  }

  public static <T> List<? extends T> combineAppendProtected(
    List<? extends T> a,
    List<? extends T> b
  ) {
    List<T> result = new LinkedList<T>();
    result.addAll(a);
    result.addAll(b);
    return result;
  }

  public static <T> List<? extends T> toAppendProtected(List<T> original) {
    List<T> result = new LinkedList<T>();
    result.addAll(original);
    return result;
  }

  public static <T> List<? extends T> pure(T t) {
    List<T> result = new LinkedList<T>();
    result.add(t);
    return result;
  }

  public static interface Monoid<X> {
    X empty();
    X combine(X a, X b);
  }

  public static <T> Monoid<List<? extends T>> appendProtectedListsMonoid() {
    return new Monoid<List<? extends T>>() {
      public List<? extends T> empty() {
        return AppendProtectedLists.<T>emptyAppendProtected();
      }

      public List<? extends T> combine(
        List<? extends T> a,
        List<? extends T> b
      ) {
        return combineAppendProtected(a, b);
      }
    };
  }

  public static void main(String[] args) {
    Monoid<List<? extends String>> monoid = appendProtectedListsMonoid();
    List<? extends String> e = monoid.empty();
    // e.add("hi"); // refuses to compile, which is good: write protection!
    List<? extends String> a = pure("a");
    List<? extends String> b = pure("b");
    List<? extends String> c = monoid.combine(e, monoid.combine(a, b));
    System.out.println(c); // output: [a, b]
  }

}

答案 2

我将“它是否是一个有用的签名”解释为“你能想到它的用例吗”。

T是在调用站点确定的,而不是在方法内部确定的,因此只能从该方法返回两个内容:null 或空列表。

鉴于您可以在与调用此方法大致相同的代码中创建这两个值,因此没有充分的理由使用它。


实际上,可以安全返回的另一个值是所有元素都为 null 的列表。但这也没有用,因为您只能调用从返回值添加或删除文本 null 的方法,因为 in 类型绑定。所以你所拥有的只是计算它所包含的空值数量的东西。这也无用。? extends


推荐