面向对象基础篇
Object Oriented Programming - Basic
从面向过程到面向对象的编程思维转变引言
所谓视界不是眼球所看到的画像,是大脑所理解的画像。
我们的视野被我们的常识所保护着,人是无法脱离自己的局限而活下去的
……本来的话。
——苍崎橙子《空之境界》
从面向过程到面向对象
在前面的学习中,我们已经掌握了面向过程编程的基础知识,能够独立编写简单的程序。
接下来我们将深入学习面向对象编程(Object Oriented Programming),这是Java语言的核心特性之一。
- 面向对象是现代高级编程语言的重要特征
- 面向对象概念对于初学者而言是一个重要的学习节点
- 部分学习者能够快速理解,而另一些可能需要更多时间来掌握
- 本章内容难度有所提升,建议认真学习和实践
熟练掌握面向对象编程是成为优秀Java开发者的关键一步。
什么是类与对象?
我们在日常生活中其实经常听到"类"这个词。比如说人类、鸟类、鱼类等等。
类(Class)
对一类东西的概括,是一种抽象的概念。比如鸟类,指的是所有具备鸟的特点的动物。
对象(Object)
这类事物中的每一个具体存在的个体,也叫实例。我们每个人都是人类这个大类别下的一个具体的例子。
Person 类
张三
李四
类与对象的禅
类就是抽象概念的人,而对象,就是具体的某一个人。
在Java中,我们可以定义一个类,然后进一步创建许多这个类的实例对象,让所有的功能都通过对象之间的交互来表达。
这种编程方式,我们称为面向对象编程。
面向过程编程
// 函数调用方式
eat(猪八戒, 西瓜);
将数据和操作分离,通过函数调用来实现功能
面向对象编程
// 对象方法调用方式
猪八戒.eat(西瓜);
将数据和操作封装在对象中,通过对象间交互实现功能
类的定义与对象创建
接下来我们将学习如何定义一个类。以定义人类为例,演示类的创建过程:
步骤1:创建类文件
在src目录上右键,选择创建新的Java类,命名为Person
- 类名通常使用英文单词
- 首字母必须大写
- 不能包含特殊字符
步骤2:基本类结构
class Person {
}
这与Main类的结构相同,Main本身也是一个类。
添加类的属性 (成员变量)
既然是"人类",那肯定要有与人相关的属性,比如名字、性别、年龄等。
我们可以把这些属性直接定义为类的成员变量。
public class Person {
// 这里定义的人类具有三个属性:名字、年龄、性别
String name; // 直接在类中定义变量,表示类具有的属性
int age;
String sex;
}
重要概念
成员变量就是这个类拥有的属性,每个实例创建出来后,这些属性的值可以各不相同。
这些变量只有在一个具体的对象中才可以使用。
创建对象
现在人类的属性都规定好了,我们就可以尝试创建一个实例对象了。
语法格式
new 类名();
实际例子
public static void main(String[] args) {
new Person(); // 使用new关键字来创建某个类的对象
// 注意new后面需要跟上 类名()
// 这里创建出来的,就是一个具体的人了
}
整个流程为:定义类 → 创建对象 → 使用对象
对象的使用
现在我们知道了怎么创建对象,那接下来怎么去使用这个对象呢?
我们可以用一个变量来代表这个对象:
public static void main(String[] args) {
// 这里的a存放的是具体的某个值
int a = 10;
// 创建一个变量指代我们刚刚创建好的对象
// 变量的类型就是对应的类名
// 这里的p存放的是对象的引用,而不是本体
Person p = new Person();
}
重要概念:引用类型
这种变量存的是对象的"地址"或"引用",而不是直接存储对象本身。
因此,这个变量的类型也被称为"引用类型",而不再是"基本类型"(如int, double)。
对象引用的概念
为什么对象类型的变量存放的是对象的引用?看下面的例子:
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = p1; // 这只是传递了对象的引用
}
赋值之后,现在p2和p1都指向同一个对象(如果你学过C语言,这就有点像指针的概念)。
基本类型内存模型
int a = 10;
int b = a;
引用类型内存模型
Person p1 = new Person();
Person p2 = p1;
测试验证
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = p1;
System.out.println(p1 == p2); // 使用 == 判断是否引用同一个对象
// 结果:true
Person p3 = new Person(); // 创建新对象
System.out.println(p1 == p3); // 不同对象的引用
// 结果:false
}
访问对象属性
现在我们有了对象的引用之后,我们就可以进行"."操作了:
public static void main(String[] args) {
Person p = new Person();
p.name = "小明"; // 要访问对象的属性,使用 . 运算符
System.out.println(p.name); // 打印对象的名字
}
重要特性
不同对象的属性是分开独立存放的,每个对象都有一个自己的空间,修改一个对象的属性并不会影响到其他对象:
Person p1 = new Person();
Person p2 = new Person();
p1.name = "小明"; // 修改第一个对象的属性
p2.name = "大明"; // 修改第二个对象的属性
System.out.println(p1.name); // 输出:小明
空引用(null)
关于对象类型的变量,我们也可以不对任何对象进行引用:
public static void main(String[] args) {
Person p1 = null; // null是一个特殊的值,表示空,不引用任何对象
}
⚠️ 空指针异常
若某一变量未引用任何对象,则不应通过该变量对对象进行操作:
public static void main(String[] args) {
Person p = null; // 此时变量没有引用任何对象
p.name = "小红"; // ❌ 这会导致空指针异常!
System.out.println(p.name);
}
这是Java中出现频率最高的异常,一定要注意避免!
对象属性的默认值
对象创建成功之后,它的属性没有进行赋值,但是能否直接访问呢?
public static void main(String[] args) {
Person p = new Person();
System.out.println("name = " + p.name);
System.out.println("age = " + p.age);
System.out.println("sex = " + p.sex);
}
运行结果
name = null
age = 0
sex = null
默认值规则
- 基本类型:默认为0(boolean默认为false)
- 引用类型:默认为null
添加类的功能(成员方法)
光有属性还不够,对象还需要能做一些事情,就像人可以走路、跳跃、思考一样。
对象也可以做各种动作,这可以通过定义方法来实现(在C语言中叫做函数)。
什么是方法?
方法就是一组指令,用来完成特定的任务。
- 有的任务完成后需要返回结果,比如计算两个数字的和
- 有的任务只需要执行操作而不需要返回结果,比如把数字打印到屏幕上
方法定义语法
返回值类型 方法名称(参数列表) {
方法体...
}
案例:自我介绍的方法
比如我们现在给人类编写一个自我介绍的行为:
public class Person {
String name;
int age;
String sex;
// 自我介绍只需要完成就行,没有返回值,所以使用void
void hello(){
// 完成自我介绍需要执行的所有代码就在这个花括号中编写
// 自我介绍需要用到当前对象的名字和年龄
// 我们直接使用成员变量即可,变量的值就是当前对象的存放值
System.out.println("我叫 " + name + " 今年 " + age + " 岁了!");
}
}
方法调用
public static void main(String[] args) {
Person p = new Person();
p.name = "小明";
p.age = 18;
p.hello(); // 使用 . 运算符调用方法,格式:.方法名称()
}
带参数的方法
比如现在我们要让人类学会加法运算,需要别人提供两个参与加法运算的值:
// 我们可以为方法设定参数,在调用方法时,需要外部传入参数
// 参数的定义需要在小括号内部编写,类似于变量定义
// 需要填写 类型和参数名称,多个参数用逗号隔开
int sum(int a, int b){ // 这里需要两个int类型的参数进行计算
int c = a + b; // 参数相当于在函数中定义了局部变量
return c; // return后面紧跟需要返回的结果
// 带返回值的方法,是一定要有一个返回结果的!
}
调用带参数的方法
public static void main(String[] args) {
Person p = new Person();
p.name = "小明";
p.age = 18;
int result = p.sum(10, 20); // 让这个对象帮我们计算10 + 20
System.out.println(result); // 成功得到30
}
return关键字
关于return关键字,我们需要进行进一步的介绍:
1. 方法立即结束
在使用return关键字之后,方法就会直接结束并返回结果,所以在这之后编写的任何代码,都是不可到达的。
int test() {
return 10;
System.out.println("这行代码永远不会执行"); // ❌ 编译错误
}
2. 分支必须都有返回值
如果程序中出现了分支语句,那么必须保证每一个分支都有返回值:
int test(int a) {
if (a > 0) {
return 1;
} else {
return -1; // 必须保证所有分支都有返回值
}
}
方法相互调用
当然,方法之间是可以相互调用的:
void test(){
System.out.println("我是test"); //实际上这里也是调用另一个方法
}
void say(){
test(); //在一个方法内调用另一个方法
}
如果我们这样写的话:
void test(){
say();
}
void say(){
test();
}
各位猜猜看会出现什么情况?
此时又出现了一个我们不认识的异常,实际上什么原因导致的我们自己都很清楚,方法之间一直在相互调用,没有一个出口。
构造方法
我们接着来看一种比较特殊的方法,构造方法。
我们前面创建对象,都是直接使用new关键字就能直接搞定了,但是我们发现,对象在创建之后,各种属性都是默认值,那么能否实现在对象创建时就为其指定名字、年龄、性别呢?要在对象创建时进行处理,我们可以使用构造方法(构造器)来完成。
实际上每个类都有一个默认的构造方法:
public class Person {
String name;
int age;
String sex;
public Person() {} //之前我们没有定义这个方法,但是其实编译器会自动添加一个无参构造方法
}
构造方法不需要填写返回值,并且方法名称与类名相同,默认情况下每个类都会自带一个没有任何参数的无参构造方法(只是不用我们去写,编译出来就自带)当然,我们也可以手动声明,对其进行修改:
public class Person {
String name;
int age;
String sex;
Person(){ //构造方法不需要指定返回值,并且方法名称与类名相同
name = "小明"; //构造方法会在对象创建时执行,我们可以将各种需要初始化的操作都在这里进行处理
age = 18;
sex = "男";
}
}
构造方法调用
构造方法会在new的时候自动执行:
public static void main(String[] args) {
Person p = new Person(); //这里的new Person()其实就是在调用无参构造方法
System.out.println(p.name);
}
当然,我们也可以为构造方法设定参数(一旦自定义,将不会自动添加无参构造):
public class Person {
String name;
int age;
String sex;
Person(String name, int age, String sex){ //跟普通方法是一样的
this.name = name;
this.age = age;
this.sex = sex;
}
}
注意,在我们自己定义一个构造方法之后,会覆盖掉默认的那一个无参构造方法,除非我们手动重载一个无参构造,否则要创建这个类的对象,必须调用我们自己定义的构造方法:
public static void main(String[] args) {
Person p = new Person("小明", 18, "男"); //调用自己定义的带三个参数的构造方法
System.out.println(p.name);
}
成员变量初始化
当然,要给成员变量设定初始值,我们不仅可以通过构造方法,也可以直接在定义时赋值:
public class Person {
String name = "未知"; //直接赋值,那么对象构造好之后,属性默认就是这个值
int age = 10;
String sex = "男";
}
这里需要特别注意,成员变量的初始化,并不是在构造方法之前之后,而是在这之前就已经完成了:
Person(String name, int age, String sex){
System.out.println(age); //在赋值之前看看是否有初始值
this.name = name;
this.age = age;
this.sex = sex;
}
代码块
我们也可以在类中添加代码块,代码块同样会在对象构造之前进行,在成员变量初始化之后执行:
public class Person {
String name;
int age;
String sex;
{
System.out.println("我是代码块"); //代码块中的内容会在对象创建时仅执行一次
}
Person(String name, int age, String sex){
System.out.println("我被构造了");
this.name = name;
this.age = age;
this.sex = sex;
}
}
只不过一般情况下使用代码块的频率比较低,标准情况下还是通过构造方法进行进行对象初始化工作,所以说这里做了解就行了。
类的加载时机
JVM在需要时才会加载类(懒加载)。以下情况会触发类加载:
- 访问类的静态变量,或为静态变量赋值
- 创建类的实例 (
new) - 调用类的静态方法
- 初始化子类时(会先初始化父类)
- (反射操作等)
有关类与对象的基本内容,我们就全部讲解完毕了。
用package组织代码
通过前面的学习,我们知道该如何创建和使用类。现在我们来学习如何组织和管理这些类。
包声明和导入
包(Package)用于组织类,类似于文件夹。它可以避免类名冲突,并更好地管理代码结构。
包的命名规则通常是反向的域名,例如 com.baidu。
package com.test; // 将当前类放入com.test包中
public class Main {
public static void main(String[] args) {
// ...
}
}
要使用其他包中的类,需要使用 import 关键字导入。
import com.test.entity.Person; // 导入Person类
import com.test.entity.*; // 导入entity包下的所有类
public class Main {
public static void main(String[] args) {
Person person = new Person();
}
}
Java会默认导入 java.lang 包下的所有类,因此像 String 和
System 这样的类无需手动导入。
本章总结
核心概念
- 类是抽象的概念,对象是具体的实例
- 面向对象编程通过对象交互来实现功能
- 引用类型变量存储的是对象的引用
重要语法
- 类定义:
public class ClassName { } - 对象创建:
new ClassName() - 属性访问:
object.property - 方法调用:
object.method()
注意事项
- 避免空指针异常
- 理解值传递机制
- 方法必须有正确的返回值
- 对象属性有默认值
掌握了类与对象的基本概念,你就迈出了面向对象编程的第一步!
实战练习
理论学习后,通过编码实践来巩固知识至关重要。以下是一些经典的算法问题,请尝试自己实现。
冒泡排序算法
将一个乱序的整数数组按照从小到大的顺序排列。
二分搜索算法
在一个有序数组中查找指定目标值,并返回其下标。
青蛙跳台阶问题
计算一只青蛙跳上n级台阶(每次可跳1或2阶)共有多少种方案。
回文串判断
判断一个字符串是否正读和反读都一样。
汉诺塔求解
输出解决N阶汉诺塔问题的每一步移动操作。
这些练习不仅能加深对Java语法的理解,还能锻炼逻辑思维和解决问题的能力。