1. 多态性使用和理解
面向对象特征之三:多态性
-
理解多态性:可以理解为一个事物的多种形态。
-
何为多态性:对象的多态性,父类的引用指向子类的对象(或子类的对象赋给父类的引用)
-
多态的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
总结:编译看左边,运行看右边。
便于站在高处(more generalized)指挥,而不局限于实施细节
-
多态性的使用前提:1)类的继承关系 2)方法的重写
-
对象的多态性,只适用于方法,不适用于属性。子类不会覆盖父类的同名属性,而是内存都有,编译和运行都看左边。
虚拟方法调用:(Virtual Method Invocation)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
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
|
package com.atguigu.java4;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.earnMoney();
// **************************
// 对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
//Person p3 = new Woman();
// 多态的使用:当调用子父类同名同参数方法时,实际执行的是子类重写父类的方法 --- 虚拟方法调用
p2.eat();
p2.walk();
// wrong
// p2.earnMoney()
}
}
|
多态性使用例子
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
55
56
57
58
59
60
61
62
|
package com.atguigu.java4;
import java.sql.Connection;
// 多态性使用
// 举例一
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal) {
animal.eat();
animal.shout();
}
}
class Animal {
public void eat() {
System.out.println("animal eat");
}
public void shout() {
System.out.println("animal shout");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("dog eat");
}
public void shout() {
System.out.println("dog shout");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("cat eat");
}
public void shout() {
System.out.println("cat shout");
}
}
// 举例二
class Order {
public void method(Object obj) {
}
}
// 举例三
class Driver {
public void doData(Connection conn) { // conn = new MySqlConnection(); new
// 规范的步骤去操作数据
// conn.method1();
// conn.method2();
// conn.method3();
}
}
|
面试:
Q:多态性是编译时行为还是运行时行为?
A:运行时行为
方法的重载与重写
- 二者的定义细节:略
- 从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。他们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
而对于多态,只有等到方法调用的那一刻,届时运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
引用一句Bruce Eckel的话:
> “不要犯傻,如果它不是晚绑定,它就不是多态。” - Bruce Eckel
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
|
package com.atguigu.java5;
import java.util.Random;
public class InterviewTest {
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat();
case 1:
return new Dog();
default:
return new Sheep();
}
}
}
class Animal {
protected void eat() {
System.out.println("animal eat");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat");
}
}
class Dog extends Animal {
protected void eat() {
System.out.println("dog eat");
}
}
class Sheep extends Animal {
protected void eat() {
System.out.println("sheep eat");
}
}
|
再来看一个例子
练习:
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。编译看左边,运行看右边
对于实例变量不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边
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
|
package com.atguigu.java;
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);// 20
s.display(); //20
Base b = s;//多态性
// == 对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
System.out.println(b == s);// true
System.out.println(b.count);//10
b.display(); //20
}
}
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
|
2. 向下转型与instanceof关键字
2.1 向下转型
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
如何才能调用子类特有的属性和方法?使用强制类型转换符,向下转型
较高级的基本数据类型(double)
| ^
强制类型转化 |
| 自动类型提升
v |
较低级的基本数据类型(int)
父类(Person)
| ^
向下转型 |
| 向上转型:多态
v |
子类(Student)
2.2 instanceof关键字使用
a instanceof A
:判断对象a是否是类A的实例。如果是返回true,如果不是返回false。
使用情景:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof
的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
如果a instanceof A
返回true,则a instanceof B
也返回true。其中类B是类A的父类。
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
55
56
57
58
|
package com.atguigu.java;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.earnMoney();
// **************************
// 对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
//Person p3 = new Woman();
// 多态的使用:当调用子父类同名同参数方法时,实际执行的是子类重写父类的方法 --- 虚拟方法调用
p2.eat();
p2.walk();
// wrong
// p2.earnMoney()
// **************************
// 不能调用子类所特有的方法,属性:编译时,p2是Person类型。
System.out.println("************************");
Man m1 = (Man)p2;
m1.earnMoney();
// 使用强转时,可能出现ClassCastException的异常
// Woman w1 = (Woman)p2;
// w1.goShopping();
// 使用instanceof判断
if (p2 instanceof Woman) {
Woman w1 = (Woman)p2;
w1.goShopping();
}
if (p2 instanceof Man) {
m1 = (Man)p2;
m1.earnMoney();
}
// 练习:
// 问题一: 编译时通过,运行时不通过
// 举例一:
// Person p3 = new Woman();
// Man m3 = (Man)p3;
// 举例二:
// Person p4 = new Person();
// Man m4 = (Man)p4;
// 问题二:编译通过,运行时也通过
Object obj = new Woman();
Person p = (Person)obj;
// 问题三:编译不过
// Man m5 = new Woman();
}
}
|
3. Object类与equals(),toString()方法
3.1 Object类
java.lang.Object
类
- Object类是所有Java类的根父类
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为
java.lang.Object
类
- Object类中的功能(属性,方法)就具有通用性。
- 属性:无
- 方法:
equals()
, toString()
, getClass()
, hashCode()
, clone()
, finalize()
, wait()
, notify()
, notifyAll()
- Object类只声明了一个空参构造器
面试题
final, finally, finalize的区别?
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.atguigu.java1;
public class ObjectTest {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.getClass().getSuperclass());
}
}
class Order {
}
|
3.2 equals()使用
面试题:==
和equals()
区别
一·回顾==
的使用
运算符
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
补充:==符号使用时,必须保证符号左右两边的变量类型一致,否则编译报错。
二·equals()
方法的使用:
-
是一个方法,而非运算符
-
只能适用于引用数据类型
-
Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明:Object类中定义的equals()
和==
的作用是相同的:比较两个对象地址值是否相同,即是否都指向同一个对象实体
-
像String, Date, File, 包装类等都重写了Object类中的equals()
方法,重写以后,比较的不是两个引用地址是否相同,而是比较两个对象的“实体内容”是否相同。
-
通常情况下,我们自定义的类如果使用equals()
的话,也通常是比较两个对象的“实体内容”是否相同。那么,我们就需要对Object类中的equals()
进行重写,重写的原则:比较两个对象的实体内容是否相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.atguigu.java1;
public class EqualsTest {
public static void main(String[] args) {
int i = 10;
int j = 10;
double d = 10.0;
System.out.println(i == j);// true
System.out.println(i == d);// true
char c = 10;
System.out.println(i == c);// true
char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2);// true
String str1 = new String("atguigu");
String str2 = new String("atguigu");
System.out.println(str1 == str2);// false
System.out.println(str1.equals(str2));// true
}
}
|
3.3 toString()
使用
Object类中toString()的使用
-
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
-
Object类中toString()的定义
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
-
像String, Date, File, 包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回“实体内容”信息
-
自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”。
4. 包装类,基本数据类型,String三者间的转换
包装类的使用
-
Java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
-
掌握的:基本数据类型,包装类,String三者间的相互转换
- 基本数据类型 -> 包装类:调用包装类的构造器, 基本数据类型无法利用多态
- 包装类 -> 基本数据类型:调用包装类的xxxValue,包装类不能加减乘除
JDK 5.0新特性:自动装箱与自动拆箱
* 基本数据类型,包装类 -> String类型: String.valueOf()
* String类型 -> 基本数据类型,包装类: 调用包装类的parseXxx()
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
|
package com.atguigu.java2;
public class WrapperTest {
public static void main(String[] args) {
int num1 = 10;
Integer in1 = new Integer(num1);
System.out.println(in1.toString());
Integer in2 = new Integer("123");
System.out.println(in2.toString());
Float f1 = new Float(12.3f);
Float f2 = new Float("12.3");
System.out.println(f1);
System.out.println(f2);
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("true");
Boolean b3 = new Boolean("TrUe");
System.out.println(b3); // true
int i1 = in1.intValue();
float f3 = f1.floatValue();
System.out.println(i1);
System.out.println(f3);
//自动装箱
int num2 = 10;
Integer in3 = num2;// 自动装箱
System.out.println(in3);
int num3 = in3;// 自动拆箱
// 基本数据类型,包装类 -> String类型:调用String重载的valueOf(Xxx xxx)
int n1 = 10;
// 方式1: 连接运算
String str1 = n1 + "";
// 方式2: 调用String的valueOf
String s1 = String.valueOf(n1); // "10"
String s2 = String.valueOf(new Double(12.4)); // "12.4"
System.out.println(s1 + ',' + s2);
int x = Integer.parseInt("123");
boolean b = Boolean.parseBoolean("true");
System.out.println(x);
System.out.println(b);
}
}
|
一道考题
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
|
package com.atguigu.java2;
public class InterviewTest {
public static void main(String[] args) {
}
public void test1() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); //1.0, 编译三元标识符,自动类型提升为Double,多态toString
}
public void test2() {
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2); // 1, 不需要自动类型提升
}
public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
// Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
// 保存了-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
// -128~127范围内时,可以直接使用数组中的元素,不用再new了。目的:提高效率
Integer m = 1;
Integer n = 1;
System.out.println(m == n); // true
Integer x = 128; // 相当于new了一个Integer对象
Integer y = 128; // 相当于又new了一个Integer对象
System.out.println(x == y); // false
}
}
|
体会包装类的引入意义
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
|
package com.atguigu.java2;
import java.util.Scanner;
import java.util.Vector;
public class ScoreTest {
public static void main(String[] args) {
// 1. 实例化Scanner,用于从键盘获取学生成绩
Scanner scan = new Scanner(System.in);
// 2. 创建Vector对象
Vector v = new Vector();
// 3. 给Vector中添加元素
int maxScore = 0;
while (true) {
System.out.println("input Student's score (negative number to end)");
int score = scan.nextInt();
if (score < 0)
break;
// before jdk5.0
// Integer el = new Integer(score);
// v.addElement(el); // 多态
// after jdk5.0
v.addElement(score); // 自动装箱
// 4. 获取成绩最大值
if (score > maxScore)
maxScore = score;
}
// 5. 遍历Vector,得到每个学生成绩,并与最大值比较,得到等级
char level;
for (int i = 0; i < v.size(); i++) {
Object obj = v.elementAt(i);
// before jdk5.0
// Integer el = (Integer)obj;
// int score = el.intValue();
// after jdk5.0
int score = (int)obj;
if (maxScore - score <= 10) {
level = 'A';
} else if (maxScore - score <= 20) {
level = 'B';
} else if (maxScore - score <= 30) {
level = 'C';
} else {
level = 'D';
}
System.out.println("student score: " + score + ", grade: " + level);
}
}
}
|