面向对象程序设计概述
- 面向过程
先确定如何操作数据,再决定数据的结构。适用于小规模问题 - 面向对象OOP:
先决定数据的结构,再考虑操作数据的算法。适用于大规模问题
类
1. 类是构造对象的模板或蓝图
2. 封装是处理对象的一个重要概念
就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式
- 实例字段:对象中的数据
- 方法:操作数据的过程
- 对象的状态:特定对象有一组特定的实例字段值,这些值的集合就是这个对象的当前状态。
3.OOP原则:
- 封装:绝对不能让类中的方法直接访问其他类的实例字段
- 扩展:可以通过扩展其他类来构建新类
4. 继承
- 基于已有的类创建新的类。继承一个类就是复用(继承)这些类的方法,而且可以增加一些新的方法和字段。
5. 定义子类
- extends关键字
- 父类,又叫基类、超类
- 子类,又叫派生类、孩子类
- 子类比父类拥有的功能更多
- 通过扩展父类定义子类时,只用指出子类与父类的不同之处
- 一般的方法放在父类中,更特殊的方法放在子类中
6. 覆盖方法
- 超类中的有些方法对子类并不一定适用,此时需要在子类中覆盖(重写)父类方法
- 实用super关键字可以调用父类方法,避免覆盖父类方法时,调用父类方法造成不必要的递归(父类和子类中的方法同名时,用
super.方法名()
调用父类方法) - super不是一个对象的引用,不能将super赋值给另一个对象变量
7. 子类构造器
- super(…)调用父类构造器
- 父类构造器必须放在子类构造器的第一条
- 若在子类构造器没有显式调用父类构造器,将自动调用父类的无参构造器
- 若父类没有无参构造器,必须要在子类构造器中明确指明调用父类哪个构造器(输入具体的参数super(参数1,参数2));否则,java编译器就会报错
在运行时能够自动选择适当的方法,成为动态绑定
例如:
我们假设Fatherft=newSon();ft.say();Son继承自Father,重写了say()。
ft.say()调用的是Son中的say(),这里就是动态绑定机制的真正体现。
8. this与super关键字
- this关键字
- 隐式参数的引用 (this.name)
- 调用该类的其他构造器(this(参数1,参数2…))
- super关键字
- 调用父类方法()
- 调用父类构造器
注意:this可以作为当前对象的引用,但那时super却不可以作为父类对象的引用(即不能用super.name)
对象
- 对象的状态改变必须通过调用方法实现(如果不经过方法改变对象状态,说明破坏了封装性)
- 对象的状态不能完全描述一个对象,每个对象有一个唯一标识
- 作为同一个类的实例,每个对象的标识总是不同的,状态往往也存在差异
类之间的关系
- 依赖:一个类的方法使用另一类的对象
- 聚合:类A的对象包含类B的对象
- 继承:一个更特殊的类和又给更一般的类之间的关系
继承与多态
1. 继承层次
- 由一个公共超类派生出来的所有类的集合称为继承层次
- 在继承层次中,从某个特定的类到其祖先的路径成为该类的继承链(子到父)
java中不支持多继承,但那时可以通过接口来实现
2. java中,对象变量时多态的
- 父类类型的变量既可以引用自身类型的变量,还可以引用子类类型的变量
- 但是,子类类型的变量不可以引用父类类型的变量
3. 子类可以调用父类public、protected、包权限的方法
但是父类不可以调用子类的特有的方法(子类的特殊属性)
- 纯父类变量(左右都是父类)不可以调用子类的任何方法
- 上转型变量(左边是父类,右边是子类)可以调用子类重写父类的方法(多态),但是仍然不能调用子类独有的方法
4. 理解方法调用
以调用x.f(arg)
为例,隐式参数x为类C的一个对象:
-
编译器查看对象的声明类型和方法名
编译器查找C类中所有名为f的方法和父类中名为f且可以访问的方法(父类private方法不可访问),此时编译器知道所有可能被调用的候选方法 -
编译器确定方法调用中提供的参数类型
重载解析:在所有名为f的方法中,找到一个与所提供参数类型完全匹配的方法,此时,编译器已经知道需要调用的方法名字和参数类型。 -
静态绑定
如果private方法,static方法,final方法或者构造器,那么编译器可以将准确地知道应该调用哪个方法,此时调用方法只用考虑x的类型(其他情况,若在C类中找不到f方法,则向其父类中找),不需要考虑类C的父类(因为这几种修饰的方式都不能被继承)
只有静态绑定成功,即编译通过了,才能进入运行阶段。 -
动态绑定
如果调用的方法依赖隐式参数(多态的对象)的实际类型,那么必须在运行时使用动态绑定。虚拟机必须调用与x所引用对象的实际类型对应的那个方法。
例如:x的实际类型是D,他是C类的子类
如果:D类定义了方法f(String) ,直接调用它
否则:将在D类的超中寻找f(String),以此类推
在覆盖一个方法的时候,子类方法不能低于父类方法的可见性。
比如,父类的方法为public,子类必须为public
如果子类遗漏了public,编译器就会报错。(这样多态就不能实现)
5. 阻止继承
- final关键字
- final修饰字段
- 基本类型:不可更改
- 引用类型:不可指向新的引用,但是对象的状态可能会改变
- final修饰类:该类不可以被继承
- final修饰方法:子类不能覆盖这个方法
- final修饰字段
- 将方法或类声明为final主要原因
确保他们不会在子类中改变语义
6. 强制类型转换
7. 向上转型
- 子类- >父类型 (左父右子,多态)
- 又被称为:自动类型转换
7. 向下转型
- 父类-> 子类
- 又被称为:强制类型转换
进行强制类型转换的唯一原因是:
在暂时忽视对象的实际类型后,使用对象的全部功能(父类不能调用子类特有的方法)
无论是向上转型,还行向下转型,这两种类型之间必须要有继承关系;否则,编译器报错。
可以将上转型对象转化为子类对象,但是不能将父类对象强制转换类型换为子类对象。(两种向下转型方式)
小节:
1. 只能在继承层次nn内进行转换
2. 在将超类转为子类之前,应该使用instanceof进行检查
通过类型转换调整对象的类型并不是一种好的做法。在一般情况下,应该尽少用强制类型转换和instanceof运算符。
大多情况下,因为多态性的动态绑定机制,我们不用将Employee转换为Manager也能正确调用方法(运行期间,找到引用实际指向的对象类型调用该对象的方法)
只有在使用manager特有的方法才需要进行强制类型转换,例如setBonus方法
但是,此时应该自问超类设计是否合理。是否需要重新设计超类,并添加setBonus方法,这才是更合适的选择。
抽象类
- abstract修饰方法
- 抽象方法,不用实现,在具体的子类中实现
- 若子类未实现抽象方法,仍然要定义为抽象类
- abstract修饰类
- 抽象类 ,包含一个或多个抽象方法(也可以一个也不包含)
- 抽象类也可以包含字段和具体方法
- 提高程序清晰度
- 父类中定义抽象方法,子类中具体实现
- 变量定义为父类(抽象类)类型,具体实现 (new)子类类型(上转型)
- 方法调用,通过父类变量(多态,动态绑定)
使用预定义类
1.对象与对象变量
- 对象变量并没有包含一个对象,它只是引用一个对象
- 在java中,任何对象变量的值都是对存储在另一个地方某个对象的引用
- new操作符的返回值也是一个引用
- java对象都存储在堆中,当一个对象包含另一个对象变量时,它只是包含着另一个对象的指针
- java中如果使用一个没有初始化的指针,运行时系统将会产生一个运行时的错误(调用这个引用变量时,会发生运行时错误,基本变量和引用对变量默认值时,只有成员变量才会给默认值),同时不必担心内存管理问题,垃圾回收器会处理相关事宜
- java中必须使用clone方法获得对象的完整副本。
2. Java中的LocalDate类
- java类库包含两种保存时间的类
- Date类: 表示时间点
- LocalDate类: 日历表示法表示日期
- 一般通过这三种静态工厂方法构造一个LocalDate
LocalDate.of(2020,4,20)
LocalDate.now()
LocalDate.parse("2021-04-20")
- 使用方法:
- 访问器方法:只访问对象而不修改对象
- 更改器方法:调用方法之后修改对象的内容。
用户自定义类
1.主力类:通常没有main方法,却有自己的实例字段和实例方法;(例如:自定义User类)
2. 一个源文件中只能有一个公共类,但是可以有任意数量的非公共类
-
多个源文件的使用
将两个类放在一个单独类文件中
例如:Employee类存放在文件EmploeeTest.java中,然后键入以下指令javac EmploeeTest.java
,不过编译器会发现需要使用这两个类时,会自动搜索Employee.java并编译这个文件 -
构造器方法
构造器与类名同名,构造器总是结合new运算符来调用,不能对一个已经存在的对象用构造器来达到重新设置实例字段的目的,(new以后出翔新的对象,两个对象的地址不同) -
用var声明局部变量(java10中)
在java10中,如果可以从变量的初始值推导他们的类型,那么可以使用var关键字声明局部变量,而无需指定类型
var关键字只能用于方法中的局部变量,参数和字段的类型必须声明 -
使用null引用
定义一个类时,最好清楚哪些字段可能为null,对于初始化时字段可能为null,一般有两种解决方案:- 宽容型
将null参数转换成一个非null值
- 严格型
直接拒绝null参数
- 宽容型
-
隐士参数和显示参数
- 隐士参数:
出现在方法名前的对象的参数 - 显示参数
位于方法名后面括号里的数值
每个方法中,用this关键字指示隐士参数,这样可以将 实例字段 和局部变量明显的区分出来
- 隐士参数: