Java学习(二)– 面向对象基础

https://www.bilibili.com/video/BV1fh411y7R8/?spm_id_from=333.999.0.0&vd_source=594d36a0860080a36fe599e0b84e5fb2

一、类与对象

1.1 问题引出

  1. 类是抽象的,概念的,代表一类事物,比如人类,猫类.., 即它是数据类型.

  2. 对象是具体的,实际的,代表一个具体事物, 即 是实例.

  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
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
65
66
67
68
69
70
71
72
73
74

public class Object01 {

//编写一个main方法
public static void main(String[] args) {

/*
张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。
还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,
就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,
则显示 张老太没有这只猫猫。
*/
//单独变量来解决 => 不利于数据的管理(你把一只猫的信息拆解)
//第1只猫信息

// String cat1Name = "小白";
// int cat1Age = 3;
// String cat1Color = "白色";

// //第2只猫信息
// String cat2Name = "小花";
// int cat2Age = 100;
// String cat2Color = "花色";

//数组 ===>(1)数据类型体现不出来(2) 只能通过[下标]获取信息,造成变量名字和内容
// 的对应关系不明确(3) 不能体现猫的行为
//第1只猫信息

// String[] cat1 = {"小白", "3", "白色"};
// String[] cat2 = {"小花", "100", "花色"};


//使用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;
//创建了第二只猫,并赋给 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; //体重

//行为

}

1.2 对象在内存中的存在形式 (※)

创建一个新的对象的时候相当于在堆区中开辟了一块空间,

在栈区中的变量名指向这个堆区的地址,

如果属性是 基本数据类型 则直接存储在堆区中,

如果是 引用类型 则存储在堆区中的是一个在方法区的地址, 真正的内容存储在方法区中

对象在内存中的存在形式


1.3 属性

  1. 属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;

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

  3. 属性如果不赋值,有默认值,规则和数组一致。具体说:

    int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null

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 PropertiesDetail {

//编写一个main方法
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 null

System.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;
}

1.3.1 创建对象

  1. 先声明再创建

Cat cat ; //声明对象 cat

cat = new Cat(); //创建

  1. 直接创建

Cat cat = new Cat();

1.3.2 访问属性

基本语法

​ 对象名.属性名;

案例演示赋值和输出

cat.name ;

cat.age;

cat.color;


1.4 类和对象内存分配机制 (※)

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

  2. 堆: 存放对象 (Cat cat , 数组等)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Object03 { 

//编写一个main方法
public static void main(String[] args) {

Person p1=new Person();
p1.age=10;
p1.name="小明";
Person p2=p1; //把p1 赋给了 p2 , 让p2指向p1
System.out.println(p2.age); // 10




}
}

class Person {
String name;
int age;
}
  1. 先加载 Person 类信息 (属性和方法信息, 只会加载一次)

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

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

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

跟数组的存储机制比较像, 可以类比理解, 不要强行记忆

类和对象内存分配机制

如果是 null 的话就什么都不指向

b = null


二、成员方法

2.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
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
65
66
67
68
69
70
71

public class Method01 {

//编写一个main方法
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;
}
}

2.2 方法调用机制 (※)

每个方法调用的时候会开辟一个新的栈空间,

除非使用 return , 否则每个占空间相互独立互不干扰

一个方法结束后占空间会销毁

程序结束后 main 栈空间销毁

image-20230129220851971


2.3 成员方法的好处及使用

2.3.1 好处

  1. 提高代码的复用性

  2. 可以将实现的细节封装起来,然后供其他用户来调用即可

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

public class Method02 {

//编写一个main方法
public static void main(String[] args) {

//请遍历一个数组 , 输出数组的各个元素值
int [][] map = {{0,0,1},{1,1,1},{1,1,3}};

//使用方法完成输出, 创建MyTools对象
MyTools tool = new MyTools();

//遍历map数组
//传统的解决方式就是直接遍历
// for(int i = 0; i < map.length; i++) {
// for(int j = 0; j < map[i].length; j++) {
// System.out.print(map[i][j] + "\t");
// }
// System.out.println();
// }
//使用方法
tool.printArr(map);

//....
//
//要求再次遍历map数组
// for(int i = 0; i < map.length; i++) {
// for(int j = 0; j < map[i].length; j++) {
// System.out.print(map[i][j] + "\t");
// }
// System.out.println();
// }
tool.printArr(map);


//...再次遍历
//
// for(int i = 0; i < map.length; i++) {
// for(int j = 0; j < map[i].length; j++) {
// System.out.print(map[i][j] + "\t");
// }
// System.out.println();
// }
tool.printArr(map);

}
}

//把输出的功能,写到一个类的方法中,然后调用该方法即可
class MyTools {
//方法,接收一个二维数组

public void printArr(int[][] map) {
System.out.println("=======");
//对传入的map数组进行遍历输出
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();
}
}
}

2.3.2 成员方法的定义

访问修饰符 返回数据类型 方法名(形参列表..) {//方法体

​ 语句;

​ return 返回值;

}

  1. 形参列表:表示成员方法输入 cal(int n) , getSum(int num1, int num2)

  2. 返回数据类型:表示成员方法输出, void 表示没有返回值

  3. 方法主体:表示为了实现某一功能代码块

  4. return 语句不是必须的。

2.3.3 方法使用细节

  1. 一个方法最多有一个返回值 [思考,如何返回多个结果 返回数组 ]

  2. 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)

  3. 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值;

    而且要求返回值类型必须和 return 的值类型一致或兼容

  4. 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;

  5. 方法名遵循驼峰命名法,最好见名知义,表达出该功能的意思即可, 比如 得到两个数的和 getSum, 开发中按照规范

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
65
66
67
68
69
70
71
72
73
74
75

public class MethodDetail {


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]);


//细节: 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型 的参数
byte b1 = 1;
byte b2 = 2;
a.getSumAndSub(b1, b2);//byte -> int
//a.getSumAndSub(1.1, 1.8);//double ->int(×)
//细节: 实参和形参的类型要一致或兼容、个数、顺序必须一致

//a.getSumAndSub(100);//× 个数不一致
a.f3("tom", 10); //ok
//a.f3(100, "jack"); // 实际参数和形式参数顺序不对

}
}

class AA {

//细节: 方法不能嵌套定义
public void f4() {
//错误
// public void f5() {

// }
}

public void f3(String str, int n) {

}

//1. 一个方法最多有一个返回值 [思考,如何返回多个结果 返回数组 ]
public int[] getSumAndSub(int n1, int n2) {

int[] resArr = new int[2]; //
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
//2. 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
// 具体看 getSumAndSub
//

//3. 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值;
// 而且要求返回值类型必须和return的值类型一致或兼容
public double f1() {

double d1 = 1.1 * 3;
int n = 100;
return n; // int ->double
//return d1; //ok? double -> int
}

//如果方法是void,则方法体中可以没有return语句,或者 只写 return ;
//老韩提示:在实际工作中,我们的方法都是为了完成某个功能,所以方法名要有一定含义
//,最好是见名知意
public void f2() {

System.out.println("hello1");
System.out.println("hello1");
System.out.println("hello1");
int n = 10;
//return ;
}

}

2.3.4 方法调用细节

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 MethodDetail02 { 

//编写一个main方法
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()被执行");
}
}

2.4 练习

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 MethodExercise01 {

//编写一个main方法
public static void main(String[] args) {

AA a = new AA();
// if(a.isOdd(2)) {//T , 这样的写法以后会看到很多
// System.out.println("是奇数");
// } else {
// System.out.println("是偶数");
// }
//
//
// 使用print方法
a.print(4, 4, '#');



}
}
//编写类AA ,有一个方法:判断一个数是奇数odd还是偶数, 返回boolean
class AA {
//思路
//1. 方法的返回类型 boolean
//2. 方法的名字 isOdd
//3. 方法的形参 (int num)
//4. 方法体 , 判断

public boolean isOdd(int num) {
// if(num % 2 != 0) {
// return true;
// } else {
// return false;
// }

//return num % 2 != 0 ? true; false;
//
return num % 2 != 0;
}

//根据行、列、字符打印 对应行数和列数的字符,
//比如:行:4,列:4,字符#,则打印相应的效果
/*
####
####
####
####
*/
//思路
//1. 方法的返回类型 void
//2. 方法的名字 print
//3. 方法的形参 (int row, int col, char c)
//4. 方法体 , 循环
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(); //换行
}
}
}

三、成员方法传参机制 (※)

3.1 基本数据类型传参机制

基本数据类型,传递的是值 (值拷贝),形参的任何改变不影响实参

main 方法中的两个数在 main 栈区

swap 方法中的两个数在 swap 栈区

两个栈区互补干扰, 既然是基本数据类型, 那么数据都存储在栈区中, 没有指向堆区中的地址

所以在没有 return 的情况下两个栈区互补干扰, 不能做到数据交换的目的

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 MethodParameter01 {

//编写一个main方法
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
}
}

3.2 引用数据类型传参机制

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

引用类型存储在堆区中, 变量存储的是这个堆区的地址

main 方法与 test100 方法的栈区都存储的是堆区的地址, 这个地址指向一个地方, test100 中的修改可以影响 main 的输出

而在 test200 中, 新建了一个对象, 这就说明现在堆区中有两个对象

而新建的对象在 test200 的栈区中存储的指向堆区的地址是新建的对象的地址, 不会与原来的地方产生关联, 所以不会影响 main 的输出

p = null 指向为空, 啥都不影响

直接通过 test200 方法中调用属性 p.age 当然会直接对堆区的对象值进行修改

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

public class MethodParameter02 {
//编写一个main方法
public static void main(String[] args) {
//测试
B b = new B();
int[] arr = {1, 2, 3};
b.test100(arr);//调用方法
System.out.println(" main的 arr数组 ");
//遍历数组
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();

//测试
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);//10000
}
}
class Person {
String name;
int age;
}
class B {
public void test200(Person p) {
// 直接通过 test200 方法中调用属性 p.age 当然会直接对堆区的对象值进行修改
//p.age = 10000; //修改对象属性
//思考
p = new Person();
p.name = "tom";
p.age = 99;
//思考
// p = null 指向为空, 啥都不影响
//p = null;
}

//B类中编写一个方法test100,
//可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化
public void test100(int[] arr) {
arr[0] = 200;//修改元素
//遍历数组
System.out.println(" test100的 arr数组 ");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();
}
}

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46

public class MethodExercise02 {

//编写一个main方法
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 {
//编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。克隆对象,
//注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
//
//编写方法的思路
//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;
}
}

3.4 方法递归调用 (※)

3.4.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
28
29
30
31
32
33

public class Recursion01 {

//编写一个main方法
public static void main(String[] args) {

T t1 = new T();
t1.test(4);//输出什么? n=2 n=3 n=4
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;
}
}


}

3.4.2 递归时的内存机制

  1. 每次执行一个方法就是创建一个新的受保护的独立栈空间

  2. 每个方法中, 即每个栈空间中的局部变量是独立的, 不会互相影响, 比如下图的 n

  3. 当然如果这些方法中使用的是引用类型变量 (数组, 对象等), 就会共享这个引用类型变量的数据

  4. 递归时必须向退出递归的条件逼近, 否则就是无限递归

  5. 当一个方法执行完毕之后, 或者遇到 return 就会返回, 遵守谁调用就返回给谁的原则

    就是说当方法执行完毕或者返回时, 这个方法就完事了, 销毁

递归就像是一个栈, 如下图所示, 从 main 方法开始一直调用 test 方法, 不断开辟栈空间, 然后从最后开辟的栈空间开始逐渐返回, 返回一个销毁一个, 最后回到 main

递归时的内存机制

3.4.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
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
65
66
67
68
69
70
71

public class RecursionExercise01 {

//编写一个main方法
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);
}
//
//桃子问题
int day = 0;
int peachNum = t1.peach(day);
if(peachNum != -1) {
System.out.println("第 " + day + "天有" + peachNum + "个桃子");
}


}
}

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;
}
}

/*
猴子吃桃子问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,
想再吃时(即还没吃),发现只有1个桃子了。问题:最初共多少个桃子?

思路分析 逆推
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天,只有1个桃
return 1;
} else if ( day >= 1 && day <=9 ) {
return (peach(day + 1) + 1) * 2;//规则,自己要想
} else {
System.out.println("day在1-10");
return -1;
}
}

}

3.4.4 迷宫问题

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

public class MiGong {

//编写一个main方法
public static void main(String[] args) {

//思路
//1. 先创建迷宫,用二维数组表示 int[][] map = new int[8][7];
//2. 先规定 map 数组的元素值: 0 表示可以走 1 表示障碍物

int[][] map = new int[8][7];
//3. 将最上面的一行和最下面的一行,全部设置为1
for(int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//4.将最右面的一列和最左面的一列,全部设置为1
for(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; //测试回溯
// map[2][1] = 1;
// map[2][2] = 1;
// map[1][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 t1 = new T();
//下右上左
t1.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, 3
return 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, 3
return false;
}
}
}
}



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
27
28
29
30

public class HanoiTower {

//编写一个main方法
public static void main(String[] args) {

Tower tower = new Tower();
tower.move(64, '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);
}
}
}

四、方法重载

Java 中允许同一个类中,多个同名方法的存在,但要求 形参列表不一致

4.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
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

public class OverLoad01 {

//编写一个main方法
public static void main(String[] args) {

// System.out.println(100);
// System.out.println("hello,world");
// System.out.println('h');
// System.out.println(1.1);
// System.out.println(true);
//
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 void calculate(int n1, int n2) {
// System.out.println("calculate(int n1, int n2) 被调用");
// int res = 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;
}

}

4.2 练习

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

public class OverLoadExercise {

//编写一个main方法
public static void main(String[] args) {

//在主类的main ()方法中分别用参数区别调用三个方法
Methods method = new Methods();
method.m(10);//100
method.m(10, 20);//200
method.m("韩顺平教育 hello");//字符串信息

//测试
System.out.println(method.max(10, 24)); // 24
System.out.println(method.max(10.0, 21.4)); // 21.4
System.out.println(method.max(10.0, 1.4, 30.0)); // 30.0
}
}

/*
编写程序,类Methods中定义三个重载方法并调用。方法名为m。
三个方法分别接收一个int参数、两个int参数、一个字符串参数。分别执行平方运算并输出结果,
相乘并输出结果,输出字符串信息。在主类的main ()方法中分别用参数区别调用三个方法


定义三个重载方法max(),第一个方法,返回两个int值中的最大值,
第二个方法,返回两个double值中的最大值,第三个方法,
返回三个double值中的最大值,并分别调用三个方法
*/
class Methods {

//分析
//1 方法名 max
//2 形参 (int,int)
//3.int
public int max(int n1, int n2) {
return n1 > n2 ? n1 : n2;
}

//分析
//1 方法名 max
//2 形参 (double,double)
//3.double
public double max(double n1, double n2) {
return n1 > n2 ? n1 : n2;
}

//分析
//1 方法名 max
//2 形参 (double,double,double)
//3.double
public double max(double n1, double n2, double n3) {

System.out.println("max(double n1, double n2, double n3)");
//求出n1 和 n2的最大值
double max1 = n1 > n2 ? n1 : n2;
return max1 > n3 ? max1 : n3;
}

public double max(double n1, double n2, int n3) {

System.out.println("max(double n1, double n2, int n3)");
//求出n1 和 n2的最大值
double max1 = n1 > n2 ? n1 : n2;
return max1 > n3 ? max1 : n3;
}


//分析
//1 方法名 m
//2 形参 (int)
//3.void
public void m(int n) {
System.out.println("平方=" + (n * n));
}

//1 方法名 m
//2 形参 (int, int)
//3.void
public void m(int n1, int n2) {
System.out.println("相乘=" + (n1 * n2));
}

//1 方法名 m
//2 形参 (String)
//3.void
public void m(String str) {
System.out.println("传入的str=" + str);
}
}

五、可变参数

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
36
37
38
39

public class VarParameter01 {

//编写一个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;
}
}

5.1 使用细节

  1. 可变参数实参可以为 0 或 任意多个
  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
27
28

public class VarParameterDetail {

//编写一个main方法
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);
}

//细节: 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
public void f2(String str, double... nums) {

}
//细节: 一个形参列表中只能出现一个可变参数
//下面的写法是错的.
// public void f3(int... nums1, double... nums2) {

// }
}

5.2 练习

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

public class VarParameterExercise {

//编写一个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;
}
}

六、作用域

  1. 在java编程中, 主要的变量就是属性 (成员变量) 和局部变量。
  2. 我们说的局部变量一般是指在成员方法中定义的变量。 [举例Cat类: cry]
  3. java中作用域的分类
    全局变量:也就是属性,作用域为整个类体Cat类: cry eat等方法使用属性
    局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中
  4. 全局变量 (属性) 可以不赋值,直接使用,因为有默认值 (与数组默认值一样),局部变量必须赋值后才能使用,因为没有默认值
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

public class VarScope {

//编写一个main方法
public static void main(String[] args) {
}
}
class Cat {
//全局变量:也就是属性,作用域为整个类体 Cat类:cry eat 等方法使用属性
//属性在定义时,可以直接赋值
int age = 10; //指定的值是 10

//全局变量(属性)可以不赋值,直接使用,因为有默认值,
double weight; //默认值是0.0

public void hi() {
//局部变量必须赋值后,才能使用,因为没有默认值
int num = 1;
String address = "北京的猫";
System.out.println("num=" + num);
System.out.println("address=" + address);
System.out.println("weight=" + weight);//属性
}

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);//错误
}
}
  1. 属性和局部变量可以重名,访问时遵循就近原则。

  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
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

public class VarScopeDetail {

//编写一个main方法
public static void main(String[] args) {
Person p1 = new Person();
/*
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。
局部变量,生命周期较短,伴随着它的代码块的执行而创建,
伴随着代码块的结束而销毁。即在一次方法调用过程中
*/
//p1.say();//当执行say方法时,say方法的局部变量比如name,会创建,当say执行完毕后
//name局部变量就销毁,但是属性(全局变量)仍然可以使用
//
T t1 = new T();
t1.test(); //第1种跨类访问对象属性的方式

t1.test2(p1);//第2种跨类访问对象属性的方式

}
}

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 = "king";
System.out.println("say() name=" + name);
}

public void hi() {
String address = "北京";
//String address = "上海";//错误,重复定义变量
String name = "hsp";//可以
}
}

七、构造器

基本语法:

​ [修饰符] 方法名(形参列表){

​ 方法体;

​ }

  1. 构造器的修饰符可以默认, 也可以是 public protected private

  2. 构造器没有返回值

  3. 方法名 和类名字必须一样

  4. 参数列表 和 成员方法一样的规则

  5. 构造器的调用, 由系统完成

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

  1. 方法名和类名相同

  2. 没有返回值

  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

public class Constructor01 {

//编写一个main方法
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;
}
}

7.1 使用细节

  1. 一个类可以定义多个不同的构造器,即构造器重载
    比如: 我们可以再给Person类定义个构造器用来创建对象的时候,只指定人名,不需要指定年龄
  2. 构造器名和类名要相同
  3. 构造器没有返回值
  4. 构造器是完成对象的初始化,并不是创建对象
  5. 在创建对象时,系统自动的调用该类的构造方法
  6. 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如Dog (){},使用javap指令反编译看看
  7. 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下
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

public class ConstructorDetail {

//编写一个main方法
public static void main(String[] args) {
Person p1 = new Person("king", 40);//第1个构造器
Person p2 = new Person("tom");//第2个构造器

Dog dog1 = new Dog();//使用的是默认的无参构造器

}
}
class Dog {
//如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器)
//使用javap指令 反编译看看
/*
默认构造器
Dog() {

}
*/
//一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,
//除非显式的定义一下,即: Dog(){} 写 (这点很重要)
//
public Dog(String dName) {
//...
}
Dog() { //显式的定义一下 无参构造器

}
}

