Java 泛型:列表、列表<对象>、列表<?>

Java 泛型:列表、列表<对象>、列表<?>

IT小君   2021-09-15T00:12:28

有人可以尽可能详细地解释以下类型之间的区别吗?

List
List<Object>
List<?>

让我更具体地说明这一点。我什么时候想用

// 1 
public void CanYouGiveMeAnAnswer(List l) { }

// 2
public void CanYouGiveMeAnAnswer(List<Object> l) { }

// 3
public void CanYouGiveMeAnAnswer(List<?> l) { }
服务器费用不足...
评论(12)
IT小君

正如其他帖子所指出的,您是在询问称为泛型的 Java 功能。在 C++ 中,这称为模板。Java 中的此功能通常比 C++ 中的功能更易于使用。

让我从功能上回答您的问题(如果这对 OO 讨论来说不是一个顽皮的话)。

在泛型之前,有像 Vector 这样的具体类。

Vector V = new Vector();

向量包含您提供给它们的任何对象。

V.add("This is an element");
V.add(new Integer(2));
v.add(new Hashtable());

他们通过将所有赋予它的值转换为一个对象(所有 Java 类的根)来实现这一点。当您尝试检索存储在 Vector 中的值时,您需要将该值转换回原始类(如果您想对其进行任何有意义的操作)。

String s = (String) v.get(0);
Integer i = (Integer) v.get(1);
Hashtable h = (Hashtable) v.get(2);

铸造会很快变老。不仅如此,编译器还会向您抱怨未经检查的强制转换。像这样的转换最紧迫的问题是 Vector 的使用者必须在编译时知道其值的类才能正确转换。在 Vector 的生产者和消费者完全相互隔离的情况下(想想 RPC 消息),这可能是一个致命的问题。

输入泛型。泛型尝试创建强类型类来执行泛型操作。

ArrayList<String> aList = new ArrayList<String>();
aList.add("One");
String element = aList.get(0); // no cast needed
System.out.println("Got one: " + element); 

设计模式》一书鼓励读者根据契约而非具体类型进行思考。从实现类中分离变量是明智的(和代码重用)。

考虑到这一点,你可能会认为,所有实现List对象应该做同样的一套东西:add()get()size()等有了一点点反思,你可以想像列表操作的许多实现服从以各种方式列表合同(例如ArrayList) . 但是,这些对象处理的数据类型与对它们执行的操作是正交的。

把它们放在一起,你会经常看到以下类型的代码:

List<String> L = new ArrayList<String>();

您应该将其读作“L 是一种处理字符串对象的列表”。当您开始处理工厂类时,处理契约而不是具体的实现是至关重要的。工厂在运行时生产各种类型的对象。

使用泛型非常简单(大多数时候)。

有一天,您可能决定要实现自己的泛型类。也许您想编写一个新的数据库抽象接口来消除各种数据存储之间的差异。当您定义该泛型类时,您将<t>用作将由方法操作的对象类型的占位符。

如果您仍然感到困惑,请使用 List 的泛型类,直到您感到满意为止。稍后,您可以更加自信地深入研究实现。或者,您可以查看 JRE 附带的各种 List 类的源代码。开源在这方面很棒。

查看有关泛型的 Oracle/Sun文档干杯。

2021-09-15T00:12:28   回复
IT小君

用我自己的简单来说:

列表

将声明一个普通集合,可以容纳任何类型,并且将始终返回 Object。

列表<对象>

将创建一个可以容纳任何类型对象的列表,但只能分配一个另一个List<Object>

例如,这不起作用;

List<Object> l = new ArrayList<String>();

当然你可以添加任何东西,但只能拉出对象。

List<Object> l = new ArrayList<Object>();

l.add( new Employee() );
l.add( new String() );

Object o = l.get( 0 );
Object o2 = l.get( 1 );

最后

列表<?>

会让你分配任何类型,包括

List <?> l = new ArrayList(); 
List <?> l2 = new ArrayList<String>();

这被称为收集未知以来的共同点未知的对象,你就能获取对象(巧合)

当与子类化一起使用时unknown的重要性就来了:

List<? extends Collection> l = new ArrayList<TreeSet>(); // compiles

List<? extends Collection> l = new ArrayList<String>(); // doesn't,
// because String is not part of *Collection* inheritance tree. 

我希望使用 Collection 作为类型不会造成混乱,这是我想到的唯一树。

这里的区别在于, l 是属于Collection层次结构未知集合

2021-09-15T00:12:28   回复
IT小君

我向您推荐优秀的Java 泛型教程“高级”泛型教程,两者都可以从 Sun Microsystems 获得。另一个很棒的资源是Java 泛型和集合这本书。

2021-09-15T00:12:29   回复
IT小君

在这里添加已经很好的答案:

方法参数:

List<? extends Foo>

