5.5 面向对象特征之三: 多态性
1. 多态性的理解
多态性可以理解为一个事物的多种形态。
2. 什么是多态性
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用);可以直接应用在抽象类和接口上
多态性提高了代码的通用性,常称作接口重用
3. 多态性的使用(虚拟方法调用)
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
-
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
-
多态情况下, “看左边” :看的是父类的引用(父类中不具备子类特有的方法)
“看右边” :看的是子类的对象(实际运行的是子类重写父类的方法)
例子
// 方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
public class Test {
public void method(Person e) {
e.getInfo();
}
public static void main(Stirng args[]) {
Test t = new Test();
Student m = new Student();
t.method(m); // 子类的对象m传送给父类类型的参数e
}
}
4. 多态性的使用前提
- 类的继承关系
- 方法的重写(如果没有方法的重写没必要使用多态)
5. 注意事项
-
对象的多态:在Java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象例子
例子
Person p = new Student(); Object o = new Person();//Object类型的变量o,指向Person类型的对象 o = new Student(); //Object类型的变量o,指向Student类型的对象
-
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
-
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
例子
Student m = new Student(); m.school = “pku”; //合法,Student类有school成员变量 Person e = new Student(); e.school = “pku”; //非法,Person类没有school成员变量
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
-
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
6. 虚拟方法调用
概念:子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
例子
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
7. 方法的重载与重写
7.1 二者定义的细节
方法的重载是指在一个类中,具有相同方法名,但是参数列表不相同的方法彼此之间构成重载。
方法的重写是指子类的对象重新实现了父类中定义的同名同参数的方法的方法体。
7.2 从编译和运行的角度看
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。 所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法, 这称为“早绑定”或“静态绑定”; 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
8. instanceof操作符
用法:a instanceof A
,判断a是否为类A的对象,返回值是boolean类型,是返回true,否返回false
注意事项
- 要求a所属的类与类A必须是子类和父类的关系,否则编译错误。
- 如果a属于类A的子类B,a instanceof A值也为true。
9. 对象类型转换 (Casting )
-
基本数据类型的Casting:
-
自动类型转换:小的数据类型可以自动转换成大的数据类型
如long g = 20; double d = 12.0f
强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
如 float f = (float)12.0; int a = (int)1200L
-
-
对Java对象的强制类型转换称为造型
- 从子类到父类的类型转换可以自动进行(向上转型)
- 从父类到子类的类型转换必须通过造型实现(向下转型/强制类型转换)
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof操作符测试一个对象的类型
例子
public class ConversionTest {
public static void main(String[] args) {
Object obj = "Hello";
String objStr = (String) obj;
}
}
10. 子类继承父类的注意点
-
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
-
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边
5.6 Object类的使用
1. 相关概念
-
Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
-
Object类中的功能(属性、方法)具有通用性。
-
Object类中没有定义属性
-
Object类中的方法:equals() / toString() / getClass() /hashCode() / clone() / finalize(),wait() 、 notify()、notifyAll()
-
Object类只声明了一个空参的构造器
2. Object类中的主要结构
方法名称 | 描述 |
---|---|
public Object() | 构造器 |
protected Object clone() throws CloneNotSupportedException | 复制当前对象 |
public boolean equals(Object obj) | 比较两个对象是否“相等” |
protected void finalize() throws Throwable | 当该对象没有引用指向时,由对象的垃圾回收器调用此方法 |
public final Class<?> getClass() | 返回该对象的运行时类 |
public int hashCode() | 返回该对象的哈希码值 |
public final void notify() | 唤醒在此对象监视器上等待的单个线程 |
public final void notifyAll() | 唤醒在此对象监视器上等待的所有线程。 |
public String toString() | 返回该对象的字符串表示 |
public final void wait([参数]) throws InterruptedException | 线程等待 |
-
clone()方法
此方法执行的是该对象的“浅表复制”,而不“深层复制”操作,即如果对象中有引用类型的字段,复制的是字段的引用,而非引用的对象,指向的是同一个对象。
-
finalize()
垃圾回收机制关键点
-
垃圾回收机制只回收JVM堆内存里的对象空间,对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力
-
现在的JVM有多种垃圾回收实现算法,表现各异。
-
垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
-
可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
-
可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有
一些效果,但是系统是否进行垃圾回收依然不确定。 -
垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一
个新的引用变量重新引用该对象,则会重新激活对象)。 -
永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
-
3. == 和 equals() 区别
== :运算符
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
-
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
equals():是一个方法,而非运算符
-
只能适用于引用数据类型,格式:
obj1.equals(obj2)
-
Object类中equals()的定义:
public boolean equals(Object obj) { return (this == obj); }
Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
-
String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
-
通常情况下,自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,就需要对Object类中的equals()进行重写,
-
重写的原则:比较两个对象的实体内容是否相同
-
对称性:如果x.equals(y)返回是“true” ,那么y.equals(x)也应该返回是 “true”
-
自反性:x.equals(x)必须返回是“true”
-
传递性:如果x.equals(y)返回是“true” ,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”
-
一致性:如果x.equals(y)返回是“true” ,只要x和y内容一直不变,不管重复x.equals(y)多少次,返回都是“true”。
-
任何情况下,x.equals(null),永远返回是“false” ;
x.equals(和x不同类型的对象)永远返回是“false”。
-
4. toString() 方法
-
toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
-
当输出一个对象的引用时,实际上就是调用当前对象的toString()
Date now = new Date(); System.out.println(now); // 相当于 System.out.println(now.toString());
-
Object类中toString()的定义:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
Object类中定义的toString()返回的是该对象的类名以及虚拟的内存地址
-
String、Date、File、包装类等都重写了Object类中的toString()方法,在调用对象的toString()时,返回的是"实体内容"信息
-
自定义类可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
-
基本类型数据转换为String类型时,调用了对应包装类的toString()方法
5.7 Java中的JUnit单元测试的使用
1. 在eclipse中:
步骤:
-
选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
-
创建Java类,进行单元测试。此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
-
此类中声明单元测试方法。此时的单元测试方法:方法的权限是public,没有返回值,没有形参
-
此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
-
声明好单元测试方法以后,就可以在方法体内测试相关的代码。
-
写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
说明:
-
如果执行结果没有任何异常:绿条
-
如果执行结果出现异常:红条
5.8 包装类的使用
java提供了8种基本数据类型对应的包装类(封装类),使得基本数据类型的变量具有类的特征
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
其中Byte、Short、Integer、Long、Float、Double的父类为Number类
1. 基本数据类型 —> 包装类
-
通过包装类的构造器:
// 数值型参数: int i = 500; Integer t = new Integer(i); // 字符串参数 Float f = new Float("3.14"); // 如果为字符串参数需要是数值,下面的这种会报异常:NumberFormatException Long l = new Long(“asdf”); // boolean类型的包装类有进行优化,当字符串转小写后与"true"相等即为true,否则为false Boolean b1 = new Boolean(true); Boolean b2 = new Boolean("TrUe"); System.out.println(b2); // true Boolean b3 = new Boolean("true123"); System.out.println(b3); // false
-
JDK1.5之后,使用自动装箱:类型必须匹配
int num = 10; Integer integer = num; // 自动装箱
2. 包装类 —> 基本数据类型
-
调用包装类Xxx的xxxValue()方法
Integer integer = new Integer(12); int num = integer.intValue(); System.out.println(num + 1); // 13 Float f1 = new Float(12.3); float f2 = f1.floatValue(); System.out.println(f2 + 1); // 13.3
-
JDK1.5之后,使用自动拆箱:类型必须匹配
Integer integer = new Integer(12); int num = integer;//自动拆箱
3. 基本数据类型、包装类 —> String类型
-
使用字符串连接
int num = 10; String str = num + "";
-
调用String重载的valueOf(xxx)
float f = 12.3f; String str1 = String.valueOf(f); // "12.3" System.out.println(str1); // "12.3" Double d = new Double(12.4); String str2 = String.valueOf(d); System.out.println(str2); // "12.4"
-
通过toString()方法
Double d = 3.14; String str1 = d.toString(); System.out.println(str1); // "3.14" String str2 = Double.toString(1.11); System.out.println(str2); // "1.11"
4. String类型 —> 基本数据类型、包装类
-
通过包装类的构造器实现
int i = new Integer("12");
-
调用包装类的parseXxx(xxx)
Float f = Float.parseFloat("12.1");
5. 补充
转换相同类型
// 在编译时,Integer类型会转换为double类型
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); // 1.0
Integer类型的缓存:IntegerCache
在Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],其保存了从-128127范围的整数。如果使用自动装箱的方式,给Integer赋值的范围在-128127范围内时,可以直接使用数组中的元素,不用再去new了,其目的是为了提高效率。
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
Integer m = 1;
Integer n = 1;
System.out.println(m == n); // true
// 相当于new了一个Integer对象
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false