class Person {
String name;
int age;//默认0
//第1个构造器
public Person(String pName, int pAge) {
name = pName;
age = pAge;
}
//第2个构造器, 只指定人名,不需要指定年龄
public Person(String pName) {
name = pName;
}
}

7.2 练习

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

public class ConstructorExercise {

//编写一个main方法
public static void main(String[] args) {
Person p1 = new Person();//无参构造器

//下面输出 name = null, age = 18
System.out.println("p1的信息 name=" + p1.name + " age=" + p1.age);

Person p2 = new Person("scott", 50);
//下面输出 name = scott, age = 50
System.out.println("p2的信息 name=" + p2.name + " age=" + p2.age);

}
}

/**
* 在前面定义的Person类中添加两个构造器:
* 第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
* 第二个带pName和pAge两个参数的构造器:
* 使得每次创建Person对象的同时初始化对象的age属性值和name属性值。
* 分别使用不同的构造器,创建对象.
*/

class Person {
String name;//默认值 null
int age;//默认 0
//第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
public Person() {
age = 18;//
}
//第二个带pName和pAge两个参数的构造器
public Person(String pName, int pAge) {
name = pName;
age = pAge;
}
}

八、对象创建流程分析 (面试)

  1. 在方法区中加载 Person 类信息 (Person.class), 只会加载一次
  2. 在堆区中为新 new 的对象分配空间, 具有一个内存地址
  3. 先进行属性的默认初始化, age = 0, name = null
  4. 然后进行属性的显示初始化, age = 90, name = null
  5. 最后进行构造器的初始化, age = 20, name = "小倩"
  6. 将对象在堆区中的地址返回给变量 p, 这个 p 是对象名, 也可以说是对象的引用, 真正的对象是堆区中开辟的那块空间, p 只是一个代理

分析流程


十、this

10.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

public class This01 {

//编写一个main方法
public static void main(String[] args) {

Dog dog1 = new Dog("大壮", 3);
System.out.println("dog1的hashcode=" + dog1.hashCode());
//dog1调用了 info()方法
dog1.info();

System.out.println("============");
Dog dog2 = new Dog("大黄", 2);
System.out.println("dog2的hashcode=" + dog2.hashCode());
dog2.info();
}
}

class Dog{ //类

String name;
int age;
// public Dog(String dName, int dAge){//构造器
// name = dName;
// age = dAge;
// }
//如果我们构造器的形参,能够直接写成属性名,就更好了
//但是出现了一个问题,根据变量的作用域原则
//构造器的name 是局部变量,而不是属性
//构造器的age 是局部变量,而不是属性
//==> 引出this关键字来解决
public Dog(String name, int age){//构造器
//this.name 就是当前对象的属性name
this.name = name;
//this.age 就是当前对象的属性age
this.age = age;
System.out.println("this.hashCode=" + this.hashCode());
}

public void info(){//成员方法,输出属性x信息
System.out.println("this.hashCode=" + this.hashCode());
System.out.println(name + "\t" + age + "\t");
}
}


10.2 深入理解 this

this 就是每个对象创建之后赋予的一个隐藏属性, 哪个对象调用, this 就代表哪个对象 (字面意思, this 的值就是这个对象在堆区的地址)

this 在内存中的机制


10.3 使用细节

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

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

  3. 访问成员方法的语法:this.方法名(参数列表);

  4. 访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)

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


10.4 练习

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

public class TestPerson {

//编写一个main方法
public static void main(String[] args) {

Person p1 = new Person("mary", 20);
Person p2 = new Person("mary", 20);

System.out.println("p1和p2比较的结果=" + p1.compareTo(p2));
}
}

/*
定义Person类,里面有name、age属性,并提供compareTo比较方法,
用于判断是否和另一个人相等,提供测试类TestPerson用于测试,
名字和年龄完全一样,就返回true, 否则返回false

*/
class Person {
String name;
int age;
//构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//compareTo比较方法
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;
}
}

十一、练习

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

