很多小伙伴在學習了Java一段時間后,不可避免的要遇到面試的問題
所以本貼是給大家開的新地圖哈哈哈
主要是一些面試中比較容易被問到的問題,逐步更新中~
1. 面向物件的特征有哪些方面?
答:面向物件的特征主要有以下幾個方面:
1.封裝:
封裝是把程序和資料包裹起來,外界對于資料的操作僅限于我們提供的方式,我們可以將生活中各種事物進行抽象封裝形成類,也可以通過private修飾符來封裝屬性與方法,不過需要注意的是,封裝后需要手動提供對應被封裝資源對外公共的操作方式,
2.繼承:繼承是一種聯結類的層次模型,并且允許和鼓勵類的重用,它提供了一種明確表述共性的方法,我們可以從現有的類中派生出一個新的類,這個程序稱為類繼承,新類繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類),派生類可以從它的基類那里繼承方法和實體變數,并且類可以修改或增加新的方法使之更適合特殊的需要,
3.多型性:多型性是指可以盡量屏蔽不同類的差異性,提供通用的解決方案,多型性語言具有靈活、抽象、行為共享、代碼共享的優勢,很好的解決了應用程式函式同名問題,我們所使用的有:
向上造型【將子類物件統一看作父型別,比如花木蘭替父從軍】
向下造型【將之前看作父型別的子類物件再重新恢復成子類物件,比如花木蘭打仗結束回家化妝】
4.抽象:抽象就是忽略一個主題中與當前目標無關的那些方面,以便更充分地注意與當前目標有關的方面,抽象并不打算了解全部問題,而只是選擇其中的一部分,暫時不用部分細節,抽象包括兩個方面,一是程序抽象,二是資料抽象,
2. 抽象類與介面的異同:
抽象類:
抽象方法必須用abstract關鍵字進行修飾,如果一個類含有抽象方法,則稱這個類為抽象類,抽象類必須在類前用abstract關鍵字修飾,因為抽象類中含有無具體實作的方法,所以不能用抽象類創建物件,
抽象類可以擁有成員變數和普通的成員方法,
抽象類和普通類的主要有三點區別:
1)抽象方法必須為public或者protected(因為如果為private,則不能被子類繼承,子類便無法實作該方法),預設情況下默認為public,
2)抽象類不能用來創建物件;
3)如果一個類繼承于一個抽象類,則子類必須實作父類的抽象方法,如果子類沒有實作父類的抽象方法,則必須將子類也定義為為abstract類,
介面:
介面中的變數會被隱式地指定為public static final變數,并且只能是public static final變數,用private修飾會報編譯錯誤,而方法會被隱式地指定為public abstract方法且只能是public abstract方法,用其他關鍵字,比如private、protected、static、 final等修飾會報編譯錯誤,并且介面中所有的方法不能有具體的實作,也就是說,介面中的方法必須都是抽象方法,
抽象類和介面的區別:
語法層面上的區別
1)一個類只能繼承一個抽象類,而一個類卻可以實作多個介面
2)抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是public static final型別的
3)介面中不能含有靜態代碼塊以及靜態方法,而抽象類可以有靜態代碼塊和靜態方法
4)抽象類可以提供成員方法的實作細節,而介面中只能存在public abstract 方法
5)抽象類的抽象方法可以是public,protected,default型別,而介面的方法只能是public
設計層面上的區別
1)抽象類是對一種事物的抽象,即對類抽象,而介面是對行為的抽象,抽象類是對整個類整體進行抽象,包括屬性、行為,但是介面卻是對類區域(行為)進行抽象
所以抽象是重構的結果,介面是設計的結果,
2)設計層面不同,抽象類作為很多子類的父類,它是一種模板式設計,而介面是一種行為規范,它是一種輻射式設計,
3. 簡單說一下常見的資料結構:
1.堆疊Stack:
又稱堆疊,它是運算受限的線性表,其限制是僅允許在標的一端進行插入和洗掉操作,不允許在其他任何位置進行添加、查找、洗掉等操作,
我們可以簡單理解成:堆疊結構對元素的存取有如下要求:
先進后出(也就是說,堆疊中越早存進去的元素,就越在最下面)
例如:子彈壓進彈夾,先壓進去的子彈在下面,后壓進去的子彈在上面,當開槍時,先彈出上面的子彈,然后才能彈出下面的子彈,
所以,堆疊的入口、出口的都是堆疊的頂端位置,我們需要知道兩個名詞:
1)壓堆疊:就是存元素,即,把元素存盤到堆疊的頂端位置,堆疊中已有元素依次向堆疊底方向移動一個位置,
2)彈堆疊:就是取元素,即,把堆疊的頂端位置元素取出,堆疊中已有元素依次向堆疊頂方向移動一個位置,
2.佇列queue:
又稱隊,它同堆疊一樣,也是一種運算受限的線性表,其限制是僅允許在表的一端進行插入,而在表的另一端進行洗掉,
我們可以簡單理解成:佇列結構對元素的存取有如下要求:
先進先出(也就是說,最先存進去的元素,是可以最先取出的)
例如:我們生活中排隊買結賬,最先到的人最先付款,最后到的人得等前面的人都付款結束后才能付款,
所以:佇列的入口、出口各占一側
3.陣列Array:
是有序的元素序列,陣列是在記憶體中開辟一段連續的空間,并在此空間存放元素,就像是一列火車,從1號車廂到最后一節車廂,每節車廂都有自己的固定編號,乘坐火車的人可以通過車廂號快速找到自己的位置,
我們可以簡單理解成:陣列結構對元素的存取有如下要求:
查找元素快:通過索引,可以快速訪問指定位置的元素,
增刪元素慢:
我們指定一個索引增加元素,不能修改原陣列的長度,而是需要創建一個新長度的陣列,將指定新元素存盤在指定索引位置,再把原陣列元素根據索引,復制到新陣列對應索引的位置,
我們指定一個索引洗掉元素:不能修改原陣列的長度,而是需要創建一個新長度的陣列,把原陣列元素根據索引復制到新陣列對應索引的位置,原陣列中指定索引位置元素不復制到新陣列中,
4.鏈表LinkedList:
是一系列節點Node(鏈表中每一個元素稱為節點)組成,節點可以在運行時動態生成,每個節點包括兩個部分:一個是存盤資料元素的資料域,另一個是存盤下一個節點地址的指標域,我們常說的鏈表結構有單向鏈表與雙向鏈表,
我們這里介紹的是單向鏈表,
我們可以簡單理解成:鏈表結構對元素的存取有如下要求:
多個節點之間,通過地址進行連接,
例如:自行車的鏈條,就是通過上一個環連接下一個環,這樣就形成一個鏈條了
查找元素慢:想查找某個元素,需要通過連接的節點,依次向后查找指定元素
增刪元素快:
增加元素:只需要修改連接下個元素的地址即可,
洗掉元素:只需要修改連接下個元素的地址即可,
5.紅黑樹
二叉樹BinaryTree:每個節點不超過2的有序樹(tree)
我們可以簡單理解成生活的樹的結構,只不過每個節點上都最多只能有兩個子節點,
二叉樹是每個節點最多有兩個子樹的樹結構,頂上的叫根節點,兩邊被稱作“左子樹”和“右子樹”,
紅黑樹:紅黑樹本身就是一顆二叉查找樹,將節點插入后,該樹仍然是一顆二叉查找樹,也就意味著,樹的鍵值仍然是有序的,紅黑樹的速度特別快,趨近平衡樹,查找葉子元素最少和最多次數不多于二倍
紅黑樹的特點:
- 節點可以是紅色的或者黑色的
- 根節點是黑色的
- 葉子節點(特指空節點)是黑色的
- 每個紅色節點的子節點都是黑色的
- 任何一個節點到其每一個葉子節點的所有路徑上黑色節點數相同
4. 什么是向上轉型?什么是向下轉型?
在JAVA中,繼承是一個重要的特征,通過extends關鍵字,子類可以復用父類的功能,如果父類不能滿足當前子類的需求,則子類可以重寫父類中的方法來加以擴展,
那么在這個程序中就存在著多型的應用,存在著兩種轉型方式,分別是:向上轉型和向下轉型,
向上轉型:可以把不同的子類物件都當作父類來看,進而屏蔽不同子類物件之間的差異,寫出通用的代碼,做出通用的編程,統一呼叫標準,
比如:父類Parent,子類Child
父類的參考指向子類物件:Parent p=new Child();
說明:向上轉型時,子類物件當成父類物件,只能呼叫父類的功能,如果子類重寫了父類中宣告過的方法,方法體執行的就是子類重過后的功能,但是此時物件是把自己看做是父型別別的,所以其他資源使用的還是父型別的,
比如:花木蘭替父從軍,大家都把花木蘭看做她爸,但是實際從軍的是花木蘭,而且,花木蘭只能做她爸能做的事,在軍營里是不可以化妝的,
向下轉型(較少):子類的參考的指向子類物件,程序中必須要采取到強制轉型,這個是之前向上造型過的子類物件仍然想執行子類的特有功能,所以需要重新恢復成子類物件
Parent p = new Child();//向上轉型,此時,p是Parent型別
Child c = (Child)p;//此時,把Parent型別的p轉成小型別Child
其實,相當于創建了一個子類物件一樣,可以用父類的,也可以用自己的
說明:向下轉型時,是為了方便使用子類的特殊方法,也就是說當子類方法做了功能拓展,就可以直接使用子類功能,
比如:花木蘭打仗結束,就不需要再看做是她爸了,就可以”對鏡貼花黃”了
5. 比較一下String與StringBuilder
String
1. 特點:
創建之后長度內容是不可變的,每次拼接字串,都會產生新的物件
- 如果是直接“ ” 或者字串常量拼接產生的,保存在字串常量池中
- 如果是直接通過new方式創建的,保存在堆中
2. 創建方式:
String() String(String s) String(char[] c) String(byte[] b) String s = “abc”;
3. 優缺點:
- 優點:String類提供了豐富的關于操作字串的方法,比如:拼接、獲取對應下標處的字符、截取子串等等
- 缺點:在進行字串拼接+=的時候,效率比較低
4. String轉StringBuilder:
String s = “abc”; StringBuilder sb = new StringBuilder(s);
StringBuilder
1. 特點:
StringBuilder是一個長度可變的字串序列,在創建的時候,會有一個長度為16的默認空間
當拼接字串的時候,實在原物件的基礎之上進行拼接,如果長度不夠就擴容
所以StringBuilder在創建之后,對應的操作一直是用一個物件
2. 創建方式:
StringBuilder sb = new StringBuilder();//創建一個長度為16的StringBuilder物件
StringBuilder sb = new StringBuilder(“abc”);//以指定字串內容為“abc”的方式創建一個StringBuilder物件
3. 優缺點:
- 優點:在拼接的時候,不會產生新的物件,就避免了因為拼接頻繁生成物件的問題,提高了程式的效率
- 缺點:對于字串的操作,不太方便,所以在使用的時候,如果拼接操作很多的話:
先將String轉為StringBuilder進行拼接,拼接完成之后再轉回String
4. StringBuilder轉String:
StringBuilder sb = new StringBuilder();
sb.append(“abc”);
String s = sb.toString();
6.什么是泛型通配符?什么是泛型的上下限?
當使用泛型類或者介面時,傳遞的資料中,泛型型別不確定,可以通過通配符<?>表示,但是一旦使用泛型的通配符后,只能使用Object類中的共性方法,集合中元素自身方法無法使用,
通配符基本使用
泛型的通配符:不知道使用什么型別來接收的時候,此時可以使用?,?表示未知通配符,
通配符高級使用----受限泛型
之前設定泛型的時候,實際上是可以任意設定的,只要是類就可以設定,但是在JAVA的泛型中可以指定一個泛型的上限和下限,
泛型的上限:
- 格式:
型別名稱 <? extends 類 > 物件名稱 - 意義:
只能接收該型別及其子類
泛型的下限:
- 格式:
型別名稱 <? super 類 > 物件名稱 - 意義:
只能接收該型別及其父型別
7. 執行緒有幾種狀態?它們是怎么切換的?
執行緒生命周期,主要有五種狀態:
- 新建狀態(New) : 當執行緒物件創建后就進入了新建狀態.如:Thread t = new MyThread();
- 就緒狀態(Runnable):當呼叫執行緒物件的start()方法,執行緒即為進入就緒狀態.
處于就緒(可運行)狀態的執行緒,只是說明執行緒已經做好準備,隨時等待CPU調度執行,并不是執行了t.start()此執行緒立即就會執行 - 運行狀態(Running):當CPU調度了處于就緒狀態的執行緒時,此執行緒才是真正的執行,即進入到運行狀態
就緒狀態是進入運行狀態的唯一入口,也就是執行緒想要進入運行狀態狀態執行,先得處于就緒狀態 - 阻塞狀態(Blocked):處于運狀態中的執行緒由于某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入就緒狀態才有機會被CPU選中再次執行.
根據阻塞狀態產生的原因不同,阻塞狀態又可以細分成三種:
等待阻塞:運行狀態中的執行緒執行wait()方法,本執行緒進入到等待阻塞狀態
同步阻塞:執行緒在獲取synchronized同步鎖失敗(因為鎖被其他執行緒占用),它會進入同步阻塞狀態
其他阻塞:呼叫執行緒的sleep()或者join()或發出了I/O請求時,執行緒會進入到阻塞狀態.當sleep()狀態超時.join()等待執行緒終止或者超時或者I/O處理完畢時執行緒重新轉入就緒狀態 - 死亡狀態(Dead):執行緒執行完了或者因例外退出了run()方法,該執行緒結束生命周期
就緒 → 執行:為就緒執行緒分配CPU即可變為執行狀態"
執行 → 就緒:正在執行的執行緒由于時間片用完被剝奪CPU暫停執行,就變為就緒狀態
執行 → 阻塞:由于發生某事件,使正在執行的執行緒受阻,無法執行,則由執行變為阻塞
(例如執行緒正在訪問臨界資源,而資源正在被其他執行緒訪問)
反之,如果獲得了之前需要的資源,則由阻塞變為就緒狀態,等待分配CPU再次執行
8.什么是執行緒池?執行緒池有什么優點?
我們使用執行緒的時候就去創建一個執行緒,這樣實作起來非常簡便,但是就會有一個問題:
如果并發的執行緒數量很多,并且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁創建執行緒就會大大降低系統的效率,因為頻繁創建執行緒和銷毀執行緒需要時間,
所以我們在在Java中可以通過執行緒池來避免這些問題:
執行緒池:其實就是一個容納多個執行緒的容器,其中的執行緒可以反復使用,省去了頻繁創建執行緒物件的操作,無需反復創建執行緒而消耗過多資源,
合理利用執行緒池能夠帶來三個好處:
- 降低資源消耗,減少了創建和銷毀執行緒的次數,每個作業執行緒都可以被重復利用,可執行多個任務,
- 提高回應速度,當任務到達時,任務可以不需要的等到執行緒創建就能立即執行,
- 提高執行緒的可管理性,可以根據系統的承受能力,調整執行緒池中作業線執行緒的數目,防止因為消耗過多的記憶體,而把服務器累趴下(每個執行緒需要大約1MB記憶體,執行緒開的越多,消耗的記憶體也就越大,最后死機),
9. 你對Java記憶體分配了解多少?簡單談談:
Java虛擬機管理的記憶體包括幾個運行時資料記憶體:方法區、虛擬機堆疊、本地方法堆疊、堆、程式計數器,其中方法區和堆是由執行緒共享的資料區,其他幾個是執行緒隔離的資料區
1. 程式計數器
程式計數器是一塊較小的記憶體,他可以看做是當前執行緒所執行的行號指示器,位元組碼解釋器作業的時候就是通過改變這個計數器的值來選取下一條需要執行的位元組碼的指令,分支、回圈、跳轉、例外處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成,如果執行緒正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機位元組碼指令的地址;如果正在執行的是Native方法,這個計數器則為空,此記憶體區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemotyError情況的區域
2 Java虛擬機堆疊
虛擬機堆疊描述的是Java方法執行的記憶體模型:每個方法在執行的同時都會創建一個堆疊幀用于儲存區域變數表、運算元堆疊、動態鏈接、方法出口等資訊,每個方法從呼叫直至完成的程序,就對應著一個堆疊幀在虛擬機堆疊中入堆疊到出堆疊的程序,
堆疊記憶體就是虛擬機堆疊,或者說是虛擬機堆疊中區域變數表的部分
區域變數表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件參考(refrence)型別和returnAddress型別(指向了一條位元組碼指令的地址)
其中64位長度的long和double型別的資料會占用兩個區域變數空間,其余的資料型別只占用1個,
Java虛擬機規范對這個區域規定了兩種例外狀況:如果執行緒請求的堆疊深度大于虛擬機所允許的深度,將拋出StackOverflowError例外,如果虛擬機擴展時無法申請到足夠的記憶體,就會拋出OutOfMemoryError例外
3 本地方法堆疊
本地方法堆疊和虛擬機堆疊發揮的作用是非常類似的,他們的區別是虛擬機堆疊為虛擬機執行Java方法(也就是位元組碼)服務,而本地方法堆疊則為虛擬機使用到的Native方法服務
本地方法堆疊區域也會拋出StackOverflowError和OutOfMemoryErroy例外
4 Java堆
堆是Java虛擬機所管理的記憶體中最大的一塊,Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機啟動的時候創建,此記憶體區域的唯一目的是存放物件實體,幾乎所有的物件實體都在這里分配記憶體,所有的物件實體和陣列都在堆上分配
Java堆是垃圾收集器管理的主要區域,Java堆細分為新生代和老年代
不管怎樣,劃分的目的都是為了更好的回收記憶體,或者更快地分配記憶體
Java堆可以處于物理上不連續的記憶體空間中,只要邏輯上是連續的即可,如果在堆中沒有完成實體分配,并且堆也無法再擴展時將會拋出OutOfMemoryError例外
5 方法區
方法區它用于儲存已被虛擬機加載的類資訊、常量、靜態變數、即時編譯器編譯后的代碼等資料
除了Java堆一樣不需要連續的記憶體和可以選擇固定大小或者可擴展外,還可以選擇不實作垃圾收集,這個區域的記憶體回收目標主要是針對常量池的回收和對型別的卸載
當方法區無法滿足記憶體分配需求時,將拋出OutOfMemoryErroy例外
1.6 運行時常量池
它是方法區的一部分,Class檔案中除了有關的版本、欄位、方法、介面等描述資訊外、還有一項資訊是常量池,用于存放編譯期生成的各種字面量和符號參考,這部分內容將在類加載后進入方法區的運行時常量池中存放
Java語言并不要求常量一定只有編譯期才能產生,也就是可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法
當常量池無法再申請到記憶體時會拋出OutOfMemoryError例外
10. 談談你對static的了解
static是Java中的一個關鍵字,可以用來修飾方法、變數、代碼塊、內部類,還可以使用靜態導包
1.static方法
static方法一般稱作靜態方法,由于靜態方法不依賴于任何物件就可以進行訪問,因此對于靜態方法來說,是沒有this的,因為它不依附于任何物件,既然都沒有物件,就談不上this了,并且由于這個特性,在靜態方法中不能訪問類的非靜態成員變數和非靜態成員方法,因為非靜態成員方法/變數都是必須依賴具體的物件才能夠被呼叫,
注意:雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變數,但是在非靜態成員方法中是可以訪問靜態成員方法/變數的,
2.static變數
static變數也稱作靜態變數,靜態變數和非靜態變數的區別是:靜態變數被所有的物件所共享,在記憶體中只有一個副本【存放在方法區】,它當且僅當在類初次加載時會被初始化【加final和不加final的static變數初始化的位置不一樣】,而非靜態變數是物件所擁有的,在創建物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響,
static成員變數的初始化順序按照定義的順序進行初始化,
3.static代碼塊
static關鍵字還有一個比較關鍵的作用就是用來形成靜態代碼塊以優化程式性能,因為靜態資源只會加載一次,static塊可以置于類中的任何地方,類中可以有多個static塊,在類初次被加載的時候,會按照static塊的順序來執行每個static塊,并且只會執行一次,
初始化的順序 靜態代碼塊 > 構造代碼塊 > 建構式
為什么說static塊可以用來優化程式性能,是因為它的特性:只會在類加載的時候執行一次,
下面看個例子:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
isBornBoomer是用來這個人是否是1946-1964年出生的,而每次isBornBoomer被呼叫的時候,都會生成startDate和birthDate兩個物件,造成了空間浪費,如果改成這樣效率會更好,其實就是利用了靜態代碼塊在記憶體中值加載一次的機制:
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
因此,很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行,
4.靜態內部類
在談論靜態內部之前,我們來談談,為何要用內部類?
定義在一個類內部的類叫內部類,包含內部類的類稱為外部類,內部類可以宣告public、protected、private等訪問限制,可以宣告為abstract的供其他內部類或外部類繼承與擴展,或者宣告為static、final的,也可以實作特定的介面,內部類有以下特點:
1.內部類一般只為其外部類使用【比如:hashmap集合中,內部類Entry<K,V>】
2.內部類提供了某種進入外部類的窗戶,內部類存在外部類的參考,所以內部類可以直接訪問外部類的屬性
3.每個內部類都能獨立地繼承一個介面,而無論外部類是否已經繼承了某個介面,因此,內部類使多重繼承的解決方案變得更加完整,
靜態內部類
定義靜態內部類:在定義內部類的時候,可以在其前面加上一個權限修飾符static,此時這個內部類就變為了靜態內部類,通常稱為嵌套類,當內部類是static時,意味著:
- 要創建嵌套類的物件,并不需要其外圍類的物件;
- 不能從嵌套類的物件中訪問非靜態的外圍類物件(不能夠從靜態內部類的物件中訪問外部類的非靜態成員);
嵌套類與普通的內部類還有一個區別:普通內部類的欄位與方法,只能放在類的外部層次上,所以普通的內部類不能有static資料和static欄位, 也不能包含嵌套類,但是在嵌套類里可以包含所有這些東西,也就是說,在非靜態內部類中不可以宣告靜態成員,只有將某個內部類修飾為靜態類,然后才能夠在這 個類中定義靜態的成員變數與成員方法,
另外,在創建靜態內部類時不需要將靜態內部類的實體系結在外部類的實體上,普通非靜態內部類的 物件是依附在外部類物件之中的,要在一個外部類中定義一個靜態的內部類,不需要利用關鍵字new來創建內部類的實體,靜態類和方法只屬于類本身,并不屬于 該類的物件,更不屬于其他外部類的物件,
補充:內部類識別符號
每個類會產生一個.class檔案,檔案名即為類名,同樣,內部類也會產生這么一個.class檔案,但是它的名稱卻不是內部類的類名,而是有著嚴格的限制:外圍類的名字,加上$,再加上內部類名字,
5.靜態導包
靜態導包就是java包的靜態匯入,用import static代替import靜態匯入包是JDK1.5中的新特性,
一般我們匯入一個類都用 import com……ClassName;
而靜態匯入是這樣:import static com……ClassName.* ;
這里的多了個static,還有就是類名ClassName后面多了個.* ,意思是匯入這個類里的靜態方法,
當然,也可以只匯入某個靜態方法,只要把 .* 換成靜態方法名就行了,
然后在這個類中,就可以直接用方法名呼叫靜態方法,而不必用ClassName.方法名的方式來呼叫,
好處:這種方法的好處就是可以簡化一些操作,例如列印操作System.out.println(…);
就可以將其寫入一個靜態方法print(…),在使用時直接print(…)就可以了,
但是這種方法建議在有很多重復呼叫的時候使用,如果僅有一到兩次呼叫,不如直接寫來的方便
在靜態匯入之前:
public class TestStatic {
public static void main(String[] args) {
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.toHexString(42));
}
}
在靜態匯入之后:
import static java.lang.System.out;
import static java.lang.Integer.*;
public class TestStaticImport {
public static void main(String[] args) {
out.println(MAX_VALUE);
out.println(toHexString(42));
}
}
讓我們看一下使用靜態匯入特性的代碼中將發生什么:
- 雖然該特性通常稱為“靜態匯入”,但語法必須是import static,后面跟你想匯入的static成員的完全限定名稱,或者通配符,在本例中,我們在System類的out物件上進行靜態匯入,
- 在本例中,我們可能想使用java.lang.Integer類的幾個static成員,該靜態匯入陳述句使用通配符來表達“我想在此類中的所有靜態成員上進行靜態匯入”,
- 現在我們終于看到靜態匯入特性的好處!我們不必在System.out.println中鍵入System,太好了!另外,我們不必在Integer.MAX_VALUE中鍵入Integer,因此,在這行代碼中,我們能夠將快捷方式用于靜態方法和一個常量,
- 最后,我們進行更多的快捷操作,這次針對Integer類的方法,
下面是使用靜態匯入的幾條原則:
你必須說import static, 不能說static import,
提防含糊不清的命名static成員,例如,如果你對Integer類和Long類執行了靜態匯入,參考MAX_VALUE將導致一個編譯器錯誤,因為Integer和Long都有一個MAX_VALUE常量,并且Java不會知道你在參考哪個MAX_VALUE,
你可以在static物件參考、常量(記住,它們是static 或final)和static方法上進行靜態匯入,
11.什么是方法?談談你對方法的理解
1.概念
方法其實是具有一定功能的代碼塊,我們可以把需要多次使用的功能提取成一個方法,這樣多次使用的時候也不需要把這些重復的代碼寫多次造成代碼的冗余,
2.格式
方法定義的格式:修飾符 回傳值型別 方法名(引數串列){方法體}
方法簽名:方法名(引數串列)
3.注意事項
注意:我們這里說的是回傳值型別而不是回傳值,如果一個方法有回傳值,那么回傳值型別必須設定為與回傳值相同的型別,并且回傳值需要使用return關鍵字來回傳,
4.方法的多載:
在同一個類中出現方法名相同但引數串列不同方法的現象
注意:方法之間能否構成多載,取決于方法的引數個數與型別,與方法的引數名無關
我們可以通過方法名+引數串列的方式確定要呼叫的是哪個方法
方法的傳值:基本型別傳遞的是實際值,參考型別傳遞的是地址
而且方法的引數屬于形參,只是格式上需要定義,但是呼叫方法時起不到限制的作用
形參:定義方法的時候的引數串列
實參:使用方法的時候傳入的資料
多載的意義:
是為了方便外界對方法進行呼叫,什么樣的引數程式都可以找到對應的方法來執行,體現的是程式的靈活性
5.方法的重寫:
子類繼承父類以后,如果子類對父類的功能不滿意,可以重寫父類的方法
但是重寫的時候需要注意如下的規則:兩同兩小一大
一大:子類方法的修飾符范圍 >= 父類方法的修飾符范圍–指的是訪問控制符
兩同:方法名相同,引數串列相同
兩小: 子類方法的回傳值型別 <= 父類方法的回傳值型別【這個大小是繼承關系,不是值的大小】
子類方法拋出的例外型別 <= 父類方法拋出的例外型別【這個還沒學,不用管】
注意:如果父類方法的回傳值型別是void,子類保持一致即可
注意:子類不可以重寫父類的私有方法,還是因為不可見
重寫的意義:是在不修改原始碼的前提下,進行功能的修改和拓展
(OCP原則:面向修改關閉,面向拓展開放)
6.方法的遞回:
遞回:在方法中呼叫自己本身
注意遞回次數過多時,會出現堆疊溢位例外
練習題 : 求數字階乘(遞回解法版)
需求:接收用戶輸入的數字,計算該數字的階乘結果
已知:負數不可以有階乘,0的階乘結果是1,
5 ! = 5 x 4 x 3 x 2 x 1
package cn.cxy.design;
//需求:求用戶輸入數字的階乘結果
//f(int n)--用來求階乘
//規律:
//f(n)= n*f(n-1)
//f(5)= 5*4*3*2*1 = 5*f(4)
//f(4)= 4*3*2*1 = 4*f(3)
//f(3)= 3*2*1 = 3*f(2)
//f(2)= 2*1 = 2*f(1)
//f(1)= 1
//
//5!=5*4*3*2*1=120
//4!=4*3*2*1
//3!=3*2*1
//2!=2*1
//1!=1
public class TestRecursion {
public static void main(String[] args) {
int result = f(15);//呼叫f()用來求階乘
System.out.println(result);
}
/**遞回的兩要素 1.總結規律 2.最簡問題*/
public static int f(int n) {
if(n == 1) {//最簡問題
return 1;
}else {//其他情況 n*f(n-1)
//遞回:再方法內部自己呼叫自己
return n*f(n-1);
}
}
}

