程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 舉例講授Java的RTTI運轉時類型辨認機制

舉例講授Java的RTTI運轉時類型辨認機制

編輯:關於JAVA

舉例講授Java的RTTI運轉時類型辨認機制。本站提示廣大學習愛好者:(舉例講授Java的RTTI運轉時類型辨認機制)文章只能為提供參考,不一定能成為您想要的結果。以下是舉例講授Java的RTTI運轉時類型辨認機制正文


1、RTTI:
運轉時類型信息可讓你在法式運轉時發明和應用類型信息。

在Java中運轉時辨認對象和類的信息有兩種方法:傳統的RTTI,和反射。上面就來講下RTTI。

RTTI:在運轉時,辨認一個對象的類型。然則這個類型在編譯時必需已知。

上面經由過程一個例子來看下RTTI的應用。這裡觸及到了多態的概念:讓代碼只操作基類的援用,而現實上挪用詳細的子類的辦法,平日會創立一個詳細的對象(Circle,Square,或許Triangle,見下例),把它向上轉型為Shape(疏忽了對象的詳細類型),並在前面的法式中應用匿名(即不曉得詳細類型)的Shape援用:

abstract class Shape {
  // this 挪用以後類的toString()辦法,前往現實的內容
  void draw(){ System.out.println(this + "draw()"); }
  // 聲明 toString()為abstract類型,強迫集成在重寫該辦法
  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 static void main(String[] args){
  // 把Shape對象放入List<Shape>的數組的時刻會向上轉型為Shape,從而喪失了詳細的類型信息
  List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());
  // 從數組中掏出時,這類容器,現實上一切的元素都當做Object持有,會主動將成果轉型為Shape,這就是RTTI的根本的應用。
  for(Shape shape : shapeList){
    shape.draw();
  }
}

輸入成果為:

Circledraw()
Squaredraw()
Triangledraw()

存入數組的時刻,會主動向上轉型為Shape,喪失了詳細的類型,當從數組中掏出的時刻,(List容器將一切的事物都當作Object持有),會主動將成果轉型回Shape,這就是RTTI的根本用法。Java中一切的類型轉換都是在運轉時停止准確性檢討的,也就是RTTI:在運轉時,辨認一個對象的類型。

下面的轉型其實不完全,數組的元素掏出時又Object轉型為Shape,而不是詳細的類型,編譯時這是由容器和Java泛型體系來確保這一點的,而在運轉不時有類型轉換操作來確保這一點的。

而可以或許經由過程Shape對象履行到子類的詳細代碼就是又多態來決議的了,詳細看Shape援用所指向的詳細對象。

別的,應用RTTI,可以查詢某個Shape援用所指向的對象切實其實切類型,然後選擇性的履行子類的辦法。

2、Class對象:
要懂得RTTI在Java中的任務道理,必需曉得類型信息在運轉時是若何表現的,這裡是由Class這個特別對象完成的。

Class對象是用來創立類的一切的“慣例”對象的。Java應用Class對象來履行其RTTI。

每當編譯一個新類,就會發生一個Class對象(.class文件)。運轉這個法式的JVM將應用“類加載器”這個子體系。

類加載器子體系:包括一條類加載器鏈,但只要一個原生類加載器,它是JVM完成的一部門。原生類加載器加載可托類,包含Java API類,平日是從當地磁盤加載的。當須要以某種特定的方法加載類,以支撐Web辦事器運用,可以掛接額定的類加載器。

2.1、加載類的機會:
當法式創立第一個對類的靜態成員的援用時,就會加載這個類。這證實其實結構器也是類的靜態辦法,當應用new操作符創立類的新對象也會當作對類的靜態成員的援用。

可見Java法式時靜態加載的,按需加載。須要用到Class時,類加載器起首會檢討這個類的Class對象能否曾經加載,假如還沒有加載,默許的類加載器就會依據類名查找到.class文件。接上去是驗證階段:加載時,它們會接收驗證,以確保其沒有被損壞,而且不包括不良Java代碼。

2.2、Class相干辦法,newInstance()
上面經由過程一個例子演示Class對象的加載:

