2019年Java面試題基礎系列228道(2),查漏補缺!

2019年Java面試題基礎系列228道

上一篇更新1~20題的答案解析

2019年Java面試題基礎系列228道(1),快看看哪些你還不會?

 

本次更新Java 面試題(一)的21~50題答案

21、描述一下 JVM 加載 class 文件的原理機制?

22、char 型變量中能不能存貯一箇中文漢字,爲什麼?

23、抽象類(abstract class)和接口(interface)有什麼異同?

24、靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不同?

25、Java 中會存在內存泄漏嗎,請簡單描述。

26、抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被 synchronized 修飾?

27、闡述靜態變量和實例變量的區別。

28、是否可以從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?

29、如何實現對象克隆?

30、GC 是什麼?爲什麼要有 GC?

31、String s = new String(“xyz”);創建了幾個字符串對象?

32 、 接 口 是 否 可 繼 承 ( extends ) 接 口 ? 抽 象 類 是 否 可 實 現(implements)接口?抽象類是否可繼承具體類(concrete class)?

33、一個”.java”源文件中是否可以包含多個類(不是內部類)?有什麼限制?

34、Anonymous Inner Class(匿名內部類)是否可以繼承其它類?是否可以實現接口?

35、內部類可以引用它的包含類(外部類)的成員嗎?有沒有什麼限制?

36、Java 中的 final 關鍵字有哪些用法?

37、指出下面程序的運行結果

38、數據類型之間的轉換:

39、如何實現字符串的反轉及替換?

40、怎樣將 GB2312 編碼的字符串轉換爲 ISO-8859-1 編碼的字符串?

41、日期和時間:

42、打印昨天的當前時刻。

43、比較一下 Java 和 JavaSciprt。

44、什麼時候用斷言(assert)?

45、Error 和 Exception 有什麼區別?

46、try{}裏有一個 return 語句,那麼緊跟在這個 try 後的 finally{}裏的代碼會不會被執行,什麼時候被執行,在 return 前還是後?

47、Java 語言如何進行異常處理,關鍵字:throws、throw、try、catch、finally 分別如何使用?

48、運行時異常與受檢異常有何異同?

49、列出一些你常見的運行時異常?

50、闡述 final、finally、finalize 的區別。

 

21、描述一下 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)開始,從 Java 2(JDK 1.2)開始,類加載過程採取了父親委託機制(PDM)。PDM 更好的保證了 Java 平臺的安全性,在該機制中,JVM 自帶的 Bootstrap 是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM 不會向 Java 程序提供對 Bootstrap 的引用。下面是關於幾個類加載器的說明:

(1) Bootstrap:一般用本地代碼實現,負責加載 JVM 基礎核心類庫(rt.jar);

(2) Extension:從 java.ext.dirs 系統屬性所指定的目錄中加載類庫,它的父加載器是 Bootstrap;

(3) System:又叫應用類加載器,其父類是 Extension。它是應用最廣泛的類加載器。它從環境變量 classpath 或者系統屬性 java.class.path 所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

 

22、char 型變量中能不能存貯一箇中文漢字,爲什麼?

char 類型可以存儲一箇中文漢字,因爲 Java 中使用的編碼是 Unicode(不選擇任何特定的編碼,直接使用字符在字符集中的編號,這是統一的唯一方法),一個 char 類型佔 2 個字節(16 比特),所以放一箇中文是沒問題的。

補充:使用 Unicode 意味着字符在 JVM 內部和外部有不同的表現形式,在 JVM內部都是 Unicode,當這個字符被從 JVM 內部轉移到外部時(例如存入文件系統中),需要進行編碼轉換。所以 Java 中有字節流和字符流,以及在字符流和字節流之間進行轉換的轉換流,如 InputStreamReader 和 OutputStreamReader,這兩個類是字節流和字符流之間的適配器類,承擔了編碼轉換的任務;對於 C 程序員來說,要完成這樣的編碼轉換恐怕要依賴於 union(聯合體/共用體)共享內存的特徵來實現了。

 

23、抽象類(abstract class)和接口(interface)有什麼異同?

抽象類和接口都不能夠實例化,但可以定義抽象類和接口類型的引用。一個類如果繼承了某個抽象類或者實現了某個接口都需要對其中的抽象方法全部進行實現,否則該類仍然需要被聲明爲抽象類。接口比抽象類更加抽象,因爲抽象類中可以定義構造器,可以有抽象方法和具體方法,而接口中不能定義構造器而且其中的方法全部都是抽象方法。抽象類中的成員可以是 private、默認、protected、public 的,而接口中的成員全都是 public 的。抽象類中可以定義成員變量,而接口中定義的成員變量實際上都是常量。有抽象方法的類必須被聲明爲抽象類,而抽象類未必要有抽象方法。

 

24、靜態嵌套類(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();

 

25、Java 中會存在內存泄漏嗎,請簡單描述。

理論上 Java 因爲有垃圾回收機制(GC)不會存在內存泄露問題(這也是 Java 被廣泛使用於服務器端編程的一個重要原因);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被 GC 回收,因此也會導致內存泄露的發生。例如Hibernate 的 Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,如果不及時關閉(close)或清空(flush)一級緩存就可能導致內存泄露。下面例子中的代碼也會導致內存泄露。

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);
		}
	}
}

上面的代碼實現了一個棧(先進後出(FILO))結構,乍看之下似乎沒有什麼明顯的問題,它甚至可以通過你編寫的各種單元測試。然而其中的 pop 方法卻存在內存泄露的問題,當我們用 pop 方法彈出棧中的對象時,該對象不會被當作垃圾回收,即使使用棧的程序不再引用這些對象,因爲棧內部維護着對這些對象的過期引 用(obsolete reference)。在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無意識的對象保持。如果一個對象引用被無意識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其他對象,即使這樣的對象只有少數幾個,也可能會導致很多的對象被排除在垃圾回收之外,從而對性能造成重大影響,極端情況下會引發 Disk Paging(物理內存與硬盤的虛擬內存交換數據),甚至造成 OutOfMemoryError。

 

26、抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被 synchronized修飾?

都不能。抽象方法需要子類重寫,而靜態的方法是無法被重寫的,因此二者是矛盾的。本地方法是由本地代碼(如 C 代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。synchronized 和方法的實現細節有關,抽象方法不涉及實現細節,因此也是相互矛盾的。

 

27、闡述靜態變量和實例變量的區別。

靜態變量是被 static 修飾符修飾的變量,也稱爲類變量,它屬於類,不屬於類的任何一個對象,一個類不管創建多少個對象,靜態變量在內存中有且僅有一個拷貝;實例變量必須依存於某一實例,需要先創建對象然後通過對象才能訪問到它。靜態變量可以實現讓多個對象共享內存。

補充:在 Java 開發中,上下文類和工具類中通常會有大量的靜態成員。

 

28、是否可以從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?

不可以,靜態方法只能訪問靜態成員,因爲非靜態方法的調用要先創建對象,在調用靜態方法時可能對象並沒有被初始化。

 

29、如何實現對象克隆?

有兩種方式:

1). 實現 Cloneable 接口並重寫 Object 類中的 clone()方法;

2). 實現 Serializable 接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆,代碼如下。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyUtil {
	private MyUtil() {
		throw new AssertionError();
	}
	@SuppressWarnings("unchecked")
	public static <T extends Serializable> 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 方法沒有任何意義
		// 這兩個基於內存的流只要垃圾回收器清理對象就能夠釋放資源,這
		一點不同於對外部資源(如文件流)的釋放
	}
}

下面是測試代碼:

import java.io.Serializable;
/**
* 人類
* @author 駱昊
*
*/
class Person implements Serializable {
	private static final long serialVersionUID = -9102017020286042305L;
	private String name;
	// 姓名
	private int age;
	// 年齡
	private Car car;
	// 座駕
	public Person(String name, int age, Car car) {
		this.name = name;
		this.age = age;
		this.car = car;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Car getCar() {
		return car;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", car=" +
		car + "]";
	}
}
/**
* 小汽車類
* @author 駱昊
*
*/
class Car implements Serializable {
	private static final long serialVersionUID = -5713945027627603702L;
	private String brand;
	// 品牌
	private int maxSpeed;
	// 最高時速
	public Car(String brand, int maxSpeed) {
		this.brand = brand;
		this.maxSpeed = maxSpeed;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public int getMaxSpeed() {
		return maxSpeed;
	}
	public void setMaxSpeed(int maxSpeed) {
		this.maxSpeed = maxSpeed;
	}
	@Override
	public String toString() {
		return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed +
		"]";
	}
}
class CloneTest {
	public static void main(String[] args) {
		try {
			Person p1 = new Person("Hao LUO", 33, new Car("Benz",
			300));
			Person p2 = MyUtil.clone(p1);
			// 深度克隆
			p2.getCar().setBrand("BYD");
			// 修改克隆的 Person 對象 p2 關聯的汽車對象的品牌屬性
			// 原來的 Person 對象 p1 關聯的汽車不會受到任何影響
			// 因爲在克隆 Person 對象時其關聯的汽車對象也被克隆了
			System.out.println(p1);
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
}

注意:基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用 Object 類的 clone 方法克隆對象。讓問題在編譯的時候暴露出來總是好過把問題留到運行時。

 

30、GC 是什麼?爲什麼要有 GC?

GC 是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java 提供的 GC 功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方法。Java 程序員不用擔心內存管理,因爲垃圾收集器會自動進行管理。要請求垃圾收集,可以調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但 JVM 可以屏蔽掉顯示的垃圾回收調用。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作爲一個單獨的低優先級的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。在 Java 誕生初期,垃圾回收是 Java最大的亮點之一,因爲服務器端的編程需要有效的防止內存泄露問題,然而時過境遷,如今 Java 的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶通常覺得 iOS 的系統比 Android 系統有更好的用戶體驗,其中一個深層次的原因就在於 Android 系統中垃圾回收的不可預知性。

補充:垃圾回收機制有很多種,包括:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。標準的 Java 進程既有棧又有堆。棧保存了原始型局部變量,堆保存了要創建的對象。Java 平臺對堆內存回收和再利用的基本算法被稱爲標記和清除,但是 Java 對其進行了改進,採用“分代式垃圾收集”。這種方法會跟 Java對象的生命週期將堆內存劃分爲不同的區域,在垃圾收集過程中,可能會將對象移動到不同區域:

(1)伊甸園(Eden):這是對象最初誕生的區域,並且對大多數對象來說,這裏是它們唯一存在過的區域。

(2)倖存者樂園(Survivor):從伊甸園倖存下來的對象會被挪到這裏。

(3)終身頤養園(Tenured):這是足夠老的倖存對象的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把對象放進終身頤養園時,就會觸發一次完全收集(Major-GC),這裏可能還會牽扯到壓縮,以便爲大對象騰出足夠的空間。

與垃圾回收相關的 JVM 參數:

-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
 -Xmn — 堆中年輕代的大小
 -XX:-DisableExplicitGC — 讓 System.gc()不產生任何作用
 -XX:+PrintGCDetails — 打印 GC 的細節
 -XX:+PrintGCDateStamps — 打印 GC 操作的時間戳
 -XX:NewSize / XX:MaxNewSize — 設置新生代大小/新生代最大大小
 -XX:NewRatio — 可以設置老生代和新生代的比例
 -XX:PrintTenuringDistribution — 設置每次新生代 GC 後輸出倖存者
樂園中對象年齡的分佈
 -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老
年代閥值的初始值和最大值
 -XX:TargetSurvivorRatio:設置倖存區的目標使用率 

 

31、String s = new String(“xyz”);創建了幾個字符串對象?

兩個對象,一個是靜態區的”xyz”,一個是用 new 創建在堆上的對象。

 

32、接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concreteclass)?

接口可以繼承接口,而且支持多重繼承。抽象類可以實現(implements)接口,抽象類可繼承具體類也可以繼承抽象類。

 

33、一個”.java”源文件中是否可以包含多個類(不是內部類)?有什麼限制?

可以,但一個源文件中最多只能有一個公開類(public class)而且文件名必須和公開類的類名完全保持一致。

 

34、Anonymous Inner Class(匿名內部類)是否可以繼承其它類?是否可以實現接口?

可以繼承其他類或實現其他接口,在 Swing 編程和 Android 開發中常用此方式來實現事件監聽和回調。

 

35、內部類可以引用它的包含類(外部類)的成員嗎?有沒有什麼限制?

一個內部類對象可以訪問創建它的外部類對象的成員,包括私有成員。

 

36、Java 中的 final 關鍵字有哪些用法?

(1)修飾類:表示該類不能被繼承;
(2)修飾方法:表示方法不能被重寫;
(3)修飾變量:表示變量只能一次賦值以後值不能被修改(常量)。

 

37、指出下面程序的運行結果

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。創建對象時構造器的調用順序是:先初始化靜態成員,然後調用父類構造器,再初始化非靜態成員,最後調用自身構造器。

提示:如果不能給出此題的正確答案,說明之前第 21 題 Java 類加載機制還沒有完全理解,趕緊再看看吧。

 

38、數據類型之間的轉換:

(1) 如何將字符串轉換爲基本數據類型?

(2) 如何將基本數據類型轉換爲字符串?

答:

(1)調用基本數據類型對應的包裝類中的方法 parseXXX(String)或valueOf(String)即可返回相應基本類型;

(2)一種方法是將基本數據類型與空字符串(”“)連接(+)即可獲得其所對應的字符串;另一種方法是調用 String 類中的 valueOf()方法返回相應字符串

 

39、如何實現字符串的反轉及替換?

方法很多,可以自己寫實現也可以使用 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);
}

 

40、怎樣將 GB2312 編碼的字符串轉換爲 ISO-8859-1 編碼的字符串?

代碼如下所示:

String s1 = "你好";
String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");

 

41、日期和時間:

(1)如何取得年月日、小時分鐘秒?

(2) 如何取得從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在的毫秒數?

(3) 如何取得某月的最後一天?

(4)如何格式化日期?

答:

問題 1:創建 java.util.Calendar 實例,調用其 get()方法傳入不同的參數即可獲得參數所對應的值。Java 8 中可以使用 java.time.LocalDateTimel 來獲取,代碼如下所示。

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());
	}
}

問題 2:以下方法均可獲得該毫秒數。

Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis();
// Java 8

問題 3:代碼如下所示。

Calendar time = Calendar.getInstance();
time.getActualMaximum(Calendar.DAY_OF_MONTH

問題 4:利用 java.text.DataFormat 的子類(如 SimpleDateFormat 類)中的format(Date)方法可將日期格式化。Java 8 中可以用java.time.format.DateTimeFormatter 來格式化時間日期,代碼如下所示。

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));
	}
}
補充:Java 的時間日期 API 一直以來都是被詬病的東西,爲了解決這一問題,Java8 中引入了新的時間日期 API,其中包括 LocalDate、LocalTime、LocalDateTime、Clock、Instant 等類,這些的類的設計都使用了不變模式,因此是線程安全的設計。

 

42、打印昨天的當前時刻。

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());
	}
}

在 Java 8 中,可以用下面的代碼實現相同的功能。

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);
	}
}

 

43、比較一下 Java 和 JavaSciprt。

JavaScript 與 Java 是兩個公司開發的不同的兩個產品。Java 是原 SunMicrosystems 公司推出的面向對象的程序設計語言,特別適合於互聯網應用程序開發;而 JavaScript 是 Netscape 公司的產品,爲了擴展 Netscape 瀏覽器的功能而開發的一種可以嵌入 Web 頁面中運行的基於對象和事件驅動的解釋性語言。JavaScript 的前身是 LiveScript;而 Java 的前身是 Oak 語言。

下面對兩種語言間的異同作如下比較:

(1)基於對象和麪向對象:Java 是一種真正的面向對象的語言,即使是開發簡單的程序,必須設計對象;JavaScript 是種腳本語言,它可以用來製作與網絡無關的,與用戶交互作用的複雜軟件。它是一種基於對象(Object-Based)和事件驅動(Event-Driven)的編程語言,因而它本身提供了非常豐富的內部對象供設計人員使用。

(2)解釋和編譯:Java 的源代碼在執行之前,必須經過編譯。JavaScript 是一種解釋性編程語言,其源代碼不需經過編譯,由瀏覽器解釋執行。(目前的瀏覽器幾乎都使用了 JIT(即時編譯)技術來提升 JavaScript 的運行效率)

(3)強類型變量和類型弱變量:Java 採用強類型變量檢查,即所有變量在編譯之前必須作聲明;JavaScript 中變量是弱類型的,甚至在使用變量前可以不作聲明,JavaScript 的解釋器在運行時檢查推斷其數據類型。

(4)代碼格式不一樣。

補充:上面列出的四點是網上流傳的所謂的標準答案。其實 Java 和 JavaScript最重要的區別是一個是靜態語言,一個是動態語言。目前的編程語言的發展趨勢是函數式語言和動態語言。在 Java 中類(class)是一等公民,而 JavaScript 中函數(function)是一等公民,因此 JavaScript 支持函數式編程,可以使用 Lambda函數和閉包(closure),當然 Java 8 也開始支持函數式編程,提供了對 Lambda表達式以及函數式接口的支持。對於這類問題,在面試的時候最好還是用自己的語言回答會更加靠譜,不要背網上所謂的標準答案。

 

44、什麼時候用斷言(assert)?

斷言在軟件開發中是一種常用的調試方式,很多開發語言中都支持這種機制。一般來說,斷言用於保證程序最基本、關鍵的正確性。斷言檢查通常在開發和測試時開啓。爲了保證程序的執行效率,在軟件發佈後斷言檢查通常是關閉的。斷言是一個包含布爾表達式的語句,在執行這個語句時假定該表達式爲 true;如果表達式的值爲 false,那麼系統會報告一個 AssertionError。斷言的使用如下面的代碼所示:

assert(a > 0); // throws an AssertionError if a <= 0

斷言可以有兩種形式:

assert Expression1;

assert Expression1 : Expression2 ;

Expression1 應該總是產生一個布爾值。

Expression2 可以是得出一個值的任意表達式;這個值用於生成顯示更多調試信息的字符串消息。

要在運行時啓用斷言,可以在啓動 JVM 時使用-enableassertions 或者-ea 標記。要在運行時選擇禁用斷言,可以在啓動 JVM 時使用-da 或者-disableassertions標記。要在系統類中啓用或禁用斷言,可使用-esa 或-dsa 標記。還可以在包的基礎上啓用或者禁用斷言。

注意:斷言不應該以任何方式改變程序的狀態。簡單的說,如果希望在不滿足某些條件時阻止代碼的執行,就可以考慮用斷言來阻止它。

 

45、Error 和 Exception 有什麼區別?

Error 表示系統級的錯誤和程序不必處理的異常,是恢復不是不可能但很困難的情況下的一種嚴重問題;比如內存溢出,不可能指望程序能處理這樣的情況;

Exception 表示需要捕捉或者需要程序進行處理的異常,是一種設計或實現問題;也就是說,它表示如果程序運行正常,從不會發生的情況。

 

46、try{}裏有一個 return 語句,那麼緊跟在這個 try 後的finally{}裏的代碼會不會被執行,什麼時候被執行,在 return前還是後?

會執行,在方法返回調用者前執行。

注意:在 finally 中改變返回值的做法是不好的,因爲如果存在 finally 代碼塊,try中的 return 語句不會立馬返回調用者,而是記錄下返回值待 finally 代碼塊執行完畢之後再向調用者返回其值,然後如果在 finally 中修改了返回值,就會返回修改後的值。顯然,在 finally 中返回或者修改返回值會對程序造成很大的困擾,C#中直接用編譯錯誤的方式來阻止程序員幹這種齷齪的事情,Java 中也可以通過提升編譯器的語法檢查級別來產生警告或錯誤,Eclipse 中可以在如圖所示的地方進行設置,強烈建議將此項設置爲編譯錯誤。

 

47、Java 語言如何進行異常處理,關鍵字:throws、throw、try、catch、finally 分別如何使用?

Java 通過面向對象的方法進行異常處理,把各種不同的異常進行分類,並提供了良好的接口。在 Java 中,每個異常都是一個對象,它是 Throwable 類或其子類的實例。當一個方法出現異常後便拋出一個異常對象,該對象中包含有異常信息,調用這個對象的方法可以捕獲到這個異常並可以對其進行處理。Java 的異常處理是通過 5 個關鍵詞來實現的:try、catch、throw、throws 和 finally。一般情況下是用 try 來執行一段程序,如果系統會拋出(throw)一個異常對象,可以通過它的類型來捕獲(catch)它,或通過總是執行代碼塊(finally)來處理;try 用來指定一塊預防所有異常的程序;catch 子句緊跟在 try 塊後面,用來指定你想要捕獲的異常的類型;throw 語句用來明確地拋出一個異常;throws 用來聲明一個方法可能拋出的各種異常(當然聲明異常時允許無病呻吟);finally 爲確保一段代碼不管發生什麼異常狀況都要被執行;try 語句可以嵌套,每當遇到一個 try 語句,異常的結構就會被放入異常棧中,直到所有的 try 語句都完成。如果下一級的try 語句沒有對某種異常進行處理,異常棧就會執行出棧操作,直到遇到有處理這種異常的 try 語句或者最終將異常拋給 JVM。

 

48、運行時異常與受檢異常有何異同?

異常表示程序運行過程中可能出現的非正常狀態,運行時異常表示虛擬機的通常操作中可能遇到的異常,是一種常見運行錯誤,只要程序設計得沒有問題通常就不會發生。受檢異常跟程序運行的上下文環境有關,即使程序設計無誤,仍然可能因使用的問題而引發。Java 編譯器要求方法必須聲明拋出可能發生的受檢異常,但是並不要求必須聲明拋出未被捕獲的運行時異常。異常和繼承一樣,是面向對象程序設計中經常被濫用的東西,在 Effective Java 中對異常的使用給出了以下指導原則:

(1)不要將異常處理用於正常的控制流(設計良好的 API 不應該強迫它的調用者爲了正常的控制流而使用異常)

(2)對可以恢復的情況使用受檢異常,對編程錯誤使用運行時異常

(3)避免不必要的使用受檢異常(可以通過一些狀態檢測手段來避免異常的發生)

(4)優先使用標準的異常

(5)每個方法拋出的異常都要有文檔

(6)保持異常的原子性

(7)不要在 catch 中忽略掉捕獲到的異常

 

49、列出一些你常見的運行時異常?

(1)ArithmeticException(算術異常)

(2) ClassCastException (類轉換異常)

(3) IllegalArgumentException (非法參數異常)

(4) IndexOutOfBoundsException (下標越界異常)

(5) NullPointerException (空指針異常)

(6) SecurityException (安全異常)

 

50、闡述 final、finally、finalize 的區別。

(1) final:修飾符(關鍵字)有三種用法:如果一個類被聲明爲 final,意味着它不能再派生出新的子類,即不能被繼承,因此它和 abstract 是反義詞。將變量聲明爲 final,可以保證它們在使用中不被改變,被聲明爲 final 的變量必須在聲明時給定初值,而在以後的引用中只能讀取不可修改。被聲明爲 final 的方法也同樣只能使用,不能在子類中被重寫。

(2)finally:通常放在 try…catch…的後面構造總是執行代碼塊,這就意味着程序無論正常執行還是發生異常,這裏的代碼只要 JVM 不關閉都能執行,可以將釋放外部資源的代碼寫在 finally 塊中.

(3)finalize:Object 類中定義的方法,Java 中允許使用 finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在銷燬對象時調用的,通過重寫 finalize()方法可以整理系統資源或者執行其他清理工作。

 

最後

歡迎大家關注我的公種浩【程序員追風】,整理了1000道2019年多家公司java面試題400多頁pdf文檔,文章都會在裏面更新,整理的資料也會放在裏面。喜歡文章記得關注我點個贊喲,感謝支持!

發佈了174 篇原創文章 · 獲贊 2290 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章