221-250方法递归调用/方法重载/可变参数/作用域/构造方法/this关键字
一、继续方法递归调用
递归调用应用实例:迷宫问题
1、小球得到的路径,和程序员设置的找路策略有关系,即:找路的上下左右的顺序相关。
2、在得到小球路径时,可以先使用(下右上左),再更改成(上右下左),看看路径是不是有变化
3、测试回溯现象
4、扩展思考:如何求出最短路径?
MiGong.java
首先设置一个二维数组表示迷宫
这个迷宫一共有八行七列
1、创建迷宫,使用二维数组表示
int[][] map = new int[8][7];
2、规定0表示可以走的路,1表示障碍物不能走
int[][] map = new int[8][7];
将最上面的一行和最下边的一行全部设置为1
因为有七列需要遍历七次
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
同时设置最下面一行
map[7][i] = 1;
}
将最右边的一列和最左边的一列全部变成1
for (int i = 0; i < 8; i++) { // 因为有八行
map[i][0] = 1;
map[i][6] = 1;
每一行的第一个和最后一个
}
// 设置没有规律的
map[3][1] = 1;
map[3][2] = 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();
}
// 使用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();
}
// 使用递归回溯的思想来解决老鼠出迷宫的问题
1、findWay方法是专门用来找迷宫的路径
2、如果找到就返回true,否则返回false
3、map就是二维数组,表示的是迷宫
4、i,j表示老鼠的当前坐标位置,初始化位置是1,1
5、因为我们是递归找路,所以要先定义map数组的各个值得含义,0表示可以走,1表示障碍物,2表示可以走通,3表示走过,但是走不通是思路
6、当map[6][5] = 2表示找到了通路,就可以结束,否则继续找。
7、先确定老鼠找路得策略,下右上左
class T {
// 首先使用递归回溯的思想来解决老鼠出迷宫的问题
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 {
return false;
}
}
}
}
过程:
map[2][1] = 1;
map[2][2] = 1;
map[1][2] = 1;
直接原地置三
接下来尝试改变这个找路得策略看看是否对于找路有影响
// 更改成上右下左
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 {
return false;
}
}
}
}
路径:
回溯现象
如果这个时候放上一睹蓝色得墙,按照之前得策略将当前得置为2然后往下面走,将当前置2然后下为墙1,右边也是墙1,上边是已经走过得2,左边也是墙1,这样就会导致将原地置3,走不了了,不会往回走。当遇到这种情况会返回上一次得位置
// 设置这堵墙
map[2][2] = 1;
扩展思考是:如何求出最短路径?可以使用最简单的穷举方法,或者是图
汉诺塔问题:
方法递归调用的经典问题-汉诺塔问题
汉诺塔:汉诺塔又称为河内塔,问题是源于印度的一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摆放着64片圆盘,大梵天命令婆罗门将圆盘从下面开始按照大小顺序重新摆放到另外一根柱子上。并且规定,在小圆盘上不能放置大圆盘,在三根柱子之间一次只能移动一个圆盘。
假如每秒钟移动一次,共需要多长的时间呢!?
移动这些金片需要5845.54亿年以上,太阳系的预期寿命据说也是数百亿年,真的过了5845.54亿年,地球上的一切生命连同梵塔,庙宇等,都早已灰飞烟灭了。
class Tower {
// num表示移动的个数,abc分别表示ABC塔
public void move(int num, char a, char b, char c) {
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);
}
}
}
Tower tower = new Tower();
tower.move(1, 'A', 'B', 'C');
八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的经典案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8*8格的国际象棋上摆放八个皇后,使其不能相互攻击,即:任意两个皇后都不能处于同一行,同一列或者同一斜线,问有多少种摆发。
八皇后的思路分析:
1、第一个皇后先放到第一行第一列
2、第二个皇后放到第二行第一列,然后判断是否ok,如果不ok,继续放到第二列,第三列,依次将所有列放完,找到一个合适的
3、继续放第三个皇后,还是第一列,第二列,到第八个皇后也能放到一个不冲突的位置才算是找到了一个正确的答案。
4、当得到一个正确的答案之后,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到。
5、然后回头继续第一个皇后放到第二列,后面继续循环执行1234的步骤
说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,使用一个一维数组来解决问题
arr[8] = {0,4,7,5,2,6,1,3};
对应arr下标表示第几行,即第几个皇后,
arr[i] = val,val表示第i+1个皇后,放到i+1行第val+1列
二、方法重载OverLoad
基本介绍:Java中允许在同一个类中,多个同名的方法存在,但是要求形参列表不一致
比如System.out.println(); out是PrintStream类型
重载的好处是:
1、减轻了起名字的麻烦
2、减轻了记名字的麻烦
比如
System.out.println(100);
System.out.println("hello,world");
System.out.println('h');
System.out.println(1.1);
System.out.println(true);
重名方法,但是形参列表不同-重载
快速入门重载
案例:类:MyCalculator方法calculate
calculate(int n1, int n2)
calculate(int n1, double n2)
calculate(double n2, int n1)
calculate(int n1, int n2, int n3)
class MyCalculator {
public int calculate(int n1, int n2) {
return n1 + n2;
}
public double calculate(int n1, double n2) {
return n1 + n2;
}
public double calculate(double n1, int n2) {
return n1 + n2;
}
public int calculate(int n1, int n2, int n3) {
return n1 + n2 + n2;
}
}
MyCalculator mc = new MyCalculator();
System.out.println(mc.calculate(1, 2));
找有两个形参的,并且两个形参都是int的
// 方法重载的注意事项和细节:
1、方法名:必须相同
2、参数类型:必须不同(参数类型或者个数或者顺序,至少有一样不同,参数名无要求)
3、返回类型:无要求
方法重载练习:
1、判断题
与 void show(int a,char b,double c){}构成重载的是?
a void show(int x,char y,double z) {}
b int show(int a,double c,char b) {} true
c void show(int a,double c,char b) {} true
d boolean show(int c, char b) {} true
e void show(double c) {} true
f double show(int x,char y,double z) {}
g void shows() {}
方法重载的另外两个题目:
1、编写程序,类Methods中定义三个重载方法并调用。方法名字为m,三个方法分别接收一个int参数,两个int参数,一个字符串参数,分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。在主类的main()方法中分别使用参数区别三个方法。
OverLoadExercise.java
class Methods {
//分析
//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);
}
}
Methods method = new Methods();
method.m(10);
method.m(10,20);
method.m("hello,world");
2、在Method类中,定义三个重载方法max(),第一个方法,返回两个int值中的最大值,第二个方法,返回两个double值中的最大值,第三个方法,返回三个double值中的最大值,分别调用者三个方法。
public int max(int n1, int n2) {
return n1 > n2 ? n1 : n2;
}
public double max(double n1, double n2) {
return n1 > n2? n1 : n2;
}
public double max(double n1, double n2, double n3) {
double max1 = n1 > n2? n1 : n2;
return max1 > n3 ? max1 : n3;
}
System.out.println(method.max(10, 24));
System.out.println(method.max(10.0, 21.4));
System.out.println(method.max(10.0, 1.4, 30));
这里输入整数是可以匹配到的可以int->double
然后如果做这样的重载看看
public double max(double n1, double n2, double n3) {}
public double max(double n1, double n2, int n3) {}
这样是构成了重载的,但是这样应该会走哪一个呢?
应该会走int的这个,没有发生自动转换就匹配了,优先级高
三、可变参数
基本概念
Java允许将同一个类中的多个同名同功能但是参数个数不同的方法,封装成一个方法。
基本语法:
访问修饰符 返回类型 方法名(数据类型... 形参名) {
}
有一个案例快速入门
类HspMethod,方法sum可以计算两个数的和,三个数的和,4个数...
参数个数是不确定的是可变的。
可以使用方法重载,但是会发现非常无聊
public int sum (int n1, int n2){}
public int sum (int n1, int n2, int n3){}
上面的这些方法名称相同,功能相同,参数个数不同,使用可变参数优化
public int sum (int... nums) {
int...表示接收的是可变参数,类型是int,即,可以接收多个int,即0到多个,接收的是int类型的参数。怎么使用呢?
使用可变参数时可以当作数组来使用,将nums可以当作数组,这样就明白了,
System.out.println("接收的参数个数="+nums.length);
return 0;
}
HspMethod m = new HspMethod();
m.sum(); // 当前的个数是0个,可以什么都不传
m.sum(1,5,100);
接收的参数是三个
因为nums是数组直接遍历求和即可
int res = 0;
for (int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
返回总和
System.out.println(m.sum(1,5,100));
结果是106
这个是可变参数的快速入门
可变参数的注意事项和细节
1、可变参数的实参可以为0或者任意多个
2、可变参数的实参可以作为数组
3、可变参数的本质就是数组
4、可变参数可以和普通类型的参数一起放到形参列表中,但是必须保证可变参数在最后
5、一个形参列表中只能出现一个可变参数
第二点讲解
int[] arr = {1,2,3};
T t1 = new T();
t1.f1(arr);
class T{
public void T1(int... nums) {
System.out.println("长度=" + nums.length);
}
}
细节四
public void f2(double... nums. String str) {
现在可变参数在第一个,现在是错误的
}
public void f2(String str, double... nums){
这样是可以的,可变参数在最后
}
细节五
不能有两个可变参数
public void f3(int... nums1, double... nums2) {
只能出现一个可变参数
}
可变参数的练习
有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩(总分)。封装成一个可变参数的方法。
类名HspMethod方法名showScore
class HspMethod {
public String showScore(String name, double... socres) {
double totalScore = 0;
for (int i = 0; i < scores.length; i++) {
totalScore += scores[i];
}
return name + " 有 " + scores.length + " 门课的成绩总分为="+ totalScore;
}
}
HspMethod hm = new HspMethod();
System.out.println(hm.showScore("milan", 90.1, 80.0));
四、作用域
作用域的基本使用
面向对象中,变量作用域是非常重要的知识点,相对来说不是特别好理解,请大家注意听,认真思考,要求深刻掌握变量作用域。
1、在Java编程中,主要的变量就是属性(成员变量)和局部变量。
2、我们说的局部变量一般是指在成员方法中定义的变量。
3、Java中的作用域的分类
全局变量:也就是属性,作用域为整个类体。
局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中!
4、全局变量可以不用赋值直接使用因为有默认值,局部变量必须赋值后才能使用,因为没有默认值。
class Cat {
// 全局变量:也就是属性,作用域为整个类体
// 属性在定义的时候可以直接赋值
int age = 10; // 不赋值也是可以的
double weight; // 这里没有赋值会有默认值
这里的默认值是0.0,面向对象的时候说过是有默认值的。
{
int num = 100;
后面还可以单独定义一个代码块,这个num只能在这个代码块中使用,另外一个地方也是用不了的。而这个num不是属性。
所以说除了属性之外的其他变量,作用域为它定义的代码块中。
}
public void h1() {
int num;
System.out.println("num=" + num);
这里局部变量没有赋值会提示尚未初始化,也就是没有赋值,需要给一个值。
}
public void say () {
// 1、局部变量一般指的是在成员方法中定义的变量
// 2、n和name就是局部变量
int n = 10;
String name = "jack";
// 这里的n和name的作用域只能在cry方法中,出了这个方法就使用不了了。
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、属性生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡。即在一次方法调用过程中。
class Person {
String name = "jack";
public void say() {
String name = "king";
System.out.println("say() name = " + name);
// 这里的属性和局部变量是可以重名的,访问的时候需要遵循就近原则。,这里输出king,如果将king注释,输出的就是jack
}
public void h1() {
String address = "北京";
String address = "上海";
// 这里不能同名
// 产生重定义
String name = "hsp";
// 这个地方是ok的,因为两个是在不同的作用域中,是可以的。
}
}
Person p1 = new Person();
p1.say(); // say方法中的name,say方法执行完毕就会被销毁,但是属性的name是可以使用的,这个是生命周期,在我们执行Person p1 = new Person();前面说过,这个对象是在堆中的,数据和这个对象关联,p1指向这个对象,当我们执行p1.say()的时候会创建一个栈空间,刚开始我们可以通过栈中的name指向Jack,当方法执行结束之后栈会被销毁,连接Jack这条线也会断掉,但是我们还有主栈的p1指向对象,我们可以通过对象访问到这个属性
作用域的注意事项和使用细节:
作用域范围不同
全局变量:可以被本类使用,或者其他类使用(通过对象调用)
局部变量:只能在本类中对应方法中使用
修饰符不同
class T{
public void test() {
可以通过对象调用前面的name
Person p1 = new Person();
System.out.println(p1.name); // jack
}
}
T t1 = new T();
t1.test();
这个是全局变量可以在本类还可以在其他类中使用
public void test2 (Person p) {
System.out.println(p.name);
}
t1.test2(p1);
还可以这样使用,一种是创建新的对象,一种是将对象直接传递过去,然后调用
这是两种跨类访问对象属性的方式
在Person中
再写一个 public int age = 20; 属性
但是局部变量是不能添加修饰符的
public void say() {
public String name = "king";
这样不行。
}
五、构造方法/构造器
看一个需求:
前面创建人类对象的时候是先将一个对象创建好了之后再给他的年龄和姓名属性赋值,如果现在我要求,在创建人类对象的时候,就直接指定这个对象的年龄和姓名,应该怎么做?这个时候就可以使用到构造器。
基本语法
修饰符 方法名(形参列表) {
方法体;
}
说明:
1、构造器的修饰符可以默认的
2、构造器没有返回值
3、方法名和类名字必须一样
4、参数列表和成员方法一样的规则
5、构造器的调用是java自己完成
构造器:
构造方法又叫做构造器constructor,是类的一种特殊方法,它的主要作用是完成对新对象的初始化。他有几个特点:
1、方法名和类名相同
2、没有返回值
3、在创建对象的时候,java会自动调用该类的构造器完成对对象的初始化。
注意这里是初始化对象,而不是创建对象。
到构造器运行的时候,这个对象其实已经存在了
只是这个对象的属性是由初始化完成。
Person p = new Person();
空间已经开辟了,只是属性这些值需要初始化
构造器的入门
现在我们使用构造方法来完成刚才提出的问题:在创建人类对象的时候,就直接指定这个对象的年龄和姓名
class Person {
String name;
int age;
// 构造器
public Person(String pName, int pAge) {
System.out.println("构造器被调用!");
name = pName;
age = pAge;
}
构造器没有返回值,也不要写void,写上就错了,构造器的名称是类名一样,必须一样。形参列表和成员方法一样。
}
// 主类main
Person p = new Person("smith", 80);
当我们new一个对象的时候直接通过构造器指定名字和年龄
构造器的调用是否会发生
命令行会打印这句话,构造器被调用,构造器被调用的时候对象其实已经存在了,是完成对象的属性的初始化,看看是否是
System.out.println("p1的信息如下");
System.out.println("p1对象name=" + p1.name);
System.out.println("p1对象age=" + p1.age);
构造器的注意事项和细节
1、一个类可以定义多个不同的构造器,即构造器的重载
比如:我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要指定年龄。
2、构造器名字和类名要相同
3、构造器没有返回值
4、构造器是完成对象的初始化,并不是创建对象
5、在创建对象时候,系统自动的调用该类的构造方法
class Person {
String name;
int age;
public Person(String pName, int pAge) {
name = pName;
age = pAge;
}
public Person(String pName) {
name = pName;
}
}
Person p1 = new Person("king", 40);
Person p2 = new Person("tom");
构造器的另外两个细节:
1、如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫做默认构造方法)
比如 Dog() {}使用Javap指令反编译看看
2、一旦定义了自己的构造方法,默认的构造器就覆盖了,就不能再继续使用默认的无参构造器,除非显式的定义:即:Person() {}
class Dog {
默认的构造器
Dog () {
}
没有参数,并且内容也没有
}
Dog dog1 = new Dog();
到底有没有可以通过javap这个java开发者工具来查看
首先使用javac编译通过,通过之后存在一个Dog.class类现在使用javap反编译,反编译成为人可以看的,相当于源码了。
javap同样再bin中
javap Dog.class
会发现果然出现了一个无参构造器
这个地方式声明,所以没有写大括号,这个式默认构造器
关于javap的使用还提供有文档,我后期补上
还可以直接看到汇编代码
javap -c Dog.class
还有一个-v输出附加信息
常用的-c和-v
其实我们javap Dog后面的class可以不屑
知道我们式对字节码进行反汇编的。
一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参数构造器,除非显示定义。
Dog(){},这点很重要要知道
class Dog {
public Dog(String dName) {}
这个式一个构造器,我们自己定义了一个构造器
这个默认的构造器已经被覆盖
我们反编译
}
javac
javap Dog
可以看到这里只有一个我们自己写的构造方法
原来的无参构造器已经被覆盖了
也就意味着我们不能使用
Dog dog1 = new Dog();
这种没有参数的方式
如果再想要使用我们需要使用我们自己定义的构造器的方式
如果还想要使用无参数的构造器我们需要显示的定义以下
Dog(){}
关于构造方法的两个题:
在前面定义的Person类中添加两个构造器;
第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
第二个带pName和pAge两个参数的构造器:使得每次创建Person对象的同时初始化对象的age属性值和name属性值。分别使用不同的构造器,创建对象。
class Person {
String name;
int age;
public Person() {
age = 18;
}
public Person(String pName, int pAge) {
name = pName;
age = pAge;
}
}
Person p = new Person();
System.out.println("p1的信息name = " + p1.name + " age=" + p1.age);
这里的name式默认值,这里age给了18就是18
应该输出name为空,age为18
然后使用另外一个构造器
Person p2 = new Person("scott", 50);
System.out.println("p2的信息name = " + p2.name + " age=" + p2.age);
走的式有两个参数的构造器,会将这两个值分别赋值
name = scott age = 50
对象创建的流程分析,当时我们讲的对象创建流程没有引入构造器,我们这里引入构造器看看创建对象的流程有没有发生变化。
代码示例:
class Person{
int age = 90;
String name;
Person(String n, int a){
name = n;
age = a;
}
}
Person p = new Person("小倩", 20);
(面试题)
内存结构
首先在方法去中加载Person类的信息
然后在堆中创建对象的空间
开始有默认值0和null,然后显示赋值有90就给90
换成九十之后对象创建完成然后看到初始化还有参数传进来需要调用构造器,将小倩给到,对应到常量池中的字符串,使用name指向常量池的字符串
然后又传递了一个年龄20
然后这里的九十更改成20
这个时候才将对象空间的地址返回给p,p正式指向这个堆空间,真正的对象式在堆中的,p式对象的引用
就是对象的一个名字,这个名字在栈中,main栈中
总结:
1、加载Person类信息,只会加载一次,也就是这个式模板
2、在堆中分配对象空间,分配完成后就会又一个地址
3、完成对象的初始化,初始化分成三部
首先进行默认初始化,给的属性的初始值
然后显示初始化,显示初始化需要看这个地方给的式90,这个name没有给初始值就是空
最后进行构造器初始化,构造器初始化式最后执行,所以给到name小倩然后age为20
第四步对象初始化完成之后将堆中的地址返回给栈中的这个p这个变量,p就是对象的引用存放对象的引用,或者说对象名
类定义的学习进程:
成员变量->成员方法->构造器->待定
六、this关键字
先来看看一段java代码
class Dog {
String name;
int age;
public Dog(String dName, int dAge){
name = dName;
age = dAge;
}
public void info() {
System.out.println(name + "\t" + age + "\t");
}
}
Dog dog1 = new Dog("大壮", 3);
dog1.info();
如果我们构造器的形参能够直接写成属性名就更好了
即:
String name, int age
name = name;
age = age;
但是这样行不行,
再次执行会输出什么
会输出null 和 0
因为这个地方式name,这个地方式age
构造方法中的name和age指的是形参还是属性呢?
所以构造器就是局部变量了,不是属性,因为就近原则
这里的name = name;age = age;都是局部变量
都是自己赋值给自己
这里希望我们的构造器传进来的名字一样又希望赋值所以我们需要引入this
什么式this
java虚拟机会给每个对象分配this,代表当前对象,坦白的讲,要明白this不是一件容易的事情。
通过上帝创世界的小故事来认识this
每个人被上帝创建出来了,每个人都有自己的耳朵鼻子
现在有一个人老韩,说我的眼睛,说我的这个词的时候就表示老韩,换了一个人,这个人式小明,这个小明有眼睛,说我的眼睛,说我的的时候说的就是小明
两个人说的都是我的,这个指向就会发生变化
不同的this指向不同的对象
然后再来一个例子
奥利尔来北京打篮球遇到了赵本山
赵本山:你知道我的爸爸的爸爸式什么?
奥尼尔:不知道
赵本山:就是我爷爷
奥尼尔回国找到科比,你知道我的爸爸的爸爸是谁码?
科比:不知道
奥尼尔:赵本山的爷爷
这里的我的应该指的是奥尼尔的
虽然都是this,但是指向的是不同使用该this的对象
使用this可以解决前面变量命名的问题
public Dog(String name, int age){
this.name = name;
this.age = age;
}
this.name表示对象的属性了,不是当前的局部变量了
this.age也表示当前对象的属性,不是当前的局部变量
谁在调用这个构造器,这个this就是哪个对象
通过this可以得到对象在堆中的地址,也就是这样
谁调用就是谁
深入理解this
Dog dog1 = new Dog("大壮", 3);
dog1.info();
创建对象的时候,new,在堆中又两个属性
age = 3,name = "大壮",name这里是一个引用类型
指向方法去中的一个常量池,这个this可以怎么理解呢?相当于在每个对象创建浩了之后,堆中还有一个属性叫做this,这个this是隐藏的,可以这么理解
这个this指向了它自己,也就是存放了自己的地址
dog1也是指向这个对象的,相当于现在有两个指向这个对象的堆中的地址dog1还有this
再创建一个dog2
Dog dog2 = new Dog("大黄", 2);
dog2.info();
堆中又开了一个空间
age2 name"大黄
同理
打印出这个地址看看呢?在JAva中式打印不出来的,因为这个地址式跑到虚拟机JVM上面的,没有办法直接获取到,可以通过哈希值来获取,可以当作地址,但是不是实际地址
使用哈希code当作对象的地址来看
可以在文档中查看到
hashCode返回的式一个对象的哈希码值
针对不同的对象返回不同的整数,将对象的内部地址转换成为一个整数来实现
System.out.println("dog1的hashcode=" + dog1.hashCode());
然后再Dog构造方法中
System.out.println("this.hashCode=" + this.hashCode());
会发现两者一摸一样
dog1创建的时候这两个地址其实式一样的
同理dog2可以输出来和this也是一样。
关于this的小结
简单来说,哪个对象调用,this就代表哪个对象
比如通过dog1.info()通过这个对象调用这个对象的方法
当我们进入到info方法中的时候输出this就是这个对象
再info方法中输出
System.out.println("this.hashCode="+this.hashCode());
和这里的dog1式一样的哈希值
同理dog2也可以试一试也是一样的
this的使用注意事项和使用细节
1、this关键字可以用来访问本类的属性,方法,构造器
2、this用去区分当前类的属性和局部变量
3、访问成员方法的语法:this.方法名(参数列表);
4、访问构造器语法:this(参数列表);注意只能再构造方法中使用
5、this不能再类定义的外部使用,只能再类定义的方法中使用
class T{
细节:访问成员方法的语法:this.方法名(参数列表);
public void f1() {
System.out.println("f1()方法...");
}
public void f2() {
System.out.println("f2()方法...");
有两种方式调用本类的f1方法
第一种
f1();
第二种通过this
this.f1();
这两种方式有没有区别呢?有区别,到讲到继承的时候会讲这两者的区别。
}
}
T t1 = new T();
t1.f2();
通过this可以访问构造器,可以this(参数列表);
只能在构造器中访问另外一个构造器
这里举例说明
细节:
public T() {
this("jack", 100);
System.out.println("T()构造器");
我们想要访问下面这个构造器
// this("jack", 100);
注意:如果有this这种访问构造器的语法我们必须放到第一条语句,这个和继承有关系,后面讲。
}
public T(String name, int age) {
System.out.println("T(String name, int age)构造器");
}
T t2 = new T();
使用无参构造器构造一个对象取调用下面的有参构造器
注意只能在构造器中调用构造器
如果在成员方法中这样调用是不行的
this是一个隐藏的属性是和对象关联的,不能拿到外面使用
T中写两个属性
String name = "jack";
int num = 100;
这两个属性想要访问在方法中访问,通过this访问
this关键字可以访问本类的属性
public void f3() {
System.out.println("name = " + name + "num=" + num);
以前是这么访问的
this也可以访问
将name和num换成
this.name this.num
name其实是按照就近原则找到
this.name直接看的就是属性
name如果是有局部变量,访问的就是这个局部变量了
如果添加一个String name = "smith";在f3()中,就是访问这个了。
}
关于this的小练习
定义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;
}
public boolean compareTo(Person p) {
return this.name.equals(p.name) && this.age == p.age;
}
}
Person p1 = new Person("mary", 20);
Person p2 = new Person("smith", 30);
Sytem.out.println(p1.compareTo(p2));











浙公网安备 33010602011771号