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

标签 java generics signature

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

这是一道面试题。我知道这一点:

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

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

最佳答案

这个方法签名是“有用的”,你可以用它实现非平凡、非退化的方法(也就是说,返回 null 并抛出错误不是你唯一的选项)。如以下示例所示,这种方法可用于实现一些代数结构,例如类半群。

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

  • 你知道这个列表的所有元素都符合类型 T ,所以每当你从这个列表中提取一个元素时,你可以在 T 的位置使用它是期待。您可以从此列表中阅读
  • 确切的类型未知,因此您永远无法确定 T 的特定子类型的实例可以添加到此列表中。也就是说,您实际上不能向这样的列表添加新元素(除非您使用 null s/类型转换/利用 Java 类型系统的不健全性)。

组合起来表示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>();
    }
    

一起,combineempty形成一个实际的代数结构(一个幺半群),以及像 pure 这样的方法确保它是非退化的(即它有更多的元素,而只是一个空列表)。事实上,如果你有一个类似于通常的界面 Monoid类型类:

  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);
      }
    };
  }

这表明具有您问题中给出的签名的方法可用于实现一些常见的设计模式/代数结构(monoids)。诚然,这个例子有点做作,你可能不想在实践中使用它,因为你 don't want to astonish the users of your API too much .


完整的可编译示例:

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]
  }

}

关于java - 是 <T> 列表<?扩展 T> f() 一个有用的签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54004821/

相关文章:

java - 使用 Java 列表 hibernate 多对多双向映射

java - 泛型方法的类型参数有什么作用?

java - 泛型实现 Map.entrySet() 时遇到问题

excel - 从 Excel VBA 将签名插入 Outlook 电子邮件

.net - 如何从 F# dll 重建 F# 程序集签名

Java环境依赖编译

java - 正则表达式 Java,为什么这个正则表达式这么慢?

java - 不常见泛型类型的接口(interface)列表

typescript - 递归类型定义似乎无法处理泛型?

c++ - 查找与 C++ dll 中的方法一起使用的参数