1. RTTI:
Runtime type information allows you to discover and use type information while the program is running.
There are two ways to identify information about objects and classes when running in Java: traditional RTTI, and reflection. Let’s talk about RTTI.
RTTI: At runtime, identify the type of an object. But this type must be known at compile time.
Let’s take an example to see the use of RTTI. This involves the concept of polymorphism: letting the code only operate on references to the base class, and actually calling methods of specific subclasses will usually create a concrete object (Circle, Square, or Triangle, see the example below), transform it upwards into Shape (ignoring the specific type of the object), and use anonymous (that is, not knowing the specific type) Shape reference in the subsequent program:
abstract class Shape { // this calls the toString() method of the current class, returning the actual content void draw(){ System.out.println(this + "draw()"); } // Declare toString() as abstract type, force integration to override the method abstract public String toString();}class Circle extends Shape { public String toString(){ return "Circle"; }}class Square extends Shape { public String toString(){ return "Square"; }}class Triangle extends Shape { public String toString(){ return "Triangle"; }}public static void main(String[] args){ // When putting Shape object into the array of List<Shape>, it will transform upward into Shape, thus losing the specific type information List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle()); // When taken out from the array, in fact, all elements of this container are held as Objects and will automatically transform the result into Shape. This is the basic use of RTTI. for(Shape shape : shapeList){ shape.draw(); }}The output result is:
Circledraw()Squaredraw()Triangledraw()
When depositing it into an array, it will automatically transform up to Shape, and the specific type is lost. When taken out from the array, (the List container holds everything as an Object), it will automatically transform the result back to Shape. This is the basic usage of RTTI. All type conversions in Java are corrective checked at runtime, that is, RTTI: at runtime, identify the type of an object.
The above transformation is not thorough. When the elements of the array are taken out, the Object is transformed into Shape, not the specific type. This is done by containers and Java generic systems during compilation, and there are type conversion operations to ensure this at runtime.
The specific code that can be executed into a subclass through a Shape object is determined by polymorphism. For details, it depends on the specific object pointed to by the Shape reference.
In addition, using RTTI, you can query the exact type of the object pointed to by a Shape reference, and then selectively execute the subclass method.
2. Class object:
To understand how RTTI works in Java, you must know how type information is represented at runtime, which is done by the special object Class.
Class objects are used to create all "regular" objects of a class. Java uses Class objects to execute its RTTI.
Whenever a new class is compiled, a Class object (.class file) is generated. The JVM running this program will use the "class loader" subsystem.
Class loader subsystem: Contains a class loader chain, but only one native class loader, which is part of the JVM implementation. Native class loaders load trusted classes, including Java API classes, usually from local disks. When a class needs to be loaded in a certain way to support web server applications, additional class loaders can be attached.
2.1. The timing of loading the class:
This class is loaded when the program creates the first reference to a static member of the class. This proves that the constructor is actually a static method of the class. When creating a new object of the class using the new operator, it will also be used as a reference to the static member of the class.
It can be seen that Java programs are loaded dynamically and loaded on demand. When Class is needed, the class loader will first check whether the Class object of this class has been loaded. If it has not been loaded, the default class loader will find the .class file based on the class name. Next is the verification phase: when loaded, they accept verification to make sure they are not corrupted and do not contain bad Java code.
2.2. Class-related methods, newInstance()
The following is an example to demonstrate the loading of the Class object:
class A { // Static code base, executed when it is loaded for the first time, and it is known when the class is loaded by printing information static { System.out.println("Loading A"); }}class B { static { System.out.println("Loading B"); }}class C { static { System.out.println("Loading C"); }}public class Load { public static void main(String[] args){ System.out.println("execute main..."); new A(); System.out.println("after new A"); try { Class.forName("com.itzhai.test.type.B"); } catch (ClassNotFoundException e) { System.out.println("cloud not find class B"); } System.out.println("after Class.forName B"); new C(); System.out.println("after new C"); }}The output result is:
execute main...Loading Aafter new ALoading Bafter Class.forName BLoading Cafter new C
It can be seen that the Class object is loaded only when needed. Note the Class.forName() method here:
The forName() method is a method to obtain a reference to the Class object. By obtaining the appropriate reference to the Class object, you can use type information at runtime.
If you already have an object of interest, you can get the Class reference by following the getClass() method provided by the class Object.
Here is a code used by Class:
interface X{}interface Y{}interface Z{}class Letter { Letter(){}; Letter(int i){};}class NewLetter extends Letter implements X, Y, Z{ NewLetter(){ super(1); };}public class ClassTest { /** * Print type information* @param c */ static void printInfo(Class c){ // getName() gets the fully qualified class name System.out.println("Class name: " + c.getName() + " is interface? " + c.isInterface()); // Get the class name System.out.println("Simple name: " + c.getSimpleName()); // Get the fully qualified class name System.out.println("Canonical name: " + c.getCanonicalName()); } public static void main(String[] args){ Class c = null; try { // Get the Class reference c = Class.forName("com.itzhai.test.type.NewLetter"); } catch (ClassNotFoundException e) { System.out.println("Can not find com.itzhai.test.type.NewLetter"); System.exit(1); } // Print interface type information for(Class face : c.getInterfaces()){ printInfo(face); } // Get superclass Class reference Class up = c.getSuperclass(); Object obj = null; try { // Create an instance of Class through the newInstance() method obj = up.newInstance(); } catch (InstantiationException e) { System.out.println("Can not instantiate"); } catch (IllegalAccessException e) { System.out.println("Can not access"); } // Print superclass type information printInfo(obj.getClass()); }}The output is:
Class name: com.itzhai.test.type.X is interface? trueSimple name: XCanonical name: com.itzhai.test.type.XClass name: com.itzhai.test.type.Y is interface? trueSimple name: YCanonical name: com.itzhai.test.type.YClass name: com.itzhai.test.type.Z is interface? trueSimple name: ZCanonical name: com.itzhai.test.type.ZClass name: com.itzhai.test.type.Letter is interface? falseSimple name: LetterCanonical name: com.itzhai.test.type.Letter
Note that the string passed to forName() must use a fully qualified name (including the package name).
Through the methods used in printInfo, you can discover an object's complete class inheritance structure at runtime.
By using Class's newInstance() method, it is a way to implement a "virtual constructor" to create an instance of Class. The object reference is obtained, but it points to the Letter object when referenced. Classes created using newInstance() must have a default constructor. (Through the reflection API, you can use any constructor to dynamically create class objects).
2.3. Class literal constants:
In addition to using the getName() method, Java also provides another way to generate a reference to a Class object, that is, using class literal constants:
NewLetter.class;
This method is simple and safe, and is checked during compilation, making it more efficient. It can be used not only for ordinary classes, but also for interfaces, arrays, and basic data types. In addition, for the wrapper class of basic data type, there is also a standard field TYPE. The TYPE field is a reference to execute the corresponding basic data type Class object. For the sake of unification, it is recommended to use the form .class.
2.4. The difference between using .class and using getName() method to create object references:
When created with .class, the Class object is not automatically initialized. The creation steps are as follows:
(1) Loading is performed by the class loader: look up the bytecode (usually in the path specified by the classpath, but not necessary), and then create a Class object from these bytecodes.
(2) The link will verify the bytecode in the class and allocate storage space for the static domain. If necessary, all references to other classes created by this class will be parsed.
(3) Initialization If the class has a superclass, initialize it, and execute the static initializer and static initialization block.
Initialization is delayed until the first reference to a static method (the constructor is implicitly static) or a non-number static domain:
class Data1{ static final int a = 1; static final double b = Math.random(); static { System.out.println("init Data1..."); }}class Data2{ static int a = 12; static { System.out.println("init Data2..."); }}class Data3{ static int a = 23; static { System.out.println("init Data3..."); }}public class ClassTest2 { public static void main(String[] args){ System.out.println("Data1.class: "); Class data1 = Data1.class; System.out.println(Data1.a); // Data1 System.out.println(Data1.b); // Data1 Initialized System.out.println(Data2.a); // Data2 Initialized try { Class data3 = Class.forName("com.itzhai.test.type.Data3"); // Data3 Initialized } catch (ClassNotFoundException e) { System.out.println("can not found com.itzhai.test.type.Data3..."); } System.out.println(Data3.a); }}The output result is:
Data1.class: 1init Data1...0.26771085109184534init Data2...12init Data3...23
Initialization effectively achieves as "lazy" as possible.
2.5. The following are some situations for determining whether to perform initialization:
(1) The class syntax obtains a reference to the class and will not cause initialization;
(2) Class.forName() generates a Class reference and is initialized immediately;
(3) If a static final value is a "compiler constant", then this value can be read without initializing the class;
(4) It is not enough to ensure this behavior if just setting a domain to static final, for example:
static final double b = Math.random();
(5) If a static domain is bushifinal, then when accessing it, you always need to be advanced linking and initialized;
2.6. Generalized Class Quote:
A Class reference represents the exact type of the object it points to, and the object is an object of the Class class. In JavaSE5, the Class object pointed to by a Class reference can be qualified by generics, and the compiler can enforce additional type checks:
Class intCls = int.class;// Use generics to define the reference pointed to by Class Class<Integer> genIntCls = int.class;// Class without generics can be reassigned to point to any other Class object intCls = double.class;// The following compilation will error// genIntCls = double.class;
2.6.1. Use wildcards? Relax the limitations of generics:
Class<?> intCls = int.class; intCls = String.class;
In JavaSE5, Class<?> is better than ordinary Class, and it is recommended to use Class<?> even if they are equivalent, because the advantage of Class<?> is that it means that you are not happening or negligent, but using a non-specific class reference.
To define a reference to Class to a certain type, or a subtype of that type can use wildcards with extends, create a scope:
Class<? extends Number> num = int.class;// The reference range of num is Number and its subclass, so you can assign the value num = double.class; num = Number.class;
2.6.2. The newInstance() method under generics:
Using the Class after generics, the object returned by calling newInstance() is of the exact type, but when you use getSuperclass() to get the superclass corresponding to the generic, there are some limitations to the real type: the compiler knows the type of the superclass during the compilation period, but the newInstance() method referenced by this obtained superclass does not return the exact type, but the Object:
Dog dog = dogCls.newInstance();abstract class Animal {}class Dog extends Animal{}// The following writing method is wrong, and it can only return Class<? super Dog> type// Class<Animal> animalCls = dogCls.getSuperclass(); Class<? super Dog> animalCls = dogCls.getSuperclass();// Through the obtained superclass reference, you can only create objects that return Object type Object obj = animalCls.newInstance(); 2.6.3. New transformation syntax: cast() method
Look directly at the code:
Animal animal = new Dog();Class<Dog> dogCls = Dog.class;Dog dog = dogCls.cast(animal);// Or directly use the following transformation method dog = (Dog)animal;
It can be found that using the cast() method has done additional work. This conversion method can be used in the following situation: when writing a generic band, if a Class reference is stored and you hope to perform the transformation through this Class reference, you can use the cast() method.
3. Type check instanceof
3.1. Check before type conversion
The compiler allows you to freely perform upward transformation assignment operations without any displayed transformation operations, just like assigning values to references to superclasses.
However, if the displayed type conversion is not used, the compiler will not allow you to perform downconversion assignment. At this time, we might as well check whether the object is an instance of a specific type, and the keyword instance of the keyword:
if(x instanceof Dog) ((Dog) x).bark();
3.2. The form of RTTI:
So, so far, we know that the forms of RTTI include:
(1) Traditional type conversion (Shape)
(2) Class object representing the type of the object
(3) Keyword instanceof
3.3. Dynamic instanceof method:
The Class.isInstance method provides a way to test objects dynamically.
The following demonstrates the usage of instanceof and Class.isInstance:
Attribute:
public interface Attribute {}Shape:
/** * Create an abstract class */public abstract class Shape{ // this calls the toString method of the current class toString method to obtain information public void draw() { System.out.println(this + ".draw()"); } // Declare the toString() method to abstract, thus forcing the inheritor to rewrite the method. abstract public String toString();}Circle:
public class Circle extends Shape implements Attribute{ public String toString(){ return "Circle"; }}Square:
public class Square extends Shape{ public String toString(){ return "Square"; }}Triangle:
public class Triangle extends Shape{ public String toString(){ return "Triangle"; }}Type checking:
// instanceOfCircle c = new Circle();// Determine whether the instance of superclass System.out.format("Using instanceof: %s is a shape? %b/n", c.toString(), c instanceof Shape);// Determine whether the instance of superclass System.out.format("Using instanceof: %s is a circle? %b/n", c.toString(), c instanceof Circle);// Determine whether the instance of superclass System.out.format("Using Class.isInstance: %s is a shape? %b/n", c.toString(), c instanceof Circle);// Determine whether the instance of superclass System.out.format("Using Class.isInstance: %s is a shape? %b/n", c.toString(), Shape.class.isInstance(c));// The instance of determining whether the interface is System.out.format("Using Class.isInstance: %s is a Attribute? %b/n", c.toString(), Attribute.class.isInstance(c));It can be found that the instanceof or Class.isInstance method determines whether to inherit an instance of the system, that is, in addition to judging itself, it also determines whether it is a superclass or an instance of an interface.
The following demonstrates how to use dynamic Class.instance:
First create an abstract shape generator class:
public abstract class ShapeCreator { private Random rand = new Random(10); // Return an array of object types provided by the implementation class. You will see two implementation forms later, based on forName and based on class literal constants.class public abstract List<Class<? extends Shape>> types(); // Randomly generate a type object instance in an array of object types public Shape randomShape(){ int n = rand.nextInt(types().size()); try { return types().get(n).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); return null; } catch (IllegalAccessException e) { e.printStackTrace(); return null; } } // Generate a random array public Shape[] createArray(int size){ Shape[] result = new Shape[size]; for(int i=0; i<size; i++){ result[i] = randomShape(); } return result; } // Generate a random array, a generic ArrayList public ArrayList<Shape> arrayList(int size){ ArrayList<Shape> result = new ArrayList<Shape>(); Collections.addAll(result, createArray(size)); return result; }}Next, write an implementation of this abstract class:
/** * forName generator implementation* @author artinking * */public class ForNameCreator extends ShapeCreator{ private static List<Class<? extends Shape>> types = new ArrayList<Class<? extends Shape>>(); private static String[] typeNames = { "com.itzhai.javanote.entity.Circle", "com.itzhai.javanote.entity.Square", "com.itzhai.javanote.entity.Triangle" }; @SuppressWarnings("unused") private static void loader(){ for(String name : typeNames){ try { types.add((Class<? extends Shape>)Class.forName(name)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } // Initialize the array of types required for loading static { loader(); } public List<Class<? extends Shape>> types() { return types; }}Finally, write a class that counts the number of shapes, using instanceof:
public class ShapeCount { static class ShapeCounter extends HashMap<String, Integer>{ public void count(String type){ Integer quantity = get(type); if(quantity == null){ put(type, 1); } else { put(type, quantity + 1); } } } // Demonstrate stating object types through the instanceof keyword public static void countShapes(ShapeCreator creator){ ShapeCounter counter = new ShapeCounter(); for(Shape shape : creator.createArray(20)){ if(shape instanceof Circle) counter.count("Circle"); if(shape instanceof Square) counter.count("Square"); if(shape instanceof Triangle){ counter.count("Triangle"); } } System.out.println(counter); } public static void main(String[] args){ countShapes(new ForNameCreator()); }}Rewrite the implementation of abstract class and re-implement it with class literal constants:
/** * literal generator implementation*/public class LiteralCreator extends ShapeCreator{ public static final List<Class<? extends Shape>> allType = Collections.unmodifiableList(Arrays.asList(Circle.class, Triangle.class, Square.class)); public List<Class<? extends Shape>> types(){ return allType; } public static void main(String[] args){ System.out.println(allType); }}Now use Class.instance to count the number of shapes as follows:
/** * Remove the monotonic instanceof statement in the original ShapeCount by using Class.instanceof dynamic test object* */public class ShapeCount2 { private static final List<Class<? extends Shape>> shapeTypes = LiteralCreator.allType; static class ShapeCounter extends HashMap<String, Integer>{ public void count(String type){ Integer quantity = get(type); if(quantity == null){ put(type, 1); } else { put(type, quantity + 1); } } } // Demonstrate the statistic object types through Class.isInstance() public static void countShapes(ShapeCreator creator){ ShapeCounter counter = new ShapeCounter(); for(Shape shape : creator.createArray(20)){ for(Class<? extends Shape> cls : shapeTypes){ if(cls.isInstance(shape)){ counter.count(cls.getSimpleName()); } } System.out.println(counter); } public static void main(String[] args){ countShapes(new ForNameCreator()); }}Now there are two implementations of the generator. We can add a layer of appearance here and set the default implementation method:
/** * Now there are two implementations of the generator. Let's add a layer of appearance here and set the default implementation method */public class Shapes { public static final ShapeCreator creator = new LiteralCreator(); public static Shape randomShape(){ return creator.randomShape(); } public static Shape[] createArray(int size){ return creator.createArray(size); } public static ArrayList<Shape> arrayList(int size){ return creator.arrayList(size); }} 3.4. Equivalence of instanceof and Class:
The result generated by instanceof and isInstance() is exactly the same, maintaining the concept of type and determining whether a class or a derived class of this class.
equals() is the same as ==, and using this more practical Class object, inheritance is not considered.
System.out.println(new Circle() instanceof Circle); // trueSystem.out.println(Shape.class.isInstance(new Circle())); // trueSystem.out.println((new Circle()).getClass() == Circle.class); // trueSystem.out.println((new Circle().getClass()).equals(Shape.class)); // false