---
url: /notes/java-se/面向对象编程/index.md
---
# 面向对象编程

# 面向对象概述

面向过程：当需要一个功能时，每一个步骤都需要自己处理

```java
打印数组[10,504,542,12,54]F
public class OOPTest {
    public static void main(String[] args) {
        //面向过程
        int[] arr1={10,504,542,12,54};
        System.out.println("面向过程");

        printArray(arr1);
    }
    public static void printArray(int[] arr){

        for (int i=0;i<arr.length;i++){
            if(i==0){
                System.out.print("["+arr[i]+",");
            }else if(i==arr.length-1){
                System.out.print(arr[i]+"]");
            }else{
                System.out.print(arr[i]+",");
            }
        }
        System.out.println();
    }

}
```

![image-20211124224607212](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124224607212.png)

面向对象：当需要一个功能时，不会去关心相关步骤，而是找一个可以替我们解决问题的人去待处理

```java
//面向对象
System.out.println("面向对象");
System.out.println(Arrays.toString(arr1));
```

## 类的创建和使用

类：是一类事物的描述，是抽象的
对象：是一类事物的实例，是具体的

类是对象的模板，对象是类的实物

```java
定义一个类需要两部分：
（1）属性（是什么）：成员变量
    位置：写在类里面方法外面
（2）行为（动作）：成员方法
```

```java
public class Student {
    String stuNum;  //学号
    double height;  //身高
    double weight;  //体重
    String name;    //姓名
    int age;        //年龄
    //学习
    public void study(){
        System.out.println("学生正在学习...");
    }
    //逃课
    public void running(){
        System.out.println("学生正在逃课...");
    }
    //背书
    public void remberBook(){
        System.out.println("学生正在背书...");
    }
}
```

类不能直接使用，需要根据这个类来创建对象

```java
创建对象？
    1.导包 import 包结构.类名称
    如果需要被使用的类在当前类同一个包下面，可以省略不写
    2.创建 类名称 对象名=new 类名称();
    3.使用
        (1)使用成员变量 对象名.属性
        (2)使用成员方法 对象名.方法
注意：成员变量未赋值，都会有一个默认值，规则和数组一致
```

```java
public class StudentTest {
    public static void main(String[] args) {
        Student stu=new Student();
        //给对象属性赋值
        stu.name="张三";
        stu.age=21;
        stu.stuNum="1712465";
        //打印对象属性值
        System.out.println("学生姓名："+stu.name);
        System.out.println("学生年龄："+stu.age);
        System.out.println("学生学号："+stu.stuNum);
        //调用成员方法
        stu.study();
        stu.remberBook();
        stu.running();
    }
}
```

![image-20211124224629870](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124224629870.png)

## 单个对象的内存执行流程

## 成员变量和局部变量的区别

```java
（1）定义的位置不同：
    成员变量：定义在类里面，方法外面
    局部变量：定义在方法里面 参数属于局部变量
（2）作用范围不同：
    成员变量：整个类里面
    局部变量：只能在其定义的方法里面
（3）默认值不同
    成员变量：有默认值，规则遵循数组
    局部变量：没有默认值，必须赋值才能使用
（4）内存地址不同
    成员变量：堆内存
    局部变量：栈内存
（5）生命周期不同
    成员变量：随着对象创建开始，对象被回收后结束
    局部变量：方法调用时创建，方法执行完出栈，消失
```

## 面向对象三大特征

**封装，继承，多态**

### 封装

将一些细节性的信息隐藏起来，对于外界不可见

```java
（1）方法的封装
（2）使用关键字 private
```

一旦加了private修饰后，本类是可以任意修改的。但是超出了本类的范围就不能直接访问。

想要访问该属性需要通过间接方式，定义一对get，set方法

```java
//设置属性
public void setWeight(double weight) {
    if (weight<=0){
        System.out.println("你不要给我瞎填！");
    }else{
        this.weight = weight;
    }
}
//获取属性
public double getWeight() {
        return weight;
}
//main
comp.setWeight(5.0);
System.out.println(comp.getWeight());
```

