面試整理(Java基礎2)

文章目錄

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

  1. 裝載:查找並加載類的二進制數據;
  2. 鏈接:
    驗證:確保被加載類的正確性;
    準備:爲類的靜態變量分配內存,並將其初始化爲默認值;
    解析:把類中的符號引用轉換爲直接引用;
  3. 初始化:
  1. 爲類的靜態變量賦予正確的初始值;
    那爲什麼我要有驗證這一步驟呢?首先如果由編譯器生成的class文件,它肯定是符合JVM字節碼格式的,但是萬一有高手自己寫一個class文件,讓JVM加載並運行,用於惡意用途,就不妙了,因此這個class文件要先過驗證這一關,不符合的話不會讓它繼續執行的,也是爲了安全考慮吧。
  1. 準備階段和初始化階段看似有點牟盾,其實是不牟盾的,如果類中有語句:private static int a = 10,它的執行過程是這樣的,首先字節碼文件被加載到內存後,先進行鏈接的驗證這一步驟,驗證通過後準備階段,給a分配內存,因爲變量a是static的,所以此時a等於int類型的默認初始值0,即a=0,然後到解析(後面在說),到初始化這一步驟時,才把a的真正的值10賦給a,此時a=10。
  1. 類的初始化
    類什麼時候才被初始化:
    1)創建類的實例,也就是new一個對象
    2)訪問某個類或接口的靜態變量,或者對該靜態變量賦值
    3)調用類的靜態方法
    4)反射(Class.forName(“com.lyj.load”))
    5)初始化一個類的子類(會首先初始化子類的父類)
    6)JVM啓動時標明的啓動類,即文件名和類名相同的那個類
  2. 只有這6中情況纔會導致類的類的初始化。
    類的初始化步驟:
    1)如果這個類還沒有被加載和鏈接,那先進行加載和鏈接
    2)假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)
    3)加入類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。

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

char 類型可以存儲一箇中文漢字,因爲 Java 中使用的編碼是 Unicode。一個 char 類型佔 2 個字節(16 比特),所以放一箇中文是沒問題的。
 
補充:使用 Unicode 意味着字符在 JVM 內部和外部有不同的表現形式,在 JVM內部都是 Unicode,當這個字符被從 JVM 內部轉移到外部時(例如存入文件系統中),需要進行編碼轉換。所以 Java 中有字節流和字符流,以及在字符流和字節流之間進行轉換的轉換流,如 InputStreamReader 和 OutputStreamReader,這兩個類是字節流和字符流之間的適配器類,承擔了編碼轉換的任務;

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

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

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

是被聲明爲靜態(static)的內部類,它可以不依賴於外部類實例被實例化。而通常的內部類需要在外部類實例化後才能實例化.

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)不會存在內存泄露問題。然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被 GC 回收,因此也會導致內存泄露的發生。

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

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

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

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

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)接口?抽象類是否可繼承具體類(concrete class)?

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

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

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

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

可以繼承其他類或實現其他接口

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

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

1) 如何將字符串轉換爲基本數據類型?
2) 如何將基本數據類型轉換爲字符串?
答:
(1)調用基本數據類型對應的包裝類中的方法 parseXXX(String)或valueOf(String)即可返回相應基本類型;
(2)一種方法是將基本數據類型與空字符串(”“)連接(+)即可獲得其所對應的字符串;另一種方法是調用 String 類中的 valueOf()方法返回相應字符

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

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

面試整理(Java基礎1)

面試整理(Java基礎3)
轉載自:https://juejin.im/post/5dea1dcb518825121d6e13ab#heading-21

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章