- Java 面試題2

文章目錄


一個".java"源文件中是否可以包括多個類(不是內部類)?有什麼限制?

可以有多個類,但只能有一個 public 的類,並且 public 的類名必須與文件名一致;

  • Java程序是從一個public類的main函數開始執行的,只能有一個public類是爲了給類裝載器提供方便;
  • 一個public類只能定義在以它的類名爲文件名的文件中;
  • 每個編譯文件都只有一個public類,因爲每個編譯文件都只能有一個公共接口,用public來表現;若有一個以上的public類,編譯器就會報錯;
  • 將程序中包含 main 方法的類名提供給字節碼解釋器, 以便啓動這個程序:

在 JAVA 中如何跳出當前的多重嵌套循環

(1)使用標號:
在最外層循環語句前定義標號,然後再裏層循環體的代碼中使用帶有標號的break語句,跳出外層循環;
在這裏插入圖片描述

(2)讓外層循環 條件表達式的結果 可以受到裏層循環體代碼的控制:
在這裏插入圖片描述


switch 語句能否作用在 byte/long/String 上?

switch(expression)中,expression 只能是一個整數表達式、枚舉常量;而整數表達式可以是int基本類型或Integer包裝類型;

byte、short、char 可以隱含轉換爲int,所以這些類型以及其包裝類型可以作爲switch的表達式;

long、String 類型不符合switch的語法規定,且不能被隱式轉換成int類型,所以不能作用於switch語句中;


short s1 = 1; s1 = s1 + 1;對錯? short s1 = 1; s1 += 1;對錯?

  • short s1 = 1; s1 = s1 + 1;:編譯錯誤!
    s1+1運算時會發生自動類型提升,所以計算結果是int型,再賦值給short類型的s1時,編譯器會報錯:Type mismatch: cannot convert from int to short;需要強制轉換類型;

  • short s1 = 1; s1 += 1;:可以正確編譯;
    因爲 += 是 java 語言規定的運算符,java 編譯器會對它進行特殊處理;


char 型變量中能不能存貯一箇中文漢字?爲什麼?

可以;

char型變量是用來存儲Unicode編碼的字符的,而Unicode編碼字符集中包含了漢字,所以char型變量中可以存儲漢字;

但是,若某個特殊的漢字沒有被包含在Unicode編碼字符集中,那麼這個漢字就不能用char型變量來存儲;

Unicode編碼佔用兩個字節,所以char型的變量也佔兩個字節;


用最有效率的方法算出 2 乘以 8 等於幾?

2<<8

將一個數左移n位,相當於這個數乘以2的n次方;
程序中所有的數在計算機中都是以二進制的形式存儲的,位運算就是直接對整數在內存中的二進制位進行操作;


Integer 與 int 的區別

int是Java的基本數據類型, Integer是Java提供的對int的封裝類型;
int默認值是0,integer默認值是null;
integer可以區分0和未賦值的區別,而int無法表示未賦值的情況;
比如想要表達考試成績爲0和沒參加考試的情況,只能使用Integer;


super.getClass().getName()輸出結果

public class Test extends Date{
    public static void main(String [] args) {
        new Test().t();
    }
    public void t() {
    	System.out.println(super.getClass().getName());    
    	System.out.println(super.getClass().getSuperclass().getName());  
    } 
}

第一個輸出:Test
第二個輸出:父類Date

在 test 方法中,直接調用 getClass().getName()方法,返回的是 Test 類名;
由於 getClass()在 Object 類中定義成了final,子類不能覆蓋該方法,所以,在test 方法中調用 getClass().getName()方法,其實就是在調用從父類繼承的 getClass()方法,等效於調用 super.getClass().getName()方法,所以,super.getClass().getName()方法返回的也應該是 Test;
如果想得到父類的名稱,應該用如下代碼:getClass().getSuperClass().getName();


String s = "Hello";s = s + " world!";這兩行代碼執行後,原始的 String 對象中的內容到底變了沒有?

沒有;
因爲 String 被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。在這段代碼中,s 原先指向一個 String 對象,內容是 “Hello”,然後我們對 s 進行了+操作,這時,s 不指向原來那個對象了, 而指向了另一個 String 對象,內容爲"Hello world!",原來那個對象還存在於內存之中,只是 s 這個引用變量不再指向它了;


String s = new String("xyz");創建了幾個 String Object?二者之間有什麼區別?

兩個;

首先會創建一個"xyz"對象防在字符串常量池中;然後通過new創建一個String類型的對象在堆內存中,


數組沒有length()這個方法,有length 的屬性;String 有 length()方法


下面這條語句一共創建了多少個對象:String s="a"+"b"+"c"+"d";

1個;

