`
jimode2013
  • 浏览: 37678 次
社区版块
存档分类
最新评论

thinking in java Type Information

 
阅读更多

具体到反射,着墨不多

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. 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 = "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);
		}
	}
}

 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). 

The result produced by Class.forNameC ) 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. 

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: 

 

// : typeinf0/SimpleProxyDemo.j ava 
import static net.mindview.util.Print.*;

interface Interface {
	void doSomething();

	void somethingElse(String arg);
}

class RealObject 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) {
		print("SimpleProxy somethingElse " + arg);
		proxied.somethingElse(arg);
	}
}

public class SimpleProxyDemo {
	public static void consumer(Interface iface) {
		iface.doSomething();
		iface.somethingElse("bonobo");
	}

	public static void main(String[] args) {
		consumer(new RealObject());
		consumer(new SimpleProxy(new RealObject()));
	}
}

 Because consumer( ) accepts an Interface, it can't know if it's getting a RealObject or a SimpleProxy, because both implement Interface. But the SimpleProxy inserted between the client and the RealObject performs operations and then calls the identical method on a RealObject. 

A proxy can be helpful anytime you'd like to separate extra operations into a different place than the "real object," and especially when you want to easily change from not using the extra operations to using them, and vice versa (the point of design patterns is to encapsulate change—so you need to be changing

things in order to justify the pattern). For example, what if you wanted to track calls to the methods in the RealObject, or to measure the overhead of such calls? This is not code you want to have incorporated in your application, so a proxy allows you to add and remove it easily. 

Java's dynamic proxy takes the idea of a proxy one step further, by both creating the proxy object  dynamically and handling calls to the proxied methods dynamically. All calls made on a dynamic proxy are redirected to a single invocation handler, which has the job of discovering what the call is and deciding what to do about it. Here's SimpleProxyDemo.java rewritten to use a dynamic proxy: 

// : typeinfo/SimpleDynamicProxy.java 
import java.lang.reflect.*;

class DynamicProxyHandler implements InvocationHandler {
	private Object proxied;

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

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

public class SimpleDynamicProxy {
	public static void consumer(Interface iface) {
		iface.doSomething();
		iface.somethingElse("bonobo");
	}

	public static void main(String[] args) {
		RealObject real = new RealObject();
		consumer(real);
		// Insert a proxy and call again:
		Interface proxy = (Interface) Proxy.newProxyInstance(
				Interface.class.getClassLoader(),
				new Class[] { Interface.class }, new DynamicProxyHandler(real));
		consumer(proxy);
	}
}

 You create a dynamic proxy by calling the static method Proxy.newProxyInstance( ), which requires a class loader (you can generally just hand it a class loader from an object that has already been 

loaded), a list of interfaces (not classes or abstract classes) that you wish the proxy to implement, and an implementation of the interface InvocationHandler. The dynamic proxy will redirect all calls to the 

invocation handler, so the constructor for the invocation handler is usually given the reference to the "real" object so that it can forward requests once it performs its intermediary task.

The invoke( ) method is handed the proxy object, in case you need to distinguish where the request came from—but in many cases you won't care.However, be careful when calling methods on the proxy inside invoke( ),because calls through the interface are redirected through the proxy. 

In general you will perform the proxied operation and then use Method.invoke( ) to forward the request to the proxied object, passing the necessary arguments. This may initially seem limiting, as if you can only 

perform generic operations. However, you can filter for certain method calls,while passing others through: 

//: typeinfo/SelectingMethods.java 
// Looking for particular methods in a dynamic proxy. 
import java.lang.reflect.*;
import static net.mindview.util.Print.*;

class MethodSelector implements InvocationHandler {
	private Object proxied;

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

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

interface SomeMethods {
	void boringl();

	void boring2();

	void interesting(String arg);

	void boring3();
}

class Implementation implements SomeMethods {
	public void boringl() {
		print("boringl");
	}

	public void boring2() {
		print("boring2");
	}

	public void interesting(String arg) {
		print("interesting " + arg);
	}

	public void boring3() {
		print("boring3");
	}
}

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.boringl();
		proxy.boring2();
		proxy.interesting("bonobo");
		proxy.boring3();
	}
}

Here, we are just looking for method names, but you could also be looking for other aspects of the method signature, and you could even search for particular argument values. 

The dynamic proxy is not a tool that you'll use every day, but it can solve certain types of problems very nicely. You can learn more about Proxy and other design patterns in Thinking in Patterns (see www.MindView.net) and Design Patterns, by Erich Gamma et al. (Addison-Wesley, 1995). 

Null Objects 

When you use the built-in null to indicate the absence of an object, you must test a reference for null-ness every time you use it. This can get very tedious and produce ponderous code. The problem is that null has no behavior of its own except for producing a NullPointerException if you try to do anything with it. Sometimes it is useful to introduce the idea of a Null Objects(Discovered by Bobby Woolf and Bruce Anderson. This can be seen as a special case of the Strategy pattern. A variant of Null Object is the Null Iterator pattern, which makes iteration over the nodes in a composite hierarchy transparent to the client (the client can then use the same logic for iterating over the composite and leaf nodes). ) that will accept messages for the object that it's "standing in" for, but will return values indicating that no "real" object is actually there. This way, you can assume that all objects are valid and you don't have to waste programming time checking for null (and reading the resulting code). 

Although it's fun to imagine a programming language that would automatically create Null Objects for you(Object C就是这样的), in practice it doesn't make sense to use them everywhere—sometimes checking for null is fine, and sometimes you can reasonably assume that you won't encounter null, and sometimes even detecting aberrations via NullPointerException is acceptable. The place where Null Objects seem to be most useful is "closer to the data," with objects that represent entities in the problem space. As a simple example,many systems will have a Person class, and there are situations in the code 

where you don't have an actual person (or you do, but you don't have all the information about that person yet), so traditionally you'd use a null reference and test for it. Instead, we can make a Null Object. But even though the Null Object will respond to all messages that the "real" object will respond to, you 

still need a way to test for nullness. The simplest way to do this is to create a tagging interface: 

// : net/mindview/uti1/Null.java 
package net.mindview.util ; 
public interface Null { }

 This allows instanceof to detect the Null Object, and more importantly,does not require you to add an isNull( ) method to all your classes (which would be, after all, just a different way 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 "Person: " + first + " " + last + " " + address;
	}

	public static class NullPerson extends Person implements Null {
		private NullPerson() {
			super("None", "None", "None");
		}

		public String toString() {
			return "NullPerson";
		}
	}

	public static final Person NULL = new NullPerson();
} // /:-

 In general, the Null Object will be a Singleton, so here it is created as a static final instance. This works because Person is immutable—you can only set the values in the constructor, and then read those values, but you can't modify them (because Strings themselves are inherently immutable). If you 

want to change a NullPerson, you can only replace it with a new Person object. Notice that you have the option of detecting the generic Null or the more specific NullPerson using instanceof, but with the Singleton approach you can also just use equals( ) or even == to compare to Person.NULL. 

Now suppose you're back in the high-flying days of Internet startups and you've been given a big pile of venture funding for your Amazing Idea. You'r ready to staff up, but while you're waiting for positions to be filled, you can use Person Null Objects as placeholders for each Position: 

//: typeinfo/Position.java 
class Position {
	private String title;
	private Person person;

	public Position(String jobTitle, Person employee) {
		title = jobTitle;
		person = employee;
		if (person == null)
			person = Person.NULL;
	}

	public Position(String jobTitle) {
		title = jobTitle;
		person = Person.NULL;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String newTitle) {
		title = newTitle;
	}

	public Person getPerson() {
		return person;
	}

	public void setPerson(Person newPerson) {
		person = newPerson;
		if (person == null)
			person = Person.NULL;
	}

	public String toString() {
		return "Position: " + title + " " + person;
	}
} // /:-

 With Position, we don't need to make a Null Object because the existence of Person.NULL implies a null Position (it's possible that, later, you'll discover the need to add an explicit Null Object for Position, but YAGNI(You Aren't Going to Need It) says to try "the simplest thing that could possibly work" for your first draft, and to wait until some aspect of the program requires you to add in the extra feature, rather than assuming it's necessary). 

The Staff class can now look for Null Objects when you are filling positions:

// : typeinfo/Staf f . Java 
import java.util.*;

public class Staff extends ArrayList<Position> {
	public void add(String title, Person person) {
		add(new Position(title, person));
	}

	public void add(String... titles) {
		for (String title : titles)
			add(new Position(title));
	}

	public Staff(String... titles) {
		add(titles);
	}

	public boolean positionAvailable(String title) {
		for (Position position : this)
			if (position.getTitle().equals(title)
					&& position.getPerson() == Person.NULL)
				return true;
		return false;
	}

	public void fillPosition(String title, Person hire) {
		for (Position position : this)
			if (position.getTitle().equals(title)
					&& position.getPerson() == Person.NULL) {
				position.setPerson(hire);
				return;
			}
		throw new RuntimeException("Position " + title + " not available");
	}

	public static void main(String[] args) {
		Staff staff = new Staff("President", "CTO", "Marketing Manager",
				"Product Manager", "Project Lead", "Software Engineer",
				"Software Engineer", "Software Engineer", "Software Engineer",
				"Test Engineer", "Technical Writer");
		staff.fillPosition("President", new Person("Me", "Last",
				"The Top, Lonely At"));
		staff.fillPosition("Project Lead", new Person("Janet", "Planner",
				"The Burbs"));
		if (staff.positionAvailable("Software Engineer"))
			staff.fillPosition("Software Engineer", new Person("Bob", "Coder",
					"Bright Light City"));
		System.out.println(staff);
	}
}

 Notice that you must still test for Null Objects in some places, which is not that different from checking for null, but in other places (such as toString( ) conversions, in this case), you don't have to perform extra tests;you can just assume that all object references are valid. 

If you are working with interfaces instead of concrete classes, it's possible to use a DynamicProxy to automatically create the Null Objects. Suppose we have a Robot interface that defines a name, model, and a List < Operation > that describes what the Robot is capable of doing. Operation contains a description and a command (it's a type of Command pattern): 

 

//: typeinfo/Operation.Java 
public interface Operation {
	String description();

	void command();
} // /:-
 You can access a Robot's services by calling operations( ): 

 

 

// : typeinfo/Robot.java 
import java.util.*;
import net.mindview.util.*;

public interface Robot {
	String name();

	String model();

	List<Operation> operations();

	class Test {
		public static void test(Robot r) {
			if (r instanceof Null)
				System.out.println(" [Null Robot] ");
			System.out.println("Robot name: " + r.name());
			System.out.println("Robot model: " + r.model());
			for (Operation operation : r.operations()) {
				System.out.println(operation.description());
				operation.command();
			}
		}
	}
} // /:-
This also incorporates a nested class to perform tests. 

 

We can now create a Robot that removes snow: 

 

// : typeinfo/SnowRemovalRobot.java 
import java.util.*;

public class SnowRemovalRobot implements Robot {
	private String name;

	public SnowRemovalRobot(String name) {
		this.name = name;
	}

	public String name() {
		return name;
	}

	public String model() {
		return "SnowBot Series 11";
	}

	public List<Operation> operations() {
		return Arrays.asList(new Operation() {
			public String description() {
				return name + " can shovel snow";
			}

			public void command() {
				System.out.println(name + " shoveling snow");
			}
		}, new Operation() {
			public String description() {
				return name + " can chip ice";
			}

			public void command() {
				System.out.println(name + " chipping ice");
			}
		}, new Operation() {
			public String description() {
				return name + " can clear the roof";
			}

			public void command() {
				System.out.println(name + " clearing roof");
			}
		});
	}

	public static void main(String[] args) {
		Robot.Test.test(new SnowRemovalRobot("Slusher"));
	}
}
There will presumably be many different types of Robot, and we'd like to have each Null Object do something special for each Robot type—in this case, incorporate information about the exact type of Robot the Null Object is standing for. This information will be captured by the dynamic proxy: 

 

 

//: typeinfo/NullRobot.java 
// Using a dynamic proxy to create a Null Object. 
import java.lang.reflect.*;
import java.util.*;
import net.mindview.util.*;

class NullRobotProxyHandler implements InvocationHandler {
	private String nullName;
	private Robot proxied = new NRobot();

	NullRobotProxyHandler(Class<? extends Robot> type) {
		nullName = type.getSimpleName() + " NullRobot";
	}

	private class NRobot implements Null, Robot {
		public String name() {
			return nullName;
		}

		public String model() {
			return nullName;
		}

		public List<Operation> operations() {
			return Collections.emptyList();
		}
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		return method.invoke(proxied, args);
	}
}

public class NullRobot {
	public static Robot newNullRobot(Class<? extends Robot> type) {
		return (Robot) Proxy.newProxyInstance(NullRobot.class.getClassLoader(),
				new Class[] { Null.class, Robot.class },
				new NullRobotProxyHandler(type));
	}

	public static void main(String[] args) {
		Robot[] bots = { new SnowRemovalRobot("SnowBee"),
				newNullRobot(SnowRemovalRobot.class) };
		for (Robot bot : bots)
			Robot.Test.test(bot);
	}
}
Whenever you need a null Robot object, you just call newNullRobot( ),passing the type of Robot you want a proxy for. The proxy fulfills the requirements of the Robot and Null interfaces, and provides the specific 

 

name of the type that it proxies.

Mock Objects & Stubs 

Logical variations of the Null Object are the Mock Object and the Stub. Like Null Object, both of these are stand-ins for the "real" object that will be used in the finished program. However, both Mock Object and Stub pretend to be live objects that deliver real information, rather than being a more   intelligent placeholder for null, as Null Object is. 

The distinction between Mock Object and Stub is one of degree. Mock Objects tend to be lightweight and self-testing, and usually many of them are created to handle various testing situations. Stubs just return stubbed data, are typically heavyweight and are often reused between tests. Stubs can be configured to change depending on how they are called. So a Stub is a sophisticated object that does lots of things, whereas you usually create lots of small, simple Mock Objects if you need to do many things. 

 

Interfaces and type information 

An important goal of the interface keyword is to allow the programmer to isolate 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 sneak around to the actual implementation type: 

import typeinfo.interfacea.A;

// : typeinfo/InterfaceViolation.java 
// Sneaking around an interface。
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();
		}
	}
}

Using RTTI, we discover that a has been implemented as a B. By casting to B,we can call a method that's not in A. 

This is perfectly legal and acceptable, but you may not want client programmers to do this, because it gives them an opportunity to couple more closely to your code than you'd like. That is, you may think that the interface keyword is protecting you, but it isn't, and the fact that you're using B to implement A in this case is effectively a matter of public records(The most famous case of this is the Windows operating system, which had a published API that you were supposed to write to, and an unpublished but visible set of functions that you could discover and call. To solve problems, programmers used the hidden API functions, which forced Microsoft to maintain them as if they were part of the public API. This became a source of great cost and effort for the company.) 

One solution is to simply say that programmers are on their own if they decide to use the actual class rather than the interface. This is probably reasonable in many cases, but if "probably" isn't enough, you might want to apply more stringent controls. 

The easiest approach is to use package access for the implementation, so that clients outside the package may not see it: 

//: typeinfo/packageaccess/HiddenC.java 

import typeinfo.interfacea.*;
import static net.mindview.util.Print.*;

class C implements A {
	public void f() {
		print("public C.f()");
	}

	public void g() {
		print("public C.g()");
	}

	void u() {
		print("package C.u()");
	}

	protected void v() {
		print("protected C.v()");
	}

	private void w() {
		print("private C.w()");
	}
}

public class HiddenC {
	public static A makeA() {
		return new C();
	}
} // /:-

The only public part of this package, HiddenC, produces an A interface when you call it. What's interesting about this is that even if you were to return a C from makeA( ), you still couldn't use anything but an A from outside the package, since you cannot name C outside the package. 

Now if you try to downcast to C, you can't do it because there is no 'C type available outside the package: 

//: typeinfo/Hiddenlmplementation.java 
// Sneaking around package access, 
import java.lang.reflect.Method;

import typeinfo.interfacea.A;
import typeinfo.packageaccess.HiddenC;

public class Hiddenlmplementation {
	public static void main(String[] args) throws Exception {
		A a = HiddenC.makeA();
		a.f();
		System.out.println(a.getClass().getName());
		// Compile error: cannot find symbol 'C :
		
		// if(a instanceof C) { C c = (C)a; c.g() ; }
		
		// Oops! Reflection still allows us to call g():
		callHiddenMethod(a, "g");
		// And even methods that are less accessible!
		callHiddenMethod(a, "u");
		callHiddenMethod(a, "v");
		callHiddenMethod(a, "w");
	}

	static void callHiddenMethod(Object a, String methodName) throws Exception {
		Method g = a.getClass().getDeclaredMethod(methodName);
		g.setAccessible(true);
		g.invoke(a);
	}
}

As you can see, it's still possible to reach in and call all of the methods using reflection, even private methods! If you know the name of the method, you can call setAccessible(true) on the Method object to make it callable, as seen in callHiddenMethod( ). 

You may think that you can prevent this by only distributing compiled code,but that's no solution. All you must do is run javap, which is the decompiler that comes with the JDK. Here's the command line: 

javap -private C

The -private flag indicates that all members should be displayed, even private ones. Here's the output: 



So anyone can get the names and signatures of your most private methods,and call them. 

What if you 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 static net.mindview.util.Print.print;
import typeinfo.interfacea.A;

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 C.u()");
		}

		protected void v() {
			print("protected C.v()");
		}

		private void w() {
			print("private C.w()");
		}
	}

	public static A makeA() {
		return new C();
	}
}

public class InnerImplementation {

	public static void main(String[] args) throws Exception {
		A a = InnerA.makeA();
		a.f();
		System.out.println(a.getClass().getName());
		// Reflection still gets into the private class:
		HiddenImplementation.callHiddenMethod(a, "g");
		HiddenImplementation.callHiddenMethod(a, "u");
		HiddenImplementation.callHiddenMethod(a, "v");
		HiddenImplementation.callHiddenMethod(a, "w");
	}
}

 That didn't hide anything from reflection . What about an anonymous class?

//: typeinfo/AnonymousImplementation.Java 
// Anonymous inner classes can't hide from reflection. 
import typeinfo.interfacea.*;
import static net.mindview.util.Print.*;

class AnonymousA {
	public static A makeA() {
		return new A() {
			public void f() {
				print("public C.f()");
			}

			public void g() {
				print("public C.g()");
			}

			void u() {
				print("package C.u()");
			}

			protected void v() {
				print("protected C.v()");
			}

			private void w() {
				print("private C.w()");
			}
		};
	}
}

public class Anonymouslmplementation {
	public static void main(String[] args) throws Exception {
		A a = AnonymousA.makeA();
		a.f();
		System.out.println(a.getClass().getName());
		// Reflection still gets into the anonymous class:
		HiddenImplementation.callHiddenMethod(a, "g");
		HiddenImplementation.callHiddenMethod(a, "u");
		HiddenImplementation.callHiddenMethod(a, "v");
		HiddenImplementation.callHiddenMethod(a, "w");
	}
}

There doesn't seem to be any way to prevent reflection from reaching in and calling methods that have non-public access. This is also true for fields, even private fields: 

// : typei nfo/ModifyingPrivateFields.Java 
import java.lang.reflect.Field;

class WithPrivateFinalField {
	private int i = 1;
	private final String s = "I'm totally safe";
	private String s2 = "Am I safe?";

	public String toString() {
		return "i = " + i + ", " + s + ", " + s2;
	}
}

public class ModifyingPrivateFields {
	public static void main(String[] args) throws Exception {
		WithPrivateFinalField pf = new WithPrivateFinalField();
		System.out.println(pf);
		Field f = pf.getClass().getDeclaredField("i");
		f.setAccessible(true);
		System.out.println("f.getInt(pf): " + f.getInt(pf));
		f.setInt(pf, 47);
		System.out.println(pf);
		f = pf.getClass().getDeclaredField("s");
		f.setAccessible(true);
		System.out.println("f.get(pf): " + f.get(pf));
		f.set(pf, "No, you're not!");
		System.out.println(pf);
		f = pf.getClass().getDeclaredField("s2");
		f.setAccessible(true);
		System.out.println("f.get(pf): " + f.get(pf));
		f.set(pf, "No, you're not!");
		System.out.println(pf);
	}
}

However, final fields are actually safe from change. The runtime system accepts any attempts at change without complaint, but nothing actually happens. 

In general, all these access violations are not the worst thing in the world. If someone uses such a technique to call methods that you marked with private or package access (thus clearly indicating they should not call them),then it's difficult for them to complain if you change some aspect of those 

methods. On the other hand, the fact that you always have a back door into a class may allow you to solve certain types of problems that could otherwise be difficult or impossible, and the benefits of reflection in general are undeniable. 

Summary 

RTTI allows you to discover type information from an anonymous base-class reference. Thus, it's ripe for misuse by the novice, since it might make sense before polymorphic method calls do. For people coming from a procedural background, it's difficult not to organize programs into sets of switch statements. You can accomplish this with RTTI and thus lose the important value of polymorphism in code development and maintenance. The intent of OO programming is to use polymorphic method calls everywhere you can, 

and RTTI only when you must

However, using polymorphic method calls as they are intended requires that you have control of the base-class definition, because at some point in the extension of your program you might discover that the base class doesn't include the method you need. If the base class comes from someone else's library, one solution is RTTI: You can inherit a new type and add your extra method. Elsewhere in the code you can detect your particular type and call that special method. This doesn't destroy the polymorphism and extensibility of the program, because adding a new type will not require you to hunt for switch statements in your program. However, when you add code that requires your new feature, you must use RTTI to detect your particular type. 

Putting a feature in a base class might mean that, for the benefit of one particular class, all of the other classes derived from that base require some meaningless stub of a method. This makes the interface less clear and annoys those who must override abstract methods when they derive from that base class. For example, consider a class hierarchy representing musical instruments. Suppose you want to clear the spit valves of all the appropriate instruments in your orchestra. One option is to put a clearSpitValve( ) method in the base class Instrument, but this is confusing because it implies that Percussion, Stringed and Electronic instruments also have spit valves. RTTI provides a much more reasonable solution because you can place the method in the specific class where it's appropriate (Wind, in this case). At the same time, you may discover that there's a more sensible solution—here, a preparelnstrument ( ) method in the base class.However, you might not see such a solution when you're first solving the problem and could mistakenly assume that you must use RTTI. 

Finally, RTTI will sometimes solve efficiency problems. Suppose your code nicely uses polymorphism, but it turns out that one of your objects reacts to this general-purpose code in a horribly inefficient way. You can pick out that type using RTTI and write case-specific code to improve the efficiency. Be wary, however, of programming for efficiency too soon. It's a seductive trap. It's best to get the program working/jrsf, then decide if it's running fast enough, and only then should you attack efficiency issues—with a profiler (see the supplement at http://MindView.net/Books/BetterJava). 

We've also seen that reflection opens up a new world of programming possibilities by allowing a much more dynamic style of programming. There are some for whom the dynamic nature of reflection is disturbing. The fact that you can do things that can only be checked at run time and reported with exceptions seems, to a mind grown comfortable with the security of static type checking, to be the wrong direction. Some people go so far as to say that introducing the possibility of a runtime exception is a clear indicator that such code should be avoided. I find that this sense of security is an illusion-there are always things that can happen at run time and throw exceptions,even in a program that contains no try blocks or exception specifications.Instead, I think that the existence of a consistent error-reporting model 

empowers us to write dynamic code using reflection. Of course it's worth trying to write code that can be statically checked ... when you can. But I believe that dynamic code is one of the important facilities that separate Java from languages like C++. 

  • 大小: 88.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics