Java面向对象详解

O泡李华 7

Java 面向对象详解

本章位置:Java 核心基础
学习目标:理解 Java 面向对象编程思想,掌握类与对象、封装、继承、多态、抽象类、接口、this、super、static、final、Object 类等核心内容,并能够独立设计基础业务对象。


一、什么是面向对象

Java 是一门典型的面向对象编程语言。

面向对象英文:

Object-Oriented Programming

简称:

OOP

面向对象最核心的思想不是“把代码写进类里”,而是:

把程序中的数据和行为,按照现实世界中的对象进行组织。

例如:

学生
教师
商品
订单
用户
汽车
银行卡

这些都可以看成对象。


二、面向过程和面向对象的区别

面向过程

面向过程关注:

第一步做什么
第二步做什么
第三步做什么

例如“把大象装进冰箱”:

1. 打开冰箱
2. 把大象放进去
3. 关闭冰箱

这种思维强调:

过程
步骤
流程

面向对象

面向对象会考虑:

有哪些对象
对象有什么属性
对象能做什么事情

例如:

对象:冰箱

属性:
品牌
容量
颜色

行为:
打开
关闭
制冷

以及:

对象:大象

属性:
名字
年龄
体重

行为:
吃东西
走路
睡觉

三、为什么需要面向对象

假设需要保存一个学生的信息:

姓名
年龄
性别
学号
成绩

如果只使用普通变量:

String name = "张三";
int age = 20;
String gender = "男";
String studentNo = "20260001";
double score = 90.5;

一个学生还能勉强管理。

如果有 100 个学生,就会出现大量:

name1
name2
name3

age1
age2
age3

代码会越来越乱。

面向对象可以把这些数据组合在一起。

例如:

Student student = new Student();

然后:

student.name
student.age
student.studentNo
student.score

这些数据都属于:

同一个学生对象

四、类和对象

面向对象中两个最基础的概念:

类
对象

什么是类

类可以理解为:

对一类事物共同特征和行为的描述。

例如:

学生类

可以描述所有学生共有的内容:

属性:
姓名
年龄
学号

行为:
学习
考试
自我介绍

什么是对象

对象是:

根据类创建出来的具体实例。

例如:

Student 是类

张三是一个 Student 对象
李四是一个 Student 对象
王五是一个 Student 对象

可以简单记:

类 = 模板

对象 = 根据模板创建出来的具体实例

五、定义一个类

最基础的类:

public class Student {

    String name;

    int age;

    double score;

    void study() {

        System.out.println("学生正在学习");
    }
}

这里:

Student

是类名。


六、类中可以写什么

一个普通类中,最基础的内容包括:

成员变量
成员方法
构造方法

例如:

public class Student {

    String name;

    int age;

    public void study() {

        System.out.println("正在学习");
    }

    public Student() {

    }
}

七、成员变量

定义在:

类中
方法外

的变量称为:

成员变量

例如:

public class Student {

    String name;

    int age;

    double score;
}

这里:

name
age
score

都是成员变量。


八、创建对象

定义类之后,需要创建对象。

语法:

类名 对象名 = new 类名();

例如:

Student student = new Student();

可以拆开理解:

Student
对象类型

student
变量名

new Student()
创建一个新的 Student 对象

九、给对象属性赋值

例如:

Student student = new Student();

student.name = "张三";

student.age = 20;

student.score = 95.5;

使用:

对象名.成员变量

访问对象中的属性。


十、访问对象属性

System.out.println(student.name);

System.out.println(student.age);

System.out.println(student.score);

输出:

张三
20
95.5

十一、调用对象方法

类:

public class Student {

    String name;

    public void study() {

        System.out.println("正在学习");
    }
}

创建对象:

Student student = new Student();

调用方法:

student.study();

这里:

student

是对象。

study()

是对象的行为。


十二、完整类与对象示例

Student.java:

public class Student {

    String name;

    int age;

    double score;

    public void study() {

        System.out.println(
                name + " 正在学习"
        );
    }

    public void introduce() {

        System.out.println(
                "我叫 " + name
                        + ",今年 "
                        + age
                        + " 岁"
        );
    }
}

测试:

public class StudentTest {

    public static void main(String[] args) {

        Student student =
                new Student();

        student.name = "张三";

        student.age = 20;

        student.score = 90;

        student.study();

        student.introduce();
    }
}

十三、一个类可以创建多个对象

例如:

Student s1 = new Student();

Student s2 = new Student();

给它们分别赋值:

s1.name = "张三";

s2.name = "李四";

输出:

System.out.println(s1.name);

System.out.println(s2.name);

结果:

张三
李四

说明:

每个对象都有自己的成员变量

十四、对象在内存中的简单理解

代码:

Student s =
        new Student();

可以简单理解成:

栈
┌──────────────┐
│ s            │
│   │          │
└───│──────────┘
    │
    ▼
堆
┌──────────────────┐
│ Student 对象     │
│                  │
│ name = null      │
│ age = 0          │
│ score = 0.0      │
└──────────────────┘

变量:

s

保存的是:

对象的引用

十五、成员变量的默认值

对象创建后,成员变量会自动获得默认值。

例如:

public class Student {

    String name;

    int age;

    double score;

    boolean login;
}

创建对象:

Student s = new Student();

默认值:

name  = null
age   = 0
score = 0.0
login = false

十六、成员变量和局部变量

成员变量:

public class Student {

    int age;
}

定义在:

类中、方法外

局部变量:

public void test() {

    int number = 10;
}

定义在:

方法内部
代码块内部

十七、成员变量和局部变量区别

对比 成员变量 局部变量
定义位置 类中、方法外 方法或代码块中
默认值 有默认值 使用前必须赋值
作用范围 对象内部 所在方法或代码块
生命周期 跟随对象 跟随方法执行
常见用途 描述对象属性 临时计算

十八、成员方法

定义在类中,用来描述对象行为的方法:

public void study() {

    System.out.println("学习");
}

例如:

public class User {

    String username;

    public void login() {

        System.out.println(
                username + " 登录系统"
        );
    }
}

十九、带参数的方法

例如:

public void study(
        String subject
) {

    System.out.println(
            "正在学习:" + subject
    );
}

调用:

student.study("Java");

二十、有返回值的方法

例如:

public int getAge() {

    return age;
}

调用:

int result =
        student.getAge();

二十一、构造方法

构造方法用于:

创建对象时初始化对象

基本语法:

public 类名() {

}

例如:

public class Student {

    public Student() {

        System.out.println(
                "Student 对象创建了"
        );
    }
}

创建:

Student student =
        new Student();

构造方法会自动执行。


二十二、构造方法特点

构造方法:

1. 方法名和类名完全一致
2. 没有返回值类型
3. 创建对象时自动执行

错误:

public void Student() {

}

这不是构造方法。

因为写了:

void

它只是一个普通方法。


二十三、默认构造方法

如果一个类:

没有手动写任何构造方法

Java 会自动提供一个:

无参构造方法

例如:

public class Student {

}

相当于编译器帮助提供:

public Student() {

}

二十四、写了构造方法后默认无参构造不再自动提供

例如:

public class Student {

    public Student(String name) {

    }
}

此时:

new Student();

会报错。

因为你已经手动写了构造方法,

编译器不会再自动补:

Student()

如果需要无参构造:

public Student() {

}

要自己写。


二十五、带参数构造方法

例如:

public class Student {

    String name;

    int age;

    public Student(
            String name,
            int age
    ) {

        this.name = name;

        this.age = age;
    }
}

创建对象:

Student student =
        new Student(
                "张三",
                20
        );

这样创建对象时就完成初始化。


二十六、构造方法重载

一个类可以有多个构造方法。

例如:

public class Student {

    String name;

    int age;

    public Student() {

    }

    public Student(
            String name
    ) {

        this.name = name;
    }

    public Student(
            String name,
            int age
    ) {

        this.name = name;

        this.age = age;
    }
}

这种:

方法名相同
参数列表不同

叫:

方法重载

二十七、this 关键字

this 表示:

当前对象。

例如:

public class Student {

    String name;

    public void show() {

        System.out.println(
                this.name
        );
    }
}

二十八、this 解决成员变量和局部变量同名问题

例如:

public class Student {

    String name;

    public void setName(
            String name
    ) {

        name = name;
    }
}

这段代码没有达到预期效果。

因为:

左右两边 name

默认优先表示局部变量。

正确:

public void setName(
        String name
) {

    this.name = name;
}

这里:

this.name

表示:

当前对象的成员变量 name

右边:

name

表示参数。


二十九、this 调用当前对象方法

