Java 基礎知識相關
Java中 == 和 equals 和 hashCode 的區別
對於關係操作符 ==
- 若運算元的型別是基本資料型別,則該關係操作符判斷的是左右兩邊運算元的值是否相等
- 若運算元的型別是引用資料型別,則該關係操作符判斷的是左右兩邊運算元的記憶體地址是否相同。也就是說,若此時返回true,則該操作符作用的一定是同一個物件。
對於使用 equals 方法,內部實現分為三個步驟:
- 先比較引用是否相同(是否為同一物件),
- 再判斷型別是否一致(是否為同一型別),
- 最後比較內容是否一致
Java 中所有內建的類的 equals 方法的實現步驟均是如此,特別是諸如 Integer,Double 等包裝器類。如以下 String
中的 equals
方法實現
// String.java
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
複製程式碼
hashcode是系統用來快速檢索物件而使用
equals方法本意是用來判斷引用的物件是否一致
重寫equals方法和hashcode方法時,equals方法中用到的成員變數也必定會在hashcode方法中用到,只不過前者作為比較項,後者作為生成摘要的資訊項,本質上所用到的資料是一樣的,從而保證二者的一致性
參考
- Java 中的 ==, equals 與 hashCode 的區別與聯絡
- Effective Java 第 8,9 條
int、char、long各佔多少位元組數
Java 基本型別佔用的位元組數
- 1位元組: byte , boolean
- 2位元組: short , char
- 4位元組: int , float
- 8位元組: long , double
注:1位元組(byte)=8位(bits)
int與integer的區別
- Integer是int的包裝類,int則是java的一種基本資料型別
- Integer變數必須例項化後才能使用,而int變數不需要
- Integer實際是物件的引用,當new一個Integer時,實際上是生成一個指標指向此物件;而int則是直接儲存資料值
- Integer的預設值是null,int的預設值是0
參考
對 Java 多型的理解
多型是指父類的某個方法被子類重寫時,可以產生自己的功能行為,同一個操作作用於不同物件,可以有不同的解釋,產生不同的執行結果。
多型的三個必要條件:
- 繼承父類。
- 重寫父類的方法。
- 父類的引用指向子類物件
然後可以使用結合里氏替換法則進一步的談理解
里氏替換法則 ---- 所有引用基類的地方必須能透明地使用其子類的物件
- 子類必須完全實現父類的方法
注意 在類中呼叫其他類時務必要使用父類或介面,如果不能使用父類或介面,則說明類的設計已經違背了LSP原則
注意 如果子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生“畸變”,則建議斷開父子繼承關係,採用依賴、聚集、組合等關係代替繼承
- 子類可以有自己的個性
- 覆蓋或實現父類的方法時輸入引數可以被放大
- 覆寫或實現父類的方法時輸出結果可以被縮小
在專案中,採用里氏替換原則時,儘量避免子類的“個性”,一旦子類有“個性”,這個子類和父類之間的關係就很難調和了, 把子類當做父類使用,子類的“個性”被抹殺——委屈了點;把子類單獨作為一個業務來使用,則會讓程式碼間的耦合關係變得撲朔迷離——缺乏類替換的標準
String、StringBuffer、StringBuilder 區別
StringBuffer 和 String 一樣都是用來儲存字串的,只不過由於他們內部的實現方式不同,導致他們所使用的範圍不同,對於 StringBuffer 而言,他在處理字串時,若是對其進行修改操作,它並不會產生一個新的字串物件,所以說在記憶體使用方面它是優於 String 的
StringBuilder 也是一個可變的字串物件,他與 StringBuffer 不同之處就在於它是執行緒不安全的,基於這點,它的速度一般都比 StringBuffer 快
String 字串的拼接會被 JVM 解析成 StringBuilder 物件拼接,在這種情況下 String 的速度比 StringBuffer 的速度快
str +=”b”等同於 str = new StringBuilder(str).append(“b”).toString();
詳解內部類
內部類使得多重繼承的解決方案變得更加完整
使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響
使用內部類才能實現多重繼承
- 內部類可以用多個例項,每個例項都有自己的狀態資訊,並且與其他外圍物件的資訊相互獨立。
- 在單個外圍類中,可以讓多個內部類以不同的方式實現同一個介面,或者繼承同一個類。
- 建立內部類物件的時刻並不依賴於外圍類物件的建立。
- 內部類並沒有令人迷惑的“is-a”關係,他就是一個獨立的實體。
- 內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。
當我們在建立某個外圍類的內部類物件時,此時內部類物件必定會捕獲一個指向那個外圍類物件的引用,只要我們在訪問外圍類的成員時,就會用這個引用來選擇外圍類的成員
成員內部類
在成員內部類中要注意兩點,
- 成員內部類中不能存在任何 static 的變數和方法
- 成員內部類是依附於外圍類的,所以只有先建立了外圍類才能夠建立內部類
靜態內部類
靜態內部類與非靜態內部類之間存在一個最大的區別,我們知道非靜態內部類在編譯完成之後會隱含地儲存著一個引用,該引用是指向建立它的外圍內,但是靜態內部類卻沒有。沒有這個引用就意味著:
- 它的建立是不需要依賴於外圍類的。
- 它不能使用任何外圍類的非static成員變數和方法。
參考
為什麼Java裡的匿名內部類只能訪問final修飾的外部變數?
匿名內部類用法
public class TryUsingAnonymousClass {
public void useMyInterface() {
final Integer number = 123;
System.out.println(number);
MyInterface myInterface = new MyInterface() {
@Override
public void doSomething() {
System.out.println(number);
}
};
myInterface.doSomething();
System.out.println(number);
}
}
複製程式碼
編譯後的結果
class TryUsingAnonymousClass$1
implements MyInterface {
private final TryUsingAnonymousClass this$0;
private final Integer paramInteger;
TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) {
this.this$0 = this$0;
this.paramInteger = paramInteger;
}
public void doSomething() {
System.out.println(this.paramInteger);
}
}
複製程式碼
因為匿名內部類最終用會編譯成一個單獨的類,而被該類使用的變數會以建構函式引數的形式傳遞給該類,例如:Integer paramInteger,如果變數 不定義成final的,paramInteger在匿名內部類被可以被修改,進而造成和外部的paramInteger不一致的問題,為了避免這種不一致的情況,因為Java 規定匿名內部類只能訪問final修飾的外部變數
抽象類和介面的區別
- 預設的方法實現抽象類可以有預設的方法實現完全是抽象的。介面根本不存在方法的實現
- 實現抽象類使用 extends 關鍵字來繼承抽象類。如果子類不是抽象類的話,它需要提供抽象類中所有宣告的方法的實現。子類使用關鍵字 implements 來實現介面。它需要提供介面中所有宣告的方法的實現。
- 抽象類可以有構造器,而介面不能有構造器
- 抽象方法可以有public、protected和default這些修飾符。介面方法預設修飾符是public。你不可以使用其它修飾符。
- 抽象類在java語言中所表示的是一種繼承關係,一個子類只能存在一個父類,但是可以存在多個介面。
- 抽象方法比介面速度要快,介面是稍微有點慢的,因為它需要時間去尋找在類中實現的方法。
- 如果往抽象類中新增新的方法,你可以給它提供預設的實現。因此你不需要改變你現在的程式碼。 如果你往介面中新增方法,那麼你必須改變實現該介面的類
參考
泛型中 extends 和 super 的區別
父類的靜態方法能不能被子類重寫
靜態方法與靜態成員變數可以被繼承,但是不能被重寫。它對子類隱藏,因此靜態方法也不能實現多型
程式和執行緒的區別
程式和執行緒的主要差別在於它們是不同的作業系統資源管理方式。程式有獨立的地址空間,一個程式崩潰後,在保護模式下不會對其它程式產生影響,而執行緒只是一個程式中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程式死掉,所以多程式的程式要比多執行緒的程式健壯,但在程式切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程式。
final, finally, finalize的區別
-
final 用於宣告屬性,方法和類, 分別表示屬性不可變, 方法不可覆蓋, 類不可繼承.
-
finally 是異常處理語句結構的一部分,表示總是執行.
-
finalize 是Object類的一個方法,在垃圾收集器執行的時候會呼叫被回收物件的此方法,可以覆蓋此方法提供垃圾收集時的其他資源回收,例如關閉檔案等. JVM不保證此方法總被呼叫.
參考
Parcelable和Serializable的區別
Serializable是Java中的序列化介面,其使用起來簡單但 是開銷很大,序列化和反序列化過程需要大量I/O操作。而Parcelable是Android中的序列化方式,因此更適合用在Android平臺上,它的缺點就是使用起來稍微麻煩 點,但是它的效率很高,這是Android推薦的序列化方式,因此我們要首選Parcelable。Parcelable主要用在記憶體序列化上,通過Parcelable將物件序列化到儲存裝置 中或者將物件序列化後通過網路傳輸也都是可以的,但是這個過程會稍顯複雜,因此在這兩種情況下建議大家使用Serializable。
參考
- 相關文章
- Android 開發藝術探索
為什麼非靜態內部類裡不可以有靜態屬性
談談對kotlin的理解
因人而異,請自行整理答案
String 轉換成 integer的方式及原理
Integer a = 2;
private void test() {
String s1 = a.toString(); //方式一
String s2 = Integer.toString(a); //方式二
String s3 = String.valueOf(a); //方式三
}
複製程式碼
方式一原始碼:
public String toString() {
return toString(value);
}
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
複製程式碼
可以看出
方式一
最終呼叫的是方式二
通過toString()方法,可以把整數(包括0)轉化為字串,但是 Integer 如果是 null 的話,就會報空指標異常
方式三原始碼:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
複製程式碼
可以看出 當 Integer 是null的時候,返回的String是 字串 "null" 而不是 null