1. 程式人生 > >Java面試系列總結 :JavaSE基礎(2) 常用 API

Java面試系列總結 :JavaSE基礎(2) 常用 API

1. Math.round(11.5)等於多少?Math.round(- 11.5) 又等於多少?

Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。
四捨五入的原理是在引數上加 0.5然後進行取整。

2. switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String上?

Java5以前switch(expr)中,expr 只能是 byte、short、char、int。從 Java 5 開始,Java 中引入了列舉型別,expr 也可以是 enum 型別。
從Java 7 開始,expr 還可以是字串(String),但是長整型(long)在目前所有的版本中都是不可以的。

3. 陣列有沒有length() 方法?String有沒有length() 方法?

陣列沒有 length()方法,而是有 length 的屬性。String 有 length()方法。JavaScript 中,獲得字串的長度是通過 length 屬性得到的,這一點容易和 Java 混淆。

4. String 、StringBuilder 、StringBuffer的區別?

Java平臺提供了兩種型別的字串:String和StringBuffer/StringBuilder,它們都可以儲存和操作字串,區別如下。

1)String是隻讀字串,也就意味著String引用的字串內容是不能被改變的。初學者可能會有這樣的誤解:

String str = “abc”; 
str = “bcd”; 

如上,字串str明明是可以改變的呀!其實不然,str僅僅是一個引用物件,它指向一個字串物件“abc”。第二行程式碼的含義是讓 str 重新指向了一個新的字串“bcd”物件,而“abc”物件並沒有任何改變,只不過該物件已經成為一個不可及物件罷了。

2)StringBuffer/StringBuilder 表示的字串物件可以直接進行修改。

3)StringBuilder是Java5中引入的,它和 StringBuffer的方法完全相同,區別在於它是在單執行緒環境下使用的,因為它的所有方法都沒有被synchronized修飾,因此它的效率理論上也比StringBuffer要高。

5. 什麼情況下用“+”運算子進行字串連線比呼叫 StringBuffer/StringBuilder 物件的append方法連線字串效能更好?

該題來自華為。
字串是Java程式中最常用的資料結構之一。在Java中String類已經過載了"+"。也就是說,字串可以直接使用"+"進行連線,如下面程式碼所示:

String s = "abc" + "ddd"; 

但這樣做真的好嗎?當然,這個問題不能簡單地回答 yes or no。要根據具體情況來定。在 Java 中提供了一個StringBuilder類(這個類只在J2SE5及以上版本提供,以前的版本使用StringBuffer類),這個類也可以起到"+"的作用。那麼我們應該用哪個呢?

下面讓我們先看看如下的程式碼:

package string; 

public class TestSimplePlus { 
    public static void main(String[] args) { 
        String s = "abc"; 
        String ss = "ok" + s + "xyz" + 5; 
        System.out.println(ss); 
    }
}

上面的程式碼將會輸出正確的結果。從表面上看,對字串和整型使用"+"號並沒有什麼區別,但事實真的如此嗎?下面讓我們來看看這段程式碼的本質。

我們首先使用反編譯工具(如jdk帶的javap、或jad)將 TestSimplePlus反編譯成Java Byte Code,其中的奧祕就一目瞭然了。在本文將使用jad來反編譯,命令如下:

jad -o -a -s d.java TestSimplePlus.class 

反編譯後的程式碼如下:

package string; 

import java.io.PrintStream; 

public class TestSimplePlus { 

    public TestSimplePlus() { 
        //    0    0:aload_0          
        //    1    1:invokespecial   #8   <Method void Object()> 
        //    2    4:return           
    } 

    public static void main(String args[]) { 
        String s = "abc"; 
        //    0    0:ldc1            #16  <String "abc"> 
        //    1    2:astore_1         
        String ss = (new StringBuilder("ok")).append(s).append("xyz").append(5).toString(); 
        //    2    3:new             #18  <Class StringBuilder> 
        //    3    6:dup              
        //    4    7:ldc1            #20  <String "ok"> 
        //    5    9:invokespecial   #22  <Method void StringBuilder(String)> 
        //    6   12:aload_1          
        //    7   13:invokevirtual   #25  <Method StringBuilder StringBuilder.append(String)> 
        //    8   16:ldc1            #29  <String "xyz"> 
        //    9   18:invokevirtual   #25  <Method StringBuilder StringBuilder.append(String)> 
        //   10   21:iconst_5         
        //   11   22:invokevirtual   #31  <Method StringBuilder StringBuilder.append(int)> 
        //   12   25:invokevirtual   #34  <Method String StringBuilder.toString()> 
        //   13   28:astore_2         
        System.out.println(ss); 
        //   14   29:getstatic       #38  <Field PrintStream System.out> 
        //   15   32:aload_2          
        //   16   33:invokevirtual   #44  <Method void PrintStream.println(String)> 
        //   17   36:return           
    } 
}

