1
0
wiki/docs/开发/Java/面对对象.md
2022-05-07 16:24:23 +08:00

668 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: 面对对象
title: 面对对象
sidebar_position: 4
data: 2022年4月27日
---
在 Java 中,**万事万物都是对象**。 Java 也是面对对象的高级编程语言,面对对象语言具有如下特征
- 面对对象是一种常见的思想,比较符合人们的思考习惯;
- 面对对象可以将复杂的业务逻辑简单化,增强代码复用性;
- 面对对象具有抽象、封装、继承、多态等特性;
## 类
类——相当于是一系列对象的抽象。具体就比如自动车一样,车的长、宽、高是车的**数据**;车的骑行,喇叭是车的**方法**。
### 定义
```java
class ClassName{
// body
}
```
### 创建
```java
ClassName classname = new ClassName();
```
### 构造
在 Java 类中,通过构造方法在对象**创建时期调用一次**来确保每个对象都被**初始化**。构造方法比较特殊,没有参数类型和返回值,构造方法名称务必和类名保持一致,并且一个类可以拥有多个构造方法。
```java
class Apple {
int sum;
String color;
public Apple(){}
public Apple(int sum){}
public Apple(String color){}
public Apple(int sum,String color){}
}
// 创建对象
class createApple {
public static void main(String[] args) {
Apple apple1 = new Apple();
Apple apple2 = new Apple(1);
Apple apple3 = new Apple("red");
Apple apple4 = new Apple(2,"color");
}
}
```
如果类中没有定义任何构造方法,那么 JVM 会自动生成一个默认构造方法。
## 面对对象
### 封装
封装英语Encapsulation是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。封装可以被认为是一个保护屏障防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据必须通过严格的接口控制。
#### 数据
数据也可以被称为属性,数据可以是任意类型的对象,也可以是基本数据类型。
```java
Class A{
int a;
Apple apple;
}
```
#### 方法
方法(函数)通过封装多行代码的实现,用来**简化代码**、**利于维护**、**重复调用**、**模块化编程**和**提高开发效率**。
```java
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}
```
- **修饰符:**定义方法的访问类型。
- **返回值类型 **定义方法返回值类型。如果方法没有返回值,返回值类型是关键字 **void**
- **函数名:**调用方法的实际访问名称。
- **参数类型:**方法调用时,限定传递参数类型。
- **参数名:**方法调用时,传递的实际参数。方法定义时,限定的形式参数。
- **方法体:**定义方法的功能
#### 权限
![访问权限](https://static.7wate.com/img/2022/04/27/46262496c680a.png)
#### 实例
```java
public class EncapTest{
private String name;
private String idNum;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public String getIdNum(){
return idNum;
}
public void setAge( int newAge){
age = newAge;
}
public void setName(String newName){
name = newName;
}
public void setIdNum( String newId){
idNum = newId;
}
}
```
### 继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。从而减少重复代码,提高维护性,代码也更佳简洁,提高代码的复用性。
![继承图示例](https://static.7wate.com/img/2022/04/27/4439d9883fb7a.jpg)
#### 类型
Java 不支持多继承,但支持多重继承。
![继承类型](https://static.7wate.com/img/2022/04/27/75c38968a30e6.png)
#### 特性
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
#### 关键字
- **extends**类的继承是单一继承,一个子类只能拥有一个父类。
- **implements**变相的使 java 具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
- **super**通过 super 关键字来实现对父类成员的访问,用来引用当前对象的父类。
- **this**指向自己的引用。
- **final**类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写。
#### 实例
```java
class 父类 {
}
class 子类 extends 父类 {
}
// 单一继承
public class Animal {
private String name;
private int id;
public Animal(String myName, String myid) {
//初始化属性值
}
public void eat() { //吃东西方法的具体实现 }
public void sleep() { //睡觉方法的具体实现 }
}
public class Penguin extends Animal{
}
// 继承多个接口
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
// super 与 this 关键字
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
// final 关键字
final class 类名 { /*方法体*/ }
[public / private / default / protected] final 返回值类型 方法名(){ /*方法体*/ }
```
### 多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
![多态图例](https://static.7wate.com/img/2022/04/27/ac5f133d3c528.png)
#### 优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
#### 多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
#### 多态的实现方式
- 重写
- 接口
- 抽象类和抽象方法
### 重写与重载
#### 重写
重写与重载虽然名字相似,但却是完全不同的东西。方法重写的描述是对**子类和父类**之间的。
- 子类中重写的方法必须与父类保持一致,包括返回值类型、方法名、参数列表。
- 子类中重写的方法可以使用 @Override 注解来标识。
- 子类中重写方法的访问权限不能低于父类中方法的访问权限。
```java
class Fruit {
public void eat(){
System.out.printl('eat fruit');
}
}
class Apple extends Fruit{
@Override
public void eat(){
System.out.printl('eat apple');
}
}
```
#### 重载
相同的方法名拥有独一无二的参数列表,其中包括参数的类型、顺序、数量等构成了方法重载的必要条件。
- **方法名称必须相同。**
- **参数列表必须不相同(类型、顺序、数量不同等)。**
- 返回类型不同不构成方法重载。
- 重载是在编译时,具体由编译器确定使用那个方法。
**即外壳不变,核心重写!**
```java
public class Apple {
int sum;
String color;
public Apple(){}
public Apple(int sum){}
public int getApple(int num){
return 1;
}
public String getApple(String color){
return "color";
}
}
```
#### 重写与重载的区别
| 区别点 | 重载方法 | 重写方法 |
| :------: | :------: | :--------------------------------------------: |
| 参数列表 | 必须修改 | 一定不能修改 |
| 返回类型 | 可以修改 | 一定不能修改 |
| 异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
| 访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
方法的重写Overriding和重载Overloading是 java 多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
1. 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写Overriding
2. 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载Overloading
3. 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
![区别图例](https://static.7wate.com/img/2022/04/27/4fdd076693397.png)
-
## 抽象
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
- 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
### 类
```java
// 抽象类
public abstract class Employee {
private String name;
private String address;
private int number;
public Employee(String name, String address, int number) {
System.out.println("Constructing an Employee");
this.name = name;
this.address = address;
this.number = number;
}
public double computePay() {
System.out.println("Inside Employee computePay");
return 0.0;
}
public void mailCheck() {
System.out.println("Mailing a check to " + this.name
+ " " + this.address);
}
public String toString() {
return name + " " + address + " " + number;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public void setAddress(String newAddress) {
address = newAddress;
}
public int getNumber() {
return number;
}
}
// 继承抽象类
public class Salary extends Employee {
private double salary; //Annual salary
public Salary(String name, String address, int number, double salary) {
super(name, address, number);
setSalary(salary);
}
public void mailCheck() {
System.out.println("Within mailCheck of Salary class ");
System.out.println("Mailing check to " + getName()
+ " with salary " + salary);
}
public double getSalary() {
return salary;
}
public void setSalary(double newSalary) {
if(newSalary >= 0.0) {
salary = newSalary;
}
}
public double computePay() {
System.out.println("Computing salary pay for " + getName());
return salary/52;
}
}
// 实例化 Salary 对象
public class AbstractDemo {
public static void main(String [] args) {
Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
System.out.println("Call mailCheck using Salary reference --");
s.mailCheck();
System.out.println("\n Call mailCheck using Employee reference--");
e.mailCheck();
}
}
```
### 方法
如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。同时抽象方法必须遵守如下约定:
- 如果一个类包含抽象方法,那么该类必须是抽象类。
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
```java
// 抽象方法
public abstract class Employee {
private String name;
private String address;
private int number;
/*
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
*/
public abstract double computePay();
//其余代码
}
```
## 接口
接口英文Interface在JAVA编程语言中是一个抽象类型是抽象方法的集合接口通常以interface来声明。一个类通过继承接口的方式从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
### 特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 **public abstract**(只能是 public abstract其他修饰符都会报错
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 **public static final** 变量(并且只能是 public用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
**接口与类相同点**
- 一个接口可以有多个方法。
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在 .class 结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
**接口与类不同点**
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
### 实例
```java
// 接口声明
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
// 接口定义
interface Animal {
public void eat();
public void travel();
}
// 接口实现
...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...
// 继承实例
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
```
重写接口中声明的方法时,需要注意以下规则:
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
在实现接口的时候,也要注意一些规则:
- 一个类可以同时实现多个接口。
- 一个类只能继承一个类,但是能实现多个接口。
- 一个接口能继承另一个接口,这和类之间的继承比较相似。
### 多继承
在Java中类的多继承是不合法但接口允许多继承。
在接口的多继承中extends关键字只需要使用一次在其后跟着继承接口。 如下所示:
```java
public interface Hockey extends Sports, Event
```
## 包
为了更好地组织类Java 提供了包机制,用于区别类名的命名空间。
一个包package可以定义为一组相互联系的类型类、接口、枚举和注释为这些类型提供访问保护和命名空间管理的功能。
- **package**定义包
```java
package pkg1[pkg2[pkg3]];
// 定义
// 路径是 net/java/util/Something.java
package net.java.util;
public class Something{
...
}
```
- **import**导入包
```java
import package1[.package2].(classname|*);
// 定义
package payroll;
public class Boss {
public void payEmployee(Employee e) {
e.mailCheck();
}
}
// 通配符
import payroll.*;
// 引入 Employee 类
import payroll.Employee
```
### 作用
1. 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
2. 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
3. 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包package这种机制是为了防止命名冲突访问控制提供搜索和定位类class、接口、枚举enumerations和注释annotation等。
### 实例
```java
// 在 animals 包中加入一个接口interface
/* 文件名: Animal.java */
package animals;
interface Animal {
public void eat();
public void travel();
}
package animals;
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
```