一、类关联类结构
- 关联关系
- 依赖关系
- 继承关系
- 实现关系(本质也是继承关系)、接口的关系
1.1 关联关系
- 对象和对象之间的连接。在Java 中关联关系的代码表现形式为一个类作为另一个类的属性而存在
- 即“有”的关系:“has-a”
1.1.2 关联关系的方向
- 关联关系分为单向关联和双向关联
- 单向关联: A类关联B类。
- 双向关联: A类关联B类,B类关联A类;
// 双向关联
public class Phone{
private Person per;
}
public class Person{
private Phone p;
}
1.1.3 关联关系的多重性
- 一对一关联:一个学生,只能在一个班级学习
public class Classes {
private Student cla;//班级
}
public class Student{
}
- 一对多关联:一个学生,可以参加多个班级学习。
1.1.4 解决一对多的方案
- 集合
- 数组
// 集合
public class Classes{
}
public class Student {
private List Classes;
}
// 数组
public class Student {
private Classess[] Classes;
}
1.2 依赖关系
1.2.1 依赖关系的定义
- 依赖关系(use-a)
- 指一个类A使用了另一个类B
- 依赖关系的特性
- 这种关系是具有偶然性的、临时性的、非常弱的、但是类B的变化会影响到类A在某个 method
- 方法的使用
public class Person{
public void travel(Bus bus) {
}
}
- 关联是“HAS”关系,依赖是“USE”关系
- A类关联B类,指的是B类对象作为A类的属性存在,称为“has”关系。
- A类依赖B类,指的是B的对象作为A类的方法参数存在,称为”use “关系。
- 生命周期不同
- 如果A类关联B类,那么创建A类的对象时实例化B类的对象,直到A类对象被销毁,所关联的B类对象也被销
毁。即只要A类对象存在,B类对象就存在。
2.3 封装实现细节
- 类中的属性一般使用 private 属性,提供 getter 和 setter 方法供外界访问并修改
封装:隐藏类的实际细节,对外提供访问接口
public class Person {
private String name;
private int age;
private String gender;
public Person() {
super();
}
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;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
Tips: 在真实开发中,我们一般会把这样的类成为实体类 (entity)
2.4 封装的好处
- 数据安全:保证数据安全性
- 方便调用:提供清晰的对外接口
-
三、继承
3.1 继承的概念
继承机制是面向对象程序设计不可缺少的关键概念,是实现软件可重用的根基,是提高软件系统的可扩展性与可维护性的主要途径。
- 所谓继承是指一个类的定义可以基于另外一个已经存在的类,即子类基于父类,从而实现父类代码的重用,子类能吸收已有类的数据属性和行为,并能扩展新的能力。
- 继承描述的是 is-a 的关系
3.2 继承的作用
- 实现代码复用 ( 子类会获得父类的 属性 和 方法)
- 减少代码冗余
3.3 继承的形式
【访问权限修饰符】 【修饰符】 子类名 extends 父类名 {子类体]
// 父类,基类
class 汽车 {
private int wheels;
public int getWheels() {
return wheels;
}
public void setWheels(int wheels) {
this.wheels = wheels;
}
// ------------------------
public void 开车() {
System.out.println("开车了");
}
public void 刹车() {
System.out.println("刹车");
}
}
// 子类、衍生类
class 卡车 extends 汽车 {
private int carryingCapacity;
public int getCarryingCapacity() {
return carryingCapacity;
}
public void setCarryingCapacity(int carryingCapacity) {
this.carryingCapacity = carryingCapacity;
}
// ------------------------
public void 挂箱() {
System.out.println("挂箱");
}
}
// 子类、衍生类
class Bus extends 汽车 {
private int seatings;
public int getSeatings() {
return seatings;
}
public void setSeatings(int seatings) {
this.seatings = seatings;
}
// ------------------------
public void 报站(String str站名) {
System.out.println(str站名 + "已到");
}
}
//演示
public class 继承 {
public static void main(String[] args) {
卡车 a=new 卡车();
a.开车();
Bus b=new Bus();
b.刹车();
}
}
3.4 继承中构造方法的调用
- 子类构造方法总是先调用父类构造方法
- 默认情况下,调用父类无参构造方法
- 可以在子类构造方法第一行,使用 super() 关键字调用父类任意一个构造方法
一般在子类写构造方法的时候,都会默认生成 super
// 父类,基类
public class Person{
private String name;
private int age;
public Peron(){
}
public Peron(String name, int age) {
this.name = name;
this.age = age;
}
}
// 子类、衍生类
public class Alice extends Person{
// 调用自己的无参构造方法
public Alice() {
}
public Alice() {
super(); // 调用父类的无参构造方法
}
public Alice(String name, int age) {
// 在子类构造方法中的第一行语句,使用 super 调用父类构造方法
super(name,age); // 调用父类的有参构造方法
}
public void eat() {
System.out.println("eat");
}
}
3.5 方法的重写(覆盖)
- 重写:如果父类的方法无法满足子类的使用,那么子类可以使用同名方法覆盖父类方法
要求:方法名一致,参数列表一致,返回名一致、访问修饰符不能比父类的权限小
3.6 继承的坏处
继承是实现类复用的重要手段,但是继承会破坏封装。 每个类都应该封装其内部信息和实现细节,仅暴露必要的方法给其他类使用。但子类可以直接访问父 类的内部信息,甚至可以重写父类的方法,这增加了子类和父类的耦合度,破坏了封装。 设计父类时,应遵循以下原则:
尽量将父类的成员变量设置为private。
- 尽量缩小方法的访问权限,能私有尽量不要共有。
- 不希望被子类重写的方法设置为final。
- 不要在父类的构造方法中调用被子类重写的方法。
四、多态
- 多态性是面向对象三大特征之一
- 多态的含义:对外一种表现形式,内部有多种具体实现
- 多态的体现
- 方法的重载
- 方法覆盖
- 多态参数
4.2 编译期 与 运行期
我们创建对象一般是 类名 名 = new 类名();
- 等号组边成为编译器
- 等号右边称为运行期
下面围绕 编译期 和 运行期 展开讲解
// 最常见的方式
A a = new A();
a.show();
// 当 A 是 B的父类的时候,我们还可以这样做
A ab = new B();
ab.show();
// 对象类型转换
B b = (B)ab;
4.3 父类的引用指向子类
- 编译类型是父类,运行期是子类,被称为父类引用指向子类的对象
class Animal{
...
}
class Cat extends Animal {
...
}
class Dog extends Animal {
...
}
Cat m = new Cat();
Animal a = new Cat(); // Animal 的引用指向 Cat 的对象
// 对象 m 可以调用 猫类的的所有方法,x 只能调用 动物类中定义的方法,猫类扩展的方法不能调用
// 在集合中,我们会经常使用到这个方法
List list = new ArrayList(); // 这也是父类引用子类的常见使用
4.4 多态环境下的调用
4.4.1 对成员方法的调用
class Animal{
void show() {
System.out.println("Animal");
}
}
class Cat extends Animal {
void show() {
System.out.println("Cat");
}
}
...
Animal x = new Cat();
x.show(); // 调用的是子类中的方法
4.4.2 对静态方法的调用
class Animal{
static void show() {
System.out.println("Animal");
}
}
class Cat extends Animal {
static void show() {
System.out.println("Cat");
}
}
...
Animal x = new Cat();
x.show(); // 调用的动物类中静态成员方法
4.4.3 对成员变量法的调用
class Animal{
int num = 3;
}
class Cat extends Animal {
int num = 4;
}
...
Animal x = new Cat();
x.num; // 调用的动物类中成员变量
编译和运行都看等号左边
变量不存在被子类覆盖的说法,只有方法存在覆写
4.5 多态参数
- 方法具有多态性
class Animal{
void eat() {}
}
class Cat extends Animal {
void eat() {}
}
class Dog extends Animal {
void eat() {}
}
// 方法的形式参数类型是父类类型,而传递的实际参数可以是任意子类对象
method(Animal animal) {
animal.eat();
}
方法参数多态性的好处:提高代码扩展性
4.6 多态环境下的对象造型
- 向上造型 (提高程序扩展性)
class Animal{
abstract void eat();
}
class Cat extends Animal{
void look() {
System.out.println("看家");
}
}
...
Animal x = new Cat(); // 向上造型, Cat 对象提升到 Animal 对象
x.eat(); // 只能使用父类中的方法
x.look(); // 报错,不能使用子类中的方法
4.7 instanceof 操作符
- instanceof 是判断对象类型的,常用来判断一个对象类是否为一个特定对象的实例
result = 对象名称 instanceof 类型
class Person {
...
}
Person p = new Person();
boolean result = Person instanceof p;
4.8 多态的两种表现形式
- 如果一个类中没有包含足够的信息来描述一个具体的对象,这样的类就是抽象类
- 比如形状类就是抽象类,圆形,三角形就是具体的类
- 用 abstract 修饰的类就是 抽象类。如果某个类中包含有抽象方法那么该类必须定义为 抽象类 ,但是抽象类不一定有抽象方法
- 抽象类可以有 成员属性 和 非抽象成员方法
- 抽象类不能被实例化,但是可以有构造函数
- 抽象类只能用作基类,表示的是一种继承关系。继承抽象类的非抽象类必须实现其中的所有抽象方法,而已实现方法的参数、返回值要和抽象类中的方法- -样。否则,该类也必须声明为抽象类。
5.1.1 抽象类的定义
使用关键字 abstract 定义抽象类
[访问权限] abstract class 类名 {
成员列表
}
比如:
public abstract class Shapes {
public abstract void draw();
}
public abstract class Shapes{
public void draw() {
//具体代码
}
}
5.1.2 抽象类的注意事项
- 抽象类可以有构造方法,但不能直接实例化,只能用来继承;
- 抽象类的派生子类应该提供对其所有抽象方法的具体实现如果抽象类的派生子类没有实现其中的所有抽象方法,那么该派生子类仍然是抽象类,只能用于继承,而不能实例化;
- 抽象类中也可以包含有非抽象的方法,子类中重写非抽象方法时返回值和参数必须与父类一致;
- 构造方法和静态方法不可以修饰为 abstract。
5.2 抽象方法
5.2.1 抽象方法的语法
[访问权限] abstract 返回值类型 方法名(参数列表);
// 这里定义了一个抽象方法,为 what to do (做什么)
public abstract void draw(); // 父类 Shapes
// 怎么做,要在子类中具体实现 How to do
@overide
public abstract void draw() {
// 方法体
}
5.2.2 抽象方法的相关概念
- 类无法提供具体的实现,可以将此方法声明成 抽象方法 是撒店
- 在类中没有方法体的方法,就是抽象方法
-
5.3 抽象的作用
在面向对象领域,抽象类主要用来进行类型隐藏;也就是使用抽象的类型来编程,但是具体运行时就可以使用具体类型。
- 利用抽象的概念,能够在开发项目中创建扩展性很好的架构,- 优化程序。
抽象类,抽象方法,在软件开发过程中都是设计层面的概念。也就是说,设计人员会设计出抽象类,抽象方法,程序员都是来继承这些抽象类并覆盖抽象方法,实现具体功能。
六、面向对象中相关的关键字
6.1 this 关键字
6.1.1 this 介绍
this关键字代表自身,在程序中主要的用途
- 使用this关键字在自身构造方法内部引用其它构造方法
- 使用this关键字代表自身类的对象.
- 直接使用this
- 使用this关键字引用成员变量
- 使用this关键字引用成员方法
注意: this关键字必须放在非静态方法里面,因为 静态方法 和当前对象没有关系
6.1.2 this 的作用
- this 作为代表当前作用
- 使用 this 可以在当前成员方法中调用其他方法
- 使用 this 也可以在构造方法中引用其它构造方法
6.1.3 示例
一个人的对象
public class Person {
private String name;
private int age;
private String gender;
Person p; // 使用this 代表当前对象
public Person() {// 使用this 代表当前对象
p = this;
}
// this 在构造方法中引用其它构造方法
public Person() {
this("alice");
}
public Person(String name) {
this.name = name;
}
// 在类内部,成员方法之间的相互调用也可以使用 this.方法名引用
public void a() {
System.out.println("我是a");
}
public void b() {
this.a();
}
.... getter 和 setter 省略
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
6.2 super 关键字
super 一般会在继承关系中使用
- 在子类构造方法中要调用父类的构造方法,需要注意: super语句只能出现在子类构造方法体的第-一行。
- 当子类方法体中的局部变量或者子类的成员变量与父类成员变量同名时,即子类局部变量覆盖父类成员变量时,用“super.成员变量名”来引用父类成员变量
- 当子类的成员方法覆盖了父类的成员方法时,也就是子类和父类有完全相同的方法定义(方法体可以不同),此时,用“super.方法名(参数列表)”的方式访问父类的方法。
- 与this的区别,this 通常指代当前对象,super 通常指代父类。
代码参考继承中的 demo 继承
6.3 方法覆盖
6.3.1 方法覆盖的定义
子类可以重写父类中某一个方法,称为方法覆盖,也称方法重写,是继承中非常重要的知识点。如果子类需要修改从父类继承到的方法的方法体,就可以使用方法覆盖。
6.3.2 方法覆盖的原则
- 同名
- 同参
- 同返回值
- 访问权限不能缩小
6.3.3 示例
public class Person() {
....
public void play() {
System.out.println("HAHA");
}
}
public class Alice() extends Person {
...
public void play() {
System.out.println("hehe"); // 方法覆盖了
}
}
个人认为方法覆盖是多态的主要表现形式,因为方法重载是在编译器就已经规定好了调用什么方法,而在方法覆盖是处于运行期选择该调用什么方法。
七、数据表与简单 Java 类之间的映射
7.1 简单映射
- 简单 java 类的定义是根据数据表的结构来实现简单 java 类
- 数据表与简单 java 类之间的基本映射关系如下
- 数据实体表设计 = 类的定义;
- 表中的字段 = 类的成员属性
- 表的一行记录 = 类的一个实例化对象
- 表的多行记录 = 对象数组;
- 表的外键关联 = 引用关联
分析一下,如上数据表中有如下关联存在
- 根据部门信息获得以下内容:
- 一个部门的完整信息
- 一个部门之中所有雇员的完整信息
- 一个雇员对应的领导信息
- 根据雇员信息得到以下内容:
- 一个雇员所在部门信息;
- 一个雇员对应的领导信息;
对于数据表与简单 java 类之间的映射最好的解决步骤:
- 先抛开所有外键关联字段,写出基类的基本组成,然后配置关联字段的关系
- 分别定义 Emp 和 Dept 两个实体类
- 根据表结构进行对象的配置
- 根据要求通过结构获取数据
映射转换
class Dept {
private long deptno;
private String dname;
private String loc;
private Emp emps[]; // 多个雇员信息
public Dept(long deptno, String dname, String loc) {
this.deptno = deptno;
this.dname = dname;
this.loc = loc;
}
public void setEmps(Emp [] emps) {
this.emps = emps;
}
public Emp [] getEmps() {
return this.emps;
}
//setter 和 getter 略
public String toString() {
return xxxx;
}
}
class Emp {
private long empno;
private String ename;
private String job;
private double sal;
private double comn;
private Dept dept;
private Emp mgr;// 所属领导
public Emp(long empno, String ename, String job, double sal, double comn) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
this.comn = comn;
}
// getter 和 setter 省略
public String toString() {
return xxxx;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public void setMgr(Emp mgr) {
this.mgr = mgr;
}
public Dept getDept() {
return this.dept;
}
public Emp getMgr() {
return this.mgr;
}
}
public class JavaDemo {
public static void main(String[] args) {
// 根据类进行定义
Dept dept = new Dept(10,"财务部","上海");
Emp empA = new Emp(7369L,"SMITH","CLERK",800.00,0.0);
Emp empB = new Emp(7566L,"FORD","MANAGER",24500.00,0.0);
Emp empC = new Emp(7839L,"KING","PRESENT",5000.00,0.0);
// 需要为对象进行关联设置
empA.setDept(dept); // 设置雇员与部门的关联
empB.setDept(dept); // 设置雇员与部门的关联
empC.setDept(dept); // 设置雇员与部门的关联
empA.setMgr(empB); // 设置雇员与领导的关联
empB.setMgr(empC); // 设置雇员与领导的关联
dept.setEmps(new Emp[] {empA,empB,empC}) ; // 部门与雇员
}
}
7.2 一对多映射
数据表
要求:
- 按照表的要求将表的结构转换为类结构,同时可以获取如下信息
- 可以根据分类获取其对应的所有子分类的信息。
class Item {
private long iid;
private String title;
private SubItem subitems [];
public Item(long iid, String title) {
this.iid = iid;
this.title = title;
}
public void setSubitems(SubItem[] subitems) {
this.subitems = subitems;
}
public SubItem[] getSubitems() {
return this.subitems;
}
@Override
public String toString() {
return "Item{" +
"iid=" + iid +
", title='" + title + '\'' +
", subitems=" + Arrays.toString(subitems) +
'}';
}
}
class SubItem {
private long sid;
private String title;
private Item item;
public SubItem(long sid, String title) {
this.sid = sid;
this.title = title;
}
public Item getItem() {
return this.item;
}
public void setItem(Item item) {
this.item = item;
}
}
public class Test {
public static void main(String[] args) {
}
}
7.3 多对多映射
7.4 复杂多对多映射
八、接口
Java 中的继承都是一对一的,为了解决这个问题,Java 提出了接口的概念
注意:
- 接口中的变量默认是 public static final 的
-
8.1 接口的定义
Java 接口是一系列方法的声明,是一些抽象的集合
- 一个接口的抽象方法没有方法实现,因为这些方法可以在不同的地方被不同的类实现,而这些实现可以有不同的行为
- 接口就是特殊的抽象类,即所有的方法都是抽象方法的抽象类就是 Java 中的接口 (interface)
8.2 接口的基本格式
[修饰符] interface 接口名 [extends 父类名列表] {
[public] [static] [final] 常量 ;
[public] [abstract] 方法 ;
}
- 修饰符:可选,用于指定接口的访问权限,可选值为pub | ic。即使省略,也依然是public。
- 接口名:必选参数,用于指定接口的名称,接口名必须是合法的Java
- 标识符。一般情况下,要求首字母大写。
- extends父接口名列表:可选参数,用于指定要定义的接口继承于哪个父接口。当使用extends关键字时,父接口名为必选参数。父接口可以存在多个,用逗号隔开。
- 方法:接口中的方法只有定义而没有被实现。
8.3 接口的特点
- 一种特殊的抽象类,abstract 可以省略
- 接口中没有变量,只能由 public static final 修饰的静态常量。三个修饰符可以省略
- 接口中所有的方法都是抽象方法,默认都是 public 权限
public interface User { public static final int TYPE = 1; // 只有定义,没有实现 public int addUser(String name,String password); public User addUser(User user); public void land(); }
8.4 类实现接口
接口的实现可以实现一对多
public class UserImpl implements User, [xxxxx,xxxx,...] {
// 这里实现了 User 接口,下面必须是方法的实现
public int addUser(String name,String password) {
xxxx
}
public User addUser(User user) {
xxx
}
public abstract void land() {
xxx
}
}
类实现接口的特点:
- 类实现接口,本质.上与类继承类相似,区别在于“类最多只能继承一个类,即单继承,而一个类却可以同时实现多”个接口多个接口用逗号隔开即可。实现类需要覆盖所有接口中的所有抽象方法,否则该类也必须声明为抽象类。
- 接口是抽象的,接口中没有任何具体方法和变量, 所以接口不能进行实例化。接口定义的是多个类都要实现的操作,即“what to do”。类可以实现接口,从而覆盖接口中的”方法,实现“how to do’。
8.5 接口继承接口
- Java 接口继承接口原则
- Java 接口可以继承多个接口
- 接口继承接口依然使用关键字 extends,不要错用 implements
- Java 接口继承接口形式
- interface3 extends interface1, interface2 …..
接口继承与类继承
- 接口继承与类继承对比: Java类的继承是单一-继承,Java接口的继承是多重继承。
- 接口可实现多继承原因分析
- 不允许类多重继承的主要原因是,如果A同时继承B和C,而B和C同时有- -个仿法,A无法确定该继承那-个。
- 接口全都是抽象方法继承谁都可以,所以接口可继承多个接口
8.6 接口与抽象类区别
8.7 接口的作用:面向接口编程
8.7.1 接口的作用
- 接口是设计层的概念,由设计师实现,将定义与实现分离
- 程序员实现接口,实现具体方法
- 常见的类设计结构
8.7.2 面向接口编程
- 面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。
- 或者说,它是面向对象编程体系中的思想精髓之一。
- 面向接口编程的意思是指在面向对象的系统中所有的类或者模块之间的交互是由接口完成的。
九、OOP 概念总结
9.1 OOP 的三大特征
- 封装
- 继承
- 多态
9.2 封装 (安全性)
- 借助 private 修饰符去修饰属性,让属性私有化,保护属性的值的安全性
- 借助 public 修饰符的方法去为私有属性提供 属性值的设置和获取 => getter 和 setter
9.3 继承(复用性)
当分析发现了很多类出现了共同的属性和方法,而且使用频率很高,使用继承
- 把相同的方法和属性抽象到父类,让子类继承并获取属性和方法
- 方法的重写:当父类的方法无法满足子类的使用,子类能够加以改造
- 重写特点:重写发生在子父类关系中,名字相同,参数列表相同,返回值相同,访问权限修饰符可以不同
- 抽象方法:当父类中的方法无法诠释,就声明为抽象方法,必须被子类重写(除非子类是抽象类)
9.4 重载 与 重写
9.4.1 重载
- 发生同一个类中
- 名字相同
- 参数列表不完全相同
- 返回值相同
- 访问权限修饰符相同
9.4.2 重写
- 发生在父子类中
- 名字相同
- 参数列表相同
- 返回值相同
- 访问权限符不同
9.5 多态 (增强程序的多样性)
多态的前提
- 有继承
- 有子类对象获取父类引用
- 方法必须重写
接口实现:
- 有实现类对象获取接口的引用
- 有方法的重写(强制的)
9.6 接口与抽象类
接口不是类,所以没有构造方法,属性权威常量,方法全为抽象方法
抽象类和接口的区别:
- 抽象类(类的模板)
- 有构造方法,不能实例化(给子类使用)
- 属性可以是变量,也可以是常量
- 单一继承
- 方法可以为抽象方法,可以为普通方法
- 接口(方法的模板)
- 不是类
- 所以没有构造方法
- 属性全为常量
- 方法均为抽象方法
- 一个类可以使用多个接口
十、面向对象的五大基本原则
- 单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
- 开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
- 里氏替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
- 依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
- 接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口