194,对象内存布局
基本数据类型放在堆里面,字符串类型放在方法区。
栈:一般存放基本数据类型(局部变量)
堆:存放对象(Cat cat,数组等)
方法区:常量池(常量,比如字符串),类加载信息
196,属性注意细节
1,属性可以是基本数据类型,也可以是引用类型(对象,数组)
2,属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;这里简单介绍访问修饰符:控制属性的访问范围。有四种访问修饰符 public,protected,默认,private。
3,属性如果不赋值,有默认值,规则和数组一致。具体说:int 0,short 0,byte 0,long 0,float 0.0,double 0.0,char \u0000,boolean false,String null。
public class test1{
public static void main(String[] args){
//创建Person对象
//p1 是对象名(对象引用)
Person p1 = new Person();//new Person() 创建的对象空间(数据)才是真正的对象
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;
String isPass;
}
运行结果:
198,对象分配机制
引用赋值,即地址赋值
199,对象创建过程
1,先加载Person类信息(属性和方法信息,只会加载一次)
2,在堆中分配空间,进行默认初始化(看规则)
3,把地址赋给 p ,p 就指向对象 Person p = new Person()
4,进行指定初始化,比如 p.name = "jack"
206,方法使用细节
1,一个方法最多有一个返回值 [思考,如何返回多个结果,返回数组]
2,返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
public class test1{
public static void main(String[] args){
AA a = new AA();
int[] res = a.getSumAndSub(1,2);//用一个数组接收
System.out.println("和 = " + res[0]);
System.out.println("差 = " + res[1]);
}
}
class AA{
public int[] getSumAndSub(int n1, int n2)
{
int[] resArr = new int[2];//创建一个数组
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
}
运行结果:
3,如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值(值为常量或表达式);而且要求返回值类型必须和 return 的值类型一致或兼容(会发生自动类型转换)
4,如果方法是 void ,则方法体中可以没有 return 语句,或者只写 return;
5,接207;一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开
6,调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
7,方法定义时的参数称为形式参数,简称形参;方法调用时的传入参数称为实际参数,简称实参,实参和形参的类型要一致或兼容,个数,顺序必须一致
8,方法体里面写完功能的具体的语句,可以为输入,输出,变量,运算,分支,循环,方法调用,但里面不能再定义方法!即:方法不能嵌套定义。
9,同一个类中的方法调用:直接调用即可。(我们想在A类里的一个方法里去调用A类里的另一个方法,直接调用即可)
public class test1{
public static void main(String[] args){
A a = new A();
a.sayOk();
}
}
class A{
public void print(int n)
{
System.out.println("print()方法被调用 n = " + n);
}
public void sayOk()// sayOk 调用 print(直接调用即可)
{
print(10);
System.out.println("继续执行sayOk");
}
}
运行结果:
10,跨类中的方法A类调用B类方法:需要通过对象名调用。(我们想在A类的一个方法里调用B类的一个方法,需要在A类的这个方法里创建B类的对象,再用B类的对象调用B类的一个方法)。
public class test1{
public static void main(String[] args){
A a = new A();
a.m1();
}
}
class A{
public void m1()
{
System.out.println("m1() 方法被调用");
B b = new B();//创建B对象
b.hi();
System.out.println("m1() 方法继续执行");
}
}
class B{
public void hi()
{
System.out.println("B类中的 hi() 被执行");
}
}
运行结果:
11,特别说明一下:跨类的方法调用和方法的访问修饰符相关,后面会细说。
213,克隆对象
编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同。
public class test1{
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);
}
}
class Person{
String name;
int age;
}
class MyTools{
//方法的返回类型 Person
// 方法的名字 copyPerson
// 方法的形参(Person p)
//方法体,创建一个新对象,并复制属性,返回即可
public Person copyPerson(Person p)
{
Person p2 = new Person();
p2.name = p.name;//把原来对象的名字赋给p2.name
p2.age = p.age;//把原来对象的年龄赋给p2.age
return p2;
}
}
运行结果:
218,递归执行机制
1,执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
2,方法的局部变量是独立的,不会相互影响,比如n变量
3,如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
4,递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
5,当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
220,猴子吃桃
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到了第10天时,想再吃时(即还没吃完),发现只有1个桃子了。问题:最初共多少个桃子?
(已知第10天只剩1个桃子,猴子每天都吃其中的一半,并多吃一个。假设第9天有 x 个桃子,第 10天有 y 个桃子,(x / 2) - 1 = y,x = (y + 1) * 2)
思路:逆推
1,day = 10 时,有 1 个桃子
2,day = 9 时,有 (day10 + 1)* 2 = 4
3,day = 8 时,有 (day9 + 1)* 2 = 10
4,规律就是 前一天的桃子 = (后一天的桃子 + 1)* 2
5,递归
public class test1{
public static void main(String[] args){
T t = new T();
int day = 1;
int peachNum = t.peach(day);
if(peachNum != -1)
{
System.out.println("第" + day + "天有" + peachNum + "个桃子");
}
}
}
class T{
public int peach(int day)
{
if(day == 10)
{
return 1;
}else if(day >= 1 && day <= 9)
{
return (peach(day + 1) + 1) * 2;
}
else
{
System.out.println("day在1-10");
return -1;
}
}
}
运行结果:
221,老鼠出迷宫
思路:
1,先创建迷宫,用二维数组表示 int[][] map = new int[8][7]
2,先规定map 数组的元素值:0 表示可以走;1 表示障碍物
3,将最上面的一行和最下面的一行,全部设置为1
4,将最右面的一列和最左面的一列,全部设置为1
使用递归回溯的思想来解决老鼠出迷宫
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 class test1{
public static void main(String[] args){
int[][] map = new int[8][7];//8行7列
for(int i = 0; i < 7; i++) //i 是列,
{
map[0][i] = 1;//将最上面的一行和最下面的一行,全部设置为1
map[7][i] = 1;
}
for(int i = 0; i < 8; i++)//i 是行
{
map[i][0] = 1;//将最右面的一列和最左面的一列,全部设置为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();
}
}
}
class T{
public boolean findWay(int[][] map, int i, int j)
{
//0 表示可以走,还没走;1 表示障碍物;2 表示可以走;3 表示走过,但是走不通,是死路
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;
}
}
}
}
运行结果:
225,汉诺塔
思路见代码:
public class test1{
public static void main(String[] args){
T t = new T();
t.move(3, 'A', 'B', 'C');
}
}
class T{
//num 表示要移动的个数,a,b,c分别表示A塔,B塔,C塔
public void move(int num, char a, char b, char c)
{
if(num == 1)//只有一个盘子,就直接从A移到C
{
System.out.println(a + "->" + c);
}
else
{
//1,如果有多个盘,可以看成两个,最下面的和上面的所有盘(num-1)
move(num - 1,a, c, b);//先移动上面的所有的盘到 b,借助 c,借助是指我们不能把上面的所有盘整体移过去,需要借助c
System.out.println(a + "->" + c); //3,把最下面的这个盘,移动到 c。
//4,再把 b 塔的所有盘,移动到 c,借助 a
move(num - 1, b, a, c);//把B塔的盘移动也当成两个盘在移动,和上面的 1 同理
}
}
}
运行结果:
229,重载使用细节
1,方法名:必须相同
2,形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)
3,返回类型:无要求
233,可变参数使用
概念:java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现。
基本语法: 访问修饰符 返回类型 方法名(数据类型... 形参名){}
public class test1{
public static void main(String[] args){
HspMethods m = new HspMethods();
System.out.println(m.sum(1,2,3,4));
}
}
class HspMethods{
//可以计算 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;
// }
//上面的两个方法名称相同,功能相同,参数个数不同 -> 使用可变参数优化
//1, int... 表示接受的是可变参数,类型是int,即可以接收多个int(0-多)
//2, 使用可变参数是,可以当做数组来使用,即 nums 可以当做数组
public int sum(int... nums)
{
int res = 0;
for(int i = 0; i < nums.length; i++)
{
res += nums[i];
}
return res;
}
}
运行结果:
234,可变参数细节
1,可变参数的实参可以为 0 个或任意多个(见 233 代码)
2,可变参数的实参可以为数组
public class test1{
public static void main(String[] args){
int[] arr = {1, 2, 3};
T t1 = new T();
t1.f1(arr);
}
}
class T{
public void f1(int... nums)
{
System.out.println("长度 = " + nums.length);
}
}
运行结果:
3,可变参数的本质就是数组
4,可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后(否则报错)
public void f2(String str, double... nums){}
5,一个形参列表中只能出现一个可变参数
235,可变参数练习
问题:有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩(总分)。封装成一个可变参数的方法。类名 HspMethod 方法名 showScore
分析:两门课,三门课,五门课成绩,理解为一个可以接收多个double 类型的可变参数,问题要求返回姓名和三门课成绩(总分),所以方法的返回类型是 String,形参(String, double... )
public class test1{
public static void main(String[] args){
HspMethod t1 = new HspMethod();
System.out.println(t1.showScore("milan", 60,80));
System.out.println(t1.showScore("jack", 60,80,80));
System.out.println(t1.showScore("lucy", 60,80,80,90,80));
}
}
class HspMethod{
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;
}
}
运行结果:
236,作用域基本使用
1,在java编程中,主要的变量就是属性(成员变量)和局部变量
2,我们说的局部变量一般是指在成员方法中定义的变量
3,java 中作用域的分类。
全局变量:也就是属性,作用域为整个类体;
局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中!
4,全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后才能使用,因为没有默认值。
237,作用域使用细节
1,属性和局部变量可以重名,访问时遵循就近原则。
public class test1{
public static void main(String[] args){
Person p1 = new Person();
p1.say();
}
}
class Person{
String name = "jack";
public void say()
{
String name = "king";
System.out.println("say() name = " + name);
}
}
运行结果:
2,在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
3,属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中。
4,全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)。 局部变量:只能在本类中对应的方法中使用
public class test1{
public static void main(String[] args){
Person p1 = new Person();
T t1 = new T();
t1.test();//第1种跨类访问对象属性的方式
t1.test(p1);//第2种跨类访问对象属性的方式
}
}
class T{
public void test()
{
Person p1 = new Person();//Person类中的全局变量name,可以在T类中使用,通过对象调用
System.out.println(p1.name);//jack
}
public void test(Person p)
{
System.out.println(p.name);//jack
}
}
class Person{
String name = "jack";
}
运行结果:
5,修饰符不同 :全局变量/属性可以加修饰符;局部变量不可以加修饰符
239,构造器基本介绍
构造方法又叫构造器,是类的一种特殊方法,它的主要作用是完成对新对象的初始化
基本语法: 修饰符 方法名(形参列表) { 方法体 }
说明:1,构造器的修饰符可以默认,也可以是public protected private
2,构造器没有返回值
3,方法名和类名字必须一样
4,参数列表 和 成员方法 一样的规则
5,在创建对象时,系统会自动调用该类的构造器完成对对象的初始化
public class test1{
public static void main(String[] args){
Person p1 = new Person("smith", 80);//当我们new 一个对象时,直接通过构造器指定名字和年龄
System.out.println("p1的信息如下");
System.out.println("p1对象name = " + p1.name);
System.out.println("p1对象age = " + p1.age);
}
}
class Person{
String name;
int age;
public Person(String pName, int pAge)
{
System.out.println("构造器被调用~~ 完成对象的属性初始化");
name = pName;
age = pAge;
}
}
运行结果:
6,一个类可以定义多个不同的构造器,即构造器重载
7,如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如 Dog() { }
8,一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显示的定义一下,即:Dog() {}
244,对象创建的流程分析
看一个案例
class Person{//类Person
int age = 90;
String name;
Person(String n, int a)//构造器
{
name = n;//给属性赋值
age = a;
}
}
Person p = new Person("小倩",20);
流程分析(面试题)
1,加载Person类信息(Person.class),只会加载一次
2,在堆中分配空间(地址)
3,完成对象初始化
3.1,默认初始化 age = 0,name = null 3.2,显示初始化 age = 90,name = null 3.3,构造器的初始化 age = 20,name = 小倩
4,在对象在堆中的地址,返回给 p(p是对象名,也可以理解成是对象的引用)
249,this 使用细节
1,哪个对象调用,this就代表哪个对象
2,this 关键字可以用来访问本类的属性,方法,构造器
3,this用于区分当前类的属性和局部变量
4,访问成员方法的语法:this.方法名(参数列表)
public class test1{
public static void main(String[] args){
T t = new T();
t.f2();
}
}
class T{
public void f1()
{
System.out.println("f1() 方法..");
}
public void f2()
{
System.out.println("f2() 方法..");
//调用本类的 f1
f1();//第一种方式
this.f1();//第二种方式 this.方法名(参数列表)
}
}
运行结果:
5,访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另外一个构造器,必须放置第一条语句)
public class test1{
public static void main(String[] args){
T t = new T();
}
}
class T{
public T()
{
this("jack", 100);//这里去访问T(String name, int age) 构造器
System.out.println("T() 构造器");
}
public T(String name, int age)
{
System.out.println("T(String name, int age) 构造器");
}
}
运行结果:
6,this不能在类定义的外部使用,只能在类定义的方法中使用
250,this课堂练习
问题:定义Person类,里面有name,age属性,并提供compareTo比较方法,用于判断是否和另一个人相等,提供测试类TestPerson 用于测试,名字和年龄完全一样,就返回true,否则返回false
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.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)//和另一个人的姓名年龄比较,所以形参是Person对象,还要知道这个对象的信息(通过构造器)
{
return this.name.equals(p.name) && this.age == p.age;
}
}
运行结果:
253,本章作业03
问题:编写类Book,定义方法updatePrice,实现更改某本书的价格,具体:如果价格>150,则更改为150,如果价格>100,更改为100,否则不变。
分析:更改某本书的价格,可以理解为更改一个对象的价格属性,需要先用构造器完成对这个对象的价格的初始化,再到方法中进行更改。
public class test1{
public static void main(String[] args){
Book book = new Book("小王子", 120);
book.info();
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 等价 price
if(price > 100)
{
price = 100;
}else if(price > 150)
{
price = 150;
}
}
public void info()
{
System.out.println("书名 = " + this.name + " 价格 = " + this.price);
}
}
运行结果:
260,本章作业10
public class test1 {
public static void main(String[] args) {
Circle c = new Circle();
PassObject po = new PassObject();
po.printAreas(c,5);
}
}
class Circle
{
double radius;
public Circle()//在第2问我们没有办法确认半径值,在for循环那里才知道半径值是变化的,所以要重写默认构造器
{
}
public Circle(double radius)//第1问要用到这个
{
this.radius = radius;
}
public double findArea()//返回面积
{
return radius * radius * Math.PI;
}
public void setRadius(double radius)//添加方法 setRadius,修改对象的半径值
{
this.radius = radius;
}
}
class PassObject
{
public void printAreas(Circle c, int times)
{
System.out.println("radius\tarea");
for(int i = 1; i <= times; i++)
{
c.setRadius(i);//可以用到Circle对象,并能把i传进去,如果每次都new 一个新对象(用构造器),就不划算
System.out.println(i + "\t" + c.findArea());
}
}
}
运行结果: