Alex Liu,note for java

java常見面試彙總

java中數據類型分爲基本數據類型和引用數據類型。

基本數據類型

整型:byte,short,int,long
浮點型:float,double
字符型:char 布爾型:boolean

引用數據類型

數組

接口

Java程序設計語言對對象採用的不是引用調用,實際上,對象引用是按 值傳遞的

在 Java 中定義一個不做事且沒有參數的構造方法的作用

Java 程序在執行子類的構造方法之前,如果沒有用 super()來調用父類特 定的構造方法,則會調用父類中“沒有參數的構造方法”。因此,如果父類中 只定義了有參數的構造方法,而在子類的構造方法中又沒有用 super()來 調用父類中特定的構造方法,則編譯時將發生錯誤,因爲 Java 程序在父類 中找不到沒有參數的構造方法可供執行。解決辦法是在父類里加上一個不做 事且沒有參數的構造方法。  

創建一個對象用什麼運算符?對象實體與對象引用有何不同?

new運算符,new創建對象實例(對象實例在堆內存中),對象引用指向對象 實例(對象引用存放在棧內存中)。一個對象引用可以指向0個或1個對象 (一根繩子可以不繫氣球,也可以系一個氣球);一個對象可以有n個引用指 向它(可以用n條繩子繫住一個氣球)。

關於 final 關鍵字的一些總結

final關鍵字主要用在三個地方:變量、方法、類。
對於一個final變量,如果是基本數據類型的變量,則其數值一旦在初始化 之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓 其指向另一個對象。
當用final修飾一個類時,表明這個類不能被繼承。final類中的所有成員方 法都會被隱式地指定爲final方法。
使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修 改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方 法轉爲內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何 性能提升(現在的Java版本已經不需要使用final方法進行這些優化了)。 類中所有的private方法都隱式地指定爲final。

爲什麼TCP客戶端最後還要發送一次確認呢?

一句話,主要防止已經失效的連接請求報文突然又傳送到了服務器,從而產生錯誤。
如果使用的是兩次握手建立連接,假設有這樣一種場景,客戶端發送了第一 個請求連接並且沒有丟失,只是因爲在網絡結點中滯留的時間太長了,由於 TCP的客戶端遲遲沒有收到確認報文,以爲服務器沒有收到,此時重新向服 務器發送這條報文,此後客戶端和服務器經過兩次握手完成連接,傳輸數 據,然後關閉連接。此時此前滯留的那一次請求連接,網絡通暢了到達了服 務器,這個報文本該是失效的,但是,兩次握手的機制將會讓客戶端和服務 器再次建立連接,這將導致不必要的錯誤和資源的浪費。
如果採用的是三次握手,就算是那一次失效的報文傳送過來了,服務端接受 到了那條失效報文並且回覆了確認報文,但是客戶端不會再次發出確認。由 於服務器收不到確認,就知道客戶端並沒有請求連接。 三次握手
###爲什麼在四次揮手客戶端最後還要等待2MSL? MSL(Maximum Segment Lifetime),TCP允許不同的實現可以設置不同 的MSL值。
第一,保證客戶端發送的最後一個ACK報文能夠到達服務器,因爲這個ACK報 文可能丟失,站在服務器的角度看來,我已經發送了FIN+ACK報文請求斷開 了,客戶端還沒有給我回應,應該是我發送的請求斷開報文它沒有收到,於 是服務器又會重新發送一次,而客戶端就能在這個2MSL時間段內收到這個重 傳的報文,接着給出迴應報文,並且會重啓2MSL計時器。
第二,防止類似與“三次握手”中提到了的“已經失效的連接請求報文段”出現 在本連接中。客戶端發送完最後一個確認報文後,在這個2MSL時間中,就可 以使本連接持續的時間內所產生的所有報文段都從網絡中消失。這樣新的連 接中不會出現舊連接的請求報文。

爲什麼建立連接是三次握手,關閉連接確是四次揮手呢?

