1、解釋一下什么是面向對象?面向對象和面向過程的區別?
面向對象是一種基於面向過程的編程思想,是向現實世界模型的自然延伸,這是一種“萬物皆對象”的編程思想。由執行者變成指揮者,在現實生活中任何物體都可以歸為一類事物,而每一個個人都是一類事物的實例。面向對象的編程是以對象為中心,以消息為驅動。
區別:
(1)編程思路不同:面向過程以實現功能的函數開發為主,而面向對象首先要抽象出類、屬性以及方法,然后通過實例化類、執行方法來完成功能。
(2)封裝性:都具有封裝性,但是面向過程封裝的是功能,而面向對象封裝的是數據和功能。
(3)面向對象具有繼承性和多態性,但是面向過程沒有繼承性和多態性,所以面向對象的優勢更加明顯。
2、面向對象的三大特性?分別解釋一下?
(1)封裝:通常認為封裝是把數據和操作數據的方法封裝起來,對數據的訪問只能通過已定義的接口。
(2)繼承:繼承是從已有類得到繼承的信息創建新類的過程。提供繼承信息的類稱為父類(超類/基類),得到繼承信息的被稱為子類(派生類)。
(3)多態:分為編譯時多態(方法重裝)和運行時多態(方法重寫)。要實現多態需要做兩件事:一是子類繼承父類並重寫父類中的方法,二是父類引用子類對象(比如User user = new Student()),這樣同樣的引用調用同樣的方法就會根據子類對象的不同而表現出不同的行為。
關於繼承的幾點補充:
(1)子類擁有父類對象的所有屬性和方法(包括私有屬性和私有方法),但是父類中的私有屬性和私有方法子類無法訪問,只是擁有。因為在一個子類被創建的時候,首先會在內存中創建一個父類對象,然后在父類對象的外部放上子類獨有的屬性,兩者和起來形成一個子類的對象。
(2)子類可以擁有自己的屬性和方法。
(3)子類可以用自己的方式實現父類的方法。(重寫)
3、JDK、JRE、JVM三者之間的關系?
JDK(Java Development Kit):是Java開發工具包,是整個Java的核心,包括了Java運行環境JRE,Java工具和Java基礎類庫。
JRE(Java Runtime Environment):是Java的運行環境,包含JVM標准實現及Java核心類庫。
JVM(Java Virtual Machine):是Java虛擬機,是整個Java實現跨平台的最核心的部分,能夠運行以Java語言寫作的軟件程序。所有的Java程序首先會被編譯成.class的類文件,這種類文件可以在虛擬機上執行。
4、重載和重寫的區別?
(1)重載:編譯時多態、同一類中同名的方法具有不同的參數列表、不能根據返回類型進行區分【原因:因為函數調用時不能指定類型信息,編譯器不知道你要調用哪個函數】。
(2)重寫(又名覆蓋):運行時多態、子類和父類之間、子類重寫父類的方法具有相同的返回類型、更好的權限訪問。
5、Java中是否可以重寫一個private或者static方法?
Java中static方法不能被覆蓋,因為方法覆蓋是基於運行時動態綁定的,而static方法是編譯時靜態綁定的。static方法跟類的任何實例都不相關,所以概念上不適用。
Java中也不可以覆蓋private方法,因為private修飾的變量和方法只能在當前類中使用,如果是其他的類繼承當前類是不能訪問到private變量或方法的,當然也不能覆蓋。
靜態方法的補充:
靜態的方法可以被繼承,但是不能重寫。如果父類和子類中存在想同名稱和參數的靜態方法,那么該子類的方法會把原來繼承過來的父類的方法隱藏,而不是覆蓋。通俗來講就是父類的方法和子類的方法是兩個沒有關系的方法,具體調用哪一個方法是看哪個對象引用;這種父子類方法也不在存在多態的性質。
6、構造器是否可以被重寫?
在講繼承的時候我們就知道父類的私有屬性和構造方法並不能被繼承,所以Constructor也就不能被Override(重寫),但是可以被Overload(重載),所以你可以看到一個類中有多個構造函數的情況。
7、構造方法有哪些特性?
(1)名字和類名相同;
(2)沒有返回值,但不能用void聲明構造函數;
(3)成類的對象時自動執行,無需調用。
8、在Java中定義一個不做事且沒有參數的構造方法有什么作用?
Java程序在執行子類構造方法之前,如果沒有用super()來調用父類特定的構造方法,則會默認調用父類中“無參構造方法”。
因此,如果父類中只定義了有參數的構造方法,而在子類的構造方法中又沒有用super()來調用特定的構造方法,則編譯時講發生錯誤,因為Java程序在父類中找不到無參構造方法可供執行。
解決方法:在父類中加一個不做事且沒有參數的構造方法。
9、Java中創建對象的幾種方式?
(1)使用new關鍵字;
(2)使用Class類的newInstance方法,該方法調用無參的構造器創建對象(反射):Class.forName.newInstance();
(3)使用clone();
(4)反序列化,比如調用ObjectInputStream類的readObject()方法。
10、抽象類和接口有什么區別?
(1)抽象類中可以定義構造方法,而接口中不可以定義構造方法;
(2)抽象類中可以有抽象方法和具體方法,而接口中只能有抽象方法(public abstract);
(3)抽象類中的成員權限可以是public、默認(default)、protected(抽象類中的抽象方法就是為了重寫,所以不能被private修飾),而接口中的成員只可以是public(方法默認:public abstact,成員變狼默認:public static final);
(4)抽象類中可以包含靜態方法,而接口中不可以包含靜態方法。
JDK8中的改變:
(1)在JDK1.8中,允許在接口中包含帶有具體實現的方法,使用default修是,這類方法就是默認方法。
(2)在JDK1.8之前接口中不能包含靜態方法,而JDK1.8以后可以包含。之前不能包含是因為接口中不可以實現方法,只可以定義方法,所以不能使用靜態方法(因為靜態方法必須實現)。現在可以包含了,只能用接口直接調用靜態方法(如:接口名調用靜態方法)。JDK1.8仍然不可以包含靜態代碼塊。
11、靜態變量和實例變量的區別?
靜態變量:是被static修飾的變量,而稱為類變量,它屬於類,因此不管創建多少個對象,靜態變量在內存中有且僅有一個拷貝;靜態變量可以實現讓多個對象共享內存。
實例變量:屬於某一實例,需要先創建對象,然后通過對象才能訪問到它。
12、short s1 = 1; s1 = s1 + 1; 有什么錯?那么short s1= 1; s1 += 1; 呢?有沒有什么錯誤?
對於 short s1 = 1; s1 = s1 + 1; 來說,在 s1 + 1 運算時會自動提升表達式的類型為int,那么將int型值賦值給short型變量,s1會出現類型轉換錯誤。
對於 short s1= 1; s1 += 1; 來說,+= 是Java語言規定的運算符,Java編譯器會對它進行特殊處理,因此可以正確編譯。
13、Integet和int的區別?
(1)int是Java的八種基本數據類型之一,而Integer是Java為int類型的提供的封裝類;
(2)int型變量的默認值是0,Integer變量的默認值是null,這一點說明Integer可以區分出未賦值和值為0的區分;
(3)Integer變量必須實例化后才可以使用,而int不需要。
關於Integer和int的比較的延伸:
(1)由於 Integer 變量實際上是對一個 Integer 對象的引用,所以兩個通過 new 生成的 Integer 變量永遠是不相等的,因為其內存地址是不同的;
(2)Integer 變量和 int 變量比較時,只要兩個變量的值是相等的,則結果為 true。因為包裝類 Integer 和基本數據類型 int 類型進行比較時,Java 會自動拆包裝類為 int,然后進行比較,實際上就是兩個 int 型變量在進行比較;
(3)非 new 生成的 Integer 變量和 new Integer() 生成的變量進行比較時,結果為 false。因為非 new 生成的 Integer 變量指向的是 Java 常量池中的對象,而 new Integer() 生成的變量指向堆中新建的對象,兩者在內存中的地址不同;
(4)對於兩個非 new 生成的 Integer 對象進行比較時,如果兩個變量的值在區間 [-128, 127] 之間,則比較結果為 true,否則為 false。Java 在編譯 Integer i = 100 時,會編譯成 Integer i = Integer.valueOf(100),而 Integer 類型的 valueOf 的源碼如下所示:
public static Integer valueOf(int var0) { return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0); }
從上面的代碼中可以看出:Java 對於 [-128, 127] 之間的數會進行緩存,比如:Integer i = 127,會將 127 進行緩存,下次再寫 Integer j = 127 的時候,就會直接從緩存中取出,而對於這個區間之外的數就需要 new 了。
- 包裝類的緩存:
Boolean:全部緩存
Byte:全部緩存
Character:<= 127 緩存
Short:-128 — 127 緩存
Long:-128 — 127 緩存
Integer:-128 — 127 緩存
Float:沒有緩存
Doulbe:沒有緩存
14、裝箱和拆箱
自動裝箱是Java編譯器在基本數據類型和對應的包裝類之間做的一個轉換。比如:int轉換成Integer,double轉換成Double等等。反之就是自動拆箱。
原始類型:boolean、char、byte、short、int、long、float、double
封裝類型:Boolean、Character、Byte、Short、Integer、Long、Float、Double
15、swicth語句能否作用在byte上,能否作用在long上,能否作用在String上?
在swicth(expr1)中,expr1只能是一個整數表達式或者枚舉常量。而整數表達式可以是int基本數據類型或者Integer包裝類型。由於,byte、short、char都可以隱式的轉換成int,所以,這些類型以及這些類型的包裝類型也都是可以的。而long和String類型不符合swicth的語法規定,並且不能被隱式的轉換成int類型,所以,它們不能作用於switch語句中。
不過,需要注意的是在JDK1.7版本之后switch就可以作用在String上了。
16、字節和字符的區別?
字節是存儲容量的基本單位;
字符是數字、字母、漢字已經其他語言的各種符號;
1字節 = 8個二進制單位,一個字符由一個字節或多個字節的二進制單位組成。
17、String為什么要設計為不可變類?
在Java中將String設計成不可變的是考慮到各種因素的結果。主要原因是有一下三點:
(1)字符串常量池的需要:字符串常量池是Java堆內存中一個特殊的存儲區域,當創建一個String對象時,假如此字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的對象;
(2)允許String對象緩存HashCode:Java中String對象的哈希碼被頻繁地使用,比如在HashMap中等容器中。字符串不變性保證了hash碼地唯一性,因此可以放心地進行緩存。這也是一種i性能優化優化手段,意味着不必每次去計算新的哈希碼。
(3)String被許多的Java類(庫)用來當作參數,例如:網絡連接地址URL、文件路徑path、還有反射機制所需要的String參數等,假若String不是固定不變的,將會引起各種隱患。
18、String、StringBuilder、StringBuffer的區別?
String:用於字符串操作,屬於不可變類;【補充:String不是基本數據類型,是引用類型,底層用char數組實現的】
StringBuilder:與StringBuffer類似,都是字符串緩沖區,但線程不安全;
StringBuffer:也用於字符串操作,不同之處是StringBuffer屬於可變類,對方法加了同步鎖,線程安全。
StringBuffer補充:
說明:StringBuffer 中並不是所有方法都使用了 Synchronized 修飾來實現同步:
@Override public StringBuffer insert(int dstOffset, CharSequence s) { // Note, synchronization achieved via invocations of other StringBuffer methods // after narrowing of s to specific type // Ditto for toStringCache clearing super.insert(dstOffset, s); return this; }
執行效率:StringBuilder > StringBuffer > String
19、String字符串修改實現的原理?
當用String類型對字符串進行修改時,其實現方法是首先創建一個StringBuffer,其次調用StringBuffer的append()方法,最后調用StringBuffer的toString()方法把結果返回。
例如:
public class Test { public static void main(String[] args) { String s = "abc"; for (int i=0; i<10000; i++) { s += "abc"; } } }
反編譯后代碼為:
public class Test { public static void main(String args[]) { String s = "abc"; for(int i = 0; i < 1000; i++) { s = (new StringBuilder()).append(s).append("abc").toString(); } } }
20、String str = "i" 與 String str = new String("i") 一樣嗎?
不一樣,因為內存的分配方式不一樣。String str = "i" 的方式,Java 虛擬機會將其分配到常量池中;而 String str = new String("i") 則會被分到堆內存中。
public class StringTest { public static void main(String[] args) { String str1 = "abc"; String str2 = "abc"; String str3 = new String("abc"); String str4 = new String("abc"); System.out.println(str1 == str2); // true System.out.println(str1 == str3); // false System.out.println(str3 == str4); // false System.out.println(str3.equals(str4)); // true } }
在執行 String str1 = "abc" 的時候,JVM 會首先檢查字符串常量池中是否已經存在該字符串對象,如果已經存在,那么就不會再創建了,直接返回該字符串在字符串常量池中的內存地址;如果該字符串還不存在字符串常量池中,那么就會在字符串常量池中創建該字符串對象,然后再返回。所以在執行 String str2 = "abc" 的時候,因為字符串常量池中已經存在“abc”字符串對象了,就不會在字符串常量池中再次創建了,所以棧內存中 str1 和 str2 的內存地址都是指向 "abc" 在字符串常量池中的位置,所以 str1 = str2 的運行結果為 true。
而在執行 String str3 = new String("abc") 的時候,JVM 會首先檢查字符串常量池中是否已經存在“abc”字符串,如果已經存在,則不會在字符串常量池中再創建了;如果不存在,則就會在字符串常量池中創建 "abc" 字符串對象,然后再到堆內存中再創建一份字符串對象,把字符串常量池中的 "abc" 字符串內容拷貝到內存中的字符串對象中,然后返回堆內存中該字符串的內存地址,即棧內存中存儲的地址是堆內存中對象的內存地址。String str4 = new String("abc") 是在堆內存中又創建了一個對象,所以 str 3 == str4 運行的結果是 false。str1、str2、str3、str4 在內存中的存儲狀況如下圖所示: