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());
}
}
}
執行結果: