Home Java javaTutorial Summary of Java Generics (1) - Detailed explanation of basic usage and type erasure

Summary of Java Generics (1) - Detailed explanation of basic usage and type erasure

Mar 22, 2017 am 10:22 AM

This article mainly introduces the use of Java generics and issues related to type erasure. Has very good reference value. Let’s take a look at it with the editor

Introduction

Java introduced the generic mechanism in 1.5. The essence of generics is parameterized types, that is to sayThe type of variable is a parameter, which is specified as a specific type when used. Generics can be used for classes, interfaces, and methods. By using generics, the code can be made simpler and safer. However, generics in Java use type erasure, so they are just pseudo-generics. This article summarizes the use of generics and existing problems, mainly referring to "Java Programming Thoughts".

The other two articles in this series:

  • Summary of Java Generics (2): Generics and Arrays

  • Summary of Java Generics (3): The use of wildcards

Basic usage

Generic classes

If there is a class Holder used to wrap a variable, the type of this variable may be arbitrary. How to write the Holder? Before there were generics, you could do this:

public class Holder1 {
 private Object a;
 public Holder1(Object a) {
 this.a = a;
 }
 public void set(Object a) {
 this.a = a;
 }
 public Object get(){
 return a;
 }
 public static void main(String[] args) {
 Holder1 holder1 = new Holder1("not Generic");
 String s = (String) holder1.get();
 holder1.set(1);
 Integer x = (Integer) holder1.get();
 }
}
Copy after login

In Holder1, there is a variable referenced by Object. Because any type can be upcast to Object, this Holder can accept any type. When taking it out, Holder only knows that it saves an Object Object, so it must be forced to the corresponding type. In the main method, holder1 first saves a string, which is a String object, and then changes to save an Integer object (parameter 1 will be automatically boxed). Casting when taking variables out of the Holder is already troublesome. You also have to remember different types here. If you make the wrong conversion, a runtime exception will occur.

Let’s take a look at the generic version of Holder:

public class Holder2<T> {
 private T a;
 public Holder2(T a) {
 this.a = a;
 }
 public T get() {
 return a;
 }
 public void set(T a) {
 this.a = a;
 }
 public static void main(String[] args) {
 Holder2<String> holder2 = new Holder2<>("Generic");
 String s = holder2.get();
 holder2.set("test");
 holder2.set(1);//无法编译 参数 1 不是 String 类型
 }
}
Copy after login

In Holder2, variable a is a parameterized type T. T is just an identifier, and other letters can also be used. When creating the Holder2 object, the type of parameter T is passed in angle brackets, then in this object, all occurrences of T are equivalent to being replaced with String. What get now takes out is not Object, but String object, so type conversion is not needed. In addition, when calling set, only the String type can be passed in, otherwise the compilation will not pass. This ensures type safety in holder2 and avoids accidentally passing in the wrong type.

Through the above example, we can see that pan makes the code simpler and safer. After the introduction of generics, some classes in the Java library, such as commonly used container classes, have also been rewritten to support generics. When we use them, we will pass in parameter types, such as: ArrayList list = ArrayList<>();.

Generic methods

Generics can not only target classes, but can also make a method generic individually. For example:

public class GenericMethod {
 public <K,V> void f(K k,V v) {
 System.out.println(k.getClass().getSimpleName());
 System.out.println(v.getClass().getSimpleName());
 }
 public static void main(String[] args) {
 GenericMethod gm = new GenericMethod();
 gm.f(new Integer(0),new String("generic"));
 }
}

代码输出:
 Integer
 String
Copy after login
## The #GenericMethod class itself is not generic. There is no need to pass in generic parameters when creating its object, but its method f is a generic method. Before the return type is its parameter identifier . Note that there are two generic parameters here, so there can be multiple generic parameters.

You do not need to explicitly pass in generic parameters when calling a generic method, which is not the case in the above call. This is because the compiler uses parameter type inference to infer the types of K and V based on the types of the arguments passed in (here, integer and String).

Type erasure

What is type erasure

Java generics use The type erasure mechanism has caused great controversy, so much so that Java's generic functions have been limited and can only be said to be "pseudo-generic". What is type erasure? To put it simply, type parameters only exist at compile time. At runtime, the Java virtual machine (JVM) does not know the existence of generics. Let’s look at an example first:

