面向对象编程
五、第五阶段–多态性
5.1、理解多态性
-
理解多态性:可以理解为一个事物的多种形态;
-
何为多态性:
- 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
-
多态的使用,虚拟方法调用:当掉用子父类同名同参数的方法时。实际执行的是子类重写父类的方法
-
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法
总结:编译,看左边;运行,看右边。
-
-
多态性的使用前提:
① 类的继承关系
② 要有方法的重写
-
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
package cs20230316.java1;
public class PersonTest {
public static void main(String[] args) {
Person p1 =new Person();
p1.eat();
Man man = new Man();
man.earnMoney();
man.eat();
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
Person p3 = new Woman();
//多态的使用:当掉用子父类同名同参数的方法时。实际执行的是子类重写父类的方法----虚拟方法调用
p2.eat();
p3.eat();
//p2.earnMoney();//Person中没有该方法,所以不能编译通过
}
}
5.2、方法的重载与重写
1.二者的定义细节:略
2.从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为**“早绑定”或“静态绑定”;
而对于多态**,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为**“晚绑定”或“动态绑定”**。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
5.3、instanceof
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
如何才能调用子类特有的属性和方法?
答:向下转型:使用强制类型转换符。
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
Man m1 = (Man) p2;
使用强转时,可能出现ClassCastException
的异常!
5.3.1、使用方法
a instanceof A
:判断对象a是否是类A的实例,如果是,返回true;如果不是。返回false。
使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行 instanceof
的判断,一旦返回true,就进行向下转型;如果返回false,不进行向下转型。
//练习:
//问题一:编译时通过,运行时不通过
//举例一
Person p4 = new Woman();//Woman和Man都是Person的子类,不能相互转换
Man m3 = (Man) p3;
//举例二
Person p5 = new Person();
Man m4 = (Man) p5;//new 的就是 Person,不能转为子类
//问题二:编译通过,运行时也通过
Object obj = new Woman();//Object是间接父类
Person p = (Person) obj;//Persons 直接父类,让间接父类转为直接父类是可行的
//问题二:编译不通过
Man m5 = new Woman();
5.4、多态性的总结和理解
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边
-
谈谈你对多态性的理解?(还需重点理解)
① 实现代码的通用性。
② Object类中定义的
public boolean equals(object obj){}
JDBC:使用java程序操作(获取数据库连接,CRUD)数据库(MySQL、Oracle、DB2、SQL Server )
③ 抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)
5.5、Object类的使用
java.lang.Object类
-
Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
-
Object类的方法具有通用性
-
属性:无
-
方法:equals() / toString() / getClass() / hashCode() / clone() / finalize()
wait() 、 notify() 、notifyAll()
-
面试题:final、 finally 、finalize的区别?
5.5.1、equals()方法和==运算符
面试题: == 和 equals() 区别
5.5.1.1、== 运算符
① 可以使用在基本数据类型变量和引用数据类型变量中
② 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同,因为有自动类型提升)
③ 如果比较的是引用数据类型变量:比较两个变量的地址值是否相等。即两个引用是否指向同一个对象实体
补充: == 符号使用时,必须保证符号左右两边的变量类型一致。
//基本数据类型
int i = 20;
char c = 20;
System.out.println(c == i);//true
char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2);//true
//引用数据类型
//因为都是 new 了不同的对象
Customer cust1 = new Customer("Tom","Andy");
Customer cust2 = new Customer("Tom","Andy");
System.out.println(cust2 == cust1);//false
String str1 = new String("Cute");
String str2 = new String("Cute");
System.out.println(str2 == str1);//false
5.5.1.2、equals()方法
equals()方法的使用:
① 是一个方法,而非运算符
② 只能适用于引用数据类型
③ Object类中equals()方法的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明:Object类中定义的equals()方法和 == 的作用是相同的,比较两个对象的比较两个变量的地址值是否相等。即两个引用是否指向同一个对象实体
④ 像 String 、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同。
⑤ 通常情况下,我们自定义的类如果使用equals()方法的话,也通常是比较两个对象的“实体内容”是否相同。那么,我们就需要对Object类中的equals()方法重写
重写的原则:比较两个对象的实体内内容是否相同。
5.5.1.3、toString()方法
Object类中toString()的使用
- 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
- Object类中toString()的定义:
- 像String 、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回“实体内容”信息。
- 自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”。
Object类中toString()的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
5.5.2、单元测试方法的使用
想测试哪一块就单独测试这一块,其他不测试。
JUnit
Eclipse中单元测试的使用
Java中的JUnit单元测试
步骤:
1.选中当前工程–右键选择: build path - add libraries - JUnit 4 -下一步
2.创建Java类,进行单元测试。
此时的Java类要求:此类是public的2此类提供公共的无参的构造器
3.此类中声明单元测试方法。
此时的单元测试方法:方法的权限是public,没有返回值,没有形参
4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
6.写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
说明:
1.如果执行结果没有任何异常:绿条
2.如果执行结果出现异常:红条
5.6、包装类(Wrapper)
虚线框中的都是数值类,父类都是Number
5.6.1、包装类的使用
-
java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
-
掌握的:基本数据类型、包装类、String三者之间的相互转换
① 基本数据类型–>包装类:调用包装类的构造器,见5.6.2的自动封箱、自动拆箱
② 包装类–>基本数据类型:调用包装类的Xxx的xxxValue(),如:intValue()、floatValue():见5.6.2的自动封箱、自动拆箱
③ 基本数据类型、包装类–>String类型:调用String重载的valueOf(Xxx xxx)
String类型–>基本数据类型、包装类:调用包装类中的parseXxx(String s)
//基本数据类型、包装类-->String类型 int num4 = 10; //方式一:连接运算 String str1 = num4 + ""; //方式二:调用String重载的valueOf(Xxx xxx) float f1 = 12.3f; String str2 = String.valueOf(f1);//12.3 Double d1 = 12.4; String str3 = String.valueOf(d1);
//String类型-->基本数据类型、包装类:调用包装类的 parseXxx(String s)
String str5 = "123a";
//错误写法
// int num1 = (int) str5;
//可能会报 NumberFormatException的错,所以必须保证String的数据可以转换
int n1 = Integer.parseInt(str5);
System.out.println(n1+5);
//只要不是 true,就全部都是false
String s2 = "true ";
boolean b1 = Boolean.parseBoolean(s2);
5.6.2、JDK5.0新特性
JDK5.0新特性:自动装箱
与 自动拆箱
//自动装箱:基本数据类型-->包装类
int num2 = 10;
Integer int1 = num2;//自动装箱
System.out.println(int1);
boolean b2 = true;
Boolean b3 = b2;//自动装箱
System.out.println(b3);
//自动拆箱:包装类-->基本数据类型
System.out.println(int1.toString());//自动拆箱
int num3 = int1;//自动拆箱
System.out.println(num3);
//自动装箱:基本数据类型-->包装类
int num2 = 10;
Integer int1 = num2;//自动装箱
System.out.println(int1);
boolean b2 = true;
Boolean b3 = b2;//自动装箱
System.out.println(b3);
//自动拆箱:包装类-->基本数据类型
System.out.println(int1.toString());//自动拆箱
int num3 = int1;//自动拆箱
System.out.println(num3);