public class Homework01 {

//编写一个main方法
public static void main(String[] args) {
A01 a01 = new A01();
double[] arr = {1, 1.4, -1.3, 89.8, 123.8 , 66}; //;{};
Double res = a01.max(arr);
if(res != null) {
System.out.println("arr的最大值=" + res);
} else {
System.out.println("arr的输入有误, 数组不能为null, 或者{}");
}
}
}
/*
编写类A01,定义方法max,实现求某个double数组的最大值,并返回

思路分析
1. 类名 A01
2. 方法名 max
3. 形参 (double[])
4. 返回值 double

先完成正常业务,然后再考虑代码健壮性
*/
class A01 {
public Double max(double[] arr) {
//老韩先判断arr是否为null,然后再判断 length 是否>0
if( arr!= null && arr.length > 0 ) {

//保证arr至少有一个元素
double max = arr[0];//假定第一个元素就是最大值
for(int i = 1; i < arr.length; i++) {
if(max < arr[i]) {
max = arr[i];
}
}

return max;//double
} else {
return null;
}
}
}
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 Homework02 {

//编写一个main方法
public static void main(String[] args) {

String[] strs = {"jack", "tom", "mary","milan"};
A02 a02 = new A02();
int index = a02.find("milan", strs);
System.out.println("查找的index=" + index);
}
}

//编写类A02,定义方法find,实现查找某字符串是否在字符串数组中,
//并返回索引,如果找不到,返回-1
//分析
//1. 类名 A02
//2. 方法名 find
//3. 返回值 int
//4. 形参 (String , String[])
//
//自己补充代码健壮性
class A02 {

public int find(String findStr, String[] strs) {
//直接遍历字符串数组,如果找到,则返回索引
for(int i = 0; i < strs.length; i++) {
if(findStr.equals(strs[i])) {
return i;
}
}
//如果没有,就返回-1
return -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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

public class Homework03 {

//编写一个main方法
public static void main(String[] args) {

//测试
Book book = new Book("笑傲江湖", 300);
book.info();
book.updatePrice();//更新价格
book.info();
}
}
/*
编写类Book, 定义方法updatePrice,实现更改某本书的价格,
具体:如果价格>150,则更改为150,如果价格>100,更改为100,否则不变

分析
1. 类名 Book
2. 属性 price, name
3. 方法名 updatePrice
4. 形参 ()
5. 返回值 void
6. 提供一个构造器
*/

class Book {
String name;
double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public void updatePrice() {
//如果方法中,没有 price 局部变量, this.price 等价 price
if(price > 150) {
price = 150;
} else if(price > 100 ) {
price = 100;
}
}

//显示书籍情况
public void info() {
System.out.println("书名=" + this.name + " 价格=" + this.price);
}
}
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 Homework04 {

//编写一个main方法
public static void main(String[] args) {
int[] oldArr = {10, 30, 50};
A03 a03 = new A03();
int[] newArr = a03.copyArr(oldArr);
//遍历newArr,验证
System.out.println("==返回的newArr元素情况==");
for(int i = 0; i < newArr.length; i++) {
System.out.print(newArr[i] + "\t");
}
}
}

/*
编写类A03, 实现数组的复制功能copyArr,输入旧数组,返回一个新数组,元素和旧数组一样
*/
class A03 {
public int[] copyArr(int[] oldArr) {
//在堆中,创建一个长度为 oldArr.length 数组
int[] newArr = new int[oldArr.length];
//遍历 oldArr,将元素拷贝到 newArr
for(int i = 0; i < oldArr.length; i++) {
newArr[i] = oldArr[i];
}

return newArr;

}
}
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

public class Homework05 {

//编写一个main方法
public static void main(String[] args) {
Circle circle = new Circle(3);
System.out.println("面积=" + circle.area());
System.out.println("周长=" + circle.len());
}
}
/*
定义一个圆类Circle, 定义属性:半径,提供显示圆周长功能的方法, 提供显示圆面积的方法
*/
class Circle {
double radius;

public Circle(double radius) {
this.radius = radius;
}

public double area() { //面积
return Math.PI * radius * radius;
}

public double len() { //周长
return 2 * Math.PI * radius;
}
}
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

public class Homework06 {

//编写一个main方法
public static void main(String[] args) {
Cale cale = new Cale(2, 10);
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);
}
}
}

