1, Introduction to generics
Recalling the rotten arraylist we have used before, it can automatically increase the length according to the number of elements. It can also store different kinds of data. Compared with ordinary arrays, these are the two main advantages of arraylist. Let's take an example:
import java.util.ArrayList; /** This program demonstrates that an ArrayList can accept a mixture of object types as its elements. */ public class UnsafeArrayList { public static void main(String[] args) { // Create an ArrayList object. ArrayList list = new ArrayList(); // Store a variety of objects in the list. list.add("Java is fun!"); // Add a String list.add(new Double(2.5)); // Add a Double list.add(new Rectangle(10, 12)); // Add a Rectangle // Retrieve a reference to each object in the list. // Note that the reference returned from the get // method must be cast to the correct type. String s = (String)list.get(0); Double d = (Double)list.get(1); Rectangle r = (Rectangle)list.get(2); } }
Pay attention to the lines that call the get method. Every time you call the get method, the instance is forcibly converted. This is necessary. If you do not forcibly convert, java will report an error during compilation.
However, even if you add forced conversion, although the program does not report an error when compiling, it may still report an error when running. Change the above code slightly:
// Create an ArrayList object. ArrayList list = new ArrayList(); // Store a variety of objects in the list. list.add("Java is fun!"); // Store a String at element 0 list.add(new Double(2.5)); // Store a Double at element 1 list.add(new Rectangle(10, 12)); // Store a Rectangle at element 2 // Retrieve a reference to each object in the list. Double d = (Double)list.get(0); // Error! Element 0 is a String String s = (String)list.get(1); Rectangle r = (Rectangle)list.get(2);
At this time, the type of forced conversion is wrong. When running, the program reports an error, and a ClassCastException exception will be thrown.
Fortunately, Java provides a feature called generics, commonly known as generic classes, which allows the compiler to perform type checking and report type incompatibilities. Instead of finding a type mismatch in the code at run time, it is obviously a better choice to find an exception at compile time.
A generic class or method is a method that specifies the object type for which the class or method is used. If you try to use a class or method for an object of a type that is not allowed, an error occurs at compile time. In the array list, it can specify the type of data you store in the array list. For example, only string objects are allowed to be stored in the arraylist:
ArrayList<String> myList = new ArrayList<String>();
At this time, the compiler already knows the data type stored in the array list, so when calling the get method to specify the reference of the object, there is no need to force conversion:
ArrayList<String> myList = new ArrayList<String>(); myList.add("Java is fun!"); String str = myList.get(0);
When we use generics, we can let the compiler infer the type of data by itself. To achieve this, we can use "< >" symbol, this symbol as a whole. For example, the original array list says:
ArrayList<String> list = new ArrayList<String>();
You can now omit the string on the right:
ArrayList<Student> list = new ArrayList<>();
Both sides of the string. The left side is used to declare the type of the reference variable, and the right side is used to call the part of the ArrayList constructor.
2, Create your own generic class
Similar to array list, java provides some built-in generic classes. Of course, you can write a generic class yourself:
/** The Point class holds X and Y coordinates. The data type of the coordinates is generic. */ public class Point<T> { private T xCoordinate; // The X coordinate private T yCoordinate; // The Y coordinate /** Constructor @param x The X coordinate. @param y The Y coordinate. */ public Point(T x, T y) { xCoordinate = x; yCoordinate = y; } /** The setX method sets the X coordinate. @param x The value for the X coordinate. */ public void setX(T x) { xCoordinate = x; } /** The setY method sets the Y coordinate. @param y The value for the Y coordinate. */ public void setY(T y) { yCoordinate = y; } /** The getX method returns the X coordinate. @return The value of the X coordinate. */ public T getX() { return xCoordinate; } /** The getY method returns the Y coordinate. @return The value of the Y coordinate. */ public T getY() { return yCoordinate; } }
An instance of the Point class has two values that store two coordinates. Notice that "< T >" appears in the header of this class, which is a type parameter. The type parameter is an identifier representing the actual parameter type. When you instantiate this class, you can use the real parameter type (Integer, balabala):
Integer intX = new Integer(10); Integer intY = new Integer(20); Point<Integer> myPoint = new Point<>(intX, intY);
For instances in the Point class, T becomes an Integer:
If we pass integers as type parameters to T, these declarations will actually look like the following:
public Point(Integer x, Integer y) public void setX(Integer x) public void setY(Integer y) public Integer getX() public Integer getY()
The following agent creates two Point instances. In the first instance, Double is passed to the type parameter T, and in the second instance, Integer is passed to T:
/** This program demonstrates the Point class. */ public class TestPoint { public static void main(String[] args) { // Create two Double objects to use as coordinates. Double dblX = new Double(1.5); Double dblY = new Double(2.5); // Create a Point object that can hold Doubles. Point<Double> dPoint = new Point<>(dblX, dblY); // Create two Integer objects to use as coordinates. Integer intX = new Integer(10); Integer intY = new Integer(20); // Create a Point object that can hold Integers. Point<Integer> iPoint = new Point<>(intX, intY); // Display the Double values stored in dPoint. System.out.println("Here are the values in dPoint."); System.out.println("X Coordinate: " + dPoint.getX()); System.out.println("Y Coordinate: " + dPoint.getY()); System.out.println(); // Display the Integer values stored in iPoint. System.out.println("Here are the values in iPoint."); System.out.println("X Coordinate: " + iPoint.getX()); System.out.println("Y Coordinate: " + iPoint.getY()); } }
The output is as follows:
When "Point < Double > dpoint = new Point < > (dblx, dbly);" is executed, Double is passed to T as a parameter. For this instance, the constructor of Point class becomes:
public Point(Double x, Double y)
At this time, if x and y are not Double types, the compiler will report an error.
3, Type parameter
1. autoboxing and unboxing
When you create a generic class, you can only pass the reference type as a variable into the type parameter, and it is not allowed to pass the basic data type into the parameter. In the previous example, we used Double and Integer instead of double and int.
However, although generics can only accept reference types as their own type parameters, we can still use autoboxing in java to simplify our code. The previous examples can also be written as follows:
Point<Double> dPoint = new Point<>(1.5, 2.5);
1.5 and 2.5 here are changed from double to double by autoboxing, so the statements here are legal.
The opposite of autoboxing is Unboxing. See the following example:
// Use autoboxing to pass doubles to the constructor. Point<Double> dPoint = new Point<>(1.5, 2.5); // Use unboxing to retrieve the X and Y coordinates // and assign them to double variables. double x = dPoint.getX(); double y = dPoint.getY();
In the last two sentences, the object returned by the get method should be a double object, but the variable type is double, so java automatically turns Double Unboxing into double.
2. Instantiate a generic class without specifying a type parameter
Although not recommended, you can create an instance of a generic class without providing type parameters.
Point myPoint = new Point(x, y);
There is no type parameter provided here. When there is no type parameter provided when instantiating a generic class, Java will provide Object as the type parameter by default. Therefore, in fact, the code in myPoint class is as follows:
private Object xCoordinate; // The X coordinate private Object yCoordinate; // The Y coordinate
Similarly, every time T appears in a class method, it will become Object.
When used in this way, the compiler will explicitly warn as follows:
Therefore, not writing parameter types is not recommended, because it gives up the type security in the original code . But it also has the advantage that you can make variables reference different types of instances. Because according to polymorphism, the Object class can reference any type of variable.
Meanwhile, if the parameter type is not written, when you return the instance of the generic class, you need to forcibly convert the instance of the Object class into the class you want:
// Create an Integer and a Double. Integer x = new Integer(1); Double y = new Double(7.5); // Create a Point object with no type argument. Point myPoint = new Point(x, y); // Retrieve the X and Y coordinates. Integer xx = (Integer) myPoint.getX(); Double yy = (Double) myPoint.getY();
3. Common type parameter names
The T used in this example is not deterministic or unique. You can use any legal identifier to represent the type parameter name. The following are common type parameter names:
4, Passing generic objects to methods
Suppose we have a method to print the x and y coordinates of Point, as follows:
public static void printPoint(Point<Integer> point) { System.out.println("X Coordinate: " + point.getX()); System.out.println("Y Coordinate: " + point.getY()); }
This method accepts a point < integer > object as a parameter, but the problem is, what if the parameter we pass is a variable of type point < double >? In principle, this transfer is obviously reasonable. Of course, the coordinates may be decimal, but this method cannot accept it as a point < double > parameter. Overloading this method is too troublesome. After all, you only want to print the coordinates for any type of parameters. For exactly the same operation, can we "reuse" this method for other types of parameters? At this time, you may think of inheritance and polymorphism.
// Will this do what we want? public static void printPoint(Point<Number> point) { System.out.println("X Coordinate: " + point.getX()); System.out.println("Y Coordinate: " + point.getY()); }
Since no matter what type of digital wrapper class inherits from the Number class, can we directly change the parameter type to Number soon? Unfortunately, if you write this, the compiler will only accept objects of class Number, and an error will be reported if you try to pass its subclass as a parameter.
What should we do? The solution is as follows:
public static void printPoint(Point<?> point) { System.out.println("X Coordinate: " + point.getX()); System.out.println("Y Coordinate: " + point.getY()); }
Note that the type of the parameter is point <? >, What's in parentheses? Wildcard, now we can pass any parameter to the method:
Point<Integer> iPoint = new Point<>(1, 2); Point<Double> dPoint = new Point<>(1.5, 2.5); printPoint(iPoint); printPoint(dPoint);
But a new problem arises: the original method is too restrictive, and the current method is too open. It can accept any type of parameter as its variable, which is obviously not what we want. What we want is that this method can accept parameters of any number type as variables. How can we limit the range of parameter types? Then we need the extends keyword.
public static void printPoint(Point<? extends Number> point) { System.out.println("X Coordinate: " + point.getX()); System.out.println("Y Coordinate: " + point.getY()); }
In this version of the method, the type of the parameter is point <? extends Number>. This means that the type parameter of the point object can be "number" or any subclass extending "number", so the first two method calls are legal, and the third method call will report an error:
Point<Integer> iPoint = new Point<>(1, 2); Point<Double> dPoint = new Point<>(1.5, 2.5); printPoint(iPoint); printPoint(dPoint); Point<String> sPoint = new Point<>("1", "2"); printPoint(sPoint); // Error!
If a method has many parameters, it will be very troublesome to write according to the previous writing method:
public static void doSomething(Point<? extends Number> arg1, Point<? extends Number> arg2, Point<? extends Number> arg3)
However, Java provides another syntax for specifying parameter types:
public static <T extends Number> void doSomething(Point<T> arg1, Point<T> arg2, Point<T> arg3)
Notice that < T extensions Number > appears between the words static and void. This defines a type parameter called T and specifies that t can accept Number or subclasses of Number. The type of the method parameter is point < T >. This means that the method can accept any point object whose type parameter is Number or numeric subclass.
5, Keywords super and extends
When you use the extends keyword in generics, you are actually limiting an upper bound for type parameters. For example, < T extensions Number > in this example, the upper bound of T is the Number class. This means that t can be any type lower than Number (or Number itself) in the class hierarchy, but it cannot be a type higher than Number in the class hierarchy.
In addition to the extends keyword, you can also use the keyword super to restrict type parameters. Here is an example:
public static void doSomething(Point<? super Integer> arg)
Contrary to the previous extends keyword, super here sets a lower bound, and the parameters here can be above any Integer class Parameter of type.