面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。

原文链接:全网最完整Java学习笔记

概述

面向对象(Object-Oriented,简称 OOP)是一种程序设计的范式,它基于对象的概念,将数据和操作数据的行为封装在对象中,以模拟现实世界的问题和解决方案。

核心概念

对象(Object): 对象是现实世界中的实体或概念,在程序中被抽象为具有状态(属性)和行为(方法)的实例

(Class): 类是对象的模板,它定义了对象的属性和方法。类是对象的抽象,实际的对象是根据类的定义实例化而来的。

封装(Encapsulation): 封装是将对象的属性和方法封装在一个单元内,对外部隐藏对象的具体实现细节。通过封装,对象的内部实现对外部是不可见的,只有公共接口对外部可见。

继承(Inheritance): 继承允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以继承父类的属性、重写父类的方法,从而减少代码量。

多态(Polymorphism): 多态允许不同对象对同一消息做出响应,提供了灵活性和可扩展性。其实现方式包括方法重载和方法重写。

类和对象

基本介绍

:现实中一种具有共同属性、行为的事物的抽象。它定义了一组属性(成员变量)和方法(成员方法),用于描述具有相似特征和行为的对象集合。

对象:对象是类的一个实例,是能够看得到摸的着的真实存在的实体。

总结起来就是一句话:类是对象的抽象,对象是类的具体实现。

例如声明一个Person类,有名字和年龄两个属性;然后便可以通过这个类创建两个对象,分别是Alice和Bob,他们的年龄不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Person {
// 成员变量
String name;
int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 成员方法
public void work() {
System.out.println(name + " is working.");
}
public void study() {
System.out.println(name + " is studying.");
}
}

public class Test {
public static void main(String[] args) {
// 创建 Person 类的对象
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Bob", 30);
// 调用对象的方法
person1.work();
person2.study();
}
}

结果:

1
2
Alice is working.
Bob is working.

内部类

内部类:在一个类中定义类。

分为:

  • 成员内部类(成员位置)
  • 局部内部类(成员方法内)
  • 匿名内部类(方法内)

匿名内部类:一个继承了其他类或者实现了其他接口的子类对象。

内部类可以直接访问外部类私有、公有成员。外部类要访问内部类成员要创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Outer {
int num = 10;
// 定义成员内部类:在成员位置定义类
public class Inner{ // 一般private,用外部类访问内部类更安全
public void show(){
System.out.println("innershow"+num); // 内部类可直接访问外部类成员。
}
}

// 外部类访问成员内部类
public void fun(){
Inner in = new Inner();
in.show();
}

// 局部内部类:在成员方法内定义类
public void fun2(){
class Inner2 { // 成员内部类不能public和private
void show() {
System.out.println("成员内部类");
}
}
Inner2 in=new Inner2();
in.show();
}
// 匿名内部类:在成员方法内定义子类,实现接口或继承抽象类
public void fun3(){
Phone p = new Phone() { // 自己写的Phone接口
@Override
public void show() {
System.out.println("实现接口的匿名内部类");
}
}; // 注意是一个语句有分号
p.show();
}
}
// 内部类创建对象(一般内部类私有,使用外部类访问内部类)
public class Test {
public static void main(String[] args) {
Outer.Inner i = new Outer().new Inner();
i.show();
}
}

创建对象的几种方法

概述:

  • new:例如Person person1 = new Person();
  • 反射:先获取类的Class对象,通过newInstance()方法创建对象;
  • 对象的clone()方法: 类实现Cloneable 接口,重写 clone()方法, 编写浅拷贝或者深拷贝的逻辑。然后调用对象的clone()方法就可以克隆出对象。
  • 反序列化:反序列化对象时,JVM会创建一个单独的对象。需要让类实现Serializable接口,通过ObjectInputStream类的readObject()方法从磁盘中反序列化对象。反序列化创建对象是深拷贝。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1.使用 new 关键字: 这是最常见的创建对象的方式,使用 new 关键字直接调用类的构造方法
Person person1 = new Person();

// 2.反射:通过反射机制,可以在运行时获取类的信息,动态创建对象。
Class<?> clazz = Person.class;
Person person2 = (Person) clazz.newInstance();

// 3.对象的 clone() 方法:要实现克隆,类必须实现 Cloneable 接口,并重写 clone() 方法。该方式可实现对象的浅拷贝或深拷贝。
public class Person implements Cloneable {
// 其他类定义

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 创建对象
Person person3 = (Person) person1.clone();

// 4.反序列化:通过反序列化,可以将对象的状态从持久性存储中重新创建出来。需要让类实现 Serializable 接口,并使用 ObjectInputStream 类的 readObject() 方法从文件或网络中反序列化对象。
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person4 = (Person) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}

