Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 108 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
108
Dung lượng
1,34 MB
Nội dung
A dynamic instanceof The Class.islnstance( ) method provides a way to dynamically test the type of an object. Thus, all those tedious instanceof statements can be removed from PetCount.java: //: typeinfo/PetCount3.java // Using isInstance() import typeinfo.pets.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class PetCount3 { static class PetCounter extends LinkedHashMap<Class<? extends Pet>,Integer> { public PetCounter() { super(MapData.map(LiteralPetCreator.allTypes, 0)); } public void count(Pet pet) { // Class.isInstance() eliminates instanceofs: for(Map.Entry<Class<? extends Pet>,Integer> pair : entrySet()) if(pair.getKey().isInstance(pet)) put(pair.getKey(), pair.getValue() + 1); } public String toString() { StringBuilder result = new StringBuilder("{"); for(Map.Entry<Class<? extends Pet>,Integer> pair : entrySet()) { result.append(pair.getKey().getSimpleName()); result.append("="); result.append(pair.getValue()); result.append(", "); } result.delete(result.length()-2, result.length()); result.append("}"); return result.toString(); } } public static void main(String[] args) { PetCounter petCount = new PetCounter(); for(Pet pet : Pets.createArray(20)) { printnb(pet.getClass().getSimpleName() + " "); petCount.count(pet); } print(); print(petCount); } } /* Output: Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Pet=20, Dog=6, Cat=9, Rodent=5, Mutt=3, Pug=3, EgyptianMau=2, Manx=7, Cymric=5, Rat=2, Mouse=2, Hamster=1} *///:~ In order to count all the different types of Pet, the PetCounter Map is preloaded with the types from LiteralPetCreator.allTypes. This uses the net.mindview.util.MapData class, which takes an Iterable (the allTypes List) and a constant value (zero, in this case), and fills the Map with keys taken from allTypes and values of zero). Without pre-loading the Map, you would only end up counting the types that are randomly generated, and not the base types like Pet and Cat. Type Information 411 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com You can see that the isInstance( ) method has eliminated the need for the instanceof expressions. In addition, this means that you can add new types of Pet simply by changing the LiteralPetCreator.types array; the rest of the program does not need modification (as it did when using the instanceof expressions). The toString( ) method has been overloaded for easier-to-read output that still matches the typical output that you see when printing a Map. Counting recursively The Map in PetCount3.PetCounter was pre-loaded with all the different Pet classes. Instead of pre-loading the map, we can use Class.isAssignableFrom( ) and create a general-purpose tool that is not limited to counting Pets: //: net/mindview/util/TypeCounter.java // Counts instances of a type family. package net.mindview.util; import java.util.*; public class TypeCounter extends HashMap<Class<?>,Integer>{ private Class<?> baseType; public TypeCounter(Class<?> baseType) { this.baseType = baseType; } public void count(Object obj) { Class<?> type = obj.getClass(); if(!baseType.isAssignableFrom(type)) throw new RuntimeException(obj + " incorrect type: " + type + ", should be type or subtype of " + baseType); countClass(type); } private void countClass(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); } public String toString() { StringBuilder result = new StringBuilder("{"); for(Map.Entry<Class<?>,Integer> pair : entrySet()) { result.append(pair.getKey().getSimpleName()); result.append("="); result.append(pair.getValue()); result.append(", "); } result.delete(result.length()-2, result.length()); result.append("}"); return result.toString(); } } ///:~ The count( ) method gets the Class of its argument, and uses isAssignableFrom( ) to perform a runtime check to verify that the object that you’ve passed actually belongs to the hierarchy of interest. countClass( ) first counts the exact type of the class. Then, if baseType is assignable from the superclass, countClass( ) is called recursively on the superclass. 412 Thinking in Java Bruce Eckel Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com //: typeinfo/PetCount4.java import typeinfo.pets.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class PetCount4 { public static void main(String[] args) { TypeCounter counter = new TypeCounter(Pet.class); for(Pet pet : Pets.createArray(20)) { printnb(pet.getClass().getSimpleName() + " "); counter.count(pet); } print(); print(counter); } } /* Output: (Sample) Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Mouse=2, Dog=6, Manx=7, EgyptianMau=2, Rodent=5, Pug=3, Mutt=3, Cymric=5, Cat=9, Hamster=1, Pet=20, Rat=2} *///:~ As you can see from the output, both base types as well as exact types are counted. Exercise 11: (2) Add Gerbil to the typeinfo.pets library and modify all the examples in this chapter to adapt to this new class. Exercise 12: (3) Use TypeCounter with the CoffeeGenerator.java class in the Generics chapter. Exercise 13: (3) Use TypeCounter with the RegisteredFactories.java example in this chapter. Registered factories A problem with generating objects of the Pets hierarchy is the fact that every time you add a new type of Pet to the hierarchy you must remember to add it to the entries in LiteralPetCreator.java. In a system where you add more classes on a regular basis this can become problematic. You might think of adding a static initializer to each subclass, so that the initializer would add its class to a list somewhere. Unfortunately, static initializers are only called when the class is first loaded, so you have a chicken-and-egg problem: The generator doesn’t have the class in its list, so it can never create an object of that class, so the class won’t get loaded and placed in the list. Basically, you’re forced to create the list yourself, by hand (unless you want to write a tool that searches through and analyzes your source code, then creates and compiles the list). So the best you can probably do is to put the list in one central, obvious place. The base class for the hierarchy of interest is probably the best place. The other change we’ll make here is to defer the creation of the object to the class itself, using the Factory Method design pattern. A factory method can be called polymorphically, and creates an object of the appropriate type for you. In this very simple version, the factory method is the create( ) method in the Factory interface: Type Information 413 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com //: typeinfo/factory/Factory.java package typeinfo.factory; public interface Factory<T> { T create(); } ///:~ The generic parameter T allows create( ) to return a different type for each implementation of Factory. This also makes use of covariant return types. In this example, the base class Part contains a List of factory objects. Factories for types that should be produced by the createRandom( ) method are "registered" with the base class by adding them to the partFactories List: //: typeinfo/RegisteredFactories.java // Registering Class Factories in the base class. import typeinfo.factory.*; import java.util.*; class Part { public String toString() { return getClass().getSimpleName(); } static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>(); static { // Collections.addAll() gives an "unchecked generic // array creation for varargs parameter" warning. 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 rand = new Random(47); public static Part createRandom() { int n = rand.nextInt(partFactories.size()); return partFactories.get(n).create(); } } class Filter extends Part {} class FuelFilter extends Filter { // Create a Class Factory for each specific type: public static class Factory implements typeinfo.factory.Factory<FuelFilter> { public FuelFilter create() { return new FuelFilter(); } } } class AirFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<AirFilter> { public AirFilter create() { return new AirFilter(); } } } class CabinAirFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<CabinAirFilter> { public CabinAirFilter create() { return new CabinAirFilter(); } 414 Thinking in Java Bruce Eckel Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com } } class OilFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<OilFilter> { public OilFilter create() { return new OilFilter(); } } } class Belt extends Part {} class FanBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<FanBelt> { public FanBelt create() { return new FanBelt(); } } } class GeneratorBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<GeneratorBelt> { public GeneratorBelt create() { return new GeneratorBelt(); } } } class PowerSteeringBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<PowerSteeringBelt> { public PowerSteeringBelt create() { return new PowerSteeringBelt(); } } } public class RegisteredFactories { public static void main(String[] args) { for(int i = 0; i < 10; i++) System.out.println(Part.createRandom()); } } /* Output: GeneratorBelt CabinAirFilter GeneratorBelt AirFilter PowerSteeringBelt CabinAirFilter FuelFilter PowerSteeringBelt PowerSteeringBelt FuelFilter *///:~ Not all classes in the hierarchy should be instantiated; in this case Filter and Belt are just classifiers so you do not create an instance of either one, but only of their subclasses. If a class should be created by createRandom( ), it contains an inner Factory class. The only way to reuse the name Factory as seen above is by qualifying typeinfo.factory.Factory. Although you can use Collections.addAll( ) to add the factories to the list, the compiler expresses its unhappiness with a warning about a "generic array creation" (which is supposed Type Information 415 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com to be impossible, as you’ll see in the Generics chapter), so I reverted to calling add( ). The createRandom( ) method randomly selects a factory object from partFactories and calls its create( ) to produce a new Part. Exercise 14: (4) A constructor is a kind of factory method. Modify RegisteredFactories.java so that instead of using an explicit factory, the class object is stored in the List, and newlnstance( ) is used to create each object. Exercise 15: (4) Implement a new PetCreator using Registered Factories, and modify the Pets Facade so that it uses this one instead of the other two. Ensure that the rest of the examples that use Pets .Java still work correctly. Exercise 16: (4) Modify the Coffee hierarchy in the Generics chapter to use Registered Factories. instanceof vs. Class equivalence When you are querying for type information, there’s an important difference between either form of instanceof (that is, instanceof or islnstance( ), which produce equivalent results) and the direct comparison of the Class objects. Here’s an example that demonstrates the difference: //: typeinfo/FamilyVsExactType.java // The difference between instanceof and class package typeinfo; import static net.mindview.util.Print.*; class Base {} class Derived extends Base {} public class FamilyVsExactType { static void test(Object x) { print("Testing x of type " + x.getClass()); print("x instanceof Base " + (x instanceof Base)); print("x instanceof Derived "+ (x instanceof Derived)); print("Base.isInstance(x) "+ Base.class.isInstance(x)); print("Derived.isInstance(x) " + Derived.class.isInstance(x)); print("x.getClass() == Base.class " + (x.getClass() == Base.class)); print("x.getClass() == Derived.class " + (x.getClass() == Derived.class)); print("x.getClass().equals(Base.class)) "+ (x.getClass().equals(Base.class))); print("x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class))); } public static void main(String[] args) { test(new Base()); test(new Derived()); } } /* Output: Testing x of type class typeinfo.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 416 Thinking in Java Bruce Eckel Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com x.getClass().equals(Base.class)) true x.getClass().equals(Derived.class)) false Testing x of type class typeinfo.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 *///:~ The test( ) method performs type checking with its argument using both forms of instanceof. It then gets the Class reference and uses == and equals( ) to test for equality of the Class objects. Reassuringly, instanceof and islnstance( ) produce exactly the same results, as do equals( ) and ==. But the tests themselves draw different conclusions. In keeping with the concept of type, instanceof says, "Are you this class, or a class derived from this class?" On the other hand, if you compare the actual Class objects using ==, there is no concern with inheritance—it’s either the exact type or it isn’t. Reflection: runtime class information If you don’t know the precise type of an object, RTTI will tell you. However, there’s a limitation: The type must be known at compile time in order for you to detect it using RTTI and to do something useful with the information. Put another way, the compiler must know about all the classes you’re working with. This doesn’t seem like that much of a limitation at first, but suppose you’re given a reference to an object that’s not in your program space. In fact, the class of the object isn’t even available to your program at compile time. For example, suppose you get a bunch of bytes from a disk file or from a network connection, and you’re told that those bytes represent a class. Since this class shows up long after the compiler generates the code for your program, how can you possibly use such a class? In a traditional programming environment, this seems like a far-fetched scenario. But as we move into a larger programming world, there are important cases in which this happens. The first is component-based programming, in which you build projects using Rapid Application Development (RAD) in an Application Builder Integrated Development Environment, which I shall refer to simply as an IDE. This is a visual approach to creating a program by moving icons that represent components onto a form. These components are then configured by setting some of their values at program time. This design-time configuration requires that any component be instantiable, that it exposes parts of itself, and that it allows its properties to be read and modified. In addition, components that handle Graphical User Interface (GUI) events must expose information about appropriate methods so that the IDE can assist the programmer in overriding these event-handling methods. Reflection provides the mechanism to detect the available methods and produce the method names. Java provides a structure for component-based programming through JavaBeans (described in the Graphical User Interfaces chapter). Another compelling motivation for discovering class information at run time is to provide the ability to create and execute objects on remote platforms, across a network. This is called Remote Method Invocation (RMI), and it allows a Java program to have objects distributed across many machines. This distribution can happen for a number of reasons. For example, perhaps you’re doing a computation-intensive task, and in order to speed things up, you want to break it up and put pieces on machines that are idle. In other situations you might want to Type Information 417 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 418 Thinking in Java Bruce Eckel place code that handles particular types of tasks (e.g., "Business Rules" in a multitier client/server architecture) on a particular machine, so the machine becomes a common repository describing those actions, and it can be easily changed to affect everyone in the system. (This is an interesting development, since the machine exists solely to make software changes easy!) Along these lines, distributed computing also supports specialized hardware that might be good at a particular task—matrix inversions, for example—but inappropriate or too expensive for generalpurpose programming. The class Class supports the concept of reflection, along with the java.lang.reflect library which contains the classes Field, Method, and Constructor (each of which implements the Member interface). Objects of these types are created by the JVM at run time to represent the corresponding member in the unknown class. You can then use the Constructors to create new objects, the get( ) and set( ) methods to read and modify the fields associated with Field objects, and the invoke( ) method to call a method associated with a Method object. In addition, you can call the convenience methods getFields( ), getMethods( ), getConstructors( ), etc., to return arrays of the objects representing the fields, methods, and constructors. (You can find out more by looking up the class Class in the JDK documentation.) Thus, the class information for anonymous objects can be completely determined at run time, and nothing need be known at compile time. It’s important to realize that there’s nothing magic about reflection. When you’re using reflection to interact with an object of an unknown type, the JVM will simply look at the object and see that it belongs to a particular class (just like ordinary RTTI). Before anything can be done with it, the Class object must be loaded. Thus, the .class file for that particular type must still be available to the JVM, either on the local machine or across the network. So the true difference between RTTI and reflection is that with RTTI, the compiler opens and examines the .class file at compile time. Put another way, you can call all the methods of an object in the "normal" way. With reflection, the .class file is unavailable at compile time; it is opened and examined by the runtime environment. A class method extractor Normally you won’t need to use the reflection tools directly, but they can be helpful when you need to create more dynamic code. Reflection is in the language to support other Java features, such as object serialization and JavaBeans (both covered later in the book). However, there are times when it’s quite useful to dynamically extract information about a class. Consider a class method extractor. Looking at a class definition source code or JDK documentation shows only the methods that are defined or overridden within that class definition. But there might be dozens more available to you that have come from base classes. To locate these is both tedious and time consuming. 1 Fortunately, reflection provides a way to write a simple tool that will automatically show you the entire interface. Here’s the way it works: //: typeinfo/ShowMethods.java // Using reflection to show all the methods of a class, // even if the methods are defined in the base class. // {Args: ShowMethods} import java.lang.reflect.*; import java.util.regex.*; import static net.mindview.util.Print.*; public class ShowMethods { private static String usage = 1 Especially in the past. However, Sun has greatly improved its HTML Java documentation so that it’s easier to see base- class methods. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com "usage:\n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving ‘word’"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { print(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors(); if(args.length == 1) { for(Method method : methods) print( p.matcher(method.toString()).replaceAll("")); for(Constructor ctor : ctors) print(p.matcher(ctor.toString()).replaceAll("")); lines = methods.length + ctors.length; } else { for(Method method : methods) if(method.toString().indexOf(args[1]) != -1) { print( p.matcher(method.toString()).replaceAll("")); lines++; } for(Constructor ctor : ctors) if(ctor.toString().indexOf(args[1]) != -1) { print(p.matcher( ctor.toString()).replaceAll("")); lines++; } } } catch(ClassNotFoundException e) { print("No such class: " + e); } } } /* Output: public static void main(String[]) public native int hashCode() public final native Class getClass() public final void wait(long,int) throws InterruptedException public final void wait() throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toString() public final native void notify() public final native void notifyAll() public ShowMethods() *///:~ The Class methods getMethods( ) and getConstructors( ) return an array of Method and array of Constructor, respectively. Each of these classes has further methods to dissect the names, arguments, and return values of the methods they represent. But you can also just use toString( ), as is done here, to produce a String with the entire method signature. The rest of the code extracts the command-line information, determines if a particular signature matches your target string (using indexOf( )), and strips off the name qualifiers using regular expressions (introduced in the Strings chapter). Type Information 419 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com The result produced by Class.forName( ) cannot be known at compile time, and therefore all the method signature information is being extracted at run time. If you investigate the JDK reflection documentation, you’ll see that there is enough support to actually set up and make a method call on an object that’s totally unknown at compile time (there will be examples of this later in this book). Although initially this is something you may not think you’ll ever need, the value of full reflection can be quite surprising. The output above is produced from the command line: java ShowMethods ShowMethods You can see that the output includes a public default constructor, even though no constructor was defined. The constructor you see is the one that’s automatically synthesized by the compiler. If you then make ShowMethods a non-public class (that is, package access), the synthesized default constructor no longer shows up in the output. The synthesized default constructor is automatically given the same access as the class. Another interesting experiment is to invoke Java ShowMethods java.lang.String with an extra argument of char, int, String, etc. This tool can be a real time-saver while you’re programming, when you can’t remember if a class has a particular method and you don’t want to go hunting through the index or class hierarchy in the JDK documentation, or if you don’t know whether that class can do anything with, for example, Color objects. The Graphical User Interfaces chapter contains a GUI version of this program (customized to extract information for Swing components) so you can leave it running while you’re writing code, to allow quick lookups. Exercise 17: (2) Modify the regular expression in ShowMethods.java to additionally strip off the keywords native and final (hint: use the OR operator’|’)- Exercise 18: (1) Make ShowMethods a non-public class and verify that the synthesized default constructor no longer shows up in the output. Exercise 19: (4) In ToyTest.java, use reflection to create a Toy object using the non- default constructor. Exercise 20: (5) Look up the interface for java.lang.Class in the JDK documentation from http://java.sun.com. Write a program that takes the name of a class as a command-line argument, then uses the Class methods to dump all the information available for that class. Test your program with a standard library class and a class you create. Dynamic proxies Proxy is one of the basic design patterns. It is an object that you insert in place of the "real" object in order to provide additional or different operations—these usually involve communication with a "real" object, so a proxy typically acts as a go-between. Here’s a trivial example to show the structure of a proxy: //: typeinfo/SimpleProxyDemo.java import static net.mindview.util.Print.*; interface Interface { void doSomething(); void somethingElse(String arg); 420 Thinking in Java Bruce Eckel Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... if(method.getName().equals("interesting")) print("Proxy detected the interesting method"); return method.invoke(proxied, args); } } interface SomeMethods { void boring1(); void boring2(); void interesting(String arg); void boring3(); } class Implementation implements SomeMethods public void boring1() { print("boring1"); public void boring2() { print("boring2"); public void interesting(String arg) { print("interesting "... implements Interface { public void doSomething() { print("doSomething"); } public void somethingElse(String arg) { print("somethingElse " + arg); } } class SimpleProxy implements Interface { private Interface proxied; public SimpleProxy(Interface proxied) { this.proxied = proxied; } public void doSomething() { print("SimpleProxy doSomething"); proxied.doSomething(); } public void somethingElse(String arg)... of performing RTTI—why not use the built -in facility instead?) //: typeinfo/Person .java // A class with a Null Object import net.mindview.util.*; class Person { public final String first; public final String last; public final String address; // etc public Person(String first, String last, String address){ this.first = first; this.last = last; this.address = address; } public String toString() { return... public void boring3() { print("boring3"); } { } } } 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.boring1(); proxy.boring2(); proxy.interesting("bonobo"); proxy.boring3(); } } /* Output: boring1 boring2 Proxy detected... implement the interface as a private inner class? Here’s what it looks like: //: typeinfo/InnerImplementation .java // Private inner classes can’t hide from reflection import typeinfo.interfacea.*; import static net.mindview.util.Print.*; class InnerA { private static class C implements A { public void f() { print("public C.f()"); } public void g() { print("public C.g()"); } void u() { print("package... anything but a final class 2 can be extended, so this flexibility is automatic much of the time Sometimes, being constrained to a single hierarchy is too limiting If a method argument is an interface instead of a class, the limitations are loosened to include anything that implements the interface—including classes that haven’t been created yet This gives the client programmer the option of implementing... import net.mindview.util.*; class Amphibian {} class Vehicle {} public class TupleTest { static TwoTuple f() { // Autoboxing converts the int to Integer: return new TwoTuple("hi", 47); } static ThreeTuple g() { return new ThreeTuple( new Amphibian(), "hi", 47); } static FourTuple h()... components, and thus reduce coupling If you write to interfaces, you accomplish this, but with type information it’s possible to get around that— interfaces are not airtight guarantees of decoupling Here’s an example, starting with an interface: //: typeinfo/interfacea/A .java package typeinfo.interfacea; public interface A { void f(); } ///:~ This interface is then implemented, and you can see how to... //: typeinfo/InterfaceViolation .java // Sneaking around an interface import typeinfo.interfacea.*; class B implements A { public void f() {} public void g() {} } public class InterfaceViolation { public static void main(String[] args) { A a = new B(); a.f(); // a.g(); // Compile error System.out.println(a.getClass().getName()); if(a instanceof B) { B b = (B)a; b.g(); } } } /* Output: B *///:~ Using RTTI,... new Class[]{ Interface.class }, new DynamicProxyHandler(real)); consumer(proxy); } } /* Output: ( 95% match) doSomething somethingElse bonobo **** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null doSomething **** proxy: class $Proxy0, method: public abstract void Interface.somethingElse (java. lang.String), args: [Ljava.lang.Object;@42e816 bonobo somethingElse bonobo . proxy: //: typeinfo/SimpleProxyDemo .java import static net.mindview.util.Print.*; interface Interface { void doSomething(); void somethingElse(String arg); 420 Thinking in Java Bruce Eckel. proxy.boring1(); proxy.boring2(); proxy.interesting("bonobo"); proxy.boring3(); } } /* Output: boring1 boring2 Proxy detected the interesting method interesting bonobo boring3. print("boring2"); } public void interesting(String arg) { print("interesting " + arg); } public void boring3() { print("boring3"); } } class SelectingMethods