首先纪念一下这是第五十篇博客了,也不知道朕的草庐有多少人会光顾,就当做是写给自己看看自己这些年来做的事吧。之前写的博客大多是算法相关的或者是开源框架相关的,大体来说不是笔记就是解题答案,并不能算是“博客”吧。今天从一道面试题里面,探究一下面向对象会坑人的一些地方。
- 追本溯源
一天,笔者来到一家公司,丢给我一份笔试,看了一眼,有道题十分特别。
public class ClassA{ public ClassA(){ method(); } public void method(){ System.out.println("in class A"); } } public class ClassB extends ClassA{public void method(){ System.out.println("in class B"); }
public static void main(String[] args){
new ClassB();
} }
问最后会输出什么?
笔者思考了一下,好像被触碰到了知识的禁区......我随便一想,构造函数好像不会继承吧,然后就写了不输出。
回来自己写了一段代码,输出了重写方法的内容……瞬间觉得自己是不是基础被人击穿了……
后来查了资料了解到,新建子类对象首先会新建父类对象,新建父类对象的时候调用构造方法,里面调用了被重写的method,所以输出了“in class B”。
- 山穷水复
浅尝辄止是人类的共性,工程师是反人类特性的征服者,学习Java的人肯定听过自动转型这个东西吧,新建子类对象的时候引用父类对象。范式:父类 变量 = new 子类();它和直接引用子类创建对象一样么?有什么区别。
从输出上看,依然是新建了父类对象之后再新建子类对象。然而,有些父类没有的方法,子类扩展的,调用的子类方法就会编译出错。而且如果子类对象和父类对象的变量命一致,调用的返回的是父类的对象。
编译出错了,因为在引用为父类的对象中,搜索不到子类的扩展方法,在调用的时候,会调用父类的方法并检查是否被子类重写。
输出了父类的变量,如果我把引用改成子类 b = new 子类()这样,他就会输出子类的变量,因为变量被继承了,但是如果重新赋值的话,它按照的还是顶级引用搜索变量。
每一个对象,都会有Class属性,各位觉得,这个引用了父类的变量,Class是指向哪一个类的呢?
我这样理解,因为他的引用是父类对象,但是新建在堆内存的对象是子类,所以对象的Class应该是和堆内存中的子类对应的。
依然有变量的强制转型,那类也可以强制转型,我们从数学上理解是这样的,如果A是B的子集,那么A一定能推断出B,那么B可能推断得出A,这样一个逻辑关系。那么对应父类和子类,那么子类是大的一方,父类是小的一方,所以从父类转型到子类是必然成功的,为什么?因为子类可以产生基因突变,而且子类会继承父类的基因。。。这样的歪理可以接受么。子类可以扩展方法,所以从多态性来讲,子类属于大的一方。
test9 demo = (test9)new test9F();
test9F demo = (test9F)new test9();
然而事实上,第一行代码会抛出异常,类型转换异常。为啥?这就算是面向对象的猫腻了吧,我也无法解释,待我寻找一下答案。
所有的类是不是都继承自Object?那转换成Object为什么会正常。
笔者并不能找到能完我集合论说法的解释,无疾而终。
- 蓦然回首
整天在讨论多态,静态方法有没有多态?
test9F demo = new test9(); demo.methodStatic();
test9 demo = new test9(); demo.methodStatic();
上述两段代码,哪个顶级引用就会调用哪个引用的静态方法。第一个引用的是父类,第二个引用的是子类。会划小黄线的原因,就是无论你调用对象还是调用类它的静态方法都是不变的,编译器的规范是类调用。
而且两个类使用共同名字的静态方法,IDE也没有重写的字样,所以静态方法并不会被重写 so do 构造方法。