讀者可能看到上面的Java位元組碼感到迷糊,不過大家不必擔心。本文的目的並不是講解Java Byte Code,因此,並不用瞭解具體的位元組碼的含義。

使用 jad 反編譯的好處之一就是可以同時生成位元組碼和原始碼。這樣可以進行對照研究。從上面的程式碼很容易看出,雖然在源程式中使用了"+",但在編譯時仍然將"+"轉換成StringBuilder。因此,我們可以得出結論,在 Java 中無論使用何種方式進行字串連線,實際上都使用的是 StringBuilder。

那麼是不是可以根據這個結論推出使用"+“和StringBuilder的效果是一樣的呢?這個要從兩個方面的解釋。如果從執行結果來解釋,那麼”+"和StringBuilder是完全等效的。但如果從執行效率和資源消耗方面看,那它們將存在很大的區別。

當然,如果連線字串行表示式很簡單(如上面的順序結構),那麼"+"和StringBuilder基本是一樣的,但如果結構比較複雜,如使用迴圈來連線字串,那麼產生的 Java Byte Code 就會有很大的區別。先讓我們看看如下的程式碼:

package string; 

import java.util.*; 

public class TestComplexPlus { 
    public static void main(String[] args) { 
        String s = ""; 
        Random rand = new Random(); 
        for (int i = 0; i < 10; i++) { 
            s = s + rand.nextInt(1000) + " "; 
        } 
        System.out.println(s); 
    } 
} 

上面的程式碼返編譯後的 Java Byte Code 如下:

package string; 

import java.io.PrintStream; 
import java.util.Random; 

public class TestComplexPlus { 
    
    public TestComplexPlus() { 
        //    0    0:aload_0          
        //    1    1:invokespecial   #8   <Method void Object()> 
        //    2    4:return           
    } 

    public static void main(String args[]) { 
        String s = ""; 
        //    0    0:ldc1            #16  <String ""> 
        //    1    2:astore_1         
        Random rand = new Random(); 
        //    2    3:new             #18  <Class Random> 
        //    3    6:dup              
        //    4    7:invokespecial   #20  <Method void Random()> 
        //    5   10:astore_2         
        for(int i = 0; i < 10; i++) 
        //*   6   11:iconst_0         
        //*   7   12:istore_3         
        //*   8   13:goto            49 
            s = (new StringBuilder(String.valueOf(s))).append(rand.nextInt(1000)).append(" ").t oString(); 
        //    9   16:new             #21  <Class StringBuilder> 
        //   10   19:dup              
        //   11   20:aload_1          
        //   12   21:invokestatic    #23  <Method String String.valueOf(Object)> 
        //   13   24:invokespecial   #29  <Method void StringBuilder(String)> 
        //   14   27:aload_2          
        //   15   28:sipush          1000 
        //   16   31:invokevirtual   #32  <Method int Random.nextInt(int)> 
        //   17   34:invokevirtual   #36  <Method StringBuilder StringBuilder.append(int)> 
        //   18   37:ldc1            #40  <String " "> 
        //   19   39:invokevirtual   #42  <Method StringBuilder StringBuilder.append(String)> 
        //   20   42:invokevirtual   #45  <Method String StringBuilder.toString()> 
        //   21   45:astore_1         
        //   22   46:iinc            3  1 
        //   23   49:iload_3          
        //   24   50:bipush          10 
        //   25   52:icmplt          16 
        System.out.println(s); 
        //   26   55:getstatic       #49  <Field PrintStream System.out> 
        //   27   58:aload_1          
        //   28   59:invokevirtual   #55  <Method void PrintStream.println(String)> 
        //   29   62:return           
    } 
}

大家可以看到,雖然編譯器將"+"轉換成了StringBuilder,但建立StringBuilder物件的位置卻在for語句內部。這就意味著每執行一次迴圈,就會建立一個StringBuilder物件(對於本例來說,是建立了10個StringBuilder物件),雖然Java有垃圾回收器,但這個回收器的工作時間是不定的。如果不斷產生這樣的垃圾,那麼仍然會佔用大量的資源。解決這個問題的方法就是在程式中直接使用StringBuilder來連線字串,程式碼如下:

package string; 

import java.util.*; 

public class TestStringBuilder { 

    public static void main(String[] args) { 
        String s = ""; 
        Random rand = new Random(); 
        StringBuilder result = new StringBuilder(); 
        for (int i = 0; i < 10; i++) { 
            result.append(rand.nextInt(1000)); 
            result.append(" "); 
        } 
        System.out.println(result.toString()); 
    } 
}

上面程式碼反編譯後的結果如下:

package string; 

import java.io.PrintStream; 
import java.util.Random; 

