0%

从0开始学java 01 java面向对象的基础知识

生活所迫呀。

由于生活所迫,我宣布我现在开始学java。

第 1 章:类与对象实战

1.1 定义类、属性、方法

在 Java 中,类(class)是对象的模板对象(object)是类的实例。类中可以包含:

  • 属性(字段、变量):描述对象的特征
  • 方法:描述对象的行为
    如:
1
2
3
4
5
6
7
8
9
10
public class Person{
// 属性(成员变量)
String name;
int age;

// 方法(成员方法)
void sayHello(){
System.out.println(name);
}
}

1.2 创建对象并调用方法

创建对象用关键字 new。创建后可以访问属性或调用方法。

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

如:

1
2
3
4
5
6
7
8
public class Main{
public static void main(String[] args) {
Person p = new Person();
p.name = "fty";
p.age = 23;
p.sayHellow();
}
}

1.3 构造方法编写与重载

构造方法(Constructor) 是在 new 一个对象时调用的方法,用来初始化属性。

  • 构造方法名必须与类名相同
  • 构造方法可以有多个(重载)
    如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person{
String name;
int age;

// 无参构造方法
public Person() {
name = "Unknown";
age = 0;
}

// 有参构造方法(重载)
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

1.4 使用 this 关键字

this 是 Java 中的一个关键字,代表“当前对象”。
用在两个场景中:

  1. 解决方法参数名和属性名冲突
  2. 在类的方法中调用该对象自己的属性或方法
    如:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person{
String name;
int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

void showInfo() {
System.out.println(this.name + this.age);
}
}

第 2 章:封装与访问控制实践

2.1 使用 private 属性封装数据

在 Java 中,封装(Encapsulation) 是把对象的属性隐藏起来,只通过方法进行访问和修改。
这通常通过把属性设为 private 实现,防止外部直接访问。
如:

1
2
3
4
5
6
7
8
public class Person {
private String name; // 私有属性,外部无法直接访问
private int age;

void introduce() {
System.out.println("Hi, my name is " + name + ", I’m " + age + " years old.");
}
}
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Person p = new Person();
// ❌ 不能直接访问私有属性
// p.name = "Tom"; // 编译错误!
}
}

2.2 编写 Getter / Setter

Getter/Setter 是访问私有属性的“通道”:

  • getXxx() 获取属性值
  • setXxx() 设置属性值
    如:
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 Person {
private String name;
private int age;

// Getter
public String getName() {
return name;
}

// Setter
public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
// 可以加判断逻辑,防止非法年龄
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("Invalid age.");
}
}
}
1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.setName("Alice");
p.setAge(20);
System.out.println("Name: " + p.getName());
System.out.println("Age: " + p.getAge());
}
}

2.3 类的访问控制修饰符应用

Java 提供四种访问修饰符控制类和类成员的访问范围:

修饰符 类内 同包 子类 其他包
private
default(无修饰)
protected
public
如:
1
2
3
4
5
6
7
8
9
10
public class Animal {
public String type = "Mammal";
protected String sound = "Growl";
String color = "Brown"; // default
private int age = 3;

public void show() {
System.out.println("Animal info: " + type + ", " + sound + ", " + color + ", " + age);
}
}
1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
Animal a = new Animal();
System.out.println(a.type); // ✅ public
System.out.println(a.sound); // ✅ 同包可访问 protected
System.out.println(a.color); // ✅ 同包可访问 default
// System.out.println(a.age); // ❌ private,不能访问
}
}

第 3 章:继承语法与调用关系

3.1 使用 extends 实现继承

ava 使用 extends 关键字让一个类继承另一个类的属性和方法。
✅ 继承的作用:

  • 子类拥有父类的非 private 成员(属性和方法)
  • 避免重复代码
  • 体现「是一种(is-a)」的关系
    如:
1
2
3
4
5
public class Animal {
public void eat() {
System.out.println("Animal is eating...");
}
}
1
2
3
4
5
public class Dog extends Animal {
public void bark() {
System.out.println("Dog is barking...");
}
}
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 继承自 Animal
dog.bark(); // 自己的
}
}