String s1 = "a"; 
String s2 = s1 + "b"; 
String s3 = "a" + "b";
System.out.println(s2 == "ab");    false 
System.out.println(s3 == "ab");    true

javac編譯會將字符串常量直接相加的表達式進行優化,直接在編譯的時候將+號去掉,將表達式編譯成一個這些常量直接相加的結果;不用等到運行時進行加法運算處理;

所以上面的語句在經過編譯器在編譯時優化之後,相當於直接定義了一個字符串"zbcde",所以只創建了一個String類型的對象放在字符串常量池中;

s1 = s1 + "b";相當於執行了s1 = new String("ab");


try {}裏有一個 return 語句,那麼緊跟在這個 try 後的 finally {}裏的 code會不會被執行?什麼時候被執行?在 return 前還是後?

不是在return之前執行,可以說是在return中間執行;

public class test extends Date{
    public static void main(String [] args) {
    	System.out.println(new test().t());
    }
    public int t() {
    	int x = 1;
    	try {
			return x;
		} finally {
			++x;
		}
    }
}   

輸出:1
主函數調用子函數並得到結果的過程,好比主函數準備一個空罐子,當子函數要返回結果時,先把結果放在罐子裏,然後再將程序邏輯返回到主函數。所謂返回,就是子函數說,我不運行了,你主函數繼續運行吧,這沒什麼結果可言,結果是在說這話之前放進罐子裏的。

public class test extends Date{
    public static void main(String [] args) {
    	System.out.println(new test().t());
    }
    public int t() {
    	try {
			System.out.println("try");
			return 1;
		} finally {
			System.out.println("finally");
			return 2;
		}
    }
}

返回結果是:try, finally, 2
try 中的 return 語句先執行,finally 語句後執行;Return 並不是讓函數馬上返回,而是 return 語句執行後,將把返回結果放置進函數棧中,此時函數並不是馬上返回,它要執行finally 語句後才真正開始返回;
finally 中的代碼比 return 和break 語句後執行;


Thread中有run,傳入的d 中也有run,爲什麼調用的是d.run?

因爲Thread類中有一個Runnable接口類型的成員變量r,運行run方法時會先判斷這個成員變量是不是爲空,若爲空就調用Thread的run方法,不爲空,就調用r的run方法;
public void run(){ if(r!=null) r.run(); }


當一個線程進入一個對象的一個 synchronized 方法後,其它線程是否可進入此對象的其它方法?

1、其他方法前是否加了 synchronized 關鍵字,如果沒加,則能。
2、如果這個方法內部調用了 wait,則可以進入其他 synchronized 方法。
3、如果其他個方法都加了 synchronized 關鍵字,並且內部沒有調用 wait,則不能
4、如果其他方法是 static,它用的同步鎖是當前類的字節碼,與非靜態的方法不能同步,因爲非靜態的方法用的是 this;


Collection 框架中實現比較要實現什麼接口

comparable/comparator


兩個對象值相同(x.equals(y) == true),但卻可有不同的 hash code,這句話對不對?

對;
Java規定equals相等的兩個對象,hashcode的值一定要相等;
如果對象要存儲在HashSet或HashMap中,它們的equals相等,那麼hashcode值就必須相等;
但是如果不是保存在HashSet或HashMap中,equals相等的兩個對象的hashcode值可以不相等,因爲對象的類可以不用重寫hashcode方法,這時候hashcode方法返回的是對象的地址值;


TreeSet 裏面放對象,如果同時放入了父類和子類的實例對象,那比較時使用的是父類的 compareTo 方法,還是使用的子類的 compareTo 方法,還是拋異常

當前add方法放入的是哪個對象,就調用那個對象的compareTo方法;至於compareTo方法怎麼做,就要看當前這個對象的類中是如何實現這個方法的;


說出一些常用的類,包,接口,請各舉 5 個

常用的包:java.langjava.iojava.utiljava.sqljavax.servletorg.hibernate

常用的接口: ListMapServletHttpServletRequestHttpServletResponseSession(Hibernate)HttpSession

常用的類:BufferedReaderBufferedWriterFileReaderFileWirterStringInteger java.util.DateSystemClass

代碼查錯

1、

abstract class Name { 
	private String name;
	public abstract boolean isStupidName(String name) {}
}

錯;abstract方法必須以結尾,並且不能有{}方法體;

2、

public class Something { 
	void doSomething () {
		private String s = ""; 
		int l = s.length();
	}
}

錯;局部變量前不能加訪問修飾符(public、private等),可以加final;

3、

abstract class Something {
	private abstract String doSomething ();
}

錯;抽象方法不能定義爲private/final,因爲它需要被子類繼承;

4、

public class Something {
	public int addOne(final int x) {
 		return ++x;
	}
}

