第十四章 类型信息RTTI Class instanceof isInstance

1.RTTI(运行时识别一个对象的类型)

  • 动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法
  • 运行时类型信息使你能够在程序运行时发现和使用(比如对象的具体类)类型信息
  • RTTI主要有两种形式
    1. 传统的RTTI一种是在编译时知道了所有的类型信息,这属于静态绑定。
    2. 另一个种是“反射机制”,允许我们在运行时发现和使用类(对象的具体类)的信息。这属于动态绑定(多态、运行时绑定)。
  • 面向对象的基本目的是:让代码只操纵对基类的引用,这样即使(从shape)派生出一个新类来也不会对原来的代码产生影响。
    第十四章 类型信息RTTI Class instanceof isInstance_第1张图片
    注意:这个例子中的Shape接口中动态绑定了draw()方法,目的是让客户端程序员使用泛化的Shape引用来调用draw()方法。
    draw()方法在所有派生类都会被覆盖,并且由于它是动态绑定的,所以即使通过泛化的Shape引用来调用,也能产生正确的行为。这就是多态。
  • 如果某个对象出现在字符串表达式中,其toString()方法会被自动调用。
  • /**
     * 一个泛化的Shape 由个别到普通的过程
     */
    abstract class Shape{
    	void draw(){System.out.println(this + ".draw()");}
    	abstract public String toString();
    }
    /**
     * 个别特殊的类型
     */
    class Circle extends Shape{
    	//toString()方法
    	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类型,
    		List shapeList = Arrays.asList(
    				new Circle(),new Square(), new Triangle());
    		for(Shape shape : shapeList)
    			/**
    			 * 因为泛化的Shape动态绑定了draw()方法,其子类型也会覆盖此方法,也可以产生正确的行为。
    			 * 多态,运行时识别对象的类型,调用相应方法
    			 */
    			shape.draw();
    	}
    }
    /**
    Circle.draw()
    Square.draw()
    Triangle.draw()
     */
    
    1. List中放入对象会把Shape对象的具体类型丢失。也就是说进入的都是Shape
    2. 当从List中取出元素时:这种容器——实际上会将所有的事物都当做Object持有——然后自动将结果转型为Shape.
      在Java中,所有的类型转换都是在运行时进行正确检查的。RTTI:在运行时,识别一个对象的类型(然后在将泛化的类型引用转化为具体的类型)。
    3. 这个例子中:首先 Objiet转为Shape,因为只知道List保存的都是Shape,在编译时由容器和java泛型系统来强制确保这一点;而在运行时,有类型转换操作来确保这一点。
    4. 接下来就是多态机制,Shape对象执行什么样的代码,是由引用所指向的具体对象所决定的,可以识别是因为多态。
  • 通常,你希望大部分代码尽可能少的了解对象的具体类型,而只与一个泛化类型表示。这样的代码更容易写,读,切更便于维护;设计也更容易实现、理解和改变。

  • 所以“多态”是面向对象的基本目标。