public class TestStringBuilder { 

    public TestStringBuilder() { 
        //    0    0:aload_0          
        //    1    1:invokespecial   #8   <Method void Object()> 
        //    2    4:return           
    }

    public static void main(String args[]) { 
        String s = ""; 
        //    0    0:ldc1            #16  <String ""> 
        //    1    2:astore_1         
        Random rand = new Random(); 
        //    2    3:new             #18  <Class Random> 
        //    3    6:dup              
        //    4    7:invokespecial   #20  <Method void Random()> 
        //    5   10:astore_2         
        StringBuilder result = new StringBuilder(); 
        //    6   11:new             #21  <Class StringBuilder> 
        //    7   14:dup              
        //    8   15:invokespecial   #23  <Method void StringBuilder()> 
        //    9   18:astore_3         
        for(int i = 0; i < 10; i++) 
        //*  10   19:iconst_0         
        //*  11   20:istore          4 
        //*  12   22:goto            47 
        { 
            result.append(rand.nextInt(1000)); 
        //   13   25:aload_3          
        //   14   26:aload_2          
        //   15   27:sipush          1000 
        //   16   30:invokevirtual   #24  <Method int Random.nextInt(int)> 
        //   17   33:invokevirtual   #28  <Method StringBuilder StringBuilder.append(int)> 
        //   18   36:pop              
            result.append(" "); 
        //   19   37:aload_3          
        //   20   38:ldc1            #32  <String " "> 
        //   21   40:invokevirtual   #34  <Method StringBuilder StringBuilder.append(String)> 
        //   22   43:pop              
        } 
        //   23   44:iinc            4  1 
        //   24   47:iload           4 
        //   25   49:bipush          10 
        //   26   51:icmplt          25 
        System.out.println(result.toString()); 
        //   27   54:getstatic       #37  <Field PrintStream System.out> 
        //   28   57:aload_3          
        //   29   58:invokevirtual   #43  <Method String StringBuilder.toString()> 
        //   30   61:invokevirtual   #47  <Method void PrintStream.println(String)> 
        //   31   64:return           
    } 
}

從上面的反編譯結果可以看出,建立StringBuilder的程式碼被放在了for語句外。雖然這樣處理在源程式中看起來複雜,但卻換來了更高的效率,同時消耗的資源也更少了。

在使用StringBuilder時要注意,儘量不要"+"和StringBuilder混著用,否則會建立更多的StringBuilder物件,如下面程式碼所:

for (int i = 0; i < 10; i++) {
    result.append(rand.nextInt(1000));
    result.append(" ");
} 

改成如下形式:

for (int i = 0; i < 10; i++) {
    result.append(rand.nextInt(1000) + " "); 
} 

則反編譯後的結果如下:

for(int i = 0; i < 10; i++)   
//*  10   19:iconst_0           
//*  11   20:istore          4   
//*  12   22:goto            65    
{     
result.append((new StringBuilder(String.valueOf(rand.nextInt(1000)))).append(" ").toString()); 
//   13   25:aload_3            
//   14   26:new             #21  <Class StringBuilder>   
//   15   29:dup    

從上面的程式碼可以看出,Java編譯器將"+"編譯成了StringBuilder,這樣for語句每迴圈一次,又建立了一個StringBuilder物件。

如果將上面的程式碼在JDK1.4下編譯,必須將StringBuilder改為StringBuffer,而JDK1.4將"+"轉換為StringBuffer(因為JDK1.4並沒有提供StringBuilder類)。StringBuffer和StringBuilder的功能基本一樣,只是StringBuffer是執行緒安全的,而StringBuilder不是執行緒安全的。因此,StringBuilder的效率會更高。

6. 請說出下面程式的輸出

class StringEqualTest { 
    public static void main(String[] args) { 
        String s1 = "Programming"; 
        String s2 = new String("Programming"); 
        String s3 = "Program"; 
        String s4 = "ming"; 
        String s5 = "Program" + "ming"; 
        String s6 = s3 + s4; 
        System.out.println(s1 == s2);               //false         
        System.out.println(s1 == s5);               //true        
        System.out.println(s1 == s6);               //false         
        System.out.println(s1 == s6.intern());      //true       
        System.out.println(s2 == s2.intern());      //false  
    }
} 

補充:解答上面的面試題需要知道如下兩個知識點:

  1. String物件的intern()方法會得到字串物件在常量池中對應的版本的引用(如果常量池中有一個字串與 String 物件的 equals結果是 true),如果常量池中沒有對應的字串,則該字串將被新增到常量池中,然後返回常量池中字串的引用;
  2. 字串的+操作其本質是建立了StringBuilder 物件進行 append 操作,然後將拼接後的 StringBuilder 物件用 toString 方法處理成 String 物件,這一點可以用 javap -c StringEqualTest.class 命令獲得 class 檔案對應的 JVM 位元組碼指令就可以看出來。

7. 如何取得年月日、小時分鐘秒?

public class DateTimeTest { 
    public static void main(String[] args) { 
        Calendar cal = Calendar.getInstance(); 
        System.out.println(cal.get(Calendar.YEAR)); 
        System.out.println(cal.get(Calendar.MONTH)); // 0 - 11 
        System.out.println(cal.get(Calendar.DATE)); 
        System.out.println(cal.get(Calendar.HOUR_OF_DAY)); 
        System.out.println(cal.get(Calendar.MINUTE)); 
        System.out.println(cal.get(Calendar.SECOND)); 
        // Java 8 
        LocalDateTime dt = LocalDateTime.now(); 
        System.out.println(dt.getYear());  
        System.out.println(dt.getMonthValue()); // 1 - 12 
        System.out.println(dt.getDayOfMonth()); 
        System.out.println(dt.getHour()); 
        System.out.println(dt.getMinute()); 
        System.out.println(dt.getSecond()); 
    } 
} 

8. 如何取得從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在的毫秒數?

Calendar.getInstance().getTimeInMillis();  //第一種方式 
System.currentTimeMillis();  //第二種方式 
// Java 8 
Clock.systemDefaultZone().millis();  

9. 如何取得某月的最後一天?

//獲取當前月第一天: 
Calendar c = Calendar.getInstance();     
c.add(Calendar.MONTH, 0); 
c.set(Calendar.DAY_OF_MONTH,1);//設定為 1 號,當前日期既為本月第一天 
String first = format.format(c.getTime()); 6. System.out.println("===============first:"+first); 

//獲取當前月最後一天 
Calendar ca = Calendar.getInstance();    
ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH)); 
String last = format.format(ca.getTime()); 
System.out.println("===============last:"+last); 

