java進階第二講-數組、String類
1 回顧一下Object
Object中的方法:
public native int hashCode();
帶有native關鍵字的方法調用的是底層C++的dll檔案
這種方法我們在工具中看不到具體的實作,當然也可以去其他地方找相關的實作代碼。
hashCode()它是一個對象在堆中的記憶體位址經過hash算法計算之後又進行了進制的轉換得出的結果.傳回值是int
問題:如果兩個對象的hashCode()值一樣,說明什麼問題?是同一個對象
hashCode()方法有什麼用?
因為hashCode()方法計算的對象是記憶體位址,記憶體位址對于一個對象來講是惟一的,是以hashCode()算法算出來的也是唯一值。是以這個方法可以判斷:
1. 一個對象是否存在。
2. 一個對象與另一個對象是否是同一個對象。
3. 兩個對象的hashCode值比較的話,就相當于引用之間使用"==",隻能比較位址,不能比較值。
protected void finalize() throws Throwable { }
Called by the garbage collector on an object
when garbage collection determines that there are no more references to the object.
// 當GC發現沒有任何的引用指向目前對象的時候,這個方法就會被GC自動調用
// 這個方法被調用的時候,對象被回收了嗎?還沒有,也就是說,對象在被GC回收之前,
// GC會自動調用這個finalize()方法
這個方法其實就是SUN公司java工程師為程式員準備的一個對象銷毀之前的時刻。可以直覺的觀測對象銷毀之前的狀态。
public class AarrayTest01 {
@Override
protected void finalize() throws Throwable {
System.out.println("對象即将被GC回收!");
}
public static void main(String[] args) throws Throwable {
Object o = new Object();
// 翻看源碼,回到上一次停留的地方ctr + alt + ←
System.out.println(o.hashCode());
Object o1 = new Object();
// 翻看源碼,回到上一次停留的地方ctr + alt + ←
System.out.println(o1.hashCode());
AarrayTest01 a = new AarrayTest01();
for (int i = 0; i < 100000; i++) {
System.out.println(new AarrayTest01());
}
}
}
2 數組
- 聲明:declare 在java中隻能聲明一個變量,也就是說沒有給這個變量賦初始值。
- 定義:define 在java中,定義一個變量,就是給變量指派的過程
int[] a = {1,2}; // 推薦這種方式,這種格式更有利于了解數組
int a1[];// 這種方式也行,API中大部分是這種格式
public class AarrayTest01 {
public static void main(String[] args) throws Throwable {
// 數組
int[] a = {1,2}; // 推薦這種方式,這種格式更有利于了解數組
int a1[];// 這種方式也行,API中大部分是這種格式
System.out.println(a);
// 什麼結果?記憶體中的首位址,什麼形式呢?
// [[email protected]類型名 + @ + 記憶體位址hash後的十六進制表示
// [[email protected] 格式其實是Object對象的toString()執行之後才會得到的結果
// System.out.println(a);它自動調用了Objcet的toString()方法
// 為什麼數組會自動調用Object類的toString方法?說明數組也是引用類型的。
// 它的父類是Object。隻有這種解釋。
// 什麼是引用類型? 類型 變量名,這個變量名指向的是一塊記憶體空間,我們說它就是引用類型。
// A a = 記憶體位址; 這種形式就是引用類型。java當中,隻有兩種類型,基礎資料類型和引用類型
// 引用類型所指向的記憶體位址實際是對象(常量、static修飾的變量)的記憶體位址。
// 這也說明了,數組是引用類型。我們也可以推斷,數組的本身是儲存在堆中的。
}
}
- Object類的簡介
/**
* Class {@code Object} is the root of the class hierarchy.
* Every class has {@code Object} as a superclass. All objects,
* including arrays, implement the methods of this class.
*/
- 數組是一種資料類型,但是它不是基礎資料類型,是引用資料類型。
- 基礎資料類型:byte short int long float double boolean char
- 除去這8中之外,全是引用類型。
public class AarrayTest01 {
public static void main(String[] args) throws Throwable {
// 數組
int[] a = {1,2}; // 推薦這種方式,這種格式更有利于了解數組
int a1[];// 這種方式也行,API中大部分是這種格式
System.out.println(a.getClass().getName());
// a.getClass()後為什麼可以直接"."getName():因為getClass()傳回了引用(一個對象的引用)
// 為什麼傳回的是引用,而不是對象?
// 因為對象在堆中,很大,引用隻是一個位址,很小,從效率上來講,你說是傳引用還是傳對象?
// 我們一般講傳輸,都是要通過網絡、通過電腦中的bus,
// 傳一個幾個Byte資料好,還是傳幾百M上G的資料好呢?---傳引用更好
// 好比,你在銀行有1個億的存款,你去取錢,是拿一張有一個億的卡好呢,還是拿1億的現金?
// 你要拿1億現金,家裡先得修一個裝得下1億現金的金庫。如果你拿一張卡,就是拿了一個引用
// 這個引用指向的是銀行的金庫,自己沒有必要再修一個金庫。
// 在java中,基礎資料類型都是傳值
// 引用資料都是傳引用(這裡的傳:實參和形參之間的傳遞方式,方法的傳回值傳回的方式)
// java做好了封裝,引用類型都是傳遞的都是引用。傳回值類型也都是引用,沒有傳回過值。
// 在C中C++中,可以傳值,也就是說可以傳指針所指向的記憶體位址的值。但這樣不好。
// 你想,你傳一個值出來,其實是做了一份拷貝,那麼你就一定要有一塊同等大小的空間去接。
// 浪費空間。由于值可能很大,傳輸上耗時,效率低下。
// 誰能"."?"類名."和"引用.","類名."靜态的屬性或者是方法;"引用."成員屬性或者是方法
// 一個方法的傳回值類型有哪些?void 基礎資料類型 引用資料類型
// 基礎資料類型可以"."方法嗎?不能的。void傳回的是null,null也是一個引用類型
// 它不能"."任何東西,一旦"."就會報NullPointerException
// 能"."成員方法的隻有引用類型。
}
}
- 通過實驗,我們看到了數組擁有Object類中所有的方法。是以我們也能斷定它是Object的子類,數組也是引用類型。數組的引用如果被置空,也會報空指針異常。
- 數組有一個屬性:length
- length屬性,指的是數組的長度。
- 任何一個數組都有這個屬性。
2.1 數組的類型
- 基礎資料類型的數組:int[] a;這意味着,a所指向的數組中儲存的是int類型的資料
- 引用類型的數組:Person[] p;這意味着,p所指向的數組中儲存的是Person類型的對象的引用。
2.2 數組的定義
- 靜态初始化
- int[] a = {1,2,3,4};
- 動态初始化
- int[] a = new int[大小];
- 然後再給數組中的元素指派。
2.3 通路數組中元素的方式
- 取下标的方式進行通路,下标從0開始到length-1
- 注意:在java中,“=” 後面的大括号中的内容就是一個對象,在javascript中也是這樣。
- 這種用法,目前來說隻對數組成立。數組指派的時候,int[] a = {……};這是靜态的初始化方式
2.4 數組的記憶體圖
2.5 兩種初始化方式的差别
- 靜态初始化是在已知數組元素值的時候,可以直接靜态初始化。
- 靜态初始的時候,實際上是根據{}中的值的個數來配置設定了數組的記憶體空間
- 動态初始化的時候,實際上是根據new int[初始化大小]中的初始化大小的值來配置設定記憶體空間
- 動态初始化是指,後續可以給數組中的元素進行指派。
- 如果是靜态初始化,一開始初始化的時候在{}中沒有給值,如果在後續指派,會抛出Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 0。數組索引越界異常。為什麼?因為靜态初始化的時候,JVM會根據{}中的值的個數在堆中給數組配置設定大小。
- 這也說明一個問題,一旦數組的大小被确定之後,是不能改變的。
int[] arr = {};
arr[0] = 1;
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
int[] arr1 = new int[3];
arr1[3] = 100;//ArrayIndexOutOfBoundsException: 3
- 數組一旦被建立,其大小就固定了,無論靜态的還是動态的。大小是固定的。數組的大小是不可以被改變的。也就是說length值已經被确立了。我們可以想象,這個length屬性一定被final修飾。
2.6 數組的優缺點
- 優點
- 數組在堆中是一塊連續的記憶體空間
- 數組中存放的是同一類型的值
- 我們要通路某個元素,是通過數組的下标進行通路,因為連續,元素的類型相同,因為每個元素的大小都是固定的,我們又能拿到首元素的位址,是以通路的速度會很快,效率很高
- 缺點:
- 數組中的元素存放在一段連續的記憶體空間上,而且數組一旦被建立,大小就固定了,不能改變。對于超出目前數組大小範圍的資料元素的存放,就需要另外開辟一個更大的數組空間,進行數組的拷貝。這樣增加系統的開銷。
- 數組的删除和增加,都會造成大量的資料移動,效率不高。
2.7 數組的擴容
- 數組的擴容,本質上沒有在原有的數組所在的堆記憶體空間上進行擴充,而是另外重新開辟了一塊記憶體空間。也就是說,原來的int[] a = 0x1234;擴容以後的int[] a 不再指向0x1234,而是指向了新開辟的數組的記憶體位址(0x2345)
-
數組的擴容的步驟:
第一步:開辟一個更大的數組
第二步:将原數組中的内容拷貝過來
第三步:将要存入的元素追加到更大數組中相應的位置
第四步:将原數組的引用指向新數組的對象
2.8 為什麼推薦使用 “類型[] a”
- 因為:類型[] 表示是一個數組類型,後邊的變量指的是這個數組的引用。
- 如果是 “類型 辨別符[]”這種形式,會讓人誤解。也很難展現出引用資料類型的特征。
- 是以,我們推薦使用 類型[] 數組名 的方式
2.9 二維數組
- 二維數組的形式:
int[][] arr
- 二維數組的定義
int[][] arr = {
{1,2,3},
{4,5,6},
{7}
}; ----靜态的方式
int[][] arr = new int[3][5]; ----動态的方式
3 String類
public final class String //意味着:這是一個最終的類,沒有子類,不能被繼承
// All string literals in Java programs,
// such as "abc", are implemented as instances of this class.
// Strings are constant; their values cannot be changed after they are created
這句話什麼意思?
字元串是常量,它們的值在它們被建立之後不能改變。
String str = "abc";
is equivalent to:等價于
char data[] = {'a', 'b', 'c'};
String str = new String(data);
分析:常量,什麼是常量?存放在哪裡?
final static修飾的是常量
存放在方法區記憶體中
String的本質是什麼?也就是說String是怎麼實作的?
char[] 數組。
我們可以想到,char[]數組中的元素要不能被改變,則一定要被final修飾。
String類型的資料全部存放于方法區的"字元串常量池"中。字元串一旦被建立,不能改變。
String name = "abc";// 這意味着String是引用類型,name是引用類型的變量名
// 引用類型的變量隻能儲存位址,不能儲存實際的值。
// "abc"不是記憶體位址,它是一個實實在在的值。
// 是以"abc"指派給name,實際上是将"abc"存放的記憶體位址的值給到了name。
// 并不是将"abc"的本身給到了name。那麼,"abc"這個字元串一定是一個對象
// 這個對象存放在方法區記憶體中的字元串常量池中。
// 為什麼要怎麼做?要将字元串存放到方法區記憶體中的字元串常量池中?
// 為什麼要規劃這麼一塊記憶體空間?
// 對事物的描述,人類最能接受的方式是字元串。字元串是不是最利于識别的,也是用得最多的
// 使用率太高了,如果把它放在堆中,是不是很占空間。也很消耗JVM的性能。
// 把字元串做成常量的話,使用率是不是高一些。重複在堆中建立銷毀的次數是不是得到了緩解。
// 測試代碼
public class AarrayTest01 {
public static void main(String[] args) throws Throwable {
String str = "abc";
str = "def";
// 這是什麼意思?是兩個字元串對象被建立了,def的位址給到了str這個引用類型變量
// 常量池中仍然存在"abc"
// 字元串常量一旦建立就不能被改變
System.out.println("abc".hashCode());
System.out.println(str.hashCode());
str += "d";
// 這裡是合并了嗎?覆寫了原來的abc嗎?沒有,建立了一個新的
System.out.println(str);
String str1 = "abc";
String string = "abc";
System.out.println(str1 == string);
System.out.println(string.hashCode());
System.out.println("------------------");
// 請問這裡建立了幾個字元串對象?3 存在于字元串常量池中的有幾個?3
// "abc" "d" "abcd"
// str = str + "d" ----> "abcd"
}
}
ng = “abc”;
System.out.println(str1 == string);
System.out.println(string.hashCode());
System.out.println("------------------");
// 請問這裡建立了幾個字元串對象?3 存在于字元串常量池中的有幾個?3
// "abc" "d" "abcd"
// str = str + "d" ----> "abcd"
}
}