【Java面試題系列】:Java基礎知識常見面試題彙總 第二篇

文中面試題從茫茫網海中精心篩選,若有錯誤,歡迎指正!html

第一篇連接:【Java面試題系列】:Java基礎知識常見面試題彙總 第一篇java

1.JDK,JRE,JVM三者之間的聯繫和區別

你是否考慮過咱們寫的xxx.java文件被誰編譯,又被誰執行,又爲何可以跨平臺運行?面試

1.1基本概念

JVM:Java Virtual Machine,Java虛擬機。spring

JVM並不能識別咱們平時寫的xxx.java文件,只能識別xxx.class文件,它可以將class文件中的字節碼指令進行識別並調用操做系統上的API完成指定的動做。因此,JVM是Java可以跨平臺的核心。springboot

JRE:Java Runtime Environment,Java運行時環境。dom

JRE主要包含2個部分,JVM的標準實現和Java的一些基本類庫。相比於JVM,多出來的是一部分Java類庫。jvm

JDK:Java Development Kit,開發工具包。ide

JDK是整個Java開發的核心,它集成了JRE和一些好用的小工具,例如:javac.exe,java.exe,jar.exe等。函數

上一篇博客中也提到了,咱們能夠經過javac命令將xxx.java文件編譯爲xxx.class文件。工具

1.2聯繫和區別

瞭解完3者的基本概念,咱們能夠看出來3者的關係爲一層層嵌套,即:JDK > JRE > JVM。

這裏,咱們提出一個問題:爲何咱們安裝完JDK後會有兩個版本的JRE?

我電腦安裝的JDK是1.8版本,安裝完的目錄以下圖所示:

而jdk目錄下也有1個jre:

我電腦環境變量配置的是:

JAVA_HOME C:\Program Files\Java\jdk1.8.0_191

Path變量最後添加的是%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin。

也就是說,我電腦用的是jdk目錄下的jre,而不是和jdk同級目錄下的jre,也許大部分人都是這樣的,可能沒人注意,說實話,我以前還真沒在乎,看了網上的文章才知道,看來真的是要多問爲何。

這兩個不一樣版本的JRE其實沒什麼聯繫,你能夠修改下Path變量,指向任意1個均可以,只是不少人在安裝JDK的時候,並不清楚JDK和JRE的區別,因此都會安裝,好比說我,哈哈。

在jdk的目錄下,有一些可執行文件,好比說javac.exe,其實內部也是調用的java類,因此jdk目錄下的jre既提供了這些工具的運行時環境,也提供了咱們編寫的Java程序的運行時環境。

因此,能夠得出以下結論:

若是你是Java開發者,安裝JDK時能夠選擇不安裝JRE

若是你的機器只是用來部署和運行Java程序,能夠不安裝JDK,只安裝JRE便可

1.3Java 爲何能跨平臺,實現一次編寫,多處運行?

Java引入了字節碼的概念,JVM只能識別字節碼,並將它們解釋到系統的API調用,針對不一樣的系統有不一樣的JVM實現,有Lunix版本的JVM實現,也有Windows版本的JVM實現,可是同一段代碼在編譯後的字節碼是一致的,而同一段字節碼,在不一樣的JVM實現上會映射到不一樣系統的API調用,從而實現代碼不修改便可跨平臺運行。

因此說Java可以跨平臺的核心在於JVM,不是Java可以跨平臺,而是它的JVM可以跨平臺。

2.接口和抽象類的區別

2.1抽象方法

當父類的一些方法不肯定時,能夠用abstract關鍵字將其聲明爲抽象方法,聲明語法以下:

public abstract double area();

抽象方法與普通方法的區別:

  1. 抽象方法須要用關鍵字abstract修飾

  2. 抽象方法沒有方法體,即只有聲明,而沒有具體的實現

  3. 抽象方法所在的類必須聲明爲抽象類

  4. 抽象方法必須聲明爲public或者protected,不能聲明爲private

    由於若是爲private,則不能被子類繼承,子類便沒法實現該方法,抽象方法也就失去了意義

2.2抽象類

