重温 Thinking in Java 5 - 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 the Class 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 RTT.

There's one Class object for each class that is part of your program. That is, each time you write and complile 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 a 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 usualy 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 calss 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 tranditional 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.

 

class Candy {
	static {
		System.out.println("Loading Candy");
	}
}

class Gum {
	static {
		System.out.println("Loading Gum");
	}
}

class Cookie {
	static {
		System.out.println("Loading Cookie");
	}
}

public class SweetShop {
	public static void main(String[] args){
		System.out.println("inside main");
		new Candy();
		System.out.println("After creating Candy");
		new Candy();
		try{
			Class.forName("Gum");
		}catch(ClassNotFoundException e){
			System.out.println("Couldn't find Gum");
		}
		
		System.out.println("After Class.forName(\"Gum\")");
		new Cookie();
		System.out.println("After creating Cookie");
	}
}

Output:

inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie

 

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

 

interface HasBatteries {}

interface Waterproof {}

interface Shoots {}

class Toy {
	//Comment out the following default constructor
	//to see NoSuchMethodError from (*1*)
	Toy(){}
	Toy(int i){}
}

class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
	FancyToy() {
		super(1);
	}
}

public class ToyTest {
	static void printInfo(Class cc){
		System.out.println("Class name: " + cc.getName() + 
			" is interface? [" + cc.isInterface() + "]");
		System.out.println("Simple name: " + cc.getSimpleName());
		System.out.println("Canonical name: " + cc.getCanonicalName());
	}
	
	public static void main(String[] args){
		Class c = null;
		try{
			c = Class.forName("FancyToy");
		}catch(ClassNotFoundException e){
			System.out.println("Can't find FancyToy");
			System.exit(1);
		}
		
		System.out.println(c);
		
		for(Class face : c.getInterfaces())
			printInfo(face);
		
		Class up = c.getSuperclass();
		Object obj = null;
		
		try{
			//Requires default constructor;
			obj = up.newInstance();
		}catch(InstantiationException ex){
			System.out.println("Cannot instantiate");
			System.exit(1);
		}catch(IllegalAccessException e){
			System.out.println("Cannot access");
			System.exit(1);
		}
		printInfo(obj.getClass());
		
	}
}
/* Output:
class FancyToy
Class name: HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name: HasBatteries
Class name: Waterproof is interface? [true]
Simple name: Waterproof
Canonical name: Waterproof
Class name: Shoots is interface? [true]
Simple name: Shoots
Canonical name: Shoots
Class name: Toy is interface? [false]
Simple name: Toy
Canonical name: Toy
*/
 

Class literals

 

Java provides a second way to produce the reference to the Class object: the class literal.

FancyToy.class;

which is not only simpler, but also safer since it's checked at compile time (and thus does not need to be placed in a try block). Because it eliminates the forName method call, it's also more efficient.

 

Class literals work with regular classes as well as interfaces, arrays, and primitive types. In addition, there's a standard field called TYPE that exists for each of the primitive wrapper classes. The TYPE field produces a reference to the Class object for the associated primitive type, such that:

... is equivalent to ...
boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

 

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 the 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, resovles all references to other classes made by this class.

3. Initialization. If there's a superclass, initialize that. Execute static initializes 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:

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);
	}
}
/* Output
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
*/

 

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 Intiable.staticFinal, that value can be read without causing the Initable class to be initalized. Making a field static and final, however, does not gurantee 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.

public class GenericClassReferences {
	public static void main(String[] args){
		Class intClass = int.class;
		Class<Integer> genericIntClass = int.class;
		genericIntClass = Integer.class;//Same thing
		intClass = double.class;
		//genericIntClass = double.class; //Illegal
	}
}

 

Class<Number> genericNumberClass = int.class;

 Error: incompatible types

 

public class WildcardClassReferences {
	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.

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

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> f1 = new FilledList<CountedInteger>(CountedInteger.class);
		System.out.println(f1.create(15));
	}
}

 

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 means 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.newInstance() 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:

class Building {}
class House extends Building {}

public class ClassCasts {
	public static void main(String[] args){
		Buiding b = new House();
		Class<House> houseType = House.class;
		House h = houseType.cast(b);
		h = (House) b;//...or just do this
	}
} 

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

 

Checking before a cast

 

So far, you've seen of 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. (upcaste, downcast)

2. The class object representing the type of your object. The Class object can be queried for useful runtime information.

3. 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();

 

- Using class literals

- A dynamic instanceof

 - Counting recursively

 

 

你可能感兴趣的:(java,jvm,C++,c,UP)