3.2 子类调用父类构造器与方法

  • 构造器不能继承,但可以通过 super() 调用父类构造器。
  • 子类实例化时,默认会先调用父类的无参构造器
    如:
3.2.1 父类有无参构造
1
2
3
4
5
6
7
8
9
10
public class Animal {
public Animal() {
System.out.println("Animal constructor called");
}
}
public class Dog extends Animal {
public Dog() {
System.out.println("Dog constructor called");
}
}
3.2.2 父类只有有参构造时,子类必须显式调用
1
2
3
4
5
6
7
8
9
10
11
public class Animal {
public Animal(String name) {
System.out.println("Animal: " + name);
}
}
public class Dog extends Animal {
public Dog() {
super("Buddy"); // 必须显式调用父类构造器
System.out.println("Dog created");
}
}

3.3 使用 super 调用父类成员

  • super.变量名:访问父类的属性
  • super.方法名():调用父类的方法
    通常在子类重写父类方法时,用 super 调用原始实现。
1
2
3
4
5
6
7
8
9
10
11
public class Animal {
public void move() {
System.out.println("Animal moves");
}
}
public class Bird extends Animal {
public void move() {
super.move(); // 调用父类方法
System.out.println("Bird flies");
}
}

3.4 子类方法重写(@Override

当子类对父类已有方法进行重新实现,称为“方法重写”或“覆盖”。
必须:

  • 方法名、参数列表相同
  • 使用 @Override 注解(推荐)
    如:
1
2
3
4
5
6
7
8
9
10
11
public class Animal {
public void sound() {
System.out.println("Animal makes a sound");
}
}
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("Cat meows");
}
}
1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
Animal a = new Cat(); // 向上转型
a.sound(); // 输出 "Cat meows"
}
}

虽然类型是 Animal,但调用的是 Cat 的实现 —— 这就是多态的表现。


第 4 章:方法重载与重写

4.1 方法重载实现

方法重载是指在同一个类中,方法名相同,但参数列表不同(个数或类型)。其特点为:

  • 与返回值无关
  • 是编译时多态的一种形式
    如:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Calculator {
public int add(int a, int b) {
return a + b;
}

public double add(double a, double b) {
return a + b;
}

public int add(int a, int b, int c) {
return a + b + c;
}
}
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
Calculator c = new Calculator();
System.out.println(c.add(1, 2)); // int + int
System.out.println(c.add(1.5, 2.5)); // double + double
System.out.println(c.add(1, 2, 3)); // 三个 int
}
}

4.2 方法重写规则

方法重写发生在继承结构中,子类重写父类已有方法,必须满足以下规则:

要求 内容
方法名 必须相同
参数列表 必须相同
返回值类型 必须相同或子类型
访问权限 不能低于父类
抛出异常 不能抛出更多的受检异常
标记 建议使用 @Override 注解
如:
1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}

class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.makeSound(); // 输出:Dog barks
}
}

4.3 重写 vs 重载对比实战

特性 重载(Overload) 重写(Override)
定义位置 同一个类中 子类与父类之间
方法名 相同 相同
参数列表 必须不同 必须相同
返回值 可不同(不构成重载) 必须相同或是子类类型
访问修饰符 无限制 子类不能比父类更严格
多态 编译时多态 运行时多态

第 5 章:多态与父类引用

5.1 父类引用指向子类对象

在Java中,父类类型的引用变量可以指向子类对象。这称为“向上转型”(Upcasting),是多态的基础。
如上面提到的:

1
Animal a = new Dog();
  • 这里a的类型是Animal,但实际引用的是Dog对象。
  • 父类引用只能访问父类中声明的成员(变量和方法)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}

class Dog extends Animal {
public void makeSound() {
System.out.println("Dog barks");
}

public void wagTail() {
System.out.println("Dog wags tail");
}
}