例如:

public void test() {

    this.show();
}

通常:

this.

可以省略:

show();

三十、this 调用构造方法

构造方法之间可以通过:

this(...)

调用。

例如:

public class Student {

    String name;

    int age;

    public Student() {

        this(
                "未知",
                0
        );
    }

    public Student(
            String name,
            int age
    ) {

        this.name = name;

        this.age = age;
    }
}

注意:

this(...)

必须写在构造方法:

第一行

三十一、封装

面向对象三大特征之一:

封装

封装可以理解成:

把对象的数据隐藏起来,通过受控的方法进行访问和修改。

例如:

public class Student {

    public int age;
}

外部可以:

student.age = -100;

显然不合理。


三十二、private

使用:

private

可以限制成员变量直接访问。

例如:

public class Student {

    private int age;
}

外部:

student.age = 20;

会报错。

因为:

age 是私有成员

三十三、Getter 和 Setter

如果成员变量使用:

private

一般会提供:

Getter
Setter

例如:

public class Student {

    private int age;

    public int getAge() {

        return age;
    }

    public void setAge(
            int age
    ) {

        this.age = age;
    }
}

三十四、Setter 中进行数据校验

封装的价值之一:

可以控制数据是否合法

例如:

public void setAge(
        int age
) {

    if (age < 0
            || age > 150) {

        System.out.println(
                "年龄不合法"
        );

        return;
    }

    this.age = age;
}

这样外部不能随便把年龄改成负数。


三十五、完整封装示例

public class User {

    private String username;

    private int age;

    public String getUsername() {

        return username;
    }

    public void setUsername(
            String username
    ) {

        this.username = username;
    }

    public int getAge() {

        return age;
    }

    public void setAge(
            int age
    ) {

        if (age < 0
                || age > 150) {

            throw new IllegalArgumentException(
                    "年龄不合法"
            );
        }

        this.age = age;
    }
}

三十六、封装的好处

封装可以:

保护对象数据
隐藏实现细节
统一数据访问方式
增加数据校验
降低代码耦合
方便以后修改内部实现

三十七、IDEA 快速生成 Getter、Setter、构造方法

在 IntelliJ IDEA 中:

Windows:
Alt + Insert

可以快速生成:

Constructor
Getter
Setter
Getter and Setter
toString
equals and hashCode

也可以:

右键
Generate

进行生成。


三十八、static 关键字

static 表示:

静态

它修饰的成员:

属于类

而不是某一个具体对象。


三十九、静态成员变量

例如:

public class Student {

    static String school =
            "第一中学";

    String name;
}

其中:

school

属于所有 Student 对象共享。


四十、访问静态变量

推荐:

Student.school

例如:

System.out.println(
        Student.school
);

不推荐通过对象访问:

student.school

虽然某些情况下语法允许,但表达含义不清晰。


四十一、静态变量共享

例如:

public class Student {

    static String school =
            "Java大学";

    String name;
}

创建:

Student s1 =
        new Student();

Student s2 =
        new Student();

修改:

Student.school =
        "OpenAI大学";

两个对象看到的学校都会变。

因为:

static 成员只有一份

四十二、静态方法

例如:

public class MathUtil {

    public static int add(
            int a,
            int b
    ) {

        return a + b;
    }
}

调用:

int result =
        MathUtil.add(
                10,
                20
        );

不需要:

new MathUtil()

四十三、静态方法中不能直接访问普通成员变量

例如:

public class Student {

    String name;

    public static void test() {

        System.out.println(name);
    }
}

会报错。

原因:

static 方法属于类

name 属于对象

静态方法执行时:

不一定存在具体对象

四十四、static 使用场景

常见场景:

工具类方法
公共常量
全局共享数据
计数器
工厂方法

例如:

Math.max()
Arrays.sort()
Integer.parseInt()

很多都是静态方法。


四十五、静态代码块

语法:

static {

}

例如:

public class Demo {

    static {

        System.out.println(
                "静态代码块"
        );
    }
}

静态代码块通常在:

类首次加载时执行一次

常见用途:

初始化静态资源
初始化配置

四十六、普通代码块

例如:

public class Demo {

    {

        System.out.println(
                "普通代码块"
        );
    }
}

通常:

每创建一次对象
就执行一次

实际开发使用频率没有构造方法高。


四十七、继承

面向对象三大特征之一:

继承

继承可以理解:

子类继承父类已有的属性和行为。

例如:

Person
│
├─ Student
└─ Teacher

学生和教师都属于:

Person

四十八、extends

Java 使用:

extends

实现类继承。

父类:

public class Person {

    String name;

    int age;

    public void eat() {

        System.out.println(
                "正在吃饭"
        );
    }
}

子类:

public class Student
        extends Person {

    public void study() {

        System.out.println(
                "正在学习"
        );
    }
}

四十九、子类可以使用父类成员

例如:

Student student =
        new Student();

student.name = "张三";

student.age = 20;

student.eat();

student.study();

Student 自己没有定义:

name
age
eat()

但通过继承获得。


五十、继承的好处

继承主要解决:

代码重复

例如:

Student
Teacher
Doctor

都有:

name
age
gender
eat()
sleep()

这些公共内容可以放进:

Person

然后让子类继承。


五十一、Java 单继承

Java 类:

只能直接继承一个父类

正确:

class Student
        extends Person {
}

错误:

class Student
        extends Person, User {
}

Java 不支持类的多继承。


五十二、继承具有传递性

例如:

A
↑
B
↑
C

代码:

class A {

}

class B extends A {

}

class C extends B {

}

那么:

C 也是 A 的子类

五十三、Object 是所有类的最终父类

Java 中:

public class Student {

}

如果没有显式继承其他类,

实际上最终都会继承:

java.lang.Object

所以所有 Java 对象都可以使用 Object 提供的一些基础方法。


五十四、private 成员能不能继承

子类对象中仍然存在父类的私有数据,

但:

子类不能直接访问父类 private 成员

例如:

public class Person {

    private String name;
}

子类:

public class Student
        extends Person {

    public void test() {

        // System.out.println(name);
    }
}

不能直接访问。

应该通过父类提供的:

Getter
Setter

访问。


五十五、super 关键字

super 表示:

父类部分

常见用途:

访问父类成员变量
调用父类方法
调用父类构造方法

五十六、super 访问父类成员变量

例如:

public class Parent {

    String name = "父类";
}

子类:

public class Child
        extends Parent {

    String name = "子类";

    public void show() {

        System.out.println(
                name
        );

        System.out.println(
                this.name
        );

        System.out.println(
                super.name
        );
    }
}

五十七、this 和 super 的区别

this
当前对象

super
当前对象中的父类部分

例如:

this.name

优先访问当前类成员。

super.name

访问父类成员。


五十八、调用父类构造方法

例如:

public class Person {

    String name;

    public Person(
            String name
    ) {

        this.name = name;
    }
}

子类:

public class Student
        extends Person {

    int score;

    public Student(
            String name,
            int score
    ) {

        super(name);

        this.score = score;
    }
}

这里:

super(name);

调用父类构造方法。


五十九、super(…) 必须写在第一行

构造方法中:

super(...)

必须是:

第一条语句

同样:

this(...)

也必须写第一行。

因此:

this(...)
super(...)

不能在同一个构造方法中同时直接使用。


六十、子类构造方法默认调用 super()

例如父类:

public class Person {

    public Person() {

        System.out.println(
                "Person 构造"
        );
    }
}

子类:

public class Student
        extends Person {

    public Student() {

        System.out.println(
                "Student 构造"
        );
    }
}

创建:

new Student();

会先执行:

Person 构造

再执行:

Student 构造

因为子类构造方法默认会调用:

super();

六十一、方法重写

子类可以重新定义父类已有方法。

这种行为叫:

方法重写

英文:

Override

父类:

public class Animal {

    public void speak() {

        System.out.println(
                "动物发出声音"
        );
    }
}

子类:

public class Dog
        extends Animal {

    @Override
    public void speak() {

        System.out.println(
                "汪汪汪"
        );
    }
}

六十二、@Override

推荐重写方法时使用:

@Override

作用:

让编译器帮助检查
这个方法是否真的重写了父类方法

例如方法名写错:

@Override
public void speek() {

}

编译器会直接提示错误。


六十三、方法重写基本规则

重写时:

方法名必须相同
参数列表必须相同
返回值类型兼容
访问权限不能更严格

例如父类:

public void test() {

}

子类不能改成:

private void test() {

}

因为权限变小。


六十四、方法重载和方法重写区别

重载 Overload

发生在:

同一个类中

特点:

方法名相同
参数列表不同

例如:

add(int a, int b)

add(double a, double b)

重写 Override

发生在:

父子类之间

特点:

子类重新实现父类方法

六十五、多态

面向对象三大特征之一:

多态

多态可以简单理解:

同一个父类引用,可以指向不同的子类对象,并表现出不同的行为。

例如:

Animal animal =
        new Dog();

或者:

Animal animal =
        new Cat();

六十六、多态基本案例

父类:

public class Animal {

    public void speak() {

        System.out.println(
                "动物叫"
        );
    }
}

Dog:

public class Dog
        extends Animal {

    @Override
    public void speak() {

        System.out.println(
                "汪汪汪"
        );
    }
}

Cat:

public class Cat
        extends Animal {

    @Override
    public void speak() {

        System.out.println(
                "喵喵喵"
        );
    }
}

测试:

Animal a1 =
        new Dog();

Animal a2 =
        new Cat();

a1.speak();

a2.speak();

输出:

汪汪汪
喵喵喵

六十七、多态的前提

多态通常需要:

1. 有继承或实现关系

2. 有方法重写

3. 父类引用指向子类对象

例如:

Animal animal =
        new Dog();

六十八、向上转型

例如:

Dog dog =
        new Dog();

Animal animal =
        dog;

或者直接:

Animal animal =
        new Dog();

这种:

子类对象
→ 父类引用

叫:

向上转型

通常是自动完成的。


六十九、多态下成员方法执行规则

例如:

Animal animal =
        new Dog();

animal.speak();

如果 Dog 重写了:

speak()

最终执行:

Dog 的 speak()

可以简单记:

编译看左边
运行看右边

尤其对于重写后的成员方法。


七十、多态下不能直接调用子类特有方法

例如 Dog 有:

public void guard() {

    System.out.println(
            "看家"
    );
}

但:

Animal animal =
        new Dog();

不能直接:

animal.guard();

因为变量声明类型:

Animal

中没有:

guard()

七十一、向下转型

如果确实需要调用子类特有方法:

Dog dog =
        (Dog) animal;

然后:

dog.guard();

这种:

父类引用
→ 子类引用

叫:

向下转型

七十二、向下转型风险

例如:

Animal animal =
        new Cat();

Dog dog =
        (Dog) animal;

编译可能通过,

运行时会出现:

ClassCastException

因为实际对象是:

Cat

不是:

Dog

七十三、instanceof

可以使用:

instanceof

判断对象实际类型。

例如:

if (animal instanceof Dog) {

    Dog dog =
            (Dog) animal;

    dog.guard();
}

这样更安全。


七十四、JDK 17 instanceof 模式匹配

JDK 17 可以写:

if (animal instanceof Dog dog) {

    dog.guard();
}

这样不需要再手动:

Dog dog =
        (Dog) animal;

代码更简洁。


七十五、多态的真正价值

多态最大的价值:

让程序依赖父类或接口
而不是依赖具体实现

例如:

public void makeSound(
        Animal animal
) {

    animal.speak();
}

调用:

makeSound(
        new Dog()
);

makeSound(
        new Cat()
);

同一个方法可以处理:

Dog
Cat
以及以后新增的其他 Animal 子类

七十六、final 关键字

final 可以修饰:

变量
方法
类

七十七、final 修饰变量

例如:

final int MAX = 100;

之后不能再修改:

MAX = 200;

会报错。

因此常量经常使用:

final

七十八、static final 常量

类级别常量通常写:

public static final double PI =
        3.1415926;

访问:

Math.PI

类似思想。

常量命名通常:

全部大写
多个单词使用下划线

例如:

MAX_COUNT
DEFAULT_SIZE

七十九、final 修饰方法

父类:

public final void test() {

}

子类:

不能重写这个方法

八十、final 修饰类

例如:

public final class A {

}

那么:

A 不能被继承

Java 中的:

String

就是 final 类。


八十一、final 引用变量

例如:

final Student student =
        new Student();

不能:

student =
        new Student();

因为引用不能重新指向其他对象。

但如果 Student 成员允许修改:

student.setName(
        "张三"
);

仍然可以。

所以:

final 引用
限制的是引用指向
不是对象内部所有内容

八十二、抽象类

有些父类不适合直接创建对象。

例如:

Animal
Shape
Employee

它们更多是抽象概念。

Java 可以使用:

abstract

定义抽象类。


八十三、定义抽象类

public abstract class Animal {

}

抽象类:

不能直接 new

错误:

Animal animal =
        new Animal();

八十四、抽象方法

抽象方法:

只有方法声明
没有方法体

例如:

public abstract void speak();

完整:

public abstract class Animal {

    public abstract void speak();
}

八十五、子类必须实现抽象方法

例如:

public class Dog
        extends Animal {

    @Override
    public void speak() {

        System.out.println(
                "汪汪汪"
        );
    }
}

如果普通子类不实现:

speak()

就会报错。

除非这个子类自己也是:

abstract

八十六、抽象类可以有普通方法

抽象类不是只能写抽象方法。

例如:

public abstract class Animal {

    public abstract void speak();

    public void sleep() {

        System.out.println(
                "正在睡觉"
        );
    }
}

所以抽象类可以包含:

成员变量
构造方法
普通方法
抽象方法
静态方法

八十七、为什么使用抽象类

例如所有动物都会:

发出声音

但是不同动物具体声音不同。

如果父类写:

public void speak() {

}

没有实际意义。

可以直接规定:

public abstract void speak();

意思:

Animal 子类必须自己实现 speak。


八十八、接口

接口可以理解为:

对一组能力或规范的定义。

Java 使用:

interface

定义接口。

例如:

public interface Flyable {

    void fly();
}

八十九、实现接口

使用:

implements

例如:

public class Bird
        implements Flyable {

    @Override
    public void fly() {

        System.out.println(
                "鸟正在飞"
        );
    }
}

九十、接口不能直接创建对象

错误:

Flyable flyable =
        new Flyable();

接口不能直接实例化。

但是可以:

Flyable flyable =
        new Bird();

这也是多态。


九十一、接口中的方法

传统接口抽象方法:

public abstract void fly();

在接口中可以简写:

void fly();

默认就是:

public abstract

九十二、接口中的常量

接口中的字段默认是:

public static final

例如:

public interface Config {

    int MAX_SIZE = 100;
}

等价:

public static final int MAX_SIZE =
        100;

九十三、一个类可以实现多个接口

这是接口非常重要的特点。

例如:

public class Duck
        implements Flyable,
                   Swimmable {

}

Java 类不能多继承:

一个类不能 extends 多个父类

但可以:

implements 多个接口

九十四、接口可以继承多个接口

例如:

public interface AdvancedAbility
        extends Flyable,
                Swimmable {

}

接口之间支持:

多继承

九十五、default 方法

Java 8 以后接口可以有默认方法。

例如:

public interface Flyable {

    default void takeOff() {

        System.out.println(
                "准备起飞"
        );
    }
}

实现类可以直接使用。


九十六、接口静态方法

接口中也可以定义:

static

方法。

例如:

public interface MathTool {

    static int add(
            int a,
            int b
    ) {

        return a + b;
    }
}

调用:

MathTool.add(
        10,
        20
);

九十七、抽象类和接口的区别

对比 抽象类 接口
关键字 abstract class interface
继承/实现 extends implements
多继承 类不支持多继承 可实现多个接口
成员变量 可以有普通成员变量 主要是常量
构造方法 有 没有普通构造方法
普通方法 可以 可有 default/static 等
设计含义 是一种“是什么” 是一种“能做什么”

九十八、什么时候用抽象类

如果几个类之间存在明显:

父子关系

并且需要共享:

成员变量
构造方法
普通方法

可以考虑抽象类。

例如:

Employee
├─ Developer
├─ Tester
└─ Manager

九十九、什么时候用接口

如果想表达:

某种能力
某种规则
某种协议

更适合接口。

例如:

Payable
Serializable
Comparable
Runnable

可以理解为:

能够支付
能够序列化
能够比较
能够运行

一百、Object 类

Java 中所有类最终都继承:

Object

Object 提供很多基础方法。

常见:

toString()
equals()
hashCode()
getClass()

一百零一、toString()

直接输出对象:

Student student =
        new Student();

System.out.println(student);

默认可能看到类似:

Student@1b6d3586

不直观。


一百零二、重写 toString()

可以:

@Override
public String toString() {

    return "Student{"
            + "name='"
            + name
            + '\''
            + ", age="
            + age
            + '}';
}

之后:

System.out.println(student);

会自动调用:

student.toString()