//Java 8  
LocalDate today = LocalDate.now();  
//本月的第一天  
LocalDate firstday = LocalDate.of(today.getYear(),today.getMonth(),1);  
//本月的最後一天  
LocalDate lastDay =today.with(TemporalAdjusters.lastDayOfMonth());  
System.out.println("本月的第一天"+firstday);  
System.out.println("本月的最後一天"+lastDay); 

10. 如何格式化日期?

1)Java.text.DataFormat 的子類(如 SimpleDateFormat 類)中的 format(Date)方法可將日期格式化。

2)Java 8 中可以用 java.time.format.DateTimeFormatter 來格式化時間日期,程式碼如下所示:

import java.text.SimpleDateFormat; 
import java.time.LocalDate; 
import java.time.format.DateTimeFormatter; 
import java.util.Date; 

class DateFormatTest { 

    public static void main(String[] args) { 
        SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd"); 
        Date date1 = new Date(); 
        System.out.println(oldFormatter.format(date1)); 
        // Java 8 
        DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); 
        LocalDate date2 = LocalDate.now(); 
        System.out.println(date2.format(newFormatter)); 
    }
} 
補充 Java的時間日期API一直以來都是被詬病的東西,為了解決這一問題,Java 8中引入了新的時間日期API,其中包括 LocalDate、LocalTime、LocalDateTime、Clock、Instant 等類,這些的類的設計都使用了不變模式,因此是執行緒安全的設計。

11. 列印昨天的當前時刻?

import java.util.Calendar; 

class YesterdayCurrent { 
    public static void main(String[] args){ 
        Calendar cal = Calendar.getInstance(); 
        cal.add(Calendar.DATE, -1); 
        System.out.println(cal.getTime()); 
    } 
} 

//java-8 
import java.time.LocalDateTime;

class YesterdayCurrent { 
    public static void main(String[] args) { 
        LocalDateTime today = LocalDateTime.now(); 
        LocalDateTime yesterday = today.minusDays(1); 
        System.out.println(yesterday); 
    } 
} 

12. Java 8日期/時間特性?

Java 8日期/時間API是JSR-310 的實現,它的實現目標是克服舊的日期時間實現中所有的缺陷,新的日期/時間API的一些設計原則是:

  • 不變性:新的日期/時間API中,所有的類都是不可變的,這對多執行緒環境有好處。
  • 關注點分離:新的API將人可讀的日期時間和機器時間(unix timestamp)明確分離,它為日期(Date)、時間(Time)、日期時間(DateTime)、時間戳(unix timestamp)以及時區定義了不同的類。
  • 清晰:在所有的類中,方法都被明確定義用以完成相同的行為。舉個例子,要拿到當前例項我們可以使用now()方法,在所有的類中都定義了format()和parse()方法,而不是像以前那樣專門有一個獨立的類。為了更好的處理問題,所有的類都使用了工廠模式和策略模