剛剛經(jīng)歷過秋招岸蜗,看了大量的面經(jīng)尉咕,順便將常見的Java常考知識(shí)點(diǎn)總結(jié)了一下璃岳,并根據(jù)被問到的頻率大致做了一個(gè)標(biāo)注年缎。一顆星表示知識(shí)點(diǎn)需要了解,被問到的頻率不高铃慷,面試時(shí)起碼能說個(gè)差不多单芜。兩顆星表示被問到的頻率較高或?qū)斫釰ava有著重要的作用,建議熟練掌握枚冗。三顆星表示被問到的頻率非常高缓溅,建議深入理解并熟練掌握其相關(guān)知識(shí),方便面試時(shí)拓展(方便裝逼)赁温,給面試官留下個(gè)好印象坛怪。
微信搜索公眾號(hào)路人zhang,回復(fù)面試手冊(cè)股囊,領(lǐng)取本文檔PDF版及更多面試資料袜匿。
推薦閱讀:一文搞懂所有HashMap面試題
JVM、JRE及JDK的關(guān)系 **
JDK(Java Development Kit)是針對(duì)Java開發(fā)員的產(chǎn)品稚疹,是整個(gè)Java的核心居灯,包括了Java運(yùn)行環(huán)境JRE祭务、Java工具和Java基礎(chǔ)類庫。
Java Runtime Environment(JRE)是運(yùn)行JAVA程序所必須的環(huán)境的集合怪嫌,包含JVM標(biāo)準(zhǔn)實(shí)現(xiàn)及Java核心類庫义锥。
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,是整個(gè)java實(shí)現(xiàn)跨平臺(tái)的最核心的部分岩灭,能夠運(yùn)行以Java語言寫作的軟件程序拌倍。
**簡單來說就是JDK是Java的開發(fā)工具,JRE是Java程序運(yùn)行所需的環(huán)境噪径,JVM是Java虛擬機(jī).它們之間的關(guān)系是JDK包含JRE和JVM柱恤,JRE包含JVM.**
JAVA語言特點(diǎn) **
- Java是一種面向?qū)ο蟮恼Z言
- Java通過Java虛擬機(jī)實(shí)現(xiàn)了平臺(tái)無關(guān)性找爱,一次編譯梗顺,到處運(yùn)行
- 支持多線程
- 支持網(wǎng)絡(luò)編程
- 具有較高的安全性和可靠性
JAVA和C++的區(qū)別 **
面試時(shí)記住前四個(gè)就行了
- Java 通過虛擬機(jī)從而實(shí)現(xiàn)跨平臺(tái)特性车摄,但是 C++ 依賴于特定的平臺(tái)寺谤。
- Java 沒有指針,它的引用可以理解為安全指針练般,而 C++ 具有和 C 一樣的指針矗漾。
- Java 支持自動(dòng)垃圾回收,而 C++ 需要手動(dòng)回收薄料。
- Java 不支持多重繼承敞贡,只能通過實(shí)現(xiàn)多個(gè)接口來達(dá)到相同目的,而 C++ 支持多重繼承摄职。
- Java 不支持操作符重載誊役,雖然可以對(duì)兩個(gè) String 對(duì)象執(zhí)行加法運(yùn)算,但是這是語言內(nèi)置支持的操作谷市,不屬于操
作符重載蛔垢,而 C++ 可以。 - Java 的 goto 是保留字迫悠,但是不可用鹏漆,C++ 可以使用 goto。
Java的基本數(shù)據(jù)類型 〈葱埂**
注意
String
不是基本數(shù)據(jù)類型
類型 | 關(guān)鍵字 | 包裝器類型 | 占用內(nèi)存(字節(jié))(重要) | 取值范圍 | 默認(rèn)值 |
---|---|---|---|---|---|
字節(jié)型 | byte | Byte | 1 | -128(-2^7) ~ 127(2^7-1) | 0 |
短整型 | short | Short | 2 | -2^15 ~ 2^15-1 | 0 |
整型 | int | Integer | 4 | -2^31 ~ 2^31-1 | 0 |
長整型 | long | Long | 8 | -2^63 ~ 2^63-1 | 0L |
單精度浮點(diǎn)型 | float | Float | 4 | 3.4e-45 ~ 1.4e38 | 0.0F |
雙精度浮點(diǎn)型 | double | Double | 8 | 4.9e-324 ~ 1.8e308 | 0.0D |
字符型 | char | Character | 2 | '\u0000' | |
布爾型 | boolean | Boolean | 1 | true/flase | flase |
隱式(自動(dòng))類型轉(zhuǎn)換和顯示(強(qiáng)制)類型轉(zhuǎn)換∫樟帷**
- 隱式(自動(dòng))類型轉(zhuǎn)換:從存儲(chǔ)范圍小的類型到存儲(chǔ)范圍大的類型。
byte
→short(char)
→int
→long
→float
→double
- 顯示(強(qiáng)制)類型轉(zhuǎn)換:從存儲(chǔ)范圍大的類型到存儲(chǔ)范圍小的類型鞠抑。
double
→float
→long
→int
→short(char)
→byte
饭聚。該類類型轉(zhuǎn)換很可能存在精度的損失。
看一個(gè)經(jīng)典的代碼
short s = 1;
s = s + 1;
這是會(huì)報(bào)錯(cuò)的搁拙,因?yàn)?是int
型秒梳,s+1
會(huì)自動(dòng)轉(zhuǎn)換為int
型法绵,將int
型直接賦值給short
型會(huì)報(bào)錯(cuò)。
做一下修改即可避免報(bào)錯(cuò)
short s = 1;
s = (short)(s + 1);
或這樣寫酪碘,因?yàn)?code>s += 1會(huì)自動(dòng)進(jìn)行強(qiáng)制類型轉(zhuǎn)換
short s = 1;
s += 1;
自動(dòng)裝箱與拆箱 **
裝箱:將基本類型用包裝器類型包裝起來
-
拆箱:將包裝器類型轉(zhuǎn)換為基本類型
這個(gè)地方有很多易混淆的地方朋譬,但在面試中問到的頻率一般,筆試的選擇題中經(jīng)常出現(xiàn)兴垦,還有一個(gè)
String
創(chuàng)建對(duì)象和這個(gè)比較像此熬,很容易混淆,在下文可以看到 -
下面這段代碼的輸出結(jié)果是什么滑进?
public class Main { public static void main(String[] args) { Integer a = 100; Integer b = 100; Integer c = 128; Integer d = 128; System.out.println(a==b); System.out.println(c==d); } }
true false
很多人看到這個(gè)結(jié)果會(huì)很疑惑,為什么會(huì)是一個(gè)
true
一個(gè)flase
.其實(shí)從源碼中可以很容易找到原因.首先找到Integer
方法中的valueOf
方法public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
可以看到當(dāng)不滿足
if
語句中的條件募谎,就會(huì)重新創(chuàng)建一個(gè)對(duì)象返回扶关,那結(jié)果必然不相等。繼續(xù)打開IntegerCache
可以看到private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
代碼挺長数冬,大概說的就是在通過
valueOf
方法創(chuàng)建Integer
對(duì)象的時(shí)候节槐,如果數(shù)值在[-128,127]之間,便返回指向IntegerCache.cache
中已經(jīng)存在的對(duì)象的引用拐纱;否則創(chuàng)建一個(gè)新的Integer
對(duì)象铜异。所以上面代碼中a
與b
相等,c
與d
不相等秸架。 -
在看下面的代碼會(huì)輸出什么
public class Main { public static void main(String[] args) { Double a = 1.0; Double b = 1.0; Double c = 2.0; Double d = 2.0; System.out.println(a==b); System.out.println(c==d); } }
flase flase
采用同樣的方法揍庄,可以看到
Double
的valueOf
方法,每次返回都是重新new
一個(gè)新的對(duì)象东抹,所以上面代碼中的結(jié)果都不想等蚂子。public static Double valueOf(double d) { return new Double(d); }
-
最后再看這段代碼的輸出結(jié)果
public class Main { public static void main(String[] args) { Boolean a = false; Boolean b = false; Boolean c = true; Boolean d = true; System.out.println(a==b); System.out.println(c==d); } }
true true
老方法繼續(xù)看
valueOf
方法public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
再看看
TRUE
和FALSE
是個(gè)什么東西,是兩個(gè)靜態(tài)成員屬性缭黔。public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false);
**說下結(jié)論 **:Integer
食茎、Short
、Byte
馏谨、Character
、Long
這幾個(gè)類的valueOf
方法的實(shí)現(xiàn)是類似的。Double
吆豹、Float
的valueOf
方法的實(shí)現(xiàn)是類似的租漂。然后是Boolean
的valueOf
方法是單獨(dú)一組的。
-
Integer i = new Integer(xxx)
和Integer i =xxx
的區(qū)別這兩者的區(qū)別主要是第一種會(huì)觸發(fā)自動(dòng)裝箱壹哺,第二者不會(huì)
最后看看下面這段程序的輸出結(jié)果
public class Main { public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Long g = 3L; int int1 = 12; int int2 = 12; Integer integer1 = new Integer(12); Integer integer2 = new Integer(12); Integer integer3 = new Integer(1); System.out.println("c==(a+b) ->"+ (c==(a+b))); System.out.println("g==(a+b) ->" + (g==(a+b))); System.out.println( "c.equals(a+b) ->" + (c.equals(a+b))); System.out.println( "g.equals(a+b) ->" + (g.equals(a+b))); System.out.println("int1 == int2 -> " + (int1 == int2)); System.out.println("int1 == integer1 -> " + (int1 == integer1)); System.out.println("integer1 == integer2 -> " + (integer1 == integer2)); System.out.println("integer3 == a1 -> " + (integer3 == a)); } }
c==(a+b) ->true g==(a+b) ->true c.equals(a+b) ->true g.equals(a+b) ->false int1 == int2 -> true int1 == integer1 -> true integer1 == integer2 -> false integer3 == a1 -> false
下面簡單解釋這些結(jié)果抄伍。
1.當(dāng) "=="運(yùn)算符的兩個(gè)操作數(shù)都是包裝器類型的引用,則是比較指向的是否是同一個(gè)對(duì)象管宵,而如果其中有一個(gè)操作數(shù)是表達(dá)式(即包含算術(shù)運(yùn)算)則比較的是數(shù)值(即會(huì)觸發(fā)自動(dòng)拆箱的過程)截珍。所以
c==a+b
攀甚,g==a+b
為true
。2.而對(duì)于
equals
方法會(huì)先觸發(fā)自動(dòng)拆箱過程岗喉,再觸發(fā)自動(dòng)裝箱過程秋度。也就是說a+b,會(huì)先各自調(diào)用intValue
方法钱床,得到了加法運(yùn)算后的數(shù)值之后荚斯,便調(diào)用Integer.valueOf
方法,再進(jìn)行equals
比較查牌。所以c.equals(a+b)
為true
事期。而對(duì)于g.equals(a+b)
,a+b
會(huì)先拆箱進(jìn)行相加運(yùn)算纸颜,在裝箱進(jìn)行equals
比較兽泣,但是裝箱后為Integer
,g
為Long
胁孙,所以g.equals(a+b)
為false
唠倦。3.
int1 == int2
為true
無需解釋,int1 == integer1
涮较,在進(jìn)行比較時(shí)稠鼻,integer1
會(huì)先進(jìn)行一個(gè)拆箱操作變成int
型在進(jìn)行比較,所以int1 == integer1
為true
狂票。4.
integer1 == integer2
->false
候齿。integer1
和integer2
都是通過new
關(guān)鍵字創(chuàng)建的,可以看成兩個(gè)對(duì)象闺属,所以integer1 == integer2
為false
毛肋。integer3 == a1
->false
,integer3
是一個(gè)對(duì)象類型,而a1
是一個(gè)常量它們存放內(nèi)存的位置不一樣屋剑,所以integer3 == a1
為false
润匙,具體原因可學(xué)習(xí)下java的內(nèi)存模型。
String(不是基本數(shù)據(jù)類型)
String的不可變性 ***
在 Java 8 中唉匾,String
內(nèi)部使用 char
數(shù)組存儲(chǔ)數(shù)據(jù)孕讳。并且被聲明為final
,因此它不可被繼承巍膘。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
}
為什么Strin
g`要設(shè)計(jì)成不可變的呢(不可變性的好處):
1.可以緩存 hash
值()
因?yàn)?`String` 的` hash `值經(jīng)常被使用厂财,例如` String` 用做 `HashMap` 的 `key`。不可變的特性可以使得 `hash `值也不可變峡懈,
因此只需要進(jìn)行一次計(jì)算璃饱。
2.常量池優(yōu)化
`String` 對(duì)象創(chuàng)建之后,會(huì)在字符串常量池中進(jìn)行緩存肪康,如果下次創(chuàng)建同樣的對(duì)象時(shí)荚恶,會(huì)直接返回緩存的引用撩穿。
3.線程安全
`String` 不可變性天生具備線程安全,可以在多個(gè)線程中安全地使用谒撼。
字符型常量和字符串常量的區(qū)別 *
- 形式上: 字符常量是單引號(hào)引起的一個(gè)字符 字符串常量是雙引號(hào)引起的若干個(gè)字符
- 含義上: 字符常量相當(dāng)于一個(gè)整形值(ASCII值),可以參加表達(dá)式運(yùn)算 字符串常量代表一個(gè)地址值(該字符串在內(nèi)存中存放位置)
- 占內(nèi)存大小 字符常量占兩個(gè)字節(jié) 字符串常量占若干個(gè)字節(jié)(至少一個(gè)字符結(jié)束標(biāo)志)
什么是字符串常量池食寡?*
字符串常量池位于堆內(nèi)存中,專門用來存儲(chǔ)字符串常量廓潜,可以提高內(nèi)存的使用率抵皱,避免開辟多塊空間存儲(chǔ)相同的字符串,在創(chuàng)建字符串時(shí) JVM 會(huì)首先檢查字符串常量池辩蛋,如果該字符串已經(jīng)存在池中呻畸,則返回它的引用,如果不存在悼院,則實(shí)例化一個(gè)字符串放到池中擂错,并返回其引用。
String 類的常用方法都有那些樱蛤?**
面試時(shí)一般不會(huì)問,但面試或筆試寫字符串相關(guān)的算法題經(jīng)常會(huì)涉及到剑鞍,還是得背一背(以下大致是按使用頻率優(yōu)先級(jí)排序)
-
length()
:返回字符串長度 -
charAt()
:返回指定索引處的字符 -
substring()
:截取字符串 -
trim()
:去除字符串兩端空白 -
split()
:分割字符串昨凡,返回一個(gè)分割后的字符串?dāng)?shù)組。 -
replace()
:字符串替換蚁署。 -
indexOf()
:返回指定字符的索引便脊。 -
toLowerCase()
:將字符串轉(zhuǎn)成小寫字母。 -
toUpperCase()
:將字符串轉(zhuǎn)成大寫字符光戈。
String和StringBuffer哪痰、StringBuilder的區(qū)別是什么?***
1.可變性
`String`不可變久妆,`StringBuilder`和`StringBuffer`是可變的
2.線程安全性
`String`由于是不可變的晌杰,所以線程安全。`StringBuffer`對(duì)方法加了同步鎖或者對(duì)調(diào)用的方法加了同步鎖筷弦,所以是線程安全的肋演。 `StringBuilder`并沒有對(duì)方法進(jìn)行加同步鎖,所以是非線程安全的烂琴。
3.性能
`StringBuilder` > `StringBuffer` > `String`
為了方便記憶爹殊,總結(jié)如下
是否可變 | 是否安全 | 性能 | |
---|---|---|---|
String | 不可變 | 安全 | 低 |
StringBuilder | 可變 | 不安全 | 高 |
StringBuffer | 可變 | 安全 | 較高 |
switch 是否能作用在 byte 上,是否能作用在 long 上奸绷,是否能作用在 String 上 *
`switch`可以作用于`char` `byte` `short` `int`及它們對(duì)應(yīng)的包裝類型梗夸,`switch`不可作用于`long` `double` `float` `boolean`及他們的包裝類型。在 JDK1.5之后可以作用于枚舉類型号醉,在JDK1.7之后可作用于`String`類型反症。
Java語言采用何種編碼方案辛块?有何特點(diǎn)?*
Java語言采用Unicode編碼標(biāo)準(zhǔn)惰帽,它為每個(gè)字符制訂了一個(gè)唯一的數(shù)值憨降,因此在任何的語言,平臺(tái)该酗,程序都可以放心的使用授药。
訪問修飾符 **
在Java編程語言中有四種權(quán)限訪問控制符,這四種訪問權(quán)限的控制符能夠控制類中成員的可見性呜魄。其中類有兩種`public`悔叽、`default`。而方法和變量有 4 種:`public`爵嗅、`default`娇澎、`protected`、`private`睹晒。
public : 對(duì)所有類可見趟庄。使用對(duì)象:類、接口伪很、變量戚啥、方法
protected : 對(duì)同一包內(nèi)的類和所有子類可見。使用對(duì)象:變量锉试、方法猫十。 注意:不能修飾類(外部類)。
default : 在同一包內(nèi)可見呆盖,不使用任何修飾符拖云。使用對(duì)象:類、接口应又、變量宙项、方法。
-
private : 在同一類內(nèi)可見株扛。使用對(duì)象:變量杉允、方法。 注意:不能修飾類(外部類)
修飾符 當(dāng)前類 同包內(nèi) 子類(同包) 其他包 public Y Y Y Y protected Y Y Y N default Y Y Y N private Y N N N
運(yùn)算符∠铩*
-
&&和&
&&
和&
都可以表示邏輯與叔磷,但他們是有區(qū)別的,共同點(diǎn)是他們兩邊的條件都成立的時(shí)候最終結(jié)果才是true
奖磁;不同點(diǎn)是&&
只要是第一個(gè)條件不成立為false
改基,就不會(huì)再去判斷第二個(gè)條件,最終結(jié)果直接為false
,而&
判斷的是所有的條件秕狰。 -
||和|
||
和|
都表示邏輯或稠腊,共同點(diǎn)是只要兩個(gè)判斷條件其中有一個(gè)成立最終的結(jié)果就是true
,區(qū)別是||
只要滿足第一個(gè)條件鸣哀,后面的條件就不再判斷架忌,而|
要對(duì)所有的條件進(jìn)行判斷。
關(guān)鍵字
static關(guān)鍵字∥页摹***
`static`關(guān)鍵字的主要用途**就是方便在沒有創(chuàng)建對(duì)象時(shí)調(diào)用方法和變量和優(yōu)化程序性能**
**1.static變量(靜態(tài)變量)**
用`static`修飾的變量被稱為靜態(tài)變量叹放,也被稱為類變量,可以直接通過類名來訪問它挠羔。靜態(tài)變量被所有的對(duì)象共享井仰,在內(nèi)存中只有一個(gè)副本,僅當(dāng)在類初次加載時(shí)會(huì)被初始化破加,而非靜態(tài)變量在創(chuàng)建對(duì)象的時(shí)候被初始化俱恶,并且存在多個(gè)副本,各個(gè)對(duì)象擁有的副本互不影響范舀。
**2.static方法(靜態(tài)方法)**
`static`方法不依賴于任何對(duì)象就可以進(jìn)行訪問合是,在`static`方法中不能訪問類的非靜態(tài)成員變量和非靜態(tài)成員方法,因?yàn)榉庆o態(tài)成員方法/變量都是必須依賴具體的對(duì)象才能夠被調(diào)用锭环,但是在非靜態(tài)成員方法中是可以訪問靜態(tài)成員方法/變量的聪全。
public class Main {
public static String s1 = "s1";//靜態(tài)變量
String s2 = "s2";
public void fun1(){
System.out.println(s1);
System.out.println(s2);
}
public static void fun2(){
System.out.println(s1);
System.out.println(s2);//此處報(bào)錯(cuò),靜態(tài)方法不能調(diào)用非靜態(tài)變量
}
}
**3.static代碼塊(靜態(tài)代碼塊)**
靜態(tài)代碼塊的主要用途是可以用來優(yōu)化程序的性能田藐,因?yàn)樗粫?huì)在類加載時(shí)加載一次,很多時(shí)候會(huì)將一些只需要進(jìn)行一次的初始化操作都放在`static`代碼塊中進(jìn)行吱七。如果程序中有多個(gè)`static`塊汽久,在類初次被加載的時(shí)候,會(huì)按照`static`塊的順序來執(zhí)行每個(gè)`static`塊踊餐。
public class Main {
static {
System.out.println("hello,word");
}
public static void main(String[] args) {
Main m = new Main();
}
}
**4.可以通過this訪問靜態(tài)成員變量嗎景醇?(可以)**
`this`代表當(dāng)前對(duì)象,可以訪問靜態(tài)變量吝岭,而靜態(tài)方法中是不能訪問非靜態(tài)變量,也不能使用`this`引用三痰。
**5.初始化順序**
靜態(tài)變量和靜態(tài)語句塊優(yōu)先于實(shí)例變量和普通語句塊,靜態(tài)變量和靜態(tài)語句塊的初始化順序取決于它們?cè)诖a中的順序窜管。如果存在繼承關(guān)系的話散劫,初始化順序?yàn)?*父類中的靜態(tài)變量和靜態(tài)代碼塊——子類中的靜態(tài)變量和靜態(tài)代碼塊——父類中的實(shí)例變量和普通代碼塊——父類的構(gòu)造函數(shù)——子類的實(shí)例變量和普通代碼塊——子類的構(gòu)造函數(shù)**
final 關(guān)鍵字 ***
`final`關(guān)鍵字主要用于修飾類幕帆,變量获搏,方法。
- 類:被
final
修飾的類不可以被繼承 - 方法:被
final
修飾的方法不可以被重寫 - 變量:被
final
修飾的變量是基本類型失乾,變量的數(shù)值不能改變常熙;被修飾的變量是引用類型纬乍,變量便不能在引用其他對(duì)象,但是變量所引用的對(duì)象本身是可以改變的裸卫。
public class Main {
int a = 1;
public static void main(String[] args) {
final int b = 1;
b = 2;//報(bào)錯(cuò)
final Main m = new Main();
m.a = 2;//不報(bào)錯(cuò),可以改變引用類型變量所指向的對(duì)象
}
}
final finally finalize區(qū)別》卤帷***
-
final
主要用于修飾類,變量墓贿,方法 -
finally
一般作用在try-catch
代碼塊中茧泪,在處理異常的時(shí)候,通常我們將一定要執(zhí)行的代碼方法finally
代碼塊
中募壕,表示不管是否出現(xiàn)異常调炬,該代碼塊都會(huì)執(zhí)行,一般用來存放一些關(guān)閉資源的代碼舱馅。 -
finalize
是一個(gè)屬于Object
類的一個(gè)方法缰泡,該方法一般由垃圾回收器來調(diào)用,當(dāng)我們調(diào)用System.gc()
方法的時(shí)候代嗤,由垃圾回收器調(diào)用finalize()
棘钞,回收垃圾,但Java語言規(guī)范并不保證inalize
方法會(huì)被及時(shí)地執(zhí)行干毅、而且根本不會(huì)保證它們會(huì)被執(zhí)行宜猜。
this關(guān)鍵字 **
重點(diǎn)掌握前三種即可
1.`this`關(guān)鍵字可用來引用當(dāng)前類的實(shí)例變量硝逢。主要用于形參與成員名字重名姨拥,用`this`來區(qū)分。
public Person(String name, int age) {
this.name = name;
this.age = age;
}
2.`this`關(guān)鍵字可用于調(diào)用當(dāng)前類方法渠鸽。
public class Main {
public void fun1(){
System.out.println("hello,word");
}
public void fun2(){
this.fun1();//this可省略
}
public static void main(String[] args) {
Main m = new Main();
m.fun2();
}
}
3.`this()`可以用來調(diào)用當(dāng)前類的構(gòu)造函數(shù)叫乌。(注意:`this()`一定要放在構(gòu)造函數(shù)的第一行,否則編譯不通過)
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this(name);
this.age = age;
}
}
4.`this`關(guān)鍵字可作為調(diào)用方法中的參數(shù)傳遞徽缚。
5.`this`關(guān)鍵字可作為參數(shù)在構(gòu)造函數(shù)調(diào)用中傳遞憨奸。
6.`this`關(guān)鍵字可用于從方法返回當(dāng)前類的實(shí)例。super
super關(guān)鍵字 **
1.`super`可以用來引用直接父類的實(shí)例變量凿试。和`this`類似排宰,主要用于區(qū)分父類和子類中相同的字段
2.`super`可以用來調(diào)用直接父類構(gòu)造函數(shù)。(注意:`super()`一定要放在構(gòu)造函數(shù)的第一行那婉,否則編譯不通過)
3.`super`可以用來調(diào)用直接父類方法板甘。
public class Main {
public static void main(String[] args) {
Child child = new Child("Father","Child");
child.test();
}
}
class Father{
protected String name;
public Father(String name) {
this.name = name;
}
public void Say(){
System.out.println("hello,child");
}
}
class Child extends Father{
private String name;
public Child(String name1, String name2) {
super(name1); //調(diào)用直接父類構(gòu)造函數(shù)
this.name = name2;
}
public void test(){
System.out.println(this.name);
System.out.println(super.name); //引用直接父類的實(shí)例變量
super.Say(); //調(diào)用直接父類方法
}
}
this與super的區(qū)別 **
-
相同點(diǎn):
-
super()
和this()
都必須在構(gòu)造函數(shù)的第一行進(jìn)行調(diào)用详炬,否則就是錯(cuò)誤的 -
this()
和super()
都指的是對(duì)象虾啦,所以,均不可以在static
環(huán)境中使用。
-
-
不同點(diǎn):
-
super()
主要是對(duì)父類構(gòu)造函數(shù)的調(diào)用傲醉,this()
是對(duì)重載構(gòu)造函數(shù)的調(diào)用 -
super()
主要是在繼承了父類的子類的構(gòu)造函數(shù)中使用蝇闭,是在不同類中的使用;this()
主要是在同一類的不同構(gòu)造函數(shù)中的使用
-
break ,continue ,return 的區(qū)別及作用∮脖稀**
-
break
結(jié)束當(dāng)前的循環(huán)體 -
continue
結(jié)束本次循環(huán),進(jìn)入下一次循環(huán) -
return
結(jié)束當(dāng)前方法
面向?qū)ο蠛兔嫦蜻^程的區(qū)別∩胍**
-
面向過程
優(yōu)點(diǎn):性能比面向?qū)ο蟾撸驗(yàn)轭愓{(diào)用時(shí)需要實(shí)例化吐咳,開銷比較大逻悠,比較消耗資源。
缺點(diǎn):沒有面向?qū)ο笠拙S護(hù)韭脊、易復(fù)用童谒、易擴(kuò)展
-
面向?qū)ο?/p>
優(yōu)點(diǎn):易維護(hù)、易復(fù)用沪羔、易擴(kuò)展饥伊,由于面向?qū)ο笥蟹庋b、繼承蔫饰、多態(tài)性的特性琅豆,可以設(shè)計(jì)出低耦合的系統(tǒng),使系統(tǒng)更加靈活篓吁、更加易于維護(hù)
缺點(diǎn):性能比面向過程低
面向?qū)ο笕筇匦?封裝茫因、繼承、多態(tài))≌燃簟***
-
封裝
封裝就是隱藏對(duì)象的屬性和實(shí)現(xiàn)細(xì)節(jié)冻押,僅對(duì)外公開接口,控制在程序中屬性的讀和修改的訪問級(jí)別盛嘿。
-
繼承
繼承就是子類繼承父類的特征和行為洛巢,使得子類對(duì)象(實(shí)例)具有父類的實(shí)例域和方法,或子類從父類繼承方法孩擂,使得子類具有父類相同的行為狼渊。
-
多態(tài)(重要)
多態(tài)是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力箱熬。這句話不是很好理解类垦,可以看這個(gè)解釋,在Java語言中城须,多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定蚤认,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量倒底會(huì)指向哪個(gè)類的實(shí)例對(duì)象糕伐,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法砰琢,必須在由程序運(yùn)行期間才能決定。
在Java中實(shí)現(xiàn)多態(tài)的三個(gè)必要條件:繼承、重寫陪汽、向上轉(zhuǎn)型训唱。繼承和重寫很好理解,向上轉(zhuǎn)型是指在多態(tài)中需要將子類的引用賦給父類對(duì)象挚冤。
public class Main { public static void main(String[] args) { Person person = new Student(); //向上轉(zhuǎn)型 person.run(); } } class Person { public void run() { System.out.println("Person"); } } class Student extends Person { //繼承 @Override public void run() { //重載 System.out.println("Student"); } }
運(yùn)行結(jié)果為
Student
面向?qū)ο笪宕蠡驹瓌t是什么】鲈觥**
-
單一職責(zé)原則(Single-Resposibility Principle)
一個(gè)類,最好只做一件事训挡,只有一個(gè)引起它的變化澳骤。單一職責(zé)原則可以看做是低耦合、高內(nèi)聚在面向?qū)ο笤瓌t上的引申澜薄,將職責(zé)定義為引起變化的原因为肮,以提高內(nèi)聚性來減少引起變化的原因。
-
開放封閉原則(Open-Closed principle)
軟件實(shí)體應(yīng)該是可擴(kuò)展的肤京,而不可修改的颊艳。也就是,對(duì)擴(kuò)展開放蟆沫,對(duì)修改封閉的籽暇。
-
里氏替換原則 (Liskov-Substituion Principle)
子類必須能夠替換其基類。這一思想體現(xiàn)為對(duì)繼承機(jī)制的約束規(guī)范饭庞,只有子類能夠替換基類時(shí)戒悠,才能保證系統(tǒng)在運(yùn)行期內(nèi)識(shí)別子類,這是保證繼承復(fù)用的基礎(chǔ)舟山。在父類和子類的具體行為中绸狐,必須嚴(yán)格把握繼承層次中的關(guān)系和特征,將基類替換為子類累盗,程序的行為不會(huì)發(fā)生任何變化寒矿。同時(shí),這一約束反過來則是不成立的若债,子類可以替換基類符相,但是基類不一定能替換子類。
-
依賴倒置原則(Dependecy-Inversion Principle)
依賴于抽象蠢琳。具體而言就是高層模塊不依賴于底層模塊啊终,二者都同依賴于抽象;抽象不依賴于具體傲须,具體依賴于抽象蓝牲。
-
接口隔離原則(Interface-Segregation Principle)
使用多個(gè)小的專門的接口,而不要使用一個(gè)大的總接口泰讽。
抽象類和接口的對(duì)比±堋***
在Java語言中昔期,`abstract class`和`interface`是支持抽象類定義的兩種機(jī)制。抽象類:用來捕捉子類的通用特性的佛玄。接口:抽象方法的集合硼一。
相同點(diǎn):
- 接口和抽象類都不能實(shí)例化
- 都包含抽象方法,其子類都必須覆寫這些抽象方法
不同點(diǎn):
類型 | 抽象類 | 接口 |
---|---|---|
定義 | abstract class | Interface |
實(shí)現(xiàn) | extends(需要提供抽象類中所有聲明的方法的實(shí)現(xiàn)) | implements(需要提供接口中所有聲明的方法的實(shí)現(xiàn)) |
繼承 | 抽象類可以繼承一個(gè)類和實(shí)現(xiàn)多個(gè)接口梦抢;子類只可以繼承一個(gè)抽象類 | 接口只可以繼承接口(一個(gè)或多個(gè))欠动;子類可以實(shí)現(xiàn)多個(gè)接口 |
訪問修飾符 | 抽象方法可以有public、protected和default這些修飾符 | 接口方法默認(rèn)修飾符是public惑申。你不可以使用其它修飾符 |
構(gòu)造器 | 抽象類可以有構(gòu)造器 | 接口不能有構(gòu)造器 |
字段聲明 | 抽象類的字段聲明可以是任意的 | 接口的字段默認(rèn)都是 static 和 final 的 |
在Java中定義一個(gè)不做事且沒有參數(shù)的構(gòu)造方法的作用【呶椤*
Java程序存在繼承,在執(zhí)行子類的構(gòu)造方法時(shí)圈驼,如果沒有用`super()`來調(diào)用父類特定的構(gòu)造方法人芽,則會(huì)調(diào)用父類中“沒有參數(shù)的構(gòu)造方法”。如果父類只定義了有參數(shù)的構(gòu)造函數(shù)绩脆,而子類的構(gòu)造函數(shù)沒有用`super`調(diào)用父類那個(gè)特定的構(gòu)造函數(shù)萤厅,就會(huì)出錯(cuò)。
在調(diào)用子類構(gòu)造方法之前會(huì)先調(diào)用父類沒有參數(shù)的構(gòu)造方法靴迫,其目的是 *
幫助子類做初始化工作惕味。
一個(gè)類的構(gòu)造方法的作用是什么?若一個(gè)類沒有聲明構(gòu)造方法玉锌,改程序能正確執(zhí)行嗎名挥?為什么? *
主要作用是完成對(duì)類對(duì)象的初始化工作主守≠骶螅可以執(zhí)行。因?yàn)橐粋€(gè)類即使沒有聲明構(gòu)造方法也會(huì)有默認(rèn)的不帶參數(shù)的構(gòu)造方法参淫。
構(gòu)造方法有哪些特性救湖? **
- 方法名稱和類同名
- 不用定義返回值類型
- 不可以寫
retrun
語句 - 構(gòu)造方法可以被重載
變量∠巡拧**
類變量:獨(dú)立于方法之外的變量鞋既,用
static
修飾。實(shí)例變量:獨(dú)立于方法之外的變量耍铜,不過沒有
static
修飾邑闺。局部變量:類的方法中的變量。
-
成員變量:成員變量又稱全局變量业扒,可分為類變量和實(shí)例變量检吆,有
static
修飾為類變量舒萎,沒有static
修飾為實(shí)例變量程储。類變量 實(shí)例變量 局部變量 定義位置 類中蹭沛,方法外 類中医舆,方法外 方法中 初始值 有默認(rèn)初始值 有默認(rèn)初始值 無默認(rèn)初始值 存儲(chǔ)位置 方法區(qū) 堆 棧 生命周期 類何時(shí)被加載和卸載 實(shí)例何時(shí)被創(chuàng)建及銷毀 方法何時(shí)被調(diào)用及結(jié)束調(diào)用
內(nèi)部類√羲妗**
內(nèi)部類包括這四種:成員內(nèi)部類莺葫、局部內(nèi)部類徒坡、匿名內(nèi)部類和靜態(tài)內(nèi)部類
-
成員內(nèi)部類
1.成員內(nèi)部類定義為位于另一個(gè)類的內(nèi)部箫柳,成員內(nèi)部類可以無條件訪問外部類的所有成員屬性和成員方法(包括
private
成員和靜態(tài)成員)涧团。class Outer{ private double a = 0; public static int b =1; public Outer(double a) { this.a = a; } class Inner { //內(nèi)部類 public void fun() { System.out.println(a); System.out.println(b); } } }
2.當(dāng)成員內(nèi)部類擁有和外部類同名的成員變量或者方法時(shí)质涛,即默認(rèn)情況下訪問的是成員內(nèi)部類的成員阐斜。如果要訪問外部類的同名成員皱蹦,需要以下面的形式進(jìn)行訪問:外部類.
this
.成員變量3.在外部類中如果要訪問成員內(nèi)部類的成員煤杀,必須先創(chuàng)建一個(gè)成員內(nèi)部類的對(duì)象,再通過指向這個(gè)對(duì)象的引用來訪問沪哺。
4.成員內(nèi)部類是依附外部類而存在的沈自,如果要?jiǎng)?chuàng)建成員內(nèi)部類的對(duì)象,前提是必須存在一個(gè)外部類的對(duì)象辜妓。創(chuàng)建成員內(nèi)部類對(duì)象的一般方式如下:
class Outter{ private double a = 0; public static int b =1; public Outter(){} public Outter(double a) { this.a = a; Inner inner = new Inner(); inner.fun(); //調(diào)用內(nèi)部類的方法 } class Inner { //內(nèi)部類 int b = 2; public void fun() { System.out.println(a); System.out.println(b); //訪問內(nèi)部類的b System.out.println(Outter.this.b);//訪問外部類的b } } } public class Main{ public static void main(String[] args) { Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //創(chuàng)建內(nèi)部類的對(duì)象 } }
-
局部內(nèi)部類
局部內(nèi)部類是定義在一個(gè)方法或者一個(gè)作用域里面的類枯途,它和成員內(nèi)部類的區(qū)別在于局部內(nèi)部類的訪問僅限于方法內(nèi)或者該作用域內(nèi)。定義在實(shí)例方法中的局部類可以訪問外部類的所有變量和方法籍滴,定義在靜態(tài)方法中的局部類只能訪問外部類的靜態(tài)變量和方法酪夷。
class Outter { private int outter_a = 1; private static int static_b = 2; public void test1(){ int inner_c =3; class Inner { private void fun(){ System.out.println(outter_a); System.out.println(static_b); System.out.println(inner_c); } } Inner inner = new Inner(); //創(chuàng)建局部內(nèi)部類 inner.fun(); } public static void test2(){ int inner_d =3; class Inner { private void fun(){ System.out.println(outter_a); //編譯錯(cuò)誤,定義在靜態(tài)方法中的局部類不可以訪問外部類的實(shí)例變量 System.out.println(static_b); System.out.println(inner_d); } } Inner inner = new Inner(); inner.fun(); } }
-
匿名內(nèi)部類
匿名內(nèi)部類只沒有名字的內(nèi)部類孽惰,在日常開發(fā)中使用較多晚岭。使用匿名內(nèi)部類的前提條件:必須繼承一個(gè)父類或?qū)崿F(xiàn)一個(gè)接口。
interface Person { public void fun(); } class Demo { public static void main(String[] args) { new Person() { public void fun() { System.out.println("hello,word"); } }.fun(); } }
-
靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類也是定義在另一個(gè)類里面的類勋功,只不過在類的前面多了一個(gè)關(guān)鍵字
static
腥例。靜態(tài)內(nèi)部類是不需要依賴于外部類的,并且它不能使用外部類的非static
成員變量或者方法酝润,這點(diǎn)很好理解燎竖,因?yàn)樵跊]有外部類的對(duì)象的情況下,可以創(chuàng)建靜態(tài)內(nèi)部類的對(duì)象要销,如果允許訪問外部類的非static
成員就會(huì)產(chǎn)生矛盾构回,因?yàn)橥獠款惖姆莝tatic成員必須依附于具體的對(duì)象。class Outter { int a = 1; static int b = 2; public Outter() { } static class Inner { public Inner() { System.out.println(a);//報(bào)錯(cuò)疏咐,靜態(tài)內(nèi)部類不能訪問非靜態(tài)變量 System.out.println(b); } } } public class Main{ public static void main(String[] args) { Outter.Inner inner = new Outter.Inner(); } }
-
內(nèi)部類的優(yōu)點(diǎn):
- 內(nèi)部類不為同一包的其他類所見纤掸,具有很好的封裝性;
- 匿名內(nèi)部類可以很方便的定義回調(diào)浑塞。
- 每個(gè)內(nèi)部類都能獨(dú)立的繼承一個(gè)接口的實(shí)現(xiàn)借跪,所以無論外部類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類都沒有影響酌壕。
- 內(nèi)部類有效實(shí)現(xiàn)了“多重繼承”掏愁,優(yōu)化 java 單繼承的缺陷歇由。
-
局部內(nèi)部類和匿名內(nèi)部類訪問局部變量的時(shí)候,為什么變量必須要加上
final
果港?public class Main { public static void main(String[] args) { } public void fun(final int b) { final int a = 10; new Thread(){ public void run() { System.out.println(a); System.out.println(b); }; }.start(); } }
對(duì)于變量
a
可以從生命周期的角度理解沦泌,局部變量直接存儲(chǔ)在棧中,當(dāng)方法執(zhí)行結(jié)束后辛掠,非final
的局部變量就被銷毀谢谦,而局部內(nèi)部類對(duì)局部變量的引用依然存在,如果局部內(nèi)部類要調(diào)用沒有final
修飾的局部變量時(shí)萝衩,就會(huì)造成生命周期不一致出錯(cuò)回挽。對(duì)于變量
b
,其實(shí)是將fun
方法中的變量b
以參數(shù)的形式對(duì)匿名內(nèi)部類中的拷貝(變量b
的拷貝)進(jìn)行賦值初始化猩谊。在run
方法中訪問的變量b
根本就不是test
方法中的局部變量b
厅各,而是一個(gè)拷貝值,所以不存在生命周期不一致的問題预柒,但如果在run
方法中修改變量b
的值會(huì)導(dǎo)致數(shù)據(jù)不一致队塘,所以需要加final
修飾。
重寫與重載∫搜臁***
重載和重寫的區(qū)別
- 重載:發(fā)生在同一個(gè)類中憔古,方法名相同參數(shù)列表不同(參數(shù)類型不同、個(gè)數(shù)不同淋袖、順序不同)鸿市,與方法返回值和訪問修飾符無關(guān),即重載的方法不能根據(jù)返回類型進(jìn)行區(qū)分即碗。
- 重寫:發(fā)生在父子類中焰情,方法名、參數(shù)列表必須相同剥懒,返回值小于等于父類内舟,拋出的異常小于等于父類,訪問修飾符大于等于父類(里氏代換原則)初橘;如果父類方法訪問修飾符為
private
則子類中就不是重寫验游。
構(gòu)造器(constructor)是否可被重寫(override)
構(gòu)造器可以被重載,不能被重寫
重載的方法能否根據(jù)返回類型進(jìn)行區(qū)分保檐?為什么耕蝉?
不能,因?yàn)檎{(diào)用時(shí)不能指定類型信息夜只,編譯器不知道你要調(diào)用哪個(gè)函數(shù)垒在。
== 和 equals 的區(qū)別 ***
-
==
對(duì)于基本數(shù)據(jù)類型扔亥,
==
比較的是值场躯;對(duì)于引用數(shù)據(jù)類型谈为,==
比較的是內(nèi)存地址。 -
eauals
對(duì)于沒有重寫
equals
方法的類推盛,equals
方法和==
作用類似;對(duì)于重寫過equals
方法的類谦铃,equals
比較的是值耘成。
hashCode 與 equals(為什么重寫equals方法后,hashCode方法也必須重寫)【匀颉***
-
equals
先看下
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; }
從源碼中可以看到:
-
equals
方法首先比較的是內(nèi)存地址,如果內(nèi)存地址相同嘹朗,直接返回true
师妙;如果內(nèi)存地址不同,再比較對(duì)象的類型屹培,類型不同直接返回false
默穴;類型相同,再比較值是否相同褪秀;值相同返回true
蓄诽,值不同返回false
∶铰穑總結(jié)一下仑氛,equals
會(huì)比較內(nèi)存地址、對(duì)象類型闸英、以及值锯岖,內(nèi)存地址相同,equals
一定返回true
甫何;對(duì)象類型和值相同出吹,equals
方法一定返回true
。 - 如果沒有重寫
equals
方法辙喂,那么equals
和==
的作用相同趋箩,比較的是對(duì)象的地址值。
-
-
hashCode
hashCode
方法返回對(duì)象的散列碼加派,返回值是int
類型的散列碼叫确。散列碼的作用是確定該對(duì)象在哈希表中的索引位置。關(guān)于
hashCode
有一些約定:- 兩個(gè)對(duì)象相等芍锦,則
hashCode
一定相同竹勉。 - 兩個(gè)對(duì)象有相同的
hashCode
值,它們不一定相等娄琉。 -
hashCode()
方法默認(rèn)是對(duì)堆上的對(duì)象產(chǎn)生獨(dú)特值次乓,如果沒有重寫hashCode()
方法吓歇,則該類的兩個(gè)對(duì)象的hashCode
值肯定不同
- 兩個(gè)對(duì)象相等芍锦,則
-
為什么重寫
equals
方法后,hashCode
方法也必須重寫- 根據(jù)規(guī)定票腰,兩個(gè)對(duì)象相等城看,
hashCode
值也許相同,所以重寫equals
方法后杏慰,hashCode
方法也必須重寫(面試官肯定不是想聽這個(gè)答案) -
hashCode
在具有哈希機(jī)制的集合中起著非常關(guān)鍵的作用测柠,比如HashMap
、HashSet
等缘滥。以HashSet
為例轰胁,HashSet
的特點(diǎn)是存儲(chǔ)元素時(shí)無序且唯一,在向HashSet
中添加對(duì)象時(shí)朝扼,首相會(huì)計(jì)算對(duì)象的HashCode
值來確定對(duì)象的存儲(chǔ)位置赃阀,如果該位置沒有其他對(duì)象,直接將該對(duì)象添加到該位置擎颖;如果該存儲(chǔ)位置有存儲(chǔ)其他對(duì)象(新添加的對(duì)象和該存儲(chǔ)位置的對(duì)象的HashCode
值相同)榛斯,調(diào)用equals
方法判斷兩個(gè)對(duì)象是否相同,如果相同搂捧,則添加對(duì)象失敗肖抱,如果不相同,則會(huì)將該對(duì)象重新散列到其他位置异旧。所以重寫equals
方法后意述,hashCode
方法不重寫的話,會(huì)導(dǎo)致所有對(duì)象的HashCode
值都不相同吮蛹,都能添加成功荤崇,那么HashSet
中會(huì)出現(xiàn)很多重復(fù)元素,HashMap
也是同理(因?yàn)?code>HashSet的底層就是通過HashMap
實(shí)現(xiàn)的)潮针,會(huì)出現(xiàn)大量相同的Key
(HashMap
中的key
是唯一的术荤,但不同的key
可以對(duì)應(yīng)相同的value
)。所以重寫equals
方法后每篷,hashCode
方法也必須重寫瓣戚。同時(shí)因?yàn)閮蓚€(gè)對(duì)象的hashCode
值不同,則它們一定不相等焦读,所以先計(jì)算對(duì)象的hashCode
值可以在一定程度上判斷兩個(gè)對(duì)象是否相等子库,提高了集合的效率〈;危總結(jié)一下仑嗅,一共兩點(diǎn):第一,在HashSet
等集合中,不重寫hashCode
方法會(huì)導(dǎo)致其功能出現(xiàn)問題仓技;第二鸵贬,可以提高集合效率。
- 根據(jù)規(guī)定票腰,兩個(gè)對(duì)象相等城看,
Java 中是值傳遞還是引用傳遞脖捻,還是兩者共存±啤**
這是一個(gè)很容易搞混又很難解釋清楚的問題,先說結(jié)論地沮,Java中只有值傳遞
先看這樣一段代碼
public class Main{
public static void main(String[] args) {
int a = 1;
printValue(a);
System.out.println("a:" + a);
}
public static void printValue(int b){
b = 2;
System.out.println("b:"+ b);
}
}
輸出
b:2
a:1
可以看到將a
的值傳到printValue
方法中嗜浮,并將其值改為2。但方法調(diào)用結(jié)束后诉濒,a
的值還是1周伦,并未發(fā)生改變夕春,所以這種情況下為值傳遞未荒。
再看這段代碼
public class Main{
public static void main(String[] args) {
Preson p = new Preson();
p.name = "zhangsan";
printValue(p);
System.out.println("p.name: " + p.name);
}
public static void printValue(Preson q){
q.name = "lisi";
System.out.println("q.name: "+ q.name);
}
}
class Preson{
public String name;
}
輸出結(jié)果
q.name: lisi
p.name: lisi
在將p
傳入printValue
方法后,方法調(diào)用結(jié)束及志,p
的name
屬性竟然被改變了片排!所以得出結(jié)論,參數(shù)為基本類型為值傳遞速侈,參數(shù)為引用類型為時(shí)為引用傳遞率寡。這個(gè)結(jié)論是錯(cuò)誤的,下面來看看判斷是值傳遞還是值傳遞的關(guān)鍵是什么倚搬,先看定義
- 值傳遞:是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份傳遞到函數(shù)中冶共,這樣在函數(shù)中如果對(duì)參數(shù)進(jìn)行修改,將不會(huì)影響到實(shí)際參數(shù)每界。
- 引用傳遞:是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)的地址直接傳遞到函數(shù)中捅僵,那么在函數(shù)中對(duì)參數(shù)所進(jìn)行的修改,將影響到實(shí)際參數(shù)眨层。
從定義中可以明顯看出庙楚,區(qū)分是值傳遞還是引用傳遞主要是看向方法中傳遞的是實(shí)際參數(shù)的副本還是實(shí)際參數(shù)的地址。上面第一個(gè)例子很明顯是值傳遞趴樱,其實(shí)第二個(gè)例子中向printValue
方法中傳遞的是一個(gè)引用的副本馒闷,只是這個(gè)副本引用和原始的引用指向的同一個(gè)對(duì)象,所以副本引用修改過對(duì)象屬性后叁征,通過原始引用查看對(duì)象屬性肯定也是被修改過的纳账。換句話說,printValue
方法中修改的是副本引用指向的對(duì)象的屬性捺疼,不是引用本身塞祈,如果修改的是引用本身,那么原始引用肯定不受影響∫樾剑看下面這個(gè)例子
public class Main{
public static void main(String[] args) {
Preson p = new Preson();
p.name = "zhangsan";
printValue(p);
System.out.println("p.name: " + p.name);
}
public static void printValue(Preson q){
q = new Preson();
q.name = "lisi";
System.out.println("q.name: "+ q.name);
}
}
class Preson{
public String name;
}
輸出結(jié)果
q.name: lisi
p.name: zhangsan
可以看到將p
傳入printValue
方法后尤蛮,printValue
方法調(diào)用結(jié)束后,p
的屬性name
沒有改變斯议,這是因?yàn)?strong>在printValue
方法中并沒有改變副本引用q
所指向的對(duì)象产捞,而是改變了副本引用q
本身,將副本引用q
指向了另一個(gè)對(duì)象并對(duì)這個(gè)對(duì)象的屬性進(jìn)行修改哼御,所以原始引用p
所指向的對(duì)象不受影響坯临。所以證明Java中只存在值傳遞。
IO流×抵纭*
Java IO流主要可以分為輸入流和輸出流看靠。按照照操作單元?jiǎng)澐郑梢詣澐譃樽止?jié)流和字符流液肌。按照流的角色劃分為節(jié)點(diǎn)流和處理流挟炬。
Java I0流的40多個(gè)類都是從4個(gè)抽象類基類中派生出來的。
- InputStream:字節(jié)輸入流
- Reader:字符輸入流
- OutputStream:字節(jié)輸出流
- Writer:字符輸出流
BIO,NIO,AIO 有什么區(qū)別?∴露摺**
BIO (Blocking I/O):服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程谤祖,即客戶端有連接請(qǐng)求時(shí)服務(wù)器就需要啟動(dòng)一個(gè)線程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì)造成不必要的線程開銷老速,可以通過線程池機(jī)制來改善粥喜。BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對(duì)服務(wù)端資源要求比較高橘券,并發(fā)局限于應(yīng)用中额湘,在jdk1.4以前是唯一的io
NIO (New I/O):服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)線程,即客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上旁舰,多路復(fù)用器輪詢到連接有IO請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理锋华。NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器鬓梅,并發(fā)局限于應(yīng)用中供置,編程比較復(fù)雜,jdk1,4開始支持
-
AIO (Asynchronous I/O):服務(wù)器實(shí)現(xiàn)模式為一個(gè)有效請(qǐng)求一個(gè)線程绽快,客戶端的IO請(qǐng)求都是由操作系統(tǒng)先完成了再通知服務(wù)器用其啟動(dòng)線程進(jìn)行處理芥丧。AIO方式適用于連接數(shù)目多且連接比較長(重操作)的架構(gòu),比如相冊(cè)服務(wù)器坊罢,充分調(diào)用OS參與并發(fā)操作续担,編程比較復(fù)雜,jdk1.7開始支持活孩。
這些概念看著比較枯燥物遇,可以從這個(gè)經(jīng)典的燒開水的例子去理解
**BIO **:來到廚房,開始燒水NIO,并坐在水壺面前一直等著水燒開询兴。
NIO:來到廚房乃沙,開AIO始燒水,但是我們不一直坐在水壺前面等诗舰,而是做些其他事警儒,然后每隔幾分鐘到廚房看一下水有沒有燒開。
AIO:來到廚房眶根,開始燒水蜀铲,我們不一直坐在水壺前面等,而是在水壺上面裝個(gè)開關(guān)属百,水燒開之后它會(huì)通知我记劝。
反射 ***
JAVA反射機(jī)制是在運(yùn)行狀態(tài)中族扰,對(duì)于任意一個(gè)類厌丑,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象别伏,都能夠調(diào)用它的任意一個(gè)方法和屬性蹄衷;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語言的反射機(jī)制忧额。
Java獲取Class對(duì)象的三種方式
class Person {
public String name = "zhangsan";
public Person() {
}
}
public class Main{
public static void main(String[] args) throws ClassNotFoundException {
//方式1
Person p1 = new Person();
Class c1 = p1.getClass();
//方式2
Class c2 = Person.class;
//方式3可能會(huì)拋出ClassNotFoundException異常
Class c3 = Class.forName("com.company");
}
}
因?yàn)樵谝粋€(gè)類在 JVM 中只會(huì)有一個(gè) Class
實(shí)例厘肮,所以對(duì)c1
、c2
睦番、c3
進(jìn)行equals
比較時(shí)返回的都是true
类茂。
反射優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):運(yùn)行期類型的判斷,動(dòng)態(tài)加載類托嚣,提高代碼靈活度巩检。
- 缺點(diǎn):性能比直接的java代碼要慢很多。
反射應(yīng)用場(chǎng)景:
- Java的很多框架都用到了反射示启,例如
Spring
中的xml的配置模式等 - 動(dòng)態(tài)代理設(shè)計(jì)模式也采用了反射機(jī)制
JAVA異尘た蓿 ***
異常主要分為Error
和Exception
兩種
- Error:
Error
類以及他的子類的實(shí)例夫嗓,代表了JVM本身的錯(cuò)誤迟螺。錯(cuò)誤不能被程序員通過代碼處理。 - EXception:
Exception
以及他的子類舍咖,代表程序運(yùn)行時(shí)發(fā)送的各種不期望發(fā)生的事件矩父。可以被Java異常處理機(jī)制使用排霉,是異常處理的核心窍株。
異常框圖
除了以上的分類,異常還能分為非檢查異常和檢查異常
-
非檢查異常(unckecked exception):該類異常包括運(yùn)行時(shí)異常(RuntimeException極其子類)和錯(cuò)誤(Error)球订。編譯器不會(huì)進(jìn)行檢查并且不要求必須處理的異常后裸,也就說當(dāng)程序中出現(xiàn)此類異常時(shí),即使我們沒有
try-catch
捕獲它冒滩,也沒有使用throws
拋出該異常轻抱,編譯也會(huì)正常通過。因?yàn)檫@樣的異常發(fā)生的原因很可能是代碼寫的有問題旦部。 -
檢查異常(checked exception):除了
Error
和RuntimeException
的其它異常祈搜。這是編譯器要求必須處理的異常。這樣的異常一般是由程序的運(yùn)行環(huán)境導(dǎo)致的士八。因?yàn)槌绦蚩赡鼙贿\(yùn)行在各種未知的環(huán)境下容燕,而程序員無法干預(yù)用戶如何使用他編寫的程序,所以必須處理這些異常婚度。
下面來看下
try{}catch(){}finally{}
和return
之間的“恩恩怨怨”蘸秘,這里有些亂,面試時(shí)問的也不是很多蝗茁,實(shí)在記不住就算啦醋虏。還是先看代碼猜結(jié)果。
public class Main{
public static void main(String[] args) {
int a = test1();
System.out.println(a);
int b = test2();
System.out.println(b);
int c = test3();
System.out.println(c);
int d = test4();
System.out.println(d);
int e = test5();
System.out.println(e);
}
public static int test1(){
int a = 1;
try{
a = 2;
return a;
}catch(Exception e){
System.out.println("hello,test1");
a = 3;
}finally{
a = 4;
}
return a;
}
//輸出 2
public static int test2(){
int a = 1;
try{
a = 2;
return a;
}catch(Exception e){
System.out.println("hello,test2");
a = 3;
return a;
}finally{
a = 4;
}
}
//輸出 2
public static int test3(){
int a = 1;
try{
a = 2/0;
return a;
}catch(Exception e){
System.out.println("hello,test3");
a = 3;
}finally{
a = 4;
}
return a;
}
//輸出 hello,test3
// 4
public static int test4(){
int a = 1;
try{
a = 2/0;
return a;
}catch(Exception e){
System.out.println("hello,test4");
a = 3;
return a;
}finally{
a = 4;
}
}
//輸出 hello,test4
// 3
public static int test5(){
int a = 1;
try{
a = 2/0;
return a;
}catch(Exception e){
a = 3;
return a;
}finally{
a = 4;
return a;
}
}
//輸出 4
}
如果沒有仔細(xì)的研究過哮翘,應(yīng)該好多會(huì)猜錯(cuò)颈嚼,下面總結(jié)下規(guī)律。
- 從前三個(gè)例子可以看出如果
try{}
中的代碼沒有異常饭寺,catch(){}
代碼塊中的代碼不會(huì)執(zhí)行阻课。所以如果try{}
和catch(){}
都含有return
時(shí),無異常執(zhí)行try{}
中的return
艰匙,存在異常執(zhí)行catch(){}
的return
限煞。 - 不管任何情況,就算
try{}
或catch(){}
中含有return
员凝,finally{}
中的代碼一定會(huì)執(zhí)行署驻,那么為什么test1
、test2
健霹、test3
中的結(jié)果不是4呢旺上,因?yàn)殡m然finally
是在return
后面的表達(dá)式運(yùn)算之后執(zhí)行的,但此時(shí)并沒有返回運(yùn)算之后的值骤公,而是把值保存起來抚官,不管finally
對(duì)該值做任何的改變,返回的值都不會(huì)改變阶捆,依然返回保存起來的值凌节。也就是說方法的返回值是在finally
運(yùn)算之前就確定了的钦听。 - 如果
return
的數(shù)據(jù)是引用數(shù)據(jù)類型,而在finally
中對(duì)該引用數(shù)據(jù)類型的屬性值的改變起作用倍奢,try
中的return
語句返回的就是在finally
中改變后的該屬性的值朴上。這個(gè)不理解可以看看上面提到的Java的值傳遞的問題。 - 如果
finally{}
中含有return
卒煞,會(huì)導(dǎo)致程序提前退出痪宰,不在執(zhí)行try{}
或catch(){}
中的return
。所以test5
返回的值是4畔裕。
最后總計(jì)一下try{}catch(){}finally{}
的執(zhí)行順序衣撬。
- 先執(zhí)行
try
中的語句扮饶,包括return
后面的表達(dá)式具练; - 有異常時(shí),執(zhí)行
catch
中的語句,包括return
后面的表達(dá)式甜无,無異常跳過catch
語句扛点; - 然后執(zhí)行
finally
中的語句,如果finally
里面有return
語句岂丘,執(zhí)行return
語句陵究,程序結(jié)束; -
finally{}
中沒有return
時(shí)奥帘,無異常執(zhí)行try
中的return
铜邮,如果有異常時(shí)則執(zhí)行catch
中的return
。前兩步執(zhí)行的return
只是確定返回的值翩概,程序并未結(jié)束牲距,finally{}
執(zhí)行之后返咱,最后將前兩步確定的return
的返回值返回钥庇。
JAVA注解 **
面試問的不多咖摹,但是在使用框架開發(fā)時(shí)會(huì)經(jīng)常使用评姨,但東西太多了,這里只是簡單介紹下概念萤晴。
Annotation
注解可以看成是java中的一種標(biāo)記記號(hào)吐句,用來給java中的類,成員店读,方法嗦枢,參數(shù)等任何程序元素添加一些額外的說明信息,同時(shí)不改變程序語義屯断。注解可以分為三類:基本注解文虏,元注解侣诺,自定義注解
-
標(biāo)準(zhǔn)注解
- @Deprecated:該注解用來說明程序中的某個(gè)元素(類,方法氧秘,成員變量等)已經(jīng)不再使用年鸳,如果使用的話的編譯器會(huì)給出警告。
- @SuppressWarnings(value=“”):用來抑制各種可能出現(xiàn)的警告丸相。
- @Override:用來說明子類方法覆蓋了父類的方法搔确,保護(hù)覆蓋方法的正確使用
-
元注解(元注解也稱為元數(shù)據(jù)注解,是對(duì)注解進(jìn)行標(biāo)注的注解灭忠,元注解更像是一種對(duì)注解的規(guī)范說明膳算,用來對(duì)定義的注解進(jìn)行行為的限定。例如說明注解的生存周期弛作,注解的作用范圍等)
- @Target(value=“ ”):該注解是用來限制注解的使用范圍的畦幢,即該注解可以用于哪些程序元素。
- @Retention(value=“ ”):用于說明注解的生存周期
- @Documnent:用來說明指定被修飾的注解可以被javadoc.exe工具提取進(jìn)入文檔中缆蝉,所有使用了該注解進(jìn)行標(biāo)注的類在生成API文檔時(shí)都在包含該注解的說明宇葱。
- @Inherited:用來說明使用了該注解的父類,其子類會(huì)自動(dòng)繼承該注解刊头。
- @Repeatable:java1.8新出的元注解黍瞧,如果需要在給程序元素使用相同類型的注解上忍,則需將該注解標(biāo)注上拜轨。
自定義注解:用@Interface來聲明注解。
JAVA泛型《┩帷***
Java 泛型是 JDK 5 中引入的一個(gè)新特性, 泛型提供了編譯時(shí)類型安全檢測(cè)機(jī)制穿肄,該機(jī)制允許程序員在編譯時(shí)檢測(cè)到非法的類型年局。泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)咸产。
-
泛型擦除(這是面試考察泛型時(shí)經(jīng)常問到的問題)
Java的泛型基本上都是在編譯器這個(gè)層次上實(shí)現(xiàn)的矢否,在生成的字節(jié)碼中是不包含泛型中的類型信息的,使用泛型的時(shí)候加上類型參數(shù)脑溢,在編譯器編譯的時(shí)候會(huì)去掉僵朗,這個(gè)過程成為類型擦除⌒汲梗看下面代碼
public class Main{ public static void main(String[] args) { ArrayList<Integer> arrayList1 = new ArrayList<>(); ArrayList<String> arrayList2 = new ArrayList<>(); System.out.println(arrayList1.getClass() == arrayList2.getClass()); } }
輸出結(jié)果
true
可以看到
ArrayList<Integer>
和ArrayList<String>
的原始類型是相同验庙,在編譯成字節(jié)碼文件后都會(huì)變成List
,JVM看到的只有List
社牲,看不到泛型信息粪薛,這就是泛型的類型擦除。在看下面這段代碼public class Main{ public static void main(String[] args) throws Exception { ArrayList<Integer> arrayList = new ArrayList<>(); arrayList.add(1); arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "a"); System.out.println(arrayList.get(0)); System.out.println(arrayList.get(1)); } }
輸出
1 a
可以看到通過反射進(jìn)行
add
操作搏恤,ArrayList<Integer>
竟然可以存儲(chǔ)字符串违寿,這是因?yàn)樵诜瓷渚褪窃谶\(yùn)行期調(diào)用的add
方法让禀,在運(yùn)行期泛型信息已經(jīng)被擦除。 -
既然存在類型擦除陨界,那么Java是如何保證在
ArrayList<Integer>
添加字符串會(huì)報(bào)錯(cuò)呢巡揍?Java編譯器是通過先檢查代碼中泛型的類型,然后在進(jìn)行類型擦除菌瘪,再進(jìn)行編譯腮敌。
JAVA序列化 **
序列化:將對(duì)象寫入到IO流中
反序列化:從IO流中恢復(fù)對(duì)象
序列化的意義:將Java對(duì)象轉(zhuǎn)換成字節(jié)序列俏扩,這些字節(jié)序列更加便于通過網(wǎng)絡(luò)傳輸或存儲(chǔ)在磁盤上糜工,在需要時(shí)可以通過反序列化恢復(fù)成原來的對(duì)象。
-
實(shí)現(xiàn)方式:
- 實(shí)現(xiàn)Serializable接口
- 實(shí)現(xiàn)Externalizable接口
-
序列化的注意事項(xiàng):
- 對(duì)象的類名录淡、實(shí)例變量會(huì)被序列化捌木;方法、類變量嫉戚、
transient
實(shí)例變量都不會(huì)被序列化刨裆。 - 某個(gè)變量不被序列化,可以使用
transient
修飾彬檀。 - 序列化對(duì)象的引用類型成員變量帆啃,也必須是可序列化的,否則窍帝,會(huì)報(bào)錯(cuò)努潘。
- 反序列化時(shí)必須有序列化對(duì)象的
class
文件。
- 對(duì)象的類名录淡、實(shí)例變量會(huì)被序列化捌木;方法、類變量嫉戚、
深拷貝與淺拷貝±ぱА***
深拷貝:對(duì)基本數(shù)據(jù)類型進(jìn)行值傳遞疯坤,對(duì)引用數(shù)據(jù)類型,創(chuàng)建一個(gè)新的對(duì)象深浮,并復(fù)制其內(nèi)容压怠,兩個(gè)引用指向兩個(gè)對(duì)象,但對(duì)象內(nèi)容相同略号。
-
淺拷貝:對(duì)基本數(shù)據(jù)類型進(jìn)行值傳遞刑峡,對(duì)引用數(shù)據(jù)類型復(fù)制一個(gè)引用指向原始引用的對(duì)象,就是復(fù)制的引用和原始引用指向同一個(gè)對(duì)象玄柠。
具體區(qū)別看下圖
-
深拷貝的實(shí)現(xiàn)方式
-
重載
clone
方法public class Main{ public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, CloneNotSupportedException { Address s = new Address("天津"); Person p = new Person("張三", 23, s); System.out.println("克隆前的地址:" + p.getAddress().getName()); Person cloneP = (Person) p.clone(); cloneP.getAddress().setName("北京"); System.out.println("克隆后的地址:" + cloneP.getAddress().getName()); } } class Address implements Cloneable { private String city; public Address(String name){ this.city = name; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public String getName() { return city; } public void setName(String name) { this.city = name; } } class Person implements Cloneable{ private String name; private int age; private Address address; public Person(String name, int age, Address address){ this.name = name; this.age = age; this.address = address; } @Override public Object clone() throws CloneNotSupportedException { Person person = (Person) super.clone(); person.address = (Address)address.clone(); return person; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
輸出
克隆前的地址:天津 克隆后的地址:北京
其實(shí)就是
Person
類和Address
類都要重寫clone
方法,這里面需要注意的一點(diǎn)是super.clone()
為淺克隆诫舅,所以在在Person
類中重寫clone
方法時(shí)羽利,address
對(duì)象需要調(diào)用address.clone()
重新賦值,因?yàn)?code>address類型為引用類型刊懈。 -
序列化
public class Main{ public static void main(String[] args) throws IOException, ClassNotFoundException { Address s = new Address("天津"); Person p = new Person("張三", 23, s); System.out.println("克隆前的地址:" + p.getAddress().getName()); Person cloneP = (Person) p.deepClone(); cloneP.getAddress().setName("北京"); System.out.println("克隆后的地址:" + cloneP.getAddress().getName()); } } class Address implements Serializable{ private String city; public Address(String name){ this.city = name; } public String getName() { return city; } public void setName(String name) { this.city = name; } } class Person implements Serializable{ private String name; private int age; private Address address; public Person(String name, int age, Address address){ this.name = name; this.age = age; this.address = address; } public Object deepClone() throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
輸出
克隆前的地址:天津 克隆后的地址:北京
-
常見的Object方法≌饣 ***
這些方法都很重要娃闲,面試經(jīng)常會(huì)問到,要結(jié)合其他知識(shí)將這些方法理解透徹
-
Object clone()
:創(chuàng)建與該對(duì)象的類相同的新對(duì)象 -
boolean equals(Object)
:比較兩對(duì)象是否相等 -
void finalize()
:當(dāng)垃圾回收器確定不存在對(duì)該對(duì)象的更多引用時(shí)匾浪,對(duì)象垃圾回收器調(diào)用該方法 -
Class getClass()
:返回一個(gè)對(duì)象運(yùn)行時(shí)的實(shí)例類 -
int hashCode()
:返回該對(duì)象的散列碼值 -
void notify()
:喚醒等待在該對(duì)象的監(jiān)視器上的一個(gè)線程 -
void notifyAll()
:喚醒等待在該對(duì)象的監(jiān)視器上的全部線程 -
String toString()
:返回該對(duì)象的字符串表示 -
void wait()
:在其他線程調(diào)用此對(duì)象的notify()
方法或notifyAll()
方法前皇帮,導(dǎo)致當(dāng)前線程等待