class A {
  // 靜態代碼庫,在第一次被加載時履行,經由過程打印信息曉得該類甚麼時刻被加載
  static { System.out.println("Loading A"); }
}
class B {
  static { System.out.println("Loading B"); }
}
class C {
  static { System.out.println("Loading C"); }
}
public class Load {
  public static void main(String[] args){
    System.out.println("execute main...");
    new A();
    System.out.println("after new A");
    try {
      Class.forName("com.itzhai.test.type.B");
    } catch (ClassNotFoundException e) {
      System.out.println("cloud not find class B");
    }
    System.out.println("after Class.forName B");
    new C();
    System.out.println("after new C");
  }
}

輸入成果為:

execute main...
Loading A
after new A
Loading B
after Class.forName B
Loading C
after new C

可見,Class對象在須要的時刻才被加載,留意到這裡的Class.forName()辦法:

forName()辦法是獲得Class對象的援用的一種辦法,經由過程這個辦法取得適當的Class對象的援用,便可以在運轉時應用類型信息了。

假如你曾經有了一個感興致的類型的對象,則可以經由過程跟類Object供給的getClass()辦法來取得Class援用。

上面是一段Class的應用的代碼:

interface X{}
interface Y{}
interface Z{}
class Letter {
  Letter(){};
  Letter(int i){};
}
class NewLetter extends Letter implements X, Y, Z{
  NewLetter(){ super(1); };
}
public class ClassTest {

  /**
   * 打印類型信息
   * @param c
   */
  static void printInfo(Class c){
    // getName()取得全限制的類名
    System.out.println("Class name: " + c.getName() + " is interface? " + c.isInterface());
    // 取得不包括包名的類名
    System.out.println("Simple name: " + c.getSimpleName());
    // 取得全限制類名
    System.out.println("Canonical name: " + c.getCanonicalName());
  }

  public static void main(String[] args){
    Class c = null;
    try {
      // 取得Class援用
      c = Class.forName("com.itzhai.test.type.NewLetter");
    } catch (ClassNotFoundException e) {
      System.out.println("Can not find com.itzhai.test.type.NewLetter");
      System.exit(1);
    }
    // 打印接口類型信息
    for(Class face : c.getInterfaces()){
      printInfo(face);
    }
    // 獲得超類Class援用
    Class up = c.getSuperclass();
    Object obj = null;
    try {
      // 經由過程newInstance()辦法創立Class的實例
      obj = up.newInstance();
    } catch (InstantiationException e) {
      System.out.println("Can not instantiate");
    } catch (IllegalAccessException e) {
      System.out.println("Can not access");
    }
    // 打印超類類型信息
    printInfo(obj.getClass());
  }
}

輸入為:

Class name: com.itzhai.test.type.X is interface? true
Simple name: X
Canonical name: com.itzhai.test.type.X
Class name: com.itzhai.test.type.Y is interface? true
Simple name: Y
Canonical name: com.itzhai.test.type.Y
Class name: com.itzhai.test.type.Z is interface? true
Simple name: Z
Canonical name: com.itzhai.test.type.Z
Class name: com.itzhai.test.type.Letter is interface? false
Simple name: Letter
Canonical name: com.itzhai.test.type.Letter

留意,在傳遞給forName()的字符串必需應用全限制名(包含包名)。

經由過程printInfo外面應用到的辦法,你可以在運轉時發明一個對象完全的類繼續構造。

經由過程應用Class的newInstance()辦法是完成“虛擬結構器”的一種門路,用來創立Class的實例,獲得的是Object援用,然則援用時指向Letter對象。應用newInstance()來創立的類,必需帶有默許的結構器。(而經由過程反射API,可以用隨意率性的結構器來靜態的創立類的對象)。

2.3、類字面常量:
除應用getName()辦法,Java還供給了另外一種辦法來生成對Class對象的援用,即便用類字面常量:

NewLetter.class;

此辦法簡略平安,編譯時就遭到檢討,更高效。不只可用於通俗類,也能夠用於接口,數組和根本數據類型。別的,關於根本數據類型的包裝器類,還有一個尺度字段TYPE,TYPE字段是一個援用,履行對應的根本數據類型的Class對象。為了同一,建議都應用.class這類情勢。

