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

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
分享到:
评论

相关推荐

    Thinking in java .txt

    《Thinking in Java》第四版由布鲁斯·埃克尔(Bruce Eckel)撰写,他是MindView公司的总裁。这本书被广泛认为是学习Java编程语言的经典教材之一。从读者的反馈来看,《Thinking in Java》不仅覆盖了Java的核心概念...

    Thinking in Java 4th Edition Annotated Solutions Guide

    根据提供的文件信息,以下是对文件《Thinking in Java 4th Edition Annotated Solutions Guide》中所包含知识点的详细解释: 首先,文件标题《Thinking in Java 4th Edition Annotated Solutions Guide》指出了这是...

    Thinking in Java 4 源码 导入IDEA可直接运行

    《Thinking in Java》是Bruce Eckel的经典之作,第四版(TIJ4)更是Java程序员必读的书籍之一。这本书深入浅出地介绍了Java语言的核心概念和技术,包括面向对象编程、集合框架、多线程、网络编程等众多主题。源码是...

    Thinking in java4(中文高清版)-java的'圣经'

    研讨课 Hands-on Java研讨课CD Thinking in Objects研讨课 Thinking in Enterprise Java Thinking in Patterns(with Java) Thinking in Patterns研讨课 设计咨询与复审 附录B 资源 软件 编辑器与IDE 书籍 分析与设计...

    Thinking in Java 练习题答案

    《Thinking in Java》是Bruce Eckel的经典之作,它深入浅出地介绍了Java语言的核心概念和技术。这本书的练习题是学习Java的重要组成部分,因为它们能够帮助读者巩固理论知识并提升实践能力。以下是对"Thinking in ...

    Thinking in Java 4th Edition + Annotated Solution Guide (代码)英文文字版 带书签 有答案

    《Thinking in Java》是Bruce Eckel的经典之作,第四版涵盖了Java编程语言的广泛主题,适合初学者和有经验的程序员。这本书深入浅出地讲解了Java的核心概念和技术,旨在帮助读者建立坚实的编程基础,并理解面向对象...

    《thinking in java》第三版完整PDF书籍+习题答案(中文版)

    《Thinking in Java》是Bruce Eckel的经典之作,被誉为学习Java编程的权威指南。该书以其深入浅出的方式,详尽地介绍了Java语言的核心概念和技术。第三版是此书的一个重要里程碑,它涵盖了Java语言的诸多关键特性,...

    Thinkingin Java电子书

    《Thinking in Java》是一本备受推崇的Java编程教材,由Bruce Eckel撰写,被誉为Java学习者的必读之作。这本书深入浅出地介绍了Java语言的核心概念和技术,覆盖了从基础到高级的主题,对于有一定Java基础的读者来说...

    thinking in java 第四版 源码

    《Thinking in Java》是Bruce Eckel的经典之作,第四版更是被广大Java开发者视为学习和进阶的重要参考书籍。这本书深入浅出地介绍了Java语言的核心概念和技术,包括面向对象编程、集合框架、多线程、网络编程、GUI...

    Thinking in Java Second Edition.doc

    《Thinking in Java》第二版是Bruce Eckel所著的一本权威性的Java编程教程,由MindView, Inc.出版。这本书受到了读者的高度评价,被认为是比其他Java书籍更出色的学习资源,其深度、完整性和精确性都是同类书籍中的...

    Thinking in java 电子书

    《Thinking in Java》是 Bruce Eckel 编著的一本经典的Java编程教材,它深受程序员喜爱,被誉为学习Java的必备参考书。这本书全面深入地探讨了Java语言的核心概念和技术,不仅适合初学者,也对有经验的程序员提供了...

    Thinking in Java 习题答案

    《Thinking in Java》是Bruce Eckel的经典编程教材,它深入浅出地介绍了Java语言的核心概念和技术。这本书以其详尽的解释、丰富的示例和实践性强的习题深受程序员喜爱。"Thinking in Java 习题答案"是配套的解答集,...

    thinking in java annotated solution guide 电子书格式

    《Thinking in Java》是Bruce Eckel的经典Java编程教材,它以其深入浅出的讲解和全面系统的内容深受程序员喜爱。这本书的Annotated Solution Guide是作者为读者提供的配套解答指南,帮助读者理解和解决书中练习题,...

    Thinking In Java 源码

    《Thinking in Java》是Bruce Eckel的经典Java编程书籍,它深入浅出地讲解了Java语言的核心概念和技术。这本书不仅适合初学者,也对有经验的程序员提供了深入理解Java的宝贵资源。现在,我们有机会免费下载这本书的...

    Thinking In Java-Java 编程思想(中英文版 第四版)

    Thinking In Java-Java 编程思想(中英文版 第四版) Thinking In Java-Java 编程思想(中英文版 第四版)

    Thinking in java java核心思想英文原版(带目录)

    Thinking in java java核心思想英文版(带目录),学java必备

    Thinking in Java

    《Thinking in Java》是Bruce Eckel的经典之作,它被誉为学习Java编程的最佳教材之一。这本书以其深入浅出的讲解方式和全面覆盖的Java知识点而受到广大程序员的推崇。本压缩包包含的是《Thinking in Java》的第三版...

    Thinking in Java (中文版)-经典书籍

    他是《Thinking in Java》、《Thinking in C++》、《C++ Inside & Out》《Using C++》和《Thinking in Patterns》的作者,同时还是《Black Belt C++》文集的编辑。他的《Thinking in C++》一本书在1995年被评为...

    thinking in java 源码

    《Thinking in Java》是Bruce Eckel的经典Java编程教材,它以其深入浅出的讲解和丰富的实例赢得了广大程序员的赞誉。这个源码包包含了第4版的开发源码,旨在帮助读者更好地理解书中所阐述的概念和原理。以下是基于...

Global site tag (gtag.js) - Google Analytics