一、类与对象
1. 引出类与对象
- 由于现有技术不利于数据的管理,效率低,不能完美的解决新的需求,Java设计者引入类与对象(OOP)
2. 类与对象
- 类是一个模板,是数据类型。比如Cat
- 对象是类的一个具体实例,有属性和行为。例如,一条狗是一个对象,它的属性有:颜色、名字、品种;行为有:摇尾巴、叫、吃等
3. 类与对象入门案例
- 使用面向对象的方式来解决养猫问题 ```java // 猫类 class Cat { // 属性/成员变量 String name; // 名字 int age; // 年龄 String color; // 颜色 }
// 主类 public class Hello {
public static void main(String[] args) {// 使用OOP面向对象解决// 实例化一只猫(创建一只猫对象)// 1. new Cat() 创建一只猫对象// 2. 把创建的猫赋给 cat1// 3. cat1 是一个对象名(对象引用),new Cat()创建的对象空间(数据)才是真正的对象Cat cat1 = new Cat();cat1.name = "小白";cat1.age = 3;cat1.color = "白色";Cat cat2 = new Cat();cat2.name = "小花";cat2.age = 100;cat2.color = "花色";// 访问对象的属性System.out.println("第一只猫:" + cat1.name+ " " + cat1.age + " " + cat1.color);System.out.println("第二只猫:" + cat2.name+ " " + cat2.age + " " + cat2.color);}
}
<a name="CfdU0"></a>#### 4. 类与对象的区别和联系1. 类是抽象的,概念的,代表一类事物。比如人类、猫类...,是数据类型1. 对象是具体的,实际的,代表一个具体事物,是实例1. 类是对象的模板,对象是类的一个个体,对应一个实例<a name="HgGUr"></a>#### 5. 对象在内存中存在形式(重要)<a name="uysjY"></a>#### 6. 属性/成员变量1. 从概念或叫法上看:成员变量 = 属性 = field(字段)```javaclass Car() {String name; // 属性/成员变量/fieldString color;double price;String[] master; // 属性可以是引用类型}
- 属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象,数组)
7. 类与对象注意事项
- 属性的定义语法同变量,示例:
访问修饰符 属性类型 属性名;- 访问修饰符:控制属性的访问范围,有四种访问修饰符。
public, proctected, default, private
- 访问修饰符:控制属性的访问范围,有四种访问修饰符。
- 属性的定义类型可以为任意类型,包含基本类型或引用类型
- 属性如果不赋值,有默认值,规则和数组一致
int 0, short 0, byte 0, long 0, float 0.0, double 0.0, char \u0000, boolean false, String null
8. 如何创建对象
- 先声明再创建
Cat cat; // 声明对象 catcat = new Cat(); // 创建
- 直接创建
Cat cat= new Cat();
- 访问属性
// 对象名.属性名;cat.name;cat.age;cat.color;
9. 类与对象的内存分配机制(重要)