2.4、應用.class與應用getName()辦法創立對象援用的差別:
應用.class創立時,不會主動的初始化Class對象。創立步調以下:

(1)加載 由類加載器履行:查找字節碼(平日是在classpath指定的途徑中查找,但並不是必需的),然後從這些字節碼中創立一個Class對象。

(2)鏈接 將驗證類中的字節碼,為靜態域分派存儲空間,假如須要,將會解析這個類創立的對其他類的一切的援用。

(3)初始化 假如該類具有超類,則對其初始化,履行靜態初始化器和靜態初始化塊。

初始化被延遲到了對靜態辦法(結構器隱式的是靜態的)或許異常數靜態域停止初次援用時才履行的:

class Data1{
  static final int a = 1;
  static final double b = Math.random();
  static {
    System.out.println("init Data1...");
  }
}

class Data2{
  static int a = 12;
  static {
    System.out.println("init Data2...");
  }
}

class Data3{
  static int a = 23;
  static {
    System.out.println("init Data3...");
  }
}

public class ClassTest2 {
  public static void main(String[] args){
    System.out.println("Data1.class: ");
    Class data1 = Data1.class;
    System.out.println(Data1.a); // 沒有初始化Data1
    System.out.println(Data1.b); // 初始化了Data1
    System.out.println(Data2.a); // 初始化了Data2
    try {
      Class data3 = Class.forName("com.itzhai.test.type.Data3"); // 初始化了Data3
    } catch (ClassNotFoundException e) {
      System.out.println("can not found com.itzhai.test.type.Data3...");
    }
    System.out.println(Data3.a);
  }
}

輸入的成果為:

Data1.class: 
1
init Data1...
0.26771085109184534
init Data2...
12
init Data3...
23

初始化有用的完成了盡量的“惰性”。

2.5、上面是斷定能否履行初始化的一些情形:
(1)class語法取得對類的援用不會激發初始化;

(2)Class.forName()發生了Class援用,立刻停止了初始化;

(3)假如一個static final值是“編譯器常量”,那末這個值不須要對類停止初始化便可以被讀取;

(4)假如只是把一個域設置為static final還缺乏以確保這類行動,例如下面的:

static final double b = Math.random();

(5)假如一個static域bushifinal的,那末在對它拜訪時,老是要先輩性鏈接和初始化;

2.6、泛化的Class援用:
Class援用表現的是它所指向的對象切實其實切類型,而該對象就是Class類的一個對象。在JavaSE5中,可以經由過程泛型對Class援用所指向的Class對象停止限制,而且可讓編譯器強迫履行額定的類型檢討:

Class intCls = int.class;
// 應用泛型限制Class指向的援用
Class<Integer> genIntCls = int.class;
// 沒有應用泛型的Clas可以從新賦值為指向任何其他的Class對象
intCls = double.class;
// 上面的編譯會失足
// genIntCls = double.class;

2.6.1、應用通配符?抓緊泛型的限制:

Class<?> intCls = int.class;
intCls = String.class;

在JavaSE5中,Class<?>優於平常的Class,更建議應用Class<?>,即使它們是等價的,由於Class<?>的利益是它表現你並不是是恰巧或許忽視,而是應用了一個非詳細的類援用。

為了限制Class的援用為某品種型,或許該類型的子類型可以將通配符與extends一路應用,創立一個規模:

Class<? extends Number> num = int.class;
// num的援用規模為Number及其子類,所以可以依照以下賦值
num = double.class;
num = Number.class;

2.6.2、泛型下的newInstance()辦法:
應用了泛型後的Class,挪用newInstance()前往的對象是確實類型的,然則當你應用getSuperclass()獲得泛型對應的超類的時刻真實的類型會有一些限制:編譯器在編譯期就曉得了超類的類型,然則,經由過程這個獲得到的超類援用的newInstance()辦法前往的不是准確類型,而是Object:

Dog dog = dogCls.newInstance();
abstract class Animal {
}
class Dog extends Animal{
}

// 上面的寫法是毛病的,只能前往 Class<? super Dog>類型
// Class<Animal> animalCls = dogCls.getSuperclass(); 
Class<? super Dog> animalCls = dogCls.getSuperclass();
// 經由過程獲得的超類援用,只能創立前往Object類型的對象
Object obj = animalCls.newInstance();

2.6.3、新的轉型語法:cast()辦法
直接看下代碼:

Animal animal = new Dog();
Class<Dog> dogCls = Dog.class;
Dog dog = dogCls.cast(animal);
// 或許直接應用上面的轉型辦法
dog = (Dog)animal;

可以發明,應用cast()辦法的做了額定的任務,這類轉換辦法可以用在一下的情形中:在編寫泛型帶的時刻,假如存儲了Class援用,並願望經由過程這個Class援用來履行轉型,便可以應用cast()辦法。

3、類型檢討 instanceof
3.1、類型轉換前先做檢討
編譯器許可你自在的做向上轉型的賦值操作,而不須要任何顯示的轉型操作,就似乎給超類的援用賦值那樣。

但是假如不應用顯示的類型轉換,編譯器就不許可你履行向下轉換賦值,這個時刻我們無妨先來檢討一下對象是否是某個特定類型的實例,應用到了症結字 instanceof:

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

3.2、RTTI的情勢:
所以,到今朝為止,我們曉得RTTI的情勢包含:

(1)傳統的類型轉換 (Shape)

(2)代表對象的類型的Class對象

(3)症結字instanceof

3.3、靜態的instanceof辦法:
Class.isInstance辦法供給給了一種靜態測試對象的門路。

上面演示下instanceof和Class.isInstance的用法:

Attribute:

public interface Attribute {

}

Shape:

/**
 * 創立一個籠統類
 */
public abstract class Shape{
  // this挪用了以後類的toString辦法取得信息
  public void draw() { System.out.println(this + ".draw()"); }
  // 聲明toString()辦法為abstract,從而強迫繼續者須要重寫該辦法。
  abstract public String toString();
}

Circle:

public class Circle extends Shape implements Attribute{
  public String toString(){ return "Circle"; }
}

Square:

public class Square extends Shape{
  public String toString(){ return "Square"; }
}

Triangle:

public class Triangle extends Shape{
  public String toString(){ return "Triangle"; }
}

類型檢討:

// instanceOf
Circle c = new Circle();
// 斷定能否超類的實例
System.out.format("Using instanceof: %s is a shape? %b\n", 
    c.toString(), c instanceof Shape);
// 斷定能否Circle的實例
System.out.format("Using instanceof: %s is a circle? %b\n", 
    c.toString(), c instanceof Circle);
// 斷定能否超類的實例
System.out.format("Using Class.isInstance: %s is a shape? %b\n", 
    c.toString(), Shape.class.isInstance(c));
// 斷定能否接口的實例
System.out.format("Using Class.isInstance: %s is a Attribute? %b\n", 
    c.toString(), Attribute.class.isInstance(c));

可以發明,instanceof或許Class.isInstance辦法斷定了能否繼續系統的實例,即除斷定自己,還斷定能否超類或接口的實例。

上面演示下應用靜態的Class.instance的用法:

起首創立一個籠統的外形生成器類:

public abstract class ShapeCreator {
  private Random rand = new Random(10);
  // 前往一個對象類型數組,由完成類供給,前面會看到兩種完成情勢,基於forName的和基於類字面常量的.class
  public abstract List<Class<? extends Shape>> types();
  // 隨機生成一個對象類型數組中的類型對象實例
  public Shape randomShape(){
    int n = rand.nextInt(types().size());
    try {
      return types().get(n).newInstance();
    } catch (InstantiationException e) {
      e.printStackTrace();
      return null;
    } catch (IllegalAccessException e) {
      e.printStackTrace();
      return null;
    }
  }
  // 生成一個隨機數組
  public Shape[] createArray(int size){
    Shape[] result = new Shape[size];
    for(int i=0; i<size; i++){
      result[i] = randomShape();
    }
    return result;
  }
  // 生成一個隨機數組,泛型的ArrayList
  public ArrayList<Shape> arrayList(int size){
    ArrayList<Shape> result = new ArrayList<Shape>();
    Collections.addAll(result, createArray(size));
    return result;
  }
}

接上去編寫一個該籠統類的完成:

/**
 * forName的生成器完成
 * @author arthinking
 *
 */
public class ForNameCreator extends ShapeCreator{

  private static List<Class<? extends Shape>> types = 
      new ArrayList<Class<? extends Shape>>();
  private static String[] typeNames = {
    "com.itzhai.javanote.entity.Circle",
    "com.itzhai.javanote.entity.Square",
    "com.itzhai.javanote.entity.Triangle"
  };

  @SuppressWarnings("unused")
  private static void loader(){
    for(String name : typeNames){
      try {
        types.add((Class<? extends Shape>)Class.forName(name));
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
    }
  }
  // 初始化加載所需的類型數組
  static {
    loader();
  }
  public List<Class<? extends Shape>> types() {
    return types;
  }
}

最初寫一個統計外形個數的類,外面用到了instanceof:

public class ShapeCount {

  static class ShapeCounter extends HashMap<String, Integer>{
    public void count(String type){
      Integer quantity = get(type);
      if(quantity == null){
        put(type, 1);
      } else {
        put(type, quantity + 1);
      }
    }
  }

  // 演示經由過程instanceof症結字統計對象類型
  public static void countShapes(ShapeCreator creator){
    ShapeCounter counter = new ShapeCounter();
    for(Shape shape : creator.createArray(20)){
      if(shape instanceof Circle)
        counter.count("Circle");
      if(shape instanceof Square)
        counter.count("Square");
      if(shape instanceof Triangle){
        counter.count("Triangle");
      }
    }
    System.out.println(counter);
  }

  public static void main(String[] args){
    countShapes(new ForNameCreator());
  }
}

改寫一下籠統類的完成,從新用類字面常量完成:

/**
 * 字面量的生成器完成
 */
public class LiteralCreator extends ShapeCreator{

  public static final List<Class<? extends Shape>> allType = 
      Collections.unmodifiableList(Arrays.asList(Circle.class, Triangle.class, Square.class));

  public List<Class<? extends Shape>> types(){
    return allType;
  }

  public static void main(String[] args){
    System.out.println(allType);
  }

}

如今應用Class.instance統計外形的個數以下:

/**
 * 經由過程應用Class.instanceof靜態的測試對象,移除失落本來的ShapeCount中單調的instanceof語句
 *
 */
public class ShapeCount2 {

  private static final List<Class<? extends Shape>> shapeTypes = LiteralCreator.allType;

  static class ShapeCounter extends HashMap<String, Integer>{
    public void count(String type){
      Integer quantity = get(type);
      if(quantity == null){
        put(type, 1);
      } else {
        put(type, quantity + 1);
      }
    }
  }

  // 演示經由過程Class.isInstance()統計對象類型
  public static void countShapes(ShapeCreator creator){
    ShapeCounter counter = new ShapeCounter();
    for(Shape shape : creator.createArray(20)){
      for(Class<? extends Shape> cls : shapeTypes){
        if(cls.isInstance(shape)){
          counter.count(cls.getSimpleName());
        }
      }
    }
    System.out.println(counter);
  }

  public static void main(String[] args){
    countShapes(new ForNameCreator());
  }
}

如今生成器有了兩種完成,我們在這裡可以添加一層外不雅,設置默許的完成方法:

/**
 * 如今生成器有了兩種完成,我們在這裡添加一層外不雅,設置默許的完成方法
 */
public class Shapes {

  public static final ShapeCreator creator =
      new LiteralCreator();
  public static Shape randomShape(){
    return creator.randomShape();
  }
  public static Shape[] createArray(int size){
    return creator.createArray(size);
  }
  public static ArrayList<Shape> arrayList(int size){
    return creator.arrayList(size);
  }
}

3.4、instanceof與Class的等價性:
instanceof和isInstance()生成的成果完整一樣,堅持了類型的概念,斷定能否一個類或許是這個類的派生類。

equals()與==也是一樣的,而應用這個比擬現實的Class對象,就沒有斟酌繼續。

System.out.println(new Circle() instanceof Circle); // true
System.out.println(Shape.class.isInstance(new Circle())); // true
System.out.println((new Circle()).getClass() == Circle.class); // true
System.out.println((new Circle().getClass()).equals(Shape.class)); // false

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved