Thinking In Java - type information reading notes and excerpts

Posted by p.utsav on Mon, 17 Feb 2020 09:50:32 +0100

Runtime type information allows you to discover and use type information while the program is running.

In Java, there are two main ways for us to recognize the information of objects and classes at run time: the first is "traditional" RTTI, which assumes that we have known all types at compile time; the other is "reflection" mechanism, which allows us to discover and use the information of classes at run time.

Why RTTI?

The basic purpose of object-oriented programming is to let code manipulate only references to base classes. In this way, if a new class is added to extend the program, the original code will not be affected.

Example code:

abstract class Shape {

    void draw(){
        System.out.println(this + ".draw()");
    }

    abstract public String toString();

}
public class Circle extends Shape {
    public String toString() {
        return "Circle";
    }
}
public class Square extends Shape {
    public String toString() {
        return "Square";
    }
}
public class Triangle extends Shape {
    public String toString() {
        return "Triangle";
    }
}
public class Shapes {

    public static void main(String[] args) {
        List<Shape> shapeList = Arrays.asList(
                new Circle(),
                new Square(),
                new Triangle()
        );
        for (Shape shape:
             shapeList) {
            shape.draw();
        }
    }

}
/*
out:
Circle.draw()
Square.draw()
Triangle.draw()
 */

When an element is removed from an array, the container - which holds everything as an Object - automatically transforms the result back to the type specified by the array. This is the most basic use of RTTI, because in Java, all type conversions are checked for correctness at runtime. This is what the RTTI name means: at run time, identify the type of an Object.

Class object

Class objects are used to create all "regular" objects of a class. Java uses the class object to perform its RTTI, even if you're doing something like transformation. The class class also has a large number of other ways to use RTTI.

Class is a part of the program. Each class has a class object. In other words, whenever a new class is written and compiled, a class object is generated (more appropriately, saved in a. Class file with the same name). To generate objects of this class, the JVM running this program will use a subsystem called a "classloader.".

The classloader can actually contain a classloader chain, but there is only one native classloader, which is part of the JVM implementation. Native class loaders load so-called trusted classes, including Java API classes, which are usually loaded locally. In this chain, there is usually no need to add additional classloaders, but if there are special requirements, there is a way to attach additional classloaders.

All class loaders are dynamically loaded into the JVM when they are first used. The class is loaded when the program creates the first reference to a static member of the class. This proves that the constructor is also a static method of the class, even if the static keyword is not used before the constructor. Therefore, using the new operator to create a new object of a class can also be used as a reference to a static member of the class.

Therefore, a Java program is not fully loaded before it starts running, and its parts are loaded when necessary.

Class loader first checks whether the class object of this class has been loaded. If not already loaded, the default class loader looks up the. Class file based on the class name. When the bytecode of this class is loaded, they are validated to ensure that it is not corrupted and does not contain bad Java code.

Once a Class object of a Class is loaded into memory, it is used to create all objects of the Class.

Example code:

public class Candy {

    static {
        System.out.println("Loading Candy...");
    }

}
public class Gum {

    static {
        System.out.println("Loading Gum...");
    }

}
public class Cookie {

    static {
        System.out.println("Loading Cookie...");
    }

}
public class SweetShop {

    public static void main(String[] args) {
        System.out.println("inside main");
        new Candy();
        System.out.println("after creating Candy");
        try {
            //Class.forName() needs to fill in the full path, otherwise it cannot be found
            Class.forName("TypeInfo.ClassObject.Gum");
        } catch (ClassNotFoundException e) {
            System.out.println("cannt find Gum");
        }

        System.out.println("after class.forName(Gum)");
        new Cookie();
        System.out.println("after creating Cookie");
    }

}
/*
out:
inside main
Loading Candy...
after creating Candy
Loading Gum...
after class.forName(Gum)
Loading Cookie...
after creating Cookie
 */

Whenever you want to use type information at run time, you must first obtain a reference to the appropriate class Object. Class.forName() is a convenient way to do this, because you don't need to hold objects of this type in order to get a class reference. However, if you already have an Object of type of interest, you can get the class reference by calling the getClass() method, which is part of the root class Object and returns the class reference representing the actual type of the Object.