錯;x被修飾成final,在方法中就不能再被修改了;

5、

public class Something {
	public static void main(String[] args) { 
		Other o = new Other();
		new Something().addOne(o);
	}
	public void addOne(final Other o) { 
		o.i++;
	}
}
class Other {
	 public int i;
}

對;因爲被定義成final的是對象o,它的引用不能改,但是對象裏面的內容可以修改;

6、

class Something { 
	int i;
	public void doSomething() {
		 System.out.println("i = "+ i);
	}
}

對;輸出 i = 0;,i屬於成員變量,成員變量有默認值,int默認值是0;

7、

class Something { 
	final int i;
	public void doSomething() { 
		System.out.println("i = "+ i);
	}
}

錯;final定義的變量必須初始化;

8、

public class Something {
	public static void main(String[] args) { 
		Something s = new Something();
		System.out.println("s.doSomething() returns " + doSomething());
	}
	public String doSomething() { 
		return "Do something ...";
	}
}

錯;
System.out.println("s.doSomething() returns " + doSomething());改成System.out.println("s.doSomething()returns " + s.doSomething());

9、此處,Something 類的文件名叫 OtherThing.java

class Something {
	private static void main(String[] something_to_do){ 
		System.out.println("Dosomething ...");
	}
}

正確;類名可以和文件名不一樣,但是public class的名字必須和文件名相同;

10、

interface A{
	int x = 0;
}
class B{
	int x =1;
}
class C extends B implements A { 
	public void pX(){
		System.out.println(x);
	}
	public static void main(String[] args) { 
		new C().pX();
	}
}

編譯時發生錯誤;
因爲兩個x都能調用,有調用的不確定性;
對於父類B的成員變量,可以使用super.x來使用;
接口的成員變量默認是public static final的,可以使用A.x來使用;

11、

interface Playable {  void play(); }
interface Bounceable {  void play(); }
interface Rollable extends Playable, Bounceable {
 	Ball ball = new Ball("PingPang");
}
class Ball implements Rollable { 
	private String name;
	public String getName() { 
		return name;
	}
	public Ball(String name) {
 		this.name =name;
	}
	public void play() {
		ball = newBall("Football");  // 這裏編譯錯誤;
		System.out.println(ball.getName());
	}
}

編譯失敗;
play方法裏面的ball變量是從接口 Rollable那繼承過來的,接口定義的變量默認是public static final,所以Ball ball = new Ball("PingPang");定義的ball不能再修改引用;而在play方法中,ball = newBall("Football");修改的ball的引用;


創建兩個線程交替執行

注意:使用的鎖應該是同一個;

public class Test {
	public static void main(String[] args) {
		Demo d1 = new Demo(true);
		Demo d2 = new Demo(false);
		Thread t1 = new Thread(d1);
		Thread t2 = new Thread(d2);
		t1.start();
		t2.start();
	}
}
class Demo implements Runnable{
	private static final Object obj = new Object();
	private boolean flag;
	public Demo(boolean flag) {
		this.flag = flag;
	}
	public void run() {
		if(flag) {
			synchronized (obj) {
				for(int i=0; i<100; i+=2) {
					System.out.println("if..." + i);
					obj.notify();
					try {
						obj.wait();
					} catch (InterruptedException e) {
					}
				}
			}
		}else {
			synchronized (obj) {
				for(int i=1; i<=100; i+=2) {
					System.out.println("else......" + i);
					obj.notify();
					try {
						obj.wait();
					} catch (InterruptedException e) {
					}
				}
			}
		}
	}
}

自動類型提升與強制類型轉換

在這裏插入圖片描述

在這裏插入圖片描述


對兩個整數變量的值進行互換 int a=3,b=5;

(1)int c; c=a;a=b;b=c; //使用第三方變量 (實際開發時用,閱讀性強)
(2)a=a+b; a=3+5=8 //不使用第三方變量
b=a-b; b=8-5=3
a=a-b; b=8-3=5
注意:這種方式不要用,若兩個整數數值過大,會超過int範圍,會強制轉換,數據會丟失精度
(3)a=a^b; a=3^5; //使用位運算 (面試時用)
b=a^b; b=(3^5)^5=3
a=a^b; a=(3^5)^3=5
原理:一個數兩次異或同一個數,不變;


switch~case

在這裏插入圖片描述


~ 面向對象練習題

interface A{}
class B implements A{
	public String func(){ return "func"; }
}
class test {
	public static void main(String[] args) {
		A a = new B();  // 多態:父類引用指向子類對象;
		// a.func();    // 編譯失敗:因爲a所屬的A接口沒有定義func方法;
	}
}
// 對於調用非靜態方法,編譯看等號左邊,運行看右邊;
// B提升爲A,隱藏自己的內容,使用A的內容,A中沒有func()方法
class Fu{
	boolean show(char ch){
		System.out.println(ch);
		return true;
	}
}
class test extends Fu {
	public static void main(String[] args) {
		int i = 0;
		Fu f = new test();
		test t = new test();
		for(f.show('A'); f.show('B') && (i<2); f.show('C')){
			i++;
			t.show('D');
		}
	}
	boolean show(char ch){
		System.out.println(ch);
		return false;
	}
}
// > A B
// 1、f.show('A'):f是接口,接口引用指向子類對象,子/父類都有show方法,
//    所以子類方法覆蓋父類方法,f調用的是子類的show方法,So輸出'A',返回false,
//    false在for循環的第一個位置,沒用;
// 2、f.show('B') && (i<2):左邊表達式調用子類的show方法,輸出'B',返回false,
//    雙 & 進行短路計算,整個大的表達式結果爲false;for循環條件爲假,循環結束;
interface A{}
class B implements A{
	public String test(){ return "yes"; }
}
class test {
	static A get(){ return new B(); }  // 靜態方法,返回值是接口A類型;
	public static void main(String[] args) {
		A a = get();
		System.out.println(a.test());
	}
}
// 編譯失敗;因爲A接口中沒有定義test方法;
// A a = get(); 相當於 A a = new B(); 接口引用指向子類對象;
class Super{
	int i = 0;
	public Super(String a){
		System.out.println("A");
		i = 1;
	}
	public Super(){
		System.out.println("B");
		i+=2;
	}
}
class test extends Super{
	public test(String a){
		// super();   // 默認訪問父類中空參數的構造函數;
		System.out.println("C");
		i+=5;
	}
	public static void main(String[] args) {
		int i = 4;
		Super d = new test("A");
		System.out.println(d.i);
	}
}
// B C 7
// 1、new test("A"):調用本類中帶參構造函數,
// 2、子類構造函數先調用父類的無參構造函數:輸出B,i=0+2=2;
// 3、子類構造函數輸出"C",i=2+5=7;
class TD{
	int y = 6;
	class Inner{
		static int y = 3;
		void show(){ System.out.println(y); }
	}
}
class test {
	public static void main(String[] args) {
		TD.Inner ti = new TD().new Inner();
		ti.show();
	}
}
// 編譯失敗:非靜態的內部類中不能定義靜態成員
選擇題:寫出錯誤答案錯誤的原因: class Demo{ int show(int a,int b){return 0;} }
下面哪些函數可以存在與Demo的子類中:
A、public int show(int a,int b){return 0;}   // 可以,覆蓋;
B、private int show(int a,int b){return 0;}  // 不可以,子類的權限應該大於父類的權限;
C、private int show(int a,long b){return 0;} // 可以,參數列表的參數類型不完全一樣,是子類的特有方法,不是父類方法的覆蓋;
D、public short show(int a,int b){return 0;} // 不可以,調用的不確定性;
E、static int show(int a,int b){return 0;}   // 不可以,靜態只能覆蓋靜態;
class Fu{
	int num = 4;
	void show(){ System.out.println("FuShow"); }
}
class Zi extends Fu{
	int num = 5;
	void show(){ System.out.println("ZiShow"); }
}
class test {
	public static void main(String[] args) {
		Fu f = new Zi();
		Zi z = new Zi();
		System.out.println(f.num); // 4
		System.out.println(z.num); // 5
		f.show(); // ZiShow
		z.show(); // ZiShow
	}
}
class Super{
	int i = 0;
	public Super(String s){ i=1; }
}
class test extends Super {
	public test(String s){i = 2;}
	public static void main(String[] args) {
		test d = new test("yes");
		System.out.println(d.i);
	}
}
// 編譯失敗:父類中沒有空參構造函數,必須顯示定義一個; 
class Super{
	public int get(){ return 4; }
}
class test extends Super {
	public long get(){ return 5; }
	public static void main(String[] args) {
		Super s = new test();
		System.out.println(s.get());
	}
}
// 編譯失敗:返回值類型不一樣,覆蓋錯誤;
interface Test { void fun(); }
class test {
	public static void main(String[] args) {
		new test().show(new Test() {
						   public void fun(){}
					    });
	}
	void show(Test t){ t.fun(); }
}
// 在主函數中補足代碼:使用匿名內部類調用show方法;
// 主函數中不能直接調用show方法,因爲show方法是非靜態的;
// 所以需要先創建一個Demo類,通過類名調用show方法;
// 往show方法裏面傳值:接口型的引用;接口裏面就一個方法,
// 這時候可以創建匿名內部類;傳的值是接口類型的,
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章