如果您不打算更改列表,并且只关心列表中的所有内容都可以分配到类型“Foo”,那么这是一个不错的选择。这样,调用者可以传入 List<FooSubclass> 并且您的方法可以工作。通常是最好的选择。

List<Foo>

如果您打算将 Foo 对象添加到您的方法中的列表中,这是一个不错的选择。调用方可能不会传入 List<FooSubclass>,因为您打算将 Foo 添加到 List。

List<? super Foo>

如果您打算将 Foo 对象添加到列表中,这是一个不错的选择,并且列表中的其他内容并不重要(即,您可以获取包含与 Foo 无关的“Dog”的 List<Object> )。

方法返回值

就像方法参数一样,但好处相反。

List<? extends Foo>

保证返回的 List 中的所有内容都具有“Foo”类型。不过它可能是 List<FooSubclass> 。来电者无法添加到列表中。这是您的首选,也是迄今为止最常见的情况。

List<Foo>

就像 List<? 扩展 Foo> 但也允许调用者添加到列表中。不常见。

List<? super Foo>

允许调用者将 Foo 对象添加到 List,但不保证将从 list.get(0) 返回什么......它可以是从 Foo 到 Object 的任何东西。唯一的保证是这不会是一个 'Dog' 列表或其他一些会阻止 list.add(foo) 合法的选择。非常罕见的用例。

我希望这有帮助。祝你好运!

附:总结一下……两个问题……

您需要添加到列表中吗?你关心列表中的内容吗?

是 是 - 使用 List<Foo>。

是 否 - 使用 List<? 超级Foo>。

否 是 - 使用 <? 扩展 Foo> --- 最常见。

不不 - 使用 <?>。

2021-09-15T00:12:29   回复
IT小君

我会尽量详细回答这个问题。在泛型之前,我们只有List(一个原始列表),它几乎可以容纳我们能想到的任何东西。

List rawList = new ArrayList();
rawList.add("String Item");
rawList.add(new Car("VW"));
rawList.add(new Runnable() {
            @Override
            public void run() {
               // do some work.
            }
        });

原始列表的主要问题是当我们想从这样的列表中取出任何元素时,它只能保证它会是Object这样,因此我们需要使用强制转换为:

   Object item = rawList.get(0); // we get object without casting.
   String sameItem = (String) rawList.get(0); // we can use casting which may fail at runtime.

所以结论是一个List可以存储的对象(Java 中几乎所有东西都是对象)并且总是返回一个对象。

泛型

现在让我们谈谈泛型。考虑以下示例:

List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
stringsList.add(new Car("Fiat")); //error
String stringItem = stringsList.get(0);

在上述情况下,我们不能插入除Stringin之外的任何内容stringsList因为 Java 编译器对泛型代码应用强类型检查,如果代码违反类型安全,则会发出错误。当我们尝试在其中插入一个Car实例时会出错它还消除了强制转换,因为您可以在我们invoke获取方法时检查检查此链接以了解为什么我们应该使用泛型

List<Object>

如果您阅读有关类型擦除的内容,那么您将了解List<String>, List<Long>, List<Animal>等在编译时将具有不同的静态类型,但List在运行时将具有相同的动态类型

如果我们有,List<Object>那么它只能存储Object在其中,并且几乎所有内容都Object在 Java 中。所以我们可以有:

 List<Object> objectList = new ArrayList<Object>();
 objectList.add("String Item");
 objectList.add(new Car("VW"));
 objectList.add(new Runnable() {
        @Override
        public void run() {

        }
 });
 Object item = objectList.get(0); // we get object without casting as list contains Object
 String sameItem = (String) objectList.get(0); // we can use casting which may fail at runtime.

看起来List<Object>List是一样的,但实际上它们不是。考虑以下情况:

List<String> tempStringList = new ArrayList<>();
rawList = tempStringList; // Ok as we can assign any list to raw list.
objectList = tempStringList; // error as List<String> is not subtype of List<Obejct> becuase generics are not convariant.

您可以看到我们可以将任何列表分配给原始列表,主要原因是允许向后兼容。由于类型擦除,List<String>List在运行时转换为,无论如何分配都可以。

但是List<Object>意味着它只能引用一个对象列表,也只能存储对象。即使String是子类型Object,我们不能分配List<String>List<Object>泛型不是协变的像阵列。它们是不变的。另请查看此链接以获取更多信息。还要检查这个问题中List之间的区别List<Object>

List<?>

现在我们剩下List<?>which 基本上意味着未知类型的列表,可以引用任何列表。

List<?> crazyList = new ArrayList<String>();
 List<String> stringsList = new ArrayList<>();
 stringsList.add("Apple");
 stringsList.add("Ball");
 crazyList = stringsList; // fine

该字符?称为通配符,List<?>是无界通配符列表。现在有一些需要注意的地方。

