对象转换概念

不同类型的基本数据类型变量之间可以进行类型转换(自动转换与强制转换),在Java中对于具有继承关系的对象类型也可以进行转换。Java允许父类类型的引用变量直接引用子类类型的对象。

上转型对象

假设,A类是B类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,称对象a是对象b的上转型对象。

1
A a = new B();

要点分析

子类新增

  • 上转型对象不能访问子类新增的数据域;不能直接访问子类新增的方法(子类中定义的覆盖、隐藏方法不算新增)。只有当对象类型强制转换为子类类型,才能进行相应的调用

Example5_10.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class  Monkey{
void crySpeak(String s) {
System.out.println(s);
}
void crawl(){
System.out.println("crawling");
}
}

class People extends Monkey {
int d =0; //子类新增的数据域
void computer(int a,int b) { //子类新增的方法
int c=a*b;
System.out.println(c);
}
@Override
void crySpeak(String s) {
System.out.println("***"+s+"***");
}
}

public class Example5_10 {
public static void main(String args[]) {
Monkey monkey = new People();
monkey.crySpeak("I love this game"); //调用子类,重写不算新增
monkey.crawl();
//monkey.computer(10,10); //上转型后,子类新增的方法失去
//System.out.println(monkey.d);//上转型后,子类新增的变量失去
People people = null;
if (monkey instanceof People)
// instanceof判断money是否为People类所创建的对象
people=(People)monkey; //把上转型对象强制转化为子类的对象
people.computer(10,10);
System.out.println(people.d);
}
}
1
2
3
4
***I love this game***
crawling
100
0

在上述的例子中,父类为Monkey,子类为People。其中,在子类中,int d为新增变量,computer为新增方法,因此上转型对象无法直接调用这两个。强制转换为子类类型后,才能正常调用。

覆盖隐藏

  • 上转型对象可以访问子类从父类继承来的数据域、方法或子类中对父类覆盖重写的实例方法,但不能直接访问子类中对父类隐藏重写的静态方法和对父类隐藏定义的数据域。
  • 如果子类覆盖了父类的某个实例方法后,当用上转型对象调用这个实例方法时,一定是调用子类中的这个实例方法。
  • 如果子类隐藏了父类的某个静态方法后,当用上转型对象调用这个静态方法时,一定是调用父类中的这个静态方法,而不是子类中的这个静态方法,输出的值若为静态变量也应该是父类中的静态变量。

MethodOverride.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class MethodOverride {

public static void main(String[] args) {
Child c=new Child();
System.out.println(c.getClassName());
System.out.println("----------------");
Parent p=new Parent();
System.out.println(p.getClassName());
System.out.println("----------------");
Parent pc=new Child();
System.out.println(pc.getClassName());
System.out.println(pc.getX());
System.out.println(pc.getZ());
}
}

class Parent{
int x =5;
static int z = 10;
String getClassName(){
return "this is Parent!";
}
int getX(){
return x;
}
static int getZ(){
return z;
}
}

class Child extends Parent{
int x =6;
int y =7;
static int z =11;
String getClassName(){
System.out.println(x+" "+y);
return "this is Child!";
}

int getX(){
return x;
}

static int getZ(){
return z;
}
}
1
2
3
4
5
6
7
8
9
6 7
this is Child!
----------------
this is Parent!
----------------
6 7
this is Child!
6
10

在上述MethodOverride文件中,父类为Parent,子类为Child。前两个对象为具体类的实例化,因此调用的是自己所属类的实例方法。
第三个为上转型对象,getClassName()是对父类的重写方法,因此调用的是子类的getClassName(),由于是子类的方法,所以方法中的x,y为间接访问数据域也为子类的。getX()也是一个道理。
getZ()为静态方法,子类虽然对静态变量及静态方法进行了隐藏,但无法直接访问。根据静态调用看左边的原则,调用的应该是父类的getZ(),z值也为父类的数据域。

数据域

  • 子类从父类继承来的方法如果没有被覆盖或隐藏,此方法中如果存在成员变量调用,则此调用是针对父类的成员变量的调用。

HidingDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.codeslogan.inherit;


public class HidingDemo {
public static void main(String[] args) {
A x = new B();
System.out.println("(1) x.i is " + x.i);
System.out.println("(2) (B)x.i is " + ((B)x).i);
System.out.println("(3) x.j is " + x.j);
System.out.println("(4) ((B)x).j is " + ((B)x).j);
System.out.println("(5) x.m1() is " + x.m1());
System.out.println("(6) ((B)x).m1() is " + ((B)x).m1());
System.out.println("(7) x.m2() is " + x.m2());
System.out.println("(8) x.m3() is " + x.m3());
System.out.println("(9) x.z is " + x.z);
System.out.println("(10) x.w is " + x.w);
}
}

class A {
public int i = 1;
public static int j = 11;
public int z = 3;
public static int w = 13;

public static String m1() {
return "A's static m1";
}

public String m2() {
return "A's instance m2";
}

public String m3() {
return "A's instance m3" + j;
}
}