方法

基本介绍

方法(Method):一组执行特定任务的代码块。它可以在类中定义一次,然后在本方法、其他方法中被多次调用。

作用:提高代码的可读性和可维护性。

例如小明要吃多顿饭,每顿饭吃的食物不同,可以将“吃饭”这个行为抽象成方法,从而减少代码量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
private static String name = "小明";
// 吃饭方法
public static void eat(String food) {
System.out.println("=====");
System.out.println(name + " is eating " + food);
System.out.println("=====");
}
public static void main(String[] args) {
eat("西瓜");
eat("白菜");
eat("可乐");
eat("番茄");
}
}

基本用法

在 Java 中,方法的定义包括以下几个要素:

  • 修饰符: 方法可以有访问修饰符,例如 public、private、protected 或默认(包内可见)。
  • 返回类型: 方法可以返回一个值,指定返回值的数据类型,如果方法不返回任何值,可以使用 void。
  • 方法名: 方法名是方法的标识符,用于在程序中调用方法。
  • 参数列表: 方法可以接受零个或多个参数,参数用于向方法传递数据。
  • 方法体: 方法体包含实际执行的代码块,实现方法的功能。
1
2
3
4
5
6
7
8
9
// 方法的定义
public int add(int num1, int num2) {
int sum = num1 + num2;
return sum;
}
// 方法的调用:可以通过方法名和参数列表来调用方法。方法调用是程序执行的一个重要步骤,它使代码更具可重用性。
int result = add(5, 3);
System.out.println("Result: " + result);
// 返回值:方法可以返回一个值,也可以是 `void`,表示不返回任何值。如果方法返回值,必须使用`return`返回对应类型的值。

方法的重载

重载(Overload)

重载(Overload):一个类中可以有多个方法具有相同的方法名,但这些方法的参数类型不同、个数不同、顺序不同

注意:方法返回值和访问修饰符可以不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class hello {
public static void main(String args[]) {
f();
}

public static void f() {
System.out.println("3f");
}
public static void f(int a) { // 重载,注意返回值同,参数不同
System.out.println(a);
}
// 下面两个注释的方法就不是重载,会报错
// public static int f(){ //返回值不同
// System.out.println("hello");
// }
// void f(){ // 修饰符不同
// return 666;
// }
}

示例:求和的数学类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MathOperations {
// 求和方法,接受两个整数参数
public int sum(int num1, int num2) {
return num1 + num2;
}
// 重载的求和方法,接受三个整数参数
public int sum(int num1, int num2, int num3) {
return num1 + num2 + num3;
}
// 重载的求和方法,接受两个双精度浮点数参数
public double sum(double num1, double num2) {
return num1 + num2;
}

public static void main(String[] args) {
// 创建 MathOperations 对象
MathOperations math = new MathOperations();
// 使用不同的方法进行求和
int result1 = math.sum(5, 10);
int result2 = math.sum(3, 7, 12);
double result3 = math.sum(2.5, 3.5);
// 打印结果
System.out.println("Sum of two integers: " + result1);
System.out.println("Sum of three integers: " + result2);
System.out.println("Sum of two doubles: " + result3);
}
}

重载和重写的区别

重载:方法名相同且参数列表。重载要求发生在同一个类中,多个方法之间方法名相同且参数列表不同。重载与返回类型和访问修饰符无关。方法名相同返回类型不同会直接报错。

重写:方法名、参数列表、返回类型与父类相同。重写发生在父类子类或接口实现类中,若子类方法想要和父类方法构成重写关系,则它的方法名、参数列表、返回类型必须与父类方法相同。

重写时

  • 返回值类可以是原返回值类的子类。例如工厂方法设计模式里,抽象工厂类的createObject()方法返回值是抽象产品类,具体工厂类的createObject()方法返回值是具体产品类
  • 访问权限不能比其父类更为严格
  • 抛出异常不能比父类更广泛

注意:构造方法不能重写。

可变参数

Java 5 以后引入了可变参数(Varargs),允许方法接受可变数量的参数。可变参数在方法的参数列表中使用省略号 ... 表示。

1
2
3
4
5
6
// 可变参数的定义
public void printNumbers(int... numbers) {
for (int num : numbers) {
System.out.print(num + " ");
}
}

注意:

  • 可变参数必须是方法的最后一个参数。例如void (int a,int… b)正确,而void (int… a,int b)会报错。

  • 一个方法最多只能有一个可变参数。

  • 可变参数在方法内部被当作数组处理。