输出对象详细信息。


一百零三、IDEA 快速生成 toString()

在 IDEA:

Alt + Insert

选择:

toString()

即可自动生成。


一百零四、equals()

默认情况下:

Object.equals()

通常比较:

是否是同一个对象

例如:

Student s1 =
        new Student();

Student s2 =
        new Student();

System.out.println(
        s1.equals(s2)
);

通常是:

false

一百零五、为什么需要重写 equals()

例如:

两个学生对象
学号都一样

业务上可能认为:

它们代表同一个学生

这时可以根据:

studentNo

重写 equals()。


一百零六、hashCode()

如果重写:

equals()

通常也应该一起重写:

hashCode()

重要规则:

如果两个对象 equals() 为 true

它们的 hashCode() 必须相同

这个规则在后面学习:

HashMap
HashSet

时非常重要。


一百零七、IDEA 自动生成 equals 和 hashCode

使用:

Alt + Insert

选择:

equals() and hashCode()

然后根据需要选择参与比较的字段。

实际开发中不要手写大量模板代码。


一百零八、包 package

Java 使用:

package

组织类。

例如:

package com.example.user;

一个项目中可能有:

com.example.user
com.example.order
com.example.product

用于组织不同业务模块。


一百零九、import

如果需要使用其他包中的类:

import java.util.ArrayList;

例如:

import java.util.Scanner;

然后就可以:

Scanner scanner =
        new Scanner(System.in);

一百一十、访问权限修饰符

Java 常见:

public
protected
默认
private

权限从宽到窄可以大致记:

public
>
protected
>
默认
>
private

一百一十一、访问权限基本范围

修饰符 本类 同包 子类 其他包
public 可以 可以 可以 可以
protected 可以 可以 可以 条件访问
默认 可以 可以 不一定 不可以
private 可以 不可以 不可以 不可以

初学阶段重点记:

public
private

一百一十二、实体类常见写法

例如:

public class User {

    private Long id;

    private String username;

    private String password;

    public Long getId() {

        return id;
    }

    public void setId(
            Long id
    ) {

        this.id = id;
    }

    public String getUsername() {

        return username;
    }

    public void setUsername(
            String username
    ) {

        this.username =
                username;
    }

    public String getPassword() {

        return password;
    }

    public void setPassword(
            String password
    ) {

        this.password =
                password;
    }
}

这类对象通常叫:

实体类
JavaBean
POJO

具体概念后面可以继续区分。


一百一十三、内部类

定义在一个类内部的类:

public class Outer {

    class Inner {

    }
}

叫:

内部类

一百一十四、成员内部类

例如:

public class Outer {

    private String name =
            "外部类";

    class Inner {

        public void show() {

            System.out.println(
                    name
            );
        }
    }
}

内部类可以访问外部类成员。


一百一十五、静态内部类

例如:

public class Outer {

    static class Inner {

        public void show() {

            System.out.println(
                    "静态内部类"
            );
        }
    }
}

创建:

Outer.Inner inner =
        new Outer.Inner();

一百一十六、局部内部类

定义在方法内部:

public void test() {

    class Inner {

    }
}

叫:

局部内部类

实际开发中使用频率相对较低。


一百一十七、匿名内部类

如果一个接口:

public interface Animal {

    void speak();
}

可以直接:

Animal animal =
        new Animal() {

            @Override
            public void speak() {

                System.out.println(
                        "汪汪"
                );
            }
        };

这就是:

匿名内部类

特点:

没有单独类名
通常只使用一次

后面学习 Lambda 时会发现:

很多只有一个抽象方法的接口
可以进一步简化

一百一十八、组合关系

除了继承,还有一种很重要的关系:

组合

例如:

Car has Engine

汽车:

public class Car {

    private Engine engine;
}

表示:

汽车拥有一个发动机

一百一十九、继承和组合的区别

继承:

is-a

例如:

Dog is an Animal

组合:

has-a

例如:

Car has an Engine

实际开发中:

不要为了复用代码就滥用继承。

如果不是明显的:

is-a

关系,通常优先考虑组合。


一百二十、面向对象三大特征

Java 面向对象最常说:

封装
继承
多态

封装

隐藏内部数据
通过方法控制访问

继承

提取公共代码
建立父子关系

多态

父类或接口引用
指向不同子类对象
运行时表现出不同实现

一百二十一、综合案例:员工薪资系统

下面用一个完整案例串起来:

封装
继承
方法重写
抽象类
多态
接口

一百二十二、需求设计

系统中有三类员工:

程序员 Developer
测试 Tester
经理 Manager

所有员工都有:

编号
姓名
基本工资

并且都需要:

计算工资
工作

不同岗位工作内容不同。


一百二十三、定义抽象父类 Employee

public abstract class Employee {

    private int id;

    private String name;

    private double baseSalary;

    public Employee() {

    }

    public Employee(
            int id,
            String name,
            double baseSalary
    ) {

        this.id = id;

        this.name = name;

        this.baseSalary =
                baseSalary;
    }

    public int getId() {

        return id;
    }

    public void setId(
            int id
    ) {

        this.id = id;
    }

    public String getName() {

        return name;
    }

    public void setName(
            String name
    ) {

        this.name = name;
    }

    public double getBaseSalary() {

        return baseSalary;
    }

    public void setBaseSalary(
            double baseSalary
    ) {

        this.baseSalary =
                baseSalary;
    }

    public abstract double calculateSalary();

    public abstract void work();

    @Override
    public String toString() {

        return "Employee{"
                + "id="
                + id
                + ", name='"
                + name
                + '\''
                + ", baseSalary="
                + baseSalary
                + '}';
    }
}

一百二十四、Developer

public class Developer
        extends Employee {

    private double bonus;

    public Developer(
            int id,
            String name,
            double baseSalary,
            double bonus
    ) {

        super(
                id,
                name,
                baseSalary
        );

        this.bonus = bonus;
    }

    @Override
    public double calculateSalary() {

        return getBaseSalary()
                + bonus;
    }

    @Override
    public void work() {

        System.out.println(
                getName()
                        + " 正在编写代码"
        );
    }
}

一百二十五、Tester

public class Tester
        extends Employee {

    private int bugCount;

    public Tester(
            int id,
            String name,
            double baseSalary,
            int bugCount
    ) {

        super(
                id,
                name,
                baseSalary
        );

        this.bugCount =
                bugCount;
    }

    @Override
    public double calculateSalary() {

        return getBaseSalary()
                + bugCount * 20;
    }

    @Override
    public void work() {

        System.out.println(
                getName()
                        + " 正在进行软件测试"
        );
    }
}

一百二十六、Manager

public class Manager
        extends Employee {

    private double performanceBonus;

    public Manager(
            int id,
            String name,
            double baseSalary,
            double performanceBonus
    ) {

        super(
                id,
                name,
                baseSalary
        );

        this.performanceBonus =
                performanceBonus;
    }

    @Override
    public double calculateSalary() {

        return getBaseSalary()
                + performanceBonus;
    }

    @Override
    public void work() {

        System.out.println(
                getName()
                        + " 正在管理项目"
        );
    }
}

一百二十七、使用多态管理员工

public class Main {

    public static void main(
            String[] args
    ) {

        Employee[] employees = {

                new Developer(
                        1,
                        "张三",
                        8000,
                        2000
                ),

                new Tester(
                        2,
                        "李四",
                        7000,
                        50
                ),

                new Manager(
                        3,
                        "王五",
                        12000,
                        5000
                )
        };

        for (Employee employee
                : employees) {

            employee.work();

            System.out.println(
                    "工资:"
                            + employee
                            .calculateSalary()
            );

            System.out.println();
        }
    }
}

这里:

Employee[]

可以保存不同子类对象。

这就是多态非常典型的使用方式。


一百二十八、增加接口能力

例如定义:

public interface Reportable {

    void generateReport();
}

让 Manager 实现:

public class Manager
        extends Employee
        implements Reportable {

    @Override
    public void generateReport() {

        System.out.println(
                "生成项目报告"
        );
    }
}

这样:

Manager

既是:

Employee

又具备:

Reportable

能力。


一百四十一、IDEA 面向对象常用快捷操作

生成代码

Alt + Insert

常用:

Constructor
Getter
Setter
Getter and Setter
toString
equals and hashCode
Override Methods
Implement Methods

重写父类方法

可以使用:

Ctrl + O

选择需要重写的方法。


实现接口方法

可以使用:

Ctrl + I

快速实现接口或抽象方法。


重构重命名

Shift + F6

用于:

类名
变量名
方法名
字段名

安全重命名。