public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // 父类引用指向子类对象
a.makeSound(); // 调用的是子类重写的方法
// a.wagTail(); // 编译错误,父类引用不能调用子类独有方法
}
}

5.2 多态方法调用效果

虽然引用类型是父类,但方法调用执行的是子类重写后的版本,体现“运行时绑定”。这让程序更加灵活,接口统一,具体实现多样。

  • 调用方法时,会执行子类重写的方法(如果有);
  • 访问成员变量时,访问的是引用类型所属类的变量(不支持多态);
    如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal {
public String name = "Animal";

public void printName() {
System.out.println(name);
}
}

class Cat extends Animal {
public String name = "Cat";

@Override
public void printName() {
System.out.println(name);
}
}

public class Main {
public static void main(String[] args) {
Animal a = new Cat();
System.out.println(a.name); // 输出 "Animal"
a.printName(); // 输出 "Cat"
}
}

5.3 多态与数组、集合应用

多态也可以与数组和集合结合使用,方便统一管理不同子类对象。
如:

5.3.1 多态数组
1
2
3
4
5
6
7
8
Animal[] animals = new Animal[3];
animals[0] = new Dog();
animals[1] = new Cat();
animals[2] = new Animal();

for (Animal a : animals) {
a.makeSound();
}
5.3.2 多态集合
1
2
3
4
5
6
7
8
9
10
import java.util.ArrayList;
import java.util.List;

List<Animal> animalList = new ArrayList<>();
animalList.add(new Dog());
animalList.add(new Cat());

for (Animal a : animalList) {
a.makeSound();
}

第 6 章:Object 类与常用方法

6.1 toString() 方法重写

toString()Object 类中定义的方法,所有Java类默认继承。 默认实现返回的是对象的类名 + @ + 哈希码,不够直观。 重写 toString() 让对象打印时更具可读性,常用于调试和日志。
如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}

public class Main {
public static void main(String[] args) {
Person p = new Person("Alice", 25);
System.out.println(p); // 自动调用 toString()
}
}

6.2 equals() 比较对象内容

默认 equals() 方法是比较两个对象的引用地址(即是否是同一个对象), 重写 equals() 方法,通常基于对象的属性内容进行比较。常与 hashCode() 一起重写,以保证集合中的正确行为。
如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
private String name;
private int age;

// 构造函数和 getter/setter 省略

@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 比较同一个对象
if (obj == null || getClass() != obj.getClass()) return false; // 类型检查

Person other = (Person) obj;
return age == other.age && (name != null ? name.equals(other.name) : other.name == null);
}
}
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Bob", 30);
Person p2 = new Person("Bob", 30);
System.out.println(p1.equals(p2)); // true
}
}

6.3 clone() 方法与浅拷贝

clone()Object 中的一个方法,用来复制对象。 默认实现是浅拷贝:基本类型拷贝,引用类型只复制引用。 需要实现 Cloneable 接口并重写 clone(),否则会抛出异常。浅拷贝对于对象中有引用类型字段时,拷贝后两个对象会共享同一个引用字段。
如:

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
public class Address implements Cloneable {
private String city;

public Address(String city) {
this.city = city;
}

@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}

// getter/setter 省略
}

public class Person implements Cloneable {
private String name;
private Address address;

public Person(String name, Address address) {
this.name = name;
this.address = address;
}

@Override
protected Person clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
// 浅拷贝:address对象引用被复制,两个Person共享同一个Address对象
return cloned;
}

// getter/setter 省略
}
  • Personclone() 是浅拷贝,拷贝的 Person 对象和原对象的 address 指向同一个 Address 实例。
  • 若需完全拷贝(深拷贝),需要对引用字段也进行 clone()

第 7 章:staticfinal 关键字用法

7.1 静态属性与静态方法

  • static 修饰的成员属于类本身,而不是某个对象。
  • 静态属性(变量)在内存中只有一份,所有对象共享。
  • 静态方法可以直接通过类名调用,无需创建实例。
  • 静态方法中不能访问非静态成员(因为非静态属于对象)。
    如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Counter {
private static int count = 0; // 静态变量,所有对象共享

public Counter() {
count++; // 每创建一个对象,count自增
}

public static int getCount() { // 静态方法,访问静态变量
return count;
}
}