示例:求和的数学类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class MathOperations {
// 求和方法(整数数组)
public static int sum(int[] numbers) {
int result = 0;
for (int num : numbers) {
result += num;
}
return result;
}

// 求和方法(可变参数)
public static int sum(int... numbers) {
int result = 0;
for (int num : numbers) {
result += num;
}
return result;
}

// 求和方法(双精度浮点数数组)
public static double sum(double[] numbers) {
double result = 0;
for (double num : numbers) {
result += num;
}
return result;
}

// 求和方法(可变参数)
public static double sum(double... numbers) {
double result = 0;
for (double num : numbers) {
result += num;
}
return result;
}

public static void main(String[] args) {
// 示例:整数求和
int[] intArray = {1, 2, 3, 4, 5};
int intSum = sum(intArray);
System.out.println("Integer Sum: " + intSum);
// 示例:可变参数整数求和
int varArgsIntSum = sum(1, 2, 3, 4, 5);
System.out.println("VarArgs Integer Sum: " + varArgsIntSum);
// 示例:双精度浮点数求和
double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
double doubleSum = sum(doubleArray);
System.out.println("Double Sum: " + doubleSum);
// 示例:可变参数双精度浮点数求和
double varArgsDoubleSum = sum(1.1, 2.2, 3.3, 4.4, 5.5);
System.out.println("VarArgs Double Sum: " + varArgsDoubleSum);
}
}

构造方法

构造方法是一种特殊的方法,与类同名,没有返回类型。

每次创建对象时,都会默认执行一次构造方法。

特点

  • 与类同名,没有返回类型;
  • 构造方法在对象创建时执行,用于设置对象的初始状态。
  • 每个类都可以有一个或多个构造方法,但通常至少有一个默认构造方法(无参数)。
  • 默认构造方法:果在类中没有明确定义任何构造方法,Java 会自动为该类提供一个默认的无参数构造方法。这个默认构造方法执行时不进行特定的初始化操作。
  • 重载:和普通方法一样,构造方法也支持重载,即在同一个类中可以定义多个同名但参数列表不同的构造方法。

简单的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Car {
private String brand;
private String model;
private int year;

// 默认构造方法:创建对象时会直接运行构造方法,输出 Default constructor called.
public Car() {
System.out.println("Default constructor called.");
}
// 带参数的构造方法
public Car(String brand, String model, int year) {
this.brand = brand;
this.model = model;
this.year = year;
System.out.println("Parameterized constructor called.");
}
// Getter 和 Setter 方法(省略)

public static void main(String[] args) {
// 使用默认构造方法创建对象
Car defaultCar = new Car();
// 使用带参数的构造方法创建对象
Car customCar = new Car("Toyota", "Camry", 2022);
}
}

接口和抽象类

概述

  • 接口:对行为的抽象,如吃饭类、睡觉类。
  • 抽象类:对事物的抽象,如动物类,小狗类。

接口

接口:对行为的抽象,如吃饭类、睡觉类。

接口的特点

  • 接口没有构造方法。
  • 接口中的方法会被隐式的指定为 public abstract方法,不能定义静态方法。
  • 接口中的变量会被隐式的指定为 public static final 变量,不能定义私有成员。因为是final所以也要显式赋初值。
  • 接口和接口多继承,接口和类多实现。
  • 接口的实现类:除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
1
2
3
4
5
6
7
8
9
public interface Phone {
public static final int price = 4; // 修饰符可省略
public abstract void show(); // 修饰符可省略
}
public class PhoneImpl implements Phone {
public void show() { // 必须是public,否则报错
System.out.println("hello");
}
}

抽象类

  • 抽象类:对事物的抽象,如动物类,小狗类。

  • 抽象类包含抽象方法的类,它不能被实例化,通常用于作为其他类的基类。

  • 特点:抽象类可以包含抽象方法、具体方法、字段和构造方法。

使用场景

  • 建模共性行为: 当多个类具有相同的行为,可以将这些行为提取到一个抽象类中,以便实现代码的重用。
  • 规范子类: 抽象类可以用于规定子类应该实现的一组方法,强制子类提供这些方法的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
// 抽象方法
public abstract void makeSound();
// 具体方法
public void eat() {
System.out.println(name + " is eating");
}
}

public class Dog extends Animal {
public Dog(String name) {
super(name);
}
// 实现抽象方法
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}
}

public class Cat extends Animal {
public Cat(String name) {
super(name);
}
// 实现抽象方法
@Override
public void makeSound() {
System.out.println("Meow! Meow!");
}
}

面对对象三特性

面向对象的三大基本特征是:封装、继承、多态。分别实现了数据的隐藏与保护、代码的复用扩展以及行为的灵活适配。‌