我们无法实例化此列表,因为以下代码将无法编译:

List<?> crazyList = new ArrayList<?>(); // any list.

我们可以说通配符参数化类型更像是接口类型,因为我们可以使用它来引用兼容类型的对象,但不能引用它本身。

List<?> crazyList2 = new ArrayList<String>();

我们无法向其中插入任何项目,因为我们不知道实际类型是什么。

crazyList2.add("Apple"); // error as you dont actually know what is that type.

现在问题出现了我什么时候想使用List<?>

您可以将其视为只读列表,其中您不关心项目的类型。您可以使用它来调用方法,例如返回列表的长度、打印它等。

 public static void print(List<?> list){
        System.out.println(list);
    }

您还可以在List, List<?>, List<T>, List<E>, and List<Object> 此处查看 之间的区别

2021-09-15T00:12:29   回复
IT小君

不是“RTFM”的最简单的解释:

List

会产生很多编译器警告,但大部分等同于:

List<Object>

尽管:

List<?>

基本上意味着它是通用的,但你不知道通用类型是什么。当您无法修改刚刚返回 List 的其他事物的返回类型时,它非常适合摆脱编译器警告。它的形式更有用:

List<? extends SomeOtherThing>
2021-09-15T00:12:30   回复
IT小君

最简短的解释是:第二项是一个可以容纳任何类型的列表,您可以向其中添加对象:

List<Object>

您列出的第一项被视为与此基本等效,除非您将收到编译器警告,因为它是“原始类型”。

List

第三个是可以容纳任何类型的列表,但不能向其中添加任何内容:

List<?> 

基本上,List<Object>当您真正拥有一个可以包含任何对象的列表并且您希望能够向列表中添加元素时,您可以使用第二种形式 ( )。List<?>当您收到作为方法返回值的列表时,您使用第三种形式 ( ) 并且您将遍历该列表但从不向其中添加任何内容 不要List在 Java 5 或更高版本下编译的新代码中使用第一种形式 ( )。

2021-09-15T00:12:30   回复
IT小君

我会这样说:虽然ListList<Object>可以包含任何类型的对象,List<?>包含未知类型的元素,但是一旦捕获了该类型,它就只能包含该类型的元素。这就是为什么它是这三个变体中唯一的类型安全变体,因此通常更可取。

2021-09-15T00:12:30   回复
IT小君

为了补充 Rob 提到的教程,这里有一本解释该主题的 wikibook:http :
//en.wikibooks.org/wiki/Java_Programming/Generics


编辑:

  1. 对列表中的项目类型没有限制

  2. 列表中的项目必须扩展对象

  3. 通配符本身使用,所以它匹配任何东西

在这一点上得出几乎没有任何区别/根本没有区别的结论是否太天真了?

2021-09-15T00:12:30   回复
IT小君

我什么时候想用

public void CanYouGiveMeAnAnswer( List l ){}

当你不能做所有的铸造你自己。

我什么时候想用

public void CanYouGiveMeAnAnswer( List l<Object> ){}

当您想限制 List 的类型时。例如,这将是一个无效的参数。

 new ArrayList<String>();

我什么时候想用

public void CanYouGiveMeAnAnswer( List l<?> ){}

主要是从来没有。

2021-09-15T00:12:30   回复
IT小君

List, List<?>, and List<? extends Object>是一样的。第二个更明确。对于这种类型的列表,您无法知道将哪些类型放入其中是合法的,并且您对可以从中获取的类型一无所知,除了它们将是对象。

List<Object> 具体意味着该列表包含任何类型的对象。

假设我们列出了Foo

List<Foo> foos= new ArrayList<Foo>();

将 aBar放入 foos是不合法的

foos.add(new Bar()); // NOT OK!

将任何内容放入List<Object>.

List<Object> objs = new ArrayList<Object>();
objs.add(new Foo());
objs.add(new Bar());

但是您不能被允许将 aBar放入 a List<Foo>- 这就是重点。所以这意味着:

List<Object> objs = foos; // NOT OK!

不合法。

但是可以说 foos 是一个列表,但我们不知道它具体是什么:

List<?> dontKnows = foos;

但这意味着必须禁止去

dontKnows.add(new Foo()); // NOT OK
dontKnows.add(new Bar()); // NOT OK

因为变量 dontKnows 不知道哪些类型是合法的。

2021-09-15T00:12:31   回复
IT小君

List < Object > 用于传递 Object 的输入类型参数。虽然列表 < ? > 代表通配符类型。通配符 < ? > 是未知参数类型。通配符不能用作泛型方法的类型参数,也不能用于创建类的泛型实例。通配符可用于扩展子类型类 List < ? 扩展数字 >。放宽对象类型的限制,在这种情况下放宽“数字”对象类型。

2021-09-15T00:12:31   回复