【问题标题】:How does casting and Generics work?铸造和泛型如何工作?
【发布时间】:2015-02-16 18:07:37
【问题描述】:

在阅读了一些示例之后,我仍然无法完全理解泛型是什么。 到目前为止我发现的最好的解释是What does <> (angle brackets) mean in Java?

根据我的阅读,如果您使用泛型,则不需要将返回值转换为类型。

但是泛型实际上做了什么?据我了解,确保(?)返回值已经转换为括号中定义的类型。

但这意味着,Java 需要强制转换每个分配。

String str = (String) otherstring;

是正确的,并且

String str = otherstring;

不合适。

所以根据我的理解,通用 &lt;String&gt; 会确保在分配值之前强制转换,所以我们不必强制转换它?

Java 是否需要强制转换每个值,即使它的类型已经相同?

【问题讨论】:

  • 泛型的主要思想是克服运行时异常并提供编译时警告。
  • Java 通常不需要任何类型转换,除非在向下转换的特殊情况下,应该避免这种情况。如果您必须使用泛型进行转换,那是因为您正在使用一个不使用泛型的库,但您确实使用了。或者,您正在做一些可能不安全的事情,或者您正试图弯曲一个专为泛型设计的类来做一些它原本不打算做的事情。有时这种情况是有效的(但不经常),因此对于那些极端情况允许进行强制转换操作。尽量不要使用它们。

标签: java generics casting


【解决方案1】:

PHP 在某种意义上更容易,但这只是因为它不关心其中的一些问题。使用 PHP,您可以稍后处理这些问题。

铸造基本上是在两个方向之一(向上或向下)。向上转换表示您希望仅通过它继承自(或继承其接口)的类的接口来处理该对象。

例如,如果我有一个 FileInputStream,我可以将其更改为“只是”一个 InputStream

FileInputStream fileInput = ...
InputStream input = (InputStream)fileInput;

请注意,这对实现可达方法的影响为零;但有时确实会隐藏以前可用的方法。

fileInput.getFileName();  // let's pretend this works
input.getFileName(); // this shouldn't work, as any InputStream doesn't have a file name.

向下转换是不同的(而且很危险)。你基本上说,即使你有一个 InputStream,你也知道它应该被视为一个 FileInputStream

InputStream input2 = ...;
FileInputStream fileInput2 = (FileInputStream)input2;

如果此场景中的 input2 没有适当的 FileInputStream 类型,那么您将在运行时收到类转换异常。如果它确实具有适当的类型,则将分配 fileInput2。

这种类型系统的主要原因是您可以轻松编写可重用的组件。 “更高”类型(向上类型)指定一般合同,而“较低”类型(向下类型)指定在其超类型指定的一般合同中变化的特定细节。

泛型;但是,是不同的蜡球。基本上在处理 Collections 以及与 Collections 相关的事情时,规则是相似的(但由于多种原因必须有所不同)。

泛型行为不同的原因之一是向后兼容性。将“额外”类型信息添加到已经存在的类型(如 java.util.List)的设计目标意味着对于混合泛型和非泛型使用,该类型实际上不能在运行时存在。这种在运行时不被认为存在的泛型类型的属性称为“擦除”。总之,当你写

List<Student> students = new ArrayList<Student>();

你正在有效地编译

List students = new ArrayList();

但是您的编译器会做“额外”工作以确保在您编写的任何源代码中,您只能添加一个“学生”对象。同样,当您阅读列表时,不需要强制转换为“Student”对象,因为编译器会假定列表中只能包含“Student”对象。

请注意,要使这样的系统工作,它只是编译时的。这意味着它们的类型层次结构和运行时转换的详细信息不适用。这反映在泛型添加的新类型约束中。

T extends Student

表示可以将 T 转换为 Student(向上转换)

T super Student

表示 T 是 Student 的一个超类,后者比较棘手,但在某些场景中很有用。

如果CollegeStudent、HighSchoolStudent和GradeSchoolStudent都扩展了Student,那么这三种Student都可以存储在一个List中

如果需要确保一个集合至少提供学生,那么你可以给出一个列表.

public void studentProcessor(Collection<? super Student> students);

可以处理 List、List、List

【讨论】:

  • developer.android.com/training/load-data-background/… 我这里有 CursorLoader 返回。但在定义上有。这意味着我将在返回之前将 CursorLoader 剥离到 Cursor 上?
  • @Gacek 在您的情况下, 意味着当您处理另一种类型的包装器时,包装器只能包含一个游标。您不能假设它是 Cursor 的特定子类,也不能将包装器作为所有碰巧都是 Cursor 的未知类型的混合集合来访问。以另一种方式重申第二个限制,禁止您知道光标类型以下的所有内容。
  • 现在我开始明白了。需要深入了解 Java 基础知识的更多细节才能完全理解它。感谢您的详细解释(也感谢大家的解释!)
  • @Gacek 很高兴为您提供帮助。祝你好运。
  • 根据您答案的最后一部分:-public void studentProcessor(Collection&lt;? super Student&gt; students); 可以处理 List、List、List 等。但是由于 CollegeStudent 和 GradeSchoolStudent 是子类的学生他们不能添加到? super Student,可能是Student的超类,比如Person,那么List&lt;Person&gt;可以被处理
【解决方案2】:

强制转换和使用泛型的区别在于它们是一种编译时机制。这意味着,下面的代码不会得到运行时异常,而是根本无法编译:

Clazz <T> c = new Clazz <U> ();

如果 T 不能转换为 U。

【讨论】:

    【解决方案3】:

    如果您有一个实例可以通过type参数化的类,请使用泛型。常见的例子是集合。

    在泛型之前,Collection 包含许多被归类为 Objects 的对象。尽管对象有可能具有编译器无法识别的更具体的类型,因此需要危险的运行时强制转换为实际类型。

    现在您可以参数化Collection&lt;E&gt;,其中E 是元素类型。现在编译器可以确保 Collection&lt;Foo&gt; 只添加了 Foo 实例,并且它可以返回类型化的 Foo 实例而无需强制转换。

    【讨论】:

      【解决方案4】:

      泛型类为您可以想象的任何对象提供功能 - 这个处理的类有时称为您的参数。例如java.util.List 接口及其实现类,例如ArrayList&lt;E&gt; 为任意类型 E 提供列表功能(添加元素、删除元素、元素计数等)(它可用于管理您自己类型的列表 Maroun)。泛型类型的实现必须独立于它处理的参数。在示例中 - List 独立于 Maroun

      所以泛型的概念与类型转换几乎没有关系,正如你所看到的 ;)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 2018-11-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多