Type Information

来源:互联网 发布:男朋友礼物学生知乎 编辑:程序博客网 时间:2024/05/15 03:11

Runtime type information (RTTI) allows you to discover and use type information while a program is running. 


It frees you from the constraint of doing type-oriented things only at compile time, and can enable some very powerful programs. The need for RTTI uncovers a plethora of interesting (and often perplexing) OO design issues, and raises fundamental questions about how you should structure your programs. 

This chapter looks at the ways that Java allows you to discover information about objects and classes at run time. This takes two forms: "traditional" RTTI, which assumes that you have all the types available at compile time,and the reflection mechanism, which allows you to discover and use class information solely at run time. 

The need for RTTI 

Consider the now-familiar example of a class hierarchy that uses polymorphism. The generic type is the base class Shape, and the specific derived types are Circle, Square, and Triangle:

This is a typical class hierarchy diagram, with the base class at the top and the derived classes growing downward.The normal goal in object-oriented programming is for your code to manipulate references to the base type (Shape, in this case), so if you decide to extend the program by adding a new class (such as Rhomboid, derived from Shape), the bulk of the code is not affected. In this example, the dynamically bound method in the Shape interface is draw( ), so the intent is for the client programmer to call draw( ) through a generic Shape reference. In all of the derived classes, draw( ) is overridden, and because it is a dynamically bound method, the proper behavior will occur even though it is called through a generic Shape reference. That's polymorphism. 

Thus, you generally create a specific object (Circle, Square, or Triangle),upcast it to a Shape (forgetting the specific type of the object), and use that anonymous Shape reference in the rest of the program.
You might code the Shape hierarchy as follows: 
package typeinfo;//: typeinfo/Shapes.java import java.util.Arrays;import java.util.List;abstract class Shape {void draw() {System.out.println(this + ".draw()");}abstract public String toString();}class Circle extends Shape {public String toString() {return "Circle";}}class Square extends Shape {public String toString() {return "Square";}}class Triangle extends Shape {public String toString() {return "Triangle";}}public class Shapes {public static void main(String[] args) {List<Shape> shapeList = Arrays.asList(new Circle(), new Square(),new Triangle());for (Shape shape : shapeList)shape.draw();}}/*Output:Circle.draw()Square.draw()Triangle.draw()*/

The base class contains a draw( ) method that indirectly uses toString( ) to print an identifier for the class by passing this to System.out.println( ) (notice that toString( ) is declared abstract to force inheritors to override it, and to prevent the instantiation of a plain Shape). If an object appears in a string concatenation expression (involving'+' and String objects), the toString( ) method is automatically called to produce a String representation for that object.Each of the derived classes overrides the toString( ) method (from Object) so that draw( ) ends up (polymorphically) printing something different in each case. 
"traditional" RTTI:主要涉及到泛型
In this example, the upcast occurs when the shape is placed into the List<Shape>. During the upcast to Shape, the fact that the objects are specific types of Shape is lost. To the array, they are just Shapes. 
At the point that you fetch an element out of the array, the container—which is actually holding everything as an Object—automatically casts the result back to a Shape. This is the most basic form of RTTI, becauseall casts are checked at run time for correctness. That's what RTTI means: At run time,the type of an object is identified. 
In this case, the RTTI cast is only partial: The Object is cast to a Shape, and not all the way to a Circle, Square, or Triangle. That's because the only thing you know at this point is that the List<Shape> is full of Shapes.At compile time, this is enforced by the container and the Java generic system,but at run time the cast ensures it. 

Now polymorphism takes over and the exact code that's executed for the Shape is determined by whether the reference is for a Circle, Square, or Triangle. And in general, this is how it should be; you want the bulk of your code to know as little as possible about specific types of objects, and to just deal with the general representation of a family of objects (in this case,Shape) . As a result, your code will be easier to write, read, and maintain, and your designs will be easier to implement, understand, and change. So polymorphism is a general goal in object-oriented programming. 
But what if you have a special programming problem that's easiest to solve if you know the exact type of a generic reference? For example, suppose you want to allow your users to highlight all the shapes of any particular type by turning them a special color. This way, they can find all the triangles on the screen by highlighting them. Or perhaps your method needs to "rotate" a list of shapes, but it makes no sense to rotate a circle so you'd like to skip the circles. With RTTI, you can ask a Shape reference the exact type that it's referring to, and thus select and isolate special cases. 