Example code:

public class ToyTest {

    static void printInfo(Class cc){
        System.out.println("Class Name:" + cc.getName()
            + ",is interface?[" + cc.isInterface() + "]"
        );
        System.out.println("Simple Name:" + cc.getSimpleName());
        System.out.println("Canonical Name:" + cc.getCanonicalName());
    }

    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("TypeInfo.ClassObject.FancyToy");
        } catch (ClassNotFoundException e) {
            System.out.println("cannt find FancyToy!");
            System.exit(1);
        }
        printInfo(c);
        for (Class face : c.getInterfaces()){
            printInfo(face);
        }
        Class up = c.getSuperclass();
        Object obj = null;
        try {
            //Abandoned
            //obj = up.newInstance();
            //Use this
            obj = up.getDeclaredConstructor().newInstance();
        } catch (InstantiationException e) {
            System.exit(1);
        } catch (InvocationTargetException e) {
            System.exit(1);
        } catch (NoSuchMethodException e) {
            System.exit(1);
        } catch (IllegalAccessException e) {
            System.exit(1);
        }
        printInfo(up.getClass());
    }

}
/*
out:
Class Name:TypeInfo.ClassObject.FancyToy,is interface?[false]
Simple Name:FancyToy
Canonical Name:TypeInfo.ClassObject.FancyToy
Class Name:TypeInfo.ClassObject.HasBatteries,is interface?[true]
Simple Name:HasBatteries
Canonical Name:TypeInfo.ClassObject.HasBatteries
Class Name:TypeInfo.ClassObject.Shoots,is interface?[true]
Simple Name:Shoots
Canonical NameTypeInfo.ClassObject.Shoots
Class Name:TypeInfo.ClassObject.Waterproof,is interface?[true]
Simple Name:Waterproof
Canonical Name:TypeInfo.ClassObject.Waterproof
Class Name:java.lang.Class,is interface?[false]
Simple Name:Class
Canonical Name:java.lang.Class
 */

Class literals

Java also provides another way to generate references to Class objects, using Class literal constants.

XXX.class.

