Interface, lambda expression and inner class
6.1 interface
6.1.1 concept of interface
Like the equals method, the Comparable interface may have problems in inheritance. For example, Manager extends Employee, which implements Comparable < Employee > instead of Comparable < Manager >. If the Manager wants to override compareTo, you must be prepared to compare managers and employees. You must not just force employees into managers:
class Manager extends Employee { public int compareTo(Employee other) { Manager otherManager = (Manager) other; // No // ... } // ... }
Violation of the antisymmetric rule. If x is an Employee object and Y is a Manager object, calling x.compareTo(y) will not throw an exception. It just compares X and y as employees. But conversely, y.compareTo(x) will throw a ClassCastException.
This situation is the same as the equals method, and the remedy is the same. There are two different situations:
- If the comparison in different subclasses has different meanings, the comparison between objects belonging to different classes should be regarded as illegal. Each compareTo method should start with the following detection: if (getclass())= other. getClass()) throw new ClassCastException();.
- If there is a general algorithm that can compare subclass objects, you can provide a compareTo method in the superclass and declare this method as final. For example, if you want to arrange by job, you should provide a rank method in the Employee class. Let each subclass override rank and implement a compareTo method that considers the rank value.
6.1.2 interface properties
All methods in the interface are automatically public methods. The interface can define constants and is always public static final, but there will never be instance fields, but simple methods can be provided. Of course, these methods cannot reference instance fields.
6.1.5 default method
You can provide a default implementation for interface methods. Such a method must be marked with the default modifier so that subclasses do not have to implement it. One important usage is interface evolution.
6.1.6 resolving default method conflicts
If you first define a method as the default method in an interface, and then define the same method in a superclass or another interface, the rules in java are as follows:
- Superclass priority. If the superclass provides a concrete method, the default method with the same name and the same parameter type will be ignored.
- Interface conflict. If one interface provides a default method and another interface provides a method with the same name and the same parameter type (whether it is the default parameter or not), this method must be overridden to resolve the conflict.
interface Person { default String getName() { return ""; } } interface Named { default String getName() { return getClass().getName() + "_" + hashCode(); } } // Class inherits two inconsistent getName methods provided by the Person and Named interfaces. Not one of them. The Java compiler reports an error, // Let the programmer solve this ambiguity problem. You only need to provide a getName method in the Student class. In this method, you can select two // One of the conflicting methods: class Student implements Person, Named { public String getName() { return Person.super.getName(); } }
6.1.7 interface and callback
Callback is a common programming pattern. In this mode, you can specify the action that should be taken when a specific event occurs.
public class TimerTest { public static void main(String[] args) { var listener = new TimePrinter(); var timer = new Timer(1000, listener); timer.start(); JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); Toolkit.getDefaultToolkit().beep(); } }
6.1.9 object cloning
The clonable interface indicates that a class provides a secure clone method. It should be noted that clone is a protected method of Object, which means that your code cannot call this method directly. For example, only the Employee class can clone an employee Object. There is a reason for this restriction. Think about how the Object class implements clone. It knows nothing about this Object, so it can only copy field by field. If all data fields in the Object are numeric or other basic types, there is no problem copying these fields. However, if the Object contains a reference to a child Object, the copy field will get another reference to the same child Object. In this way, the original Object and the cloned Object will still share some information.
By default, cloning is a shallow copy, with no other objects referenced in the cloned object. Will shallow copies have any impact? It depends on the specific situation. If the child objects shared by the original object and the clone object are immutable, the sharing is secure. This is the case if the child object belongs to an immutable class, such as String. Or in the life cycle of the object, the child object always contains constant, no modifier method will change it, and no method will generate its reference. This is also safe.
However, child objects are usually mutable, and the clone method must be redefined to create a deep copy and clone all child objects at the same time.
For each class, you need to determine:
- Whether the default clone method meets the requirements.
- Whether clone can be called on mutable child objects to patch the default clone method.
- Whether clone should not be used.
In fact, the third option is the default. If item 1 or 2 is selected, the class must:
- Implement clonable interface.
- Redefine the clone method and specify the public access modifier.
The clone method in the Object class is declared protected, so your code cannot directly call anobject clone(). Due to the subtle rules of protected access, a subclass can only call the protected clone method to clone its own objects. Clone must be redefined as public to allow all methods to clone objects.
public class CloneTest { public static void main(String[] args) throws CloneNotSupportedException { Employee original = new Employee("John Q. Public", 50000); original.setHireDay(2000, 1, 1); Employee copy = original.clone(); copy.raiseSalary(10); copy.setHireDay(2002, 12, 31); System.out.println("original=" + original); System.out.println("copy=" + copy); } } class Employee implements Cloneable { private String name; private double salary; private Date hireDay; public Employee(String name, double salary) { this.name = name; this.salary = salary; hireDay = new Date(); } @Override public Employee clone() throws CloneNotSupportedException { Employee cloned = (Employee) super.clone(); cloned.hireDay = (Date) hireDay.clone(); return cloned; } public void setHireDay(int year, int month, int day) { Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime(); hireDay.setTime(newHireDay.getTime()); } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } } class Manager extends Employee { private double bonus; public Manager(String name, double salary) { super(name, salary); } public double getBonus() { return bonus; } public void setBonus(double bonus) { this.bonus = bonus; } } // Must be a clone of the heart subclass. For example, once the clone method is defined for the Employee class, anyone can use it to clone the Manager object. // Can the Employee cloning method complete the work? This depends on the fields of the Manager class. There is no problem here, because the bonus field is // Basic type. However, the Manager may have fields that require deep copy or cannot be cloned. There is no guarantee that the implementer of the subclass will fix the clone // Method to make it work. For this reason, in the Object class, the clone method is declared protected. However, if you want class users // You cannot do this by calling clone. // Do you want to implement clone in your own class? If the customer needs to create a deep copy, it may need to implement this method, so it depends.
6.2 lambda expression
A Lambda expression is a transitive block of code that can be executed one or more times later.
6.2.3 functional interface
For an interface with only one abstract method, you can provide a lambda expression when you need the object of this interface. This interface is called a functional interface.
Arrays.sort(words, (first, second) -> first.length() - second.length());
At the bottom, arrays The sort method will accept objects that implement a class of comparator < string >. Calling the compare method on this object will execute the body of the lambda expression. The management of these objects and classes depends entirely on the specific implementation, which may be much more efficient than using traditional inline classes. It's best to think of a lambda expression as a function rather than an object. In addition, accept that lambda expressions can be passed to functional interfaces.
Lambda expressions can be converted to interfaces, which makes lambda expressions attractive:
var timer = new Timer(1000, event -> { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); Toolkit.getDefaultToolkit().beep(); });
In fact, in java, all you can do with a lambda expression is to convert it into a functional interface. Therefore, you cannot assign a lambda expression to a variable of type object. Object is not a functional interface.
6.2.4 method reference
Sometimes lambda expressions involve only one method call:
var timer = new Timer(1000, event -> System.out.println(event)); // Equivalent to var timer = new Timer(1000, System.out::println);
Expression system Out:: println is a method reference that instructs the compiler to generate an instance of a functional interface and override the abstract method of the interface to call the given method. In this example, an ActionListener will be generated, and its actionPerformed(ActionEvent e) method will call system out. println(e).
Similar to a lambda expression, a method reference is not an object. However, assigning a value to a variable whose type is a functional interface produces an object.
There are three main situations to separate method name from object or class name with:: Operator:
- object::instanceMethod
- Class::instanceMethod
- Class::staticMethod
In the first case, the method reference is equivalent to a lambda expression that passes parameters to the method. For system Out:: println, the object is system Out, so the method expression is equivalent to X - > system out. println(x).
In case 2, the first parameter becomes an implicit parameter of the method. For example, String::compareToIgnoreCase is equivalent to (x, y) - > x.comparetoignorecase (y).
In case 3, all parameters are passed to the static method: Math::pow is equivalent to (x, y) - > math pow(x, y).
Method reference | Equivalent lambda expression | explain |
---|---|---|
separator::equals | x -> separator.equals(x) | This is a method expression that contains an object and an instance method. Lambda parameters are passed in as explicit parameters for this method |
String::trim | x -> x.trim() | This is a method expression that contains a class and an instance method. Lambda expressions become implicit parameters |
String::concat | (x, y) -> x.concat(y) | Again, there is an instance method, but this time by an explicit parameter. As before, the first lambda parameter becomes an implicit parameter, and the remaining parameters are passed to the method |
Integer::valueOf | x -> Integer::valueOf(x) | This is a method expression that contains a static method. Lambda parameters are passed to this static method |
Integer::sum | (x, y) -> Integer::sum(x, y) | This is another static method, but this time there are two parameters. Both lambda parameters are passed to this static method. Integer. The sum method is specifically created as a method reference. For lambda expressions, you can write only (x, y) - > x + y |
Integer::new | x -> new Integer(x) | This is a constructor reference. Lambda parameters are passed to this constructor |
Integer[]::new | n -> new Integer[n] | This is an array constructor reference. The Lambda parameter is the array length |
Note that you can rewrite a lambda expression as a method reference only when the body of the lambda expression calls only one method without doing anything else.
There is a subtle difference between the method reference containing the object and the equivalent lambda expression. Consider a method reference, such as separator::equals. If the separator is null, a NullPointerException exception will be thrown immediately when constructing separator::equals. Lambda expression X - > separator Equals (x) NullPointerException is thrown only when called.
You can use this parameter in a method reference. For example, this::equals is equivalent to X - > this equals(x). Using super is also legal. Using super as the target will call the superclass version of the given method:
class Greeter { public void greet(ActionEvent event) { System.out.println("Hello, the time is " + Instant.ofEpochMilli(event.getWhen())); } } class RepeatedGreeter extends Greeter { public void greet(ActionEvent event) { // Equivalent to event - > super greet(event) var timer = new Timer(1000, super::greet); timer.start(); } }
6.2.5 constructor reference
Constructor references are similar to method references, except that the method name is new, and which constructor to use depends on the context.
ArrayList<String> names = ...; Stream<Person> stream = names.stream().map(Person::new); List<Person> people = stream.collect(Collectors.toList()); // If there are multiple Person constructors, the compiler will select the constructor with a String parameter to take it from the context // It is deduced that this is when calling the constructor with a string
Constructor references can be created with array types. For example, int []: new is a constructor reference that has one parameter, that is, the length of the array. This is equivalent to lambda expression X - > New Int [x].
Java has a limitation that it cannot construct arrays of generic type T. Array constructor references are useful to overcome this limitation. The expression new T[n] generates an error because it changes to new Object[n]. This is a problem for developers of class libraries. Therefore, a better solution is:
Person[] people = stream.toArray(Person[]::new); // The toArray method calls this constructor to get an array with the correct type, // Then fill in and return the array
6.2.6 variable scope
You can access variables in peripheral methods or classes in lambda expressions. The specific implementation details are that a lambda expression can be converted into an object containing a method, so that the value of the free variable will be copied to the instance variable of the object. However, it should be noted that in lambda expressions, only variables whose values will not change can be referenced (in fact, the final variable, that is, the variable will not be assigned a new value after initialization).
There is a reason for this restriction. If you change variables in a lambda expression, it is not safe to perform multiple actions concurrently. Therefore, the following codes are illegal:
public static void countDown(int start, int delay) { ActionListener listener = event -> { // Error: Can't mutate captured variable // Variable used in lambda expression should be final or effectively final start--; System.out.println(start); }; new Timer(delay, listener).start(); } public static void repeat(String text, int count) { for (int i = 1; i <= count; i++) { ActionListener listener = event -> { System.out.println(i + ": " + text); // Error: Cannot refer to changing i }; new Timer(1000, listener).start(); } }
The body of a lambda expression has the same scope as a nested block. The same rules apply here for naming conflicts and masking. It is illegal to declare a parameter or local variable with the same name as a local variable in a lambda expression:
Path first = Path.of("/usr/bin"); Comparator<String> comp = (first, second) -> first.length - second.lenght; // Error: variable first already defined
When this keyword is used in a lambda expression, it refers to the this parameter of the method that creates the lambda expression:
public class Application { public void init() { ActionListener listener = event -> { System.out.println(this.toString()); // ... }; // ... } } // this.toString() will call the toString method of the Application object, not the method of the ActionListener instance. // The scope of the lambda expression is nested in the init method. Like other places in this method, this is in the lambda expression // No change in meaning.
6.2.7 handling lambda expressions
The focus of using lambda expressions is to delay execution. After all, if you want to execute code immediately, you can execute it directly without wrapping it in a lambda expression. There are many reasons why you want to execute code later:
- Run the code in a separate thread.
- Run the code multiple times.
- Run the code in the appropriate place of the algorithm.
- Execute code when something happens
- Run the code only if necessary.
Common functional interfaces:
Functional interface | Parameter type | Return type | Abstract method name | describe | Other methods |
---|---|---|---|---|---|
Runnable | nothing | void | run | Run as an action with no parameters or return values | |
Supplier<T> | nothing | T | get | Provide a value of type T | |
Consumer<T> | T | void | accept | Handle a value of type T | andThen |
BiConsumer<T, U> | T, U | void | accept | Process values of T and U types | andThen |
Function<T, R> | T | R | apply | Function with a T-type parameter | compose,andThen,identity |
BiFunction<T, U, R> | T, U | R | apply | Functions with T and U type parameters | andThen |
UnaryOperator<T> | T | T | apply | Unary operator on type T | compose,andThen,identity |
BinaryOperator<T> | T, T | T | apply | Binary operator on type T | andThen,maxBy,minBy |
Predicate<T> | T | booean | test | Boolean function | and,or,negate,isEqual |
BiPredicate<T, U> | T, U | boolean | test | Boolean function with two parameters | and,or,negate |
Functional interfaces of basic types (Note: P and Q are int, long and double; P and Q are int, long and Double):
Functional interface | Parameter type | Return type | Abstract method name |
---|---|---|---|
BooleanSupplier | nothing | boolean | getAsBoolean |
PSupplier | nothing | p | getAsP |
PConsumer | p | void | accept |
ObjPConsumer<T> | T, p | void | accept |
PFunction<T> | p | T | apply |
PToQFunction | p | q | applyAsQ |
ToPFunction<T> | T | p | applyAsP |
ToPBiFunction<T, U> | T, U | p | applyAsP |
PUnaryOperator | p | p | applyAsP |
PBinaryOperator | p, p | p | applyAsP |
PPredicate | p | boolean | test |
Most standard functional interfaces provide non abstract methods to generate or merge functions. For example, predict Isequal (a) is equivalent to a::equals, but it works if a is null. Default methods and, or, and negate have been provided to merge predicates. For example, predict isEqual(a). Or (predict. Isequal (b)) is equivalent to X - > a.equals (x) | b.equals (x).
6.3 internal class
An internal class is a class defined in another class. There are two main reasons for using an internal class:
- Inner classes can be hidden from other classes in the same package.
- Internal class methods can access the data in the scope that defines this class, including the original private data.
6.3.1 accessing object status using internal classes
public class InnerClassTest { public static void main(String[] args) { var clock = new TalkingClock(1000, true); clock.start(); JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } class TalkingClock { private int interval; private boolean beep; public TalkingClock(int interval, boolean beep) { this.interval = interval; this.beep = beep; } public void start() { var listener = new TimePrinter(); var timer = new Timer(interval, listener); timer.start(); } // Being inside the TalkingClock class does not mean that every TalkingClock has a TimePrinter instance field public class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); // beep instructs the TalkingClock object to create the field of this TimePrinter if (beep) { Toolkit.getDefaultToolkit().beep(); } } } }
As you can see, an internal class method can access its own data fields, as well as the data fields of the peripheral class objects that create it. For this reason, the object of the inner class always has an implicit reference to the external class object that created it. This reference is not visible in the definition of the inner class. However, to illustrate this concept, the reference to the peripheral class object is called outer:
public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); if (outer.beep) { Toolkit.getDefaultToolkit().beep(); } }
The reference of the peripheral class is set in the constructor. The compiler will modify all internal class constructors and add a parameter corresponding to the peripheral class reference:
// Automatically generated code public TimePrinter(TalkingClock clock) { outer = clock; }
After constructing a TimePrinter object in the start method, the compiler will pass the this reference of the current voice clock to the constructor: var listener = new TimePrinter(this);.
6.3.2 special syntax rules for internal classes
In fact, the formal syntax for using peripheral class references is more complex: outerclass This indicates the peripheral class reference:
public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); if (TalkingClock.this.beep) { Toolkit.getDefaultToolkit().beep(); } }
Conversely, you can use outerobject Syntax such as new innerclass (construction parameters) more explicitly writes the constructor of internal class objects: actionlistener listener = this new TimePrinter();.
Here, the peripheral class reference of the newly constructed TimePrinter object is set as the this reference of the method that creates the inner class object. This is the most common case. Usually, this Qualifiers are redundant. However, peripheral class references can also be set to other objects by explicitly naming them. For example, since TimePrinter is a public internal class, a TimePrinter can be constructed for any voice clock:
var jabberer = new TalkingClock(1000, true); TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
6.3.3 is the internal class useful, necessary and safe
Inner class is a compiler phenomenon, which has nothing to do with virtual machine. The compiler will convert the internal class into a regular class file, separating the external class name from the internal class name with $, and the virtual machine knows nothing about it:
public class innerClass.TalkingClock$TimePrinter implements java.awt.event.ActionListener { final innerClass.TalkingClock this$0; public innerClass.TalkingClock$TimePrinter(innerClass.TalkingClock); public void actionPerformed(java.awt.event.ActionEvent); } // You can clearly see that the compiler generates an additional instance field this this $0,, corresponding to the reference of the peripheral class. In addition, // You can also see the TalkingClock parameter of the constructor.
The reason why the inner class has those additional access permissions is that the compiler adds some additional static methods to the outer class, such as:
class TalkingClock { private int interval; private boolean beep; public TalkingClock(int, boolean); static boolean access$0(TalkingClock); // The inner class only accesses the beep of the outer class, so only the corresponding static methods are generated public void start(); } // Therefore, if (beep) is equivalent to if (TalkingClock.access if (talkingclock. Access $0 (outer))(outer))
However, there are some security risks in doing so. Hackers familiar with the class file structure can easily create a class file using a hexadecimal editor, in which the static method is called using virtual machine instructions. Since the covert method needs to have package visibility, the attack code needs to be placed in the same package as the attacked class.
Internal part
If an internal class is used only once in the method of creating an object of this type, it can be defined locally in a method:
public void start() { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); if (beep) { Toolkit.getDefaultToolkit().beep(); } } } var listener = new TimePrinter(); var timer = new Timer(interval, listener); timer.start(); }
You cannot have an access specifier when declaring a local class. The scope of a local class is limited to the block that declares the local class. A local class has a great advantage, that is, it is completely hidden from the external world, and even other code of the external class cannot access it.
6.3.5 accessing variables by external methods
Compared with other inner classes, local classes have another advantage. They can access not only fields of external classes, but also local variables. However, those local variables must be fact final variables, that is, they will never change once assigned.
public void start(int interval, boolean beep) { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); if (beep) { Toolkit.getDefaultToolkit().beep(); } } } var listener = new TimePrinter(); var timer = new Timer(interval, listener); timer.start(); } // TalkingClock no longer needs to store the instance variables beep and interval. TimePrinter just references the beep and interval parameter variables in the start method. // This is possible because the compiler: class TalkingClock$1TimePrinter { TalkingClock$1TimePrinter(TalkingClock, boolean, int); public void actionPerformed(java.awt.event.ActionEvent); final boolean val$beep; final int val$interval; final TalkingClock this$0; } // Notice constructor parameters and instance variables. When an object is created, the corresponding value is passed to the constructor and stored in the instance variable. // The compiler detects the access to local variables, establishes the corresponding instance field for each variable, and copies the local variables to the constructor // Initialize these instance fields.
6.3.6 anonymous inner class
When using local inner classes, you can usually go further. If you only want to create an object of this class, you don't even need to specify a name for the class. Such a class is called an anonymous inner class.
public void start(int interval, boolean beep) { var listener = new ActionListener(){ public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen())); if (beep) { Toolkit.getDefaultToolkit().beep(); } } }; var timer = new Timer(interval, listener); timer.start(); } // The corresponding syntax meaning is to create a new object of a class that implements the ActionListener interface and the methods to be implemented // actionPerformed is defined in parentheses {}.
Because the constructor name must be the same as the class name, and the anonymous inner class has no class name, the anonymous inner class cannot have a constructor. In fact, the construction parameters are passed to the superclass constructor. Specifically, as long as the inner class implements an interface, it cannot have any construction parameters. However, a set of parentheses should still be provided.
When generating logs or debugging messages, you usually want to include the class name of the current class:
System.err.println("Something awful happened in " + getClass());
However, this does not work for static methods. After all, this is called when getClass is called Getclass(), but the static method does not have this. Therefore, the following expression should be used:
new Object(){}.getClass().getEnclosingClass() // gets class or static method
Here, new Object() {} will create an anonymous Object of the anonymous subclass of Object, and getEnclosingClass will get its enclosing class, that is, the class containing this static method.
6.3.7 static internal class
Sometimes, internal classes are used only to hide one class inside another class, and it is not necessary for the internal class to have a reference to the peripheral class object. To do this, you can declare the inner class static so that the reference will not be generated.
public class StaticInnerClassTest { public static void main(String[] args) { var values = new double[4]; for (int i = 0; i < values.length; i++) { values[i] = 100 * Math.random(); } ArrayAlg.Pair p = ArrayAlg.minmax(values); System.out.println("min = " + p.getFirst()); System.out.println("max = " + p.getSecond()); } } class ArrayAlg { /** * Since Pair is a very popular name, to prevent naming conflicts, the solution is to define Pair as a public internal class */ public static class Pair { private double first; private double second; public Pair(double first, double second) { this.first = first; this.second = second; } public double getFirst() { return first; } public double getSecond() { return second; } } public static Pair minmax(double[] values) { double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; for (double v : values) { if (min > v) { min = v; } if (max < v) { max = v; } } // Static inner classes must be used in the example because inner class objects are constructed in static methods. // If Pair is not declared as static, the compiler will report an error indicating that there is no Pair // An implicit ArrayAlg type object can be used to initialize an inner class object return new Pair(min, max); } }
Static inner classes should be used as long as inner classes do not need to access peripheral class objects. Unlike regular inner classes, static inner classes can have static fields and methods. The inner classes declared in the interface are automatically static and public.
6.5 agency
Agents allow you to create new classes at run time that implement a given set of interfaces. It is only necessary to use a proxy when it is not possible to determine at compile time which interface needs to be implemented.
6.5.1 when to use an agent
Suppose you want to construct an object of a class that implements one or more interfaces, but you may not know what these interfaces are at compile time. To construct a concrete class, simply use the newInstance method or use reflection to find out the constructor. However, you cannot instantiate an interface. You need to define a new class in the running program.
To solve this problem, some programs generate code, put them in a file, invoke the compiler, and load the class files. Naturally, this is slower and requires the compiler to be deployed with the program. The agent mechanism is a better solution. Proxy classes can create entirely new classes at run time. Such a proxy class can implement the specified interface. Specifically, the proxy class contains the following methods:
- Specify all the methods required by the interface.
- All methods in the Object class. For example, toString, equals, and so on.
However, you cannot define new code for these methods at run time. In fact, a call handler must be provided. The calling processor is an object of a class that implements the InvocationHandler interface. This interface has only one method:
Object invoke(Object proxy, Method method, Object[] args)
Whenever the Method of the proxy object is called, the invoke Method of the calling processor will be called, and the Method object and the parameters of the original call will be passed to it. After that, the processor must determine how to handle the call.
6.5.2 creating proxy objects
To create a Proxy object, you need to use the newProxyInstance method of the Proxy class. This method has three parameters:
- A class loader. As part of the java security model, different class loaders can be used for platform and application classes, classes downloaded from the Internet, etc.
- An array of Class objects. Each element corresponds to each interface to be implemented.
- A calling processor.
Agents can be used for many purposes, such as:
- Route method calls to remote servers.
- Associate user interface events with actions in a running program.
- To debug and track method calls.
public interface IUserDao { void save(); } public class UserDao implements IUserDao { @Override public void save() { System.out.println("----Data saved!----"); } } public class ProxyFactory { // Maintain a target object private Object target; public ProxyFactory(Object target) { this.target = target; } // Generate proxy object for target object public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> { System.out.println("Start transaction 2"); System.out.println("Class to which the target object belongs: " + target.getClass()); System.out.println("Target object method: " + method.getName()); System.out.println("Target object method parameters: " + Arrays.toString(args)); // Execute target object method Object returnValue = method.invoke(target, args); System.out.println("Commit transaction 2"); return returnValue; }); } } public class App { public static void main(String[] args) { // Target object IUserDao target = new UserDao(); // Original type UserDao System.out.println(target.getClass()); // Create a proxy object for the target object IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance(); // class $Proxy0 dynamically generated proxy object in memory System.out.println(proxy.getClass()); // Execute proxy object method proxy.save(); } }
public class ProxyTest { public static void main(String[] args) { var elements = new Object[1000]; // Fill elements with proxies for the integers 1 ... 1000 for (int i = 0; i < elements.length; i++) { Integer value = i + 1; var handler = new TraceHandler(value); // Create a proxy object Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Comparable.class}, handler); elements[i] = proxy; } // Construct a random integer Integer key = 288; // Search for the key int result = Arrays.binarySearch(elements, key); // print match if found if (result >= 0) { System.out.println(elements[result]); } } } /** * An invocation handler that prints out the method name and parameters, then invokes the original method */ class TraceHandler implements InvocationHandler { private Object target; /** * Constructs a TraceHandler * @param target the implicit parameter of the method call */ public TraceHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.print(target); System.out.print("." + method.getName() + "("); if (args != null) { for (int i = 0; i < args.length; i++) { System.out.print(args[i]); if (i < args.length - 1) { System.out.print(", "); } } } System.out.println(")"); return method.invoke(target, args); } }
6.5.3 characteristics of agent class
Remember that proxy classes are created dynamically during program execution. However, once created, they become regular classes, no different from any other class in the virtual machine.
All Proxy classes extend the Proxy class. A Proxy class has only one instance field -- the calling handler, which is defined in the Proxy superclass. Any additional data required to complete the Proxy object task must be stored in the calling processor.
All proxy classes should override the toString, equals and hashCode methods of the Object class. Like all proxy methods, these methods simply call invoke on the calling processor. Other methods in the Object class, such as clone and getClass, are not redefined.
For a specific class loader and a preset set of interfaces, there can only be one proxy class. That is, if the newProxyInstance method is called twice using the same class loader and interface array, two objects of the same class will be obtained. You can also use getProxyClass to obtain this class:
Class proxyClass = Proxy.getProxyClass(null, interfaces);
Proxy classes are always public and final. If all interfaces implemented by the proxy class are public, the proxy class does not belong to any specific package; Otherwise, all non-public interfaces must belong to the same package, and the proxy class also belongs to this package.
You can detect whether a specific Class object represents a Proxy Class by calling the isProxyClass method of the Proxy Class.