The Class object 

To understand how RTTI works in Java, you must first know how type information is represented at run time. This is accomplished through a special kind of object called theClass object,which contains information about the class. In fact,the Class object is used to create all of the "regular" objects of your class. Java performs its RTTI using the Class object, even if you're doing something like a cast. The class Class also has a number of other ways you can use RTTI.

There's one Class object for each class that is part of your program. That is,each time you write and compile a new class, a single Class object is also created (and stored, appropriately enough, in an identically named .class file). To make an object of that class, the Java Virtual Machine (JVM) that's executing your program uses a subsystem called a class loader. 
The class loader subsystem can actually comprise a chain of class loaders, but there's only one primordial class loader, which is part of the JVM implementation. The primordial class loader loads so-called trusted classes,including Java API classes, typically from the local disk. It's usually not necessary to have additional class loaders in the chain, but if you have special needs (such as loading classes in a special way to support Web server applications, or downloading classes across a network), then you have a way to hook in additional class loaders. 
All classes are loaded into the JVM dynamically, upon the first use of a class.This happens when the program makes the first reference to a static member of that class. It turns out that the constructor is also a static method of a class, even though the static keyword is not used for a constructor.Therefore, creating a new object of that class using the new operator also counts as a reference to a static member of the class.
Thus, a Java program isn't completely loaded before it begins, but instead pieces of it are loaded when necessary. This is different from many traditional languages. Dynamic loading enables behavior that is difficult or impossible to duplicate in a statically loaded language like C++. 
The class loader first checks to see if the Class object for that type is loaded.If not, the default class loader finds the .class file with that name (an add-on class loader might, for example, look for the bytecodes in a database instead).As the bytes for the class are loaded, they are verified to ensure that they have not been corrupted and that they do not comprise bad Java code (this is one of the lines of defense for security in Java).

Once the Class object for that type is in memory, it is used to create all objects of that type. Here's a program to prove it: 
// : typeinfo/SweetShop.java // Examination of the way the class loader works. import static net.mindview.util.Print.print;class Candy {static {print("Loading Candy");}}class Gum {static {print("Loading Gum");}}class Cookie {static {print("Loading Cookie");}}public class Sweetshop {public static void main(String[] args) {print("inside main");new Candy();print("After creating Candy");try {Class.forName("Gum");} catch (ClassNotFoundException e) {print("Couldn't find Gum");}print("After Class.forName(\"Gum\")");new Cookie();print("After creating Cookie");}}

Each of the classes Candy, Gum, and Cookie has a static clause that is executed as the class is loaded for the first time. Information will be printed to tell you when loading occurs for that class. In main( ), the object creations are spread out between print statements to help detect the time of loading.
You can see from the output that each Class object is loaded only when it's needed, and the static initialization is performed upon class loading. 
A particularly interesting line is: 
Class.forName("Gum"); 

All Class objects belong to the class Class. A Class object is like any other object, so you can get and manipulate a reference to it (that's what the loader  does). One of the ways to get a reference to the Class object is the static forName( ) method, which takes a String containing the textual name (watch the spelling and capitalization!) of the particular class you want a reference for. It returns a Class reference, which is being ignored here; the call to forName( ) is being made for its side effect, which is to load the class Gum if it isn't already loaded. In the process of loading, Gum's static clause is executed. 
In the preceding example, if Class.forName( ) fails because it can't find the class you're trying to load, it will throw a ClassNotFoundException. Here,we simply report the problem and move on, but in more sophisticated programs, you might try to fix the problem inside the exception handler. 
Anytime you want to use type information at run time, you must first get a reference to the appropriate Class object.Class.forName( ) is one convenient way to do this, because you don't need an object of that type in order to get the Class reference. However, if you already have an object of the type you're interested in, you can fetch the Class reference by calling a method that's part of the Object root class:getClass( ). This returns the Class reference representing the actual type of the object. Class has many interesting methods; here are a few of them: 

My preference is to use the ".class" versions if you can, since they're more consistent with regular classes. 
It's interesting to note that creating a reference to a Class object using ".class" doesn't automatically initialize the Class object. There are actually three steps in preparing a class for use: 
1. Loading, which is performed by the class loader. This finds the bytecodes (usually, but not necessarily, on your disk in your classpath) and creates a Class object from those bytecodes. 
2. Linking. The link phase verifies the bytecodes in the class, allocates storage for static fields, and if necessary, resolves all references to other classes made by this class. 
3. Initialization. If there's a superclass, initialize that. Execute static initializers and static initialization blocks. 
Initialization is delayed until the first reference to a static method (the constructor is implicitly static) or to a non-constant static field
//: typeinfo/ClassInitialization.Java import java.util.*;class Initable {static final int staticFinal = 47;static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);static {System.out.println("Initializing Initable");}}class Initable2 {static int staticNonFinal = 147;static {System.out.println("Initializing Initable2");}}class Initable3 {static int staticNonFinal = 74;static {System.out.println("Initializing Initable3");}}public class ClassInitialization {public static Random rand = new Random(47);public static void main(String[] args) throws Exception {Class initable = Initable.class;System.out.println("After creating Initable ref");// Does not trigger initialization:System.out.println(Initable.staticFinal);// Does trigger initialization:System.out.println(Initable.staticFinal2);// Does trigger initialization:System.out.println(Initable2.staticNonFinal);Class initable3 = Class.forName("Initable3");System.out.println("After creating Initable3 ref");System.out.println(Initable3.staticNonFinal);}}

Effectively, initialization is "as lazy as possible." From the creation of the initable reference, you can see that just using the .class syntax to get a reference to the class doesn't cause initialization. However, Class.forName( ) initializes the class immediately in order to produce the Class reference, as you can see in the creation of initable3. 

If a static final value is a "compile-time constant," such as Initable.staticFinal, that value can be read without causing the Initable class to be initialized. Making a field static and final, however, does not guarantee this behavior: accessing Initable.staticFinal2 forces class initialization because it cannot be a compile-time constant. 
If a static field is not final, accessing it always requires linking (to allocate storage for the field) and initialization (to initialize that storage) before it can be read, as you can see in the access to Initable2.staticNonFinal. 

Generic class references 

A Class reference points to a Class object, which manufactures instances of classes and contains all the method code for those instances. It also contains the statics for that class. So a Class reference really does indicate the exact type of what it's pointing to: an object of the class Class. 
However, the designers of Java SE5 saw an opportunity to make this a bit more specific by allowing you to constrain the type of Class object that the Class reference is pointing to, using the generic syntax. In the following example, both syntaxes are correct: 
//: typeinfo/GenericClassReferences.Java public class GenericClassReferences {public static void main(String[] args) {Class intClass = int.class;Class<Integer> genericIntClass = int.class;genericIntClass = Integer.class; // Same thingintClass = double.class;//generidntClass = double. class ; / / Illegal}} // /: -

The ordinary class reference does not produce a warning. However, you can see that the ordinary class reference can be reassigned to any other Class object, whereas the generic class reference can only be assigned to its declared type. By using the generic syntax, you allow the compiler to enforce extra type checking.
What if you'd like to loosen the constraint a little? Initially, it seems like you ought to be able to do something like: 
Class<Number> genericNumberClass = int.class ; 
This would seem to make sense because Integer is inherited from Number.But this doesn't work, because the Integer Class object is not a subclass of the Number Class object (this may seem like a subtle distinction; we'll look into it more deeply in the Generics chapter). 
To loosen the constraints when using generic Class references, I employ the wildcard, which is part of Java generics. The wildcard symbol is '?', and it indicates "anything." So we can add wildcards to the ordinary Class reference in the above example and produce the same results: 
//: typeinfo/WiIdcardClassReferences.Java public class WiIdcardClassReferences {public static void main(String[] args) {Class<?> intClass = int.class;intClass = double.class;}} // /:-

