第五章 面向对象编程(中)
一. 面向对象的特征二:继承性
继承性的好处:
① 减少了代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 让类与类之间产生了关系,为之后多态性的使用,提供了前提
1. 继承性的格式: class A extends B {…}
理解:“子类 is a 父类”
A:子类、派生类、subclass
B:父类、超类、superclass
注意:不要仅为了获取其他类中某个功能而去继承
- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有属性和方法。
- 特别地,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
- 子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的扩展。在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
2. Java中关于继承性的规定
① 一个类可以被多个子类继承
② java中类的单继承性:一个类只能有一个父类,但可以多层继承
③ 子父类是相对的概念
④ 子类直接继承的父类,称为:直接父类;间接继承的父类,称为:间接父类
⑤ 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
3. Object类的理解
① 若没有显式地声明一个类的父类的话,则此类继承于java.lang.Object类
② 所有的java类 (除java.lang.Object类之外) 都直接或间接的继承于java.lang.Object类
③ 意味着,所有的java类都具有java.lang.Object类中声明的功能
二. 方法的重写 override/overwrite
1. 重写: 子类继承父类以后,可以对父类中同名同参数的方法进行覆盖操作
2. 应用: 重写以后,当创建子类对象并通过子类对象调用子父类中同名同参的方法时,实际执行的是子类重写后的方法
3. 重写的规定
方法的声明:权限修饰符 返回值类型 方法名( 形参列表 ) throws 异常的类型 {
// 方法体
}
约定:子类中叫重写的方法,父类中叫被重写的方法
① 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
② 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
③ 返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值类型是基本数据类型( 比如: double ),则子类重写的方法的返回值类型必须是相同的基本数据类型( 必须也是double )
④ 子类方法抛出的异常不能大于父类被重写方法的异常
特别地,子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写,static方法是不可以被重写的)
三. 关键字:import
super理解为:父类的
super可以用来访问父类的属性,调用父类的方法、构造器
super的使用:
- 在子类的方法或构造器中,通过使用 “super.属性” 或 “super.方法” 的方式,显式的调用父类中声明的属性或方法,但通常情况下,习惯省略super
- 当子类和父类定义了同名的属性时,若想在子类中调用父类中声明的属性,则必须显式的使用 “super.属性”的方式,表明调用的是父类中声明的属性
- 当子类重写了父类中的方法后,若想在子类方法中调用父类中被重写的方法时,必须显式的使用 “super.方法”的方式,表明调用的是父类被重写的方法
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
》》》super调用构造器:
- 我们可以在子类的构造器中显式的使用“super( 形参列表 )”的方式,调用父类中声明的指定的构造器
- “super( 形参列表 )”的使用,必须声明在子类构造器的首行
- 我们在类的构造器中,针对于“this( 形参列表 )” 或 “super( 形参列表 )” 只能二选一,不能同时出现
- 在构造器首行,没有显式的声明 “this( 形参列表 )” 或 “super( 形参列表 )”,则默认调用的是父类中空参的构造器
- 在类的多个构造器中,至少有一个类的构造器中使用了“super( 形参列表 )”,调用父类中的构造器
- 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
四. 面向对象的特征三:多态性
理解:可以理解为一个事物的多种形态
对象的多态性:父类的引用指向子类的对象( 或子类的对象赋给父类的引用 )
- 可以直接应用在抽象类和接口上
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。总结:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
- “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
》》》多态的使用
- 虚拟方法调用(多态情况下):子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。(动态绑定)
- 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行时,实际执行的是子类中重写父类的方法
- 需要注意的是,对象的多态性只适用于重写后的方法不适用于属性。也就是说父类的引用不能访问子类中另外添加的属性和方法,属性是在编译时确定的。
2.对象类型转换和instanceof关键字
- 引入:有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法,不能调用子类中特有的属性和方法
- 如何才能调用子类特有的属性和方法? ——> 向下转型:使用强制类型转换符
x instanceof A:判断对象x是否为类A的实例,返回值为boolean型。
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- 若 x instanceof A 返回true,则x instanceof B也返回true,其中B是A的父类
使用情景:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型,若返回false则不进行。
3.java.lang.Object类
① Object类是所有Java类的根父类
② 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
③ Object类中的功能( 属性、方法 )具有通用性
- 属性:无
- 方法:equals( )、toString( )、getClass( )、hashCode( )、clone( )、finalize( )、wait( )、notify( )、notifyAll( )
④ Object类只声明了一个空参的构造器
》》》面试题:== 和 equeals( ) 的区别
① 回顾 == 的使用:
- ==: 比较运算符
- 可以使用在基本数据类型变量和引用数据类型变量中
- 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等(类型可以不匹配)
- 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
② equals( )方法的使用
- 是一个方法,而非运算符
- 只能适用于引用数据类型
- Object类中equals( )的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明:Object类中定义的equals( )和==作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
- 特别地,像String、Date、File、包装类等类重写了Object类中的equals( )方法。重写之后,比较的不是两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同
- 通常情况下,自定义的类如果使用equals( )的话,是想比较两个对象的“实体内容”是否相同。因此,需要对Object类中的equals( )进行重写。重写的原则:比较两个对象的每个(或重要)属性是否相同。
》》》toString( )方法的使用
① 当我们输出一个对象时,实际上就是调用当前对象的toString( )方法
② toString( )方法的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
输出的是对象的类型和地址
③ 像String、Date、File、包装类等都重写了Object类中的toString( )方法,使得在调用对象的toString( )时,返回对象 的“实体内容”
④ 自定义类也可以重写toString( )方法,当调用此方法时,返回对象的“实体内容”
五. Java中的JUnit单元测试
步骤:
① 选中当前工程 ⇢ 右键选择:build path ⇢ add libraries ⇢ JUnit 4 ⇢ 下一步
② 创建Java类,进行单元测试
此时的Java类要求:此类是public的;此类提供公共的无参的构造器
③ 在类中声明单元测试方法,要求方法的权限是public,返回值类型是void,没有形参
④ 单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
⑤ 写好单元测试方法后,直接在方法体内测试相关代码,左键双击单元测试方法名,右键run as ⇢ Junit Test
说明:
若执行结果没有任务异常,绿条;若出现异常,红条。
实际开发中不用像进行上述步骤的复杂操作,直接写@Test,然后鼠标放在注解上,点击Add JUnit 4 library to the build path可以一步到位
六. 包装类(Wrapper)的使用
Java针对八种基本数据类型定义了相应的引用类型—包装类( 封装类 ),使得基本数据类型的变量具有类的特征
【应用场景】举例:
Vector类中关于添加元素,只定义了形参为Object类型的方法: v.addElement( Object obj )
在向容器中添加数据时需要考虑,基本数据类型 ⇢ 包装类 ⇢ 使用多态
- 基本数据类型 ⇨ 包装类:装箱
- 调用包装类的构造器 e.g. int i = 500; Integer t = new Integer(i);
- 通过字符串参数构造包装类对象 e.g. Float f = new Float(“4.56”); 注意:字符串的内容要和基本数据类型相匹配,否则会报错NumberFormatException;特别地,Boolean构造器可以接收任意字符串的时候,忽略大写小情况下,只要参数是“true”,则构造出的对象实体内容就为true
- 包装类 ⇨ 基本数据类型:拆箱
- 调用包装类的.xxxValue( )方法 e.g. int i = iObj.intValue( )
- JDK 5.0 新特性:自动装箱、自动拆箱
e.g. int num =10; Integer t = num; //自动装箱
int i = t; //自动拆箱
- String类型 ⇨ 基本数据类型、包装类
- 调用包装类的.parseXxx( String s )静态方法 e.g. int i = Integer.parseInt(“123”); 同样Boolean中的方法可以接收任意字符串
- 通过字符串参数构造包装类对象
- 基本数据类型、包装类 ⇨ String类型
- 连接运算 String str = “” + 5
- 调用String重载的valueOf( )方法 String str = String.valueOf( 12.3f )
简易版:
基本数据类型 ⟺ 包装类: JDK 5.0 新特性:自动装箱、自动拆箱
基本数据类型、包装类 ⟹ String:调用String重载的.vauleOf( )
String ⟸ 基本数据类型、包装类:调用包装类的parseXxx( String s ) 注: 转换时可能会报NumberFormatException
》》》面试题考点
Integer内部定义了一个叫 IntegerCache 的内部类,其中定义了Integer类型的数组cache,缓存了从-128到127范围的整数Integer对象,在自动装箱时,若赋值符号右边的整数在闭区间[-128,127]中,可以直接使用cache数组中已经new好的Integer。
这样做可以提高效率,因为-128到127在日常中使用频率很高
上面m和n在自动装箱时都指向了cache数组中同一个对象