public class ErasedTypeEquivalence {
 public static void main(String[] args) {
 Class c1 = new ArrayList<String>().getClass();
 Class c2 = new ArrayList<Integer>().getClass();
 System.out.println(c1 == c2);
 }
}
Copy after login

The above code has two different ArrayLists: ArrayList and ArrayList. In our opinion, their parameterized types are different, one saves integers and the other saves strings. But by comparing their Class objects, the above code output is true. This shows that they are the same class in the JVM's view. In languages ​​such as C++ and

C# that support true generics, they are different classes.

The generic parameter will be erased to its first boundary. For example, in the Holder2 class above, if the parameter type is a single T, then it will be erased to Object, which is equivalent to all places where T appears. Replace with Object. So from the perspective of the JVM, the saved variable a is still of type Object. The reason why it is automatically taken out is the parameter type we passed in. This is because the compiler inserts the type conversion code into the compiled bytecode file, so we do not need to manually convert it. If the parameter type has boundaries, then erase it to its first boundary, which will be discussed in the next section.

擦除带来的问题

擦除会出现一些问题,下面是一个例子:

class HasF {
 public void f() {
 System.out.println("HasF.f()");
 }
}
public class Manipulator<T> {
 private T obj;
 public Manipulator(T obj) {
 this.obj = obj;
 }
 public void manipulate() {
 obj.f(); //无法编译 找不到符号 f()
 }
 public static void main(String[] args) {
 HasF hasF = new HasF();
 Manipulator<HasF> manipulator = new Manipulator<>(hasF);
 manipulator.manipulate();
 }
}
Copy after login

上面的 Manipulator 是一个泛型类,内部用一个泛型化的变量 obj,在 manipulate 方法中,调用了 obj 的方法 f(),但是这行代码无法编译。因为类型擦除,编译器不确定 obj 是否有 f() 方法。解决这个问题的方法是给 T 一个边界:

class Manipulator2<T extends HasF> {
 private T obj;
 public Manipulator2(T x) { obj = x; }
 public void manipulate() { obj.f(); }
}
Copy after login

现在 T 的类型是 ,这表示 T 必须是 HasF 或者 HasF 的导出类型。这样,调用 f() 方法才安全。HasF 就是 T 的边界,因此通过类型擦除后,所有出现 T 的

地方都用 HasF 替换。这样编译器就知道 obj 是有方法 f() 的。

但是这样就抵消了泛型带来的好处,上面的类完全可以改成这样:

class Manipulator3 {
 private HasF obj;
 public Manipulator3(HasF x) { obj = x; }
 public void manipulate() { obj.f(); }
}
Copy after login

所以泛型只有在比较复杂的类中才体现出作用。但是像 这种形式的东西不是完全没有意义的。如果类中有一个返回 T 类型的方法,泛型就有用了,因为这样会返回准确类型。比如下面的例子:

class ReturnGenericType<T extends HasF> {
 private T obj;
 public ReturnGenericType(T x) { obj = x; }
 public T get() { return obj; }
}
Copy after login

这里的 get() 方法返回的是泛型参数的准确类型,而不是 HasF。

类型擦除的补偿

类型擦除导致泛型丧失了一些功能,任何在运行期需要知道确切类型的代码都无法工作。比如下面的例子:

public class Erased<T> {
 private final int SIZE = 100;
 public static void f(Object arg) {
 if(arg instanceof T) {} // Error
 T var = new T(); // Error
 T[] array = new T[SIZE]; // Error
 T[] array = (T)new Object[SIZE]; // Unchecked warning
 }
}
Copy after login

通过 new T() 创建对象是不行的,一是由于类型擦除,二是由于编译器不知道 T 是否有默认的构造器。一种解决的办法是传递一个工厂对象并且通过它创建新的实例。