```java
IDEA快捷键 Alt+insert
```

#### `this`

当局部变量和成员变量同名时，采用**就近原则** ，优先使用局部变量

如果要改变成员变量，可采用以下方式

```java
this.成员变量
```

通过谁调用方法，谁就是this

#### 构造方法

构造函数是一种特殊的函数。其主要功能是用来在**创建对象时初始化对象** ， 即为对象成员变量**赋初始值** ，总与new运算符一起使用在创建对象的语句中。

```java
格式：
//无参构造
public 类名(){
    代码
}
//有参构造
public Game(String gameName, String version) {
    this.gameName = gameName;
    this.version = version;
}
```

**注意：**
（1）构造方法**必须要和类名一样**
（2）**不能有返回值，不要 void**
（3）任何一个类都有无参构造，如果没有写，系统会默认会生成一个默认的无参构造方法，且参数和方法体什么都没有

```java
new xxx;//就是在调用其无参构造方法
```

（4）**一旦写了带有参数的构造方法，系统的默认的无参构造方法将失效**
（5）构造方法可以是重载方法

```java
标准javaBean
    1.所有的成员变量使用 private 关键字
    2.为成员变量添加 get/set 方法
    3.编写一个无参构造
    4.编写一个全参构造
```

### 继承

`OOP(Object Oriented Programming)`
多态的前提，如果没有继承，就没有多态

> 解决的问题：共性抽取

```java
父类：也叫基类，超类
子类：也叫派生类
```

```java
使用关键字 extends
```

继承关系的**特点** ：

* 子类拥有父类的内容

* 子类拥有自己专有的内容

```java
父类的定义格式：
public class 父类名称{}
定义子类的格式：
public class 子类名称 extends 父类名称{}
```

直接通过子类对象访问**成员变量** ：

* 等号左边是谁优先使用谁的，没有就向上找

间接访问通过成员方法访问成员变量：

#### `super`

该方法属于谁，就先使用谁的，没有则向上找

* 想使用父类的成员变量：

```java
super.成员变量
```

* 想使用本类的成员变量：

```java
this.成员变量
```

在父子类的继承关系中，创建子类对象访问成员方法的规则：

> 创建的对象是谁，就优先使用谁，没有则向上找

子类和父类重名方法，使用时还是看调用对象

**注意：**

**子类自动获得了父类的所有字段，严禁定义与父类重名的字段！**
**子类无法继承父类的构造方法！**

> 会使用 `super()` 调用
> 只能在**第一行用一次**

```java
super();//调用父类的构造方法 必须在子类构造的第一行！
```

```java
super 代表父类的存储空间标识，可以理解为父类的引用
this 代表当前对象的引用
```

子类创建对象，先使用父类的构造方法，从上往下查找
java中的继承是**单一继承**
java中的任何类都有父类 **Object类**

```java
Object 类：所有类的父类
```

#### 重写方法，重载方法

在**继承关系** 中，**方法名称一样，参数列表一样** 的方法

#### 重载方法和重写方法的对比

```java
重写方法 Override
    方法名相同，参数列表相同，返回值也要相同
重载方法 Overload
    方法名相同，参数列表不同,在同一个类中出现；与返回值无关
```

**方法的覆盖重写** ：创建子类对象的时候，使用重写方法，优先使用的是子类的方法

改变参数的数目和类型都可以实现方法重载，为什么改变方法的**返回值类型** 就不可以
**编译时报错优于运行时报错** ，所以当两个方法的名字相同，参数个数和类型也相同的时候，虽然返回值类型不同，但依然会提示方法已经被定义的错误

#### 复制对象

* 通过构造方法

* 通过对象的值

* 通过 `Object 类`的`clone()`⽅法

```Java
通过clone()方法复制对象的时候，ClonePerson 必须先实现 Cloneable 接口的clone()方法，然后再
调用clone()方法
（ClonePerson p2 = (ClonePerson) p1.clone()）。
```

