注:有包名的類不能調用無包名的類。
基礎部分,數據類型方面:
1:在定義Long或者Float類型變量的時候,要加L或者f。
整數默認是int類型,浮點數默認是double。
byte,short在定義的時候,他們接收的其實是一個int類型的值。
這個是自己做了一個數據檢測的,如果不再它們的範圍內,就報錯。
2:byte值的問題
byte b1 = 127;
byte b5=128; //編譯不通過
byte b2 = (byte)128; //-128
byte b3 = (byte)129; //-127
byte b4 = (byte)130; //-126
byte的範圍:-128 ~ 127
也就是說byte表示的類型數是從-128到127之間的,如果超過了範圍,比如定義一個byte類型的數位128,那麼那麼它就會從頭開始表示,即-128,byte b3 = (byte)129 b3就是-127了。
128:10000000
-128:10000000 (這裏的1即是符號位,也是數值位)
3:數據類型轉換之默認轉換
byte,short,char -- >int -- >long -- >float -- > double
long: 8個字節
float:4個字節
那麼問題來了,8字節的long類型轉爲4字節的float類型,怎麼存儲呢?
A:它們底層的存儲結構不同。(所有的整數都是按照101010...這樣存儲的,而所有的浮點數存時用一種科學計數法存儲,它存時存有效數字位以及字冪,有效數字位也是按二進制存儲的)
B:float表示的數據範圍比long的範圍要大
long:2^63-1
float:3.4*10^38 > 2*10^38 > 2*8^38 = 2*2^3^38 = 2*2^114 > 2^63-1
4:Java語言中的字符char可以存儲一箇中文漢字嗎?爲什麼呢?
可以。因爲java語言中的字符佔用兩個字節。
Java語言採用的是Unicode編碼。
5.面試題:
short s=1;s = s+1;
short s=1;s+=1;
上面兩個代碼有沒有問題,如果有,那裏有問題。
答:第一個有問題,第二個沒有,那麼爲什麼第二個木有問題呢?
擴展的賦值運算符其實隱含了一個強制類型轉換。
s += 1;
不是等價於 s = s + 1;
而是等價於 s = (s的數據類型)(s + 1);
6.交換a和b值面試題:
方法一(位異或法):
int a=10; int b=20; 用位異或實現a和b的交換
a = a ^ b;
b = a ^ b;
a = a ^ b;
這樣a和b就完成交換了,即可以簡單記憶爲:左邊a,b,a;右邊a^b。
方法二(變量相加法):
a = a+b;
b = a - b;
a = a - b;
這樣a和b就完成交換了。
方法三(一句話搞定):
b = (a+b) - (a=b)
這樣a和b就完成交換了。
7.下面式子哪個不能編譯:
int [] a1=new int [3];
int [] a2=new int [4];
int [][] a3=new int [2][3];
String[] a4=new String[4];
(1).Object obj1=a1;
(2).Object obj2=a4;
(3).Object[] obj3=a1;
(4)Object[] obj4=a3;
(5)Object [] obj5=a4;
答:第三個不能編譯,因爲第三個是一維數組,數組裏存放的是基本類型,不能把基本類型轉化爲Object類型。其他的都可以,第四個要注意,Object數組裏存放的是一個int類型的一維數組,而一維數組是引用類型,可以轉爲Object。
8.面試題:
byte b1=3,b2=4,b;
b=b1+b2;
b=3+4;
哪句是編譯失敗的呢?爲什麼呢?
答:b = b1 + b2;是有問題的。
因爲變量相加,會首先看類型問題,最終把結果賦值的也會考慮類型問題。
常量相加,首先做加法,然後看結果是否在賦值的數據類型範圍內,如果不是,才報錯。
9.面試題:請用最有效率的方式寫出計算2乘以8的結果?2 * 8
答:2 << 3
內部類方面:
1.成員內部類面試題:要求:請填空分別輸出30,20,10
注意:
1:內部類和外部類沒有繼承關係。
2:通過外部類名限定this對象
Outer.this
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(?); num
System.out.println(??); this.num
//System.out.println(new Outer().num);
System.out.println(???); Outer.this.num
}
}
}
class InnerClassTest {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
2.匿名內部類面試題:
要求: 按照要求,補齊代碼
interface Inter { void show(); }
class Outer { //補齊代碼 }
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}
要求在控制檯輸出”HelloWorld”
interface Inter {
void show();
//public abstract
}
class Outer {
//補齊代碼
public static Inter method() {
//子類對象 -- 子類匿名對象
return new Inter() {
public void show() {
System.out.println("HelloWorld");
}
};
}
}
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
/* 分析:
1:Outer.method()可以看出method()應該是Outer中的一個靜態方法。
2:Outer.method().show()可以看出method()方法的返回值是一個Inter對象。
又由於接口Inter中有一個show()方法,所以我認爲method()方法的返回值類型是一個接口。
*/
}
}
集合方面:
1.集合遍歷時需要注意:防止出現NoSuchElementException;
例如以下代碼:
// 創建集合對象
Collection c = new ArrayList();
// 創建學生對象
Student s1 = new Student("林青霞", 27);
Student s2 = new Student("風清揚", 30);
Student s3 = new Student("令狐沖", 33);
Student s4 = new Student("武鑫", 25);
Student s5 = new Student("劉曉曲", 22);
// 把學生添加到集合中
c.add(s1);
c.add(s2);
c.add(s3);
c.add(s4);
c.add(s5);
// 遍歷
Iterator it = c.iterator();
while (it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s.getName() + "---" + s.getAge());
// System.out.println(((Student) it.next()).getName() + "---"
// + ((Student) it.next()).getAge());
// NoSuchElementException 不要多次使用it.next()方法
}
如上紅色代碼處將會出現異常(NoSuchElementException),因爲
It.next()方法會自動取下一個元素,也就是說它是取了第一個人的名字,而接着取了第二個人的年齡(不是第一個人的,因爲當取完第一個人的名字時,((Student) it.next()).getAge()方法使它跳到第二個人上去了)
2.將上面while(){}部分代碼替換成for()循環。
源碼:
Iterator it = c.iterator();
while (it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s.getName() + "---" + s.getAge());
}
改寫:
for(Iterator it = c.iterator();it.hasNext();){
Student s = (Student) it.next();
System.out.println(s.getName() + "---" + s.getAge());
}
For循環改寫後的優點是for代碼執行完後對象Iterator it 就銷燬了(for代碼出棧,內容銷燬)
泛型方面:
1.List<String> listArray = new ArrayList<>(); 有沒有辦法向listArray集合中放入integer類型或其他類型的數據?
答:如果按正常的add()方法添加肯定是不行的,編譯器不通過。但 是利用反射是可以的。例如:
listArray.getClass().getMethod("add",Object.class).invoke(listArray, 23);
這樣子是通過對象得到Class文件,進而得到該類的方法,調用該方法。反射越過編譯器的檢查,就可以把其他類型放入listArray集合中了。泛型是給編譯器看的,運行期不存在泛型,這叫做泛型的擦除。因此可以利用反射越過編譯器。
2.下面代碼會不會報錯?
Vector v1=new Vector<String>(); (1)
Vector<Object> v=v1; (2)
答:編譯器不會報錯,第一條把參數化類型給原子類型不會報錯,第二句把原子類型給參數化類型也不會報錯。我們不能把兩句連起來看,因爲編譯器一句一句的執行,是一個嚴格按語法檢查的工具,不考慮運行時的效果。
3.注:數組不能和泛型結合。
4.問題:定義一個方法,該方法用於打印出任意參數化類型的集合中的所有數據,該方法如何定義?
答:
錯誤方式:
publicstaticvoid printCollection(Collection<Object> cols){
for(Object obj:cols){
System.out.println(obj);
}
cols.add("abc");//不會報錯 ①
cols=new HashSet<Date>();//編譯報錯 ②
}
正確方式:
publicstaticvoid printCollection(Collection<?> cols){
for(Object obj:cols){
System.out.println(obj);
}
cols.add("abc");//報錯 ①
cols.size();//不會報錯,這個方法與參數類型沒有關係 ②
cols=new HashSet<Date>();// ③
}
分析以上情況:
在錯誤方式下,①不會報錯,Collection<Object> 中的Object只是說明Collection<Object>實例對象中的方法接受的參數是Object,String對象也是Object的,因此不會報錯。②編譯報錯Collection<Object>是一種具體類型,new HashSet<Date>也是一種具體類型,兩者不兼容,因此編譯會報錯。
在正確方式下:①報錯,因爲Collection<?> cols可以與任意參數類型匹配,到底匹配的是什麼類型,只有以後才知道,而①方式添加一個String對象,就會報錯,因爲它不知道自己未來匹配的就一定是String。
②不會報錯,因爲這個方法與參數類型沒有關係。③不會報錯,因爲Collection<?>可以接受任意類型,那麼Date類型也可以。
總結:使用?通配符可以引用其他各種參數類型,?通配符定義的變量主要用作引用,可以調用與參數化無關的方法,不能調用與參數化有關的方法。
5.java的泛型:java的泛型類型(或者泛型)類似於C++的模板。但這種相似性僅限於表面,java語言的泛型基本上完全是在編譯器中實現的,用於編譯器執行類型檢查和類型推斷,然後生成普通的非泛型的字節碼,這種技術叫做擦除(編譯器使用泛型類型信息保證類型安全,然後在生成字節碼之前將其清除),這是因爲擴展虛擬機指令集來支持泛型被認爲是無法接受的,這會爲java廠商升級其jvm造成障礙,所以java的泛型採用了可以完全在編譯器中實現的擦除。所以List<Integer>和List<String>得到的字節碼是一份字節碼。
6.只有引用類型才能作爲泛型方法的實際參數。
7.除了在應用泛型時可以使用extends限定符,在自定義泛型時也可以使用extends限定符,例如:Class.getAnnotation()的定義。並且可以使用&來指定多個邊界,如<V extends Serializable & cloneable> void method(){}。
8.普通方法、構造方法、靜態方法中都可以使用泛型。
9.也可以用類型變量表示異常,稱爲參數化異常,可以用於方法的throws列表中,但是不能用於catch子句中。
private static <T extends Exception> sayHello() throws T{
try{
}catch(Exception e){
Throw (T)e;
}
}
10.在泛型中可以同時有多個類型參數,在定義它們的尖括號中用逗號分,例如: public static <k,v> V getValue(k key){return map.get(key);}
11.類型參數的類型推斷:
根據調用泛型方法時實際傳遞的參數類型或返回值類型判斷,具體規則如下:
(1)當某個類變量只在整個參數列表中的所有參數和返回值中的一處被調用了,那麼根據調用方法時該處的實際應用類型來確定,這很容易憑藉着感覺推斷出來,即直接根據調用方法時傳遞的參數類型或返回值來決定泛型參數的類型,例如:
swap(new String[3],3,4) —> static <E> void swap(E[] a,int i, int j)
(2) 當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多出的實際應用類型都對應同一種類型來確定,這很容易憑藉感覺推斷出來,例如:
add(3,5) —> static <T> T add(T a,T b)
(3) 當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多出的實際應用類型對應到了不同的類型,且沒有使用返回值,這時候取多個參數中的最大交集類型,例如,下面語句實際對應的類型就是Number了,編譯沒問題,只是運行時出了問題:
fill(new Integer[3],3.5f) —> static <T> void fill(T [] a,T v)
(4) 當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型,並且使用返回值,這時優先考慮返回值的類型,例如,下面語句實際對應的類型就是Integer了,編譯將報告錯誤,將變量x的類型改爲float,對比eclipse報告的錯誤提示,接着再將變量x類型改爲Number,則沒有了錯誤:
int x=(3,3.5f) —> static <T> T add(T a,T b);
(5) 參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型爲Object,編譯沒有問題,而第二種情況則根據參數化的Vector類實例將類型變量直接確定爲String類型,編譯出現問題:
copy(new Integer[5],new String[5])—> static <T> void copy(T[] a,T[] b)
copy(new Vector<String>(),new Integer[5]) —> static <T> void copy(Collection <T> a,T[] b);
12.在泛型類中,當一個變量被聲明爲泛型時,只能被實例變量和方法調用(還有內嵌類型),而不能被靜態變量和靜態方法調用。因爲靜態成員是被所有參數化的類所共享的,所以靜態成員不應該有類級別的類型參數。例如:①編譯出錯,②可以(因爲聲明爲泛型方法)
13.下面代碼是重載嗎?
public void test(List<String> list ){}
public void test(List<Integer> list ){}
答:不是重載,這兩個方法同時存在編譯器會報錯,因爲編譯後泛型擦除,去掉類型化,參數就是一樣的了。
14.利用反射獲得泛型的實際參數類型方法。
答:可以用如下方法:
得到的結果:
分析上面代碼:第①句是利用反射得到該類的字節碼文件,進而得到方法。第②句是得到泛型類型的參數,第③句轉換爲具體的類型,第④句是得到實際參數類型。
很多框架利用這樣的代碼生成具體參數類型的對象。
反射、類加載方面
1.注:Class文件裏面的東西不是字節碼,只有用類加載器把Class文件加載進內存後,類加載器對文件進行處理(安全檢查等等)後,最終在內存中得到的二進制的文件纔是字節碼。
2.註解的生命週期有三個階段:java源文件、class文件、內存中的字節碼。Javac把源文件翻譯成class文件時有可能去掉註解,類加載器把class文件加載進內存是也有可能去掉註解。所以一搬可以加上@ReteionPolicy來聲明註解存在哪個階段,@ReteionPolicy的三種取值:ReteionPolicy.SOURCE,ReteionPolicy.CLASS,ReteionPolicy.SUNTIME,默認值在CLASS階段。
類加載器方面:
1.類加載器也是java類,因爲其他是java類的類加載器本身也要被類加載器加載,顯然必須有第一個類加載器不是java類,這正是BootStrap。這個類是用C++寫的。
2.查看類加載器的名稱。
輸出結果:
3.查看類加載器的繼承關係:
輸出結果:
從結果可以看出AppClassLoader類加載器繼承ExtClassLoader類加載器,而null表示BootStrap類加載器,因爲它不是用java寫的,所以用java的getName()方法當然得不到該類加載器了。
4.類加載器之間的關係以及管轄範圍:
5.類加載器的委託機制:
(1)當java虛擬機要加載一個類時,到底派哪個類加載器去加載呢?
答:首先當前線程的類加載器去加載線程中的第一個類。
如果類A中引用了類B,java虛擬機使用加載類A的類加載器來加載類B。
還可以調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。
6.每個ClassLoader本身只能分別加載特定位置和目錄的類,但它們可以委託其他類加載器去加載類,這就是類加載器的委託模式。類加載器一級級委託到BootStrap類加載器,當BootStrap無法加載當前所要加載的類時,然後才一級級回退到子孫類加載器去進行真正的加載。當回到最初的類加載器時,如果它自己也不能完成類的加載,那就應該報告ClassNotFoundException異常。
7.有一個面試題:能不能自己寫一個類叫java.lang.System?
答:通常情況下不可以,類加載器的委託機制保證了先使用父類的類加載器,父類的類加載器先加載了java.lang.System類了,也就是說,爲了不讓我們寫System類,類加載器採用委託機制,這樣可以保證爸爸優先,也就是總是使用爸爸們能找到的類,這樣總是使用java系統提供的System類。但如果我們自己寫類加載器是可以加載自己的System類的。
代理方面:
1.讓java虛擬機創建動態類以及實例對象,需要給它提供哪些信息?
答:三個方面:
u 生成的類有哪些方法,通過讓其實現哪些接口的方式進行告知。
u 產生的類字節碼必須有一個關聯的類加載器對象。
u 生成的類中方法的代碼是怎樣的,也由我們提供。把我們的代碼寫在一個約定好的接口對象的方法中,把對象傳給它,它調用我的方法,即相當於它插入我的代碼。提供執行代碼的對象就是那個InvocationHandler對象,它是在創建動態類的實例對象的構造方法時傳遞進去的。在上面的InvocationHandler對象的invoke方法中加以點代碼,就可以看到這些代碼被調用運行了。