1.RTTI(运行时识别一个对象的类型)
- 动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
- 运行时类型信息使你能够在程序运行时发现和使用(比如对象的具体类)类型信息。
- RTTI主要有两种形式
- 传统的RTTI一种是在编译时知道了所有的类型信息,这属于静态绑定。
- 另一个种是“反射机制”,允许我们在运行时发现和使用类(对象的具体类)的信息。这属于动态绑定(多态、运行时绑定)。
- 面向对象的基本目的是:让代码只操纵对基类的引用,这样即使(从shape)派生出一个新类来也不会对原来的代码产生影响。
注意:这个例子中的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<Shape> shapeList = Arrays.asList( new Circle(),new Square(), new Triangle()); for(Shape shape : shapeList) /** * 因为泛化的Shape动态绑定了draw()方法,其子类型也会覆盖此方法,也可以产生正确的行为。 * 多态,运行时识别对象的类型,调用相应方法 */ shape.draw(); } } /** Circle.draw() Square.draw() Triangle.draw() */
- List中放入对象会把Shape对象的具体类型丢失。也就是说进入的都是Shape
- 当从List中取出元素时:这种容器——实际上会将所有的事物都当做Object持有——然后自动将结果转型为Shape.
在Java中,所有的类型转换都是在运行时进行正确检查的。RTTI:在运行时,识别一个对象的类型(然后在将泛化的类型引用转化为具体的类型)。 - 这个例子中:首先 Objiet转为Shape,因为只知道List<Shape>保存的都是Shape,在编译时由容器和java泛型系统来强制确保这一点;而在运行时,有类型转换操作来确保这一点。
- 接下来就是多态机制,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的标准字段。
- 当使用.class来创建对Class对象的引用时,不会自动初始化该Class对象。为了使用类而做的准备工作实际包含三个步骤,而.class不会走带三个步骤“初始化”
加载:由类加载器执行。查找字节码文件中的字节码(.class文件中)并从这些字节码中创建一个Class对象。链接:在链接阶段将验证类中的字节码,为静态域分配存储空间(内存),如果必需,将解析这个类创建的对其他类的所有引用。初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。【初始化存储空间】
初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非常数静态域进行首次引用时才执行:
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 */
- 初始化有效地实现了尽可能的“惰性”。.class语法获得对
类的引用不会引发初始化,为了产生Class引用,Class.forName()立即进入初始化。
- 如果一个static final值是“编译器常量”(有可能不是 如Initable.staticFinal2),这个值不需对Initable类进行初始化就可以被获取。
- 如果一个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<Integer> genericIntClass = int.class;//使用泛型限定的Class引用,只能指向指定的引用的Class对象 // genericIntClass = double.class;//这里会报错,因为使用了泛型进行限定 //普通的类引用不会产生警告信息,可以指向任何其他Class对象 intClass = double.class; //使用通配符来指定此泛型的限定范围 Class<? extends Number> genericNumberClass = int.class; } }
- 通过使用泛型语法,可以让编译器强制执行额外的类型检查。使用通配符可以稍微放松一些这种限制
通配符?表示“任何事物”。
Class<?>优于平凡的Class,即便他们是等价的,Class<?>表示使用了一个非具体的类引用,就选择了非具体的版本。Class<?> intClass = int.class;
- 限定为某种类型或该类型的任何子类型,使用?与extends关键字相结合,创建一个范围。
//使用通配符来指定此泛型的限定范围 Class<? extends Number> genericNumberClass = int.class;
- 向Class引用添加泛型语法仅仅是为了提供编译期类型检查,使用普通的Class引用,如果犯了错误,直到运行时才会发现,显得很不方便。
当你将泛型语法用于Class对象时,newInstance()将返回改对象的确切类型,而不仅仅是基本的object(普通的Class引用)class CountedInteger { private static long counter; private final long id = counter++; public String toString() {return Long.toString(id);} } public class FilledList<T> { public static void main(String[] args) { // CountedInteger类必须有一个无参构造器 Class<CountedInteger> type = CountedInteger.class; try { //这个类必须假设与它一同工作的任何类型都有一个某人的构造器(无参构造器),否则抛出异常 CountedInteger obj = type.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } }
//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<FancyToy> ftClass = FancyToy.class; FancyToy fancyToy = ftClass.newInstance(); // Class<Toy> up2 = ftClass.getSuperclass();//这样编译器会报错 //?代表某个类 他是FancyToy的超类,而不会接受Class<Toy> up2这样的声明 Class<? super FancyToy> 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<House> houseType = House.class;//先获得目标转型的Class引用 House h = houseType.cast(b);//使用cast进行转型的语法 h = (House) b;//直接转型的语法 } }
- cast()方法转型比普通的转型语法多了很多额外的工作,它对于普通转型语法不能转型的情况非常有用
- Class.asSubclass;允许将一个类对象(Class)转型为更加具体的类型。
3.类型转型前先做检查
- RTTI形式
- 传统的类型转换,如“(Shape)”,由RTTI确保类型转换的正确性,如果执行错误的类型转换,就会抛出一个ClassCastException异常
- 类型安全的向下转型(强制类型转换),类层次结构图,由Shape转化成Circle是向下的,所以是向下转型(显式的,因为编译器不知道你要转换成什么类型)。
- 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<House> houseType = House.class; Class<Building> 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 ++++++++++++++++++++++++++++++++++++++++++++++ */