最近面试碰到一道父类子类的面试题:
public class SuperClass {
public int a;
public SuperClass(){
a=1;
System.out.println("a is"+a);
}
public int getA(){
return a;
}
}
public class SubClass extends SuperClass {
public int a =2;
public SubClass(){
System.out.println("a is"+a);
}
public int getA(){
return a;
}
public static void main(String[] args) {
SuperClass aClass = new SuperClass();
SuperClass bClass = new SubClass();
System.out.println("num1 is "+(aClass.a+bClass.a));
System.out.println("num2 is "+(aClass.getA()+bClass.getA()));
System.out.println("num3 is "+(aClass.a+bClass.getA()));
System.out.println("num4 is "+(aClass.getA()+bClass.a));
}
}
问输出的结果?
这题一看就知道输出的四个结果肯定不同。
本身涉及到继承的知识,也就是重写的知识,不过这部分比较好理解,就是子类继承于父类,重写的父类的方法,那么调用的时候就调用子类自己的方法。
但是这道题恶心的地方在于bClass采用了父类声明子类实例化,那么bClass里面的参数到底是什么样的呢?
首先我们知道java继承中,通过继承,子类可以得到父类除构造函数以外所有的成员(包括成员变量和成员函数),但是要注意得到并不等于可以随便使用。子类能否使用(访问)父类的成员由父类成员的属性决定。
对于子类对象来说,在调用自身类的构造函数之前会先调用父类的构造函数。第一种情况:若子类构造函数向父类构造函数传递参数(通过super()在构造函数第一行实现),那么会自动调用父类有对应参数的构造函数;第二种情况:子类构造函数没有参数传递给父类构造函数,这种情况下系统会隐式调用父类无参数的构造函数,倘若父类没有任何构造函数,编译正常;若父类有带参数的构造函数而没有无参数的构造函数,编译时会发生错误。
对刚才的调用情况小结一下:无论如何,子类构造函数都会先调用父类构造函数,如果要传参数,通过super()实现;如果不传参数,那父类要么有一个无参数的构造函数,要么一个构造函数都没有,不然就会编译出错,通俗一点说就是我可以接受你啥也没有,但是不能有其他的但是没我要的
所以两个在二句实例化的时候,会先调用父类的构造方法。那么也就是说,结果中会先显示父类构造方法里面的打印数据。
由于bClass是通过父类声明子类实例化的方式定义的。
bClass这个实例是子类的,但是因为你声明时是用父类声明的,所以你用正常的办法访问不到子类自己的成员,只能访问到从父类继承来的成员。
在子类中重写父类中方法时,实例化父类调用该方法,执行时调用的是子类中重写的方法;
所以bClass.a=1,bClass.getA()返回2
所以最后的结果是:
我在代码中加入打印各项信息:
public class SuperClass {
public int a;
private int c;
protected int d;
public SuperClass(){
a=1;
c=5;
System.out.println("a is"+a);
}
public int getA(){
return a;
}
public int getC() {
return c;
}
public int getD() {
return d;
}
}
public class SubClass extends SuperClass {
public int a =2;
private int c=6;
protected int d =9;
public SubClass(){
System.out.println("a is"+a);
}
public int getA(){
return a;
}
public int getC() {
return c;
}
public int getD() {
return d;
}
public static void main(String[] args) {
SuperClass aClass = new SuperClass();
SuperClass bClass = new SubClass();
System.out.println("num1 is "+(aClass.a+bClass.a));
System.out.println("num2 is "+(aClass.getA()+bClass.getA()));
System.out.println("num3 is "+(aClass.a+bClass.getA()));
System.out.println("num4 is "+(aClass.getA()+bClass.a));
System.out.println("b.a is "+bClass.a);
System.out.println("b.getA is "+bClass.getA());
System.out.println("b.getC is "+bClass.getC());
System.out.println("b.d is "+bClass.d);
System.out.println("b.getD is "+bClass.getD());
System.out.println("a.a is "+aClass.a);
System.out.println("a.getA is "+aClass.getA());
SubClass cClass = new SubClass();
System.out.println("c.a is "+cClass.a);
System.out.println("c.getA is "+cClass.getA());
}
}
打印的结果:
这里面会涉及到一个依赖倒置原则(DIP)
依赖倒置原则,DIP,Dependency Inverse Principle DIP的表述是:
1、高层模块不应该依赖于低层模块,二者都应该依赖于抽象。 2、抽象不应该依赖于细节,细节应该依赖于抽象。 这里说的“依赖”是使用的意思,如果你调用了一个类的一个方法,就是依赖这个类,如果你直接调用这个类的方法,就是依赖细节,细节就是具体的类,但如果你调用的是它父类或者接口的方法,就是依赖抽象, 所以DIP说白了就是不要直接使用具体的子类,而是用它的父类的引用去调用子类的方法,这样就是依赖于抽象,不依赖具体。
其实简单的说,DIP的好处就是解除耦合,用了DIP之后,调用者就不知道被调用的代码是什么,因为调用者拿到的是父类的引用,它不知道具体指向哪个子类的实例,更不知道要调用的方法具体是什么,所以,被调用代码被偷偷换成另一个子类之后,调用者不需要做任何修改, 这就是解耦了。