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()); } } }
运行结果:
标签:java,name,Person,int,class,面向对象,String,public,顺平 From: https://www.cnblogs.com/romantichuaner/p/18050203