Java中除了static方法和final方法(private方法本质上属于final方法,由于不能被子类访问)以外,其它全部的方法都是动态绑定,这意味着一般状况下,咱们没必要断定是否应该进行动态绑定—它会自动发生。html
class StaticSuper { public static String staticGet() { return "Base staticGet()"; } public String dynamicGet() { return "Base dynamicGet()"; } } class StaticSub extends StaticSuper { public static String staticGet() { return "Derived staticGet()"; } public String dynamicGet() { return "Derived dynamicGet()"; } } public class StaticPolymorphism { public static void main(String[] args) { StaticSuper sup = new StaticSub(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); } }
Base staticGet()
Derived dynamicGet()java
构造函数并不具备多态性,它们其实是static方法,只不过该static声明是隐式的。所以,构造函数不可以被override。程序员
在父类构造函数内部调用具备多态行为的函数将致使没法预测的结果,由于此时子类对象还没初始化,此时调用子类方法不会获得咱们想要的结果。算法
class Glyph { void draw() { System.out.println("Glyph.draw()"); } Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; System.out.println("RoundGlyph.RoundGlyph(). radius = " + radius); } void draw() { System.out.println("RoundGlyph.draw(). radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } }
输出:编程
Glyph() before draw()
RoundGlyph.draw(). radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(). radius = 5设计模式
为何会这样输出?这就要明确掌握Java中构造函数的调用顺序:数组
(1)在其余任何事物发生以前,将分配给对象的存储空间初始化成二进制0;
(2)调用基类构造函数。从根开始递归下去,由于多态性此时调用子类覆盖后的draw()方法(要在调用RoundGlyph构造函数以前调用),因为步骤1的缘故,咱们此时会发现radius的值为0;
(3)按声明顺序调用成员的初始化方法;
(4)最后调用子类的构造函数。安全
只有非private方法才能够被覆盖,可是还须要密切注意覆盖private方法的现象,这时虽然编译器不会报错,可是也不会按照咱们所指望的来执行,即覆盖private方法对子类来讲是一个新的方法而非重载方法。所以,在子类中,新方法名最好不要与基类的private方法采起同一名字(虽然不要紧,但容易误解,觉得可以覆盖基类的private方法)。cookie
Java类中属性域的访问操做都由编译器解析,所以不是多态的。父类和子类的同名属性都会分配不一样的存储空间,以下:网络
// Direct field access is determined at compile time.
class Super { public int field = 0; public int getField() { return field; } } class Sub extends Super { public int field = 1; public int getField() { return field; } public int getSuperField() { return super.field; } } public class FieldAccess { public static void main(String[] args) { Super sup = new Sub(); System.out.println("sup.filed = " + sup.field + ", sup.getField() = " + sup.getField()); Sub sub = new Sub(); System.out.println("sub.filed = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField()); } }
输出:
sup.filed = 0, sup.getField() = 1
sub.filed = 1, sub.getField() = 1, sub.getSuperField() = 0
Sub子类实际上包含了两个称为field的域,然而在引用Sub中的field时所产生的默认域并不是Super版本的field域,所以为了获得Super.field,必须显式地指明super.field。
is-a关系属于纯继承,即只有在基类中已经创建的方法才能够在子类中被覆盖,以下图所示:
基类和子类有着彻底相同的接口,这样向上转型时永远不须要知道正在处理的对象的确切类型,这经过多态来实现。
is-like-a关系:子类扩展了基类接口。它有着相同的基本接口,可是他还具备由额外方法实现的其余特性。
缺点就是子类中接口的扩展部分不能被基类访问,所以一旦向上转型,就不能调用那些新方法。
使用方式
Java是如何让咱们在运行时识别对象和类的信息的,主要有两种方式(还有辅助的第三种方式,见下描述):
Shape s = (Shape)s1;
Class.forName()
。instanceof
,它返回一个bool值,它保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”。而若是用==或equals比较实际的Class对象,就没有考虑继承—它或者是这个确切的类型,或者不是。工做原理
要理解RTTI在Java中的工做原理,首先必须知道类型信息在运行时是如何表示的,这项工做是由称为Class对象
的特殊对象完成的,它包含了与类有关的信息。Java送Class对象来执行其RTTI,使用类加载器的子系统实现。
不管什么时候,只要你想在运行时使用类型信息,就必须首先得到对恰当的Class对象的引用,获取方式有三种:
(1)若是你没有持有该类型的对象,则Class.forName()
就是实现此功能的便捷途,由于它不须要对象信息;
(2)若是你已经拥有了一个感兴趣的类型的对象,那就能够经过调用getClass()
方法来获取Class引用了,它将返回表示该对象的实际类型的Class引用。Class包含颇有有用的方法,好比:
package rtti; 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 RTTITest { static void printInfo(Class cc) { System.out.println("Class name: " + cc.getName() + ", is interface? [" + cc.isInterface() + "]"); System.out.println("Simple name: " + cc.getSimpleName()); System.out.println("Canonical name: " + cc.getCanonicalName()); } public static void main(String[] args) { Class c = null; try { c = Class.forName("rtti.FancyToy"); // 必须是全限定名(包名+类名) } catch(ClassNotFoundException e) { System.out.println("Can't find FancyToy"); System.exit(1); } printInfo(c); for(Class face : c.getInterfaces()) { printInfo(face); } Class up = c.getSuperclass(); Object obj = null; try { // Requires default constructor. obj = up.newInstance(); } catch (InstantiationException e) { System.out.println("Can't Instantiate"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("Can't access"); System.exit(1); } printInfo(obj.getClass()); } }
输出:
Class name: rtti.FancyToy, is interface? [false]
Simple name: FancyToy
Canonical name: rtti.FancyToy
Class name: rtti.HasBatteries, is interface? [true]
Simple name: HasBatteries
Canonical name: rtti.HasBatteries
Class name: rtti.WaterProof, is interface? [true]
Simple name: WaterProof
Canonical name: rtti.WaterProof
Class name: rtti.Shoots, is interface? [true]
Simple name: Shoots
Canonical name: rtti.Shoots
Class name: rtti.Toy, is interface? [false]
Simple name: Toy
Canonical name: rtti.Toy
(3)Java还提供了另外一种方法来生成对Class对象的引用,即便用类字面常量。好比上面的就像这样:FancyToy.class;
来引用。
这样作不只更简单,并且更安全,由于它在编译时就会受到检查(所以不须要置于try语句块中),而且它根除了对forName方法的引用,因此也更高效。类字面常量不只能够应用于普通的类,也能够应用于接口、数组以及基本数据类型。
注意:当使用“.class”来建立对Class对象的引用时,不会自动地初始化该Class对象,初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非final静态域(注意final静态域不会触发初始化操做)进行首次引用时才执行:。而使用Class.forName时会自动的初始化。
为了使用类而作的准备工做实际包含三个步骤:
- 加载:由类加载器执行。查找字节码,并从这些字节码中建立一个Class对象
- 连接:验证类中的字节码,为静态域分配存储空间,而且若是必需的话,将解析这个类建立的对其余类的全部引用。
- 初始化:若是该类具备超类,则对其初始化,执行静态初始化器和静态初始化块。
这一点很是重要,下面经过一个实例来讲明这二者的区别:
package rtti; import java.util.Random; 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) { // Does not trigger initialization Class initable = Initable.class; System.out.println("After creating Initable ref"); // Does not trigger initialization System.out.println(Initable.staticFinal); // Does trigger initialization(rand() is static method) System.out.println(Initable.staticFinal2); // Does trigger initialization(not final) System.out.println(Initable2.staticNonFinal); try { Class initable3 = Class.forName("rtti.Initable3"); } catch (ClassNotFoundException e) { System.out.println("Can't find Initable3"); System.exit(1); } System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } }
输出:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
能够突破这个限制吗?是的,突破它的就是反射机制。Class
类与java.lang.reflect
类库一块儿对反射的概念进行了支持,该类库包含了Field
、Method
以及Constructor
类(每一个类都实现了Member
接口)。这些类型的对象是由JVM在运行时建立的,用以表示未知类里对应的成员。这样你就可使用Constructor
建立新的对象,用get()/set()
方法读取和修改与Field
对象关联的字段,用invoke()
方法调用与Method
对象关联的方法。另外,还能够调用getFields()、getMethods()和getConstructors()
等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被彻底肯定下来,而在编译时不须要知道任何事情。
####反射与RTTI的区别
当经过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪一个特定的类(就像RTTI那样),在用它作其余事情以前必须先加载那个类的Class
对象,所以,那个类的.class
文件对于JVM来讲必须是可获取的:要么在本地机器上,要么能够经过网络取得。因此RTTI与反射之间真正的区别只在于:对RTTI来讲,编译器在编译时打开和检查.class文件(也就是能够用普通方法调用对象的全部方法);而对于反射机制来讲,.class文件在编译时是不可获取的,因此是在运行时打开和检查.class文件。
下面的例子是用反射机制打印出一个类的全部方法(包括在基类中定义的方法):
package typeinfo; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.regex.Pattern; // Using reflection to show all the methods of a class. // even if the methods are defined in the base class. public class ShowMethods { private static String usage = "usage: \n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or: \n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors(); if(args.length == 1) { for(Method method : methods) { System.out.println(p.matcher(method.toString()).replaceAll("")); } for(Constructor ctor : ctors) { System.out.println(p.matcher(ctor.toString()).replaceAll("")); } lines = methods.length + ctors.length; } else { for(Method method : methods) { if(method.toString().indexOf(args[1])