This is not only simpler, but also safer, because it is checked at compile time (so it doesn't need to be in the try statement block). And eliminate the call of forName() method, so it is more efficient.

Class literal constants can be used not only for ordinary classes, but also for interfaces, arrays, and basic Type data. In addition, there is a standard field Type for wrapper classes of basic types. The Type field is a reference to the class object of the corresponding basic data Type.

Corresponding tables:

Class literals Standard field TYPE
boolean.class Boolean.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE
char.class Character.TYPE

The preparation for using classes actually consists of three steps:

  1. Load, which is performed by the Class loader. This step will look up the bytecode (usually in the path specified by classpath, but this is not necessary), and create a Class object from the bytecode;

  2. Links. In the link phase, the bytecode in the class will be verified to allocate storage space for the static domain, and if necessary, all references to other classes created by this class will be resolved;

  3. Initialization. If the class has a superclass, it is initialized with a static initializer and a static initialization block.

    Initialization is delayed until static methods (constructors are implicitly static) or non constant static fields are referenced for the first time;

Example code:

public class Initable {

    static final int staticFinal = 47;
    static final int staticFinal2 = ClassInit.rand.nextInt(1000);

    static {
        System.out.println("Init Initable...");
    }

}

public class Initable2 {

    static int staticNonFinal = 147;
    static {
        System.out.println("Init Initable2...");
    }

}

public class Initable3 {

    static int staticNonFinal = 74;
    static {
        System.out.println("Init Initable3...");
    }

}

public class ClassInit {

    public static Random rand = new Random(47);

    public static void main(String[] args) throws ClassNotFoundException {
        //Class object does not trigger initialization
        Class initable = Initable.class;
        System.out.println("After creating Initable ref");
        System.out.println(Initable.staticFinal);
        System.out.println(Initable.staticFinal2);
        System.out.println(Initable2.staticNonFinal);
        //forName triggers initialization
        Class initable3 = Class.forName("TypeInfo.Classliterals.Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);
    }

}
/*
out:
After creating Initable ref
47
Init Initable...
258
Init Initable2...
147
Init Initable3...
After creating Initable3 ref
74
 */

In the output, it can be seen that when obtaining the class reference of the Initable, the initialization of the class is not triggered through. Class, while the initialization of the class is triggered by the relative Class.forName();

If a static domain is not final, when accessing it, it is always required to link (allocate storage space for this domain) and initialize (initialize storage space) before it is read.

Generalized Class reference

Ordinary Class references do not generate warnings. Although generic Class references can only be assigned to the type they declare, ordinary Class references can be reassigned to any other Class object. By using generic syntax, you can have the compiler enforce additional type checking.

Example code:

public class GenericClassReferences {

    public static void main(String[] args) {
        Class intClass = int.class;

        Class<Integer> genericIntClass = int.class;
        genericIntClass = Integer.class;
        intClass = double.class;
        //Report errors
        //genericIntClass = double.class;
    }

}

To ease restrictions when using generalized Class references, you can use wildcards, which are part of Java generics. The wildcard is "?, which means" anything ".

Example code:

public class WildcardClassREferences {

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

In order to create a Class reference, which is limited to a certain type, or any subclass of that type, you need to combine wildcards with the extends keyword to create a scope.

Example code:

public class BoundedClassReference {

    public static void main(String[] args) {
        Class<? extends Number> bounded = int.class;
        bounded = double.class;
        bounded = Number.class;
    }
    
}

The reason to add generic syntax to a Class reference is only to provide compile time type checking, so if there is an error in the operation, it will be found immediately. When using ordinary Class references, you will not go astray, but if you do make a mistake, you may not find the error until the runtime, which is very inconvenient.

Example code:

public class CountedInteger {

    private static long counter;
    private final long id = counter++;

    @Override
    public String toString() {
        return "CountedInteger{" +
                "id=" + id +
                '}';
    }
}

public class FilledList<T> {

    private Class<T> type;

    public FilledList(Class<T> type){
        this.type = type;
    }

    public List<T> craete(int nElements){
        List<T> result = new ArrayList<>();
        try {
            for (int i = 0; i < nElements; i++) {
                result.add(type.getDeclaredConstructor().newInstance());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void main(String[] args) {
        FilledList<CountedInteger> fl = new FilledList<>(CountedInteger.class);
        System.out.println(fl.craete(5));
    }
}
/*
out:
[CountedInteger{id=0},
CountedInteger{id=1},
CountedInteger{id=2},
CountedInteger{id=3},
CountedInteger{id=4}]
 */

Note that this class must assume that any type it works with has a default constructor (parameterless constructor), and if it doesn't, you get an exception.

Check before type conversion

Several common RTTI forms include:

  1. In traditional type conversion, RTTI ensures the correctness of type conversion. If a wrong type conversion is executed, a ClassCastException will be thrown;
  2. A Class object that represents the type of the object. By querying the Class object, you can get the information needed at runtime;
  3. Keyword instanceof. It returns a Boolean value that tells us whether an object is an instance of a particular type;

There is a strict restriction on instanceof: it can only be compared with a named type, not with a Class object.

Dynamic instanceof

The Class.isInstance method provides a way to test objects dynamically.

Registered factory

In order to facilitate the caller to create the object he wants to obtain, the factory mode can be used at this time. The object creator only needs to prepare the method of creating some objects and save them in the registration factory, while the user only needs to call.

Example code:

public class Part {

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }

    public static List<Factory<? extends Part>> partFactories = new ArrayList<>();

    static {
        partFactories.add(new FuelFilter.Factory());
        partFactories.add(new AirFilter.Factory());
        partFactories.add(new CabinAirFilter.Factory());
        partFactories.add(new OilFilter.Factory());
        partFactories.add(new FanBelt.Factory());
        partFactories.add(new PowerSteeringBelt.Factory());
        partFactories.add(new GeneratorBelt.Factory());
    }

    private static Random random = new Random(47);

    public static Part createRandom(){
        int n = random.nextInt(partFactories.size());
        return partFactories.get(n).create();
    }
}

public class Filter extends Part {
}

public class FuelFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<FuelFilter>{

        @Override
        public FuelFilter create() {
            return new FuelFilter();
        }
    }

}

public class OilFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<OilFilter>{

        @Override
        public OilFilter create() {
            return new OilFilter();
        }
    }
}

public class AirFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<AirFilter>{

        @Override
        public AirFilter create() {
            return new AirFilter();
        }
    }

}

public class CabinAirFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<CabinAirFilter>{

        @Override
        public CabinAirFilter create() {
            return new CabinAirFilter();
        }
    }

}

public class Belt extends Part {
}

public class FanBelt extends Belt {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<FanBelt>{

        @Override
        public FanBelt create() {
            return new FanBelt();
        }
    }
}

public class GeneratorBelt extends Belt {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<GeneratorBelt>{

        @Override
        public GeneratorBelt create() {
            return new GeneratorBelt();
        }
    }
}

public class PowerSteeringBelt extends Belt {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<PowerSteeringBelt>{

        @Override
        public PowerSteeringBelt create() {
            return new PowerSteeringBelt();
        }
    }
}

public interface Factory<T> {
    T create();
}

public class RegisterFactories {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Part.createRandom());
        }
        //Users don't need to know how to create an object, they just need to call
        Part part = Part.partFactories.get(2).create();
    }

}

The equivalence of instanceof and Class

instanceof and isInstance() produce exactly the same results, as do equals() and = =. instanceof keeps the concept of type, which means "are you this Class, or are you a derived Class of this Class?" , and if you use a Class object that = = compares time, you don't consider inheritance -- it's the exact type or not.

Example code:

public class FamilyVsExactType {

    public static void test(Object x){
        System.out.println("Testing x of type " + x.getClass());
        System.out.println("x instanceof Base " + (x instanceof Base));
        System.out.println("x instanceof Derived " + (x instanceof Derived));
        System.out.println("Base.isInstance(x)" + Base.class.isInstance(x));
        System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x));
        System.out.println("x.getClass() == Base.class " + (x.getClass() == Base.class));
        System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class));
        System.out.println("x.getClass().equals(Base.class) " + (x.getClass().equals(Base.class)));
        System.out.println("x.getClass().equals(Derived.class) " + (x.getClass().equals(Derived.class)));
    }

    public static void main(String[] args) {
        test(new Base());
        System.out.println("*****************************");
        test(new Derived());
    }
}
/*
out:
Testing x of type class TypeInfo.InstanceofAndClass.Base
x instanceof Base true
x instanceof Derived false
Base.isInstance(x)true
Derived.isInstance(x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass().equals(Base.class) true
x.getClass().equals(Derived.class) false
*****************************
Testing x of type class TypeInfo.InstanceofAndClass.Derived
x instanceof Base true
x instanceof Derived true
Base.isInstance(x)true
Derived.isInstance(x) true
x.getClass() == Base.class false
x.getClass() == Derived.class true
x.getClass().equals(Base.class) false
x.getClass().equals(Derived.class) true
 */

Reflection: class information at run time

If you don't know the exact type of an object, RTTI can tell you. But there is a limitation: this type must be known at compile time so that RTTI can be used to identify it and use this information to do something useful. In other words, at compile time, the compiler must know all classes to be processed through RTTI. In fact, at compile time, the program does not know the class to which this object belongs.

Class class supports the concept of reflection together with java.lang.reflect class library, which includes Field, Method and Constructor classes (each class implements the Member interface). These types of objects are created by the JVM at run time to represent the corresponding members of unknown classes. In this way, you can use Constructor to create new objects, use get() and set() methods to read and modify the fields associated with the Field object, and use invoke() Method to call the methods associated with the Method object. In addition, convenient methods such as getField(), getMethods(), and getConstructors() can be called to return an array of objects representing fields, methods, and constructors. In this way, the class information of anonymous objects can be completely determined at run time without knowing anything at compile time.

Note that there is nothing magical about the reflection mechanism. When dealing with a location type object through reflection, the JVM simply checks whether the object belongs to a specific class. You must load the class object of that class before you can do anything else with it. Therefore, that kind of. Class file must be accessible to the JVM: either locally or over the network. So the real difference between RTTI and reflection is that for RTTI, the compiler opens and checks the. Class file at compile time. For the reflection mechanism, the. Class file is not available at compile time, so it is opened and checked at run time. (reflection is relatively flexible compared with RTTI)

Class method extractor

Here is an example of getting the getter method in an object and calling it (write a non TIJ example yourself):

public class Frame implements Comparable<Frame>{

    Integer allFrameNum;

    Integer currFrameNum;

    byte[] data;

    Integer prmCode;

    Integer fnCode;

    MessageDirection dir;

    Date tp;

    String tpStr;

    Integer clientId;

    String firAndFin;

    Integer pfc;

    Integer Afn;

    Integer secretData;

    public Integer isSecretData() {
        return secretData;
    }

    public void setSecretData(Integer secretData) {
        this.secretData = secretData;
    }

    public Integer getAfn() {
        return Afn;
    }

    public void setAfn(Integer afn) {
        Afn = afn;
    }

    public Integer getAllFrameNum() {
        return allFrameNum;
    }

    public void setAllFrameNum(Integer allFrameNum) {
        this.allFrameNum = allFrameNum;
    }

    public Integer getCurrFrameNum() {
        return currFrameNum;
    }

    public void setCurrFrameNum(Integer currFrameNum) {
        this.currFrameNum = currFrameNum;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }

    public Integer getPrmCode() {
        return prmCode;
    }

    public void setPrmCode(Integer prmCode) {
        this.prmCode = prmCode;
    }

    public Integer getFnCode() {
        return fnCode;
    }

    public void setFnCode(Integer fnCode) {
        this.fnCode = fnCode;
    }

    public MessageDirection getDir() {
        return dir;
    }

    public void setDir(MessageDirection dir) {
        this.dir = dir;
    }

    public Date getTp() {
        return tp;
    }

    public void setTp(Date tp) {
        this.tp = tp;
    }

    public String getTpStr() {
        return tpStr;
    }

    public void setTpStr(String tpStr) {
        this.tpStr = tpStr;
    }

    public Integer getClientId() {
        return clientId;
    }

    public void setClientId(Integer clientId) {
        this.clientId = clientId;
    }

    public String getFirAndFin() {
        return firAndFin;
    }

    public void setFirAndFin(String firAndFin) {
        this.firAndFin = firAndFin;
    }

    public Integer getPfc() {
        return pfc;
    }

    public void setPfc(Integer pfc) {
        this.pfc = pfc;
    }

    @Override
    public String toString() {
        return "Frame{" +
                "allFrameNum=" + allFrameNum +
                ", currFrameNum=" + currFrameNum +
                ", data=" + Arrays.toString(data) +
                ", prmCode=" + prmCode +
                ", fnCode=" + fnCode +
                ", dir=" + dir +
                ", tp=" + tp +
                ", tpStr='" + tpStr + '\'' +
                ", clientId=" + clientId +
                ", firAndFin='" + firAndFin + '\'' +
                ", pfc=" + pfc +
                ", Afn=" + Afn +
                ", secretData=" + secretData +
                '}';
    }

    @Override
    public int compareTo(Frame frame){
        return Long.compare(this.getCurrFrameNum(),frame.getCurrFrameNum());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null){
            return false;
        }
        //Judge yourself
        if (this == obj){
            return true;
        }
        //Judgement type
        if (obj instanceof Frame){
            Frame other = (Frame)obj;
            Method[] methods = Frame.class.getMethods();
            for (Method cursor : methods) {
                //get method only
                if (!cursor.getName().contains("get")
                        ||cursor.getName().contains("set")){
                    continue;
                }
                //Compare the results obtained by get method
                try {
                    Method otherMothod = Frame.class.getMethod(cursor.getName());
                    //If this field is null, it must be the same for both parties
                    if (cursor.invoke(this) == null
                            && otherMothod.invoke(other) == null){
                        continue;
                    }
                    //It must be different if both parties have one or none of the fields
                    if (cursor.invoke(this) == null
                            && otherMothod.invoke(other) != null){
                        return false;
                    }
                    if (cursor.invoke(this) != null
                            && otherMothod.invoke(other) == null){
                        return false;
                    }
                    //If both sides of this field have to judge
                    if (cursor.getReturnType().getCanonicalName().contains("[]")){
                        //If it's an array
                        byte[] myData = (byte[]) cursor.invoke(this);
                        byte[] otherData = (byte[]) cursor.invoke(other);
                        if (!Arrays.equals(myData, otherData)){
                            return false;
                        }
                    }else if (!cursor.invoke(this).equals(otherMothod.invoke(other))){
                        //If it's not a collection
                        return false;
                    }
                } catch (Exception e) {
                    System.out.println("error method:" + cursor.getName());
                    e.printStackTrace();
                    return false;
                }
            }
        }else {
            return false;
        }
        return true;
    }

}

The example is to compare two self-defined objects. If getter is called one by one, the first is that the code is very bloated, and the second is that if later extension needs to be modified one by one, it is easy to make mistakes and not easy to expand.

Dynamic proxy

Proxy is one of the basic design patterns. It is an object inserted to replace the "actual" object in order to provide additional or different operations. These operations usually involve communication with "real" objects, so agents often act as intermediaries.

Example code:

public interface Interface {
    void doSomething();
    void doSomethingElse(String arg);
}

public class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                new Exception().getStackTrace()[0].getMethodName()
        );
    }

    @Override
    public void doSomethingElse(String arg) {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                new Exception().getStackTrace()[0].getMethodName() +
                ":" + arg
        );
    }
}

public class SimpleProxy implements Interface {

    private Interface proxied;
    public SimpleProxy(Interface proxied){
        this.proxied = proxied;
    }

    @Override
    public void doSomething() {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                        new Exception().getStackTrace()[0].getMethodName()
        );
        proxied.doSomething();
    }

    @Override
    public void doSomethingElse(String arg) {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                        new Exception().getStackTrace()[0].getMethodName() +
                        ":" + arg
        );
        proxied.doSomethingElse(arg);
    }
}

public class SPDemo {

    public static void consumer(Interface iface){
        iface.doSomething();
        iface.doSomethingElse("ABCDEFG");
    }

    public static void main(String[] args) {
        consumer(new RealObject());
        System.out.println("*********************");
        consumer(new SimpleProxy(new RealObject()));
    }

}
/*
out:
RealObject:doSomething
RealObject:doSomethingElse:ABCDEFG
*********************
SimpleProxy:doSomething
RealObject:doSomething
SimpleProxy:doSomethingElse:ABCDEFG
RealObject:doSomethingElse:ABCDEFG
 */

About Exception.getStackTrace()

Note in JDK:

/**
     * Provides programmatic access to the stack trace information printed by
     * {@link #printStackTrace()}.  Returns an array of stack trace elements,
     * each representing one stack frame.  The zeroth element of the array
     * (assuming the array's length is non-zero) represents the top of the
     * stack, which is the last method invocation in the sequence.  Typically,
     * this is the point at which this throwable was created and thrown.
     * The last element of the array (assuming the array's length is non-zero)
     * represents the bottom of the stack, which is the first method invocation
     * in the sequence.
     *
     * <p>Some virtual machines may, under some circumstances, omit one
     * or more stack frames from the stack trace.  In the extreme case,
     * a virtual machine that has no stack trace information concerning
     * this throwable is permitted to return a zero-length array from this
     * method.  Generally speaking, the array returned by this method will
     * contain one element for every frame that would be printed by
     * {@code printStackTrace}.  Writes to the returned array do not
     * affect future calls to this method.
     *
     * @return an array of stack trace elements representing the stack trace
     *         pertaining to this throwable.
     * @since  1.4
     */

Machine turn:

/**

*Provides programmatic access to stack trace information printed by
 *{@link#printStackTrace()}.  Returns an array of stack trace elements,
*Each represents a stack frame. Element 0 of array
 *(assuming the length of the array is non-zero)
*Stack, which is the last method call in the sequence. In general,
*This is the point where the projectile is created and thrown.
*The last element of the array (assuming the length of the array is non-zero)
*Represents the bottom of the stack, which is the first method call
 *In order.
*
*<p> In some cases, some virtual machines may ignore one
 *Or more stack frames in the stack trace. In extreme cases,
*Virtual machine without stack trace information
 *Allow this throwable to return a zero length array from
 * methods. In general, the array returned by this method will
 *Contains an element for each frame to be printed
 *{@code printStackTrace}.  Write to return array not
 *Affects future calls to this method.
*
*@Returns an array of stack trace elements representing a stack trace
 *About the missile.
*@From 1.4
*/