- Java内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Cat cat, 数组等)
- 方法区:常量池(常量,比如字符串),类加载信息
Java创建对象的流程简单分析
Person p = new Person();p.name = "jack";p.age = 10;
先加载
Person类信息(属性和方法信息,只会加载一次)- 在堆中分配空间,进行默认初始化(默认值)
- 把地址赋给
p,p就指向对象 - 进行指定初始化赋值,比如
p.name = "jack" p是对象引用/对象名,堆中的才是真正的对象
二、成员方法
1. 成员方法基本介绍
在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外(年龄,姓名…),我们人类还有一些行为比如可以说话、跑步,通过学习,还可以做算术题。这时就
要用成员方法才能完成。现在要求对Person类完善。
2. 成员方法案例演示
public class Method01 {/** 1. 创建Person类* 2. 添加speak成员方法,输出 我是一个好人* 3. 添加cal01成员方法,可以计算 1+...+1000的结果* 4. 添加cal02成员方法,可以接收一个数n,计算从1+...+n的结果* 5. 添加getSum成员方法,可以计算两个数的和*/public static void main(String[] args) {Person p = new Person(); // 创建对象p.speak(); // 调用speak成员方法p.cal01(); // cal01成员方法p.cal02(10); //cal01成员方法// 把 方法getSum()返回的值,赋给变量sumint sum = p.getSum(10, 20); // 调用getSum a01 = 10, b = 20System.out.println(sum);}}// Person类class Person {// 属性String name;int age;// 1. public表示方法是公开的// 2. void:没有返回值// 3. speak:方法名// 4. ():形参列表// 5. {}:方法体public void speak() { // 输出"我是一个好人"System.out.println("我是一个好人");}public void cal01() { // 计算 1+...+1000的结果int sum = 0;for (int sum = 1; sum <= 1000; sum++) {sum += sum;}System.out.println("cal01: " + sum);}public void cal02(int n) { // 接收一个数n,计算从1+...+n的结果int sum = 0;for (int sum = 1; sum <= n; sum++) {sum += sum;}System.out.println("cal01: " + sum);}// int:表示方法执行后,返回一个int值// (int a01, int b):形参列表,可以接收用户传入的两个数// return:返回a+b的值public int getSum(int a01, int b) { // 计算两个数的和int res = a01 + b;return res;}}
3. 方法的调用机制原理(重要)
4. 方法的优点
- 提高代码的复用性
- 可以将实现的细节封装起来,然后供其他用户来调用
5. 成员方法的定义
- 参数列表:表示成员方法输入
- 返回数据类型(返回类型):表示成员方法输出,void表示没有返回值
- 方法主体:表示为了实现某一功能的代码块
return语句不是必须的
访问修饰符 返回数据类型 方法名 (形参列表) {// 方法体语句;return 返回值;}
6. 方法的注意事项和使用细节
- 访问修饰符
- 控制方法的适用范围,可选,如果不写默认访问
- 访问修饰符共有四种:
public, protected, default, private
- 返回值
- 一个方法最多有一个返回值
- 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象等)
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为
return值;而且要求返回值类型必须和return的值类型一致或兼容 - 如果方法是
void,则方法体中可以没有return语句,或者只写return
- 方法名
- 遵循驼峰命名法,最好见名知义,表达出该功能的意思即可
- 形参列表
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开
- 参数类型可以为任意类型,包含基本类型或引用类型
- 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
- 方法定义时的参数称为形式参数,简称形参;方法调用时的参数称为实际参数,简称实参,实参和形参的类型要一致或兼容,个数、顺序必须一致
- 方法体
- 里面写完成功能的具体语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但是方法体内不能再定义方法。即:方法不能嵌套定义
- 方法调用
- 同一个类中的方法直接调用即可。比如:
方法名(参数) - 跨类中的方法:A类调用B类的方法,需要创建B类对象,然后通过对象名调用。比如:
对象名.方法名(参数) - 跨类的方法调用和方法的访问修饰符相关
- 同一个类中的方法直接调用即可。比如:
7. 课后练习
编写类AA,方法:判断一个数是奇数odd还是偶数,返回
boolean```java /*- 方法的返回类型 boolean
- 方法的名字 isOdd
- 方法的形参 int num
- 方法体 判断 */ public class MethodExercise01 {
public static void main(String[] args) {
AA a01 = new AA();if (a01.isOdd(2)) { // TSystem.out.println("是奇数");} else {System.out.println("是偶数");}
} }
class AA { public boolean isOdd(int num) { // return num % 2 != 0 ? true : false; return num % 2 != 0; } }
2. 根据行、列、字符打印对应行数和列数的字符,比如:行:4, 列:4, 字符#,则打印相应效果```java/** 根据行、列、字符打印对应行数和列数的字符* 比如,[行:4, 列:4, 字符:#],则打印相应效果:* ####* ####* ####* ####* 1. 方法的返回类型 void* 2. 方法的名字 print* 3. 方法的形参 int row, int column, char c* 4. 方法体 for循环打印输出*/public class MethodParameter01 {public static void main(String[] args) {AA a01 = new AA();a01.print(4, 4, '#');}}class AA {public void print(int row, int col, char c) {for (int i = 0; i < col; i++) {for (int j = 0; j < row; j++) {System.out.print(c);}System.out.println();}}}
8. 成员方法传参机制(重要)
- 对于基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参
- 对于引用数据类型(包括类),传递的是地址值,可以通过形参影响实参
案例演示
- 编写
MyTools类,编写一个方法可以打印二维数组的数据 编写一个方法
copyPerson可以复制一个Person对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同 ```java public class Hello {public static void main(String[] args) { int[][] arr = { { 1, 1, 1 }, { 2, 2, 2 }, { 3, 3, 3 } }; MyTools tools = new MyTools(); tools.printArr(arr); // 打印二维数组
Person p = new Person(); p.name = “jack”; p.age = 10;
Person p2 = tools.copyPerson(p); // 克隆对象
// p 和 p2 是两个独立的Person对象,属性相同 System.out.println(“p:” + p.age + “ “ + p.name); System.out.println(“p2:” + p2.age + “ “ + p2.name);
// 通过比较查看是否为同一个对象 System.out.println(p == p2); // false } }
- 编写
class Person { // Person类 String name; int age; }
class MyTools { // MyTools类 public void printArr(int[][] arr) { // 打印二维数组的数据 for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + “ “); } System.out.println(); } }
public Person copyPerson(Person p) {Person p2 = new Person();p2.name = p.name;p2.age = p.age;return p2;}
}
<a name="Tef68"></a>### 三、方法递归调用<a name="hgSmc"></a>#### 1. 递归基本介绍- 递归是指在函数的定义中使用函数自身的方法,每次调用时传入不同的变量- 递归有助于编程者解决复杂问题,同时可以让代码变得简洁<a name="ZT0F6"></a>#### 2. 递归能解决什么问题1. 各种数学问题,如:八皇后、汉诺塔、阶乘、迷宫、球和篮子(Google编程大赛)等1. 各种算法中也会使用到递归,比如:快排、归并排序、二分查找、分治算法等1. 将用栈解决的问题,递归代码比较简洁<a name="NnkDF"></a>#### 3. 递归案例演示1. 打印问题1. 阶乘问题```javapublic class recursion01 {public static void main(String[] args) {A a01 = new A();a01.test(4); // 打印输出 2int res = a01.factorial(5);System}}class A {public void test(int n) {// 打印问题if (n > 2) {test(n - 1);}System.out.println("n = " + n);}public int factorial(int n) {// 阶乘问题if (n == 1) {return 1;} else {return factorial(n-1) * n;}}}
4. 递归的重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响
- 如果方法中使用的是引用类型变量(比如数组、对象),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现
StackOverflowError,死龟了:) - 当一个方法执行完毕,或者遇到
return,就会返回,遵守谁调用,就将结果返回给谁,同时当发法执行完毕或者返回时,该方法也就执行完毕
5. 课堂练习
- 请使用递归的方式求出斐波那契数列
1,1,2,3,5,8,13...求第n个数的值 - 猴子吃桃问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,想再吃时发现只有一个桃子了。问题:最初共有多少个桃子?
public class maze {public static void main(String[] args) {T t1 = new T();System.out.println("=========斐波那契数=========");int n = 7;int res = t1.fibNum(7);System.out.println("当n = " + n + "时,斐波那契数为:" + res);System.out.println("\n=========猴子吃桃子=========");int day = 1;int peachNum = t1.peach(1);System.out.println("第" + day + "天的桃子数为:" + peachNum); // 1534}}class T {/** 请使用递归的方式求出斐波那契数* 1,1,2,3,5,8,13...求第n个数的值** 思路分析:* 1. 当 n = 1 时,斐波那契数 是 1* 2. 当 n = 2 时,斐波那契数 是 1* 3. 当 n >= 3 时,斐波那契数 是 前两个数的和* 4. 递归思路*/public int fibNum(int n) {if (n >= 3) {return fibNum(n - 1) + fibNum(n - 2);} else {return 1;}}/** 2. 猴子吃桃问题:* 有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。* 以后每天猴子都吃其中的一半,然后再多吃一个。* 当到第10天时,想再吃(还没吃)时发现只有一个桃子了。* 问题:最初共有多少个桃子?** 思路分析:(逆推)* 1. day = 10 时,有1个桃子* 2. day = 9 时,有(day10+1)*2=4个桃子* 3. day = 8 时,有(day9+1)*2=10个桃子* 4. 前一天的桃子 = (后一天的桃子 + 1 ) * 2* 5. 递归算法*/public int peach(int day) {if (day == 10) { // 第10天,只有一个桃子return 1;} else if (day >= 1 && day <= 9) {return (peach(day + 1) + 1) * 2;} else {System.out.println("day的范围是:1-10");return -1;}}}
6. 应用实例 1 - 迷宫问题
- 小球得到的路径,和程序员设置的找路策略有关,即:找路的上下左右的顺序相关
- 再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化
- 测试回溯现象
- 扩展思考:如何求出最短路径?思路:(1)穷举 (2)图->求出最短路径

public class Hello {public static void main(String[] args) {// 思路分析// 1. 先创建迷宫,用二维数组表示// 2. 规定 map 数组的元素值:0 表示可以走;1 表示障碍物int[][] map = new int[8][7]; // 共有8行7列for (int i = 0; i < 7; i++) {// 第一行和最后一行为 1map[0][i] = 1;map[7][i] = 1;}for (int i = 0; i < 8; i++) {// 第一列和最后一列为 1map[i][0] = 1;map[i][6] = 1;}map[3][1] = 1;map[3][2] = 1;map[2][2] = 1; // 测试回溯System.out.println("=======迷宫图=======");for (int i = 0; i < map.length; i++) {for (int j = 0; j < map[i].length; j++) {System.out.print(map[i][j] + " ");}System.out.println();}System.out.println("\n========老鼠走迷宫========");T t1 = new T();t1.findWay(map, 1, 1);for (int i = 0; i < map.length; i++) {for (int j = 0; j < map[i].length; j++) {System.out.print(map[i][j] + " ");}System.out.println();}}}class T {/** 使用递归回溯的思想来解决老鼠出迷宫* 1. findWay方法就是专门来找出迷宫的路径* 2. 如果找到,就返回true,否则返回false* 3. map就是二维数组,表示迷宫* 4. i, j 是老鼠的位置,初始化位置为(1, 1)* 5. 因为是递归找路,所有先规定map数组的各个值含义* 0:可以走但没走过的路,1:障碍物,2:确定可以走的路,3:走过,但是走不通* 6. 当map[6][5] = 2 就说明找到通路,可以结束,否则继续找* 7. 找路策略为:下->右->上->左*/// 找路策略为:下->右->上->左public boolean findWay(int[][] map, int i, int j) {if (map[6][5] == 2) { // 说明已被找到return true;} else {if (map[i][j] == 0) { // 表示当前点可以走但没走过// 假定可以走通map[i][j] = 2;// 使用找路策略,来确定该位置是否真的走通// 下->右->上->左if (findWay(map, i + 1, j)) { // 下return true;} else if (findWay(map, i, j + 1)) { // 右return true;} else if (findWay(map, i - 1, j)) { // 上return true;} else if (findWay(map, i, j - 1)) { // 左return true;} else {map[i][j] = 3;return false;}} else { // map[i][j] == 1, 2, 3return false;}}}// 更改找路策略为:上->右->下->左public boolean findWay2(int[][] map, int i, int j) {if (map[6][5] == 2) { // 说明已被找到return true;} else {if (map[i][j] == 0) { // 表示当前点可以走但没走过// 假定可以走通map[i][j] = 2;// 使用找路策略,来确定该位置是否真的走通// 下->右->上->左if (findWay2(map, i - 1, j)) { // 下return true;} else if (findWay2(map, i, j + 1)) { // 右return true;} else if (findWay2(map, i + 1, j)) { // 上return true;} else if (findWay2(map, i, j - 1)) { // 左return true;} else {map[i][j] = 3;return false;}} else { // map[i][j] == 1, 2, 3return false;}}}}
7. 应用实例 2 - 汉诺塔
- 汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。
- 大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片圆盘。
- 大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。
- 并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

public class HanoiTower {public static void main(String[] args) {Tower tower = new Tower();tower.move(5, 'A', 'B', 'C');}}class Tower {// num 代表盘子的个数// a01,b,c 代表三个塔 A, B, Cpublic void move(int num, char a01, char b, char c) {if (num == 1) { // 如果只有一个盘System.out.println(a01 + "->" + c); // 从a移动到c} else {// 如果有多个盘,可以看成一个盘,最下面的和上面的所有盘// 1. 先移动上面所有的盘到b,借助cmove(num - 1, a01, c, b);// 2. 把最下面的这个盘,移动到cSystem.out.println(a01 + "->" + c);// 3. 再把b塔所有盘,移动到c,借助amove(num - 1, b, a01, c);}}}
8. 应用实例3 - 八皇后
- 八皇后问题
- 在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击
- 即:任意两个皇后都不能处于同一行、同一列或同一斜上,问有多少种摆法

class EightQueens {/** 思路分析:* 1)第一个皇后先放第一行第一列* 2)第二个皇后放在第二行第一列、然后判断是否OK,如果不OK,* 继续放在第二列、第三列、依次把所有列都放完,找到一个合适* 3)继续第三个皇后,还是第一列、第二列直到第8个皇后也能* 放在一个不冲突的位置,算是找到了一个正确解* 4)当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,* 即将第一个皇后,放到第一列的所有正确解,全部得到。* 5)然后回头继续第一个皇后放第二列,后面继续循环执行1,2,3,4的步骤** 说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,* 用一个一维数组即可解决问题.arr[8]={0,4,7,5,2,6,1,3}* 对应arr下标表示第几行,即第几个皇后,* arr[i]=val,val表示第i+1个皇后,放在第i+1行的第val+1列*/public int queen(int[] arr, int i, int val, int res) {// 略;}}
四、重载(overload)
1. 重载基本介绍
- Java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致
- 重载减轻了起名、记名的麻烦
2. 重载案例演示
public class OverLoadExercise {// 方法重载public static void main(String[] args) {// 求和MyCalculator mc = new MyCalculator();System.out.println(mc.calculate(1.1, 2));// 求最大值MyMax md = new MyMax();System.out.println(md.max(2.3, 4.5, 7,6));}}class MyCalculator {// 两个整数的和public int calculate(int n1, int n2) {return n1 + n2;}// 一个整数,一个double的和public double calculate(int n1, double n2) {return n1 + n2;}// 一个double,一个整数的和public double calculate(double n1, int n2) {return n1 + n2;}// 三个整数的和public int calculate(int n1, int n2, int n3) {return n1 + n2 + n3;}}class MyMax {// 返回两个int中的最大值public int max(int n1, int n2) {n1 > n2 ? n1 : n2;}// 返回两个double值中的最大值public double max(double d1, double d2) {d1 > d2 ? d1 : d2}// 返回三个double值中的最大值public double max(double d1, double d2, double d3) {double max1 = d1 > d2 ? d1 : d2;return max1 > d3 ? max1 : d3;}}
3. 方法重载的注意事项
- 方法名:必须相同
- 形参列表:必须不同(形参类型、个数、顺序至少有一样不同,参数名无要求)
- 返回类型:无要求
五、可变参数
1. 可变参数基本介绍
- 基本概念:Java允许将用一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现
- 基本语法:
访问修饰符 返回类型 方法名(数据类型...形参名) { }
2. 可变参数案例演示
/** 可以计算 2个数的和,3个数的和,4个,5个...* 功能相同,参数个数不同 -->> 使用可变参数*/public class Hello {public static void main(String[] args) {HspMethod hsp = new HspMethod();System.out.println("和为:" + hsp.sum(1, 2, 3, 4));}}class HspMethod {// 可以计算 2个数的和,3个数的和,4个,5个...// 1. int... 表示接收的是可变参数,类型是int,即可以接收多个int// 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;}}
3. 可变参数的注意事项
- 可变参数的实参可以为0个或任意多个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
public void f2(String str, double... nums) { } - 一个形参列表中只能出现一个可变参数
4. 可变参数课堂练习
- 有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩(总分)。封装成一个可变参数的方法
```java
public class VarParameterExercise {
public static void main(String[] args) {
} }HspMethod hsp = new HspMethod();System.out.println(hsp.showScore("米兰", 90.1, 80.1));System.out.println(hsp.showScore("特瑞", 90.0, 80.1, 44.5, 22, 57.65));
class HspMethod { // 分析:1. 方法名 2. 形参(String, double…) 3. 返回String public String showScore(String name, double… score) { double sum = 0; for (int i = 0; i < score.length; i++) { sum += score[i]; } return name + “ 有 “ + score.length + “ 门课的总分为:” + sum; } }
<a name="qvI7K"></a>### 六、作用域<a name="UhJCt"></a>#### 1. 作用域基本介绍1. 在Java编程中,主要的变量就是属性(成员变量)和局部变量1. 局部变量一般是指在**成员方法**中定义的变量1. Java中作用域的分类- 全局变量:也就是属性,作用域为**整个类体**- 局部变量:也就是除了属性之外的其他变量,作用域为定义它的**代码块**{}中4. 全局变量**有默认值**,可以不赋值直接使用,局部变量**没有默认值**,必须赋值后才能使用<a name="LYyRK"></a>#### 2. 作用域注意事项1. 属性和局部变量可以重名,访问时遵循就近原则1. 在**同一个作用域**中,两个局部变量**不能重名**1. **属性生命周期较长**,伴随着对象的创建而创建,伴随着对象的销毁而销毁。**局部变量生命周期较短**,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。1. 作用域范围不同- 全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)- 局部变量:只能在本类中对应的方法中使用5. 修饰符不同- 全局变量可以加修饰符- 局部变量不可以加修饰符```javapublic class VarScopDetail {public static void main(String[] args) {Person p1 = new Person();p1.say(); // AlexSystem.out.println(p1.age);T t1 = new T();t1.test(); // 第一种跨类访问:通过创建对象t1.test2(p1); // 第二种跨类访问:通过传入参数}}class T {public void test() {Person p1 = new Person();System.out.println(p1.name); // Jack}public void test2(Person p) {System.out.println(p.name); // Jack}}class Person {// 属性可以加修饰符(public protected private...)public int age = 20;String name = "Jack";public void say() {String name = "Alex";System.out.println(name); // Alex}}
七、构造器
1. 构造器基本介绍
- 构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化
- 基本语法
[修饰符] 方法名(形参列表) {方法体;}
- 构造器的特点
- 构造器的修饰符可以默认,也可以是
public protected private - 构造器没有返回值
- 方法名和类名必须一样
- 参数列表和成员方法一样的规则
- 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化
- 构造器的修饰符可以默认,也可以是
2. 构造器快速入门
- 在创建人类的对象时,直接指定这个对象的年龄和姓名
public class Constructor01 {public static void main(String[] args) {// 当我们new一个对象时,直接通过构造器指定名字和年龄Person p1 = new Person("Smith", 80);System.out.println("p1的信息如下:");System.out.println("name = " + p1.name);System.out.println("age = " + p1.age);}}class Person {String name;int age;/** 构造器* 1. 构造器没有返回值,也不能写void* 2. 构造器的名称和类名一样* 3. (String pName, int pAge)是构造器的形参列表,规则和成员方法以一样*/public Person(String pName, int pAge) {System.out.println("构造器被调用~~ 完成对象的属性初始化");name = pName;age = pAge;}}
3. 构造器注意事项
- 一个类可以定义多个不同的构造器,即构造器重载
- 比如:我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要年龄
- 构造器的名字要和类名完全一样
- 构造器没有返回值
- 构造器是完成对象的初始化,并不是创建对象
- 在创建对象时,系统自动的调用该类的构造方法
- 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如
Dog(){},使用javap指令,反编译结果为:Dog(); - 一旦定义了自己的构造器,默认的构造器就被覆盖,就不能再使用默认的无参构造器,除非再显式的定义一下,即:
Dog(){}
4. 构造器课堂练习
- 在前面定义的
Person类中添加两个构造器: - 第一个无参构造器:利用构造器设置所有人的
age属性初始值都为18 - 第二个带
pName和pAge两个参数的构造器:使得每次创建Person对象的同时初始化对象的age属性值和name属性值。分别使用不用的构造器,创建对象
public class Constructor01 {public static void main(String[] args) {// 无参构造器Person p1 = new Person();System.out.print("p1的信息:");System.out.print("name = " + p1.name); // nullSystem.out.print(" age = " + p1.age); // 18System.out.println();// 两个参数构造器Person p2 = new Person("Smith", 80);System.out.print("p2的信息:");System.out.print("name = " + p2.name); // SmithSystem.out.print(" age = " + p2.age); // 80}}class Person {String name;int age;// 无参构造器public Person() {age = 18;}// 两个参数构造器public Person(String pName, int pAge) {name = pName;age = pAge;}}
5. 对象创建的流程分析
public class Process {public static void main(String[] args) {Person p = new Person("小倩", 20);}}class Person {int age = 90;String name;Person(String n, int a01) { // 构造器name = n;age = a01;}}

- 先加载Person类信息(Person.class),只会加载一次
- 在堆中分配空间(地址)
- 完成对象初始化
- 默认初始化:
age = 0, name = null - 显式初始化:
age = 90, name = null - 构造器初始化:
age = 20, name = "小倩"
- 默认初始化:
- 把对象在堆中的地址,返回给
p(p是对象名,也是对象的引用)
八、this
1. this基本介绍
- Java虚拟机会给每个对象分配
this,代表当前对象 - 哪个对象调用,
**this**就代表哪个对象
2. 深入理解
public class This01 {public static void main(String[] args) {Dog dog1 = new Dog("大壮", 3);dog1.info();Dog dog2 = new Dog("大黄", 2);dog2.info();}}class Dog {public String name;public int age;public Dog(String name, int age) {this.name = name;this.age = age;}public void info() {System.out.println(this.name + " "+ this.age + " "+ + "当前对象的hashCode是:" + this.hashCode());}}
- 内存分析图
3. 注意事项和使用细节
- this关键字可以用来访问本类的属性、方法、构造器
this用于区分当前类的属性和局部变量访问成员方法的语法:
this.方法名(参数列表);class T {// this访问成员方法public void f1() {System.out.println("f1() 方法");}public void f2() {System.out.println("f2() 方法");f1(); // 第一种方式this.f1(); // 第二种方式}}
访问构造器语法:
this(参数列表);,注意只能在构造器中访问另一个构造器,必须是构造器中的第一条语句class T {// this访问构造器// 注意只能在构造器中访问另一个构造器// 如果有 this(参数列表); 必须是构造器中的第一条语句public T() {this("jack", 100); // 放在第一条语句System.out.println("T() 构造器");// 在这里访问 T(String name, int age) 构造器}public T(String name, int age) {System.out.println("T(String name, int age) 构造器");}}
this不能在类定义的外部使用,只能在类定义的方法中使用
4. this课堂练习
定义
Person类,里面有name, age属性,并提供compareTo比较方法,用于判断是否和另一个人相等,提供测试类TestPerson用于测试,名字和年龄完全一样,就返回true,否则返回false```java public class TestPerson { public static void main(String[] args) {Person p1 = new Person("mary", 20);Person p2 = new Person("smith", 30);System.out.println("p1和p2比较的结果:" + p1.compareTo(p2));
} }
class Person { String name; int age;
public Person(String name, int age) {this.name = name;this.age = age;}public boolean compareTo(Person p) {// if (this.name.equals(p.name) && this.age == p.age) {// return true;// } else {// return false;// }return this.name.equals(p.name) && this.age == p.age;}
}
<a name="NhqVS"></a>### 九、本章作业1. 编写类`A01`,定义方法`max`,实现求某个`double数组`的最大值,并返回```javapublic class Homework01 {public static void main(String[] args) {A01 a01 = new A01();double[] arr = { 3.2, 4.4, 55.88, 751.4, 44.55 };Double res = a01.max(arr);if (res != null) { // 代码健壮性System.out.println("arr的最大值为:" + res);} else {System.out.println("arr的输入有误,arr不能为null或{}");}}}class A01 {// 先完成正常业务,再考虑代码健壮性public Double max(double[] arr) {// 先判断arr是否为null,再判断length是否大于0if (arr != null && arr.length > 0) {double max = arr[0]; // 假定第一个元素为最大值for (int i = 1; i < arr.length; i++) {if (arr[i] > max) {max = arr[i];}}return max;} else {return null;}}}
编写类
A02,定义方法find,实现查找某字符串数组中的元素,并返回索引,如果找不到,返回-1```java public class Homework02 { public static void main(String[] args) {A02 a02 = new A02();String[] arr = { "aaa", "bbb", "ccc" };int index = a02.find(arr, "ddd");if (index == -2) {System.out.println("数组不能为null或{}");} else if (index == -1) {System.out.println("没有找到");} else {System.out.println("找到了,索引为:" + index);}
} }
class A02 { // 先完成正常业务,再考虑代码健壮性 public int find(String[] arr, String str) { if (arr != null && arr.length > 0) { // 遍历字符串数组,如果找到,返回索引,没有找到返回-1 for (int i = 0; i < arr.length; i++) { if (str.equals(arr[i])) { return i; } } return -1; } else { return -2; } } }
3. 编写类`Book`,定义方法`updatePrice`,实现更改某本书的价格。具体:如果`价格 > 150`,则更改为150,如果`价格 > 100`,更改为100,否则不变```javapublic class Homework03 {public static void main(String[] args) {Book book = new Book("book1", 200);book.info(); // 显示书籍信息System.out.println("=====更新书籍价格=====");book.updatePrice(); // 更新书籍价格book.info(); // 显示书籍信息}}class Book {String name;double price;// 构造器public Book(String name, double price) {this.name = name;this.price = price;}public void updatePrice() {// 如果方法中,没有price局部变量,this.price 等价 priceif (price > 150) {price = 150;} else if (price > 100) {price = 100;}}// 显示书籍情况public void info() {System.out.println("书名:" + name + "\n价格:" + price);}}
编写类
A03,实现数组的复制功能copyArr,输入旧数组,返回一个新数组,元素和旧数组一样 ```java public class Homework04 { public static void main(String[] args) {A03 a03 = new A03();int[] arr = { 5, 4, 3 };int[] arrNew = a03.copyArr(arr);for (int i = 0; i < arrNew.length; i++) {System.out.print(arrNew[i] + " ");}
} }
class A03 { public int[] copyArr(int[] arr) { // 在堆中,创建一个长度为arr.length的数组 int[] arrNew = new int[arr.length]; // 遍历arr,将元素拷贝到arrNew for (int i = 0; i < arr.length; i++) { arrNew[i] = arr[i]; } return arrNew; } }
5. 定义一个圆类`Circle`,定义属性:半径,提供显示圆周长功能的方法,提供显示圆面积的方法```javapublic class Homework05 {public static void main(String[] args) {Circle circle = new Circle(5);System.out.println("面积:" + circle.area());System.out.println("周长:" + circle.perimeter());}}class Circle {/** 圆的周长 = 2πr* 圆的面积 = πr²*/double radius;public Circle(double radius) {this.radius = radius;}public double perimeter() {return 2 * Math.PI * radius;}public double area() {return Math.PI * radius * radius;}}
编程创建一个
Cale计算类,在其中定义2个变量表示两个操作数,定义四个方法实现求和、差、乘、商(要求除数为0的话,要提示)并创建两个对象,分别测试 ```java public class Homework06 { public static void main(String[] args) {Cale cale = new Cale(5, 25);System.out.println("和:" + cale.sum());System.out.println("差:" + cale.minus());System.out.println("积:" + cale.mul());Double divRes = cale.div();if (divRes != null) {System.out.println("商:" + divRes);}
} }
class Cale { double num1; double num2;
public Cale(double num1, double num2) {this.num1 = num1;this.num2 = num2;}public double sum() { // 和return num1 + num2;}public double minus() { // 差return num1 - num2;}public double mul() { // 积return num1 * num2;}public Double div() { // 商if (num2 == 0) {System.out.println("除数不能为零");return null;} else {return num1 / num2;}}
}
7. 设计一个`Dog`类,有名字、颜色和年龄属性,定义输出方法`show()`显示其信息。并创建对象,进行测试```javapublic class Homework07 {public static void main(String[] args) {Dog dog = new Dog("大黄", "黄色", 5);dog.show();}}class Dog {String name;String color;int age;public Dog(String name, String color, int age) {this.name = name;this.color = color;this.age = age;}public void show() {System.out.println("名字:" + name);System.out.println("颜色:" + color);System.out.println("年龄:" + age);}}
- 给定一个Java程序的代码如下,编译运行后,输出结果是:
10, 9, 10```java public class Homework08 { }
class Test { // 公共类 int count = 9; // 属性
public void count1() { // Test类的成员方法count = 10;System.out.println("count1 = " + count);}public void count2() { // Test类的成员方法System.out.println("count2 = " + count++);}// 这是Test类的main方法,任何一个类,都可有mainpublic static void main(String[] args) {// 1. new Test() 是匿名对象,只能使用一次// 2. new Test().count1(); 创建好匿名对象后,就调用count1()new Test().count1();Test t1 = new Test();t1.count2();t1.count2();}
}
9. 定义`Music`类,里面有音乐名`name`、音乐时长`time`属性,并有播放`play`功能和返回本身属性信息的功能方法`getInfo````javapublic class Homework09 {public static void main(String[] args) {Music music = new Music("ABC", 200);music.play();System.out.println(music.getInfo());}}class Music {String name;int time;public Music(String name, int time) {this.name = name;this.time = time;}public void play() {for (int i = 0; i <= time; i++) {System.out.printf("音乐《%s》正在播放:%d秒 / %d秒%n", name, i, time);}}public String getInfo() {return "歌曲名:" + name + "\n歌曲总时长:" + time;}}
- 试写出以下代码的运行结果(101, 100, 101, 101) ```java public class Homework10 { }
class Demo { int i = 100; public void m() { int j = i++; System.out.println(“i = “ + i); // 101 System.out.println(“j = “ + j); // 100 } }
class Test { public static void main(String[] args) { Demo d1 = new Demo(); Demo d2 = d1; d2.m(); System.out.println(d1.i); // 101 System.out.println(d2.i); // 101 } }
- 分析<br />11. 在测试方法中,调用`method`方法,代码如下,编译正确,试写出`method`方法的定义形式,调用语句为:`System.out.println(method(method(10.0, 20.0), 100));````javapublic class Homework11 {}class Test {public double method(double d1, double d2) {return d1 + d2;}public void print() {System.out.println(method(method(10.0, 20.0), 100));}}
创建一个
Employee类,属性有(名字,性别,年龄,职位,薪水),提供3个构造方法,可以初始化如下,要求充分复用构造器- 名字,性别,年龄,职位,薪水
- 名字,性别,年龄
- 职位,薪水 ```java public class Homework12 { } class Employee { String name; String gender; int age; String job; int sal;
public Employee(String name, String gender, int age) { this.name = name; this.gender = gender; this.age = age; }
public Employee(String job, int sal) { this.job = job; this.sal = sal; }
public Employee(String name, String gender, int age, String job, int sal) { this(name, gender, age); // 使用到前面的构造器,构造器复用 this.job = job; this.sal = sal; } } ```
将对象作为参数传递给方法
题目要球:- 定义一个
Circle类,包含一个double型的radius属性代表圆的半径,findArea()方法返回圆的面积 - 定义一个类
PassObject,在类中定义一个方法printAreas(),该方法的定义如下:public void printAreas(Circle c, int times)//方法签名/声明 - 在
printAreas()方法中打印输出1到times之间的每个整数半径值,以及对应的面积
例如,times为5,则输出半径1,2,3,4,5,以及对应的圆面积 在
main方法中调用printAreas()方法,调用完毕后输出当前半径值。程序运行结果如图所示:
```java
public class Hello {
public static void main(String[] args) {
Circle circle = new Circle();
PassObject po = new PassObject();po.printAreas(circle, 5); } }
- 定义一个
class Circle { double radius; // 圆的半径
// 圆的面积public double findArea() {return Math.PI * radius * radius;}// 添加方法setRadius,修改对象的半径值public void setRadius(double radius) {this.radius = radius;}
}
class PassObject { public void printAreas(Circle c, int times) { System.out.println(“Radius\tArea”); for (double i = 1; i <= times; i++) { c.setRadius(i); System.out.println(i + “\t” + c.findArea()); } } }
14. 扩展题1. 有个人`Tom`设计他的`成员变量.成员方法`,可以电脑猜拳。每次都会随机生成`0, 1, 2`1. `0`表示石头,`1`表示剪刀,`2`表示布1. 并要可以显示`Tom`的输赢次数(清单)```javapublic class Hello {public static void main(String[] args) {Tom t = new Tom(); // 创建一个玩家对象// 用来记录最后输赢的次数int isWinCount = 0;// 创建一个二维数组,用来接收局数,Tom出拳情况以及电脑出拳情况int[][] arr1 = new int[3][3];int j = 0;// 创建一个一维数组,用来接收输赢情况String[] arr2 = new String[3];Scanner scanner = new Scanner(System.in);for (int i = 0; i < 3; i++) {// 获取玩家出的拳System.out.print("请输入你要出的拳(0-拳头,1-剪刀,2-布):");int num = scanner.nextInt();t.setTomGuessNum(num);int tomGuess = t.getTomGuessNum();arr1[i][j + 1] = tomGuess;// 获取电脑出的拳int comGuess = t.comGuess();arr1[i][j + 2] = comGuess;// 将玩家猜的拳与电脑作比较String isWin = t.vsComputer();arr2[i] = isWin;arr1[i][j] = t.count;// 对每一局的情况进行输出System.out.println("======================");System.out.println("局数\t玩家的出拳\t电脑的出拳\t输赢情况");System.out.println(t.count + "\t" + tomGuess + "\t\t" + comGuess + "\t\t" + t.vsComputer());System.out.println("\n\n");isWinCount = t.winCount(isWin);}// 对游戏的最终结果进行输出System.out.println("局数\t\t玩家的出拳\t电脑的出拳\t输赢情况");for (int a = 0; a < arr1.length; a++) {for (int b = 0; b < arr1[a].length; b++) {System.out.print(arr1[a][b] + "\t\t");}System.out.print(arr2[a]);System.out.println();}System.out.println("你赢了" + isWinCount + "次");}}class Tom {int tomGuessNum; // 玩家出拳的类型int comGuessNum; // 电脑出拳的类型int winCountNum; // 玩家赢的次数int count = 1;public int comGuess() {Random r = new Random();comGuessNum = r.nextInt(3); // 返回0-2的随机数return comGuessNum;}/*** 设置玩家猜拳的数字的方法** @param tomGuessNum*/public void setTomGuessNum(int tomGuessNum) {if (tomGuessNum > 2 || tomGuessNum < 0) {// 抛出一个异常throw new IllegalArgumentException("数字输入错误");}this.tomGuessNum = tomGuessNum;}public int getTomGuessNum() {return tomGuessNum;}/*** 比较猜拳的结果** @return*/public String vsComputer() {if (tomGuessNum == 0 && comGuessNum == 1) {return "你赢了!";} else if (tomGuessNum == 1 && comGuessNum == 2) {return "你赢了!";} else if (tomGuessNum == 2 && comGuessNum == 0) {return "你赢了!";} else if (tomGuessNum == comGuessNum) {return "平手!";} else {return "你输了!";}}/*** 记录玩家赢的次数* @return*/public int winCount(String s) {count++;if (s.equals("你赢了")) {winCountNum++;}return winCountNum;}}