#### 注解

```java
@Override //自动检查是否是重写方法
```

> 重写方法：子类可以根据自己的需求进行差异化定制即沿袭了父类的功能名称，又根据子类的需求重新实现了父类的方法，从而进行了功能的扩展

**注意：**

```.properties
“首先来说一下**方法重载** 时的注意事项，‘**两同一不同** ’。”
“‘两同’：在同一个类，方法名相同。”
“‘一不同’：参数不同。”

“再来说一下**方法重写** 时的注意事项，‘**两同一小一大** ’。”
“‘两同’：方法名相同，参数相同。”
“‘一小’：子类方法声明的异常类型要比父类小一些或者相等。”
“‘一大’：子类方法的访问权限应该比父类的更大或者相等。”
```

> 对于对象来说，返回值必须是父类或他的子类

```java
访问权限修饰符
    public -> protected -> (default) -> private
    依次递减
```

#### 抽象类

```java
抽象方法：在方法声明时加上 abstract ，去掉方法体 {} 为 ;
抽象类：抽象方法所在的类，就是在class前面加上 abstract
```

**注意：**

1. 抽象类**不能实例化**

2. 使用抽象类**必须要有一个子类继承它**

3. 子类**必须要重写抽象方法**

4. 需要创建子类对象使用

> 重写抽象方法：只要去掉`abstract`加上方法体`{}`

* 抽象类可以继承抽象类，**可以不重写抽象方法**

* 抽象类里面**不一定** 有抽象方法

* 抽象方法**一定** 要在抽象类里面

在抽象类中**可以有构造方法** ，只是不能直接创建抽象类的实例对象，但**实例化子类的时候，就会初始化父类** ，不管父类是不是抽象类都会调用父类的构造方法，初始化一个类，先初始化父类。

### 组合

```Java
public class SoccerPlayer {
  private Stringname;
  private Soccersoccer;
}
public class Soccer {
  private String soccerName;    
}
```

![image-20211124224728318](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124224728318.png)

### 代理

![image-20211124224740719](https://note-1259190304.cos.ap-chengdu.myqcloud.com/note/image-20211124224740719.png)

### 多态

指的是同一种行为具有多个不同表现形式。指一个类（对象）的相同方法在不同情形下具有不同表现形式。

多态的**前提条件** 有三个：

* **子类** **继承** **父类**

* **子类** **重写父类的方法**

* **父类引用指向子类对象**

在运行时根据对象的类型进行**后期绑定** ，编译器在编译阶段并不知道对象的类型，但是Java的方法调用机制能找到正确的方法体，然后执行出正确的结果​

多态就是指**程序中定义的引用变量所指向的具体类型** 和通过该引用变量发出的方法调用在编译时并不确定，而是在程序运行期间才确定；
即一个引用变量倒底会指向哪个类的实例对象，该引用变量发出的方法调用到底是哪个类中实现的方法，必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类，这样，不用修改源程序代码，就可以让引用变量绑定到各种不同的类实现上，从而导致该引用调用的具体方法随之改变，即不修改程序代码就可以改变程序运行时所绑定的具体代码，让程序可以选择多个运行状态，这就是多态性。

**定义格式**

```java
父类名称 对象名=new 子类名称();
接口名称 对象名=new 实现类名称();
```

**使用方式**

* **多态对象调用成员方法时，访问规则**

看**new** （**右边** ）的是谁，如果没有则向上找

> 编译看左边，**运行看右边**

* **多态对象调用** **成员变量** **时，访问规则**

直接访问时，看**左边** 是谁，优先用谁的，没有则向上找

> 编译看左边，**运行看左边**

```java
public class Animals {
    int num = 20;//共有
    int sum = 88;//动物独有

    public void run() {
        System.out.println("这是动物在跑");
    }

    public void methodA() {
        System.out.println("这是动物的独有方法");
    }
}
```

```java
public class Dog extends Animals{
    int num=30;//共有
    int age=60;//狗独有
    @Override
    public void run() {
        System.out.println("这是狗在奔跑");
    }
    public void methodB(){
        System.out.println("这是狗的独有方法");
    }
}
```

```java
public class Demo {
    public static void main(String[] args) {
        Animals hsq = new Dog();
        hsq.run();//类型为Animals有该方法，可以访问，运行时Dog有该方法，优先访问
        hsq.methodA();//左边有该方法，运行时右边没有，运行左边的
        System.out.println(hsq.num);//类型为Animals有该成员变量，可以访问，运行时优先访问Animals的成员变量
        System.out.println(hsq.sum);//类型为Animals有该成员变量，可以访问，运行时优先访问Animals的成员变量
    }
}
```

#### 多态的好处

```java
public class Animals {
    public void run() {
        System.out.println("这是动物在跑");
    }
}
```

```java
public class Dog extends Animals{
    @Override
    public void run() {
        System.out.println("这是狗在奔跑");
    }
}
```

```java
public class Rabbit extends Animals{
    @Override
    public void run() {
        System.out.println("这是兔子跑");
    }
}
```

```java
public class Demo {
    public static void main(String[] args) {
        Animals animals1=new Dog();
        Animals animals2=new Rabbit();
        showAnimalsRun(animals1);
        showAnimalsRun(animals2);
    }
    public static void showDogRun(Dog dog){
        dog.run();
    }
    public static void showRabbitRun(Rabbit rabbit){
        rabbit.run();
    }
    public static void showAnimalsRun(Animals animals){
        animals.run();
    }
}
```

在开发中，当我们不强调具体的对象是谁时，使用多态可增强代码的扩展性

#### 向上转型

把一个**子类类型** 安全地变为**父类类型** 的赋值，被称为**向上转型** （upcasting）。

```java
Animals animals=new Dog();
```

一定是安全的，但是有一个弊端，一旦向上造型成功，那么无法调用子类原本的特有的内容

**解决** ：向下转型（数据的还原）

#### 向下转型

把一个**父类类型** 强制转型为**子类类型** ，就是**向下转型** （downcasting）。

```java
Animals animals=new Dog();
// 在向下转型时，判断对象是不是相应类型的数据
if(animals instanceof Dog){
   Dog dog=(Dog)animals;
}
if(animals instanceof Rabbit){
   Rabbit rabbit= (Rabbit) animals;//类型转换异常 狗不能转换为兔子
}
```

#### `instanceof`

判断对象是不是相应类型的数据

**格式**

```java
要转的对象 instanceof 要转的类型 //得到布尔值
```

## 静态方法和静态字段

### `static`关键字

可使用`static`关键字解决数据共享问题，被修饰的内容就不再属于某个对象，而被类所有

```java
static可以修饰
1.成员变量 -> 静态变量
2.成员方法 -> 静态方法
```

**调用** ：

```java
类名称.变量名/方法名
```

如果在本类中，静态方法调用可省略类名

**注意：**

```java
（1）静态只能访问静态的
    静态方法无法访问非静态变量或方法
    static 不能修饰局部变量
（2）静态方法中不能使用 this 关键字
    this 代表对象，而静态的属于类所有
```

`static `**修饰的方法不能被重写可以被继承** ！！！
必须使用类去调用

### 静态/构造代码块

```java
public static void main(String[] args) {}

//普通代码块
// 实例化时，在构造方法中的最前面执行
{
    System.out.println();
    ...
}

//静态代码块
// 在构造方法之前执行，在类加载时只执行一次
static{
    code...
}
```

**注意：**

1. **静态块在本类加载的第一次就自动执行一次** ！

2. 执行顺序优先于非静态，**优先级比构造方法要高**

3. 普通代码块

> **一般用来一次性对静态的成员变量赋值**
>
> 可以用来进行一些文件的加载任务（图片等多媒体内容）

## `final`关键字

**代表最终，不可改变的**

#### 四种常用方法

```java
（1）修饰一个类  final class 类名称{}
```

这个类不能有子类，**无法被继承** ，所以该类的所有方法**无法被重写**

```java
（2）修饰一个方法  修饰词 final 返回值 方法名称(参数列表){}
```

`final`关键字**不能修饰** `abstract`**类和抽象类的方法**

```java
（3）修饰局部变量  访问修饰符 final 变量类型 变量名称;
```

final关键字修饰的**局部变量一经赋值无法修改**

**引用类型数据** 使用final修饰后**内存地址不能改变** ，属性值可以修改

```java
（4）修饰成员变量  访问修饰符 final 变量类型 变量名称;
```

final修饰成员变量应该**立即赋值** （二选一）
（1）直接赋值
（2）构造赋值 	无参必须给值	有参必须传值

## 常量

```java
常量定义格式： public final static 变量类型 变量名称 = 值;
```

1. 声明时，**必须要赋值**   **使用static静态代码块赋初始值**

2. 常量名称必须**要全部大写** ，多个单词使用下划线"`_`"连接

#### 调用

```java
类名.常量名称
```

## 访问修饰符

|访问修饰符|同一个类|同一个包，子类|不同包-子类|不同包-非子类|
|---|---|---|---|---|
|`public` 公共的|✅|✅|✅|✅|
|`protected` 受保护的|✅|✅|✅| |
|默认(`default`)|✅|✅| | |
|`private` 私有的|✅| | | |

```java
public
    本类可以使用，同包类（子类）可以使用，不同包也可以使用，不同包的子类也可使用
protected
    修饰字段和方法 本类可使用，同包类（子类）可以使用，不同包的子类可使用；
    不同包的其他类无法使用
default 
    本类可使用，同包类（子类）可以使用；
    不同包的子类无法使用，不同包的其他类无法使用
private
    本类可使用;
    同包类（子类）无法使用,不同包的子类无法使用，不同包的其他类无法使用
```

```java
protected 和 private 不能修饰类！！！
```

**合理使用 !**

```java
尽可能选择范围较小的访问修饰符！
```

## 接口

就是一套规范，同时也是一种引用类型数据，里面定义的抽象方法是最重要的内容

**格式**

```java
public interface 接口名称{
    抽象方法
}
```

**注意：**

> 普通类编译 `.java` -> `.class`接口编译后 `.java` -> `.class`

接口中的内容包含：

```java
jdk7
    主要存放 常量(public final static)， 抽象方法(public abstract)
jdk8
    添加了 默认方法(public default)， 静态方法(public static)
jdk9
    添加了 私有方法(private) //接口里面自己使用，不对外公开
```

### 接口的使用

接口不能直接创建对象，**必须要有一个实现类来实现该接口**

```java
访问修饰符 class 实现类 implements 接口类{}
```

### 默认方法

**定义**

```java
public default 返回值类型 方法名称(参数列表){code}
```

**调用**

```java
实现类创建对象.默认方法
```

如果实现类中没有该方法，就会向上找；可以解决接口升级的问题

实现类不用重写

### 接口中静态方法调用

```java
public static 返回值类型 方法名称(参数列表){code}
```

```java
接口名.静态方法
```

### 接口中私有方法

解决接口中代码重复的问题，该代码不应该被实现类使用，只能接口自己使用

```java
private 返回值类型 方法名称(参数列表){code}
private static 返回值类型 方法名称(参数列表){code} //静态私有方法
```

**接口使用时注意：**

1. 不能有静态块 `static{}`

2. 一个类**只有一个父类** ，但是一个类**可以实现多个接口**

```java
格式： public class 类名 implements 接口a,接口b...{
    //重写代码
}
```

1. 当实现类**实现的多个接口** 中出现了**重名的抽象方法** ，那么实现类只需要重写一个该方法

2. 当实现类实现多个接口，并且**没有覆盖重写接口中的方法** ，那么该实现类一定是一个抽象类

3. 当实现类实现了多个接口，并且**接口中出现了同名的默认方法** 时，那么实现类一定要重写默认方法

4. 当**一个子类的直接父类跟接口中的方法相同** 时，优先使用父类中的该方法

### 接口和接口之间的关系

接口可以**继承多个接口**

**注意：**

1. 如果**接口中有跟父接口相同的抽象方法** ，可以**同时存在**

2. 如果**父接口中有同名的默认方法** ，子接口要**重写** 该默认方法；并且重写默认方法时需要带上`default`关键字

## 内部类

一个实物里面包含另外一个事物，那么这就是一类内部包含另一个类

### 分类

1. 成员内部类

2. 局部内部类

### 定义格式

```java
修饰符 class InnerClass{code}
```

```java
public class Outer {
    int sum = 100;
    //成员内部类
    public class Inner {
        int num = 50;

        public void method() {
            System.out.println("成员内部类的方法");
        }
    }
}
```

### 成员内部类

```java
直接使用：外部类.内部类 = new 外部类().new 内部类();
间接使用：通过外部类的方法来创建对象；创建外部类对象，调用方法
```

```java
public class Outer {
    //成员内部类
    public class Inner {
        public void method() {
            System.out.println("成员内部类的方法");
        }
    }
    //调用内部类的方法
    public void run(){
        System.out.println("外部类中的方法");
        //创建内部类对象 匿名对象
        new Inner().method();
    }
}
```

```java
public class Demo {
    public static void main(String[] args) {
        //使用外部类
        Outer outer=new Outer();
        outer.run();
        //使用内部类
        Outer.Inner inner=new Outer().new Inner();
        inner.method();
    }
}
```

### 局部内部类

一个类定义**在方法里面**

布局内部类 不能使用 `public`,`private`,`protected`修饰符

**局部内部类只能在方法中使用！**

```java
public class Outer {

    public void method() {
        int num = 10;
        
        //局部内部类
        class Inner {
            int num = 50;
            public void run() {
                System.out.println("局部内部类中的方法");
            }
        }
        //使用局部内部类
        Inner inner=new Inner();
        System.out.println(inner.num);
        inner.run();
    }
}
```

### 匿名内部类

如果接口中的实现类（或者父类中的子类）只需要使用一次；可以忽略该类的定义，而使用匿名内部类

**定义格式**

```java
接口名称(父类名称) 对象名称 = new 接口名称(){
    //重写接口中的方法
}
```

```java
MyInterface myInterface1=new MyInterface() {
       @Override
       public void methodA() {
    }
};
myInterface.methodA();
```

**解释说明**

```java
new 创建对象
new 后面的接口名称表示一个类需要实现的接口
{code} 内部类的内容
```

**注意**

1. 匿名内部类，在创建对象时，**只能使用一次** ；
2. ​	需要多次创建对象，而且类的内容相同需要重新定义类文件

```java
MyInterface myInterface = new MyInterface() {
        @Override
        public void methodA() {
        }
    };
myInterface.methodA();
```

1. 匿名对象，在调用的时候，**只能调用一次**

```java
new Engine().engineWork();
```

1. **匿名内部类是省略了【子类/实现类】** ，但是**匿名对象省略了【对象名称】** 	匿名内部类和匿名对象强调的不是一回事

### 静态内部类

```Java
第一，静态内部类不能访问外部类的所有成员变量 ；
第二，静态内部类可以访问外部类的所有静态变量，包括私有静态变量。
第三，*外部类不能声明为 static* 。”
```

## 引用类型数据作为返回值

```java
//引用类型返回 NewPhone，OldPhone继承自Phone抽象类
public Phone methodA(){
    Phone phone=new NewPhone();
    Phone phone1=new OldPhone();
    if(true){
        return phone;
    }else{
        return phone1;
    }
}

//接口作为返回值 Iplay为接口类型
public IPlay methodB(){
    IPlay iPlay=new NewPhone();
    return iPlay;
}

public void usePhone(Phone phone,IPlay iPlay){
        phone.callPhone();
    phone.sendMessage();
    iPlay.playGame();
}
new Demo().usePhone(new Demo().methodA(),new Demo().methodB());
```
