Java面向对象详解
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
用于:
类名
变量名
方法名
字段名
安全重命名。