建立連接的時候, 服務器在LISTEN狀態下,收到建立連接請求的SYN報文 後,把ACK和SYN放在一個報文裏發送給客戶端。
而關閉連接時,服務器收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,而自己也未必全部數據都發送給對方了,所以己方可以立即關閉,也可以發送一些數據給對方後,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送,從而導致多了一次。

四 TCP 協議如何保證可靠傳輸

  • 應用數據被分割成 TCP 認爲最適合發送的數據塊。
  • TCP 給發送的每一個包進行編號,接收方對數據包進行排序,把有序數據傳 送給應用層。
  • 校驗和: TCP 將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段。
  • TCP的接收端會丟棄重複的數據。
  • 流量控制: TCP 連接的每一方都有固定大小的緩衝空間,TCP的接收端只 允許發送端發送接收端緩衝區能接納的數據。當接收方來不及處理髮送方的 數據,能提示發送方降低發送的速率,防止包丟失。TCP 使用的流量控制協 議是可變大小的滑動窗口協議。(TCP 利用滑動窗口實現流量控制)
  • 擁塞控制: 當網絡擁塞時,減少數據的發送。
  • ARQ協議: 也是爲了實現可靠傳輸的,它的基本原理就是每發完一個分組就 停止發送,等待對方確認。在收到確認後再發下一個分組。
  • 超時重傳: 當 TCP 發出一個段後,它啓動一個定時器,等待目的端確認收 到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
    三次握手

五.接口與抽象的異同

1.抽象類

抽象類是關鍵字abstract修飾的類,既爲抽象類,抽象抽象即不能被實例化。而不能被實例化就無用處,所以抽象類只能作爲基類(父類),即被繼承的類。抽象類中可以包含抽象方法也可以不包含,但具有抽象方法的類一定是抽象類。   抽象類的使用原則如下:

