2019年Java面試題基礎系列228道
上一篇更新1~20題的答案解析
本次更新Java 面試題(一)的21~50題答案
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文件,文章都會在裡面更新,整理的資料也會放在裡面。喜歡文章記得關注我點個贊喲,感謝支援!