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
}
}
補充:解答上面的面試題需要知道如下兩個知識點:
- String物件的intern()方法會得到字串物件在常量池中對應的版本的引用(如果常量池中有一個字串與 String 物件的 equals結果是 true),如果常量池中沒有對應的字串,則該字串將被新增到常量池中,然後返回常量池中字串的引用;
- 字串的+操作其本質是建立了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()方法,而不是像以前那樣專門有一個獨立的類。為了更好的處理問題,所有的類都使用了工廠模式和策略模