1. 程式人生 > >Java長見到的面試題,看你能答出幾題,就知道自己有多菜了

Java長見到的面試題,看你能答出幾題,就知道自己有多菜了

5c4a8e4b-95a8-42f3-9b70-cc56091bd16c.jpg

作者: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的區別

  1. char是固定長度,varchar長度可變。varchar:如果原先儲存的位置無法滿足其儲存的需求,就需要一些額外的操作,根據儲存引擎的不同,有的會採用拆分機制,有的採用分頁機制

  2. char的儲存方式是:英文字元佔1個位元組,漢字佔用2個位元組;varchar的儲存方式是:英文和漢字都佔用2個位元組,兩者的儲存資料都非unicode的字元資料。

  3. 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