面向对象(中)
- 一、面向对象特征之一:封装与隐藏
- 1.1 简介
- 1.2 封装性的体现
- 1.3 四种访问权限修饰符
- 二、类的成员之三:构造器
- 2.1 构造器的特征
- 2.2 构造器的作用
- 2.3 语法格式
- 2.4 构造器分类
- 2.5 构造器重载
- 2.6 属性赋值过程
- 三、扩展知识
- 3.1 JavaBean
- 3.2 UML类图
- 四、关键字:this的使用
- 4.1 this是什么?
- 4.2 this关键字使用
- 4.2.1 修饰内容
- 4.2.2 this修饰属性和方法
- 4.2.3 this调用构造器
- 五、关键字:package、import
- 5.1 package关键字的使用
- 5.2 JDK主要的包介绍
- 5.3 import关键字的使用
- 六、面向对象特征之二:继承性
- 6.1 为什么要有继承
- 6.2 类继承语法规则
- 6.3 继承的好处
- 6.4 注意点
- 6.5 关于继承的规则
- 6.6 单继承和多层继承
- 七、方法的重写(override/overwrite)
- 7.1 定义
- 7.2 要求
- 7.3 注意
- 7.4 示例
- 八、关键字:super
- 8.1 super的使用
- 8.2 super调用构造器
- 8.3 示例
- 8.4 this和super的区别
- 九、子类对象实例化的全过程
- 十、面向对象特征之三:多态性
- 10.1 多态性的体现
- 10.2 为什么出现多态性
- 10.3 **正常的方法调用**
- 10.4 **虚拟方法调用(多态情况下)**
- 10.5 **编译时类型和运行时类型**
- 10.6 多态性的使用前提:
- 10.7 多态性的适用
- 10.8 虚拟方法调用举例
- 10.9 方法的重载与重写 关于多态性
- 10.10 instanceof操作符
- 10.11 对象类型转换
- 十一、Object类的使用
- 11.1 Object类的使用
- 11.2 ==和equals的区别
- 11.3 toString()方法
一、面向对象特征之一:封装与隐藏
1.1 简介
-
为什么要封装?封装的作用和含义
- 我要用洗衣机,只需要按一下开发和洗涤模式就可以了。有必要了解洗衣机内部的机构吗?有必要碰电动机吗?
- 我要开车,。。。
-
我们程序设计追求“高内聚,低耦合”
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用。
-
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,
把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装的设计思想
。
class Animal {
public int legs;
public void eat(){
System.out.println("Eating");
}
public void move(){
System.out.println("Moving.");
}
}
public class Zoo {
public static void main(String args[]) {
Animal xb = new Animal();
xb.legs = 4;
System.out.println(xb.legs);
xb.eat();
xb.move();
}
}
1.2 封装性的体现
- 将类的属性xxx私有化(private),再提供公共的(public)方法来获取getXxx和设置塞setXxx此属性的值。
- 不对外暴露的私有的方法
- 单例模式(将构造器私有化)
- 如果不希望类再包外被调用,可以将类设置为缺省的。
- 举例说明:
/**
* 一、面型对象的特性一:封装与隐藏
* 当我们创建一个对象的类以后,我们可以通过 “对象.属性” 的方式,对对象的属性进行赋值。
* 这里,赋值的操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。
* 但是,在实际问题当中,我们往往需要给属性赋值加入额外的限制条件,这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。
* 比如 setLegs(),同时我们需要避免用户再使用 “对象.属性” 的方式进行赋值。则需要将属性声明为私有的(private)
* 此时针对于属性就体现了封装性
*
* 二、封装性的体现
* 我们将类的属性私有化(private),同时,提供公共的方法来获取(getXxx)和设置(setXxx)
*
* 拓展:封装性的体现有很多
* (1) 如上
* (2)不对外暴露的私有的方法
* (3)单例模式 ...
*
* 三、封装性的体现,需要权限修饰符来配合
* 1、Java规定的4种权限:private、缺省、protected、public
* 2、4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
* 修饰类的话只能用public、缺省
*
* 总结封装性:Java提供了4种权限修饰符来修饰类以及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
*/
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
animal.name = "大黄";
// animal.age = 2; //'age' has private access
animal.setAge(2);
// animal.legs = 4; //'legs' has private access
animal.setLegs(4);
animal.show();
// animal.legs = -4;
animal.setLegs(-4);
animal.show();
int legs = animal.getLegs();
System.out.println(legs);
}
}
class Animal{
String name;
private int age;
// int legs; //腿的个数
private int legs; //腿的个数
//对属性的设置
public void setLegs(int l){
if (l >= 0 && l % 2 == 0){
legs = l;
}else {
legs = 0;
}
}
//对属性的获取
public int getLegs(){
return legs;
}
//提供age的get和set方法
public int getAge(){
return age;
}
public void setAge(int a){
if (age >= 0){
age = a;
}else {
age = 0;
}
}
public void eat(){
System.out.println("动物进食");
}
public void show(){
System.out.println("[ name = " + name + ", age = " + age + ", legs = " + legs + " ]" );
}
}
1.3 四种访问权限修饰符
Java权限修饰符public、protected、private置于类的成员
定义前,用来限制对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | yes | |||
(缺省) | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
对于class的权限修饰只可以用public和default(缺省)
- public类可以再任意地方被访问
- default类只可以被同一个包内部的类访问
二、类的成员之三:构造器
2.1 构造器的特征
- 它具有与类相同的名称
- 它不声明返回值类型(与声明为void不同)
- 不能被static、final、synchronized、abstaract、native修饰,不能有return语句返回值。
2.2 构造器的作用
创建对象;给对象进行初始化
如:Order o = new Order(); Person p = new Person(“Peter”,15);- 如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
2.3 语法格式
修饰符 类名(参数列表) {
初始化语句;
}
2.4 构造器分类
- 根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参、有参)
- 注 意:
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
2.5 构造器重载
构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。
public class Person{
public Person(String name, int age, Date d) {this(name,age);…}
public Person(String name, int age) {…}
public Person(String name, Date d) {…}
public Person(){…}
}
2.6 属性赋值过程
- 赋值过程如下:
- ①默认初始化
- ②显示初始化/代码块初始化
- ③构造器初始化
- ④通过 “对象.方法” 或者 “对象.属性”方式进行赋值
/**
*
* 总结:属性赋值的先后顺序
* (1)默认初始化
* (2)显示初始化
* (3)构造器初始化
* (4)通过 “对象.方法” 或者 “对象.属性”方式进行赋值
*
* 以上操作的先后顺序 (1) (2) (3) (4)
*/
public class UserTest {
public static void main(String[] args) {
User user = new User();
int age1 = user.getAge();
System.out.println("age: " + age1);
user.setAge(100);
int age2 = user.getAge();
System.out.println("age: " + age2);
}
}
class User{
String name;
int age = 1;
public User(){
age = 2;
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
}
三、扩展知识
3.1 JavaBean
-
JavaBean是一种Java语言写成的可重用组件。
-
所谓JavaBean,是值符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
-
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者 可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
-
示例:
/**
*
* JavaBean是一种Java语言写的可重用的组件
* 所谓JavaBean,是指符合以下标准的Java类
* (1)类是公共的
* (2)有一个无参的公共的构造器
* (3)有属性,且有对应的get、set方法
*/
public class Custom {
private int id;
private String name;
public int getId() {
return id;
}
public Custom() {
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.2 UML类图
四、关键字:this的使用
4.1 this是什么?
- 在Java中,this关键字比较难理解,它的作用和其词义很接近。
- 它在方法内部使用,即这个方法所属对象的应用;
- 它在构造器内部使用,表示该构造器正在初始化的对象
- this表示当前对象,可以调用类的属性、方法和构造器
- 什么时候使用this关键字呢?
- 当在方法内需要用到调用该方法的对象时,就用this。具体的:我们可以用this来区分局部变量和是属性。比如this.name = name;
4.2 this关键字使用
4.2.1 修饰内容
this可以用来修饰、调用:属性、方法、构造器
4.2.2 this修饰属性和方法
this理解为当前对象 或 当前正在创建的对象
- 在类的方法中,我们可以使用 “this.属性” 或 “this.方法” 的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略 “this.”。
- 特殊情况下,如果方法的形参和类的属性重名时,我们必须显示的使用 “this.属性”,表明此变量是属性,而非形参。
- 在类的构造器中,我们可以使用 “this.属性” 或 “this.方法” 的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略 “this.”。特殊情况下,如果方法的形参和类的属性重名时,我们必须显示的使用 “this.属性”,表明此变量是属性,而非形参。
4.2.3 this调用构造器
- 我们在类的构造器中,可以显示的
使用 “this(形参列表)” 方式,调用本类中指定的其它构造器。
构造器中不能通过 “this(形参列表)” 方式调用自己
如果一个类中有n个构造器,则最多有 n-1 个构造器中使用了 “this(形参列表)”
规定:“this(形参列表)” 必须声明在当前构造器的首行。
构造器内部,最多只能声明一个 “this(形参列表)” 方式,用来调用其他构造器。
注意:调用的构造器会有一个构造器没有调用this的那个就是调用super关键字的
public class PersonTest {
public static void main(String[] args) {
}
}
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name){
this(); // 调用本类中的无参构造器
this.name = name ;
}
public Person(String name, int age) {
this(name) ; // 调用有一个参数的构造器
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
五、关键字:package、import
5.1 package关键字的使用
- 为了更好的实现项目中类的管理,提供包的概念
- 使用package声明类或接口所属的包,声明在源文件的首行
- 包属于标识符,遵循标识符的命名规则、规范,“见名知意”
- 每
.
一次,就代表一层文件目录 - 补充:同一个包下,不可以命名同名的类、接口;不同的包下,可以命名同名的类、接口;
5.2 JDK主要的包介绍
1. java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
2. java.net----包含执行与网络相关的操作的类和接口。
3. java.io ----包含能提供多种输入/输出功能的类。
4. java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
5. java.text----包含了一些java格式化相关的类
6. java.sql----包含了java进行JDBC数据库编程的相关类/接口
7. java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
B/S C/S
5.3 import关键字的使用
- 在源文件中显示的使用import结构导入指定包下的类、接口
- 声明在包的声明和类的声明之间
- 如果需要导入多个结构,并列写出即可
可以使用 “xxx.*” 的方式可以导入xxx这个包下的所有结构
/**
*
*/
//import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
import pack1.pack2.Test;
public class PackTest{
public static void main(String args[]){
//Test类在pack1.pack2包中定义
Test t = new Test();
t.display();
}
}
-
如果使用的类或接口是 java.lang 包下定义的,则可以省略import结构
-
如果使用的类或接口是本包下定义的,则也可以省略import结构
-
如果在源文件中,使用了不同包下的同名的类,至少有一个类需要以全类名的方式显示
-
使用 “xxx.*” 方式表明可以调用xxx包下的所有结构,但是如果使用的是xxx子包下的结构,则仍需要显式的导入
-
import static: 导入指定类或接口中的静态结构 属性和方法
import static java.lang.System.*;
public class importDemo {
public static void main(String[] args) {
out.print("测试import");
}
}
六、面向对象特征之二:继承性
6.1 为什么要有继承
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需在定义这些属性和行为,只需要继承那个类即可。
- 此处的多个类称为子类(派生类),单独的这个类称为父类(基类或者超类)。可以理解为“子类is a 父类”。
6.2 类继承语法规则
class Subclass extends SuperClass {}
6.3 继承的好处
- 减少了代码的冗余,提供了代码的复用性
- 便于功能的扩展
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
6.4 注意点
-
子类继承了父类,就继承了父类的方法和属性。
-
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
-
在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
6.5 关于继承的规则
子类不能直接访问父类中私有的(private)的成员变量和方法。
6.6 单继承和多层继承
- Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2...//erro
- 示例
/**
*
* 面向对象的特征之二:继承性
*
* 一、继承的好处 why?
* (1)减少了代码的冗余,提高了代码的复用性
* (2)便于功能的扩展
* (3)为之后的多态性提供了前提
*
* 二、继承性的格式:class A extends B{}
* A:子类、派生类、subclass
* B:父类、超类、superclass
*
* 2.1 体现:一旦子类A继承了父类B以后,子类A就获取了父类B中声明的结构:属性和方法
* 特别的:父类声明为private的属性和方法,子类继承父类之后,仍然认为获取了父类中的私有结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
*
* 2.2 子类继承父类之后,还可以声明自己特有的属性或方法,实现功能的拓展。
* 子类和父类的关系,不同于子集和集合的关系
* extends:延展、扩展
*
* 三、Java中关于继承性的规定
* (1)一个类可以被多个子类继承
* (2)Java中类的单继承性:一个类只能有一个父类
* (3)子父类是相对的概念
* (4)子类直接继承的父类,称为直接父类。间接继承的父类,间接父类。
* (5)子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
*
* 四、1、如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
* 2、所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
* 3、意味着,所有的java类都具有java.lang.Object类声明的功能
*
*/
public class ExtendTest {
public static void main(String[] args) {
Person person = new Person();
person.setName("张三");
person.age = 28;
person.eat();
Student student = new Student();
student.setName("李四");
student.age = 30;
student.major = "计算机";
student.eat();
student.study();
student.breath(); //间接父类中的方法
Creature creature = new Creature();
}
}
七、方法的重写(override/overwrite)
7.1 定义
在子类中可以根据需要对父类中继承来的方法进行改造,也称为方法的重置、覆盖
。在程序执行时,子类的方法将覆盖父类的方法。
7.2 要求
-
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
-
返回值类型
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
- 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型。
-
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限(多态)
- 子类不能重写父类中声明为private权限的方法
-
子类方法抛出的异常不能大于被重写方法的异常
7.3 注意
子类与父类中同名参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法属于类的,子类无法覆盖父类的方法
static修改的属于类的方法,不能被重写
7.4 示例
/**
*
* 方法的重写(override / overwrite)
* 1、重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
* 2、应用:重写以后,当创建子类对象以后,通过子类对象调用字父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
* 3、重写的规定:
* 方法的声明:权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型 {方法体}
* 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
* (1)子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同。
* (2)子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
* 特殊情况:子类不能重写父类中声明为private权限的方法
* (3)返回值类型
* 父类被重写的方法返回值类型是void,则子类重写的方法的返回值类型只能是void
* 父类被重写的方法返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或者A类的子类型
* 父类被重写的方法返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
* (4)子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
*
* 子类和父类中同名同参数的方法,要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)
*
*
* 面试题:区分方法的重载与重写
*/
public class StudentTest {
public static void main(String[] args) {
Student student = new Student("计算机技术");
student.setName("张三");
student.setAge(18);
student.walk(10);
student.study();
student.eat();
}
}
八、关键字:super
8.1 super的使用
- 我们可以在子类的方法或构造器中。通过使用“super.属性”或“super.方法”的方式,显示的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”
- 特殊情况:
当子类和父类中定义了同名的属性时
,我们要想在子类中调用父类中声明的属性,则必须显示的使用“super.属性”的方式,声明调用的是父类中声明的属性。 - 特殊情况:
当子类重写了父类中的方法以后
,我们想在子类的方法中调用父类中被重写的方法时,则必须显示的使用“super.方法”的方式,表名调用的是父类中被重写的方法。
8.2 super调用构造器
- 我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器
- “super(形参列表)”的使用,
必须声明在子类构造器的首行
- 我们在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现
- 在构造器的首行,没有显示的声明“this(形参列表)”和“super(形参列表)”,
则默认调用的是父类中空参的构造器“super()”
- 在类的构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器
8.3 示例
/**
*
* super关键字的使用
* 1、super理解为:父类的
* 2、super可以用来调用属性、方法、构造器
* 3、super的使用
* (1)我们可以在子类的方法或构造器中,通过使用 “super.属性” 或 “super.方法” 的方式,显示的调用父类中声明的属性或方法。
* 但是通常情况下,我们习惯省略 “super”
* (2)特殊情况:当子类和父类中定义了同名的属性时,我们想要在子类中调用父类声明的属性,则必须显示的使用 “super.属性” 的方式,表明调用的是父类中声明的属性。
* (3)特殊情况:当子类重写了父类的方法以后,我们想要在子类中调用父类中被重写的方法时,必须显式的使用 “super.方法” 的方式,表明调用的是父类中被重写的方法。
*
* 4、super调用构造器
* (1)我们可以在子类的构造器中显式的使用 “super(形参列表)” 的方式,调用父类中声明的指定的构造器
* (2)“super(形参列表)” 的使用,必须声明在构造器的首行
* (3)我们在类的构造器器中,针对“this(形参列表)” 和 “super(形参列表)” 只能二选一
* (4)在构造器的首行,没有显式的声明 “this(形参列表)” 和 “super(形参列表)”,则默认调用的是父类中空参的构造器 “super()”
* (5)在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器
*/
public class CylinderTest {
public static void main(String[] args) {
// Cylinder cylinder = new Cylinder();
Cylinder cylinder = new Cylinder(1,2);
double area = cylinder.findArea();
System.out.println("圆柱的表面积: " + area);
double volume = cylinder.findVolume();
System.out.println("圆柱的体积: " + volume);
}
}
public class Circle {
private double radius;
public Circle() {
System.out.println("Circle()");
radius = 1;
}
public Circle(double radius) {
System.out.println("Circle(double radius)");
this.radius = radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius() {
return radius;
}
/**
* 计算圆的面积
* @return 返回圆的面积
*/
public double findArea(){
return Math.PI * Math.pow(radius, 2);
}
}
public class Cylinder extends Circle {
private double length;
public Cylinder() {
// super(); //默认调用
length = 1;
}
public Cylinder(double length) {
this.length = length;
}
public Cylinder(double radius, double length) {
super(radius);//super调用父类的构造器
this.length = length;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
/**
* 计算圆柱的表面积
* @return
*/
public double findArea() {
// return Math.PI * Math.pow(getRadius(), 2) * 2 + 2 * Math.PI * getRadius() * length;
return super.findArea() * 2 + 2 * Math.PI * getRadius() * length; //super调用父类的方法
}
/**
* 计算圆柱的体积
* @return 返回圆柱的体积
*/
public double findVolume(){
// return Math.PI * Math.pow(getRadius(), 2) * length;
return super.findArea() * length; super调用父类的方法
}
}
8.4 this和super的区别
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
九、子类对象实例化的全过程
- 从结果上来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性(非private的)。
- 从过程上来看:
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang. Object类中空参的构造器为止。正因为加载所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用
- 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
- 思考
- 为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
解答:this()和super()是不可以同时出现在同一个构造函数中的。
原因如下:创建一个子类的实例时,首先会调用父类的构造函数,然后再调用子类的构造函数,如果父类中没有缺省构造函数,则必须再子类的构造函数中显示的调用它(如下面的例子),
在程序中的顺序是这样的:
- 为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
super(...) //父类构造函数
..... //当前类构造函数语句
同样的道理,当一个类中有多个构造函数的时候,在其中一个构造函数中也可以先调用其他的构造函数来初始化对象,这种方法叫做“显式构造方法调用”,当那样的构造方法被调用,它将执行通常的super() 过程以及后续的操作。
然后在执行本构造函数中的构造语句,这个时候的顺序是这样的:
this(....) //当前类的其他构造函数
... //当前构造函数的其他语句
+ 为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?
解答:无论通过哪个构造器创建子类对象,需要保证先初始化父类。目的:当子类继承父类后,“继承”父类中所有的属性和方法,因此此子类有必要知道父类如何为对象进行初始化。
十、面向对象特征之三:多态性
10.1 多态性的体现
-
多态性,是面向对象中最重要的概念,在Java中的体现:
对象的多态性:父类的引用指向子类的对象
- 可以直接应用在抽象类和接口上
10.2 为什么出现多态性
Java引用变量有两个类型:编译时类型和运行时类型
。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,
“看左边”
:看的是父类的引用(父类中不具备子类特有的方法)
“看右边”
:看的是子类的对象(实际运行的是子类重写父类的方法)
10.3 正常的方法调用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
10.4 虚拟方法调用(多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此事父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译器是无法确定的。
Person e = new Student();
e.getInfo();//调用Student类的getInfo()方法
10.5 编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。————————动态绑定
10.6 多态性的使用前提:
①类的继承关系 ②方法的重写
10.7 多态性的适用
- 对象的多态性,只适用于方法,不适用于属性
10.8 虚拟方法调用举例
前提:Person类中定义了welcome()方法,各个子类重写了welcome()。
执行:多态的情况下,调用对象的welcome()方法,实际执行的是子类重写的方法。
10.9 方法的重载与重写 关于多态性
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了
。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为**“早绑定”或“静态绑定”
**;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为**“晚绑定”或“动态绑定”
**。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。
”
10.10 instanceof操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型
-
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
-
如果x属于类A的子类B,x instanceof A值也为true。
public class Person extends Object {…}
public class Student extends Person {…}
public class Graduate extends Person {…}
-------------------------------------------------------------------
public void method1(Person e) {
if (e instanceof Person)
// 处理Person类及其子类对象
if (e instanceof Student)
//处理Student类及其子类对象
if (e instanceof Graduate)
//处理Graduate类及其子类对象
}
10.11 对象类型转换
-
基本数据类型的Casting
- 自动类型转换:小的数据类型可以自动转换成大的数据类型
long g=20; double d=12.0f
- 强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
- 从子类到父类的类型转换可以自动进行
- 从父类到子类的类型转换必须通过造型(强制类型转换)实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof操作符测试一个对象的类型
float f=(float)12.0; int a=(int)1200L
-
练习
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到之类中。
- 对于实力变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
public class ParentClass {
int num = 10;
public void say() {
System.out.println("----------父类输出----------");
}
}
public class SonClass extends ParentClass {
int num = 5;
public void say() {
System.out.println("----------子类输出----------");
}
}
public class ClassDemo {
public static void main(String[] args) {
ParentClass parentClass = new SonClass();
//父类的变量
System.out.println("parentClass.num = " + parentClass.num);
//子类的方法
parentClass.say();
}
}
十一、Object类的使用
11.1 Object类的使用
- Object类是所有Java类的根父类
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
public class Person { ... }
等价于
public class Person extends Object {
...
}
例如:
method(Object obj){…} //可以接收任何类作为其参数
Person o=new Person();
method(o);
- Object类中的主要结构
方法名称 | 类型 | 描述 |
---|---|---|
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public int hashCode() | 普通 | 取得Hash码 |
public String toString() | 普通 | 对象打印时调用 |
11.2 ==和equals的区别
- ==和equals的区别
- == 操作符
基本类型比较值:只要两个变量的值相等,即为true。
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回trueint a=5; if(a==6){…}
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错Person p1=new Person(); Person p2=new Person(); if (p1==p2){…}
- equals方法
- 是一个方法,而非运算符
- 只能适用于引用数据类型
- 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址值是否相同,而是比较两个对象的“实体内容”(属性)是否相同
- 通常情况下,我们自定义的类如果使用equals()的话,通常也是比较两个对象的“实体内容”是否相同,那么,我们就需要对Object类中的equals()方法进行重写。
- 重写的原则:比较两个对象的实体内容是否相同
- Object类中equals()的定义:
public boolean equals(Object obj) { return (this == obj); }
- == 操作符
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一对象实体
-
重写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”。 -
面试题:== 和equals的区别
- == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
- equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
- 具体要看自定义类里有没有重写Object的equals方法来判断。
- 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
11.3 toString()方法
- toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址
- 在进行String与其它类型数据的连接操作时,自动调用toString()方法
Date now = new Date();
System.out.println("now=" + now);
相当于
System.out.println("now=" + now.toString());
- 可以根据需要在用户自定义类型中重写toString()方法
如String类重写了toString()方法,返回字符串的值
s1 = "hello";
System.out.println(s1);//相当于System.out.println(s1.toString());
- 基本类型数据转换为String类型时,调用了对应包装类的toString()方法
int a= 10;System.out.println("a=" + a);