<Java> 6 面向对象编程(基础)

本文最后更新于:2023年11月25日 下午

面向对象是一种开发软件的方法,使分析、设计和实现一个系统的方法尽可能接近人们认识一个系统的方法。包括三个方面:面向对象分析、面向对象设计、面向对象程序设计。

Java 语言是纯面向对象的语言。其所有数据类型都有相应的类,程序可以完全基于对象编写。

6.1 类与对象(OOP)

类 就是数据类型。可以是 int 也可以是 人类

对象 就是其中具体的实例。可以是 100 也可以是 顶真

从类到对象,可以称为 创建一个对象,也可以说 实例化一个对象,或者 把对象实例化

  1. 类是抽象的、概念的,代表一类事物
  2. 对象是具体的、实际的,代表一个个具体事物,即是实例
  3. 类是对象的模板,对象是类的一个个体,对应一个实例

6.1.1 类与对象引出

6.1.1 看一个养猫猫问题

张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。

6.1.2 使用现有技术解决 Object01.java

  1. 单独的定义变量解决
  2. 使用数组解决

6.1.3 现有技术解决的缺点分析

不利于数据的管理

效率低 ===> 引出我们的新知识点 类与对象

java 设计者 引入 类与对象(OOP) ,根本原因就是现有的技术,不能完美的解决新的新的需求.

image-20231125175919546
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
 //使用 OOP 面向对象解决上述问题
public class test {
//编写一个 main 方法
public static void main(String[] args) {

//实例化一只猫[创建一只猫对象]
//1. new Cat() 创建一只猫(猫对象)
//2. Cat cat1 = new Cat(); 把创建的猫赋给 cat1
//3. cat1 就是一个对象
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
cat1.weight = 10;
//创建了第二只猫,并赋给 cat2
//cat2 也是一个对象(猫对象)
Cat cat2 = new Cat();
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
cat2.weight = 20;
//怎么访问对象的属性呢
System.out.println("第 1 只猫信息: " + cat1.name
+ " " + cat1.age + " " + cat1.color + " " + cat1.weight);
System.out.println("第 2 只猫信息: " + cat2.name
+ " " + cat2.age + " " + cat2.color + " " + cat2.weight);
}
}


//定义一个猫类 Cat -> 自定义的数据类型
class Cat {
//属性/成员变量
String name; //名字
int age; //年龄
String color; //颜色
double weight; //体重

}

6.1.2 对象内存布局

image-20231125181407271

6.1.3 属性/成员变量

从概念或叫法上看:成员变量 = 属性 = field(字段)

1
2
3
4
class Cat{
String name;
int age;
}

其中,String name; 就是一个成员变量(属性)。

属性是类的一个组成部分,一般是基本数据类型,也可以是引用数据类型(对象,数组)。

  1. 属性的定义语法同变量。

    1
    访问修饰符 属性类型 属性名

    访问修饰符:控制属性的访问范围。有四种:publicprotected默认(空)private

  2. 属性的定义类型可以为任意类型,包含基本类型或引用类型

  3. 属性如果不赋值,有默认值。规则同数组

6.1.4 创建对象

  • 先声明再创建:

    1
    2
    Cat cat1;  				    //声明对象cat1
    cat1 = new Cat(); //创建对象,new会开空间
  • 直接创建:

    1
    Cat cat2 = new Cat();

注意事项:

  1. 声明对象时,只是在内存中建立了一个引用。此时,该地址引用不指向任何内存空间(NULL)。对象的引用,也被称为对象的句柄。

  2. 使用 new 运算符创建对象实例时,会为对象分配空间。之后,会将该段内存的首地址赋给刚才建立的引用。

6.1.5 访问对象

基本语法:对象名.属性名

1
System.out.println(cat1.name);

6.1.6 类与对象的内存分配机制

Java内存的结构分析

栈:一般存放基本数据类型(局部变量)

堆:存放对象(如Cat cat1 = new Cat(),是在这里开辟的空间)

方法区:常量池(常量,比如字符串),类加载信息

1
2
3
Person p = new Person();
p.name = “jack”;
p.age = 10;
  1. 先加载 Person 类信息(属性和方法信息,只会加载一次)

  2. 在堆中分配空间,进行默认初始化(看规则)

  3. 把地址赋给 p , p 就指向对象

  4. 进行指定初始化, 比如 p.name =”jack”p.age = 10

6.2 成员方法