/*
编程创建一个Cale计算类,在其中定义2个变量表示两个操作数,
定义四个方法实现求和、差、乘、商(要求除数为0的话,要提示) 并创建两个对象,分别测试
*/

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("num2 不能为0");
return null;
} else {
return num1 / num2;
}
}
}
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
public class Homework09 { 

//编写一个main方法
public static void main(String[] args) {
Music music = new Music("笑傲江湖", 300);
music.play();
System.out.println(music.getInfo());
}
}
/*
义Music类,里面有音乐名name、音乐时长times属性,
并有播放play功能和返回本身属性信息的功能方法getInfo
*/
class Music {
String name;
int times;
public Music(String name, int times) {
this.name = name;
this.times = times;
}
//播放play功能
public void play() {
System.out.println("音乐 " + name + " 正在播放中.... 时长为" + times + "秒");
}
//返回本身属性信息的功能方法getInfo
public String getInfo() {
return "音乐 " + name + " 播放时间为" + times;
}

}
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 Homework10 {

//编写一个main方法
public static void main(String[] args) {
}
}

class Demo{
int i=100;
public void m(){
int j=i++;
System.out.println("i="+i);
System.out.println("j="+j);
}
}
class Test{
public static void main(String[] args){//运行它
Demo d1=new Demo();
Demo d2 = d1;
d2.m();
System.out.println(d1.i);
System.out.println(d2.i);
}
}

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

public class Homework12 {

//编写一个main方法
public static void main(String[] args) {
}
}
/*
创建一个Employee类,
属性有(名字,性别,年龄,职位,薪水), 提供3个构造方法,可以初始化
(1) (名字,性别,年龄,职位,薪水),
(2) (名字,性别,年龄) (3) (职位,薪水), 要求充分复用构造器
*/
class Employee {
//名字,性别,年龄,职位,薪水
String name;
char gender;
int age;
String job;
double sal;
//因为要求可以复用构造器,因此老韩先写属性少的构造器
//职位,薪水
public Employee(String job, double sal) {
this.job = job;
this.sal = sal;
}
//名字,性别,年龄
public Employee(String name, char gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//名字,性别,年龄,职位,薪水
public Employee(String job, double sal, String name, char gender, int age) {
this(name, gender, age);//使用到 前面的 构造器
this.job = job;
this.sal = sal;
}


}
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

public class Homework13 {

//编写一个main方法
public static void main(String[] args) {

Circle c = new Circle();
PassObject po = new PassObject();
po.printAreas(c, 5);
}
}

/*
题目要求:
(1) 定义一个Circle类,包含一个double型的radius属性代表圆的半径,findArea()方法返回圆的面积。
(2) 定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义如下:
public void printAreas(Circle c, int times) //方法签名/声明
(3) 在printAreas方法中打印输出1到times之间的每个整数半径值,以及对应的面积。例如,times为5,则输出半径1,2,3,4,5,以及对应的圆面积。
(4) 在main方法中调用printAreas()方法,调用完毕后输出当前半径值。程序运行结果如图所示

*/
class Circle { //类
double radius;//半径
public Circle() { //无参构造器

}
public Circle(double radius) {
this.radius = 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(int i = 1; i <= times; i++) {//输出1到times之间的每个整数半径值
c.setRadius(i) ; //修改c 对象的半径值
System.out.println((double)i + "\t" + c.findArea());
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class Test { //公有类
int count = 9; //属性
public void count1() { //Test类的成员方法
count=10;//这个count就是属性 改成 10
System.out.println("count1=" + count); //10
}
public void count2() { //Test类的成员方法
System.out.println("count1=" + count++);
}

//这是Test类的main方法, 任何一个类,都可有main
public static void main(String args[]) {
//老韩解读
//1. new Test() 是匿名对象, 匿名对象使用后,就不能使用
//2. new Test().count1() 创建好匿名对象后, 就调用count1()
new Test().count1();

Test t1= new Test();
t1.count2();
t1.count2();
}
}