Java長見到的面試題,看你能答出幾題,就知道自己有多菜了
作者:Java3y
前言
只有光頭才能變強
Redis目前還在看,今天來分享一下我在秋招看過(遇到)的一些面試題(相對比較常見的)
0、final關鍵字
簡要說一下final關鍵字,final可以用來修飾什麼?
這題我是在真實的面試中遇到的,當時答得不太好,現在來整理一下吧。
final可以修飾類、方法、成員變數
當final修飾類的時候,說明該類不能被繼承
當final修飾方法的時候,說明該方法不能被重寫
在早期,可能使用final修飾的方法,編譯器針對這些方法的所有呼叫都轉成內嵌呼叫,這樣提高效率(但到現在一般我們不會去管這事了,編譯器和JVM都越來越聰明瞭)
當final修飾成員變數時,有兩種情況:
如果修飾的是基本型別,說明這個變數的所代表數值永不能變(不能重新賦值)!
如果修飾的是引用型別,該變數所的引用不能變,但引用所代表的物件內容是可變的!
值得一說的是:並不是被final修飾的成員變數就一定是編譯期常量了。比如說我們可以寫出這樣的程式碼:private final int java3y = new Randon().nextInt(20);
你有沒有這樣的程式設計經驗,在編譯器寫程式碼時,某個場景下一定要將變數宣告為final,否則會出現編譯不通過的情況。為什麼要這樣設計?
在編寫匿名內部類的時候就可能會出現這種情況,匿名內部類可能會使用到的變數:
外部類例項變數
方法或作用域內的區域性變數
方法的引數
class Outer {
// string:外部類的例項變數
String string = "";
//ch:方法的引數
void outerTest(final char ch) {
// integer:方法內區域性變數
final Integer integer = 1;
new Inner() {
void innerTest() {
System.out.println(string);
System.out.println(ch);
System.out.println(integer);
}
};
}
public static void main(String[] args) {
new Outer().outerTest(' ');
}
class Inner {
}
}
其中我們可以看到:方法或作用域內的區域性變數和方法引數都要顯示使用final關鍵字來修飾(在jdk1.7下)!
如果切換到jdk1.8編譯環境下,可以通過編譯的~
下面我們首先來說一下顯示宣告為final的原因:為了保持內部外部資料一致性
Java只是實現了capture-by-value形式的閉包,也就是匿名函式內部會重新拷貝一份自由變數,然後函式外部和函式內部就有兩份資料。
要想實現內部外部資料一致性目的,只能要求兩處變數不變。JDK8之前要求使用final修飾,JDK8聰明些了,可以使用effectively final的方式
為什麼僅僅針對方法中的引數限制final,而訪問外部類的屬性就可以隨意
內部類中是儲存著一個指向外部類例項的引用,內部類訪問外部類的成員變數都是通過這個引用。
在內部類修改了這個引用的資料,外部類再獲取時拿到的資料是一致的!
那當你在匿名內部類裡面嘗試改變外部基本型別的變數的值的時候,或者改變外部引用變數的指向的時候,表面上看起來好像都成功了,但實際上並不會影響到外部的變數。所以,Java為了不讓自己看起來那麼奇怪,才加了這個final的限制。
參考資料:
java為什麼匿名內部類的引數引用時final?https://www.zhihu.com/question/21395848
一、char和varchar的區別
char是固定長度,varchar長度可變。varchar:如果原先儲存的位置無法滿足其儲存的需求,就需要一些額外的操作,根據儲存引擎的不同,有的會採用拆分機制,有的採用分頁機制。
char的儲存方式是:英文字元佔1個位元組,漢字佔用2個位元組;varchar的儲存方式是:英文和漢字都佔用2個位元組,兩者的儲存資料都非unicode的字元資料。
char是固定長度,長度不夠的情況下,用空格代替。varchar表示的是實際長度的資料型別
選用考量:
如果欄位長度較短和字元間長度相近甚至是相同的長度,會採用char字元型別
二、多個執行緒順序列印問題
三個執行緒分別列印A,B,C,要求這三個執行緒一起執行,列印n次,輸出形如“ABCABCABC….”的字串。
原博主給出了4種方式,我認為訊號量這種方式比較簡單和容易理解,我這裡貼上一下(具體的可到原博主下學習)..
public class PrintABCUsingSemaphore {
private int times;
private Semaphore semaphoreA = new Semaphore(1);
private Semaphore semaphoreB = new Semaphore(0);
private Semaphore semaphoreC = new Semaphore(0);
public PrintABCUsingSemaphore(int times) {
this.times = times;
}
public static void main(String[] args) {
PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(10);
// 非靜態方法引用 x::toString 和() -> x.toString() 是等價的!
new Thread(printABC::printA).start();
new Thread(printABC::printB).start();
new Thread(printABC::printC).start();
/*new Thread(() -> printABC.printA()).start();
new Thread(() -> printABC.printB()).start();
new Thread(() -> printABC.printC()).start();
*/
}
public void printA() {
try {
print("A", semaphoreA, semaphoreB);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void printB() {
try {
print("B", semaphoreB, semaphoreC);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void printC() {
try {
print("C", semaphoreC, semaphoreA);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void print(String name, Semaphore current, Semaphore next)
throws InterruptedException {
for (int i = 0; i < times; i++) {
current.acquire();
System.out.print(name);
next.release();
}
}
}
作者:cheergoivan
連結:https://www.jianshu.com/p/40078ed436b4
來源:簡書
2018年9月14日18:15:36 yy筆試題就出了..
三、生產者和消費者
在不少的面經都能看到它的身影哈~~~基本都是要求能夠手寫程式碼的。
其實邏輯並不難,概括起來就兩句話:
如果生產者的佇列滿了(while迴圈判斷是否滿),則等待。如果生產者的佇列沒滿,則生產資料並喚醒消費者進行消費。
如果消費者的佇列空了(while迴圈判斷是否空),則等待。如果消費者的佇列沒空,則消費資料並喚醒生產者進行生產。
基於原作者的程式碼,我修改了部分並給上我認為合適的註釋(下面附上了原作者出處,感興趣的同學可到原文學習)
生產者:
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
public class Producer implements Runnable {
// true--->生產者一直執行,false--->停掉生產者
private volatile boolean isRunning = true;
// 公共資源
private final Vector sharedQueue;
// 公共資源的最大數量
private final int SIZE;
// 生產資料
private static AtomicInteger count = new AtomicInteger();
public Producer(Vector sharedQueue, int SIZE) {
this.sharedQueue = sharedQueue;
this.SIZE = SIZE;
}
@Override
public void run() {
int data;
Random r = new Random();
System.out.println("start producer id = " + Thread.currentThread().getId());
try {
while (isRunning) {
// 模擬延遲
Thread.sleep(r.nextInt(1000));
// 當佇列滿時阻塞等待
while (sharedQueue.size() == SIZE) {
synchronized (sharedQueue) {
System.out.println("Queue is full, producer " + Thread.currentThread().getId()
+ " is waiting, size:" + sharedQueue.size());
sharedQueue.wait();
}
}
// 佇列不滿時持續創造新元素
synchronized (sharedQueue) {
// 生產資料
data = count.incrementAndGet();
sharedQueue.add(data);
System.out.println("producer create data:" + data + ", size:" + sharedQueue.size());
sharedQueue.notifyAll();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupted();
}
}
public void stop() {
isRunning = false;
}
}
消費者:
import java.util.Random;
import java.util.Vector;
public class Consumer implements Runnable {
// 公共資源
private final Vector sharedQueue;
public Consumer(Vector sharedQueue) {
this.sharedQueue = sharedQueue;
}
@Override
public void run() {
Random r = new Random();
System.out.println("start consumer id = " + Thread.currentThread().getId());
try {
while (true) {
// 模擬延遲
Thread.sleep(r.nextInt(1000));
// 當佇列空時阻塞等待
while (sharedQueue.isEmpty()) {
synchronized (sharedQueue) {
&n