[英]Instantiating generic type ArrayList<T>
我是泛型的新手并在文章中读到“参数化类型,例如ArrayList<T>
,不可实例化 - 我们无法创建它们的实例”。
完全引用,来自Java in a Nutshell:
参数化类型(如
ArrayList<T>
)不可实例化 - 我们无法创建它们的实例。 这是因为<T>
只是一个类型参数 - 仅仅是真正类型的占位符。 只有当我们为type参数(例如,ArrayList<String>
)提供具体值时,才会完全形成类型,并且我们可以创建该类型的对象。如果我们想要使用的类型在编译时未知,则会出现问题。 幸运的是,Java类型系统能够适应这一概念。 它通过具有未知类型的明确概念来表达,其表示为
<?>
。
我知道它不应该是可实例化的,因为具体(实际)类型是未知的。 如果是这样,为什么下面的代码编译没有错误?
public class SampleTest {
public static <T> List<T> getList(T... elements) {
List<T> lst = new ArrayList<>(); // shouldn't this line return an error?
return lst;
}
}
我知道我对仿制药的理解存在差距。 有人能指出我在这里缺少什么吗?
因为T
是另一个泛型类型参数。
泛型的全部目的是使类型可参数化。 因此调用者可以指定类型。 这可以在多个层中完成:调用者也可以是通用的,并让其调用者指定类型。
public static void main(String[] args)
{
foo(7);
}
public static <T> void foo(T value)
{
bar(value);
}
public static <U> void bar(U value)
{
baz(value);
}
public static <V> void baz(V value)
{
System.out.println(value.getClass().getSimpleName());
}
打印出来
Integer
参数化类型(例如
ArrayList<T>
)不可实例化
意思是:你不能创建未知T的ArrayList。它必须在编译时指定。 但它可以通过另一种通用方法间接完成。 在你的情况下,它是另一个 T
,它将由你的通用getList
的调用者再次指定。
通配符<?>
是不同的东西。 它用于指定兼容性。 <?>
是避免指定类型的语法。 您可以使用extends
来要求基本类型或接口。 但是,您无法使用通配符创建实例。
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();
否则这是不可能的。 在参数规范中使用它时最有意义,例如:
public static int foo(List<? extends Comparable> list)
{
return list.get(1).compareTo(list.get(2));
}
这本书非常混乱。 它假定<?>
以某种方式解决了无法实例化具有未知T
的List
的问题。 恕我直言,这是垃圾。 必须指定T才能创建实例。
你提到的代码可以编译,因为在调用方法之前,实际上没有初始化Object“lst”。 由于该方法知道它将获得类型为T的var-args参数,因此可以在此方案中进行编译。 以下面的Wrapper类为例:
public class Wrapper<T> {
public static <T> List<T> getList(T... elements){
List<T> lst = new ArrayList<>();
for(T element: elements) {
lst.add(element);
}
return lst;
}
}
此代码可以编译,因为尚未调用该方法。 调用该方法时,Type T将是我们作为var-args参数传递的类型,代码将没有问题编译。 让我们在主要方法中测试一下:
public static void main( String[] args ){
System.out.println(Wrapper.getList("Hi", "Hello", "Yo"));
}
输出是:
[Hi, Hello, Yo]
但是,让我们生成编译时错误,以查看文章在我们的main方法中讨论的内容:
Wrapper<T> myWrap = new Wrapper<>();
我们实际上是在上面的代码中尝试初始化Wrapper类的通用Object,但是未知。 由于占位符的值即使在调用方法时也是未知的,因此会导致编译时错误,而在getList方法中创建类型为T的List不会导致编译时错误,因为它将初始化为调用方法时的类型。
一旦调用方法 - >您正在使用具体值。
该方法定义了T,稍后您将在返回类型和参数列表中使用它。
public static <T> List<T> getList(T... elements)
一旦你将从特定类型发送第一个参数 - >合同将强制你下一个参数。
List<? extends Object> list = getList("", 1);
- >在这种情况下,java在字符串和整数之间找不到共同点,所以它使用最基本的连接“对象”
List<String> list2 = getList("test", "test2");
- >在这里你可以看到,因为所有的参数都是字符串 - java发现它的共同点并用它作为T.
书中的具体段落没有任何意义,也是错误的。 new ArrayList<T>()
完全没问题,前提是我们在一个名为T
的类型参数的范围内(我们所在的泛型类的类型参数,或者我们所在的泛型方法的类型参数)。
new ArrayList<T>()
可以实例化,而不是new ArrayList<String>()
- 两者都编译为相同的字节码,并且只是在运行时实例化一个ArrayList
对象。 该对象在运行时对其类型参数一无所知,因此在运行时不需要知道T
来实例化它。 参数化类型( new ArrayList<T>
)的实例化表达式中的type参数只是由编译器用于对传递给构造函数的参数进行类型检查(在这种情况下没有)并找出返回的类型表达方式; 它不以任何其他方式使用。
顺便说一下,该方法不需要接收任何类型为T
参数,也不需要接收包含T
的任何类型的参数,以使其工作。 不接收任何参数的方法仍然可以实例化并返回ArrayList<T>
完全正常:
public static <T> List<T> emptyList() {
List<T> lst = new ArrayList<T>();
return lst;
}
此外,书中出现此语句的部分与实例化没有任何关系 - 该部分是关于通配符的,并且通配符实际上与对象实例化没有任何关系。 所以我不确定为什么他们(错误地)在那里提到它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.