封装是对数据和行为进行集中管理的过程‌,其核心在于隐藏内部实现细节,仅通过接口与外界交互。主要作用包括:‌

  • 数据保护‌:通过访问权限控制,如 private、protected 限制外部直接修改属性;‌‌‌‌‌
  • 接口标准化‌:提供统一的方法调用入口,如 getter、setter 控制属性访问;‌‌‌
  • 模块化设计‌:将同一类功能封装到同一对象中。

继承通过父子类关系实现代码复用和扩展‌,其特点包括:‌

  • 子类复用父类功能‌:子类可直接使用父类的公共属性和方法;‌‌‌‌‌
  • 层级扩展能力‌:子类可新增特性或重写父类方法,如子类用 extends 继承父类并添加特有属性;‌‌‌
  • 访问规则约束‌:父类私有成员(private)不可被子类继承,保证封装性。‌‌‌

多态通过统一的接口实现不同类型的差异化行为‌,具体表现为:‌

  • 动态绑定‌:父类引用指向子类对象;‌‌‌
  • 方法重写与重载‌:子类覆写父类方法(重写)或同一类中同名不同参方法(重载);‌‌‌‌‌
  • 灵活性增强‌:同一方法在不同子类中表现不同。‌

封装

封装:通过private修饰符,将类的某些信息隐藏在类的内部,不允许外部程序直接访问。

封装将对象的状态和行为包装在一个类中并对外界隐藏实现的细节,可以通过访问修饰符控制成员的访问权限,让外部程序通过该类提供的方法来实现对内部信息的操作和访问。

示例:将价格、年龄等信息封装到小狗类中,外部不能直接访问,只能通过get和set方法访问。

1
2
3
4
5
6
7
8
9
10
11
public class Dog{
int price; // 成员变量在堆内存
private int age;

public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}

优点:安全性和复用性。

  • 安全性:通过方法来控制成员变量的操作,提高了代码的安全性
  • 复用性:把代码用方法进行封装,提高了代码的复用性,降低了耦合性。

继承

继承:继承是指一个类通过从另一个类继承其属性和方法。这使得子类具有其父类的行为和属性,同时可以扩展或修改这些行为和属性以满足特定的需求。

  • 优点:提高代码复用性,维护性,实现代码共享。

  • 缺点:高耦合性,有侵入性,父类改变子类也会改变。

  • 特点:子类拥有父类非 private 的属性、方法。可以拥有自己的属性和方法,即子类可以对父类进行扩展。可以用自己的方式实现父类的方法。

  • 注意: Java 不支持多继承,但支持多重继承。

  • 子类所有构造方法会默认先运行super();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Animal { 
    public String name;
    int age = 3;
    public Animal(String name, int age) {
    // 初始化属性值
    }
    public void show() { // 吃东西方法的具体实现 }
    public void sleep() { // 睡觉方法的具体实现 }
    }
    // 子类
    public class Penguin extends Animal{
    int age=4;
    public Penguin(){
    super(); // 不写也会默认运行
    }
    @Override // 注解重写,检查重写是否正确。例如修饰符(子类重写方法的访问权限要≥父类)、函数名错误。
    public void show(){ // 重写父类中show(),如果去掉public会报错。想再用super.show();
    int age = 5;
    System.out.println(age); // 5,子类可以访问父类非私有成员变量
    System.out.println(this.age); // 4
    System.out.println(super.age); // 3
    }
    }
  • 使用 implements 关键字可以变相的使java具有多继承的特性

    1
    2
    3
    4
    5
    6
    7
    8
    public interface A {
    public void eat();
    public void sleep();
    }
    public interface B {
    public void show();
    }
    public class C implements A,B {}

多态

多态:同一行为具有多个不同的表现形式或形态。例如同一个接口,使用不同的实例而执行不同操作。使程序更灵活、易于扩展。

多态最常用的是接口引用指向实现类对象,这样当之后程序需要更新时候,只需要修改new后面的实现类即可,左边就不用修改了,从而降低耦合。

简单一句话,多态是:接口引用指向实现类对象。

在Spring框架中,甚至可以通过配置或注解连实例化的new也不用了,从而更高层次的降低耦合。

1
List<String> a = new ArrayList<String>();

这样写的话,等号右边换成Vector 或 LinkedList 时,就可以很少修改代码量,降低耦合。

1
2
3
4
5
6
7
8
// 向上转型:父类引用指向子类对象。编译看左边(调用子类特有变量、方法会报错),运行看右边(优先运行子类重写后的方法)。
// 向下转型:子类引用指向父类对象。编译看右边,运行看右边。
public static void main(String[] args) {
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}