<Java> 6 面向对象编程(基础)
本文最后更新于:2023年11月25日 下午
面向对象是一种开发软件的方法,使分析、设计和实现一个系统的方法尽可能接近人们认识一个系统的方法。包括三个方面:面向对象分析、面向对象设计、面向对象程序设计。
Java 语言是纯面向对象的语言。其所有数据类型都有相应的类,程序可以完全基于对象编写。
6.1 类与对象(OOP)
类 就是数据类型。可以是
int
也可以是人类
对象 就是其中具体的实例。可以是
100
也可以是顶真
从类到对象,可以称为 创建一个对象,也可以说 实例化一个对象,或者 把对象实例化
- 类是抽象的、概念的,代表一类事物
- 对象是具体的、实际的,代表一个个具体事物,即是实例
- 类是对象的模板,对象是类的一个个体,对应一个实例
6.1.1 类与对象引出
6.1.1 看一个养猫猫问题
张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
6.1.2 使用现有技术解决 Object01.java
- 单独的定义变量解决
- 使用数组解决
6.1.3 现有技术解决的缺点分析
不利于数据的管理
效率低 ===> 引出我们的新知识点 类与对象
java 设计者 引入 类与对象(OOP) ,根本原因就是现有的技术,不能完美的解决新的新的需求.
1 |
|
6.1.2 对象内存布局
6.1.3 属性/成员变量
从概念或叫法上看:成员变量 = 属性 = field(字段)
1 |
|
其中,String name;
就是一个成员变量(属性)。
属性是类的一个组成部分,一般是基本数据类型,也可以是引用数据类型(对象,数组)。
属性的定义语法同变量。
1
访问修饰符 属性类型 属性名
访问修饰符:控制属性的访问范围。有四种:
public
,protected
,默认(空)
,private
属性的定义类型可以为任意类型,包含基本类型或引用类型
属性如果不赋值,有默认值。规则同数组
6.1.4 创建对象
先声明再创建:
1
2Cat cat1; //声明对象cat1
cat1 = new Cat(); //创建对象,new会开空间直接创建:
1
Cat cat2 = new Cat();
注意事项:
声明对象时,只是在内存中建立了一个引用。此时,该地址引用不指向任何内存空间(NULL)。对象的引用,也被称为对象的句柄。
使用 new 运算符创建对象实例时,会为对象分配空间。之后,会将该段内存的首地址赋给刚才建立的引用。
6.1.5 访问对象
基本语法:对象名.属性名
1 |
|
6.1.6 类与对象的内存分配机制
Java内存的结构分析
栈:一般存放基本数据类型(局部变量)
堆:存放对象(如
Cat cat1 = new Cat()
,是在这里开辟的空间)方法区:常量池(常量,比如字符串),类加载信息
1 |
|
先加载 Person 类信息(属性和方法信息,只会加载一次)
在堆中分配空间,进行默认初始化(看规则)
把地址赋给 p , p 就指向对象
进行指定初始化, 比如
p.name =”jack”
,p.age = 10
6.2 成员方法
在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外( 年龄,姓名……),我们人类还有一些行为,比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用成员方法才能完成。现在要求对 Person 类完善。
1 |
|
6.2.1 成员方法的定义
1 |
|
方法名必须是一个合法的标识符
返回类型即返回值的类型。如果方法没有返回值,应声明为 void
修饰符段可以有几个不同的修饰符。(还没学到
比如
1
2
3public static strictfp final void method() {
System.out.println("~~~~~");
}其中 public(访问修饰符)、static(static 关键字)、final(final 关键字)
参数列表是传递给方法的参数表。各个元素间以
,
分隔。每个元素由一个类型和一个标识符表示的参数组成。
- 方法写好后,不去调用就不会输出
- 先创建对象,然后调用方法即可
- 使用成员方法,能提高代码的复用性。而且能把实现的细节封装起来,供其他用户调用。
6.2.2 方法调用机制
1 |
|
- 当程序执行到方法时,在 栈 中开辟一个新的栈空间。该空间里储存
num1 = 10
num2 = 20
,之后计算并储存结果res = 30
- 当方法执行完毕,或执行到
return
语句时,就会返回 - 把 新栈空间 中的
res = 30
返回到调用方法的地方 - 返回后,继续执行该方法的后续代码
- 当main方法(栈)执行完毕,整个程序退出
6.2.3 方法使用细节
访问修饰符:作用是控制方法的使用范围。
- 不写(默认访问控制范围)
- public:公共
- protected:受保护
- private:私有
返回数据类型:
- 一个方法最多有一个返回值。要返回多个结果可以使用数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class test {
public static void main(String[] args) {
AA a = new AA();
int[] res = a.getSumAndSub(1, 4);
System.out.println("和=" + res[0]);
System.out.println("差=" + res[1]);
}
}
//返回多个结果可以使用数组
class AA {
public int[] getSumAndSub(int n1, int n2) {
int[] resArr = new int[2];
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
}- 返回类型为任意类型。包括 基本数据类型和引用数据类型。
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必为
return 值
,且返回值类型必须和 return 的值类型一致或兼容。 - 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return 。
方法名:
- 遵循驼峰命名法,最好见名知意,表达出该功能的意思。
参数列表(形参列表):
- 一个方法可以有 0 个参数,也可以有多个参数。参数间用
,
间隔。 - 参数类型可以为任意类型,包含基本类型和引用类型。
- 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数。
- 方法定义时的参数称为形式参数 ,简称形参;方法调用时的参数(传入的参数)称为实际参数,简称实参。实参与形参的类型、个数、顺序必须一致。
- 一个方法可以有 0 个参数,也可以有多个参数。参数间用
方法体:
- 里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但里面不能再定义方法,即:方法不能嵌套定义!
调用细节:
同一个类中的方法调用,可以直接调用。
跨类的方法调用,需要创建新对象,然后再调用方法。
特别说明一下:跨类的方法调用和方法的访问修饰符相关,先暂时这么提一下,后面我们讲到访问修饰符时,还要再细说。
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
31public class test {
public static void main(String[] args) {
A a = new A();
a.sayOk();
a.m1();
}
}
class A {
//同一个类中的方法调用:直接调用即可
public void print(int n) {
System.out.println("print()方法被调用 n=" + n);
}
public void sayOk() { //sayOk 调用 print(直接调用即可)
print(10);
System.out.println("继续执行 sayOK()~~~");
}
//跨类中的方法 A 类调用 B 类方法:需要通过对象名调用
public void m1() {
//创建 B 对象, 然后再调用方法即可
System.out.println("m1() 方法被调用");
B b = new B();
b.hi();
System.out.println("m1() 继续执行:)");
}
}
class B {
public void hi() {
System.out.println("B 类中的 hi()被执行");
}
}
#### 两个小练习
1 |
|
6.2.4 方法传参机制
Java 语言对对象采用的是 值传递,方法得到的总是那个传入对象的副本。
基本数据类型的传参机制
方法不能修改基本数据类型的参数。基本数据类型传递的是一个值(值拷贝),形参的任何改变不影响实参!
1 |
|
引用数据类型的传参机制
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
引用类型传递的是一个地址,形参和实参指向一处,两者总会相关。
但改变那个形参地址指向的场合,实参的指向不会改变。
1 |
|
一个lj练习
编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
1 |
|
6.3 方法递归调用
递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于编程者解决复杂问题,同时可以让代码变得简洁
- 各种数学问题如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(google编程大赛)
- 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等
- 将用栈解决的问题
-->
递归代码比较简洁
6.3.1 几个引例
1 |
|
下面,示范一个斐波那契数列方法
1 |
|
6.3.2 使用细节
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)。
- 方法的局部变量是独立的,不会相互影响。
- 如果方法中使用的是引用类型变量(比如数组,对象),就会共享数据。
- 递归必须向退出递归的条件逼近,否则就是无限递归,会提示
StackOverflowError
“死龟” - 当一个方法执行完毕,或遇到
return
就会返回。遵守谁调用,就将结果返回给谁。同时当方法执行完毕或返回时,该方法也就执行完毕。
6.3.3 递归斐波那契
1 |
|
*6.3.4 猴子吃桃
*6.3.5 老鼠出迷宫
### 6.3.6 汉诺塔问题
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
1 |
|
不得不承认递归的妙处,真的是有点太妙了
*6.3.7 八皇后
6.4 方法重载
方法重载(Overload):Java 中允许同一类中,多个同名方法的存在,但要求 形参列表 不一致。
比如:System.out.println(),out 是 PrintStream 类型
重载的好处:减轻了起名和记名的麻烦。
一个例子:
1 |
|
6.4.1 使用细节
- 方法名:必须相同
- 形参列表:必须不同(形参的类型、个数、顺序,这其中至少一个不同,参数名无要求)
- 返回值:无要求
6.5 可变参数
Java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
语法:访问修饰符 返回类型 方法名(数据类型... 形参名){代码块;}
1 |
|
6.5.1 使用细节
可变参数 的实参可以是 0 个,也可以是 任意多 个。
可变参数 的实参可以是数组
1
2
3
4
5
6
7
8
9
10
11
12
13public class test {
public static void main(String[] args) {
//细节: 可变参数的实参可以为数组
int[] arr = {1, 2, 3};
T t1 = new T();
t1.f1(arr);
}
}
class T {
public void f1(int... nums) {
System.out.println("长度=" + nums.length);
}
}可变参数本质就是数组
因此,出现:
1
2public void m(int... n){ //这个方法与下面的方法不能构成重载
}的场合,不能有方法:
1
2public void m(int[] n){
}可变参数 和 普通参数 可以一起放在形参列表,但必须保证 可变参数 在最后
1
public void m(double dou, int... n) {}
一个形参列表最多出现 一个 可变参数。
1
2
3//下面的写法是错的
public void f3(int... nums1, double... nums2) {
}
6.5.2 一个练习
有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩 (总分)。封装成一个可变参数的方法,类名 HspMethod,方法名 showScore
1 |
|
6.6 作用域
- 在 Java 编程中,主要的变量就是属性(成员变量)和 局部变量。
- 我们说的 局部变量 一般是指在成员方法中定义的变量。
- 作用域的分类
- 全局变量:也就是属性,作用域为整个类体
- 局部变量:除了属性外的其他变量。作用域为定义它的代码块中
- 全局变量(属性)可以不赋值直接使用,因为有默认值。局部变量必须赋值使用
6.6.1 使用细节
属性 和 局部变量 可以重名,访问时遵循就近原则
在同一作用域中,两个局部变量不能重名
属性的生命周期较长。其伴随对象的创建而创建,伴随对象的销毁而销毁。
局部变量生命周期较短。其伴随代码块的执行而创建,伴随代码块的结束而销毁。
作用域不同:
- 全局变量/属性 可以被本类使用,也可以被其他类(通过对象)使用。
- 局部变量 只能被本类的对应方法中调用
全局变量/属性 可以加 修饰符(public private....)
局部变量 不能加 修饰符
6.7 构造方法、构造器
构造方法又叫构造器(constructor),是类的一种特殊的方法。它的主要作用是完成对新对象的初始化。它有几个特点:
- 方法名和类名相同
- 没有返回值
- 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
语法:[修饰符] 方法名(形参列表){ 方法体; }
- 构造器的修饰符可以默认, 也可以是 public protected private
- 构造器没有返回值
- 方法名和类名字必须一样
- 参数列表 和 成员方法一样的规则
- 构造器的调用,由系统完成
1 |
|
6.7.1 使用细节
构造器本质也是方法。所以,可以构造器重载。
1
2
3
4
5
6
7
8
9
10
11
12
13...
class Person {
String name;
int age;
public Person(String pName, int pAge) {
System.out.println("构造器被调用~~ 完成对象的属性初始化");
name = pName;
age = pAge;
}
public Person(String pName) {
name = pName;
}
}构造器名 和 类名 相同
构造器无返回值
构造器是完成对象的初始化,而不是创建
创建对象时,系统自动调用该类的构造方法,像上面的,不能
p1.Person()
如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器)
一旦定义了自己的构造器,就不能用无参构造器了。除非显式的定义一个无参构造器
6.7.2 流程分析
1 |
|
加载 类信息(方法区)
在 堆 中开辟空间(地址)
完成对象初始化
首先默认初始化。
age = 0; name = null
之后显式初始化。
age = 20; name = null
其中,显式初始化和代码块初始化按编写的先后顺序依次进行。
之后构造器的初始化。
age = 10; name = "Amy"
把对象在 堆 中的 地址,返回给
p1
6.8 this
关键字
JVM 会给每个对象分配 this 代表当前对象。
相当于在 堆 中,this 指向自己(对象)
在类定义的方法中,Java 会自动用 this 关键字把所有变量和方法引用结合在一起。
遇到有同名的局部变量的场合,需要程序员加入 this 关键字进行区分。不加入 this 关键字的场合,Java 遵循就近原则。
1 |
|
上面这个类的 act()
方法实际有 2 个参数。对其调用:
1 |
|
可见,出现在方法名前的参数
e
,以及出现在方法名后的括号中的参数 100
出现在方法名前的参数被称为 隐式参数(也称为 方法调用的 目标 或 接收者)
出现在方法名后的参数被称为 显式参数,就是所谓的实参
在每一个方法中,用 this 指代隐式参数。
1 |
|
此时,再以相同方式调用方法:
1 |
|
6.8.1 使用方法
this
关键字可以用来访问本类的属性、方法、构造器this
用于区分当前类的 属性 和 局部变量访问本类中成员方法的语法:
this.方法名
访问构造器的语法:
this(参数列表);
注意:只能在构造器中访问另一个构造器。而且,如果有这个语法,必须放置在第一条语句。
this
不能在类定义的 外部 使用,只能在类定义的 方法中 使用