在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外( 年龄,姓名……),我们人类还有一些行为,比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用成员方法才能完成。现在要求对 Person 类完善。

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
//初步认识的例子
public class test {

public static void main(String[] args) {
//方法使用
//1. 方法写好后,如果不去调用(使用),不会输出
//2. 先创建对象 ,然后调用方法即可
Person p1 = new Person();
p1.speak(); //调用方法
p1.cal01(); //调用 cal01 方法
p1.cal02(5); //调用 cal02 方法,同时给 n = 5
p1.cal02(10); //调用 cal02 方法,同时给 n = 10
//调用 getSum 方法,同时 num1=10, num2=20
//把 方法 getSum 返回的值,赋给 变量 returnRes
int returnRes = p1.getSum(10, 20);
System.out.println("getSum 方法返回的值=" + returnRes);
}
}

class Person {
String name;
int age;
//方法(成员方法)
//添加 speak 成员方法,输出 “我是一个好人”
//1. public 表示方法是公开
//2. void : 表示方法没有返回值
//3. speak() : speak 是方法名, () 形参列表,目前是空
//4. {} 方法体,可以写我们要执行的代码
//5. System.out.println("我是一个好人"); 表示我们的方法就是输出一句话
public void speak() {
System.out.println("我是一个好人");
}

//添加 cal01 成员方法,可以计算从 1+..+1000 的结果
public void cal01() {
int res = 0;
for(int i = 1; i <= 1000; i++) {
res += i;
}
System.out.println("cal01 方法 计算结果=" + res);
}

//添加 cal02 成员方法,该方法可以接收一个数 n,计算从 1+..+n 的结果
//1. (int n) 形参列表, 表示当前有一个形参 n, 可以接收用户输入
public void cal02(int n) {
int res = 0;
for(int i = 1; i <= n; i++) {
res += i;
}
System.out.println("cal02 方法 计算结果=" + res);
}

//添加 getSum 成员方法,可以计算两个数的和
//1. public 表示方法是公开的
//2. int :表示方法执行后,返回一个 int 值
//3. getSum 方法名
//4. (int num1, int num2) 形参列表,2 个形参,可以接收用户传入的两个数
//5. return res; 表示把 res 的值, 返回
public int getSum(int num1, int num2) {

int res = num1 + num2;
return res;
}
}

6.2.1 成员方法的定义

1
2
3
4
修饰符 返回数据类型 方法名(形参列表){
方法体语句;
return 返回值; //返回数据类型是 void 的场合,return语句不是必须的
}
  1. 方法名必须是一个合法的标识符

  2. 返回类型即返回值的类型。如果方法没有返回值,应声明为 void

  3. 修饰符段可以有几个不同的修饰符。(还没学到

    比如

    1
    2
    3
    public static strictfp final void method() {
    System.out.println("~~~~~");
    }

    其中 public(访问修饰符)、static(static 关键字)、final(final 关键字)

  4. 参数列表是传递给方法的参数表。各个元素间以 , 分隔。每个元素由一个类型和一个标识符表示的参数组成。

  1. 方法写好后,不去调用就不会输出
  2. 先创建对象,然后调用方法即可
  3. 使用成员方法,能提高代码的复用性。而且能把实现的细节封装起来,供其他用户调用。

6.2.2 方法调用机制

1
2
3
4
5
6
...
public int getSum(int num1, int num2) {
int res = num1 + num2;
return res;
}
...
  1. 当程序执行到方法时,在 栈 中开辟一个新的栈空间。该空间里储存 num1 = 10 num2 = 20,之后计算并储存结果 res = 30
  2. 当方法执行完毕,或执行到 return 语句时,就会返回
  3. 把 新栈空间 中的 res = 30 返回到调用方法的地方
  4. 返回后,继续执行该方法的后续代码
  5. 当main方法(栈)执行完毕,整个程序退出

6.2.3 方法使用细节

  1. 访问修饰符:作用是控制方法的使用范围。

    • 不写(默认访问控制范围)
    • public:公共
    • protected:受保护
    • private:私有
  2. 返回数据类型:

    • 一个方法最多有一个返回值。要返回多个结果可以使用数组。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public 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 。
  3. 方法名:

    • 遵循驼峰命名法,最好见名知意,表达出该功能的意思。
  4. 参数列表(形参列表):

    • 一个方法可以有 0 个参数,也可以有多个参数。参数间用 , 间隔。
    • 参数类型可以为任意类型,包含基本类型和引用类型。
    • 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数。
    • 方法定义时的参数称为形式参数 ,简称形参;方法调用时的参数(传入的参数)称为实际参数,简称实参。实参与形参的类型、个数、顺序必须一致。
  5. 方法体:

    • 里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但里面不能再定义方法,即:方法不能嵌套定义!
  6. 调用细节:

    • 同一个类中的方法调用,可以直接调用。

    • 跨类的方法调用,需要创建新对象,然后再调用方法。

    • 特别说明一下:跨类的方法调用和方法的访问修饰符相关,先暂时这么提一下,后面我们讲到访问修饰符时,还要再细说。

      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
      public 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
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
//1.编写类 A ,有一个方法:判断一个数是奇数 odd 还是偶数, 返回 boolean
//2.根据行、列、字符打印 对应行数和列数的字符,比如:行:4,列:4,字符#,则打印相应的效果
import java.util.Scanner;
public class test {
public static void main(String[] args) {
Scanner myScanner = new Scanner(System.in);
System.out.println("输入一个数字");
int num = myScanner.nextInt();
A a = new A();
if(a.isOdd(num)){
System.out.println("是奇数");
}
else{
System.out.println("不是奇数");
}
B bb = new B();
bb.print(5,6,'+');
}
}

class A{
public boolean isOdd(int num){
return num % 2 != 0;
}
}

class B{
public void print(int row,int col,char c){
for(int i =0; i < row; i++){
for(int j = 0; j < col ; j++){
System.out.print(c);
}
System.out.println();
}
}

}

上述程序执行结果

6.2.4 方法传参机制

Java 语言对对象采用的是 值传递,方法得到的总是那个传入对象的副本。

基本数据类型的传参机制

方法不能修改基本数据类型的参数。基本数据类型传递的是一个值(值拷贝),形参的任何改变不影响实参!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MethodParameter01 {
public static void main(String[] args) {
int a = 10;
int b = 20;
//创建 AA 对象 名字 obj
AA obj = new AA();
obj.swap(a, b); //调用 swap
System.out.println("main 方法 a=" + a + " b=" + b);//a=10 b=20
}
}

class AA {
public void swap(int a,int b){
System.out.println("\na 和 b 交换前的值\na=" + a + "\tb=" + b);//a=10 b=20
//完成了 a 和 b 的交换
int tmp = a;
a = b;
b = tmp;
System.out.println("\na 和 b 交换后的值\na=" + a + "\tb=" + b);//a=20 b=10
}
}
上述程序执行结果

引用数据类型的传参机制

引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!

引用类型传递的是一个地址,形参和实参指向一处,两者总会相关。

但改变那个形参地址指向的场合,实参的指向不会改变。

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
public class test {
public static void main(String[] args) {
B b = new B();
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
//测试题, 如果 test200 执行的是 p = null ,下面的结果是 10
//测试题, 如果 test200 执行的是 p = new Person();..., 下面输出的是 10
System.out.println("main的p.age = " + p.age + "\t" + p.name);//10000
}
}

class Person {
String name;
int age;
}
class B {
public void test200(Person p) {
p.age = 10000; //修改对象属性
//思考
// p = new Person();
// p.name = "tom";
// p.age = 99;
//思考
// p = null;
}

}

一个lj练习

编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同

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 test {
public static void main(String[] args) {
Person p = new Person();
p.name = "milan";
p.age = 100;
//创建 tools
MyTools tools = new MyTools();
Person p2 = tools.copyPerson(p);
//到此 p 和 p2 是 Person 对象,但是是两个独立的对象,属性相同
System.out.println("p 的属性 age=" + p.age + " 名字=" + p.name);
System.out.println("p2 的属性 age=" + p2.age + " 名字=" + p2.name);
//比较看看是否为同一个对象
System.out.println(p == p2);//false
}
}
class Person {
String name;
int age;
}
class MyTools {
//编写方法的思路
//1. 方法的返回类型 Person
//2. 方法的名字 copyPerson
//3. 方法的形参 (Person p)
//4. 方法体, 创建一个新对象,并复制属性,返回即可
public Person copyPerson(Person p) {
//创建一个新的对象
Person p2 = new Person();
p2.name = p.name; //把原来对象的名字赋给 p2.name
p2.age = p.age; //把原来对象的年龄赋给 p2.age
return p2;
}
}
上面程序执行的结果

6.3 方法递归调用

递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于编程者解决复杂问题,同时可以让代码变得简洁

  1. 各种数学问题如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(google编程大赛)
  2. 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等
  3. 将用栈解决的问题-->递归代码比较简洁

6.3.1 几个引例

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
//1. 打印问题
//2. 阶乘问题
public class test {
public static void main(String[] args) {
T t1 = new T();
t1.test(4);//输出什么? n=2 n=3 n=4,加个else只输出 n=2
int res = t1.factorial(5);
System.out.println("5 的阶乘 res =" + res);
}
}
class T {
public void test(int n) {
if (n > 2) {
test(n - 1);
}
System.out.println("n=" + n);
}

//factorial 阶乘
public int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n - 1) * n;
}
}
}
程序执行结果

下面,示范一个斐波那契数列方法

1
2
3
4
5
6
7
8
9
class T{
public int fib(int n){
if(n == 1 || n == 2){
return 1;
}else{
return (fib(n - 1)) + (fib(n - 2));
}
}
}

6.3.2 使用细节

  1. 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)。
  2. 方法的局部变量是独立的,不会相互影响。
  3. 如果方法中使用的是引用类型变量(比如数组,对象),就会共享数据。
  4. 递归必须向退出递归的条件逼近,否则就是无限递归,会提示 StackOverflowError “死龟”
  5. 当一个方法执行完毕,或遇到 return 就会返回。遵守谁调用,就将结果返回给谁。同时当方法执行完毕或返回时,该方法也就执行完毕。

6.3.3 递归斐波那契

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

public class test {
public static void main(String[] args) {
T t1 = new T();
int n = 7;//这里选择输出第几个斐波那契数
int res = t1.fibonacci(n);
if(res >= -1){
System.out.println("当n = " + n + " 对应的斐波那契数为:" + res);
}
}
}
class T {
/*
请使用递归的方式求出斐波那契数 1,1,2,3,5,8,13...给你一个整数 n,求出它的斐波那契数
1. 当 n = 1 斐波那契数 是 1
2. 当 n = 2 斐波那契数 是 1
3. 当 n >= 3 斐波那契数 是前两个数的和
4. 这里就是一个递归的思路
*/
public int fibonacci(int n){
if( n >= 1) {
if( n == 1 || n == 2) {
return 1;
} else {
return fibonacci(n-1) + fibonacci(n-2);
}
} else {
System.out.println("要求输入的 n>=1 的整数");
return -1;
}
}
}

*6.3.4 猴子吃桃

*6.3.5 老鼠出迷宫

### 6.3.6 汉诺塔问题

汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

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 class test {
public static void main(String[] args) {
Tower tower = new Tower();
tower.move(5, 'A', 'B', 'C');
}
}
class Tower {
//方法
//num 表示要移动的个数, a, b, c 分别表示 A 塔,B 塔, C 塔
public void move(int num , char a, char b ,char c) {
//如果只有一个盘 num = 1
if(num == 1) {
System.out.println(a + "->" + c);
} else {
//如果有多个盘,可以看成两个 , 最下面的和上面的所有盘(num-1)
//(1)先移动上面所有的盘到 b, 借助 c
move(num - 1 , a, c, b);

//(2)把最下面的这个盘,移动到 c
System.out.println(a + "->" + c);
//(3)再把 b 塔的所有盘,移动到 c ,借助 a
move(num - 1, b, a, c);
}
}
}

不得不承认递归的妙处,真的是有点太妙了

*6.3.7 八皇后

6.4 方法重载

方法重载(Overload):Java 中允许同一类中,多个同名方法的存在,但要求 形参列表 不一致。

比如:System.out.println(),out 是 PrintStream 类型

重载的好处:减轻了起名和记名的麻烦。

一个例子:

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
public class test {
public static void main(String[] args) {
MyCalculator mc = new MyCalculator();
System.out.println(mc.calculate(1, 2));
System.out.println(mc.calculate(1.1, 2));
System.out.println(mc.calculate(1, 2.1));
}
}
class MyCalculator {
//下面的四个 calculate 方法构成了重载
//两个整数的和
public int calculate(int n1, int n2) {
System.out.println("calculate(int n1, int n2) 被调用");

return n1 + n2;
}
//没有构成方法重载, 仍然是错误的,因为是方法的重复定义
// public double calculate(int n1, int n2) {
// System.out.println("calculate(int n1, int n2) 被调用");
// return n1 + n2;
// }

//看看下面是否构成重载, 没有构成,而是方法的重复定义
// public int calculate(int a1, int a2) {
// System.out.println("calculate(int n1, int n2) 被调用");
// return a1 + a2;
// }

//一个整数,一个 double 的和
public double calculate(int n1, double n2) {
return n1 + n2;
}
//一个 double ,一个 Int 和
public double calculate(double n1, int n2) {
System.out.println("calculate(double n1, int n2) 被调用..");
return n1 + n2;
}
//三个 int 的和
public int calculate(int n1, int n2,int n3) {

return n1 + n2 + n2;
}
}

6.4.1 使用细节

  1. 方法名:必须相同
  2. 形参列表:必须不同(形参的类型、个数、顺序,这其中至少一个不同,参数名无要求)
  3. 返回值:无要求

6.5 可变参数

Java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。

语法:访问修饰符 返回类型 方法名(数据类型... 形参名){代码块;}

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
public class test {
//编写一个 main 方法
public static void main(String[] args) {
HspMethod m = new HspMethod();
System.out.println(m.sum(1, 5, 100)); //106
System.out.println(m.sum(1,19)); //20
}
}
class HspMethod {
//可以计算 2 个数的和,3 个数的和 , 4. 5, 。。
//可以使用方法重载
// public int sum(int n1, int n2) {//2 个数的和
// return n1 + n2;
// }
// public int sum(int n1, int n2, int n3) {//3 个数的和
// return n1 + n2 + n3;
// }
// public int sum(int n1, int n2, int n3, int n4) {//4 个数的和
// return n1 + n2 + n3 + n4;
// }
//.....
//上面的三个方法名称相同,功能相同, 参数个数不同-> 使用可变参数优化

//1. int... 表示接受的是可变参数,类型是 int ,即可以接收多个 int(0-多)
//2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
//3. 遍历 nums 求和即可
public int sum(int... nums) {
System.out.println("接收的参数个数=" + nums.length);
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
程序执行结果

6.5.1 使用细节

  1. 可变参数 的实参可以是 0 个,也可以是 任意多 个。

  2. 可变参数 的实参可以是数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public 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);
    }
    }
  3. 可变参数本质就是数组

    因此,出现:

    1
    2
    public void m(int... n){				//这个方法与下面的方法不能构成重载
    }

    的场合,不能有方法:

    1
    2
    public void m(int[] n){				
    }
  4. 可变参数 和 普通参数 可以一起放在形参列表,但必须保证 可变参数 在最后

    1
    public void m(double dou, int... n) {}
  5. 一个形参列表最多出现 一个 可变参数。

    1
    2
    3
    //下面的写法是错的
    public void f3(int... nums1, double... nums2) {
    }

6.5.2 一个练习

有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩 (总分)。封装成一个可变参数的方法,类名 HspMethod,方法名 showScore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class test {
//编写一个 main 方法
public static void main(String[] args) {
HspMethod hm = new HspMethod();
System.out.println(hm.showScore("milan" , 90.1, 80.0 ));
System.out.println(hm.showScore("terry" , 90.1, 80.0,10,30.5,70 ));
}
}

class HspMethod {
//分析 1. 方法名 showScore 2. 形参(String ,double... ) 3. 返回 String
public String showScore(String name ,double... scores ) {
double totalScore = 0;
for(int i = 0; i < scores.length; i++) {
totalScore += scores[i];
}
return name + " 有 " +scores.length + " 门课的成绩总分为 = " + totalScore;
}
}

6.6 作用域

  1. 在 Java 编程中,主要的变量就是属性(成员变量)和 局部变量。
  2. 我们说的 局部变量 一般是指在成员方法中定义的变量。
  3. 作用域的分类
    • 全局变量:也就是属性,作用域为整个类体
    • 局部变量:除了属性外的其他变量。作用域为定义它的代码块中
  4. 全局变量(属性)可以不赋值直接使用,因为有默认值。局部变量必须赋值使用

6.6.1 使用细节

  1. 属性 和 局部变量 可以重名,访问时遵循就近原则

  2. 在同一作用域中,两个局部变量不能重名

  3. 属性的生命周期较长。其伴随对象的创建而创建,伴随对象的销毁而销毁。

    局部变量生命周期较短。其伴随代码块的执行而创建,伴随代码块的结束而销毁。

  4. 作用域不同:

    • 全局变量/属性 可以被本类使用,也可以被其他类(通过对象)使用。
    • 局部变量 只能被本类的对应方法中调用
  5. 全局变量/属性 可以加 修饰符(public private....)

    局部变量 不能加 修饰符