(1)被繼承性:抽象方法必須爲public或者protected(因爲如果爲private,則不能被子類繼承,子類便無法實現該方法),缺省情況下默認爲public;
(2)抽象性:抽象類不能直接實例化,需要依靠子類採用向上轉型的方式處理; (3)抽象類必須有子類,使用extends繼承,一個子類只能繼承一個抽象類; (4)子類(如果不是抽象類)則必須覆寫抽象類之中的全部抽象方法(如果子類沒有實現父類的抽象方法,則必須將子類也定義爲爲abstract類。

1、接口與類相似點

一個接口可以有多個方法。 接口文件保存在 .java 結尾的文件中,文件名使用接口名。 接口的字節碼文件保存在 .class 結尾的文件中。 接口相應的字節碼文件必須在與包名稱相匹配的目錄結構中。

2、接口與類的區別

接口不能用於實例化對象。 接口沒有構造方法。 接口中所有的方法必須是抽象方法。 接口不能包含成員變量,除了 static 和 final 變量。 接口不是被類繼承了,而是要被類實現。 接口支持多繼承。

3、接口特性

接口中每一個方法也是隱式抽象的,接口中的方法會被隱式的指定爲 public abstract(只能是 public abstract,其他修飾符都會報錯)。 接口中可以含有變量,但是接口中的變量會被隱式的指定爲 public static final 變量(並且只能是 public,用 private 修飾會報編譯錯誤)。 接口中的方法是不能在接口中實現的,只能由實現接口的類來實現接口中的方法。 接口與抽象類異同

六.static關鍵字

  • static關鍵字並不會改變變量和方法的訪問權限。 與C/C++中的static不同,Java中的static關鍵字不會影響到變量或者方法的作用域。在Java中能夠影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。
  • 爲什麼說static塊可以用來優化程序性能,是因爲它的特性:只會在類加載的時候執行一次。 很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行
  • static成員變量的初始化順序按照定義的順序進行初始化。
  • 即使沒有顯示地聲明爲static,類的構造器實際上也是靜態方法。
  • static方法一般稱作靜態方法,由於靜態方法不依賴於任何對象就可以進行訪問,因此對於靜態方法來說,是沒有this的,因爲它不依附於任何對象,既然都沒有對象,就談不上this了。並且由於這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,因爲非靜態成員方法/變量都是必須依賴具體的對象才能夠被調用。
    • static代碼塊也叫靜態代碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM加載類時會執行這些靜態的代碼塊,如果static代碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個代碼塊只會被執行一次。
  • 普通類是不允許聲明爲靜態的,只有內部類纔可以。

被static修飾的內部類可以直接作爲一個普通類來使用,而不需實例一個外部類

  • static能作用於局部變量麼?

  在C/C++中static是可以作用域局部變量的,但是在Java中切記:static是不允許用來修飾局部變量。不要問爲什麼,這是Java語法的規定。

  • 常見的筆試面試題
    1. public class Test extends Base{

    static{ System.out.println("test static"); }

    public Test(){ System.out.println("test constructor"); } ` public static void main(String[] args) { new Test(); } }`

class Base{

static{
    System.out.println("base static");
}
 
public Base(){
    System.out.println("base constructor");
} `}`   輸出:
`base static   
 test static    
 base constructor    
 test constructor    ` 先來想一下這段代碼具體的執行過程,在執行開始,先要尋找到main方法,因爲main方法是程序的入口,但是在執行main方法之前,必須先加載Test類,而在加載Test類的時候發現Test類繼承自Base類,因此會轉去先加載Base類,在加載Base類的時候,發現有static塊,便執行了static塊。在Base類加載完成之後,便繼續加載Test類,然後發現Test類中也有static塊,便執行static塊。在加載完所需的類之後,便開始執行main方法。在main方法中執行new Test()的時候會先調用父類的構造器,然後再調用自身的構造器。因此,便出現了上面的輸出結果。
  • static塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),並且執行是按照static塊的順序執行的。

  • 這段代碼的輸出結果是什麼? ` public class Test {
    Person person = new Person(“Test”);
    ` ` static{ System.out.println(“test static”);
    } `

    public Test() { ` System.out.println(“test constructor”); `

    } `

public static void main(String[] args) { new MyClass(); } }

class Person{ ` static{
System.out.println(“person static”);
} public Person(String str) { ` ` System.out.println(“person “+str);
} } `

class MyClass extends Test { ` Person person = new Person(“MyClass”); ` ` static{
System.out.println(“myclass static”);
}`

public MyClass() {  
    System.out.println("myclass constructor");  
}   ` }	 ` 輸出: ` test static `  ` myclass static `  ` person static  ` ` person Test` ` test constructor` ` person MyClass` ` myclass constructor` ##### 思路: 首先加載Test類,因此會執行Test類中的static塊。接着執行new MyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時候,發現MyClass類繼承自Test類,但是由於Test類已經被加載了,所以只需要加載MyClass類,那麼就會執行MyClass類的中的static塊。在加載完之後,就通過構造器來生成對象。而在生成對象的時候,***必須先初始化父類的成員變量***,因此會執行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會先加載Person類並執行Person類中的static塊,接着執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接着執行MyClass中的Person person = new Person(),最後執行MyClass的構造器。

七.final

  • 根據程序上下文環境,Java關鍵字final有“這是無法改變的”或者“終態的”含義,它可以修飾非抽象類、非抽象類成員方法和變量。你可能出於兩種理解而需要阻止改變:設計或效率。 final類不能被繼承,沒有子類,final類中的方法默認是final的。 final方法不能被子類的方法覆蓋,但可以被繼承。 final成員變量表示常量,只能被賦值一次,賦值後值不再改變。 final不能用於修飾構造方法。 注意:父類的private成員方法是不能被子類方法覆蓋的,因此private類型的方法默認是final類型的。
  • final類不能被繼承,因此final類的成員方法沒有機會被覆蓋,默認都是final的。在設計類時候,如果這個類不需要有子類,類的實現細節不允許改變,並且確信這個類不會載被擴展,那麼就設計爲final類。
  • final方法 如果一個類不允許其子類覆蓋某個方法,則可以把這個方法聲明爲final方法。 使用final方法的原因有二: 第一、把方法鎖定,防止任何繼承類修改它的意義和實現。 第二、高效。編譯器在遇到調用final方法時候會轉入內嵌機制,大大提高執行效率。
  • final變量(常量) 用final修飾的成員變量表示常量,值一旦給定就無法改變! final修飾的變量有三種:靜態變量、實例變量和局部變量,分別表示三種類型的常量。 從下面的例子中可以看出,一旦給final變量初值後,值就不能再改變了。 另外,final變量定義的時候,可以先聲明,而不給初值,這中變量也稱爲final空白,無論什麼情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,爲此,一個類中的final數據成員就可以實現依對象而有所不同,卻有保持其恆定不變的特徵。

  • static和final一塊用表示什麼 static final用來修飾成員變量和成員方法,可簡單理解爲“全局常量”! 對於變量,表示一旦給值就不可修改,並且通過類名可以訪問。 對於方法,表示不可覆蓋,並且可以通過類名直接訪問。

      ** 特別要注意一個問題: **
      對於被static和final修飾過的實例常量,實例本身不能再改變了,但**對於一些容器類型(比如,ArrayList、HashMap)的實例變量,不可以改變容器變量本身,但可以修改容器中存放的對象,**這一點在編程中用到很多。
    

八.Java關鍵字this、super使用總結

一、this

Java關鍵字this只能用於方法方法體內。當一個對象創建後,
Java虛擬機(JVM)就會給這個對象分配一個引用自身的指針,這個指針的名字就是this。因此,this只能在類中的非靜態方法中使用,靜態方法和靜態的代碼塊中絕對不能出現this,這在“Java關鍵字static、final使用總結”一文中給出了明確解釋。並且this只和特定的對象關聯,而不和類關聯,同一個類的不同對象有不同的this。

  • 在什麼情況下需要用到this: 第一、通過this調用另一個構造方法,用發是this(參數列表),這個僅僅在類的構造方法中,別的地方不能這麼用。 第二、函數參數或者函數中的局部變量和成員變量同名的情況下,成員變量被屏蔽,此時要訪問成員變量則需要用“this.成員變量名”的方式來引用成員變量。當然,在沒有同名的情況下,可以直接用成員變量的名字,而不用this,用了也不爲錯,呵呵。 第三、在函數中,需要引用該函所屬類的當前對象時候,直接用this。

    二、super

      super關鍵和this作用類似,是被屏蔽的成員變量或者成員方法  或變爲可見,或者說用來引用被屏蔽的成員變量和成員成員方法。 不過super是用在子類中,目的是訪問直接父類中被屏蔽的成員,注意是直接父類(就是類之上最近的超類)。  
    
  • 總結一下super的用法: 第一、在子類構造方法中要調用父類的構造方法,用“super(參數列表)”的方式調用,參數不是必須的。同時還要注意的一點是:“super(參數列表)”這條語句只能用在子類構造方法體中的第一行。 第二、當子類方法中的局部變量或者子類的成員變量與父類成員變量同名時,也就是子類局部變量覆蓋父類成員變量時,用“super.成員變量名”來引用父類成員變量。當然,如果父類的成員變量沒有被覆蓋,也可以用“super.成員變量名”來引用父類成員變量,不過這是不必要的。 第三、當子類的成員方法覆蓋了父類的成員方法時,也就是子類和父類有完全相同的方法定義(但方法體可以不同),此時,用“super.方法名(參數列表)”的方式訪問父類的方法。

    九.java中的匿名內部類總結

    匿名內部類也就是沒有名字的內部類

正因爲沒有名字,所以匿名內部類只能使用一次,它通常用來簡化代碼編寫

但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口

匿名內部類的基本實現

abstract class Person {

public abstract void eat();}public class Demo {

public static void main(String[] args) {

    Person p = new Person() {

        public void eat() {

            System.out.println("eat something");

        }

    };

    p.eat();

}}   可以看到,我們直接將抽象類Person中的方法在大括號中實現了

這樣便可以省略一個類的書寫

並且,匿名內部類還能用於接口上

在接口上使用匿名內部類

interface Person {

public void eat();}public class Demo {

public static void main(String[] args) {

    Person p = new Person() {

        public void eat() {

            System.out.println("eat something");

        }

    };

    p.eat();

}}   由上面的例子可以看出,只要一個類是抽象的或是一個接口,那麼其子類中的方法都可以使用匿名內部類來實現

最常用的情況就是在多線程的實現上,因爲要實現多線程必須繼承Thread類或是繼承Runnable接口

Thread類的匿名內部類實現

public class Demo {

public static void main(String[] args) {

    Thread t = new Thread() {

        public void run() {

            for (int i = 1; i <= 5; i++) {

                System.out.print(i + " ");

            }

        }

    };

    t.start();

}}   #### Runnable接口的匿名內部類實現
public class Demo {

public static void main(String[] args) {

    Runnable r = new Runnable() {

        public void run() {

            for (int i = 1; i <= 5; i++) {

                System.out.print(i + " ");

            }

        }

    };

    Thread t = new Thread(r);

    t.start();

}}    

垃圾回收算法原理

轉載自:https://blog.csdn.net/FateRuler/article/details/81158510

第一種:標記清除

它是最基礎的收集算法。 原理:分爲標記和清除兩個階段:首先標記出所有的需要回收的對象,在標記完成以後統一回收所有被標記的對象。 特點:(1)效率問題,標記和清除的效率都不高;(2)空間的問題,標記清除以後會產生大量不連續的空間碎片,空間碎片太多可能會導致程序運行過程需要分配較大的對象時候,無法找到足夠連續內存而不得不提前觸發一次垃圾收集。 地方 :適合在老年代進行垃圾回收,比如CMS收集器就是採用該算法進行回收的。

第二種:標記整理

原理:分爲標記和整理兩個階段:首先標記出所有需要回收的對象,讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。 特點:不會產生空間碎片,但是整理會花一定的時間。 地方:適合老年代進行垃圾收集,parallel Old(針對parallel scanvange gc的) gc和Serial old收集器就是採用該算法進行回收的。

第三種:複製算法

原理:它先將可用的內存按容量劃分爲大小相同的兩塊,每次只是用其中的一塊。當這塊內存用完了,就將還存活着的對象複製到另一塊上面,然後把已經使用過的內存空間一次清理掉。 特點:沒有內存碎片,只要移動堆頂指針,按順序分配內存即可。代價是將內存縮小位原來的一半。 地方:適合新生代區進行垃圾回收。serial new,parallel new和parallel scanvage 收集器,就是採用該算法進行回收的。

複製算法改進思路:

由於新生代都是朝生夕死的,所以不需要1:1劃分內存空間,可以將內存劃分爲一塊較大的Eden和兩塊較小的Suvivor空間。每次使用Eden和其中一塊Survivor。當回收的時候,將Eden和Survivor中還活着的對象一次性地複製到另一塊Survivor空間上,最後清理掉Eden和剛纔使用過的Suevivor空間。其中Eden和Suevivor的大小比例是8:1。缺點是需要老年代進行分配擔保,如果第二塊的Survovor空間不夠的時候,需要對老年代進行垃圾回收,然後存儲新生代的對象,這些新生代當然會直接進入來老年代。

優化收集方法的思路 分代收集算法 原理:根據對象存活的週期的不同將內存劃分爲幾塊,然後再選擇合適的收集算法。 一般是把java堆分成新生代和老年代,這樣就可以根據各個年待的特點採用最適合的收集算法。在新生代中,每次垃圾收集都會有大量的對象死去,只有少量存活,所以選用複製算法。老年代因爲對象存活率高,沒有額外空間對他進行分配擔保,所以一般採用標記整理或者標記清除算法進行回收。

對於以上兩種標記算法存在爭議,在深入瞭解JVM最佳實踐第二版中,是寫的標記需要回收的對象,我也沒太深入思考,直到有人提出來,我也去查了一下和想了一下。我個人現在偏向,標記存活的對象。 標記算法的大概流程:通過引用鏈給所有存活的對象做個標記,然後回收所有沒有標記的對象 和 清除存活對象的標記,等待下一次GC

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