若是一個類包含抽象方法,則這個類是抽象類,必須由關鍵字abstract修飾。

抽象類是爲了繼承而存在的,若是你定義了一個抽象類,卻不去繼承它,那麼等於白白建立了這個抽象類,由於你不能用它來作任何事情,即沒由起到抽象類的意義。對於一個父類,若是它的某個方法在父類中沒有具體的實現,必須根據子類的實際需求來進行不一樣的實現,那麼就能夠將這個方法聲明爲abstract方法,此時這個類也就成爲abstract類了。

抽象類與普通類的區別:

  1. 抽象類不能被實例化,即不能經過new來建立對象
  2. 抽象類須要用關鍵字abstract修飾
  3. 若是一個類繼承於一個抽象類,則子類必須實現父類的抽象方法。若是子類沒有實現父類的抽象方法,則必須將子類也定義爲abstract類。
  4. 抽象類除了能夠擁有普通類的成員變量和成員方法,還能夠擁有抽象方法

值得注意的是,抽象類不必定必須包含抽象方法,只是通常你們使用時,都包含了抽象方法

舉個具體的例子,好比咱們有一個平面圖形類Shape,它有兩個抽象方法area()和perimeter(),分別用來獲取圖形的面積和周長,而後咱們有矩形類Rectangle和圓形類Circle,來繼承抽象類Shape,各自實現area()方法和和perimeter()方法,由於矩形和圓形計算面積和周長的方法是不同的,下面看具體代碼:

package com.zwwhnly.springbootdemo;

public abstract class Shape {

    public abstract double area();

    public abstract double perimeter();
}
package com.zwwhnly.springbootdemo;

public class Rectangle extends Shape {
    private double length;
    private double width;

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double area() {
        return getLength() * getWidth();
    }

    @Override
    public double perimeter() {
        return (getLength() + getWidth()) * 2;
    }
}
package com.zwwhnly.springbootdemo;

public class Circle extends Shape {
    private double diameter;

    public double getDiameter() {
        return diameter;
    }

    public void setDiameter(double diameter) {
        this.diameter = diameter;
    }

    @Override
    public double area() {
        return Math.PI * Math.pow(getDiameter() / 2, 2);
    }

    @Override
    public double perimeter() {
        return Math.PI * getDiameter();
    }
}
public static void main(String[] args) {
    Rectangle rectangle = new Rectangle();
    rectangle.setLength(10);
    rectangle.setWidth(5);

    double rectangleArea = rectangle.area();
    double rectanglePerimeter = rectangle.perimeter();
    System.out.println("矩形的面積:" + rectangleArea + ",周長" + rectanglePerimeter);

    Circle circle = new Circle();
    circle.setDiameter(10);

    double circleArea = circle.area();
    double circlePerimeter = circle.perimeter();
    System.out.println("圓形的面積:" + circleArea + ",周長" + circlePerimeter);
}

輸出結果:

矩形的面積:50.0,周長30.0

圓形的面積:78.53981633974483,周長31.41592653589793

2.2接口

接口,是對行爲的抽象,聲明語法爲:

package com.zwwhnly.springbootdemo;

public interface Alram {
    void alarm();
}

能夠看出,接口中的方法沒有具體的實現(會被隱式的指定爲public abstract方法),具體的實現由實現接口的類來實現,類實現接口的語法爲(這裏以ArrayList類爲例):

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ......
}

能夠看出,一個類能夠實現多個接口。

若是一個非抽象類實現了某個接口,就必須實現該接口中的全部方法。

若是一個抽象類實現了某個接口,能夠不實現該接口中的方法,但其子類必須實現。

2.3抽象類和接口的區別

語法層面上的區別:

  1. 一個類只能繼承一個抽象類,而一個類卻能夠實現多個接口
  2. 接口中不能含有靜態代碼塊以及靜態方法,而抽象類能夠有靜態代碼塊和靜態方法
  3. 抽象類能夠提供成員方法的實現細節,而接口中的方法不能夠
  4. 接口的方法默認是public,全部方法在接口中不能有實現,抽象類能夠有非抽象的方法
  5. 抽象類中的成員變量能夠是各類類型的,而接口中的成員變量只能是public static final類型的
  6. 接口不能用new實例化,但能夠聲明,可是必須引用一個實現該接口的對象, 從設計層面來講,抽象是對類的抽象,是一種模板設計,接口是行爲的抽象,是一種行爲的規範。

設計層面上的區別:

  1. 抽象類是對整個類總體進行抽象,包括屬性、行爲,可是接口倒是對類局部(行爲)進行抽象。

    繼承是一個 "是否是"的關係,而 接口實現則是 "有沒有"的關係。若是一個類繼承了某個抽象類,則子類一定是抽象類的種類,而接口實現則是有沒有、具有不具有的關係,好比鳥是否能飛(或者是否具有飛行這個特色),能飛行則能夠實現這個接口,不能飛行就不實現這個接口。

  2. 設計層面不一樣,抽象類做爲不少子類的父類,它是一種模板式設計。而接口是一種行爲規範,它是一種輻射式設計。

    對於抽象類,若是須要添加新的方法,能夠直接在抽象類中添加具體的實現,子類能夠不進行變動;而對於接口則不行,若是接口進行了變動,則全部實現這個接口的類都必須進行相應的改動。

這裏引用下網上的門和警報的例子,門都有open()和close()兩個動做,此時咱們能夠經過抽象類或者接口定義:

public abstract class Door {
    public abstract void open();

    public abstract void close();
}

或者使用接口:

public interface Door {
    void open();

    void close();
}

如今咱們須要門具備警報alarm功能,該如何設計呢?

你可能想到的2個思路爲:

1)在抽象類中增長alarm()方法,這樣一來,全部繼承於這個抽象類的子類都具有了報警功能,可是有的門並不必定具有報警功能。

2)在接口中增長alarm()方法,這樣一來,用到報警功能的類就必需要實現接口中的open()和close()方法,也許這個類根本就不具有open()和close()這兩個功能,好比火災報警器。

從這裏能夠看出,Door的open(),close()和alarm()屬於兩個不一樣範疇內的行爲,open()和close()屬於門自己固有的行爲特性,而alarm()屬於延伸的附加行爲。

所以最好的設計方式是單獨將報警設計爲一個接口Alarm,包含alarm()行爲,Door設計爲單獨的抽象類,包含open()和close()行爲,再設計一個報警門繼承Door類並實現Alarm接口:

public abstract class Door {
    public abstract void open();

    public abstract void close();
}
public interface Alarm {
    void alarm();
}
public class AlarmDoor extends Door implements Alarm {

    @Override
    public void alarm() {

    }

    @Override
    public void open() {

    }

    @Override
    public void close() {

    }
}

3.重載與重寫的區別

3.1基本概念

重載(Overload):發生在1個類裏面,是讓類以統一的方式處理不一樣類型數據的一種手段,實質表現就是容許一個類中存在多個具備不一樣參數個數或者類型同名函數/方法,是一個類中多態性的一種表現。

返回值類型可隨意,不能以返回類型做爲重載函數的區分標準

重載規則以下:

  1. 必須具備不一樣的參數列表
  2. 能夠有不一樣的返回類型
  3. 能夠有不一樣的訪問修飾符
  4. 能夠拋出不一樣的異常

重寫(Override):發生在父子類中,是父類與子類之間的多態性,實質是對父類的函數進行從新定義,若是在子類中定義某方法與父類有相同的方法名稱和參數則該方法被重寫,不過子類函數的訪問修飾符權限不能小於父類的;若子類中的方法與父類中的某一方法具備相同的方法名、返回類型和參數列表,則新方法將覆蓋原有的方法,如需調用父類中原有的方法可以使用super關鍵字調用。

重寫規則以下:

  1. 參數列表必須徹底與被重寫的方法相同,不然不能稱其爲重寫而是重載
  2. 返回類型必須一直與被重寫的方法相同,不然不能稱其爲重寫而是重載
  3. 訪問修飾符的限制必定要大於等於被重寫方法的訪問修飾符
  4. 重寫方法必定不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常,譬如父類方法聲明瞭一個檢查異常 IOException,在重寫這個方法時就不能拋出 Exception,只能拋出 IOException 的子類異常,能夠拋出非檢查異常