public class Main {
public static void main(String[] args) {
new Counter();
new Counter();
System.out.println(Counter.getCount()); // 输出 2
}
}

7.2 静态代码块

  • 静态代码块用于类加载时执行一次的初始化操作。
  • 适合做复杂的静态变量初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Config {
public static String ENV;
public static int MAX_USERS;

static {
ENV = "production";
MAX_USERS = 1000;
System.out.println("Config 类被加载,静态代码块执行");
}
}

public class Main {
public static void main(String[] args) {
System.out.println(Config.ENV); // "production"
System.out.println(Config.MAX_USERS); // 1000
}
}

注意:静态代码块只执行一次,且在类被首次加载时执行。

7.3 final 类、方法、变量使用

final 可以修饰变量、方法、类,含义不同:

用法 说明
final 变量 常量,一旦赋值不可更改
final 方法 不可被子类重写
final 类 不能被继承的类(比如 String 类)
如:
7.3.1 final变量
1
2
3
4
5
6
7
8
public class Constants {
public static final double PI = 3.14159; // 常量,命名规范全部大写

public static void main(String[] args) {
// PI = 3.14; // 编译错误,final变量不可修改
System.out.println(Constants.PI);
}
}
7.3.2 final方法
1
2
3
4
5
6
7
8
9
10
11
class Parent {
public final void show() {
System.out.println("Parent final method");
}
}

class Child extends Parent {
// public void show() { // 编译错误,final方法不能被重写
// System.out.println("Child override");
// }
}
7.3.3 final类
1
2
3
4
5
6
7
8
public final class Utility {
public static void help() {
System.out.println("Helping...");
}
}

// class ExtendedUtility extends Utility { // 编译错误,final类不能被继承
// }

第 8 章:抽象类与接口

8.1 抽象类与抽象方法编写

  • 抽象类 是不能被实例化的类,用于被继承。
  • 抽象类中可以包含抽象方法(没有方法体,只定义方法签名),必须由子类实现。
  • 抽象类也可以有普通方法和成员变量。
  • abstract 关键字声明。
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 abstract class Animal {
protected String name;

public Animal(String name) {
this.name = name;
}

// 抽象方法,没有方法体
public abstract void sound();

public void sleep() {
System.out.println(name + " is sleeping.");
}
}

public class Dog extends Animal {
public Dog(String name) {
super(name);
}

@Override
public void sound() {
System.out.println(name + " says: Woof!");
}
}
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Animal dog = new Dog("Buddy");
dog.sound(); // 输出 Buddy says: Woof!
dog.sleep(); // 输出 Buddy is sleeping.
}
}

8.2 接口的定义与实现

  • 接口 是一种纯抽象的规范,只有方法签名和常量(Java 8+ 支持默认方法和静态方法)。
  • 类通过 implements 关键字实现接口,必须实现接口中的所有抽象方法。
  • 接口体现的是“能做什么”的能力,抽象类体现的是“是什么”的属性和行为。
    如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Flyable {
void fly();
}

public interface Swimmable {
void swim();
}

public class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("Duck is flying.");
}

@Override
public void swim() {
System.out.println("Duck is swimming.");
}
}
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Duck duck = new Duck();
duck.fly();
duck.swim();
}
}

8.3 接口多实现、向上转型

  • 一个类可以实现多个接口(多继承的替代方案)。
  • 可以将实现类对象向上转型为接口类型,利用多态调用接口方法。
  • 接口变量只能调用接口中声明的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
Duck duck = new Duck();

Flyable f = duck; // 向上转型为Flyable接口
Swimmable s = duck; // 向上转型为Swimmable接口

f.fly(); // 调用Flyable的方法
s.swim(); // 调用Swimmable的方法

// duck.fly(); // 直接调用实现类方法也可以
}
}