At any time, as long as you want to separate additional operations from the "actual" objects, especially if you want to make changes easily, you can use these operations instead of using them, or vice versa. (the key to the design pattern is to encapsulate the changes -- so you need to modify the transaction to prove this pattern The correctness of the formula).

Java's idea of dynamic proxy goes a little further than the idea of proxy, because it can dynamically create a proxy and handle calls to the proxy methods. All calls made on the dynamic agent are redirected to a single call processor. Its job is to reveal the type of call and determine the corresponding countermeasures.

Example code:

public class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** Proxy :" + proxy.getClass() + ".method:" + method + ",arg:" + args);
        if (args != null){
            for (Object arg :
                    args) {
                System.out.print("  " + arg);
            }
        }
        return method.invoke(proxied, args);
    }
}

public class SimpleDynamicProxyDemo {

    public static void consumer(Interface iface){
        iface.doSomething();
        iface.doSomethingElse("ABCDEFG");
    }

    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);
    }
}
/*
out:
RealObject:doSomething
RealObject:doSomethingElse:ABCDEFG
**** Proxy :class com.sun.proxy.$Proxy0.method:public abstract void TypeInfo.ProxyDemo.SimpleProxyDemo.Interface.doSomething(),arg:null
RealObject:doSomething
**** Proxy :class com.sun.proxy.$Proxy0.method:public abstract void TypeInfo.ProxyDemo.SimpleProxyDemo.Interface.doSomethingElse(java.lang.String),arg:[Ljava.lang.Object;@433c675d
  ABCDEFGRealObject:doSomethingElse:ABCDEFG
 */

You can create a dynamic proxy by calling the static method Proxy.newProxyInstance(). This method needs to get a loader, a list of interfaces you want the proxy to implement (not a class or an abstract class), and an implementation of the InvocationHandler interface. The dynamic agent can redirect all calls to the calling processor, so it usually passes the constructor of the calling processor to a reference of an "actual" object, so that the calling processor can forward the request when it performs its mediation task.

A proxy object is passed in the invoke() method in case you need to distinguish the source of the request, but in many cases you don't care. However, inside invoke(), you need to be very careful when calling methods on the proxy, because calls to the interface will be redirected to calls to the proxy.

Usually, you will perform the operation of the proxy, and then use Method.invoke() to forward the request to the proxy object, and pass in the required parameters. In this way, although it seems to be somewhat limited, it is just like the generalization operation can only be performed. However, some method calls can be filtered by passing other parameters.

Example code:

public interface SomeMethods {

    void boringOne();
    void interesting(String arg);
    void boringTwo();
    void boringThree();

}

public class Implementation implements SomeMethods {
    @Override
    public void boringOne() {
        System.out.println("boringOne");
    }

    @Override
    public void interesting(String arg) {
        System.out.println("interesting:" + arg);
    }

    @Override
    public void boringTwo() {
        System.out.println("boringTwo");
    }

    @Override
    public void boringThree() {
        System.out.println("boringThree");
    }
}

public class MethodSelector implements InvocationHandler {
    private Object proxied;

    public MethodSelector(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("interesting")){
            System.out.println("Proxy detected the interesting method");
        }
        return method.invoke(proxied, args);
    }
}

public class SelectingMethods {

    public static void main(String[] args) {
        SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance(
                SomeMethods.class.getClassLoader(),
                new Class[]{SomeMethods.class},
                new MethodSelector(new Implementation())
        );
        proxy.boringOne();
        proxy.boringTwo();
        proxy.boringThree();
        proxy.interesting("ABCEDFG");
    }
}

About agent mode

Provides a proxy for other objects to control access to that object. The proxy object mediates between the client and the target object.

Published 23 original articles, won praise 8, visited 1665
Private letter follow

Topics: Java jvm Programming network