總之,重載與重寫是Java多態性的不一樣表現,重寫是父類與子類之間多態性的表現,在運行時起做用;而重載是一個類中多態性的表現,在編譯時起做用

3.2示例

其實JDK的源碼中就有不少重載和重寫的例子,重載的話,咱們看下Math類的abs()方法,就有如下幾種實現:

public static int abs(int a) {
    return (a < 0) ? -a : a;
}

public static long abs(long a) {
    return (a < 0) ? -a : a;
}

public static float abs(float a) {
     return (a <= 0.0F) ? 0.0F - a : a;
}

public static double abs(double a) {
     return (a <= 0.0D) ? 0.0D - a : a;
}

重寫的話,咱們以String類的equals()方法爲例,基類中equals()是這樣的:

public boolean equals(Object obj) {
    return (this == obj);
}

而子類String的equals()重寫後是這樣的:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

咱們再來看一個特殊的例子:

package com.zwwhnly.springbootdemo;

public class Demo {

    public boolean equals(Demo other) {
        System.out.println("use Demo equals.");
        return true;
    }

    public static void main(String[] args) {
        Object o1 = new Demo();
        Object o2 = new Demo();
        Demo o3 = new Demo();
        Demo o4 = new Demo();

        if (o1.equals(o2)) {
            System.out.println("o1 is equal with o2.");
        }

        if (o3.equals(o4)) {
            System.out.println("o3 is equal with o4.");
        }
    }
}

輸出結果:

use Demo equals.

o3 is equal with o4.

是否是和你預期的輸出結果不一致呢,出現這個的緣由是,該類的equals()方法並無真正重寫Object類的equals()方法,違反了參數規則,所以o1.equals(o2)時,調用的還是Object類的equals()方法,即比較的是內存地址,所以返回false。而o3.equals(o4)比較時,由於o3,o4都是Demo類型,所以調用的是Demo類的equals()方法,返回true。

4.成員變量和局部變量的區別

4.1定義的位置不同

成員變量:在方法外部,能夠被public,private,static,final等修飾符修飾

局部變量:在方法內部或者方法的聲明上(即在參數列表中),不能被public,private,static等修飾符修飾,但能夠被final修飾

4.2做用範圍不同

成員變量:整個類全均可以通用

局部變量:只有方法當中纔可使用,出了方法就不能再用

4.3默認值不同

成員變量:若是沒有賦值,會有默認值(類型的默認值)

局部變量:沒有默認值,使用前必須賦值,不然編譯器會報錯

4.4內存的位置不同

成員變量:位於堆內存

局部變量:位於棧內存

4.5.生命週期不同

成員變量:隨着對象建立而誕生,隨着對象被垃圾回收而消失

局部變量:隨着方法的調用或者代碼塊的執行而存在,隨着方法的調用完畢或者代碼塊的執行完畢而消失

package com.zwwhnly.springbootdemo;

public class VariableDemo {
    private String name = "成員變量";

    public static void main(String[] args) {
        new VariableDemo().show();
    }

    public void show() {
        String name = "局部變量";
        System.out.println(name);
        System.out.println(this.name);
    }
}

輸出結果:

局部變量

成員變量

5.字符型常量和字符串常量的區別

  1. 形式上: 字符常量是單引號引發的一個字符 字符串常量是雙引號引發的若干個字符
  2. 含義上: 字符常量至關於一個整形值(ASCII值),能夠參加表達式運算 字符串常量表明一個地址值(該字符串在內存中存放位置)
  3. 佔內存大小:字符常量只佔一個字節 字符串常量佔若干個字節

6.參考連接

弄懂 JRE、JDK、JVM 之間的區別與聯繫

Java抽象類和抽象方法例子

深刻理解Java的接口和抽象類

JAVA重寫和重載的區別

JAVA中局部變量 和 成員變量有哪些區別

成員變量與局部變量的區別

最最最多見的Java面試題總結——第二週

相關文章
相關標籤/搜索