interface FactoryI<T> {
 T create();
}
class Foo2<T> {
 private T x;
 public <F extends FactoryI<T>> Foo2(F factory) {
 x = factory.create();
 }
 // ...
}
class IntegerFactory implements FactoryI<Integer> {
 public Integer create() {
 return new Integer(0);
 }
}
class Widget {
 public static class Factory implements FactoryI<Widget> {
 public Widget create() {
 return new Widget();
 }
 }
}
public class FactoryConstraint {
 public static void main(String[] args) {
 new Foo2<Integer>(new IntegerFactory());
 new Foo2<Widget>(new Widget.Factory());
 }
}
Copy after login

另一种解决的方法是利用模板设计模式

abstract class GenericWithCreate<T> {
 final T element;
 GenericWithCreate() { element = create(); }
 abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
 X create() { return new X(); }
 void f() {
 System.out.println(element.getClass().getSimpleName());
 }
}
public class CreatorGeneric {
 public static void main(String[] args) {
 Creator c = new Creator();
 c.f();
 }
}
Copy after login

具体类型的创建放到了子类继承父类时,在 create 方法中创建实际的类型并返回。

总结

本文介绍了 Java 泛型的使用,以及类型擦除相关的问题。一般情况下泛型的使用比较简单,但是某些情况下,尤其是自己编写使用泛型的类或者方法时要注意类型擦除的问题。接下来会介绍数组与泛型的关系以及通配符的使用。

The above is the detailed content of Summary of Java Generics (1) - Detailed explanation of basic usage and type erasure. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Perfect Number in Java Perfect Number in Java Aug 30, 2024 pm 04:28 PM

Guide to Perfect Number in Java. Here we discuss the Definition, How to check Perfect number in Java?, examples with code implementation.

Weka in Java Weka in Java Aug 30, 2024 pm 04:28 PM

Guide to Weka in Java. Here we discuss the Introduction, how to use weka java, the type of platform, and advantages with examples.

Smith Number in Java Smith Number in Java Aug 30, 2024 pm 04:28 PM

Guide to Smith Number in Java. Here we discuss the Definition, How to check smith number in Java? example with code implementation.

Java Spring Interview Questions Java Spring Interview Questions Aug 30, 2024 pm 04:29 PM

In this article, we have kept the most asked Java Spring Interview Questions with their detailed answers. So that you can crack the interview.

Break or return from Java 8 stream forEach? Break or return from Java 8 stream forEach? Feb 07, 2025 pm 12:09 PM

Java 8 introduces the Stream API, providing a powerful and expressive way to process data collections. However, a common question when using Stream is: How to break or return from a forEach operation? Traditional loops allow for early interruption or return, but Stream's forEach method does not directly support this method. This article will explain the reasons and explore alternative methods for implementing premature termination in Stream processing systems. Further reading: Java Stream API improvements Understand Stream forEach The forEach method is a terminal operation that performs one operation on each element in the Stream. Its design intention is

TimeStamp to Date in Java TimeStamp to Date in Java Aug 30, 2024 pm 04:28 PM

Guide to TimeStamp to Date in Java. Here we also discuss the introduction and how to convert timestamp to date in java along with examples.

Java Program to Find the Volume of Capsule Java Program to Find the Volume of Capsule Feb 07, 2025 am 11:37 AM

Capsules are three-dimensional geometric figures, composed of a cylinder and a hemisphere at both ends. The volume of the capsule can be calculated by adding the volume of the cylinder and the volume of the hemisphere at both ends. This tutorial will discuss how to calculate the volume of a given capsule in Java using different methods. Capsule volume formula The formula for capsule volume is as follows: Capsule volume = Cylindrical volume Volume Two hemisphere volume in, r: The radius of the hemisphere. h: The height of the cylinder (excluding the hemisphere). Example 1 enter Radius = 5 units Height = 10 units Output Volume = 1570.8 cubic units explain Calculate volume using formula: Volume = π × r2 × h (4

Create the Future: Java Programming for Absolute Beginners Create the Future: Java Programming for Absolute Beginners Oct 13, 2024 pm 01:32 PM

Java is a popular programming language that can be learned by both beginners and experienced developers. This tutorial starts with basic concepts and progresses through advanced topics. After installing the Java Development Kit, you can practice programming by creating a simple "Hello, World!" program. After you understand the code, use the command prompt to compile and run the program, and "Hello, World!" will be output on the console. Learning Java starts your programming journey, and as your mastery deepens, you can create more complex applications.

See all articles