參考:http://www.importnew.com/22083.htmlhtml
參考:http://www.importnew.com/22087.html前端
十分感謝原做者的彙總,我在這個基礎上,有所改動,添加本身的備忘,總結java
一、面向對象的特性特徵:程序員
-抽象:面試
將一類對象的共同特徵 總結出來 構造類 的過程,包括 數據抽象 和 行爲抽象 兩方面。抽象只關注對象有哪些屬性和行爲,並不關注這些行爲的細節是什麼。也就是從實際需求中 設計出類的一個過程,包括類的屬性和方法。算法
-封裝:編程
把數據和操做數據的方法綁定起來,對數據的訪問只能經過已定義的接口。面向對象的本質就是將現實世界描繪成一系列徹底自治、封閉的對象。咱們在類中編寫的方法就是對實現細節的一種封裝;咱們編寫一個類就是對數據和數據操做的封裝。能夠說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的編程接口設計模式
-繼承:數組
繼承是從已有類獲得繼承信息建立新類的過程。提供繼承信息的類被稱爲父類(超類、基類);獲得繼承信息的類被稱爲子類(派生類)。繼承讓變化中的軟件系統有了必定的延續性,同時繼承也是封裝程序中可變因素的重要手段。子類繼承父類的屬性和方法。瀏覽器
-多態:
容許不一樣子類型的對象對同一消息做出不一樣的響應。簡單的說就是用一樣的對象引用調用一樣的方法可是作了不一樣的事情。
多態性分爲 編譯時的多態性 和 運行時的多態性。
若是將對象的方法視爲對象向外界提供的服務,那麼運行時的多態性能夠解釋爲:當A系統訪問B系統提供的服務時,B系統有多種提供服務的方式,但一切對A系統來講都是透明的。
方法重載(overload)實現的是 編譯時的多態性(也稱爲前綁定),
而方法重寫(override)實現的是 運行時的多態性(也稱爲後綁定)。
運行時的多態是面向對象最精髓的東西,要實現多態須要作兩件事:
1). 方法重寫(子類繼承父類並重寫父類中已有的或抽象的方法);
2). 對象造型(用父類型引用子類型對象,這樣一樣的引用調用一樣的方法就會根據子類對象的不一樣而表現出不一樣的行爲)。
二、面向切面編程的特徵特性?
三、修飾符的區分
類的成員不寫訪問修飾時默認爲default。、
默認對於同一個包中的其餘類至關於公開(public),對於不是同一個包中的其餘類至關於私有(private)。
受保護(protected)對子類至關於公開,對不是同一包中的沒有父子關係的類至關於私有。
Java中,外部類的修飾符只能是public或默認,
類的成員(包括內部類)的修飾符能夠是以上四種。
修飾符 | 當前類 | 同包 | 子類 | 其餘包 |
public | √ | √ | √ | √ |
protect | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
四、Java基本數據類型:
Java中的基本數據類型只有8個:byte、short、int、long、float、double、char、boolean;
除了基本類型(primitive type)和枚舉類型(enumeration type),剩下的都是引用類型(reference type)。
Java爲每個基本數據類型都引入了對應的包裝類型(wrapper class),int的包裝類就是Integer,從Java 5開始引入了自動裝箱/拆箱機制,使得兩者能夠相互轉換。
Java 爲每一個原始類型提供了包裝類型:
- 原始類型: boolean,char,byte,short,int,long,float,double
- 包裝類型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
示例代碼:
class AutoUnboxingTest { public static void main(String[] args) { Integer a = new Integer(3); Integer b = 3; // 將3自動裝箱成Integer類型 int c = 3; System.out.println(a == b); // false 兩個引用沒有引用同一對象 System.out.println(a == c); // true a自動拆箱成int類型再和c比較 } }
想起來以前代碼中的一個bug 不能用 == 判斷 而是用equals()方法判斷,== 判斷失效
自動裝箱/拆箱示例代碼:
public class Test03 { public static void main(String[] args) { Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150; System.out.println(f1 == f2); // true System.out.println(f3 == f4); // false } }
解析:
f一、f二、f三、f4四個變量都是Integer對象引用,因此下面的==運算比較的不是值而是引用。裝箱的本質是什麼呢?當咱們給一個Integer對象賦一個int值的時候,會調用Integer類的靜態方法valueOf,若是看看valueOf的源代碼就知道發生了什麼。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
IntegerCache是Integer的內部類,其代碼以下所示:
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
簡單的說,若是整型字面量的值在-128到127之間,那麼不會new新的Integer對象,而是直接引用常量池中的Integer對象,因此上面的面試題中f1==f2的結果是true,而f3==f4的結果是false。
五、&和&&的區別?
&運算符有兩種用法:
(1)按位與;(2)邏輯與。
&&運算符是短路與運算。邏輯與跟短路與的差異是很是巨大的,雖然兩者都要求運算符左右兩端的布爾值都是true整個表達式的值纔是true。
&&之因此稱爲短路運算是由於,若是&&左邊的表達式的值是false,右邊的表達式會被直接短路掉,不會進行運算。
不少時候咱們可能都須要用&&而不是&,例如在驗證用戶登陸時斷定用戶名不是null並且不是空字符串,應當寫爲:username != null &&!username.equals(「」),兩者的順序不能交換,更不能用&運算符,由於第一個條件若是不成立,根本不能進行字符串的equals比較,不然會產生NullPointerException異常。
注意:邏輯或運算符(|)和短路或運算符(||)的差異也是如此。
六、解釋內存中的棧(stack)、堆(heap)和靜態區(static area)的用法。(在JVM學習中加深理解)
答:一般咱們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用內存中的棧空間;而經過new關鍵字和構造器建立的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、」hello」和常量都是放在靜態區中。棧空間操做起來最快可是棧很小,一般大量的對象都是放在堆空間,理論上整個內存沒有被其餘進程使用的空間甚至硬盤上的虛擬內存均可以被當成堆空間來使用。
1
|
String str =
new
String(
"hello"
);
|
上面的語句中變量str放在棧上,用new建立出來的字符串對象放在堆上,而」hello」這個字面量放在靜態區。
補充:較新版本的Java(從Java 6的某個更新開始)中使用了一項叫」逃逸分析」的技術,能夠將一些局部對象放在棧上以提高對象的操做性能。
七、String相等的判斷
public static void main(String[] args) { String s01 = "Programming"; String s02 = new String("Programming"); String s03 = "Program" + "ming"; System.out.println(s01 == s02); //false System.out.println(s01 == s03); // true System.out.println(s01 == s01.intern()); //true System.out.println("intern method:"); String s0="kvill"; String s1=new String("kvill"); String s2=new String("kvill"); System.out.println(s0==s1); //false s1.intern(); s2=s2.intern(); System.out.println(s0==s1); //false System.out.println(s0==s1.intern()); // true System.out.println(s0==s2); // true }
結果:
關於s1.intern():
返回字符串對象的規範化表示形式。
一個初始時爲空的字符串池,它由類 String 私有地維護。
當調用 intern 方法時,若是池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法肯定),則返回池中的字符串。不然,將此 String 對象添加到池中,而且返回此 String 對象的引用。
它遵循對於任何兩個字符串 s 和 t,當且僅當 s.equals(t) 爲 true 時,s.intern() == t.intern() 才爲 true。
存在於.class文件中的常量池,在運行期間被jvm裝載,而且能夠擴充。
String的intern()方法就是擴充常量池的一個方法;當一個String實例str調用intern()方法時,java查找常量池中是否有相同unicode的字符串常量,若是有,則返回其引用,若是沒有,則在常量池中增長一個unicode等於str的字符串並返回它的引用。
八、重載(Overload)和重寫(Override)的區別。重載的方法可否根據返回類型進行區分?
方法的重載和重寫都是 實現多態 的方式,
區別在於前者(重載)實現的是編譯時的多態性,然後者(重寫)實現的是運行時的多態性。
重載發生在一個類中,同名的方法若是有不一樣的參數列表(參數類型不一樣、參數個數不一樣或者兩者都不一樣)則視爲重載;
重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。
重載對返回類型沒有特殊的要求。
面試題中曾經有這樣一個問題 – 「爲何不能根據返回類型來區分重載」
九、JVM加載class文件的原理機制
JVM中類的裝載是由 類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責 在運行時查找和裝入類文件中的類。
因爲Java的跨平臺性,通過編譯的Java源程序並非一個可執行程序,而是一個或多個類文件。
當Java程序須要使用某個類時,JVM會確保這個類已經被 加載、鏈接(驗證、準備和解析)和初始化。
類的加載是指把類的.class文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class文件,而後產生與所加載類對應的Class對象。
加載完成後,Class對象還不完整,因此此時的類還不可用。
當類被加載後就進入鏈接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。
最後JVM對類進行初始化,包括:
1)若是類存在直接的父類而且這個類尚未被初始化,那麼就先初始化父類;
2)若是類中存在初始化語句,就依次執行這些初始化語句。
類的加載是由 類加載器 完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。
從Java 2(JDK 1.2)開始,類加載過程採起了 父親委託機制(PDM)。
PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是 根加載器,其餘的加載器都有且僅有一個父類加載器。
類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。
JVM不會向Java程序提供對Bootstrap的引用。
下面是關於幾個類加載器的說明:
- Bootstrap:通常用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar);
- Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;
- System:又叫應用類加載器,其父類是Extension。它是應用最普遍的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。
十、char 型變量中能不能存貯一箇中文漢字,爲何?
char類型能夠存儲一箇中文漢字,由於Java中使用的編碼是Unicode(不選擇任何特定的編碼,直接使用字符在字符集中的編號,這是統一的惟一方法),一個char類型佔2個字節(16比特),因此放一箇中文是沒問題的。
十一、抽象類(abstract class)和接口(interface)有什麼異同?
抽象類和接口都不可以實例化,但能夠定義抽象類和接口類型的引用。
一個類若是繼承了某個抽象類或者實現了某個接口 都須要對其中的抽象方法所有進行實現,不然該類仍然須要被聲明爲抽象類。
接口比抽象類更加抽象,由於抽象類中能夠定義構造器,能夠有抽象方法和具體方法,
而接口中不能定義構造器並且其中的方法所有都是抽象方法。
抽象類中的成員能夠是private、默認、protected、public的,而接口中的成員全都是public的。
抽象類中能夠定義成員變量,而接口中定義的成員變量實際上都是常量。
有抽象方法的類必須被聲明爲抽象類,而抽象類未必要有抽象方法。
十二、靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不一樣?
Static Nested Class是被聲明爲靜態(static)的內部類,它能夠不依賴於外部類實例被實例化。而一般的內部類須要在外部類實例化後才能實例化.
/** * 撲克類(一副撲克) * @author 駱昊 * */ public class Poker { private static String[] suites = {"黑桃", "紅桃", "草花", "方塊"}; private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; private Card[] cards; /** * 構造器 * */ public Poker() { cards = new Card[52]; for(int i = 0; i < suites.length; i++) { for(int j = 0; j < faces.length; j++) { cards[i * 13 + j] = new Card(suites[i], faces[j]); } } } /** * 洗牌 (隨機亂序) * */ public void shuffle() { for(int i = 0, len = cards.length; i < len; i++) { int index = (int) (Math.random() * len); Card temp = cards[index]; cards[index] = cards[i]; cards[i] = temp; } } /** * 發牌 * @param index 發牌的位置 * */ public Card deal(int index) { return cards[index]; } /** * 卡片類(一張撲克) * [內部類] * @author 駱昊 * */ public class Card { private String suite; // 花色 private int face; // 點數 public Card(String suite, int face) { this.suite = suite; this.face = face; } @Override public String toString() { String faceStr = ""; switch(face) { case 1: faceStr = "A"; break; case 11: faceStr = "J"; break; case 12: faceStr = "Q"; break; case 13: faceStr = "K"; break; default: faceStr = String.valueOf(face); } return suite + faceStr; } } }
測試代碼:
class PokerTest { public static void main(String[] args) { Poker poker = new Poker(); poker.shuffle(); // 洗牌 Poker.Card c1 = poker.deal(0); // 發第一張牌 // 對於非靜態內部類Card // 只有經過其外部類Poker對象才能建立Card對象 Poker.Card c2 = poker.new Card("紅心", 1); // 本身建立一張牌 System.out.println(c1); // 洗牌後的第一張 System.out.println(c2); // 打印: 紅心A } }
面試題:
代碼哪些地方會產生編譯錯誤?
class Outer { class Inner {} public static void foo() { new Inner(); } public void bar() { new Inner(); } public static void main(String[] args) { new Inner(); } }
Java中非靜態內部類對象的建立要依賴其外部類對象,上面的面試題中foo和main方法都是靜態方法,靜態方法中沒有this,也就是說沒有所謂的外部類對象,所以沒法建立內部類對象,若是要在靜態方法中建立內部類對象,能夠這樣作:
new Outer().new Inner();
同mybatis中Example類中的Criteria內部類的使用規則
1三、Java 內存管理機制
1四、Java 中會存在內存泄漏嘛? (內存管理機制)
理論上Java由於有垃圾回收機制(GC)不會存在內存泄露問題(這也是Java被普遍使用於服務器端編程的一個重要緣由);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被GC回收,所以也會致使內存泄露的發生。例如hibernate的Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,若是不及時關閉(close)或清空(flush)一級緩存就可能致使內存泄露。下面例子中的代碼也會致使內存泄露。典型的內存泄漏的例子 棧的pop操做
import java.util.Arrays; import java.util.EmptyStackException; public class MyStack<T> { private T[] elements; private int size = 0; private static final int INIT_CAPACITY = 16; public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; } public void push(T elem) { ensureCapacity(); elements[size++] = elem; } public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
其中的pop方法卻存在內存泄露的問題,當咱們用pop方法彈出棧中的對象時,該對象不會被看成垃圾回收,即便使用棧的程序再也不引用這些對象,由於棧內部維護着對這些對象的過時引用(obsolete reference)
1五、抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被synchronized修飾
抽象方法須要子類重寫,而靜態的方法是沒法被重寫的,所以兩者是矛盾的。
本地方法是由本地代碼(如C代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。
synchronized和方法的實現細節有關,抽象方法不涉及實現細節,所以也是相互矛盾的。
1六、靜態變量和實例變量的區別。
靜態變量是被static修飾符修飾的變量,也稱爲類變量,它屬於類,不屬於類的任何一個對象,一個類無論建立多少個對象,靜態變量在內存中有且僅有一個拷貝;
實例變量必須依存於某一實例,須要先建立對象而後經過對象才能訪問到它。
靜態變量能夠實現讓多個對象共享內存。
1七、是否能夠從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?
不能夠,靜態方法只能訪問靜態成員,由於非靜態方法的調用要先建立對象,在調用靜態方法時可能對象並無被初始化。
1八、實現對象克隆?
1). 實現Cloneable接口並重寫Object類中的clone()方法;
2). 實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆,代碼以下。
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class MyUtil { private MyUtil() { throw new AssertionError(); } public static <T> T clone(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); return (T) ois.readObject(); // 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義 // 這兩個基於內存的流只要垃圾回收器清理對象就可以釋放資源,這一點不一樣於對外部資源(如文件流)的釋放 } }
1九、GC機制:
Java提供的GC功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操做方法。Java程序員不用擔憂內存管理,由於垃圾收集器會自動進行管理。要請求垃圾收集,能夠調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能夠屏蔽掉顯示的垃圾回收調用。
垃圾回收能夠有效的防止內存泄露,有效的使用可使用的內存。垃圾回收器一般是做爲一個單獨的低優先級的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,由於服務器端的編程須要有效的防止內存泄露問題,然而時過境遷,現在Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶一般以爲iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的緣由就在於Android系統中垃圾回收的不可預知性。
20、String s = new String(「xyz」);建立了幾個字符串對象?
兩個對象,一個是靜態區的」xyz」,一個是用new建立在堆上的對象。
2一、接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concrete class)?
接口能夠繼承接口,並且支持多重繼承。
抽象類能夠實現(implements)接口,、
抽象類可繼承具體類也能夠繼承抽象類。
2二、一個」.java」源文件中是否能夠包含多個類(不是內部類)?有什麼限制?
能夠,但一個源文件中最多隻能有一個公開類(public class)並且文件名必須和公開類的類名徹底保持一致。
2三、Anonymous Inner Class(匿名內部類)是否能夠繼承其它類?是否能夠實現接口?
能夠繼承其餘類或實現其餘接口,在Swing編程和Android開發中經常使用此方式來實現事件監聽和回調。
2四、內部類能夠引用它的包含類(外部類)的成員嗎?有沒有什麼限制?
一個內部類對象能夠訪問建立它的外部類對象的成員,包括私有成員。
2五、Java 中的final關鍵字有哪些用法?
(1)修飾類:表示該類不能被繼承;
(2)修飾方法:表示方法不能被重寫;
(3)修飾變量:表示變量只能一次賦值之後值不能被修改(常量)。
2六、
class A { static { System.out.print("1"); } public A() { System.out.print("2"); } } class B extends A{ static { System.out.print("a"); } public B() { System.out.print("b"); } } public class Hello { public static void main(String[] args) { A ab = new B(); ab = new B(); } }
輸出:
1a2b2b
建立對象時構造器的調用順序是:先初始化靜態成員,而後調用父類構造器,再初始化非靜態成員,最後調用自身構造器。
考察 Java類 加載機制
2七、數據類型之間的轉換:
- 如何將字符串轉換爲基本數據類型?
- 如何將基本數據類型轉換爲字符串?
答:
- 調用基本數據類型對應的包裝類中的方法parseXXX(String)或valueOf(String)便可返回相應基本類型;
- 一種方法是將基本數據類型與空字符串(」")鏈接(+)便可得到其所對應的字符串;另外一種方法是調用String 類中的valueOf()方法返回相應字符
2八、Date 與Calender之間的轉換
2九、如何實現字符串的反轉及替換?
本身寫實現
使用String或StringBuffer/StringBuilder中的方法。
用遞歸實現字符串反轉,代碼以下所示:
public static String reverse(String originStr) { if(originStr == null || originStr.length() <= 1) return originStr; return reverse(originStr.substring(1)) + originStr.charAt(0); }
還有 用正則實現 數字的三位/四位 加一個空格的替換
正則的正向預查 與 反向預查
30、怎樣將GB2312編碼的字符串轉換爲ISO-8859-1編碼的字符串?不一樣編碼之間的轉換
String s1 = "你好"; String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");
3一、日期和時間:
- 如何取得年月日、小時分鐘秒?
- 如何取得從1970年1月1日0時0分0秒到如今的毫秒數?
- 如何取得某月的最後一天?
- 如何格式化日期?
public class DateTimeTest { public static void main(String[] args) { Calendar cal = Calendar.getInstance(); System.out.println(cal.get(Calendar.YEAR)); System.out.println(cal.get(Calendar.MONTH)); // 0 - 11 System.out.println(cal.get(Calendar.DATE)); System.out.println(cal.get(Calendar.HOUR_OF_DAY)); System.out.println(cal.get(Calendar.MINUTE)); System.out.println(cal.get(Calendar.SECOND)); // Java 8 LocalDateTime dt = LocalDateTime.now(); System.out.println(dt.getYear()); System.out.println(dt.getMonthValue()); // 1 - 12 System.out.println(dt.getDayOfMonth()); System.out.println(dt.getHour()); System.out.println(dt.getMinute()); System.out.println(dt.getSecond()); } }
Calendar.getInstance().getTimeInMillis(); System.currentTimeMillis(); Clock.systemDefaultZone().millis(); // Java 8
Calendar time = Calendar.getInstance(); time.getActualMaximum(Calendar.DAY_OF_MONTH);
import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Date; class DateFormatTest { public static void main(String[] args) { SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd"); Date date1 = new Date(); System.out.println(oldFormatter.format(date1)); // Java 8 DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); LocalDate date2 = LocalDate.now(); System.out.println(date2.format(newFormatter)); } }
3二、獲取制定的時間 昨天的這個時間 今天的零點 和 23:59:59
import java.util.Calendar; class YesterdayCurrent { public static void main(String[] args){ Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -1); System.out.println(cal.getTime()); } }
import java.time.LocalDateTime; class YesterdayCurrent { public static void main(String[] args) { LocalDateTime today = LocalDateTime.now(); LocalDateTime yesterday = today.minusDays(1); System.out.println(yesterday); } }
/** * 字符串轉換成日期 * @param str * @return date */ public Date StrToDate(String str) { if(str.length()<=10){ str = str.trim()+" 00:00:00"; } SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = null; try { date = format.parse(str); } catch (ParseException e) { e.printStackTrace(); } return date; }
先獲取一下當前的時間 Date 格式 yyyy-MM-dd 而後轉換成String 而後拼 String 後面的具體零點時間 仍是 24點-1s時間 拼好制定時間以後 再String轉Date
3三、闡述final、finally、finalize的區別。
- final:修飾符(關鍵字)有三種用法:若是一個類被聲明爲final,意味着它不能再派生出新的子類,即不能被繼承,
所以它和abstract是反義詞。將變量聲明爲final,能夠保證它們在使用中不被改變,被聲明爲final的變量必須在聲明時給定初值,而在之後的引用中只能讀取不可修改。被聲明爲final的方法也一樣只能使用,不能在子類中被重寫。
- finally:一般放在try…catch…的後面構造老是執行代碼塊,這就意味着程序不管正常執行仍是發生異常,這裏的代碼只要JVM不關閉都能執行,能夠將釋放外部資源的代碼寫在finally塊中。
- finalize:Object類中定義的方法,Java中容許使用finalize()方法 在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。
這個方法是由垃圾收集器在銷燬對象時調用的,經過重寫finalize()方法能夠整理系統資源或者執行其餘清理工做。
3四、類ExampleA繼承Exception,類ExampleB繼承ExampleA。
有以下代碼片段:
try { throw new ExampleB("b") } catch(ExampleA e){ System.out.println("ExampleA"); } catch(Exception e){ System.out.println("Exception"); }
請問執行此段代碼的輸出是什麼?
輸出:ExampleA。(根據里氏代換原則[能使用父類型的地方必定能使用子類型],抓取ExampleA類型異常的catch塊可以抓住try塊中拋出的ExampleB類型的異常)
里氏代換原則[能使用父類型的地方必定能使用子類型]
3五、同上面的考察點
class Annoyance extends Exception {} class Sneeze extends Annoyance {} class Human { public static void main(String[] args) throws Exception { try { try { throw new Sneeze(); } catch ( Annoyance a ) { System.out.println("Caught Annoyance"); throw a; } } catch ( Sneeze s ) { System.out.println("Caught Sneeze"); return ; } finally { System.out.println("Hello World!"); } } }
我以爲輸出結果是:
Caught Annoyance
Hello World!
須要驗證結果。
3四、List、Set、Map是否繼承自Collection接口?
List、Set 是,Map 不是。
Map是鍵值對映射容器,與List和Set有明顯的區別,而Set存儲的零散的元素且不容許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形。
3五、闡述ArrayList、Vector、LinkedList的存儲性能和特性。
ArrayList 和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引數據快而插入數據慢,
Vector中的方法因爲添加了synchronized修飾,所以Vector是線程安全的容器,但性能上較ArrayList差,所以已是Java中的遺留容器。
LinkedList使用雙向鏈表實現存儲(將內存中零散的內存單元經過附加的引用關聯起來,造成一個能夠按序號索引的線性結構,這種鏈式存儲方式與數組的連續存儲方式相比,內存的利用率更高),按序號索引數據須要進行前向或後向遍歷,可是插入數據時只須要記錄本項的先後項便可,因此插入速度較快。
Vector屬於遺留容器(Java早期的版本中提供的容器,除此以外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經不推薦使用,可是因爲ArrayList和LinkedListed都是非線程安全的,若是遇到多個線程操做同一個容器的場景,則能夠經過工具類Collections中的synchronizedList方法將其轉換成線程安全的容器後再使用(這是對裝潢模式的應用,將已有對象傳入另外一個類的構造器中建立新的對象來加強實現)。
3六、Collection和Collections的區別?
Collection是一個接口,它是Set、List等容器的父接口;Collections是個一個工具類,提供了一系列的靜態方法來輔助容器操做,這些方法包括對容器的搜索、排序、線程安全化等等。
3七、List、Map、Set三個接口存取元素時,各有什麼特色?
List以特定索引來存取元素,能夠有重複元素。
Set不能存放重複元素(用對象的equals()方法來區分元素是否重複)。
Map保存鍵值對(key-value pair)映射,映射關係能夠是一對一或多對一。
Set和Map容器都有基於哈希存儲和排序樹的兩種實現版本,基於哈希存儲的版本理論存取時間複雜度爲O(1),而基於排序樹版本的實如今插入或刪除元素時會按照元素或元素的鍵(key)構成排序樹從而達到排序和去重的效果。
3八、TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?
TreeSet要求存放的 對象所屬的類 必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。
TreeMap要求存放的鍵值對映射的 鍵 必須實現Comparable接口,從而根據鍵對元素進行排序。
Collections工具類的sort方法有兩種重載的形式,
第一種要求傳入的待排序容器中存放的對象比較實現Comparable接口以實現元素的比較;
第二種不強制性的要求容器中的元素必須可比較,可是要求傳入第二個參數,參數是Comparator接口的子類型(須要重寫compare方法實現元素的比較),至關於一個臨時定義的排序規則,其實就是經過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。
public class Student implements Comparable<Student> { private String name; // 姓名 private int age; // 年齡 public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } @Override public int compareTo(Student o) { return this.age - o.age; // 比較年齡(年齡的升序) } }
import java.util.Set; import java.util.TreeSet; class Test01 { public static void main(String[] args) { Set<Student> set = new TreeSet<>(); // Java 7的鑽石語法(構造器後面的尖括號中不須要寫類型) set.add(new Student("Hao LUO", 33)); set.add(new Student("XJ WANG", 32)); set.add(new Student("Bruce LEE", 60)); set.add(new Student("Bob YANG", 22)); for(Student stu : set) { System.out.println(stu); } // 輸出結果: // Student [name=Bob YANG, age=22] // Student [name=XJ WANG, age=32] // Student [name=Hao LUO, age=33] // Student [name=Bruce LEE, age=60] } }
public class Student { private String name; // 姓名 private int age; // 年齡 public Student(String name, int age) { this.name = name; this.age = age; } /** * 獲取學生姓名 */ public String getName() { return name; } /** * 獲取學生年齡 */ public int getAge() { return age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; class Test02 { public static void main(String[] args) { List<Student> list = new ArrayList<>(); // Java 7的鑽石語法(構造器後面的尖括號中不須要寫類型) list.add(new Student("Hao LUO", 33)); list.add(new Student("XJ WANG", 32)); list.add(new Student("Bruce LEE", 60)); list.add(new Student("Bob YANG", 22)); // 經過sort方法的第二個參數傳入一個Comparator接口對象 // 至關因而傳入一個比較對象大小的算法到sort方法中 // 因爲Java中沒有函數指針、仿函數、委託這樣的概念 // 所以要將一個算法傳入一個方法中惟一的選擇就是經過接口回調 Collections.sort(list, new Comparator<Student> () { @Override public int compare(Student o1, Student o2) { return o1.getName().compareTo(o2.getName()); // 比較學生姓名 } }); for(Student stu : list) { System.out.println(stu); } // 輸出結果: // Student [name=Bob YANG, age=22] // Student [name=Bruce LEE, age=60] // Student [name=Hao LUO, age=33] // Student [name=XJ WANG, age=32] } }
3九、Thread類的sleep()方法和對象的wait()方法均可以讓線程暫停執行,它們有什麼區別?
sleep()方法(休眠)是 線程類(Thread)的靜態方法,調用此方法會 讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其餘線程,可是對象的鎖依然保持,所以休眠時間結束後會自動恢復(線程回到就緒狀態,請參考線程狀態轉換圖)。
wait()是 Object類的方法,調用對象的wait()方法致使 當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),若是線程從新得到對象的鎖就能夠進入就緒狀態。
40、
進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,是操做系統進行資源分配和調度的一個獨立單位;
線程是進程的一個實體,是CPU調度和分派的基本單位,是比進程更小的能獨立運行的基本單位。線程的劃分尺度小於進程,這使得多線程程序的併發性高;
進程在執行時一般擁有獨立的內存單元,而線程之間能夠共享內存。
4一、線程的sleep()方法和yield()方法有什麼區別?
① sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
② 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
③ sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操做系統CPU調度相關)具備更好的可移植性。
4二、當一個線程進入一個對象的synchronized方法A以後,其它線程是否可進入此對象的synchronized方法B?
不能。
其它線程只能訪問該對象的非同步方法,同步方法則不能進入。
由於非靜態方法上的synchronized修飾符要求執行方法時要得到對象的鎖,若是已經進入A方法說明對象鎖已經被取走,那麼試圖進入B方法的線程就只能在 等鎖池 (注意不是等待池哦)中等待對象的鎖。
4三、與線程同步以及線程調度相關的方法。
- wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
- sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;
- notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;
- notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;
下面的例子演示了100個線程同時向一個銀行帳戶中存入1元錢,在沒有使用同步機制和使用同步機制狀況下的執行狀況。
銀行帳戶類:
/** * 銀行帳戶 * @author 駱昊 * */ public class Account { private double balance; // 帳戶餘額 /** * 存款 * @param money 存入金額 */ public void deposit(double money) { double newBalance = balance + money; try { Thread.sleep(10); // 模擬此業務須要一段處理時間 } catch(InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } /** * 得到帳戶餘額 */ public double getBalance() { return balance; } }
存錢線程類:
/** * 存錢線程 * @author 駱昊 * */ public class AddMoneyThread implements Runnable { private Account account; // 存入帳戶 private double money; // 存入金額 public AddMoneyThread(Account account, double money) { this.account = account; this.money = money; } @Override public void run() { account.deposit(money); } }
測試類:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test01 { public static void main(String[] args) { Account account = new Account(); ExecutorService service = Executors.newFixedThreadPool(100); for(int i = 1; i <= 100; i++) { service.execute(new AddMoneyThread(account, 1)); } service.shutdown(); while(!service.isTerminated()) {} System.out.println("帳戶餘額: " + account.getBalance()); } }
在沒有同步的狀況下,執行結果一般是顯示帳戶餘額在10元如下,出現這種情況的緣由是,當一個線程A試圖存入1元的時候,另一個線程B也可以進入存款的方法中,線程B讀取到的帳戶餘額仍然是線程A存入1元錢以前的帳戶餘額,所以也是在原來的餘額0上面作了加1元的操做,同理線程C也會作相似的事情,因此最後100個線程執行結束時,原本指望帳戶餘額爲100元,但實際獲得的一般在10元如下(極可能是1元哦)。解決這個問題的辦法就是同步,當一個線程對銀行帳戶存錢時,須要將此帳戶鎖定,待其操做完成後才容許其餘的線程進行操做,代碼有以下幾種調整方案:
一、在銀行帳戶的存款(deposit)方法上同步(synchronized)關鍵字
/** * 銀行帳戶 * @author 駱昊 * */ public class Account { private double balance; // 帳戶餘額 /** * 存款 * @param money 存入金額 */ public synchronized void deposit(double money) { double newBalance = balance + money; try { Thread.sleep(10); // 模擬此業務須要一段處理時間 } catch(InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } /** * 得到帳戶餘額 */ public double getBalance() { return balance; } }
二、在線程調用存款方法時對銀行帳戶進行同步
/** * 存錢線程 * @author 駱昊 * */ public class AddMoneyThread implements Runnable { private Account account; // 存入帳戶 private double money; // 存入金額 public AddMoneyThread(Account account, double money) { this.account = account; this.money = money; } @Override public void run() { synchronized (account) { account.deposit(money); } } }
三、經過Java 5顯示的鎖機制,爲每一個銀行帳戶建立一個鎖對象,在存款操做進行加鎖和解鎖的操做
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 銀行帳戶 * * @author 駱昊 * */ public class Account { private Lock accountLock = new ReentrantLock(); private double balance; // 帳戶餘額 /** * 存款 * * @param money * 存入金額 */ public void deposit(double money) { accountLock.lock(); try { double newBalance = balance + money; try { Thread.sleep(10); // 模擬此業務須要一段處理時間 } catch (InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } finally { accountLock.unlock(); } } /** * 得到帳戶餘額 */ public double getBalance() { return balance; } }
上述三種方式對代碼進行修改後,重寫執行測試代碼Test01,將看到最終的帳戶餘額爲100元。固然也可使用Semaphore或CountdownLatch來實現同步。
4四、編寫多線程程序有幾種實現方式?
Java 5之前實現多線程有兩種實現方法:
一種是繼承Thread類;
另外一種是實現Runnable接口。
兩種方式都要經過重寫run()方法來定義線程的行爲,推薦使用後者,由於Java中的繼承是單繼承,一個類有一個父類,若是繼承了Thread類就沒法再繼承其餘類了,顯然使用Runnable接口更爲靈活。
Java 5之後建立線程還有第三種方式:實現Callable接口,該接口中的call方法能夠在線程執行結束時產生一個返回值
4五、面向對象的」六原則一法則
- 單一職責原則:一個類只作它該作的事情。
(單一職責原則想表達的就是」高內聚」,寫代碼最終極的原則只有六個字」高內聚、低耦合」,就如同葵花寶典或辟邪劍譜的中心思想就八個字」欲練此功必先自宮」,所謂的高內聚就是一個代碼模塊只完成一項功能,在面向對象中,若是隻讓一個類完成它該作的事,而不涉及與它無關的領域就是踐行了高內聚的原則,這個類就只有單一職責。咱們都知道一句話叫」由於專一,因此專業」,一個對象若是承擔太多的職責,那麼註定它什麼都作很差。這個世界上任何好的東西都有兩個特徵,一個是功能單一,好的相機絕對不是電視購物裏面賣的那種一個機器有一百多種功能的,它基本上只能照相;另外一個是模塊化,好的自行車是組裝車,從減震叉、剎車到變速器,全部的部件都是能夠拆卸和從新組裝的,好的乒乓球拍也不是成品拍,必定是底板和膠皮能夠拆分和自行組裝的,一個好的軟件系統,它裏面的每一個功能模塊也應該是能夠輕易的拿到其餘系統中使用的,這樣才能實現軟件複用的目標。)
- 開閉原則:軟件實體應當對擴展開放,對修改關閉。
(在理想的狀態下,當咱們須要爲一個軟件系統增長新功能時,只須要從原來的系統派生出一些新類就能夠,不須要修改原來的任何一行代碼。要作到開閉有兩個要點:①抽象是關鍵,一個系統中若是沒有抽象類或接口系統就沒有擴展點;②封裝可變性,將系統中的各類可變因素封裝到一個繼承結構中,若是多個可變因素混雜在一塊兒,系統將變得複雜而換亂,若是不清楚如何封裝可變性,能夠參考《設計模式精解》一書中對橋樑模式的講解的章節。)
- 依賴倒轉原則:面向接口編程。
(該原則說得直白和具體一些就是聲明方法的參數類型、方法的返回類型、變量的引用類型時,儘量使用抽象類型而不用具體類型,由於抽象類型能夠被它的任何一個子類型所替代,請參考下面的里氏替換原則。)
-里氏替換原則:任什麼時候候均可以用子類型替換掉父類型。
(關於里氏替換原則的描述,Barbara Liskov女士的描述比這個要複雜得多,但簡單的說就是能用父類型的地方就必定能使用子類型。里氏替換原則能夠檢查繼承關係是否合理,若是一個繼承關係違背了里氏替換原則,那麼這個繼承關係必定是錯誤的,須要對代碼進行重構。例如讓貓繼承狗,或者狗繼承貓,又或者讓正方形繼承長方形都是錯誤的繼承關係,由於你很容易找到違反里氏替換原則的場景。須要注意的是:子類必定是增長父類的能力而不是減小父類的能力,由於子類比父類的能力更多,把能力多的對象當成能力少的對象來用固然沒有任何問題。)
- 接口隔離原則:接口要小而專,毫不能大而全。
(臃腫的接口是對接口的污染,既然接口表示能力,那麼一個接口只應該描述一種能力,接口也應該是高度內聚的。例如,琴棋書畫就應該分別設計爲四個接口,而不該設計成一個接口中的四個方法,由於若是設計成一個接口中的四個方法,那麼這個接口很難用,畢竟琴棋書畫四樣都精通的人仍是少數,而若是設計成四個接口,會幾項就實現幾個接口,這樣的話每一個接口被複用的可能性是很高的。Java中的接口表明能力、表明約定、表明角色,可否正確的使用接口必定是編程水平高低的重要標識。)
- 合成聚合複用原則:優先使用聚合或合成關係複用代碼。
(經過繼承來複用代碼是面向對象程序設計中被濫用得最多的東西,由於全部的教科書都無一例外的對繼承進行了鼓吹從而誤導了初學者,類與類之間簡單的說有三種關係,Is-A關係、Has-A關係、Use-A關係,分別表明繼承、關聯和依賴。其中,關聯關係根據其關聯的強度又能夠進一步劃分爲關聯、聚合和合成,但說白了都是Has-A關係,合成聚合複用原則想表達的是優先考慮Has-A關係而不是Is-A關係複用代碼,緣由嘛能夠本身從百度上找到一萬個理由,須要說明的是,即便在Java的API中也有很多濫用繼承的例子,例如Properties類繼承了Hashtable類,Stack類繼承了Vector類,這些繼承明顯就是錯誤的,更好的作法是在Properties類中放置一個Hashtable類型的成員而且將其鍵和值都設置爲字符串來存儲數據,而Stack類的設計也應該是在Stack類中放一個Vector對象來存儲數據。記住:任什麼時候候都不要繼承工具類,工具是能夠擁有並可使用的,而不是拿來繼承的。)
- 迪米特法則:迪米特法則又叫最少知識原則,一個對象應當對其餘對象有儘量少的瞭解。
(迪米特法則簡單的說就是如何作到」低耦合」,門面模式和調停者模式就是對迪米特法則的踐行。對於門面模式能夠舉一個簡單的例子,你去一家公司洽談業務,你不須要了解這個公司內部是如何運做的,你甚至能夠對這個公司一無所知,去的時候只須要找到公司入口處的前臺美女,告訴她們你要作什麼,她們會找到合適的人跟你接洽,前臺的美女就是公司這個系統的門面。再複雜的系統均可覺得用戶提供一個簡單的門面,Java Web開發中做爲前端控制器的Servlet或Filter不就是一個門面嗎,瀏覽器對服務器的運做方式一無所知,可是經過前端控制器就可以根據你的請求獲得相應的服務。調停者模式也能夠舉一個簡單的例子來講明,例如一臺計算機,CPU、內存、硬盤、顯卡、聲卡各類設備須要相互配合才能很好的工做,可是若是這些東西都直接鏈接到一塊兒,計算機的佈線將異常複雜,在這種狀況下,主板做爲一個調停者的身份出現,它將各個設備鏈接在一塊兒而不須要每一個設備之間直接交換數據,這樣就減少了系統的耦合度和複雜度,以下圖所示。迪米特法則用通俗的話來將就是不要和陌生人打交道,若是真的須要,找一個本身的朋友,讓他替你和陌生人打交道。)