練習題 : 遞回求目錄總大小
需求:遞回求目錄的總大小 D:\ready,步驟分析如下:
1.列出檔案夾中的所有資源–listFiles()–>File[]
2.判斷,當前資源是檔案還是檔案夾–檔案夾大小為0,檔案大小需要累加
–是檔案,求檔案的位元組量大小length(),累加就行
–是檔案夾,繼續列出檔案夾下的所有資源–listFiles()–>File[]
–判斷,是檔案,求檔案的位元組量大小length(),累加就行
–判斷,是檔案夾,再一次列出檔案夾下的所有資源
–…重復操作
也就是說,規律就是:只要是檔案夾,就需要重復步驟1 2
package cn.cxy.file;
import java.io.File;
/**本類用來遞回求目錄總大小*/
public class FileSumRecursion {
public static void main(String[] args) {
//1.指定要求哪個目錄的總大小
/**注意:此處指定的目錄必須是真實存在的
* 如果傳一個不存在的檔案夾會報錯,如果是傳了一個空檔案夾,大小為0*/
File file = new File("D:\\ready");
//2.呼叫size()求目錄大小
long total = size(file);
//3.接收結果并列印
System.out.println("檔案夾的總大小為:"+total);
}
private static long size(File file) {
//1.列出檔案夾中的所有資源--listFiles()-->File[]
File[] fs = file.listFiles();
//2.遍歷陣列,獲取每file物件
//2.1定義變數,記錄總和
long sum = 0;
for(int i = 0;i < fs.length ; i++) {
//2.2通過下標操作當前遍歷到的資源
File f = fs[i];
//2.3判斷,當前資源是檔案還是檔案夾--檔案夾大小為0,檔案大小需要累加
if(f.isFile()) {
//--是檔案,求檔案的位元組量大小length(),累加就行
sum += f.length();//相當于:sum = sum + f.length();
}else if(f.isDirectory()) {
//--是檔案夾,繼續列出檔案夾下的所有資源,1 2步驟--listFiles()-->File[]
/**方法的遞回,遞回現象,就是在方法的內部呼叫方法自身*/
sum += size(f);
}
}
return sum;//把sum記錄的值回傳呼叫位置
}
}
練習題 : 遞回洗掉檔案夾
需求:遞回洗掉檔案夾 D:\ready\a
1.列出檔案夾下的所有資源listFiles()
2.判斷,當前資源是檔案還是檔案夾
–判斷,是檔案,直接洗掉delete()
–判斷,是檔案夾,繼續重復操作1 2
具體思路可以分為這么幾步:
1.首先,我們需要指定一個根目錄作為要洗掉的物件
2.列出檔案夾下的所有資源listFiles(),并進行遍歷
3.判斷當前的資源,如果是檔案,直接洗掉;如果是檔案夾,則執行步驟2
4.將檔案夾中的內容洗掉完畢后,洗掉檔案夾本身
package cn.tedu.file;
import java.io.File;
/**本類用于遞回洗掉目錄*/
public class TestFileDeleteRecursion {
public static void main(String[] args) {
//1.指定要洗掉的目錄
/**為了更好的測驗,注意指定的目錄是已存在的目錄,但是,千萬不要刪盤符!!!!*/
/*我們也有一些沒有權限的檔案夾,那個是無法訪問且不能洗掉的哦*/
File file = new File("D:\\ready\\a");
//2.呼叫洗掉目錄的方法
boolean result = del(file);
//3.列印洗掉的結果
System.out.println("洗掉的結果為:"+result);
}
public static boolean del(File file) {//完成的同學不是很多,抓緊時間寫,寫完截圖發群里哈,這首歌結束我們繼續
//1.列出檔案夾下的所有資源
File[] fs = file.listFiles();
//2.回圈遍歷拿到的所有資源
for (int i = 0; i < fs.length; i++) {
//2.1獲取本次回圈遍歷到的file物件
File f = fs[i];
//3.判斷,當前資源是檔案還是檔案夾
if(f.isFile()) {
f.delete();//是檔案,直接洗掉
System.out.println(file.getName()+"檔案洗掉成功!");
}else if(f.isDirectory()) {
//是檔案夾,需要繼續進行步驟1 2 ,出現了重復呼叫的情況
//遞回,在方法的內部呼叫自己
del(f);
}
}
//位置:在for回圈執行之外洗掉檔案夾
file.delete();//空檔案夾直接洗掉
System.out.println(file.getName()+"檔案夾洗掉成功!");
return true;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/295640.html
標籤:java
上一篇:超強二叉樹決議.必收藏!(陣列,鏈表實作,8種遍歷方法,前,中,后序線索化二叉樹及其遍歷)---風之java
下一篇:別發照片騙技術人
