Runtime type information allows you to discover and use type information while your program is running. It frees you from the constraint that you can only perform type-oriented operations at compile time.

Java allows us to identify information about objects and classes at run time in two ways: traditional RTTI, which assumes that all types are known at compile time; The other is reflection, which allows us to discover and use information about a class at runtime.

Usually you want most of your code to know as little as possible about the specific types of objects, and instead deal only with a generic representation within the object family, so that code is easier to write, easier to read, and easier to maintain, so polymorphism is a fundamental goal of object-oriented programming. But if you have a particular programming problem — if you know the exact type of a generalization reference, you can solve it in a simpler way. (RTTI covers polymorphism, but also supports the use of additional polymorphism to provide flexible applications. This concept is a continuation of the author’s C++ concept, and readers can understand its design, implementation, and usage scenarios without too much lexical entanglement.)

RTTI forms include:

  • Traditional conversions, such as Shape, are ensured by RTTI, and a ClassCastException is thrown if an incorrect conversion is performed.
  • A Class object that represents the type of the object, which can be queried for information needed at run time.
  • The instanceof keyword determines whether an object is an instanceof a particular type

Class object

The Class object contains information about the Class and is used to create all the “normal” objects of the Class. Each time a new Class is written and compiled, a Class object is generated, which is supported by the classloader (I’ll share this with my friends later on).

Class loading

All classes are dynamically loaded into the JVM on their first use. The class is loaded when the program creates the first reference to a static member of the class. This proof constructor is also a static structure of the class, even if the static keyword is not used before the constructor. The Class loader first checks if the Class object is loaded, and if not, looks for the.class file based on the Class name. Then it loads the bytecode and creates a Class object. Once the Class object of a Class is loaded into memory, it is used to create all the objects of that Class.

Class object reference

Whenever you want to use type information at runtime, you must first get a reference to the appropriate Class object. Class.forname () is a convenient way to do this because you don’t need to hold an object of that type in order to get a Class reference. You can also get a Class reference by inheriting Object’s getClass() method.

Class’s newInstacne() method is one way to implement a virtual constructor that allows you to declare “I don’t know your exact type, but create yourself correctly anyway.” Because you’re using only a Class reference and don’t have any further type information at compile time, when you create a new instance, You get an Object reference, and you have to learn more about it and perform some kind of transformation before you can send any message that Object can accept. In addition, newInstance() requires that this class have a default constructor.

Class up = c.getSuperClass();
Object obj = up.newInstance():
Copy the code

Java also provides Class literal constants to generate references to Class objects

FancyToy.class
Copy the code

Do not only simpler, and safer, because it will be checked at compile time, and it eliminated the class.forname () method calls, not to automatically initialize the Class object, the initialization is delayed by the static method (static constructor implicitly) for a first reference to a static field or very few, so also more efficient. Class literal constants can be applied not only to ordinary classes, but also to interfaces, arrays, and primitive data types.

A generalized Class reference

Java allows you to qualify the type of the Class object to which a Class reference refers. By using generic syntax, you can let the compiler enforce additional type checking.

public class GenericClassReferences {
    public static void main(String[] args) {
        Class intClass = int.class;
        CLass<Integer> genericIntClass = int.class
        genericIntClass = Integer.class
        intClass = double.class;
        // genericIntClass = double.class // Illegal
    }
}
Copy the code

To loosen restrictions when using generalized Class references, use wildcards? Means “anything”.

public class WildcardClassReferences {
    public static void main(String[] args) {
        Class<?> intClass = int.class;
        intClass = double.class;
    }
}
Copy the code

Class is better than plain classes, even if they are equivalent. Plain classes don’t generate compiler alerts. The benefit of Class is that it means that you didn’t accidentally or accidentally use a nonspecific Class reference, you just chose the nonspecific version.

Type checking

instanceof

Type checking can be used to provide the security and accuracy of the transformation before the transformation of the type downward.

if (x instanceof Dog) {
    ((Dog)x).bark();
}
Copy the code

isInstance

Instanceof is strictly limited: it can only be compared to named types, not Class objects. The class.isinstance method provides a way to test objects dynamically.

public void count(Pet pet) { for (Map.Entry<Class<? extends Pet>, Integer> pair : entrySet()) { if (pair.getKey().isInstance(pet)) { put(pair.getKey(), pair.getValue() + 1); }}}Copy the code

isAssignableFrom

Class.isassignablefrom () allows type determination on inherited types.

public void count(Object obj) { Clss<? > type = obj.getClass(); if (! baseType.isAssignableFrom(type)) { throw new RuntimeException(); } countClass(type): } private void count(Class<? > type) { Integer quantity = get(type); put(type, quantity == null ? 1 : quantity + 1); Class<? > superClass = type.getSuperClass(); if (superClass ! = null && baseType.isAssignableFrom(superClass)) { countClass(superClass): } }Copy the code

reflection

RTTI has one limitation: at compile time the compiler must know all classes to be processed through RTTI. If you get a reference to an object that is not in your program space, at compile time your program has no way of knowing which class the object belongs to, such as disk files and remote network byte streams.

The Class Class supports the concept of reflection along with the java.lang.Reflect Class library, which contains the Field, Method, and Constructor classes (each of which implements the Member interface). These types of objects are created by the JVM at run time to represent the corresponding members of an unknown class, so that the anonymous object’s class information can be fully determined at run time without needing to know anything at compile time.

public clss ShowMethods { public static void main(String[] args) { // ignore arguments check and exception catch Class<? > c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors(); // extract method and constructor info } }Copy the code

The result produced by class.forname () is unknowable at compile time, so all method signature information is extracted at execution time, and reflection provides enough support to create an object that is completely unknown at compile time and call its methods.

By using reflection, you can still reach and call all methods, even private methods, and if you know the name of the Method, you can call serAccessible(true) on its Method object. The same is true for domains except for final, where final domains are actually death-safe on modification, and the system will accept any modification attempt at runtime without throwing an exception, but no modification will actually occur. (Actual application scenarios have dynamic configuration projects, such as Apollo)

The real difference between RTTI and reflection is that for RTTI, the compiler opens and examines.class files at compile time, whereas for reflection,.class files are not available at compile time, so they are opened and examined at run time.

A dynamic proxy

Proxies are useful anytime you want to separate additional operations from the actual object, especially if you want to be able to easily change from not using additional operations to using them, or vice versa. Java’s dynamic proxy takes the idea of proxies a step further, because it dynamically creates proxies and handles calls to propped methods. All calls made by dynamic proxies are redirected to a single invocation handler, whose job is to reveal the type of invocation and determine countermeasures.

class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); return method.invoke(proxied, args); } } class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real)); consumer(proxy); }}Copy the code