8.4 继承和实现的混合使用

  • 一个类只能继承一个直接父类(单继承)。
  • 但可以实现多个接口(多实现)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父类
public class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}

// 接口
public interface Flyable {
void fly();
}

// 子类继承父类,同时实现接口
public class Bird extends Animal implements Flyable {
@Override
public void fly() {
System.out.println("Bird is flying");
}
}
1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Bird bird = new Bird();
bird.eat(); // 继承自 Animal
bird.fly(); // 实现自 Flyable
}
}

第 8 章:示例-地理要素系统

设计说明

  • 抽象类 GeoFeature(地理要素),定义公共属性和抽象方法
  • 子类:PointFeature(点)、LineFeature(线)、PolygonFeature(多边形)
  • 接口 Renderable(可渲染),Calculable(可计算面积或长度)
  • 利用封装隐藏属性,提供 getter/setter
  • 使用 static 统计创建的地理要素数量
  • 使用 final 定义常量和禁止修改的方法
  • 重写 toString(), equals(), clone()
  • 演示多态和接口多实现

代码实现

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// 1. 抽象类,定义基础地理要素
public abstract class GeoFeature implements Cloneable {
private String id;
private String name;
private static int featureCount = 0; // 统计创建要素数量

public GeoFeature(String id, String name) {
this.id = id;
this.name = name;
featureCount++;
}

// 封装属性
public String getId() { return id; }
public void setId(String id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public static int getFeatureCount() {
return featureCount;
}

// 抽象方法,必须子类实现
public abstract void draw();

// final方法,不能被子类覆盖,打印信息
public final void showInfo() {
System.out.println("GeoFeature: " + toString());
}

// 重写toString()
@Override
public String toString() {
return "ID=" + id + ", Name=" + name;
}

// 重写equals,按id判断
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof GeoFeature)) return false;
GeoFeature other = (GeoFeature) obj;
return this.id.equals(other.id);
}

// 实现clone浅拷贝
@Override
public GeoFeature clone() {
try {
return (GeoFeature) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}

// 2. 接口:可渲染
interface Renderable {
void render();
}

// 3. 接口:可计算长度或面积
interface Calculable {
double calculate();
}

// 4. 点要素
class PointFeature extends GeoFeature implements Renderable {
private double x, y;

public PointFeature(String id, String name, double x, double y) {
super(id, name);
this.x = x;
this.y = y;
}

// 封装
public double getX() { return x; }
public void setX(double x) { this.x = x; }
public double getY() { return y; }
public void setY(double y) { this.y = y; }

@Override
public void draw() {
System.out.println("Drawing Point at (" + x + "," + y + ")");
}

@Override
public void render() {
System.out.println("Rendering Point: " + getName());
}

@Override
public String toString() {
return super.toString() + ", Point(" + x + "," + y + ")";
}
}

// 5. 线要素
class LineFeature extends GeoFeature implements Renderable, Calculable {
private double length;

public LineFeature(String id, String name, double length) {
super(id, name);
this.length = length;
}

public double getLength() { return length; }
public void setLength(double length) { this.length = length; }

@Override
public void draw() {
System.out.println("Drawing Line of length " + length);
}

@Override
public void render() {
System.out.println("Rendering Line: " + getName());
}

@Override
public double calculate() {
return length;
}

// 方法重载:计算长度乘以比例尺
public double calculate(double scale) {
return length * scale;
}

@Override
public String toString() {
return super.toString() + ", Line length=" + length;
}
}

// 6. 多边形要素
class PolygonFeature extends GeoFeature implements Renderable, Calculable {
private double area;

public PolygonFeature(String id, String name, double area) {
super(id, name);
this.area = area;
}

public double getArea() { return area; }
public void setArea(double area) { this.area = area; }

@Override
public void draw() {
System.out.println("Drawing Polygon of area " + area);
}

@Override
public void render() {
System.out.println("Rendering Polygon: " + getName());
}

@Override
public double calculate() {
return area;
}

@Override
public String toString() {
return super.toString() + ", Polygon area=" + area;
}
}