一、类与对象
1.1 简单介绍
为什么引入?
现有技术解决:Object01.java
import java.util.Scanner;public class Object01 {public static void main(String[] args) {/*需求:张老太太有两只猫,一只叫小白,今年3岁,白色;另一只叫小黑,今年12岁,黑色。请编写一个程序,当用户输入猫的名字时,就显示该猫的名字年龄,颜色。如果用户输入错误,则显示 张老太太没有这只猫。*/Scanner scan = new Scanner(System.in);// 传统方法// 数组存放// 缺点:无法同时保存不同的数据类型// 只能通过[下标]获取信息,名字和内容的对应关系不明确// 不能体现猫的行为String cat1[] = {"小白", "3", "白色"};String cat2[] = {"小黑", "12", "黑色"};System.out.println("请输入小猫的名字");String cat = scan.next();if (cat.equals(cat1[0])) {for (int i = 0; i < cat1.length; i++) {System.out.print(cat1[i] + " ");}} else if (cat.equals(cat2[0])) {for (int i = 0; i < cat2.length; i++) {System.out.print(cat2[i] + " ");}} else {System.out.println("张老太太没有这只猫");}// 传统方法// 单独变量解决 缺点:不利于数据的管理// 第1只猫信息String cat1Name = "小白";int cat1Age = 3;String cat1Color = "白色";// 第2只猫信息String cat2Name = "小花";int cat2Age = 100;String cat2Color = "花色";}}
现有技术解决的缺点分析:
- 不利于数据的管理
- 效率低
1.2 类与对象的关系示意图

说明:
- 类就是数据类型,比如 Cat
- 对象就是一个具体的实例
1.3 案例演示
Object02.java
public class Object02 {public static void main(String[] args) {// 使用OOP面向对象解决// 实例化一只猫/创建一只猫/把猫实例化 (一个意思)// 解读:// 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.5;// 创建第二只猫,并赋给 cat2// cat2 也是一个对象(猫对象)Cat cat2 = new Cat();cat2.name = "小花";cat2.age = 100;cat2.color = "花色";cat2.weight = 20.2;// 访问对象的属性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; // 体重}
1.4 类与对象的区别和联系
- 类是抽象的,概念的,代表一类事物,比如人类、猫类…,即它是数据类型
- 对象是具体的,实际的,代表一个具体事物,即 是实例
- 类是对象的模板,对象是类的一个个体,对应一个实例
1.5 对象在内存中的存在形式(重要)

1.6 创建对象的方法
先声明再创建
Cat cat;
cat = new Cat();直接创建
Cat cat = new Cat();
1.7 类与对象的内存分配机制(重要)
根据代码,分析
Person p1 = new Person();p1.age = 10;p1.name = "小明";Person p2 = p1; // 把p1赋给p2,即让p2指向p1System.out.println(p2.age);p2.age = 15;System.out.println(p1.age);

Java内存的结构分析:
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Cat cat,数组等)
- 方法区:常量池(常量、比如字符串),类加载信息
Java创建对象的流程简单分析:
Person p = new Person();
p.name = “jack”;
p.age = 10;
- 先加载Person类信息(属性和方法信息,只会加载一次)
- 在堆中分配空间,进行默认初始化(看规则)
- 把地址赋给p,p就指向对象
- 进行指定初始化 比如:p.name = “jack”; p.age = 10;
二、属性(成员变量)
2.1 基本介绍
- 从概念或叫法上看:成员变量 = 属性 = field(字段) (即 成员变量是用来表示属性的)。
- 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。
比如我们前面定义猫类的 int age 就是属性
2.2 属性的注意事项和细节说明
- 属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
访问修饰符:用于控制属性的访问范围
4种访问修饰符:public,protected,默认,private - 属性 的定义类型可以为任意类型,包含基本类型和引用类型
- 属性如果不赋值,有默认值,规则和数组一致
2.3 案例演示
PropertiesDetail.java
public class PropertiesDetail {public static void main(String[] args) {// 创建Person对象// p1 是对象名(对象引用)// new Person() 创建的对象空间(数据) 才是真正的对象Person p1 = new Person();// 对象的属性默认值,遵守数组规则:// int:0,short:0,byte:0,long:0,float:0.0,// double:0.0,char:\u0000,boolean:false,String:nullSystem.out.println("\n当前这个人的信息");System.out.println("age=" + p1.age + " name=" + p1.name + " sal="+ p1.sal + " isPass=" + p1.isPass);}}class Person {// 四个属性int age;String name;double sal;boolean isPass;}
2.4 如何访问属性
基本语法
对象名.属性名;
三、成员方法
3.1 基本介绍
在某些情况下,我们需要定义成员方法(简称方法) 比如人类:除了有一些属性(年龄、姓名…..),还有一些行为(说话、跑步、通过学习可以做算术题)。这时就要用成员方法才能完成。现在要求对Person类完善。
3.2 案例演示
添加speak 成员方法,输出 我是一个好人
2. 添加cal01 成员方法,可以计算从1+…+1000的结果
3. 添加cal02 成员方法,该方法可以接收一个数n,计算1+…+n的结果
4. 添加getSum 成员方法,可以计算两个数的和
Method01.java ```java import java.util.Scanner; public class Method01 {public static void main(String[] args) {
Scanner scan = new Scanner(System.in);// 方法使用// 1. 方法写好后如果不调用,不会主动运行// 2. 先创建对象,然后调用方法即可Person p1 = new Person(); // 创建对象p1.speak(); // 调用方法p1.cal01();p1.cal02(100); // 调用cal02方法,同时给形参n = 100// 调用 getSum方法,同时 num1 = 10, num2 = 20// 把方法 getSum方法 返回的值 赋给 变量returnSumint returnSum = p1.getSum(10, 20);System.out.println("两数之和等于" + returnSum);
} }
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 sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}System.out.println("sum=" + sum);}// 添加cal02 成员方法,该方法可以接收一个数n,计算1+...+n的结果// 解读:// 1. (int n) 形参列表,表示当前有一个形参 n 可以接收用户输入public void cal02(int n) {int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}System.out.println("sum=" + sum);}// 添加getSum 成员方法,可以计算两个数的和// 解读:// 1. public:表示方法是公开的// 2. int:表示方法执行后返回一个 int 值// 3. getSum:方法名// 4. (int num1,int num2) 形参列表,2个形参,可以接收用户输入的两个数// 5. return sum; 表示把 sum 的值返回public int getSum(int num1,int num2) {int sum = num1 + num2;return sum;}
}
<a name="rrHnA"></a>#### 3.3 方法的调用机制原理(重要)<br />说明:1. 当程序执行到方法时,就会开辟一个独立的空间(栈空间)1. 当方法执行完毕,或者执行到 return语句时,就会返回1. 返回到调用方法的地方1. 返回后继续执行方法后面的代码(独立空间自动销毁)1. 当 main方法(栈)执行完毕,整个程序退出<a name="ei7qY"></a>#### 3.4 成员方法的必要性看一个需求:<br />请遍历一个数组,输出数组的各个元素值。<br />**Method02.java**```javapublic class Method02 {public static void main(String[] args) {// 请遍历一个数组,输出数组的各个元素值。int[][] map = {{0, 0, 1}, {1, 1, 1}, {1, 1, 3}};// 遍历map数组// 传统方法:for遍历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();}// ...要求再次遍历map数组// 传统方法:再for遍历一遍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();}// ...再次遍历// 传统方法冗余度太高,同样的代码写很多次// 如果同样的代码某个地方需要做同样的修改,很麻烦// 使用方法完成输出MyTools tool = new MyTools();tool.printArr(map); // map必须填写,方法里的只是形参,这是是实参// ...再次遍历tool.printArr(map);}}// 把遍历输出map的功能,写到一个类的方法中,然后调用该方法即可class MyTools {// 方法,接收一个二维数组public void printArr(int[][] map) { // 这里的map是形参 名字可以随意定义 不影响System.out.println("==== 使用方法输出的map数组 ====");// 对传入的map数组进行遍历输出for (int i = 0; i < map.length; i++) { // 方法里面的map要保持和形参名字一致for (int j = 0; j < map[i].length; j++) {System.out.print(map[i][j] + " ");}System.out.println();}}}
成员方法的好处:
- 提高代码的复用性
- 可以将实现的细节封装起来,然后供其他用户来调用即可
3.5 成员方法的定义
访问修饰符 返回数据类型 方法名(参数列表..) { // 方法体
语句;
return 返回值;
}
- 访问修饰符:控制方法的使用范围 如public
- 返回数据类型:表示成员方法输出,void 表示没有返回值
- 形参列表:表示成员方法输入
- 方法主体:表示为了实现某一功能代码块
- return 语句不是必须的
3.6 方法的注意事项和使用细节
访问修饰符
有四种(public,protected,默认,private),如果不写就是默认访问
返回数据类型
- 一个方法最多有一个返回值,如果想返回多个值,可以借用数组
- 返回类型可以为任意类型,包含基本类型或引用类型
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的值类型一致或兼容
- 如果方法是 void ,则方法体中可以没有 return 语句,或者只写 return;
方法名
遵循驼峰命名法,最好见名知义,表达出该功能的意思即可,
比如 得到两个数的和 getSum,开发中按照规范
参数列表
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开
- 参数类型可以为任意类型,包含基本类型和引用类型
- 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
- 方法定义时的参数成为形式参数,简称形参;方法调用时的参数成为实际参数,简称实参,实参和形参的类型要一致或兼容,个数、顺序必须一致
方法体
里面写完成功能具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但里面不能再定义方法!即:方法不能嵌套定义。
方法调用细节说明
- 同一个类中的方法调用:直接调用即可。比如: print(参数);
- 跨类中的方法A类调用B类方法:需要通过对象名调用。比如:对象名.方法名(参数);
- 特别说明:跨类的方法调用和方法的访问修饰符相关。
⏱ 小练习
- 编写类 AA ,方法:判断一个数是奇数odd 还是偶数even,返回boolean ```java
public class MethodExercise01 {
public static void main(String[] args) {int num = 1;AA a = new AA();if (a.isOdd(num)) {System.out.println(num + "是奇数");} else {System.out.println(num + "是偶数");}}
} class AA { public boolean isOdd(int i) { // if (i % 2 != 0) { // return true; // } else { // return false; // }
// return i % 2 !=0 ? true : false;return i % 2 != 0;}
}
2. 根据行、列、字符 打印对应的行数和列数的字符,比如:行:4,列:4,字符 #,打印相应的效果```javapublic class MethodExercise01 {public static void main(String[] args) {AA a = new AA();a.printChar(4,4,'#');}}class AA {public void printChar(int row,int column,char c) {for (int i = 0; i < row; i++) {for (int j = 0; j < column; j++) {System.out.print(c + " ");}System.out.println();}}}
3.7 成员方法传参机制
方法的传参机制对我们今后的编程非常重要,一定要搞的清清楚楚明明白白。
基本数据类型的传参机制
MethodParameter01.java
public class MethodParameter01 {public static void main(String[] args) {int a = 10;int b = 20;AA obj = new AA();obj.swap(a,b);System.out.println(" a=" + a + " b=" + b);}}class AA {public void swap(int a,int b) {System.out.println("交换前 a=" + a + " b=" + b);int temp = a;a = b;b = temp;System.out.println("交换后 a=" + a + " b=" + b);}}
分析代码的运行流程及结果
结论:基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参!
引用数据类型的传参机制
MethodParameter02.java
public class MethodParameter02 {public static void main(String[] args) {/*看一个案例B类中编写一个方法 test100,可以接收一个数组,在方法中修改该数组,看看原数组是否变化?B类中编写一个方法 test200,可以接受一个Person(age,sal)对象,在方法中修改该对象属性,看看原来的对象是否变化?*/int arr[] = {1,2,3};B b = new B();b.test100(arr); //调用方法System.out.println("main中的arr数组");for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();Person p1 = new Person();p1.age = 10;p1.sal = 100.8;b.test200(p1);System.out.println(p1.age);}}class B {public void test100(int[] a) {a[0] = 100;System.out.println("test100方法中的arr数组");for (int i = 0; i < a.length; i++) {System.out.print(a[i] + " ");}System.out.println();}public void test200(Person p) {p.age = 50;}}class Person {int age;double sal;}


思考:如果 test200 中是 p = null; 或者 p = new Person(); main中 执行 b.test200(p1); 后会输出什么?
⏱ 小练习
编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。
克隆对象,注意要求新对象和原来的对象是两个独立的对象,只是它们的属性相同。
public class MethodExercise {public static void main(String[] args) {MyTools tool = new MyTools();Person p1 = new Person();p1.name = "Jack";p1.age = 18;Person p2 = tool.copyPerson(p1);//到此 p1 和 p2 是两个独立的对象,下面验证p1.name = "Bob";p1.age = 20;System.out.println("拷贝的新对象p2的属性 name=" + p2.name + " age=" + p2.age);System.out.println("原对象p1修改后的属性 name=" + p1.name + " age=" + p1.age);// 也可通过输出对象的 HashCode 看看对象是否是一个}}class Person {String name;int age;}class MyTools {//编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。//克隆对象,注意要求新对象和原来的对象是两个独立的对象,只是它们的属性相同。////编写方法的思路//1. 方法的返回类型 Person//2. 方法的名字 copyPerson//3. 方法的形参 (Person p)//4. 方法体,创建一个新对象,并复制属性,返回即可public Person copyPerson(Person p) {// 创建一个新的PersonPerson p2 = new Person();p2.name = p.name; // 把原来对象的name赋给p2p2.age = p.age; // 把原来对象的age赋给p2return p2;}}
四、方法递归调用
4.1 基本介绍
递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于编程者解决复杂问题,同时可以让代码变得简洁。
4.2 递归能解决什么问题?
- 各种数学问题:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(Google编程大赛)
- 各种算法中也会使用到递归,比如快速排序,归并排序,二分查找,分治算法等。
- 将用栈解决的问题 -> 递归代码比较简洁
4.3 递归调用机制
结合下面代码进行分析
public class Recursion01 {public static void main(String[] args) {T t = new T();t.test(5);}}class T {public void test(int n) {if (n > 2) {test(n - 1);}System.out.println("n=" + n);}}

阶乘问题:结合上图试分析递归过程
public class Recursion02 {public static void main(String[] args) {T t = new T();System.out.println(" = " + t.factorial(5));}}class T {public int factorial(int n) {if (n == 1) {System.out.print(n);return 1;} else {System.out.print(n + " * ");return factorial(n - 1) * n;}}}
4.4 递归重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如 n 变量
- 如果 方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
⏱ 小练习
请使用递归的方式求出斐波那契数1,1,2,3,5,8,13….给你一个整数n,求出第n个数的值是多少 ```java import java.util.Scanner; public class RecursionExercise01 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);System.out.println("请问要输出第几位斐波那契数?");int n = scan.nextInt();T t = new T();System.out.println(t.resNum(n));
} }
class T { public int resNum(int n) { if (n > 0) { if (n == 1 || n == 2) { return 1; } else { return resNum(n - 1) + resNum(n - 2); } } else { return 0; } } }
2. 猴子吃桃问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天都是如此,当到第10天时,想再吃时(即还没吃),发现只有1个桃子了。问:最初有多少个桃子?```javapublic class RecursionExercise02 {public static void main(String[] args) {Monkey monkey = new Monkey();System.out.println(monkey.peach(1));}}class Monkey {public int peach(int day) {if (day == 10) {return 1;} else {return (peach(day + 1) + 1) * 2;}}}
4.5 迷宫问题
- 小球得到的路径,和程序员设置的找路策略有关:即路的上下左右的顺序相关
- 再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化
- 测试回溯现象
扩展思考:如何求出最短路径?
思路:(1)穷举 (2)图
MiGong.java ```java public class MiGong {public static void main(String[] args) {
// 思路// 1. 先创建迷宫,用二维数组表示// 2. 先规定 map 数组的元素值:0表示可以走,1表示障碍物int[][] map = new int[8][7];// 3. 将最上面的一行和最下面的一行,全部设置为1for (int i = 0; i < 7; i++) {map[0][i] = 1;map[7][i] = 1;}// 4. 将最右面的一列和最左面的一列,全部设置为1for (int i = 0; i < 8; i++) {map[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();}// 使用findWay给老鼠找路T t = new T();t.findWay(map, 1, 1);// 输出找到的路线System.out.println("\n===== 找到的路线 =====");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) { // 当前这个位置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) { // 当前这个位置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;}}}
}
<a name="o87PE"></a>#### 4.6 汉诺塔问题**汉诺塔**(Tower of Hanoi),又称**河内塔**,是一个源于[印度](https://baike.baidu.com/item/%E5%8D%B0%E5%BA%A6/121904)古老传说的[益智玩具](https://baike.baidu.com/item/%E7%9B%8A%E6%99%BA%E7%8E%A9%E5%85%B7/223159)。[大梵天](https://baike.baidu.com/item/%E5%A4%A7%E6%A2%B5%E5%A4%A9/711550)创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令[婆罗门](https://baike.baidu.com/item/%E5%A9%86%E7%BD%97%E9%97%A8/1796550)把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。<br />[点击查看【codepen】](https://codepen.io/finnhvman/embed/gzmMaa)<br />**HanoiTower.java**```javapublic class HanoiTower {public static void main(String[] args) {Tower tow = new Tower();tow.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 = 1if (num == 1) {System.out.println(a + "->" + c);} else {// 如果有多个盘,可以看作两个,最下面的和上面的所有// 1. 先移动上面所有的盘到 B,借助 Cmove(num - 1, a, c, b);// 2. 把最下面的这个盘,移动到CSystem.out.println(a + "->" + c);// 2. 再把 B塔的所有盘,移动到C,借助Amove(num - 1, b, a, c);}}}
4.7 八皇后问题
八皇后问题,是个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击。即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
public class EightQueen {public static void main(String[] args) {// map[]表示棋盘,假设map[] = {2, 5, 7, 0, 4, 6, 1 ,3}// 则 map[0] = 2 表示第一(层)行第三列放置第一颗皇后棋子// map[1] = 5 表示第二(层)行第六列放置第二颗皇后棋子// ......// map[7] = 3 表示第八(层)行第四列放置第八颗皇后棋子int[] map = new int[8];Q q = new Q(); // 新建q对象q.findWay(map,0); // 引用q对象的findWay方法,//传入棋盘,第一颗棋子先放在第一层第一列System.out.println(q.count); // 输出总方法数}}class Q {int count; // 用于记录成功摆放的次数 全局变量 默认初始值为0public void findWay(int[] map, int queen) {if (queen == 8) { // 如果八颗皇后全部放在了棋盘上for (int i = 0; i < 8; i++) { //循环遍历数组System.out.print((i + 1) + " " + (map[i] + 1) + "\t");} // 将这次摆放结果输出System.out.println();count++; // 成功计数器+1} else {for (int i = 0; i < 8; i++) { // 找到这一层(行)可以放置的位置(列)map[queen] = i;if (isSafe(map, queen)) { // 如果当前位置可以放置findWay(map, queen + 1); // 进入下一层尝试// 下一层也会找当前层可以放置的位置(列),并再进入下一层// 层层遍历,最终如果没有将八颗棋子都放在棋盘上,// 由于不满足上面的if (queen == 8) 所以结果不会输出,等于舍弃了这次// 直到找到可以将八颗棋子都放在棋盘上的结果,即map数组每一位都成功赋值// 会将这次摆放结果输出,并寻找下一个正确的摆放结果// 直至第一层每一列都尝试了,全部结果也就都找到了}}}}public boolean isSafe(int[] map, int queen) {for (int i = 0; i < queen; i++) {// 这一层放置的位置依次与前面层放置的位置对比// 由于这一次放置的是相对之前最下面的一层,所以肯定不会与之前在同一层// map[i] == map[queen] 表示放置的位置与之前层的某个位置同列// Math.abs(i - queen) == Math.abs(map[i] - map[queen]) 表示同斜线// Math.abs() 表示取绝对值,保证结果大于零// 这里可以借助等腰直角三角形理解// 假设 到这一层 棋盘的结构如下 此时 queen = 2// 第一层 { #, 0, 0, 0, 0, 0, 0, 0 } 这里 # 表示 map[0] = 0,queen = 0// 第二层 { 0, 0, #, 0, 0, 0, 0, 0 } 这里 # 表示 map[1] = 2,queen = 1// 第三层 { 0, 0, 0, #, 0, 0, 0, 0 } 这里 # 表示 map[2] = 3,queen = 2 当前// 第二层的#和它正下方的0以及第三层(当前层)的# 组成了一个等腰直角三角形// 由于i < queen时,i++ 所以当i = 1时// map[i] - map[queen] = 2 - 3// i - queen = 1 - 2// Math.abs(i - queen) == Math.abs(map[i] - map[queen]) = 1// 此时构成了等腰直角三角形 即当前层所在列和之前层所在列处在了同一斜线上if (map[i] == map[queen] || Math.abs(i - queen) == Math.abs(map[i] - map[queen])) {return false; // 同列或者同斜线都返回不能放置}}return true; //不是同列或者同斜线,可以放置}}
五、方法重载(OverLoad)
5.1 基本介绍
Java种允许同一个类中,多个同名方法的存在,但要求 形参列表不一致!
比如:System.out.println(); out 是 PrintStream 类型
重载的好处:
- 减轻了起名的麻烦
- 减轻了记名的麻烦
5.2 案例演示
public class OverLoad01 {public static void main(String[] args) {MyCalculator mc = new MyCalculator();System.out.println(mc.calculate(5,3));System.out.println(mc.calculate(5,.3));System.out.println(mc.calculate(.5,3));System.out.println(mc.calculate(5,3,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;}}
5.3 方法重载注意事项和细节
- 方法名:必须相同
- 参数列表:必须不相同(参数类型或个数或顺序,至少有一样不同,形参名无所谓)
- 返回类型:无要求
⏱ 小练习
判断题:
与 void show(int a, char b, double c) { } 构成重载的有 b c d e- void show(int x, char y, double z) { } // 二者相同
- int show(int a, double c, char b) { }
- void show(int a, double c, char b) { }
- boolean show(int c, char b) { }
- void show(double c) { }
- double show(int x, char y, double z) { } // 二者相同
- void shows() { } // 方法名不同
编写程序,类Methods中定义三个重载方法并调用。方法名为m。三个方法分别接收一个 int 参数、两个 int 参数、一个字符串参数。分别执行平方运算并输出结果、相乘并输出结果、输出字符串信息。在主类的main() 方法中分别用参数区别调用三个方法。
在 Methods 类,定义三个重载方法 max(),第一个方法,返回两个 int 值中的最大值,第二个方法,返回两个 double 值中的最大值,第三个方法,返回三个 double 值中的最大值,并分别调用三个方法。
public class OverLoadExercise {public static void main(String[] args) {Methods me = new Methods();me.m(6);me.m(5,4);me.m("java is a game");System.out.println(me.max(3,6));System.out.println(me.max(.6,.8));System.out.println(me.max(.3,.2,9)); // 可以运行,9 自动转成 double// 但是如果再新建一个 double max(double x, double y, int z) 方法// 会调用新的这个方法 新方法和原来的不会冲突,依然符合重载}}class Methods {public void m(int i) {int j = 0;j = i * i;System.out.println(i + "的平方 = " + j);}public void m(int i, int j) {int k = 0;k = i * j;System.out.println(i + " × " + j + " = " + k);}public void m(String x) {System.out.println(x);}public int max(int i, int j) {return i > j ? i : j;}public double max(double i, double j) {return i > j ? i : j;}public double max(double x, double y, double z) {double max = x > y ? x : y;return max > z ? max : z;}}
六、可变参数
6.1 基本介绍
Java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。通过可变参数技术实现。
基本语法:
访问修饰符 返回类型 方法名(数据类型… 形参名) {
……
}
6.2 案例演示
VarParameter01.java
public class VarParameter01 {public static void main(String[] args) {VarMethod var = new VarMethod();System.out.println("所求的和" + var.sum(15,56,17));}}class VarMethod {// 可以计算2个数的和,3个数的和,4,5.....// 可以使用方法重载/*public int sum(int n1, int n2) {return n1 + n2;}public int sum(int n1, int n2, int n3) {return n1 + n2 + n3;}public int sum(int n1, int n2, int n3, int n4) {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 sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];}return sum;}}
6.3 可变参数注意事项和使用细节
- 可变参数的实参可以为0个或任意个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
- 一个形参列表中只能出现一个可变参数
⏱ 小练习
有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩(总分)。封装成一个方法。
VarParameterExercise.java
public class VarParameterExercise {public static void main(String[] args) {VarMethod var = new VarMethod();var.showScore("小王", 66, 78.5);var.showScore("小白", 56, 89.5, 98);var.showScore("小真", 79, 86, 90.5, 99,100);}}class VarMethod {public void showScore(String name, double... score) {double sum = 0;for (int i = 0; i < score.length; i++) {sum += score[i];}System.out.println(name + "的" + score.length + "门课总分为" + sum);}}
七、作用域
7.1 基本介绍
面向对象中,变量作用域是非常重要的知识点,相对来说不是特别好理解 ,请注意认真思考,深刻掌握变量作用域。
- 在 Java 编程中,主要的变量就是属性(成员变量)和局部变量。
- 我们说的局部变量一般是指在成员方法中定义的变量。
- Java中作用域的分类
全局变量:也就是属性,作用域为整个类体
局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中 - 全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值
7.2 案例演示
Scope01.java
public class Scope01 {public static void main(String[] args) {Cat cat = new Cat();cat.cry();cat.eat();}}class Cat {// 全局变量:也就是属性,作用域为整个类体// 属性在定义时,可以直接赋值int age = 10; // 可以不赋值,有默认值public void cry() {// 1. 局部变量一般是指在成员方法中定义的变量// 2. n 和 name 就是局部变量// 3. n 和 name 的作用域在 cry 方法中int n = 10; // 必须赋值,因为没有默认值String name = "Jack";System.out.println("在 cry 方法中使用属性 age= " + age);}public void eat() {System.out.println("在 eat 方法中使用属性 age= " + age);// System.out.println("在 eat 方法中使用cry方法中的局部变量name" + name); // 会报错}}
7.3 作用域的注意事项和使用细节
- 属性和局部变量可以重名,访问时遵循就近原则。
- 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
- 属性声明周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。即存在于一次方法调用中。
- 作用域范围不同:
全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
局部变量:只能在本类中对应的方法中使用 - 修饰符不同
全局变量/属性可以加修饰符(public,protected,private 等)
局部变量不可以加修饰符
八、构造方法/构造器
8.1 基本介绍
前面我们在创建对象时,是先把一个对象创建后,再给它的属性赋值
现在通过构造器,可以实现在创建对象同时,就可以指定这个对象的各个属性
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
基本语法
[ 修饰符 ] 方法名(形参列表) {
方法体;
}
8.2 构造器的说明和特点
- 构造器的修饰符可以默认
- 构造器没有返回值
- 方法名 和类名字 必须一样
- 参数列表 和 成员方法一样的规则
- 构造器的调用 由系统完成
即在创建对象时,系统会自动的调用该类的构造器完成对对象的初始化
8.3 案例演示
Constructor01.java
public class Constructor01 {public static void main(String[] args) {// 当我们new 一个对象时,直接通过构造器指定名字和年龄Person p1 = new Person("Smith", 80);System.out.println(p1.name);System.out.println(p1.age);}}// 在创建人类的对象时,就直接指定这个对象的年龄和姓名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;}}
8.4 构造器的注意事项和使用细节
- 一个类可以定义多个不同的构造器,即构造器重载
比如:我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要指定年龄 - 构造器名和类名要相同
- 构造器没有返回值
- 构造器是完成对象的初始化,不是创建对象
- 在创建时,系统会自动的调用该类的构造方法
- 如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造方法),比如 Person(){},使用 javap指令 反编译看看
- 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下,即:Person(){}
ConstructorDetail.java
public class ConstructorDetail {public static void main(String[] args) {Person p1 = new Person("King", 40); // 调用第一个构造器System.out.println(p1.name + "\t" + p1.age);Person p2 = new Person("Ling"); // 调用第二个构造器System.out.println(p2.name + "\t" + p2.age);}}class Person {String name;int age; // 默认0// 第一个构造器public Person(String pName, int pAge) {name = pName;age = pAge;}// 第二个构造器,只指定人名public Person(String pName) {name = pName;}}
⏱ 小练习
在前面定义的Person类中添加两个构造器:
第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
第二个带pName和pAge两个参数的构造器:使得每次创建Person对象的同时初始化对象的age属性值和name属性值。分别使用不同 的构造器,创建对象。
8.5 对象创建流程分析
class Person{int age = 90;String name;Person(String n, int a) {name = n;age = a;}}Person p = new Person("小倩", 20);

流程分析:
- 加载Person类信息(Person.class),只会加载一次
- 在堆中分配空间(地址)
- 完成对象初始化
- 默认初始化
- 显式初始化
- 构造器初始化
- 把对象在堆中的地址返回给p(p是对象名,也可以理解成对象引用)
九、this 关键字
9.1 基本介绍
当对象被创建时,默认隐含一个this方法,this指向对象本身。可用于在对象的成员方法中调用对象的属性(即全局变量),以此和成员变量同名的局部变量区分开来。
9.2 案例演示
This01.java
public class This01 {public static void main(String[] args) {Dog dog1 = new Dog("小花", 5);dog1.info();}}class Dog {String name;int age;// public Dog(String dName, int dAge) { // 构造器// name = dName;// age = dAge;// }// 如果我们构造器的形参,能够直接写成属性名,就更好了public Dog(String name, int age) { // 构造器// this.name 就是当前对象的属性namethis.name = name;// this.name 就是当前对象的属性agethis.age = age; // 如果不加this,前后两个age都指的是局部变量形参age}public void info() { // 成员方法System.out.println(name + "\t" + age + "\t");}}
9.3 this 的注意事项和使用细节
- this 关键字可以用来访问本类的属性、方法、构造器
- this 用于区分当前类的属性和局部变量
- 访问成员方法的语句:this.方法名(参数列表)
- 访问构造器语法:this(参数列表);注意只能在构造器中使用,即只能 构造器中访问其他构造器
且 对 this 的调用必须是构造器中的第一个语句
- this 不能再类定义外部使用,只能在类定义的方法中使用
ThisDetail.java
public class ThisDetail {public static void main(String[] args) {T t1 = new T();t1.f2();}}class T {public void f1() {System.out.println("f1() 方法...");}public void f2() {System.out.println("f2() 方法...");// 调用本类的 f1// 第一种方式f1();// 第二种方式this.f1();}}
⏱ 小练习
定义Person类,里面有 name、age 属性,并提供 compareTo 比较方法,用于判断是否和另一个人相等,名字和年龄完全一样,就返回 true ,否则返回 false
public class ThisExercise {public static void main(String[] args) {Person p1 = new Person("小明", 12);Person p2 = new Person("小明", 15);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) {return this.name.equals(p.name) && this.age == p.age; // 重点理解}}
十、本章作业
编写类 A01,定义方法 max,实现求某个 double 数组的最大值,并返回
编写类 A02,定义方法 find,实现查找某字符串数组中的元素查找,并返回索引,如果找不到,返回-1
编写类 Book,定义方法 updatePrice,实现更改某本书的,具体:如果价格 > 150,则改为150,如果价格 > 100,则改为100,否则不更改。
编写类 A03,实现数组的复制功能copyArr,输入旧数组,返回一个新数组,元素和旧数组一样
定义一个圆类 Circle,定义属性:半径,提供显示周长功能的方法,提供显示圆面积的方法
编程创建一个 Cale 计算类,在其中定义2个变量表示两个操作数,定义四个方法实现求和、差、乘、商(要求除数为0的时候,要给出提示) 并创建两个对象,分别测试
设计一个Dog类,有名字、颜色和年龄属性,定义输出方法 show() 显示其信息。并创建对象,进行测试、【提示 this.属性】
- 给定一个Java程序的代码如下所示,则编译运行后,输出结果是
public class Test {
int count = 9;
public void count1() {
count = 10;
System.out.println( “count1=” + count );
}
public void count2() {
System.out.println(“count1=” + count++);
}
public static void main(String args[]) {
new Test().count1(); // 匿名对象 输出 count1=10
Test t1 = new Test(); // 新 t1对象
t1.count2(); // 输出 count1=9
t1.count2(); // 输出 count1 = 10
}
定义 Music类,里面有音乐名 name、音乐时长 times 属性,并有播放 play 功能和返回本身属性信息的功能方法 getlnfo
试写出以下代码的运行结果
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
}
}
在测试方法中,调用method方法,代码如下,编译正确!试写出method方法的定义形式,调用语句为:System.out.printin( method( method( 10.0, 20.0), 100);
答案:[ public ] double method(double num1, double num2) { }创建一个Employee类,属性有(名字,性别,年龄,职位,薪水),提供3个构造方法,可以初始化
- (名字,性别,年龄,职位,薪水)
- (名字,性别,年龄)
- (职位,薪水)
要求充分复用构造器
public class Homework12 {public static void main(String[] args) {}}class Employee {String name;char gender;int age;String job;double salary;public Employee(String job, double salary) {this.job = job;this.salary = salary;}public Employee(String name, char gender, int age) {this.name = name;this.gender = gender;this.age = age;}public Employee(String job, double salary, String name, char gender, int age) {this(name, gender, age);// this(job, salary); 不能再使用,因为不在首句this.job = job;this.salary = salary;}}
- 将对象作为参数传递给方法
题目要求:
- 定义一个 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()方法,调用完毕后输出当前半径值。程序运行结果如国所示

public class Homework13 {public static void main(String[] args) {Circle c = new Circle();PassObject obj = new PassObject();obj.printAreas(c,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);// c.radius = i;System.out.println(i + "\t" + c.findArea());}}}
- 扩展题,学员自己做.
有个人Tom,设计他的成员变量,成员方法,可以电脑猜拳。
电脑每次都会随机生成0,1,2
0 表示石头 1 表示剪刀 2 表示布,并要可以显示Tom的输赢次数(清单)
import java.util.Random;import java.util.Scanner;public class Homework14 {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.println("请输入你要出的拳头 (0-拳头, 1-剪刀, 2-布):");int num = scanner.nextInt();t.setTomGuessNum(num);int tomGuess = t.getTomGuessNum();arr1[i][j + 1] = tomGuess;// 获取电脑出的拳int comGuess = t.computerNum();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" + comGuess + "\t" + isWin);System.out.println("========================================");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 void showInfo() {//.....}/*** 电脑随机生成猜拳的数字的方法* @return*/public int computerNum() {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 玩家赢返回true,否则返回false*/public String vsComputer() {if (tomGuessNum == comGuessNum) {return "平手";} else if ((tomGuessNum + 1) % 3 == comGuessNum) {return "你赢了";} else {return "你输了";}}/*** 记录玩家赢的次数*/public int winCount(String s) {count++;if (s.equals("你赢了")) {winCountNum++;}return winCountNum;}}
学习参考(致谢):
- B站 @程序员鱼皮 Java学习一条龙
- B站 @韩顺平 零基础30天学会Java