class B extends A {
public int i = 2;
public static int j = 12;

public static String m1() {
return "B's static m1";
}

public String m2() {
return "B's instance m2";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(1) x.i is 1
//上转型对象实例数据域看父类
(2) (B)x.i is 2
//强转后看子类
(3) x.j is 11
//上转型对象静态数据域看父类
(4) ((B)x).j is 12
//强转后看子类
(5) x.m1() is A's static m1
//静态方法看父类(左)
(6) ((B)x).m1() is B's static m1
//强转后看子类
(7) x.m2() is B's instance m2
//实例方法重写,看子类
(8) x.m3() is A's instance m3 11
//继承来的方法,子类没有其对应的重写,结果看父类
//若其中有涉及数据域,无论是静态数据,还是实例数据,都应该是父类的数据域
(9) x.z is 3
//显而易见,继承而来的实例数据
(10) x.z is 13
//继承而来的静态数据

总结:

  1. 继承而来(子类没有对其进行额外的操作),全部看父类的内容
  2. 强转后,无条件看强转后的内容
  3. 数据域默认看的是父类
  4. 如果调用了上转型对象调用了父类的方法,那么对应的数据,也应该是父类的数据,即使子类对变量进行了覆盖

不变的强转

上转型对象即使采用父类做一个强制转换,所访问到的被覆盖的实例方法依旧是子类的

1
2
3
A a = new B();
a.getX();
((A)a).getX(); //a.getX();

简单来说就是,原来它的类型本来就是A,再转为A相当于没转。

TestChange.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.codeslogan.inherit;


public class TestChange {

public static void main(String[] args) {
Animal x=new Tiger();
System.out.println("(1):x.news is "+x.news);
System.out.println("(2):((Tiger)x).news is "+((Tiger)x).news);
System.out.println("(3):x.smile() is "+x.smile());
System.out.println("(4):((Tiger)x).smile() is "+((Tiger)x).smile());
System.out.println("(5):((Animal)x).getNews() is "+((Animal)x).getNews());
System.out.println("(6):x.getNews() is "+x.getNews());
System.out.println("(7):x.getMessage() is "+x.getMessage());
System.out.println("(8):((Tiger)x).eat() is "+((Tiger)x).eat());
}
}


class Animal{
public String news="Animal's news";
public String message="Animal's message";

public static String smile(){
return "smile from Animal";
}

public String getNews(){
return news;
}

public String getMessage(){
return message;
}
}


class Tiger extends Animal{
public String news="Tiger's news";
public String message="Tiger's message";

public static String smile(){ //隐藏
return "smile from Tiger";
}

public String getNews(){ //覆盖
return news;
}

//新增的方法
public String eat(){ //新
return "need eat meat";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(1):x.news is Animal's news
// 默认访问父类的成员变量
(2):((Tiger)x).news is Tiger's news
// 强转
(3):x.smile() is smile from Animal
// 静态方法看左
(4):((Tiger)x).smile() is smile from Tiger
// 强转
(5):((Animal)x).getNews() is Tiger's news
// 不变的强转,易错点
(6):x.getNews() is Tiger's news
// 5与6是等效的
(7):x.getMessage() is Animal's message
// 继承来的方法,访问变量看的是父类
(8):((Tiger)x).eat() is need eat meat
// 访问子类新增需要强转

经过上面的沉淀,到这里应该没有问题了,有问题底下留言

动态绑定机制

DynamicBindingDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.codeslogan.inherit;


public class DynamicBindingDemo {
public static void main(String[] args) {
m(new GraduateStudent());
System.out.println("-----");
m(new Student());
System.out.println("-----");
m(new Person());
System.out.println("-----");
m(new Object());
}

public static void m(Object x) {
System.out.println(x.toString());
}
}

class Person extends Object{
public String toString(){
return "Person";
}
}

class Student extends Person{
public String toString(){
return "Student";
}
}

class GraduateStudent extends Student{

}



这道题要注意分析题目,不要看成构造函数。m()方法本质上是调用了每个类中的toString(),根据继承关系分析toString()方法来自哪个类就能得出答案。GraduateStudent是Student的子类,Student是Person的子类,Person是Object的子类。在调用toString()方法时,如果创建的是GraduateStudent的一个实例,JVM会依次在类GraduateStudent,Student,Person中查找toString()方法的实现,一旦找到一个,就停止查找,然后调用这个先找到的

1
2
3
4
5
6
7
Student
-----
Student
-----
Person
-----
java.lang.Object@12a3a380

构造方法链

定义:Java中,用子类构造一个实例对象时,系统会自行在执行子类的构造方法前沿着继承链依次调用所有父类的无参构造方法(按继承关系由其顶层父类至当前子类依次调用,无论其是否在子类的构造方法中被显式调用),这称为构造方法链。

本小节讨论的是上转型对象的构造方法,以及构造方法调用实例方法所出现的情况,具体如下:

TestC.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A {
int value = 5;
A( ) {
setValue(10);
System.out.println( "A:" + value);
}
void setValue(int v) {
value = 2 * v; }
}


class B extends A {
B( ) {
//super();
System.out.println( "B:" + value);
}
@Override
void setValue(int v) {
value = 5 * v; }
}


public class TestC {
public static void main(String[ ] args){
new A( );
new B( );
A c =new B();
}
}

运行答案如下:

1
2
3
4
5
6
7
A:20
-------------
A:50
B:50
-------------
A:50
B:50

刚刚接触这道题我是有点意外的,没想到Java还能这么考,感觉自己平常学的Java和老师教的Java不是一个东西,但是还是得默默承受(4.0 yyds!!!) 话不多说,进入正题

  • 第一值输出为20,创建的是父类对象,调用其构造方法+实例方法,结果无需多言
  • 第二组为两个50,在创建B对象时,因为它是A类的子类,默认会先调用A的构造方法。但是在A的构造方法中,调用了个实例方法。根据前面的知识我们知道,如果子类对父类的实例方法进行了重写覆盖,那么优先调用的应该是子类的实例方法,所以value值为50。
  • 第三组的答案与第二组相同,道理同上,作为上转型只是一个障眼法。

参考资料

某海大某张姓老师