This article describes the advantages of generics in Java in relation to type safety and the rationale for introducing it in Java 5.
本文介绍了Java与类型安全有关的泛型优势以及在Java 5中引入泛型的原理。
为什么要泛型? (Why Generics?)
Consider the following lines of code.
考虑下面的代码行。
上面的代码有什么问题? (What is wrong with the above code?)
The first thing we notice is that we are defining a list without specifying what the content should be. In the above definition, List is known as a raw type and if you have read Effective Java, item 23 strongly discourages this. Why is this discouraged?
我们注意到的第一件事是,我们在定义列表时未指定内容是什么。 在上面的定义中,List被称为原始类型,如果您已经阅读了有效的Java,则第23项强烈建议不要这样做。 为什么不鼓励这样做?
Firstly, lines 14 and 15 adds integers into the list but while manipulating these items (like looping in 20 to 23), we may need to add explicit casts if we need to perform Integer-specific actions. The need for explicit casts makes code brittle and can lead to serious issues at runtime. Looking at lines 26–33, we see another danger. The method addItem takes a raw type List as its parameter and in line 27, we called the method with a list of integers. However, a double is added to the list. While assuming that the list contains integers, line 33 tries to cast the added double to an integer, which results in a ClassCastException at runtime. We are not warned by the compiler during this process, making it very dangerous. A good solution will be to use a generic method so that JVM warns us about any potential illegal casts at compile time.
首先,第14行和第15行将整数添加到列表中,但是在处理这些项目时(例如在20到23中循环),如果需要执行特定于Integer的操作,则可能需要添加显式强制转换。 对显式强制转换的需求使代码变得脆弱,并可能在运行时导致严重的问题。 查看第26-33行,我们看到了另一个危险。 方法addItem使用原始类型List作为其参数,在第27行中,我们使用整数列表调用该方法。 但是,一个双添加到列表。 尽管假定列表包含整数,但第33行尝试将添加的双精度转换为整数,这会在运行时导致ClassCastException。 在此过程中,编译器不会警告我们,这非常危险。 一个好的解决方案是使用通用方法,以便JVM在编译时警告我们任何潜在的非法强制转换。
泛型在编译时强制类型安全 (Generics enforces type safety at compile time)
With our knowledge of supertypes Java and that Objects are supertypes of all other types, we may be tempted to write the following generic code:
凭借对Java超类型的了解以及Object是所有其他类型的超类型的知识,我们可能会尝试编写以下通用代码:
static void addItemSafe(List<Object> items){
Double itemToAdd = new Double(40.98);
items.add(itemToAdd);
}
List<Integer> intList = new ArrayList<>();
addItemSafe(intList);
int firstvalue = intList.get(0); //will not compile and hence error will be fixed at compile time
However, this fails because of a property in Java called invariance (Lists are invariant while arrays are not). Due to invariance, line 7 will result in a compilation error thus, further enforcing compile-time type safety. This is possible because of generics. This is put in place to avoid unnecessary cast exceptions that may arise at runtime.
但是,由于Java中的一个属性称为不变性而导致此操作失败(列表不变,而数组不变)。 由于不变性,第7行将导致编译错误,从而进一步加强了编译时类型的安全性。 由于泛型,这是可能的。 这样做是为了避免在运行时可能出现不必要的强制转换异常。
通用类型 (Generic Types)
According to the official oracle docs, a generic type is a generic class or interface that is parameterized over types. Imagine we have a class called Box. A Box object can contain any type. To generify this class, we can use Object as follows:
根据官方的oracle docs,通用类型是在类型上参数化的通用类或接口。 想象我们有一个叫做Box的类。 Box对象可以包含任何类型。 为了泛化此类,我们可以如下使用Object:
package com.semanticsqaure.demos.generics;
public class Box {
private Object object;
void set(Object object) {
this.object = object;
}
Object get() {
return object;
}
public static void main(String[] args){
Box box = new Box();
box.set(10);
System.out.println(box.get());
Box secondBox = new Box();
secondBox.set("This is wrong");
}
}
However, as explained above, using Object as a common supertype is too generic and can lead to cast exceptions during runtime. Therefore, the class above can be changed like below:
但是,如上所述,将Object用作通用超类型太通用了,并且可能在运行时导致强制转换异常。 因此,可以如下更改上面的类:
package com.semanticsqaure.demos.generics;
public class GenericBox<T> {
private T object;
public T getObject() {
return object;
}
public void setObject(T object) {
this.object = object;
}
public static void main(String[] args){
GenericBox<Integer> intBox = new GenericBox<>();
intBox.setObject(1234);
//intBox.setObject(76.4); //compilation issue
System.out.println(intBox.getObject());
}
}
In the above example, our code will not compile when we try to add a Double into a box of Integers. In the above declaration, T is known as a type parameter while the entire class is known as a generic type.
在上面的示例中,当我们尝试将Double添加到Integer框中时,我们的代码将无法编译。 在上面的声明中,T被称为类型参数,而整个类被称为泛型类型。
有界泛型 (Bounded Generics)
In the example above, we may want to restrict the type argument, T, to a specific supertype, say, Number. A simple reason for this may be that we want our Box objects to only operate on numbers and nothing else.
在上面的示例中,我们可能希望将类型参数T限制为特定的超类型(例如Number)。 一个简单的原因可能是我们希望Box对象仅对数字进行操作,而不能对其他对象进行操作。
package com.semanticsqaure.demos.generics;
public class GenericBox<T extends Number> {
private T object;
public T getObject() {
return object;
}
public void setObject(T object) {
this.object = object;
}
public static void main(String[] args){
GenericBox<Integer> intBox = new GenericBox<>();
intBox.setObject(1234);
GenericBox<Double> doublesBox = new GenericBox<>();
doublesBox.setObject(76.4); //works as 76.4 is a number
System.out.println(intBox.getObject());
GenericBox<Double> stringsBox = new GenericBox<>();
stringsBox.setObject("I am not a GenericBox member"); //fails as String is not a number
intBox.setObject("I am not a member of box"); //Fails as string is not a number
}
}
In the above example, T is an upper-bounded type parameter and the bound is Number.
在上面的示例中,T是上限类型参数,界限是Number。
Also note that when we do not specify an upper bound as above, Object is explicitly used as the bound.
还要注意,当我们没有如上所述指定上限时,显式使用Object作为界限。
Therefore,
因此,
class Box<T> {} is the same as class Box<T extends Object> {}
Box <T> {}类与Box <T extends Object> {}相同
通用方法(Generic Methods)
Generic methods are infinitely overloaded, i.e. they can take arguments of any type (although we can also induce bounds to method parameter types). They also ensure type safety by allowing the compiler to flag out issues at compile time thus, avoiding type-specific exceptions that may spring up at runtime.
泛型方法是无限重载的,即它们可以接受任何类型的参数(尽管我们也可以得出方法参数类型的界限)。 它们还通过允许编译器在编译时标记问题来确保类型安全,从而避免了可能在运行时出现的特定于类型的异常。
通用方法:示例 (Generic Methods: Examples)
The Java Collections class defines a method called replaceAll() that reads as follows:
Java Collections类定义了一个名为replaceAll()的方法,其内容如下:
public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)
We read this as “Given a List of type T, replace all oldval in the list to newVal”
我们将其读为“给出类型T的列表,将列表中的所有oldval替换为newVal”
Notice that oldVal and newVal must be the same type parameter T.
请注意,oldVal和newVal必须是相同的类型参数T。
We can call the above method like:
我们可以像上面这样调用上面的方法:
List<Integer> someList = getListOfIntegers()booeal allReplaced = replaceAll(someList, 12, 10);boolean allReplaced = replaceAll(someList, 12, 10.4) // will result to a compilation issue
A fix for the addItems in the first example is seen below:
下面是第一个示例中对addItems的修复:
public static <T> void addItemSafe(List<T> items){
Double itemToAdd = new Double(40.98);
items.add(itemToAdd);
}
List<Integer> intList = new ArrayList<>();
List<Double> doubles = new ArrayList<>();
addItemSafe(intList, 12);
addItemSafe(doubles, 12.9);
List<String> stringList = Arrays.asList("Me", "You", "Us");
addItemSafe(stringList, "Them")
addItemSafe(doubles, 12); //will not work as 12 is autoboxed into an Integer
The method is generic and ensures that we don't add an item into a list of items that are not of the same type as that item
该方法是通用方法,可确保我们不会将一个项目添加到与该项目类型不同的项目列表中
Generic and Type Erasure
通用和类型擦除
In the above paragraphs, we have seen how generics can enforce compile-time safety. We now understand that it is better for an illegal code to fail the compilation stage than bursting out and production and thus, producing more undesirable outcomes. However, how does generics ensure compile-time type safety? To implement generics, the Java compiler does the following:
在以上各段中,我们已经看到了泛型如何强制执行编译时安全性。 我们现在了解到,使非法代码失败的编译阶段要比爆破和生产,从而产生更多不良结果更好。 但是,泛型如何确保编译时类型的安全性? 为了实现泛型,Java编译器执行以下操作:
- Replace all type parameters (T in the above sections) in the generic types with the bounds (in the case of upper bounded generics) or with Object (we have seen above that when no bound is specified, Object is used as the upper bound) if no bound is specified. The byte code produced at this step, therefore, contains only Interfaces, classes, and methods. 将所有类型参数(以上各节中的T)替换为泛型类型(使用上界泛型)或对象(如上所示,当未指定任何边界时,将对象用作上限)如果未指定界限。 因此,在此步骤中生成的字节码仅包含接口,类和方法。
- Insert casts where necessary and notify programmers of potential cast exceptions. 在必要时插入强制类型转换,并将可能的强制类型异常通知程序员。
由于类型擦除而导致的类型替换 (Type Replacement due to Type Erasure)
class Box<T> {
private T object;
public T getObject(){
return object;
}
public void setObject(T object){
this.object = object;
}
}
//T is unbounded and is replaced with Object
class Box {
private Object object;
public Object getObject(){
return object;
}
public void setObject(Object object){
this.object = object;
}
}
class Box<T extends Number> {
private T object;
public T getObject(){
return object;
}
public void setObject(T object){
this.object = object;
}
}
//T is bounded with Number and T is Number
class Box {
private Number object;
public Number getObject(){
return object;
}
public void setObject(Number object){
this.object = object;
}
}
In the first figure, T is replaced with Object since it is unbounded (remember any unbounded type is bounded by Object). However, in the second figure, T is replaced with Numer by the compiler because it is bounded by Number. The same replacement strategies also apply for generic methods.
在第一个图中,T被Object取代,因为它是无界的(记住任何无界类型都受Object约束)。 但是,在第二图中,编译器将T替换为Numer,因为它受Number限制。 相同的替换策略也适用于通用方法。
Class casting
类铸造
In addition to type replacement, the compiler also performs explicit casts during compilation to ensure type safety.
除了类型替换之外,编译器还在编译期间执行显式强制转换,以确保类型安全。
Box<Integer> intBox = new Box<>();
Box <Integer> intBox =新Box <>();
intBox.setObject(12.4) // 12.4 is autoboxed to Double and casted to Integer and since this is an illegal cast, the compiler throws a ClassCastException.
intBox.setObject(12.4)// 12.4自动装箱成Double并转换为Integer,由于这是非法转换,因此编译器将引发ClassCastException。
Here, Integer is the inferred type (Note that JVM uses complex algorithms for type inference that will not be discussed int this article)
在这里,Integer是推断的类型(请注意,JVM使用复杂的算法进行类型推断,本文将不再讨论)。
结论 (Conclusion)
Generics is a really cool feature in Java that ensures type safety at compile time. When used well, it prevents unnecessary illegal casts that may result at runtime. Apart from the fact that generic are invariant: we are not allowed to substitute a subtype with a supertype and vice-versa, the compiler also has a property called type erausre which further ensures type safety of generic types. Again, we can specify bounds to generic methods and generic types to further protect our code from illegal assignments as seen in the above sections. Apart from the advantage of making our code extremely flexible, generics also enforees type safety and we should take advantage of this where necessary.
泛型是Java中非常酷的功能,可确保编译时的类型安全。 如果使用得当,它可以防止在运行时导致不必要的非法强制转换。 除了泛型是不变的这一事实之外:不允许我们用超类型替换子类型,反之亦然,编译器还有一个称为类型erausre的属性,该属性进一步确保了泛型类型的类型安全。 同样,我们可以指定泛型方法和泛型类型的界限,以进一步保护我们的代码免受非法分配的影响,如上节所述。 除了使我们的代码具有极高的灵活性外,泛型还可以确保类型安全,因此我们应在必要时加以利用。
翻译自: https://medium.com/@mumaticha/introduction-to-generics-842e10b42e77