2.Class 对象

  • 当我们运行一个程序时,这个程序的所有类并不是一次都全部向虚拟机加载完成的,而是当程序运行到那一块使用到那个类(比如声明了一个对象或调用了一个静态的东西)时才会将这个类加载到jvm虚拟机当中去,在这个时候,对应的类也会生成一个Class的对象,它包含了这个类的一切信息(比你想象的要多)。
  • 类型信息在运行时有Class对象来表示,它包含了与类相关的有关信息。事实上,Class对象就是用来创建类的所有“常规”对象的。Java使用Class对象来执行其RTTI,即使正在执行的是类似转型的操作。
  • 类加载器子系统实际上可以包含一条类加载器链,但只有一个原生类加载器;它是JVM实现的一部分。原生类加载器加载的是所谓的可信类,包括Java API类,它们通常从本地磁盘加载。通常不需要添加额外的类加载器,但如果有特殊需求(如从网络下载的类),那么可以挂接额外的类加载器。
  • 类加载的过程:所有类都是在对其第一次使用时(static初始化是在类加载的时候进行的),动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个也证明构造器也是类的静态方法,即使构造器之前没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。
    因此:Java程序在运行之前并非被完全加载,而是在其各个部分必需时才会被加载的。
  • 类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时(实际上是字节码文件加载入内存,他们会接受验证,并且不包含不良代码。
  • 特别注意:一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

    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();//如果没有.class文件,会自动生成一个同名的.class文件。
    		System.out.println("After creating Candy");
    		try{
    			/**
    			 * Class对象和其他对象一样,我们可以获取并操作它的引用(类加载器)
    			 * Class的静态成员,得到这个名字的类,返回一个Class对象的引用
    			 * 
    			 * Class.forName("Gum")方法如果找不到要加载的类(.class文件)就会抛出异常
    			 */
    			Class.forName("Gum");
    		}catch (ClassNotFoundException e) {
    			//找不到类Gum,因为它还没有被第一次加载过(也就是说这个类的静态成员一次也没有引用过)
    			System.out.println("Couldn't find Gum");
    		}
    	}
    }
    
  • 无论何时,只要在运行时使用类型信息,就必须首先获得对恰当Class对象的引用。使用Class.forName()就可以实现。如果你有一个类的引用,可以通过getClass()获得该对象实际类型的Class引用。

    interface HasBatteries{}
    interface Waterproof{}
    interface Shoots{}
    
    class Toy{
    	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("类名: " + cc.getName() 
    		+ " 是否接口? [" + cc.isInterface() + "]");
    		System.out.println("简单类名: " + cc.getSimpleName());
    		System.out.println("规范名: " + cc.getCanonicalName());
    		System.out.println();
    	}
    	public static void main(String[] args) {
    		Class c = null;
    		try {
    			c = Class.forName("com.yue.rtti.FancyToy");//获取一个Class对象
    		} catch (ClassNotFoundException e) {
    			System.out.println("Can't find FancyToy");
    			System.exit(1);
    		}
    		printInfo(c);//第一次输出信息
    		//getInterfaces会返回此Class对象所表示的类实现的所有接口 是数组形式 Class[]
    		System.out.println("以下是此类的全部接口");
    		for(Class face : c.getInterfaces())
    			printInfo(face);//逐条打印
    		System.out.println("以上是此类的全部的接口");
    		Class up = c.getSuperclass();//获取到此类的超类 即父类 基类
    		Object obj = null;
    		try {
    			obj = up.newInstance();//使用这个类的Class对象创建一个具体的对象,并赋予一个obj引用
    		} catch (InstantiationException e) {
    			System.out.println("Cannot instantite");
    			System.exit(1);
    		} catch (IllegalAccessException e) {
    			System.out.println("Cannot access");
    			System.exit(1);
    		}
    		printInfo(obj.getClass());//打印
    	}
    }
    /**
    类名: com.yue.rtti.FancyToy 是否接口? [false]
    简单类名: FancyToy
    规范名: com.yue.rtti.FancyToy
    
    以下是此类的全部接口
    类名: com.yue.rtti.HasBatteries 是否接口? [true]
    简单类名: HasBatteries
    规范名: com.yue.rtti.HasBatteries
    
    类名: com.yue.rtti.Waterproof 是否接口? [true]
    简单类名: Waterproof
    规范名: com.yue.rtti.Waterproof
    
    类名: com.yue.rtti.Shoots 是否接口? [true]
    简单类名: Shoots
    规范名: com.yue.rtti.Shoots
    
    以上是此类的全部的接口
    类名: com.yue.rtti.Toy 是否接口? [false]
    简单类名: Toy
    规范名: com.yue.rtti.Toy
    */
    

2.1.类字面常量.class

  • FancyToy.class这样也可以获得一个class对象。这样做简单安全,因为它在编译时就会受到检查。
    并且根除了forName()方法的调用,所以更高效。
  • 类字面常量不仅可以应用于普通类,也可应用于接口、数组以及基本数据类型
  • 对于基本类型的包装器类,还有一个TYPE的标准字段。
    第十四章 类型信息RTTI Class instanceof isInstance_第2张图片
  • 当使用.class来创建对Class对象的引用时,不会自动初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤,而.class不会走带三个步骤“初始化”
    1. 加载:由类加载器执行。查找字节码文件中的字节码(.class文件中)并从这些字节码中创建一个Class对象。
    2. 链接:在链接阶段将验证类中的字节码,为静态域分配存储空间(内存),如果必需,将解析这个类创建的对其他类的所有引用
    3. 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。【初始化存储空间】
      初始化被延迟到了对静态方法构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行:
      class Initable{
      	static final int saticFinal = 47;
      	static final int staticFinal2 = //注意这种情况也会被初始化的,虽然是final,但具有不确定性,非常数静态域。
      			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");
      	}
      }
      //有继承超类的情况,情况不特殊也不会被初始化
      class Initable4 extends Initable3{
      	static final int saticFinal = 123;
      	static {
      		System.out.println("Initializing Initable4");
      	}
      }
      public class ClassInitialization {
      	public static Random rand = new Random(47);
      	public static void main(String[] args) throws ClassNotFoundException {
      		//得到一个Class对象 这样不会先对其初始化,而是把初始化延迟到了第一次使用其静态域时。
      		Class initable = Initable.class;
      		System.out.println("After creating Initable ref");
      		System.out.println(Initable.saticFinal);
      		//仅仅是编译器常量还不足以保证不初始化
      		System.out.println(Initable.staticFinal2);
      		//直接使用一个类的静态域(他不是final的) 其他静态域也会被初始化
      		System.out.println(Initable2.staticNonFinal);
      		//另一种方式获取Class对象 这样会首先对其初始化
      		Class initable3 = Class.forName("com.yue.rtti.Initable3");
      		System.out.println("After creating Initable3 ref");
      		System.out.println(Initable3.staticNonFinal);
      		System.out.println("继承超类的情况:"+Initable4.saticFinal);
      	}
      }
      /**
      After creating Initable ref
      47
      Initializing Initable
      258
      Initializing Initable2
      147
      Initializing Initable3
      After creating Initable3 ref
      74
      继承超类的情况:123
       */ 
      
    4. 初始化有效地实现了尽可能的“惰性”。.class语法获得对的引用不会引发初始化,为了产生Class引用,Class.forName()立即进入初始化。
    5. 如果一个static final值是“编译器常量”(有可能不是 如Initable.staticFinal2),这个值不需对Initable类进行初始化就可以被获取。
    6. 如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个类分配存储空间)和初始化(初始化存储空间)。