In Java SE5, Class<?> is preferred over plain Class, even though they are equivalent and the plain Class, as you saw, doesn't produce a compiler warning. The benefit of Class<?> is that it indicates that you aren't just using a non-specific class reference by accident, or out of ignorance. You chose the non-specific version. 
In order to create a Class reference that is constrained to a type or any subtype, you combine the wildcard with the extends keyword to create a bound. So instead of just saying Class<Number>, you say: 
//: typeinfo/BoundedClassReferences.java public class BoundedClassReferences {public static void main(String[] args) {Class<? extends Number> bounded = int.class;bounded = double.class;bounded = Number.class;// Or anything else derived from Number.}} // /:-

The reason for adding the generic syntax to Class references is only to provide compile-time type checking, so that if you do something wrong you find out about it a little sooner. You can't actually go astray with ordinary Class references, but if you make a mistake you won't find out until run time, which can be inconvenient. 
Here's an example that uses the generic class syntax. It stores a class reference, and later produces a List filled with objects that it generates using newlnstance( ): 
//: typeinfo/FilledList.java import java.util.*;class CountedInteger {private static long counter;private final long id = counter++;public String toString() {return Long.toString(id);}}public class FilledList<T> {private Class<T> type;public FilledList(Class<T> type) {this.type = type;}public List<T> create(int nElements) {List<T> result = new ArrayList<T>();try {for (int i = 0; i < nElements; i++)result.add(type.newInstance());} catch (Exception e) {throw new RuntimeException(e);}return result;}public static void main(String[] args) {FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class);System.out.println(fl.create(15));}}/*Output:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]*/


Notice that this class must assume that any type that it works with has a default constructor (one without arguments), and you'll get an exception if that isn't the case. The compiler does not issue any warnings for this program. 

An interesting thing happens when you use the generic syntax for Class objects: newlnstance( ) will return the exact type of the object, rather than just a basic Object as you saw in ToyTest.java. This is somewhat limited: 
//: typeinfo/toys/GenericToyTest.Java // Testing class Class, package typeinfo.toys;public class GenericToyTest {public static void main(String[] args) throws Exception {Class<FancyToy> ftClass = FancyToy.class;// Produces exact type:FancyToy fancyToy = ftClass.newInstance();Class<? super FancyToy> up = ftClass.getSuperclass();// This won't compile:// Class<Toy> up2 = ftClass.getSuperclass();// Only produces Object:Object obj = up.newInstance();}} // /:-

If you get the superclass, the compiler will only allow you to say that the superclass reference is "some class that is a superclass of FancyToy" as seen in the expression Class <? super FancyToy >. It will not accept a declaration of Class<Toy>. This seems a bit strange because getSuperclass( ) returns the base class (not interface) and the compiler knows what that class is at compile time—in this case, Toy.class, not just "some superclass of FancyToy." In any event, because of the vagueness, the 
return value of up.newlnstance( ) is not a precise type, but just an Object. 

New cast syntax 

Java SE5 also adds a casting syntax for use with Class references, which is the cast( ) method: 
package typeinfo.toys;//: typeinfo/ClassCasts.Java class Building {}class House extends Building {}public class ClassCasts {public static void main(String[] args) {Building b = new House();Class<House> houseType = House.class;House h = houseType.cast(b);h = (House) b; // ... or just do this.}} // /:-

The cast( ) method takes the argument object and casts it to the type of the Class reference. Of course, if you look at the above code it seems like a lot of extra work compared to the last line in main( ), which does the same thing. 
The new casting syntax is useful for situations where you can't just use an ordinary cast. This usually happens when you're writing generic code (which you'll learn about in the Generics chapter), and you've stored a Class reference that you want to use to cast with at a later time. It turns out to be a rare thing—I found only one instance where cast( ) was used in the entire Java SE5 library (it was in com.sun.mirror.util.DeclarationFilter). 
Another new feature had no usage in the Java SE5 library: Class.asSubclass( ). This allows you to cast the class object to a more specific type. 

Checking before a cast 

So far, you've seen forms of RTTI, including: 
1. The classic cast; e.g., "(Shape)," which uses RTTI to make sure the cast is correct. This will throw a ClassCastException if you've performed a bad cast. 
2. The Class object representing the type of your object. The Class object can be queried for useful runtime information. 
In C++, the classic cast "(Shape)" does not perform RTTI. It simply tells the compiler to treat the object as the new type. In Java, which does perform the type check, this cast is often called a "type-safe downcast." The reason for the term "downcast" is the historical arrangement of the class hierarchy diagram.If casting a Circle to a Shape is an upcast, then casting a Shape to a Circle is a downcast. However, because it knows that a Circle is also a Shape, the compiler freely allows an upcast assignment, without requiring any explicit cast syntax. The compiler cannot know, given a Shape, what that Shape actually is—it could be exactly a Shape, or it could be a subtype of Shape,such as a Circle, Square, Triangle or some other type. At compile time, the compiler only sees a Shape. Thus, it won't allow you to perform a downcast assignment without using an explicit cast, to tell it that you have extra information that allows you to know that it is a particular type (the compiler will check to see if that downcast is reasonable, so it won't let you downcast to a type that's not actually a subclass). 
There's a third form of RTTI in Java. This is the keyword instanceof, which tells you if an object is an instance of a particular type. It returns a boolean so you use it in the form of a question, like this: 
if (x instanceof Dog)     ((Dog)x).bark(); 
The if statement checks to see if the object x belongs to the class Dog before casting x to a Dog. It's important to use instanceof before a downcast when you don't have other information that tells you the type of the object; otherwise, you'll end up with a ClassCastException. 
Ordinarily, you might be hunting for one type (triangles to turn purple, for example), but you can easily tally all of the objects by using instanceof. For example, suppose you have a family of classes to describe Pets (and their people, a feature which will come in handy in a later example). Each Individual in the hierarchy has an id and an optional name. Although the classes that follow inherit from Individual, there are some complexities in the Individual class, so that code will be shown and explained in the Containers in Depth chapter. As you can see, it's not really necessary to see the code for Individual at this point—you only need to know that you can create it with or without a name, and that each Individual has a method id( ) that returns a unique identifier (created by counting each object).There's also a toString( ) method; if you don't provide a name for an Individual, toString( ) only produces the simple type name. 
Here is the class hierarchy that inherits from Individual: 
//: typeinfo/pets/Person.Java package typeinfo.pets;import typeinfo.pets.Individual;public class Person extends Individual {public Person(String name) {super(name);}} // /:-

//: typeinfo/pets/Pet.Java package typeinfo.pets;public class Pet extends Individual {public Pet(String name) {super(name);}public Pet() {super();}} // /:-

//: typeinfo/pets/Dog.Java package typeinfo.pets;public class Dog extends Pet {public Dog(String name) {super(name);}public Dog() {super();}} // /:-

//: typeinfo/pets/Mutt.java package typeinfo.pets;public class Mutt extends Dog {public Mutt(String name) {super(name);}public Mutt() {super();}} // /:-

//: typeinfo/pets/Pug.java package typeinfo.pets;public class Pug extends Dog {public Pug(String name) {super(name);}public Pug() {super();}} // /:-

//: typeinfo/pets/Cat.java package typeinfo.pets;public class Cat extends Pet {public Cat(String name) {super(name);}public Cat() {super();}}

//: typeinfo/pets/EgyptianMau.java package typeinfo.pets;public class EgyptianMau extends Cat {public EgyptianMau(String name) {super(name);}public EgyptianMau() {super();}} // /:-

//: typeinfo/pets/Manx.java package typeinfo.pets;public class Manx extends Cat {public Manx(String name) {super(name);}public Manx() {super();}}

//: typeinfo/pets/Cymric.java package typeinfo.pets;public class Cymric extends Manx {public Cymric(String name) {super(name);}public Cymric() {super();}} // /:-
//: typeinfo/pets/Rodent.java package typeinfo.pets;public class Rodent extends Pet {public Rodent(String name) {super(name);}public Rodent() {super();}} // /:-


//: typeinfo/pets/Rat.java package typeinfo.pets;public class Rat extends Rodent {public Rat(String name) {super(name);}public Rat() {super();}} // /:-


//: typeinfo/pets/Mouse.Java package typeinfo.pets;public class Mouse extends Rodent {public Mouse(String name) {super(name);}public Mouse() {super();}} // /:-

//: typeinfo/pets/Hamster.Java package typeinfo.pets;public class Hamster extends Rodent {public Hamster(String name) {super(name);}public Hamster() {super();}}

Next, we need a way to randomly create different types of pets, and for convenience, to create arrays and Lists of pets. To allow this tool to evolve through several different implementations, we'll define it as an abstract class: 
//: typeinfo/pets/PetCreator.Java // Creates random sequences of Pets, package typeinfo.pets;import java.util.*;public abstract class PetCreator {private Random rand = new Random(47);// The List of the different types of Pet to create:public abstract List<Class<? extends Pet>> types();public Pet randomPet() { // Create one random Petint n = rand.nextInt(types().size());try {return types().get(n).newInstance();} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}}public Pet[] createArray(int size) {Pet[] result = new Pet[size];for (int i = 0; i < size; i++)result[i] = randomPet();return result;}public ArrayList<Pet> arrayList(int size) {ArrayList<Pet> result = new ArrayList<Pet>();Collections.addAll(result, createArray(size));return result;}} // /:-

The abstract getTypes( ) method defers to a derived class to get the List of Class objects (this is a variation of the Template Method design pattern).Notice that the type of class is specified to be "anything derived from Pet," so that newlnstance( ) produces a Pet without requiring a cast.randomPet( ) randomly indexes into the List and uses the selected Class object to generate a new instance of that class with Class.newlnstance( ).The createArray( ) method uses randomPet( ) to fill an array, and arrayList( ) uses createArray( ) in turn. 
You can get two kinds of exceptions when calling newlnstance( ). You can see these handled in the catch clauses following the try block. Again, the names of the exceptions are relatively useful explanations of what went wrong (IllegalAccessException relates to a violation of the Java security mechanism, in this case if the default constructor is private). 
When you derive a subclass of PetCreator, the only thing you need to supply is the List of the types of pet that you want to create using randomPet( ) and the other methods. The getTypes( ) method will normally just return reference to a static List. Here's an implementation using forName( ): 
//: typeinf0/pets/ForNameCreator.Java package typeinfo.pets;import java.util.*;public class ForNameCreator extends PetCreator {private static List<Class<? extends Pet>> types = new ArrayList<Class<? extends Pet>>();// Types that you want to be randomly created:private static String[] typeNames = { "typeinfo.pets.Mutt","typeinfo.pets.Pug", "typeinfo.pets.EgyptianMau","typei nfo.pets.Manx", "typeinfo.pets.Cymric","typei nfo.pets.Rat", "typeinfo.pets.Mouse","typeinfo.pets.Hamster" };@SuppressWarnings("unchecked")private static void loader() {try {for (String name : typeNames)types.add((Class<? extends Pet>) Class.forName(name));} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}static {loader();}public List<Class<? extends Pet>> types() {return types;}} // /: -

The loader( ) method creates the List of Class objects using Class.forName( ). This may generate a ClassNotFoundException,which makes sense since you're passing it a String which cannot be validated at compile time. Since the Pet objects are in package typeinfo, the package name must be used when referring to the classes. 
In order to produce a typed List of Class objects, a cast is required, which produces a compile-time warning. The loader( ) method is defined separately and then placed inside a static initialization clause because the @SuppressWarnings annotation cannot be placed directly onto the static initialization clause. 
To count Pets, we need a tool that keeps track of the quantities of various different types of Pets. A Map is perfect for this; the keys are the Pet type names and the values are Integers to hold the Pet quantities. This way, you can say, "How many Hamster objects are there?" We can use instanceof to count Pets: 
// : typeinfo/PetCount.java // Using instanceof . import static net.mindview.util.Print.print;import static net.mindview.util.Print.printnb;import java.util.HashMap;import typeinfo.pets.Cat;import typeinfo.pets.Dog;import typeinfo.pets.ForNameCreator;import typeinfo.pets.Hamster;import typeinfo.pets.Manx;import typeinfo.pets.Mouse;import typeinfo.pets.Mutt;import typeinfo.pets.Pet;import typeinfo.pets.PetCreator;import typeinfo.pets.Pug;import typeinfo.pets.Rat;import typeinfo.pets.Rodent;public class PetCount {static class PetCounter extends HashMap<String, Integer> {public void count(String type) {Integer quantity = get(type);if (quantity == null)put(type, 1);elseput(type, quantity + 1);}}public static void countPets(PetCreator creator) {PetCounter counter = new PetCounter();for (Pet pet : creator.createArray(20)) {// List each individual pet:printnb(pet.getClass().getSimpleName() + " ");if (pet instanceof Pet)counter.count("Pet");if (pet instanceof Dog)counter.count("Dog");if (pet instanceof Mutt)counter.count("Mutt");if (pet instanceof Pug)counter.count("Pug");if (pet instanceof Cat)counter.count("Cat");if (pet instanceof Manx)counter.count("EgyptianMau");if (pet instanceof Manx)counter.count("Manx");if (pet instanceof Manx)counter.count("Cymric");if (pet instanceof Rodent)counter.count("Rodent");if (pet instanceof Rat)counter.count("Rat");if (pet instanceof Mouse)counter.count("Mouse");if (pet instanceof Hamster)counter.count("Hamster");}// Show the counts:print();print(counter);}public static void main(String[] args) {countPets(new ForNameCreator());}}

In countPets( ), an array is randomly filled with Pets using a PetCreator. Then each Pet in the array is tested and counted using instanceof. 
There's a rather narrow restriction on instanceof: You can compare it to a named type only, and not to a Class object. In the preceding example you might feel that it's tedious to write out all of those instanceof expressions,and you're right. But there is no way to cleverly automate instanceof by creating an array of Class objects and comparing it to those instead (stay tuned—you'll see an alternative). This isn't as great a restriction as you might think, because you'll eventually understand that your design is probably flawed if you end up writing a lot of instanceof expressions. 

Using class literals 

If we reimplement the PetCreator using class literals, the result is cleaner in many ways: 
//: typeinfo/pets/LiteralPetCreator.java // Using class literals, package typeinfo.pets;import java.util.Arrays;import java.util.Collections;import java.util.List;public class LiteralPetCreator extends PetCreator {// No try block needed.@SuppressWarnings("unchecked")public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList(Arrays.asList(Pet.class, Dog.class, Cat.class,Rodent.class, Mutt.class, Pug.class, EgyptianMau.class,Manx.class, Cymric.class, Rat.class, Mouse.class,Hamster.class));// Types for random creation:private static final List<Class<? extends Pet>> types = allTypes.subList(allTypes.indexOf(Mutt.class), allTypes.size());public List<Class<? extends Pet>> types() {return types;}public static void main(String[] args) {System.out.println(types);}}

In the upcoming PetCount3.java example, we need to pre-load a Map with all the Pet types (not just the ones that are to be randomly generated), so the allTypes List is necessary. The types list is the portion of allTypes (created using List.subList( )) that includes the exact pet types, so it is used for random Pet generation. 
This time, the creation of types does not need to be surrounded by a try block since it's evaluated at compile time and thus won't throw any exceptions, unlike Class.forName( ). 
We now have two implementations of PetCreator in the typeinfo.pets library. In order to provide the second one as a default implementation, we can create a Faqade that utilizes LiteralPetCreator: 
//: typeinfo/pets/Pets.java // Facade to produce a default PetCreator. package typeinfo.pets;import java.util.*;public class Pets {public static final PetCreator creator = new LiteralPetCreator();public static Pet randomPet() {return creator.randomPet();}public static Pet[] createArray(int size) {return creator.createArray(size);}public static ArrayList<Pet> arrayList(int size) {return creator.arrayList(size);}} // /:-

This also provides indirection to randomPet( ), createArray( ) and arrayList( ). 
Because PetCount.countPets( ) takes a PetCreator argument, we can easily test the LiteralPetCreator (via the above Facade): 
package typeinfo.pets;import typeinfo.PetCount;//: typeinfo/PetCount2.java public class PetCount2 {public static void main(String[] args) {PetCount.countPets(Pets.creator);}} /* (Execute to see output) */// :-

The output is the same as that of PetCount.java.

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: 
package typeinfo.pets;//: typeinfo/PetCount3.Java // Using islnstance() import static net.mindview.util.Print.print;import static net.mindview.util.Print.printnb;import java.util.LinkedHashMap;import java.util.Map;import net.mindview.util.MapData;public class PetCount3 {static class PetCounter extendsLinkedHashMap<Class<? extends Pet>, Integer> {public PetCounter() {super(MapData.map(LiteralPetCreator.allTypes, 0));}public void count(Pet pet) {// Class.1slnstance() 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);}}

In order to count all the different types of Pet, the PetCounter Map is pre-loaded 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. 
You can see that the islnstance( ) 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/uti1/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. 
package net.mindview.util;// : typeinfo/PetCount4.java import static net.mindview.util.Print.print;import static net.mindview.util.Print.printnb;import typeinfo.pets.Pet;import typeinfo.pets.Pets;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);}}
As you can see from the output, both base types as well as exact types are counted. 

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: 
// : typeinfo/factory/Factory.Javapackage 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: 
package typeinfo.factory;// : type info/RegisteredFactories.jav a // Registering Class Factories in the base class. import java.util.ArrayList;import java.util.List;import java.util.Random;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 implementstypeinfo.factory.Factory<CabinAirFilter> {public CabinAirFilter create() {return new CabinAirFilter();}}}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 implementstypeinfo.factory.Factory<GeneratorBelt> {public GeneratorBelt create() {return new GeneratorBelt();}}}class PowerSteeringBelt extends Belt {public static class Factory implementstypeinfo.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());}}

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

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.islnstance(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.getClassO.equals(Derived.class)) "+ (x.getClass().equals(Derived.class)));}public static void main(String[] args) {test(new Base());test(new Derived());}}

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 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 general-purpose programming. 
我在想上面说所的话到底是什么意思呢?后来我想明白了,假设你接受到一个对象(系列化的字节码),你没有相应的类,那么你要怎么操作该类,比如调用某个方法?
import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;public class WriteObjectToFile {public static void main(String[] args) throws FileNotFoundException, IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myObject"));oos.writeObject(new MyObject());oos.flush();oos.close();}}class MyObject implements Serializable{private static final int INSTANCE = 10;public void printInstance(Integer i){System.out.println(INSTANCE + i);}}

import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;import java.lang.reflect.Method;public class ReadObjectFromFile {public static void main(String[] args) throws FileNotFoundException, IOException, Exception {ObjectInputStream iis = new ObjectInputStream(new FileInputStream("myObject"));Object o = iis.readObject();Method m = o.getClass().getMethod("printInstance", Integer.class);m.invoke(o,1);//Assumption that I don't know anything,for example,method name,method parameter typeMethod[] ms = o.getClass().getDeclaredMethods();for(Method mt : ms){String methodName = mt.getName();Class<?>[] classs = mt.getParameterTypes();for(Class c : classs){if(c.equals(Integer.class)){mt.invoke(o, 11);}}}}}

其实上如果你没有这个类是绝对调用不了其中的方法的
第一种情况:

第二种情况:
假设MyObject这个类实现了MyInterface这个接口,我有这个接口,但是没有实现类,结果也还是报错

请注意上面没有MyObject这个类
public interface MyInterface {public void printInstance(Integer i);}

import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;public class WriteObjectToFile {public static void main(String[] args) throws FileNotFoundException, IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myObject"));oos.writeObject(new MyObject());oos.flush();oos.close();}}class MyObject implements Serializable,MyInterface{private static final int INSTANCE = 10;public void printInstance(Integer i){System.out.println(INSTANCE + i);}}

import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;import java.lang.reflect.Method;public class ReadObjectFromFile {public static void main(String[] args) throws FileNotFoundException, IOException, Exception {ObjectInputStream iis = new ObjectInputStream(new FileInputStream("myObject"));MyInterface o = (MyInterface)iis.readObject();o.printInstance(10);}}

现在就是有一个问题,在你编译的时候你没有这个.class文件,后来在运行的过程中从网络上或者是数据库中接收到了某个.class文件,你要怎么加载进来呢?
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. 


0 0
原创粉丝点击