当看到比较有难度的题目时,就忍不住想复制粘贴过来,以供回头再学习。
参考博客:https://blog.csdn.net/lihengjing1968/article/details/50883440
参考博客:https://blog.csdn.net/ypt523/article/details/79598289
参考博客:https://www.cnblogs.com/TalkWithWorld/p/5641169.html
参考博客:https://blog.csdn.net/zhangting19921121/article/details/87185665
示例5参考博客:https://blog.csdn.net/cauchyweierstrass/article/details/48943077
示例6参考博客:https://www.cnblogs.com/jianxia612/articles/1255837.html
下面给出几个示例,见到难题就忍不住ctrl c和ctrl v.
示例1:
public class A {
public int a = 0;
public void fun(){
System.out.println("-----A-----");
}
}
public class B extends A{
public int a = 1;
public void fun(){
System.out.println("-----B-----");
}
public static void main(String[] args){
A classA = new B();
System.out.println(classA.a);
classA.fun();
}
}
//输出的结果:
0
---------B--------
多态小结:
变量多态看左边,
方法多态看右边,
静态多态看左边。(静态方法只能用静态方法重写)
关于静态方法只能用静态方法重写的说明:(静态方法称为重写不准确)
另一种记法:
分类:运行时多态和编译时多态(除了重写都看左侧类型)
1)运行时多态:方法重写(看右侧的类型)(重写永远是针对实例方法,其他都不叫重写)
2) 编译时多态:方法重载(先继承过来,再重载方法)。(看左侧的类型)
JAVA静态方法形式上可以重写,但从本质上来说不是JAVA的重写。因为静态方法只与类相关,不与具体实现相关,声明的是什么类,则引用相应类的静态方法(本来静态无需声明,可以直接引用),看下例子:
示例2:
class Base{
static void a( ){System.out.println("A"); }
void b( ){System.out.println("B"); }
}
public class Inherit extends Base{
static void a( ){System.out.println("C"); }
void b( ){System.out.println("D"); }
public static void main(String args[]){
Base b=new Base();
Base c=new Inherit();
b.a();
b.b();
c.a();
c.b();
}
}
以上输出的结果是: A
B
A
D
非静态方法 按重写规则调用相应的类实现方法,而静态方法只与类相关。
所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。
专业术语有严格的含义,用语要准确."重写"只能适用于实例方法.不能用于静态方法.对于静态方法,只能隐藏(刚才的例子可以重写那只是形式上的 ,并不满足多态的特征,所以严格说不是重写)。
静态方法的调用不需要实例化吧.. 不实例化也就不能用多态了,也就没有所谓的父类引用指向子类实例.因为不能实例化 也就没有机会去指向子类的实例。所以也就不存在多态了。
示例3:
这道题中,关键点在于说,父类构造函数调用了在子类中重写的函数之后,在new子类对象的时候,会执行子类中重写的函数,而不会执行父类中原有的函数。比如这题的setValue函数。为什么???
参考博客:https://blog.csdn.net/Bettarwang/article/details/26160183
博客指出:父类构造方法中调用子类重写的方法时,涉及到编译时类型和运行时类型的问题,new子类对象的时候,首先调用父类构造函数,编译时类型是父类,但是运行时编程了类型是子类,因此调用的是子类重写的方法,而不是父类的方法。在写程序时,父类构造方法尽量不要调用其他方法,如果要调用,不能调用被子类重写的方法,否则会出现意想不到的错误,为了避免这种情况,不得已调用方法时,要么调用父类的私有方法,要么调用fianl方法,因为他们都是不能被子类重写的方法。
此外,在父类构造方法中调用子类重写方法时,还涉及到成员变量的初始化和赋值顺序问题,这个要研究底层的jvm原理。
示例4:感觉有难度
public class Test {
// 则下面测试类中的输出结果分别是什么?
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println(a1.show(b));
System.out.println(a1.show(c));
System.out.println(a1.show(d));
System.out.println("============");
System.out.println(a2.show(b));
System.out.println(a2.show(c));
System.out.println(a2.show(d));
System.out.println("============");
System.out.println(b.show(b));
System.out.println(b.show(c));
System.out.println(b.show(d)); //这里调用b.show(d)的时候,实际上b中是有继承A中的show(D d)放方法的,因此,一定会执行这个方法,而不是执行show(B b)的方法。
}
}
class A {
public String show(D d) {
return "A and D";
}
public String show(A a) {
return "A and A";
}
}
class B extends A {
public String show(B b) {
return "B and B";
}
@Override
public String show(A a) {
return "B and A";
}
}
class C extends B {}
class D extends B {}
//输出结果:想想为什么???
A and A
A and A
A and D
============ //优先调用最短的继承路径
B and A (有挑战哦) 为什么??? 这里不是调用show(B b)这个方法,原因就是该方法不是对父类show方法的重写,类型不一样,多态是调用子类重写的那个方法
B and A (有挑战哦) 为什么????
A and D //这里调用b.show(d)的时候,实际上b中是有继承A中的show(D d)放方法的,因此,一定会执行这个方法,而不是执行show(B b)的方法。
============ //对外类型会影响调用哦
B and B
B and B
A and D
示例5:有点难了
public class App {
public static void main(String[] args)throws Exception{
Sub b=new Sub();
b.callName(); //这一步是多态的 使用,很好理解
}
}
class Base{
private String baseName="base";
public Base(){
System.out.println("hellobase");
callName();
}
public void callName(){
System.out.println(baseName);
}
}
class Sub extends Base{
private String baseName="sub";
public void callName(){
System.out.println("hello");
System.out.println(baseName);
}
}
public static void main(String[] args ) {
Base b = new Sub();
}
}
输出是null 为什么???
Base b = new Sub();先初始化父类,然后调用子类的callName方法时子类的属性还没有初始化执行代码,所以打印的是null.
(经过个人的调试,终于发现了构造类时 变量的初始化顺序的秘密)
为了方便,本人重新写了一份代码,下面记录我的代码执行顺序:
1 public class ForTest {
public static void main(String[] args){
2 A b=new B(); //加上断点
}
}
class A{
3 int a=3; //加上断点
public A(){
//默认super()
4 System.out.println(a); //加上断点
5 method();
}
7 public void method(){
6 System.out.println(a);
}
}
class B extends A{
8 int a=2; //加上断点
public B(){
9 System.out.println("hello");
10 System.out.println(a);
}
public void method(){
11 System.out.println(a); //加上断点
}
}
图解:
从上一步到下一步都是点击step over:
step 1:进入调试 代码在 行2 处
step 2: 代码在行3 处。
想一下为什么一下就到了父类A中的变量赋值处?实际上在new子类的时候,调用了父类A的构造函数,你所看到的是一下子就到了父类A中的变量初始化这儿,也就是说变量在new一个类对象的时候,先执行的变量初始化的,再执行构造函数,如果仅仅是这样记忆,又会产生疑惑,你肯定会想,那这里在从父类构造函数中调用子类的callName的时候,肯定能打印出a=2 这个值啊,但是实际上打印的是0,也就是说此时的a还是默认初始值0,那难道上面说的new对象的时候 先执行变量初始化是错的???实际上,一般情况下,不用考虑父类构造函数调用子类重写的方法的时候,这样记忆确实是对的,但是原理是什么,原理这样是错的,原理上,并不是初始化先执行,下面给出我的分析:
根本原因在于,这里忘了分析最最关键的一步,实际上,new类对象的时候的时候,最先执行的还是构造函数,这里执行的一定是构造函数的第一步super()函数,它去首先构造了父类的对象,所有类都是Object的子类,所以会一直向上构造父类,知道Object,当父类构造完毕,super执行完毕,再返回本类中,从代码块,变量的初始化开始执行,这里要注意,此时,本类构造函数super()函数执行完毕,并没有继续执行构造函数中super()函数下面的语句,而是去执行了变量的初始化,然后当构造代码块和变量初始化完毕,再继续执行构造函数super()以下的语句,所以导致一种假象,我们看起来就是先执行了构造代码块和变量的初始化,最后再执行构造函数,实际上是第一步执行了构造函数,但是只执行了super(),它去构造父类对象去了。
为了验证我上面的分析,在new一个对象的时候,我们可以将super()函数显示写在构造函数中,再打上断点,你会发现,一定是先执行super()函数(如果该类父类不是Object,那么就会走到父类的构造函数,如果该类的父类是Object,也会走到父类Object,只不过Object是一种特殊的类,与jvm底层相关),然后等父类构造完毕之后,才会执行变量的初始化。
step3: 到达4 ,此时打印a,值为3
step 4 :到达5,
step 5: 到达11 ,这里父类构造函数中调用的重写方法,是子类的,为什么?看示例3后面的解析。注意,此时a的输出是0,为什么??经过step2的分析可知:此时父类对象还没有构造完成,也就是说B中构造函数中的super()还没有执行完,此时,并不会进行B中变量的初始化,所以a是默认的初始值0.
step 6: 到达8 ,此时,才会对变量a进行赋值,B的父类已经构造完毕。
step7: 到达9 ,step7和step8是执行构造函数中super剩下的步骤。
step8: 到达10
step 9:到达2 ,至此,新对象B的构造过程才算是结束
示例6:指出下面的程序错误的地方,说明原因。答案看链接
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball = new Ball("PingPang");
}
class Ball implements Rollable {
private String name;
public String getName() {
return name;
}
public Ball(String name) {
this.name = name;
}
public void play() {
ball = new Ball("Football");
System.out.println(ball.getName());
}
}