6.7 构造方法、构造器

构造方法又叫构造器(constructor),是类的一种特殊的方法。它的主要作用是完成对新对象的初始化。它有几个特点:

  1. 方法名和类名相同
  2. 没有返回值
  3. 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。

语法:[修饰符] 方法名(形参列表){ 方法体; }

  1. 构造器的修饰符可以默认, 也可以是 public protected private
  2. 构造器没有返回值
  3. 方法名和类名字必须一样
  4. 参数列表 和 成员方法一样的规则
  5. 构造器的调用,由系统完成
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
public class test {
public static void main(String[] args) {
//当我们 new 一个对象时,直接通过构造器指定名字和年龄
Person p1 = new Person("smith", 80);
System.out.println("p1 的信息如下");
System.out.println("p1 对象 name = " + p1.name);//smith
System.out.println("p1 对象 age = " + p1.age);//80
}
}
//在创建人类的对象时,就直接指定这个对象的年龄和姓名
class Person {
String name;
int age;
//构造器

//1. 构造器没有返回值, 也不能写 void
//2. 构造器的名称和类 Person 一样
//3. (String pName, int pAge) 是构造器形参列表,规则和成员方法一样
public Person(String pName, int pAge) {
System.out.println("构造器被调用~~ 完成对象的属性初始化");
name = pName;
age = pAge;
}

}

运行结果

6.7.1 使用细节

  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;
    }
    }
  2. 构造器名 和 类名 相同

  3. 构造器无返回值

  4. 构造器是完成对象的初始化,而不是创建

  5. 创建对象时,系统自动调用该类的构造方法,像上面的,不能p1.Person()

  6. 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器)

  7. 一旦定义了自己的构造器,就不能用无参构造器了。除非显式的定义一个无参构造器

6.7.2 流程分析

1
2
3
4
5
6
7
8
9
10
11
12
Person p1 = new Person("Amy", 10);

...

class Person{
String name;
int age = 20;
public Person(String pName, int pAge){
name = pName;
age = pAge;
}
}JAVA
  1. 加载 类信息(方法区)

  2. 在 堆 中开辟空间(地址)

  3. 完成对象初始化

    • 首先默认初始化。age = 0; name = null

    • 之后显式初始化。age = 20; name = null

      其中,显式初始化和代码块初始化按编写的先后顺序依次进行。

    • 之后构造器的初始化。age = 10; name = "Amy"

  4. 把对象在 堆 中的 地址,返回给 p1

6.8 this 关键字

JVM 会给每个对象分配 this 代表当前对象。

相当于在 堆 中,this 指向自己(对象)

在类定义的方法中,Java 会自动用 this 关键字把所有变量和方法引用结合在一起。

遇到有同名的局部变量的场合,需要程序员加入 this 关键字进行区分。不加入 this 关键字的场合,Java 遵循就近原则。

1
2
3
4
class Example{
int n = 0;
public void act(int n) {}
}JAVA

上面这个类的 act() 方法实际有 2 个参数。对其调用:

1
2
Example e = new Exmaple();
e.act(100);JAVA

可见,出现在方法名前的参数 e,以及出现在方法名后的括号中的参数 100

出现在方法名前的参数被称为 隐式参数(也称为 方法调用的 目标 或 接收者)

出现在方法名后的参数被称为 显式参数,就是所谓的实参

在每一个方法中,用 this 指代隐式参数。

1
2
3
public void act(int n) {
this.n = n;
}JAVA

此时,再以相同方式调用方法:

1
2
e.act(100);					// <———— 相当于 e.n = 100;
JAVA

6.8.1 使用方法

  1. this 关键字可以用来访问本类的属性、方法、构造器

  2. this 用于区分当前类的 属性 和 局部变量

  3. 访问本类中成员方法的语法:this.方法名

  4. 访问构造器的语法:this(参数列表);

    注意:只能在构造器中访问另一个构造器。而且,如果有这个语法,必须放置在第一条语句。

  5. this 不能在类定义的 外部 使用,只能在类定义的 方法中 使用

附录


<Java> 6 面向对象编程(基础)
http://viper2383.github.io/2023/11/25/Java 6 面向对象编程(基础)/
作者
w1per3
发布于
2023年11月25日
许可协议