2.2.泛化的Class引用(有特殊到一般的过程)

  • Class引用总是指向某个Class对象(包含一个类的所有信息,通过类加载器加载.class文件中的字节码而创建)的
    可以使用这个引用来创建类的实例(对象),并包含可作用域这些实例的所有方法代码。还包括该类的静态成员,因
    此,Class引用表示的就是它所指向的对象(Class类的一个对象)的确切类型(具体类型)。
  • 但是使用泛型语法进行限定还可以使它的类型更具体。如下:
    public class GenericClassReferences {
        public static void main(String[] args) {
            Class intClass = int.class;//普通的Class引用
            Class genericIntClass = int.class;//使用泛型限定的Class引用,只能指向指定的引用的Class对象
    //        genericIntClass = double.class;//这里会报错,因为使用了泛型进行限定
            //普通的类引用不会产生警告信息,可以指向任何其他Class对象
            intClass = double.class;
            //使用通配符来指定此泛型的限定范围 
            Class genericNumberClass = int.class;
        }
    } 
    
  • 通过使用泛型语法,可以让编译器强制执行额外的类型检查。使用通配符可以稍微放松一些这种限制
    通配符?表示“任何事物”。
    Class intClass = int.class;
    Class优于平凡的Class,即便他们是等价的,Class表示使用了一个非具体的类引用,就选择了非具体的版本。
  • 限定为某种类型或该类型的任何子类型,使用?与extends关键字相结合,创建一个范围。
    //使用通配符来指定此泛型的限定范围 
    		Class genericNumberClass = int.class;
  • 向Class引用添加泛型语法仅仅是为了提供编译期类型检查,使用普通的Class引用,如果犯了错误,直到运行时才会发现,显得很不方便。
  • class CountedInteger {
        private static long counter;
        private final long id = counter++;
        public String toString() {return Long.toString(id);}
    }
    
    public class FilledList {
        public static void main(String[] args) {
            // CountedInteger类必须有一个无参构造器
            Class type = CountedInteger.class;
            try {
                //这个类必须假设与它一同工作的任何类型都有一个某人的构造器(无参构造器),否则抛出异常
                CountedInteger obj = type.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    当你将泛型语法用于Class对象时,newInstance()将返回改对象的确切类型,而不仅仅是基本的object(普通的Class引用)
  • //class Toy{
    //	Toy() {}
    //	Toy(int i){}
    //}
    //class FancyToy extends Toy implements
    //HasBatteries , Waterproof, Shoots{
    //	FancyToy() {super(1);}
    //}
    public class GenericToyTest {
    	public static void main(String[] args) throws Exception {
    		Class ftClass = FancyToy.class;
    		FancyToy fancyToy = ftClass.newInstance();
    //		Class up2 = ftClass.getSuperclass();//这样编译器会报错
    		//?代表某个类 他是FancyToy的超类,而不会接受Class up2这样的声明
    		Class up = ftClass.getSuperclass();
    		Object obj = up.newInstance();
    	}
    }
    
    

2.3新的转型语法

  • class Building {}
    class House extends Building {}
    public class ClassCasts {
    	public static void main(String[] args) {
    		Building b = new House();
    		Class houseType = House.class;//先获得目标转型的Class引用
    		House h = houseType.cast(b);//使用cast进行转型的语法
    		h = (House) b;//直接转型的语法
    	}
    }
    
  • cast()方法转型比普通的转型语法多了很多额外的工作,它对于普通转型语法不能转型的情况非常有用
  • Class.asSubclass;允许将一个类对象(Class)转型为更加具体的类型

3.类型转型前先做检查

  • RTTI形式
    1. 传统的类型转换,如“(Shape)”,由RTTI确保类型转换的正确性,如果执行错误的类型转换,就会抛出一个ClassCastException异常
    2. 类型安全的向下转型(强制类型转换),类层次结构图,由Shape转化成Circle是向下的,所以是向下转型(显式的,因为编译器不知道你要转换成什么类型)。
    3. instanceof:检查对象是否从属于某个类
      		if (h instanceof Building) {
      			h = (House) b;
      		}
      

3.1动态的instanceof[Class.isInstance()]

  • Class.isInstance()方法提供了一种动态地测试对象的途径。检查某个对象是否从属于某个类。
    class Building {}
    class House extends Building {}
    public class ClassCasts {
    	public static void main(String[] args) {
    		Building b = new House();//这里引用的实际对象是一个House,它在运行时会识别这个类型RTTI
    		Class houseType = House.class;
    		Class buildType = Building.class;
    		House h = houseType.cast(b);//使用cast进行转型的语法
    		h = (House) b;//直接转型的语法
    		if (h instanceof Building) {
    			h = (House) b;
    			System.out.println("转型");
    		}
    		if (buildType.isInstance(b)) {//可以改写成这样
    			System.out.println(buildType.isInstance(b));
    		}
    	}
    }
    

3.2递归计数法

+++这是一种方法,根据不同情况会有不同的实现。

4.注册工厂

这个东西会在设计模式中说。

5.Instanceof与Class的等价性

  • 在查询类型信息的时候,以instanceof的形式(instanceof的形式或isInstance()的形式)与直接比较Class对象有一个很重要的差别。
    class Base{}
    class Derived extends Base{}
    public class FamilyVsExactType {
    	static void test(Object x){
    		System.out.println("测试x的类型 " + x.getClass());
    		System.out.println("x instanceof Base: " + (x instanceof Base));
    		System.out.println("x instanceof Derived: " + (x instanceof Derived));
    		System.out.println("Base.isInstance(x): " + Base.class.isInstance(x));
    		System.out.println("Derived.isInstance(x): " + Derived.class.isInstance(x));
    		System.out.println("下面的输出与上面对比");
    		System.out.println("x.getClass() == Base.class: " + (x.getClass() == Base.class));
    		System.out.println("x.getClass() == Derived.class: " +(x.getClass() == Derived.class));
    		System.out.println("x.getClass().equals(Base.class): " + (x.getClass().equals(Base.class)));
    		System.out.println("x.getClass().equals(Derived.class): " + (x.getClass().equals(Derived.class)));
    		System.out.println("++++++++++++++++++++++++++++++++++++++++++++++");
    	}
    	public static void main(String[] args) {
    		test(new Base());
    		test(new Derived());
    	}
    }
    /**
    测试x的类型 class com.yue.rtti.Base
    x instanceof Base: true
    x instanceof Derived: false
    Base.isInstance(x): true
    Derived.isInstance(x): false
    下面的输出与上面对比
    x.getClass() == Base.class: true
    x.getClass() == Derived.class: false
    x.getClass().equals(Base.class): true
    x.getClass().equals(Derived.class): false
    ++++++++++++++++++++++++++++++++++++++++++++++
    测试x的类型 class com.yue.rtti.Derived
    x instanceof Base: true
    x instanceof Derived: true
    Base.isInstance(x): true
    Derived.isInstance(x): true
    下面的输出与上面对比
    x.getClass() == Base.class: false
    x.getClass() == Derived.class: true
    x.getClass().equals(Base.class): false
    x.getClass().equals(Derived.class): true
    ++++++++++++++++++++++++++++++++++++++++++++++
     */
    
    



你可能感兴趣的:(Thinking,in,java,的学习总结)