Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十四)之Type Information

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十四)之Type Information相关的知识,希望对你有一定的参考价值。

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

  This take two forms:

    1. "traditional" RTTI, which assumes that you have all the types available at compile time,

    2. the reflection mechanism, which allows you to discover and use class information solely at run time

The need for RTTI

  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,because all casts are checked at run time for correctness. That‘s what RTTI means: At run time, the type of an object is identified

  But what if you have a special programming problem that’s easiest to solve if you know the exact type of a generic reference?

The Class object

  the Class object contains information about the class.

  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

  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 ( the constructor is also a static method of a class)

  Class.forName("Gum"); // return a Class reference, the call to forName() is being made for its side effect,

              // which is to load the class Gum if it isn‘t already loaded

  All Class objects belong to the class Class.

  object.getClass()

  Notice that you must use the full qualified name (including the package name) in the string that you pass to forName()

  getName() //返回的是虚拟机里面的class的表示

  getSimpleName()

  getCanonicalName() //返回的是更容易理解的表示

  isInterface()

  getInterfaces()

  getSuperclass() 

  newInstance() //the class that’s being created with newlnstance( ) must have a default constructor.

  getName()和getCanonicalName() 对于普通类来说,二者没什么区别,只是对于特殊的类型上有点表示差异

  比如byte[]类型,前者就是[B,后者就是byte[]

  比如byte[][]类型,前者就是[[B,后者就是byte[][]

  Class literals

  FancyToy.class: return a reference to the Class object, safe than Class.forName(), and more efficient.

  Class literals work with regular classes as well as interfaces, arrays, and primitive types.

  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

  for example: boolean.class ==  Boolean.TYPE

         boolean.class != Boolean.class

  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. Finds the bytecodes and creates a Class object from those bytecodes.

    2. Linking. verifies the bytecodes, allocates storage for static fields, and if necessary, resolves all references to other classes made by this class

    3. Initialization. If there‘s a supperclass, 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

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
*///:~

  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.

  public class GenericClassReferences {

    public static void main(String[] args) {

      Class intClass = int.class;

      Class<Integer> genericIntClass = int.class;

      genericIntClass = Integer.class;

      intClass = double.class; // it‘s OK

      // genericIntClass = double.class; // Illegal

    }

  }

  By using the generic syntax, you allow the compiler to enforce extra type checking

  Class<Number> genericNumberClass = int.class; // it doesn‘t work, because the Integer Class object is not a subclass of the Number class object

  To loosen the constraints when using generic Class references, use the wildcard "?" 

  Class<?> intClass = int.class;

  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.

  Class<? extends Number> bounded = int.class // it‘s OK

  The reason for adding the generic syntax to Class references is only to provide compile-time type checking.

  Class clazz = int.class; 

  Class<int> intClass = int.class;

  clazz.newInstance(); 返回Object

  intClass.newInstance(); 返回int

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

  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

  Class.asSubclass(): allow you to cast the class object to a more specific type.

Checking before a cast

  Forms of RTTI:

    1. The classic cast; e.g., "(Shape)," which uses RTTI to make sure that 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

    3. instanceof. tells you if an object is an instance of a particular type.

  A dynamic instanceof

  i instanceof Integer // instanceof后必须写死为某个类名

  Integer.class.isInstance(i) //isInstance前为引用比较灵活

  Counting recursively

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();
  }
}

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.

  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.

instanceof vs. Class equivalence

  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

Dynamic proxies

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

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);
  }
}

  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.

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);
  }
}
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);
  }
} /* Output: (95% match)
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816
bonobo
somethingElse bonobo
*///:~

  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 dynamic proxy is not a tool that you’ll use every day, but it can solve certain types of problems very nicely

Null Objects(??)

  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

  Mock Objects & Stubs

  Logical variations of the Null Object are the MocA: 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

Summary

  The intent of O O 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.

  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.

  

以上是关于Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十四)之Type Information的主要内容,如果未能解决你的问题,请参考以下文章

Thinking in Java & Writing in Python

Thinking in Java

Thinking in java 16/3/5

Thinking in java 16/3/6

Thinking in Java

thinking in java ----reading note