Java类型信息详解(1) - Java基础 - Java - 9SSSD.COM

创建时间:2015/10/8 23:41
来源:http://java.9sssd.com/javabase/art/1043


您的位置: 首页>Java>Java基础>Java类型信息详解(1)

Java类型信息详解(1)

2012-11-06 16:40 来源:博客园 作者:xupengnannan20070617 字号:T|T

[摘要]本文介绍Java类型信息,包括RTTI、Class对象、转换前检查等内容,并提供详细的示例代码供参考。

RTTI

为了实现变化与不便的解藕和,我们通常针对接口编程,在客户端调用的使用声明接口或者基类,然后引用到具体的类型,然后调用接口的方法,根据派生类型的动态绑定来执行特定对象的方法,这是多态。这样写出来的代码更加容易阅读书写和维护,因此多态通常作为面向对象编程的一大特点和编程目标。

但是如果在进行上塑造型之后,我们需要知道引用的具体类型来对不同的派生类执行不同的操作呢?这个时候就需要用到RTTI,运行时类型信息。

Class对象

要理解RIIT如何工作的,首先就要理解类型信息是如何在运行时保存的。Class对象是专门用来保存所有类的信息。Java通过使用Class对象来执行RTTI。对每个类都有一个对应的Class对象,因此每次编译一个新的类的时候,就会创建一个Class对象,保存在对应的.class文件里面。为了创建对应的class对象,Java虚拟机将会使用到一个叫做类加载器的东西。

类加载器系统能够组织一个类加载器链条,有一个初始的类加载器作为JVM应用的一部分。这个初始的加载器当作信任类来加载,包括Java的API类,通常是本地磁盘上的。一般情况下不需要额外的类加载器,但是如果你有特殊的需求,例如WEb服务器应用程序或者从网络上下载类来满足应用等,这个时候需要挂在额外的类加载器。

所有的类都是动态加载到JVM中的,在他们第一次使用的时候,这里的第一次指的是程序第一次调用类的静态成员,需要注意的是类的构造器也是静态的。当类加载器会首先检查Class对象是否加载,如果没有,那么找到同名的class文件,然后加载字节吗文件,验证代码的完整性和安全性。一旦这个类型的Class对象加载到内存中,它将会用来创建所有那个对象的类型。如下:

1//: typeinfo/SweetShop.java
2// Examination of the way the class loader works.
3import static net.mindview.util.Print.*;
4class Candy {
5  static { print("Loading Candy"); }
6}
7class Gum {
8  static { print("Loading Gum"); }
9}
10class Cookie {
11  static { print("Loading Cookie"); }
12}
13public class SweetShop {
14  public static void main(String[] args) {
15    print("inside main");
16    new Candy();
17    print("After creating Candy");
18    try {
19      Class.forName("Gum");
20    } catch(ClassNotFoundException e) {
21      print("Couldn’t find Gum");
22    }
23    print("After Class.forName(\"Gum\")");
24    new Cookie();
25    print("After creating Cookie");
26  }
27} /* Output:
28inside main
29Loading Candy
30After creating Candy
31Loading Gum
32After Class.forName("Gum")
33Loading Cookie
34After creating Cookie
35*///:~

在运行的任何时候想要获得类的信息都必须先使用Class获得类的引用,Class.forName是实现这一目的的很方便的方法,因为你不需要传递类的引用来获得类的信息,但是如果你有一个类的引用,你想要知道这个类的信息,那么调用基类Object的getCLass方法可以返回这个类型的具体类型的信息,另外还有很多其他的使用方法,如下:

1//: typeinfo/toys/ToyTest.java
2// Testing class Class.
3package typeinfo.toys;
4import static net.mindview.util.Print.*;
5interface HasBatteries {}
6interface Waterproof {}
7interface Shoots {}
8class Toy {
9  // Comment out the following default constructor
10  // to see NoSuchMethodError from (*1*)
11  Toy() {}
12  Toy(int i) {}
13}
14class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
15  FancyToy() { super(1); }
16}
17public class ToyTest {
18  static void printInfo(Class cc) {
19    print("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
20    print("Simple name: " + cc.getSimpleName());
21    print("Canonical name : " + cc.getCanonicalName());
22  }
23  public static void main(String[] args) {
24    Class c = null;
25    try {
26      c = Class.forName("typeinfo.toys.FancyToy");
27    } catch(ClassNotFoundException e) {
28      print("Can’t find FancyToy");
29      System.exit(1);
30    }
31    printInfo(c);
32    for(Class face : c.getInterfaces())
33      printInfo(face);
34    Class up = c.getSuperclass();
35    Object obj = null;
36    try {
37      // Requires default constructor:
38      obj = up.newInstance();
39    } catch(InstantiationException e) {
40      print("Cannot instantiate");
41      System.exit(1);
42    } catch(IllegalAccessException e) {
43      print("Cannot access");
44      System.exit(1);
45    }
46    printInfo(obj.getClass());
47  }
48} /* Output:
49Class name: typeinfo.toys.FancyToy is interface? [false]
50Simple name: FancyToy
51Canonical name : typeinfo.toys.FancyToy
52Class name: typeinfo.toys.HasBatteries is interface? [true]
53Simple name: HasBatteries
54Canonical name : typeinfo.toys.HasBatteries
55Class name: typeinfo.toys.Waterproof is interface? [true]
56Simple name: Waterproof
57Canonical name : typeinfo.toys.Waterproof
58Class name: typeinfo.toys.Shoots is interface? [true]
59Simple name: Shoots
60Canonical name : typeinfo.toys.Shoots
61Class name: typeinfo.toys.Toy is interface? [false]
62Simple name: Toy
63Canonical name : typeinfo.toys.Toy
64*///:~

另外,Java中提供了另外一种获取Class对象引用的方法,class属性,这个属性更加方便而且不许要放在try语句中,因为它不许要使用forName方法,而且更加有效率。这种方法适用于一般类和接口,包括初始类型,另外还可以对初始类型的封装类使用TYPE属性,它返回每个初始类型对应的Class对象,如下blloean.class==Bollean.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。建议使用.class属性来获取类型信息,因为这样使得对于一般类和初始值类型的兼容性更好。

使用class属性获取Class对象不会自动初始化Class对象,而是分为三步,首先加载,找到字节码从字节码中创建Class对象;连接,验证类的字节码,为静态字段分配空间,如果需要的话,创建这个类中对其他类的引用;初始化,如果有基类,初始化基类,执行静态初始化代码。初始化一直延迟到第一次引用静态方法,或者非常量静态字段:

1//: typeinfo/ClassInitialization.java
2import java.util.*;
3class Initable {
4  static final int staticFinal = 47;
5  static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
6  static {
7    System.out.println("Initializing Initable");
8  }
9}
10class Initable2 {
11  static int staticNonFinal = 147;
12  static {
13    System.out.println("Initializing Initable2");
14  }
15}
16class Initable3 {
17  static int staticNonFinal = 74;
18  static {
19    System.out.println("Initializing Initable3");
20  }
21}
22public class ClassInitialization {
23  public static Random rand = new Random(47);
24  public static void main(String[] args) throws Exception {
25    Class initable = Initable.class;
26    System.out.println("After creating Initable ref");
27    // Does not trigger initialization:
28    System.out.println(Initable.staticFinal);
29    // Does trigger initialization:
30    System.out.println(Initable.staticFinal2);
31    // Does trigger initialization:
32    System.out.println(Initable2.staticNonFinal);
33    Class initable3 = Class.forName("Initable3");
34    System.out.println("After creating Initable3 ref");
35    System.out.println(Initable3.staticNonFinal);
36  }
37} /* Output:
38After creating Initable ref
39Initializing Initable
40
41Initializing Initable2
42
43Initializing Initable3
44After creating Initable3 ref
45
46*///:~

Class还可以通过范型语法来指定获得的类型信息,如:

1//: typeinfo/GenericClassReferences.java
2public class GenericClassReferences {
3  public static void main(String[] args) {
4    Class intClass = int.class;
5    Class<Integer> genericIntClass = int.class;
6    genericIntClass = Integer.class; // Same thing
7    intClass = double.class;
8    // genericIntClass = double.class; // Illegal
9  }
10} ///:~

如果想要放宽到对任何类型的class属性都可以的话可以使用如下语法:

1//: typeinfo/WildcardClassReferences.java
2public class WildcardClassReferences {
3  public static void main(String[] args) {
4    Class<?> intClass = int.class;
5    intClass = double.class;
6  }
7} ///:~

这种语法与不用范型的效果一样,但是更加强调了一种编程的意向,那就是我是故意使得Class对象可以指向任何类型的对象信息的。那么如果想要使得对于范型和他的基类都适用的语法,如下:

1//: typeinfo/BoundedClassReferences.java
2public class BoundedClassReferences {
3  public static void main(String[] args) {
4    Class<? extends Number> bounded = int.class;
5    bounded = double.class;
6    bounded = Number.class;
7    // Or anything else derived from Number.
8  }
9} ///:~

使用范型语法的好处就是可以在编译的时候进行类型检查,这样可以更快的发现代码中的错误。在使用范型的时候有一个有趣的现象,那就是newInstance返回的是具体类型的对象,而不是基本的Object:

1//: typeinfo/toys/GenericToyTest.java
2// Testing class Class.
3package typeinfo.toys;
4public class GenericToyTest {
5  public static void main(String[] args) throws Exception {
6    Class<FancyToy> ftClass = FancyToy.class;
7    // Produces exact type:
8    FancyToy fancyToy = ftClass.newInstance();
9    Class<? super FancyToy> up = ftClass.getSuperclass();
10    // This won’t compile:
11    // Class<Toy> up2 = ftClass.getSuperclass();
12    // Only produces Object:
13    Object obj = up.newInstance();
14  }
15} ///:~

这里调用getSuperClass的时候范型只能指定为FancyToy的基类型,如语法?superFancyToy,而不是Toy,虽然编译器知道它的基类就是Toy,因为这样,所以up.newInstance不能返回具体的类型,而是Object对象。

Java SE5中Class还提供了cast方法,如下:

1//: typeinfo/ClassCasts.java
2class Building {}
3class House extends Building {}
4public class ClassCasts {
5  public static void main(String[] args) {
6    Building b = new House();
7    Class<House> houseType = House.class;
8    House h = houseType.cast(b);
9    h = (House)b; // ... or just do this.
10  }
11} ///:~

另外还有Class.asSubclass方法将类型转换为更加具体的类型。

转换前检查

我们已经介绍了RTTI的两种形式,一种是类型转换,一种是Class类型,下面还有一种形式是instanceof,语法如下:

1if(x instanceof Dog)
2  ((Dog)x).bark();

下面是一个instanceof方法的使用实例,首先我们创建一个类的层级关系:

1//: typeinfo/pets/Person.java
2package typeinfo.pets;
3public class Person extends Individual {
4  public Person(String name) { super(name); }
5} ///:~
6//: typeinfo/pets/Pet.java
7package typeinfo.pets;
8public class Pet extends Individual {
9  public Pet(String name) { super(name); }
10  public Pet() { super(); }
11} ///:~
12//: typeinfo/pets/Dog.java
13package typeinfo.pets;
14public class Dog extends Pet {
15  public Dog(String name) { super(name); }
16  public Dog() { super(); }
17} ///:~
18//: typeinfo/pets/Mutt.java
19package typeinfo.pets;
20public class Mutt extends Dog {
21  public Mutt(String name) { super(name); }
22  public Mutt() { super(); }
23} ///:~
24//: typeinfo/pets/Pug.java
25package typeinfo.pets;
26public class Pug extends Dog {
27  public Pug(String name) { super(name); }
28  public Pug() { super(); }
29} ///:~
30//: typeinfo/pets/Cat.java
31package typeinfo.pets;
32public class Cat extends Pet {
33  public Cat(String name) { super(name); }
34  public Cat() { super(); }
35} ///:~
36//: typeinfo/pets/EgyptianMau.java
37package typeinfo.pets;
38public class EgyptianMau extends Cat {
39  public EgyptianMau(String name) { super(name); }
40  public EgyptianMau() { super(); }
41} ///:~
42//: typeinfo/pets/Manx.java
43package typeinfo.pets;
44public class Manx extends Cat {
45  public Manx(String name) { super(name); }
46  public Manx() { super(); }
47} ///:~
48//: typeinfo/pets/Cymric.java
49package typeinfo.pets;
50public class Cymric extends Manx {
51  public Cymric(String name) { super(name); }
52  public Cymric() { super(); }
53} ///:~
54//: typeinfo/pets/Rodent.java
55package typeinfo.pets;
56public class Rodent extends Pet {
57  public Rodent(String name) { super(name); }
58  public Rodent() { super(); }
59} ///:~
60//: typeinfo/pets/Rat.java
61package typeinfo.pets;
62public class Rat extends Rodent {
63  public Rat(String name) { super(name); }
64  public Rat() { super(); }
65} ///:~
66//: typeinfo/pets/Mouse.java
67package typeinfo.pets;
68public class Mouse extends Rodent {
69  public Mouse(String name) { super(name); }
70  public Mouse() { super(); }
71} ///:~
72//: typeinfo/pets/Hamster.java
73package typeinfo.pets;
74public class Hamster extends Rodent {
75  public Hamster(String name) { super(name); }
76  public Hamster() { super(); }
77} ///:~

然后我们创建一个可以随意生成Pet实例的数组:

1//: typeinfo/pets/PetCreator.java
2// Creates random sequences of Pets.
3package typeinfo.pets;import java.util.*;
4public abstract class PetCreator {
5  private Random rand = new Random(47);
6  // The List of the different types of Pet to create:
7  public abstract List<Class<? extends Pet>> types();
8  public Pet randomPet() { // Create one random Pet
9    int n = rand.nextInt(types().size());
10    try {
11      return types().get(n).newInstance();
12    } catch(InstantiationException e) {
13      throw new RuntimeException(e);
14    } catch(IllegalAccessException e) {
15      throw new RuntimeException(e);
16    }
17  }
18  public Pet[] createArray(int size) {
19    Pet[] result = new Pet[size];
20    for(int i = 0; i < size; i++)
21      result[i] = randomPet();
22    return result;
23  }
24  public ArrayList<Pet> arrayList(int size) {
25    ArrayList<Pet> result = new ArrayList<Pet>();
26    Collections.addAll(result, createArray(size));
27    return result;
28  }
29} ///:~

1//: typeinfo/pets/ForNameCreator.java
2package typeinfo.pets;
3import java.util.*;
4public class ForNameCreator extends PetCreator {
5  private static List<Class<? extends Pet>> types = new ArrayList<Class<? extends Pet>>();
6  // Types that you want to be randomly created:
7  private static String[] typeNames = {
8    "typeinfo.pets.Mutt","typeinfo.pets.Pug","typeinfo.pets.EgyptianMau","typeinfo.pets.Manx",
9"typeinfo.pets.Cymric","typeinfo.pets.Rat","typeinfo.pets.Mouse","typeinfo.pets.Hamster"
10};
11  @SuppressWarnings("unchecked")
12  private static void loader() {
13    try {
14      for(String name : typeNames)
15        types.add((Class<? extends Pet>)Class.forName(name));
16    } catch(ClassNotFoundException e) {
17      throw new RuntimeException(e);
18    }
19  }
20  static { loader(); }
21  public List<Class<? extends Pet>> types() {return types;}
22} ///:~

下面就可以通过Map来对每个具体的类型进行计数:

1//: typeinfo/PetCount.java
2// Using instanceof.
3import typeinfo.pets.*;
4import java.util.*;
5import static net.mindview.util.Print.*;
6public class PetCount {
7  static class PetCounter extends HashMap<String,Integer> {
8    public void count(String type) {
9      Integer quantity = get(type);
10      if(quantity == null)
11        put(type, 1);
12      else
13        put(type, quantity + 1);
14    }
15  }
16  public static void countPets(PetCreator creator) {
17    PetCounter counter= new PetCounter();
18    for(Pet pet : creator.createArray(20)) {
19      // List each individual pet:
20      printnb(pet.getClass().getSimpleName() + " ");
21      if(pet instanceof Pet)
22        counter.count("Pet");
23      if(pet instanceof Dog)
24        counter.count("Dog");
25      if(pet instanceof Mutt)
26        counter.count("Mutt");
27      if(pet instanceof Pug)
28        counter.count("Pug");
29      if(pet instanceof Cat)
30        counter.count("Cat");
31      if(pet instanceof Manx)
32        counter.count("EgyptianMau");
33      if(pet instanceof Manx)
34        counter.count("Manx");
35      if(pet instanceof Manx)
36        counter.count("Cymric");
37      if(pet instanceof Rodent)
38        counter.count("Rodent");
39      if(pet instanceof Rat)
40        counter.count("Rat");
41      if(pet instanceof Mouse)
42        counter.count("Mouse");
43      if(pet instanceof Hamster)
44        counter.count("Hamster");
45    }
46    // Show the counts:
47    print();
48    print(counter);
49  }
50  public static void main(String[] args) {
51    countPets(new ForNameCreator());
52  }
53} /* Output:
54Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster
55EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
56{Pug=3, Cat=9, Hamster=1, Cymric=7, Mouse=2, Mutt=3, Rodent=5, Pet=20,
57Manx=7, EgyptianMau=7, Dog=6, Rat=2}
58*///:~

也可以使用class属性来实现PetCreator:

1//: typeinfo/pets/LiteralPetCreator.java
2// Using class literals.
3package typeinfo.pets;
4import java.util.*;
5public class LiteralPetCreator extends PetCreator {
6  // No try block needed.
7  @SuppressWarnings("unchecked")
8  public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList(Arrays.asList(
9Pet.class, Dog.class, Cat.class, Rodent.class,Mutt.class, Pug.class, EgyptianMau.class, Manx.class,
10Cymric.class, Rat.class, Mouse.class,Hamster.class));
11  // Types for random creation:
12  private static final List<Class<? extends Pet>> types = allTypes.subList(allTypes.indexOf(Mutt.class),
13allTypes.size());
14  public List<Class<? extends Pet>> types() {
15    return types;
16  }
17  public static void main(String[] args) {
18    System.out.println(types);
19  }
20} /* Output:
21[class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class
22typeinfo.pets.EgyptianMau, class typeinfo.pets.Manx, class
23typeinfo.pets.Cymric, class typeinfo.pets.Rat, class
24typeinfo.pets.Mouse, class typeinfo.pets.Hamster]
25*///:~

现在我们有了两种PetCreator的实现,为了将第二种方法设置为默认的实现方式,可以使用Facade模式:

1//: typeinfo/pets/Pets.java
2// Facade to produce a default PetCreator.
3package typeinfo.pets;
4import java.util.*;
5public class Pets {
6  public static final PetCreator creator = new LiteralPetCreator();
7  public static Pet randomPet() {
8    return creator.randomPet();
9  }
10  public static Pet[] createArray(int size) {
11    return creator.createArray(size);
12  }
13  public static ArrayList<Pet> arrayList(int size) {
14    return creator.arrayList(size);
15  }
16} ///:~

然后定义PetCount2输出与PetCount同样的结果:

1//: typeinfo/PetCount2.java
2import typeinfo.pets.*;
3public class PetCount2 {
4  public static void main(String[] args) {
5    PetCount.countPets(Pets.creator);
6  }
7} /* (Execute to see output) *///:~

Class.instance方法还提供了一种动态检测对象类型的方法,如下:

1//: typeinfo/PetCount3.java
2// Using isInstance()
3import typeinfo.pets.*;
4import java.util.*;
5import net.mindview.util.*;
6import static net.mindview.util.Print.*;
7public class PetCount3 {
8  static class PetCounter extends LinkedHashMap<Class<? extends Pet>,Integer> {
9    public PetCounter() {
10      super(MapData.map(LiteralPetCreator.allTypes, 0));
11    }
12    public void count(Pet pet) {
13      // Class.isInstance() eliminates instanceofs:
14      for(Map.Entry<Class<? extends Pet>,Integer> pair : entrySet())
15        if(pair.getKey().isInstance(pet))
16          put(pair.getKey(), pair.getValue() + 1);
17    }
18    public String toString() {
19      StringBuilder result = new StringBuilder("{");
20      for(Map.Entry<Class<? extends Pet>,Integer> pair : entrySet()) {
21        result.append(pair.getKey().getSimpleName());
22        result.append("=");
23        result.append(pair.getValue());
24        result.append(", ");
25      }
26      result.delete(result.length()-2, result.length());
27      result.append("}");
28      return result.toString();
29    }
30  }
31  public static void main(String[] args) {
32    PetCounter petCount = new PetCounter();
33    for(Pet pet : Pets.createArray(20)) {
34      printnb(pet.getClass().getSimpleName() + " ");
35      petCount.count(pet);
36    }
37    print();
38    print(petCount);
39  }
40} /* Output:
41Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster
42EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
43{Pet=20, Dog=6, Cat=9, Rodent=5, Mutt=3, Pug=3, EgyptianMau=2, Manx=7,
44Cymric=5, Rat=2, Mouse=2, Hamster=1}
45*///:~

在PetCount3中要预先加载所有的Pet类型,除了这个方法,我们可以使用Class.isAssignableFrom方法,,然后创建一个通用的计数方法:

1//: net/mindview/util/TypeCounter.java
2// Counts instances of a type family.
3package net.mindview.util;
4import java.util.*;
5public class TypeCounter extends HashMap<Class<?>,Integer>{
6  private Class<?> baseType;
7  public TypeCounter(Class<?> baseType) {
8    this.baseType = baseType;
9  }
10  public void count(Object obj) {
11    Class<?> type = obj.getClass();
12    if(!baseType.isAssignableFrom(type))
13      throw new RuntimeException(obj + " incorrect type: " + type + ", should be type or subtype of " + baseType);
14    countClass(type);
15  }
16  private void countClass(Class<?> type) {
17    Integer quantity = get(type);
18    put(type, quantity == null ? 1 : quantity + 1);
19    Class<?> superClass = type.getSuperclass();
20    if(superClass != null && baseType.isAssignableFrom(superClass))
21      countClass(superClass);
22  }
23  public String toString() {
24    StringBuilder result = new StringBuilder("{");
25    for(Map.Entry<Class<?>,Integer> pair : entrySet()) {
26      result.append(pair.getKey().getSimpleName());
27      result.append("=");
28      result.append(pair.getValue());
29      result.append(", ");
30    }
31    result.delete(result.length()-2, result.length());
32    result.append("}");
33    return result.toString();
34  }
35} ///:~

1//: typeinfo/PetCount4.java
2import typeinfo.pets.*;
3import net.mindview.util.*;
4import static net.mindview.util.Print.*;
5public class PetCount4 {
6  public static void main(String[] args) {
7    TypeCounter counter = new TypeCounter(Pet.class);
8    for(Pet pet : Pets.createArray(20)) {
9      printnb(pet.getClass().getSimpleName() + " ");
10      counter.count(pet);
11    }
12    print();
13    print(counter);
14  }
15} /* Output: (Sample)
16Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster
17EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
18{Mouse=2, Dog=6, Manx=7, EgyptianMau=2, Rodent=5, Pug=3, Mutt=3,
19Cymric=5, Cat=9, Hamster=1, Pet=20, Rat=2}
20*///:~