Java基礎知識點面試手冊(上)

前言

本文快速回顧了Java中最基礎的知識點,用作面試復習,事半功倍。

參考

基礎知識點

面向對象的特性

答:封裝、繼承和多態。

多態分為編譯時多態和運行時多態。

  • 編譯時多態:方法的重載
  • 運行時多態:指程序中定義的對象引用所指向的具體類型在運行期間才確定。

運行時多態有三個條件

多態的存在有三個前提:

  • 要有繼承關系
  • 子類要重寫父類的方法
  • 父類引用指向子類對,

父類Animal

class Animal {
    int num = 10;
    static int age = 20;
    public void eat() {
        System.out.println("動物吃飯");
        }
    public static void sleep() {
        System.out.println("動物在睡覺");
        }
    public void run(){
        System.out.println("動物在奔跑");
       }
}

子類Cat

class Cat extends Animal {
    int num = 80;
    static int age = 90;
     String name = "tomCat";
    public void eat() {
        System.out.println("貓吃飯");
    }
    public static void sleep() {
        System.out.println("貓在睡覺");
    }
    public void catchMouse() {
        System.out.println("貓在抓老鼠");
    }
}

測試類Demo_Test1

class Demo_Test1 {
    public static void main(String[] args) {
    Animal am = new Cat();
    am.eat();
    am.sleep();
    am.run();
    //am.catchMouse();這里先注釋掉,等會會說明
     //System.out.println(am.name);//這里先注釋,待會說明
    System.out.println(am.num);
    System.out.println(am.age);
    }
}

以上的三段代碼充分體現了多態的三個前提,即:

1、存在繼承關系

Cat類繼承了Animal類

2、子類要重寫父類的方法

子類重寫(override)了父類的兩個成員方法eat(),sleep()。其中eat()是非靜態的,sleep()是靜態的(static)。

3、父類數據類型的引用指向子類對象。

如果再深究一點呢,我們可以看看上面測試類的輸出結果,或許對多態會有更深層次的認識。猜一猜上面
的結果是什么。

可以看出來

子類Cat重寫了父類Animal的非靜態成員方法am.eat();的輸出結果為:貓吃飯。

子類重寫了父類(Animal)的靜態成員方法am.sleep();的輸出結果為:動物在睡覺

未被子類(Cat)重寫的父類(Animal)方法am.run()輸出結果為:動物在奔跑

那么我們可以根據以上情況總結出多態成員訪問的特點:

成員變量
- 編譯看左邊(父類),運行看左邊(父類)
成員方法
- 編譯看左邊(父類),運行看右邊(子類)。動態綁定
靜態方法
- 編譯看左邊(父類),運行看左邊(父類)。

(靜態和類相關,算不上重寫,所以,訪問還是左邊的)
只有非靜態的成員方法,編譯看左邊,運行看右邊

那么多態有什么弊端呢?

不能使用子類特有的成員屬性和子類特有的成員方法。

參考:https://www.zhihu.com/question/30082151

很明顯,執行強轉語句Cat ct = (Cat)am;之后,ct就指向最開始在堆內存中創建的那個Cat類型的對象了。
這就是多態的魅力吧,雖然它有缺點,但是它確實十分靈活,減少多余對象的創建,不用說為了使用子類的某個方法又去重新再堆內存中開辟一個新的子類對象。

數據類型

String是類類型,不是基本類型。

基本類型 有八種:整型 (4種)字符型 (1種)浮點型 (2種)布爾型(1種)

[圖片上傳失敗...(image-5a07c5-1548051041893)]

[圖片上傳失敗...(image-d3c767-1548051041893)]

緩存池

public class Main_1 {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        System.out.println(c == d);//true,緩存池
        System.out.println(e == f);//false,不在緩存池
        System.out.println(c == (a + b));//true
        System.out.println(c.equals(a + b));//true
        System.out.println(g == (a + b));//true
        System.out.println(g.equals(a + b));//false
        System.out.println(g.equals(a + h));//true
    }

使用==的情況:

  • 如果比較Integer變量,默認比較的是地址值。
  • 特例:如果比較的某一邊有操作表達式(例如a+b),那么比較的是具體數值

使用equals()的情況:

  • 無論是Integer還是Long中的equals()默認比較的是數值
  • 特例:Long的equals()方法,JDK的默認實現:會判斷是否是Long類型

new Integer(123) 與 Integer.valueOf(123) 的區別在于:

  • new Integer(123) 每次都會新建一個對象
  • Integer.valueOf(123) 會使用緩存池中的對象,多次調用會取得同一個對象的引用。

valueOf() 方法的實現比較簡單,就是先判斷值是否在緩存池中,如果在的話就直接返回緩存池的內容。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

基本類型對應的緩沖池如下:

  • boolean values true and false
  • all byte values
  • short values between -128 and 127
  • int values between -128 and 127
  • char in the range \u0000 to \u007F

String

概覽

String 被聲明為 final,因此它不可被繼承。

在 Java 8 中,String 內部使用 char 數組存儲數據。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在 Java 9 之后,String 類的實現改用 byte 數組存儲字符串,同時使用 coder 來標識使用了哪種編碼。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;

    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

不可變的好處

1. 可以緩存 hash 值

因為 String 的 hash 值經常被使用,例如 String 用做 HashMap 的 key。不可變的特性可以使得 hash 值也不可變,因此只需要進行一次計算。

2. String Pool 的需要

如果一個 String 對象已經被創建過了,那么就會從 String Pool 中取得引用。只有 String 是不可變的,才可能使用 String Pool。

注意:不是string創建后就默認進入池的,請看下方intern()

在這里插入圖片描述

3. 安全性

String 經常作為參數,String 不可變性可以保證參數不可變。例如在作為網絡連接參數的情況下如果 String 是可變的,那么在網絡連接過程中,String 被改變,改變 String 對象的那一方以為現在連接的是其它主機,而實際情況卻不一定是。

4. 線程安全

String 不可變性天生具備線程安全,可以在多個線程中安全地使用。

Program Creek : Why String is immutable in Java?

String, StringBuffer and StringBuilder

1. 可變性

  • String 不可變
  • StringBuffer 和 StringBuilder 可變

2. 線程安全

  • String 不可變,因此是線程安全的
  • StringBuilder 不是線程安全的
  • StringBuffer 是線程安全的,內部使用 synchronized 進行同步

對于三者使用的總結:

1.如果要操作少量的數據用 String

2.單線程操作字符串緩沖區下操作大量數據 StringBuilder

3.多線程操作字符串緩沖區下操作大量數據 StringBuffer

String 和StringBuffer的區別?

String是immutable的,其內容一旦創建好之后,就不可以發生改變。

StringBuffer 是可以變長的,內容也可以發生改變
改變的原理是StringBuffer內部采用了字符數組存放數據,在需要增加長度的時候,創建新的數組,并且把原來的數據復制到新的數組這樣的辦法來實現。

https://blog.csdn.net/yeweiyang16/article/details/51755552

初始化:可以指定給對象的實體的初始容量為參數字符串s的長度額外再加16個字符

擴容:嘗試將新容量擴為大小變成原容量的1倍+2,然后if判斷一下 容量如果不夠,直接擴充到需要的容量大小。

StackOverflow : String, StringBuffer, and StringBuilder

String Pool

字符串常量池(String Pool)保存著所有字符串字面量(literal strings),這些字面量在編譯時期就確定。不僅如此,還可以使用 String 的 intern() 方法在運行過程中將字符串添加到 String Pool 中。

當一個字符串調用 intern() 方法時,如果 String Pool 中已經存在一個字符串和該字符串值相等(使用 equals() 方法進行確定),那么就會返回 String Pool 中字符串的引用;否則,就會在 String Pool 中添加一個新的字符串,并返回這個新字符串的引用。

下面示例中,s1 和 s2 采用 new String() 的方式新建了兩個不同字符串,而 s3 和 s4 是通過 s1.intern() 方法取得一個字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回這個字符串引用。因此 s3 和 s4 引用的是同一個字符串。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4);           // true

如果是采用 "bbb" 這種字面量的形式創建字符串,會自動地將字符串放入 String Pool 中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true

在 Java 7 之前,String Pool 被放在運行時常量池中,它屬于永久代。而在 Java 7,String Pool 被移到堆中。這是因為永久代的空間有限,在大量使用字符串的場景下會導致 OutOfMemoryError 錯誤。

new String("abc")

使用這種方式一共會創建兩個字符串對象(前提是 String Pool 中還沒有 "abc" 字符串對象)。

  • "abc" 屬于字符串字面量,因此編譯時期會在 String Pool 中創建一個字符串對象,指向這個 "abc" 字符串字面量;
  • 而使用 new 的方式會在堆中創建一個字符串對象。

創建一個測試類,其 main 方法中使用這種方式來創建字符串對象。

public class NewStringTest {
    public static void main(String[] args) {
        String s = new String("abc");
    }
}

使用 javap -verbose 進行反編譯,得到以下內容:

// ...
Constant pool:
// ...
   #2 = Class              #18            // java/lang/String
   #3 = String             #19            // abc
// ...
  #18 = Utf8               java/lang/String
  #19 = Utf8               abc
// ...

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: ldc           #3                  // String abc
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1
// ...

在 Constant Pool 中,#19 存儲這字符串字面量 "abc",#3 是 String Pool 的字符串對象,它指向 #19 這個字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中創建一個字符串對象,并且使用 ldc #3 將 String Pool 中的字符串對象作為 String 構造函數的參數。

以下是 String 構造函數的源碼,可以看到,在將一個字符串對象作為另一個字符串對象的構造函數參數時,并不會完全復制 value 數組內容,而是都會指向同一個 value 數組。

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

運算

參數傳遞

參數傳遞

Java 的參數是以值傳遞的形式傳入方法中,而不是引用傳遞。

以下代碼中 Dog dog 的 dog 是一個指針,存儲的是對象的地址。在將一個參數傳入一個方法時,本質上是將對象的地址以值的方式傳遞到形參中。因此在方法中使指針引用其它對象,那么這兩個指針此時指向的是完全不同的對象,在一方改變其所指向對象的內容時對另一方沒有影響。

public class Dog {

    String name;

    Dog(String name) {
        this.name = name;
    }

    String getName() {
        return this.name;
    }

    void setName(String name) {
        this.name = name;
    }

    String getObjectAddress() {
        return super.toString();
    }
}
public class PassByValueExample {
    public static void main(String[] args) {
        Dog dog = new Dog("A");
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        func(dog);
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        System.out.println(dog.getName());          // A
    }

    private static void func(Dog dog) {
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        dog = new Dog("B");
        System.out.println(dog.getObjectAddress()); // Dog@74a14482
        System.out.println(dog.getName());          // B
    }
}

如果在方法中改變對象的字段值會改變原對象該字段值,因為改變的是同一個地址指向的內容。

class PassByValueExample {
    public static void main(String[] args) {
        Dog dog = new Dog("A");
        func(dog);
        System.out.println(dog.getName());          // B
    }

    private static void func(Dog dog) {
        dog.setName("B");
    }
}

StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?

float 與 double

Java 不能隱式執行向下轉型,因為這會使得精度降低。

1.1 字面量屬于 double 類型,不能直接將 1.1 直接賦值給 float 變量,因為這是向下轉型。Java 不能隱式執行向下轉型,因為這會使得精度降低。

// float f = 1.1;

1.1f 字面量才是 float 類型。

float f = 1.1f;

隱式類型轉換

因為字面量 1 是 int 類型,它比 short 類型精度要高,因此不能隱式地將 int 類型下轉型為 short 類型。

short s1 = 1;
// s1 = s1 + 1;

但是使用 += 運算符可以執行隱式類型轉換。

s1 += 1;

上面的語句相當于將 s1 + 1 的計算結果進行了向下轉型:

s1 = (short) (s1 + 1);

StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?

switch

從 Java 7 開始,可以在 switch 條件判斷語句中使用 String 對象。

String s = "a";
switch (s) {
    case "a":
        System.out.println("aaa");
        break;
    case "b":
        System.out.println("bbb");
        break;
}

switch 不支持 long,是因為 switch 的設計初衷是對那些只有少數的幾個值進行等值判斷,如果值過于復雜,那么還是用 if 比較合適。

繼承

訪問權限

Java 中有三個訪問權限修飾符:private、protected 以及 public,如果不加訪問修飾符,表示包級可見。

可以對類或類中的成員(字段以及方法)加上訪問修飾符。

  • 類可見表示其它類可以用這個類創建實例對象。
  • 成員可見表示其它類可以用這個類的實例對象訪問到該成員;

protected 用于修飾成員,表示在繼承體系中成員對于子類可見,但是這個訪問修飾符對于類沒有意義。

如果子類的方法重寫了父類的方法,那么子類中該方法的訪問級別不允許低于父類的訪問級別:這是為了確保可以使用父類實例的地方都可以使用子類實例,也就是確保滿足里氏替換原則。

字段決不能是公有的,因為這么做的話就失去了對這個字段修改行為的控制,客戶端可以對其隨意修改。

例如下面的例子中,AccessExample 擁有 id 共有字段,如果在某個時刻,我們想要使用 int 去存儲 id 字段,那么就需要去修改所有的客戶端代碼。

public class AccessExample {
    public String id;
}

可以使用公有的 getter 和 setter 方法來替換公有字段,這樣的話就可以控制對字段的修改行為。

public class AccessExample {

    private int id;

    public String getId() {
        return id + "";
    }

    public void setId(String id) {
        this.id = Integer.valueOf(id);
    }
}

但是也有例外,如果是包級私有的類或者私有的嵌套類,那么直接暴露成員不會有特別大的影響。

public class AccessWithInnerClassExample {
    private class InnerClass {
        int x;
    }

    private InnerClass innerClass;

    public AccessWithInnerClassExample() {
        innerClass = new InnerClass();
    }

    public int getValue() {
        return innerClass.x;  // 直接訪問
    }
}

抽象類與接口

1. 抽象類

抽象類和普通類最大的區別是:

抽象類不能被實例化,需要繼承抽象類才能實例化其子類

public abstract class AbstractClassExample {

    protected int x;
    private int y;

    public abstract void func1();

    public void func2() {
        System.out.println("func2");
    }
}
public class AbstractExtendClassExample extends AbstractClassExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}

2. 接口

接口是抽象類的延伸,在 Java 8 之前,它可以看成是一個完全抽象的類,也就是說它不能有任何的方法實現。

從 Java 8 開始,接口也可以擁有默認的方法實現,這是因為不支持默認方法的接口的維護成本太高了。

在 Java 8 之前,如果一個接口想要添加新的方法,那么要修改所有實現了該接口的類。

接口的成員(字段 + 方法)默認都是 public 的,并且不允許定義為 private 或者 protected。

接口的字段默認都是 static 和 final 的。

public interface InterfaceExample {
    void func1();

    default void func2(){
        System.out.println("func2");
    }

    int x = 123;
    // int y;               // Variable 'y' might not have been initialized
    public int z = 0;       // Modifier 'public' is redundant for interface fields
    // private int k = 0;   // Modifier 'private' not allowed here
    // protected int l = 0; // Modifier 'protected' not allowed here
    // private void fun3(); // Modifier 'private' not allowed here
}
public class InterfaceImplementExample implements InterfaceExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}

3. 比較

  • 從設計層面上看,抽象類提供了一種 IS-A 關系,那么就必須滿足里式替換原則,即子類對象必須能夠替換掉所有父類對象。而接口更像是一種 LIKE-A 關系,它只是提供一種方法實現契約,并不要求接口和實現接口的類具有 IS-A 關系。
  • 從使用上來看,一個類可以實現多個接口,但是不能繼承多個抽象類。
  • 接口的字段只能是 static 和 final 類型的,而抽象類的字段沒有這種限制。
  • 接口的成員只能是 public 的,而抽象類的成員可以有多種訪問權限。

4. 使用選擇

使用接口:

  • 需要讓不相關的類都實現一個方法,例如不相關的類都可以實現 Compareable 接口中的 compareTo() 方法;
  • 需要使用多重繼承。

使用抽象類:

  • 需要在幾個相關的類中共享代碼。
  • 需要能控制繼承來的成員的訪問權限,而不是都為 public。
  • 需要繼承非靜態static和非常量final字段。

在很多情況下,接口優先于抽象類,因為接口沒有抽象類嚴格的類層次結構要求,可以靈活地為一個類添加行為。并且從 Java 8 開始,接口也可以有默認的方法實現,使得修改接口的成本也變的很低。

super

  • 訪問父類的構造函數:可以使用 super() 函數訪問父類的構造函數,從而委托父類完成一些初始化的工作。
  • 訪問父類的成員:如果子類重寫了父類的中某個方法的實現,可以通過使用 super 關鍵字來引用父類的方法實現。

Using the Keyword super

繼承相關小問題

接口是否可繼承接口?

可以,比如List 就繼承了接口Collection

抽象類是否可實現(implements)接口?

可以,比如 MouseAdapter鼠標監聽適配器 是一個抽象類,并且實現了MouseListener接口

抽象類是否可繼承實體類(concrete class)?

可以,所有抽象類,都繼承了Object

Object 通用方法

經典:

https://fangjian0423.github.io/2016/03/12/java-Object-method/

概覽


public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native Class<?> getClass()

protected void finalize() throws Throwable {}

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException
  • getClass方法

    • 返回當前運行時對象的Class對象
  • hashCode方法

    • 該方法返回對象的哈希碼,主要使用在哈希表中,比如JDK中的HashMap。(文章中有對hashcode的詳細解釋)
  • equals方法

    • 如果重寫了equals方法,通常有必要重寫hashCode方法,這點已經在hashCode方法中說明了。
  • clone方法

    • 創建并返回當前對象的一份拷貝。Object本身沒有實現Cloneable接口,所以不重寫clone方法并且進行調用的話會發生CloneNotSupportedException異常。
    • 使用 clone() 方法來拷貝一個對象即復雜又有風險,它會拋出異常,并且還需要類型轉換。Effective Java 書上講到,最好不要去使用 clone(),可以使用拷貝構造函數或者拷貝工廠來拷貝一個對象。
  • toString方法

    • Object對象的默認實現,即輸出類的名字@實例的哈希碼的16進制。
  • notify方法:

    • 喚醒一個在此對象監視器上等待的線程(監視器相當于就是鎖的概念)。
  • notifyAll方法

    • 跟notify一樣,唯一的區別就是會喚醒在此對象監視器上等待的所有線程,而不是一個線程。
  • wait(long timeout) throws InterruptedException方法

    • wait方法會讓當前線程等待直到另外一個線程調用對象的notify或notifyAll方法,或者超過參數設置的timeout超時時間。
  • wait(long timeout, int nanos) throws InterruptedException方法

    • 跟wait(long timeout)方法類似,多了一個nanos參數,這個參數表示額外時間(以毫微秒為單位,范圍是 0-999999)。 所以超時的時間還需要加上nanos毫秒。
  • wait() throws InterruptedException方法

    • 跟之前的2個wait方法一樣,只不過該方法一直等待,沒有超時時間這個概念。

    需要注意的是 wait(0, 0)和wait(0)效果是一樣的,即一直等待。

  • finalize方法

    • 該方法的作用是實例被垃圾回收器回收的時候觸發的操作,就好比 “死前的最后一波掙扎”。

補充:什么是Native Method

簡單地講,一個Native Method就是一個java調用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實現由非java語言實現,比如C。這個特征并非java所特有,很多其它的編程語言都有這一機制,比如在C++中,你可以用extern "C"告知C++編譯器去調用一個C的函數。

equals()

1. 等價關系

Ⅰ 自反性

x.equals(x); // true

Ⅱ 對稱性

x.equals(y) == y.equals(x); // true

Ⅲ 傳遞性

if (x.equals(y) && y.equals(z))
    x.equals(z); // true;

Ⅳ 一致性

多次調用 equals() 方法結果不變

x.equals(y) == x.equals(y); // true

Ⅴ 與 null 的比較

對任何不是 null 的對象 x 調用 x.equals(null) 結果都為 false

x.equals(null); // false;

2. 等價與相等

  • 對于基本類型,== 判斷兩個值是否相等,基本類型沒有 equals() 方法。
  • 對于引用類型,== 判斷兩個變量是否引用同一個對象,而 equals() 判斷引用的對象是否等價。
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y);      // false

3. 實現

  • 檢查是否為同一個對象的引用,如果是直接返回 true;
  • 檢查是否是同一個類型,如果不是,直接返回 false;
  • 將 Object 對象進行轉型;
  • 判斷每個關鍵域是否相等。
public class EqualExample {

    private int x;
    private int y;
    private int z;

    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EqualExample that = (EqualExample) o;

        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
}

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

因為hashCode()方法和equals()方法都可以通過自定義類重寫,是可以做到equals相同,但是hashCode不同的

但是,在Object類的equals()方法中有這么一段話

翻譯如下:

通常來講,在重寫這個方法的時候,也需要對hashCode方法進行重寫,
以此來保證這兩個方法的一致性——
當equals返回true的時候,這兩個對象一定有相同的hashcode.

hashCode()

hashCode() 返回散列值,而 equals() 是用來判斷兩個對象是否等價。等價的兩個對象散列值一定相同,但是散列值相同的兩個對象不一定等價。

在覆蓋 equals() 方法時應當總是覆蓋 hashCode() 方法,保證等價的兩個對象散列值也相等。

理想的散列函數應當具有均勻性,即不相等的對象應當均勻分布到所有可能的散列值上。這就要求了散列函數要把所有域的值都考慮進來。可以將每個域都當成 R 進制的某一位,然后組成一個 R 進制的整數。R 一般取 31,因為它是一個奇素數,如果是偶數的話,當出現乘法溢出,信息就會丟失,因為與 2 相乘相當于向左移一位。

一個數與 31 相乘可以轉換成移位和減法:31*x == (x<<5)-x,編譯器會自動進行這個優化。

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + x;
    result = 31 * result + y;
    result = 31 * result + z;
    return result;
}

toString()

默認返回 ToStringExample@4554617c 這種形式,其中 @ 后面的數值為散列碼的無符號十六進制表示。

public class ToStringExample {

    private int number;

    public ToStringExample(int number) {
        this.number = number;
    }
}
ToStringExample example = new ToStringExample(123);
System.out.println(example.toString());
ToStringExample@4554617c

clone()

1. cloneable

clone() 是 Object 的 protected 方法,它不是 public,一個類不顯式去重寫 clone(),其它類就不能直接去調用該類實例的 clone() 方法。

public class CloneExample {
    private int a;
    private int b;
}
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'

重寫 clone() 得到以下實現:

public class CloneExample {
    private int a;
    private int b;

    @Override
    public CloneExample clone() throws CloneNotSupportedException {
        return (CloneExample)super.clone();
    }
}
CloneExample e1 = new CloneExample();
try {
    CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
java.lang.CloneNotSupportedException: CloneExample

以上拋出了 CloneNotSupportedException,這是因為 CloneExample 沒有實現 Cloneable 接口。

應該注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一個 protected 方法。Cloneable 接口只是規定,如果一個類沒有實現 Cloneable 接口又調用了 clone() 方法,就會拋出 CloneNotSupportedException。

public class CloneExample implements Cloneable {
    private int a;
    private int b;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

2. 淺拷貝

拷貝對象和原始對象的引用類型引用同一個對象。

public class ShallowCloneExample implements Cloneable {

    private int[] arr;

    public ShallowCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected ShallowCloneExample clone() throws CloneNotSupportedException {
        return (ShallowCloneExample) super.clone();
    }
}
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 222

3. 深拷貝

拷貝對象和原始對象的引用類型引用不同對象。

public class DeepCloneExample implements Cloneable {

    private int[] arr;

    public DeepCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected DeepCloneExample clone() throws CloneNotSupportedException {
        DeepCloneExample result = (DeepCloneExample) super.clone();
        result.arr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            result.arr[i] = arr[i];
        }
        return result;
    }
}
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

4. clone() 的替代方案

使用 clone() 方法來拷貝一個對象即復雜又有風險,它會拋出異常,并且還需要類型轉換。Effective Java 書上講到,最好不要去使用 clone(),可以使用拷貝構造函數或者拷貝工廠來拷貝一個對象。

public class CloneConstructorExample {

    private int[] arr;

    public CloneConstructorExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public CloneConstructorExample(CloneConstructorExample original) {
        arr = new int[original.arr.length];
        for (int i = 0; i < original.arr.length; i++) {
            arr[i] = original.arr[i];
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

關鍵字

final

  • 修飾類:表示該類不能被繼承

  • 修飾方法:表示該方法不能被重寫

  • 修飾變量:表示該變量只能被賦值一次

    聲明數據為常量,可以是編譯時常量,也可以是在運行時被初始化后不能被改變的常量。

    • 對于基本類型,final 使數值不變;
    • 對于引用類型,final 使引用不變,表示該引用只有一次指向對象的機會,也就不能引用其它對象,但是被引用的對象本身是可以修改的。

finally

finally 是用于異常處理的場面,無論是否有異常拋出,都會執行

finalize

finalize是Object的方法,所有類都繼承了該方法。 當一個對象滿足垃圾回收的條件,并且被回收的時候,其finalize()方法就會被調用

static

1. 靜態變量

  • 靜態變量:又稱為類變量,也就是說這個變量屬于類的,類所有的實例都共享靜態變量,可以直接通過類名來訪問它;靜態變量在內存中只存在一份。
  • 實例變量:每創建一個實例就會產生一個實例變量,它與該實例同生共死。
public class A {
    private int x;         // 實例變量
    private static int y;  // 靜態變量

    public static void main(String[] args) {
        // int x = A.x;  // Non-static field 'x' cannot be referenced from a static context
        A a = new A();
        int x = a.x;
        int y = A.y;
    }
}

2. 靜態方法

靜態方法在類加載的時候就存在了,它不依賴于任何實例。所以靜態方法必須有實現,也就是說它不能是抽象方法(abstract)。

public abstract class A {
    public static void func1(){
    }
    // public abstract static void func2();  // Illegal combination of modifiers: 'abstract' and 'static'
}

只能訪問所屬類的靜態字段和靜態方法,方法中不能有 this 和 super 關鍵字。

public class A {
    private static int x;
    private int y;

    public static void func1(){
        int a = x;
        // int b = y;  // Non-static field 'y' cannot be referenced from a static context
        // int b = this.y;     // 'A.this' cannot be referenced from a static context
    }
}

3. 靜態語句塊

靜態語句塊在類初始化時運行一次。

public class A {
    static {
        System.out.println("123");
    }

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
    }
}
123

4. 靜態內部類

非靜態內部類依賴于外部類的實例,而靜態內部類不需要。

當一個內部類沒有使用static修飾的時候,是不能直接使用內部類創建對象,須要先使用外部類對象.new內部類對象及(外部類對象.new 內部類())

而靜態內部類只需要new OuterClass.InnerClass();

public class OuterClass {
    class InnerClass {
    }

    static class StaticInnerClass {
    }

    public static void main(String[] args) {
        // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
}

靜態內部類不能訪問外部類的非靜態的變量和方法。

5. 靜態導包

在使用靜態變量和方法時不用再指明 ClassName,從而簡化代碼,但可讀性大大降低。

import static com.xxx.ClassName.*

6. 初始化順序

存在繼承的情況下,初始化順序為:

  • 父類(靜態變量、靜態語句塊)
  • 子類(靜態變量、靜態語句塊)
  • 父類(實例變量、普通語句塊)
  • 父類(構造函數)
  • 子類(實例變量、普通語句塊)
  • 子類(構造函數)

反射

每個類都有一個 Class 對象,包含了與類有關的信息。當編譯一個新類時,會產生一個同名的 .class 文件,該文件內容保存著 Class 對象。

類加載相當于 Class 對象的加載,類在第一次使用時才動態加載到 JVM 中。也可以使用 Class.forName("com.mysql.jdbc.Driver") 這種方式來控制類的加載,該方法會返回一個 Class 對象。

反射可以提供運行時的類信息,并且這個類可以在運行時才加載進來,甚至在編譯時期該類的 .class 不存在也可以加載進來。

Class 和 java.lang.reflect 一起對反射提供了支持,java.lang.reflect 類庫主要包含了以下三個類:

  • Field :可以使用 get() 和 set() 方法讀取和修改 Field 對象關聯的字段;
  • Method :可以使用 invoke() 方法調用與 Method 對象關聯的方法;
  • Constructor :可以用 Constructor 創建新的對象。

反射的優點:

  • 可擴展性 :應用程序可以利用全限定名創建可擴展對象的實例,來使用來自外部的用戶自定義類。
  • 類瀏覽器和可視化開發環境 :一個類瀏覽器需要可以枚舉類的成員。可視化開發環境(如 IDE)可以從利用反射中可用的類型信息中受益,以幫助程序員編寫正確的代碼。
  • 調試器和測試工具 : 調試器需要能夠檢查一個類里的私有成員。測試工具可以利用反射來自動地調用類里定義的可被發現的 API 定義,以確保一組測試中有較高的代碼覆蓋率。

反射的缺點:

盡管反射非常強大,但也不能濫用。如果一個功能可以不用反射完成,那么最好就不用。在我們使用反射技術時,下面幾條內容應該牢記于心。

  • 性能開銷 :反射涉及了動態類型的解析,所以 JVM 無法對這些代碼進行優化。因此,反射操作的效率要比那些非反射操作低得多。我們應該避免在經常被執行的代碼或對性能要求很高的程序中使用反射。

  • 安全限制 :使用反射技術要求程序必須在一個沒有安全限制的環境中運行。如果一個程序必須在有安全限制的環境中運行,如 Applet,那么這就是個問題了。

  • 內部暴露 :由于反射允許代碼執行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會導致意料之外的副作用,這可能導致代碼功能失調并破壞可移植性。反射代碼破壞了抽象性,因此當平臺發生改變的時候,代碼的行為就有可能也隨著變化。

異常

運行時異常:

NullPointerException 空指針異常
ArithmeticException 算術異常,比如除數為零
ClassCastException 類型轉換異常
ConcurrentModificationException同步修改異常,遍歷一個集合的時候,刪除集合的元素,就會拋出該異常 
IndexOutOfBoundsException 數組下標越界異常
NegativeArraySizeException 為數組分配的空間是負數異常

一般異常又叫做可查異常(受檢異常),在編譯過程中,必須進行處理,要么捕捉,要么通過throws 拋出去.

比如FileNotFoundException
在這里插入圖片描述

Error和Exception有什么區別?

Error和Exception都實現了Throwable接口

Error指的是JVM層面的錯誤,比如內存不足OutOfMemoryError

Exception 指的是代碼邏輯的異常,比如下標越界OutOfIndexException

泛型

public class Box<T> {
    // T stands for "Type"
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

零散知識點

&和&&的區別

& 有兩個作用,分別是 位與 和 邏輯與

&& 就是邏輯與

長路與&:兩側,都會被運算

短路與&&:只要第一個是false,第二個就不進行運算了

heap和stack有什么區別

heap: 堆

stack: 棧

  • 存放的內容不一樣:

    • heap: 是存放對象的
    • stack: 是存放基本類型(int, float, boolean 等等)、引用(對象地址)、方法調用
  • 存取方式不一樣:

    • heap: 是自動增加大小的,所以不需要指定大小,但是存取相對較慢
    • stack: 先入后出的順序,并且存取速度比較快

獲取長度

數組獲取長度的手段是 .length 屬性

String獲取長度的手段是 length()方法

集合獲取長度的手段是 size()方法

文件獲取長度的手段是 length()方法

Set里的元素是不能重復的,那么用什么方法來區分重復與否呢

以HashSet為例,判斷重復的邏輯是:

  1. 首先看hashcode是否相同,如果不同,就是不重復的
  2. 如果hashcode一樣,再比較equals,如果不同,就是不重復的,否則就是重復的。

http://how2j.cn/k/collection/collection-hashcode/371.html#step2530

構造函數器/Constructor是否可被override?是否可以繼承String類?

子類不能繼承父類的構造方法,所以就不存在重寫父類的構造方法。

String是final修飾的,所以不能夠被繼承

try catch finally 執行順序

try里的return和finally里的return 都會支持,但是當前方法只會采納finally中return的值

https://www.cnblogs.com/superFish2016/p/6687549.html

總結以上測試:

1、finally語句總會執行

2、如果try、catch中有return語句,finally中沒有return,那么在finally中去修改除了包裝類型和靜態變量、全局變量以外的數據都不會對try、catch中返回的變量有任何的影響(包裝類型、靜態變量會改變、全局變量)。但是修改包裝類型和靜態變量、全局變量,會改變變量的值。

3、盡量不要在finally中使用return語句,如果使用的話,會忽略try、catch中的返回語句,也會忽略try、catch中的異常,屏蔽了錯誤的發生。

4、finally中避免再次拋出異常,一旦finally中發生異常,代碼執行將會拋出finally中的異常信息,try、catch中的異常將被忽略。

所以在實際項目中,finally常常是用來關閉流或者數據庫資源的,并不額外做其他操作。

char型變量中能不能存貯一個中文漢字?為什么?

char是16位的,占兩個字節

漢字通常使用GBK或者UNICODE編碼,也是使用兩個字節

所以可以存放漢字

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

可以包括多個類,但是只能出現一個public修飾的類,但是可以出現多個非public修飾的類。

java中有幾種類型的流?

Java中所有的流都是基于字節流,所以最基本的流是

輸入輸出字節流

InputStream

OutputStream

在字節流的基礎上,封裝了字符流

Reader

Writer

進一步,又封裝了緩存流

BufferedReader

PrintWriter

以及數據流

DataInputStream

DataOutputStream

對象流

ObjectInputStream

ObjectOutputStream

以及一些其他的奇奇怪怪的流 ~~~

什么是java序列化,如何實現java序列化?

序列化指的是把一個Java對象,通過某種介質進行傳輸,比如Socket輸入輸出流,或者保存在一個文件里。

實現java序列化的手段是讓該類實現接口 Serializable,這個接口是一個標識性接口,沒有任何方法,僅僅用于表示該類可以序列化。

JAVA序列化ID問題

https://blog.csdn.net/qq_35370263/article/details/79482993

虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清單 1 中,雖然兩個類的功能代碼完全一致,但是序列化 ID 不同,他們無法相互序列化和反序列化。

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

public class HelloWorld {
    public static void main(String[] args) {
           
        //打印單數    
        outloop: //outloop這個標示是可以自定義的比如outloop1,ol2,out5
        for (int i = 0; i < 10; i++) {
              
            for (int j = 0; j < 10; j++) {
                System.out.println(i+":"+j);
                if(0==j%2) 
                    break outloop; //如果是雙數,結束外部循環
            }
              
        }
          
    }
}

Anonymous Inner Class (匿名內部類) 是否可以extends(繼承)其它類,是否可以 implements(實現)interface(接口)?

匿名內部類本質上就是在繼承其他類,實現其他接口

如例:
匿名類1,就是繼承了Thread
匿名類2 ,就是實現了Runnable接口

package j2se;
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        // 匿名類1
        new Thread() {
            public void run() {
 
            }
        };
 
        // 匿名類2
        new Runnable() {
            public void run() {
 
            }
        };
 
    }
}

內部類可以引用外部類的成員嗎?有沒有什么限制?

可以使用

如果是非靜態內部類,可是使用外部類的所有成員

如果是靜態內部類,只能使用外部類的靜態成員

Class.forName的作用?為什么要用?

Class.forName常見的場景是在數據庫驅動初始化的時候調用。

Class.forName本身的意義是加載類到JVM中。 一旦一個類被加載到JVM中,它的靜態屬性就會被初始化,在初始化的過程中就會執行相關代碼,從而達到"加載驅動的效果"

JDK 中常用的包有哪些?

答:java.lang、java.util、java.io、java.net、java.sql。

集合

在這里插入圖片描述

容器主要包括 Collection 和 Map 兩種,Collection 又包含了 List、Set 以及 Queue。

Collection

Set

  • HashSet:基于哈希實現,支持快速查找,但不支持有序性操作,例如根據一個范圍查找元素的操作。并且失去了元素的插入順序信息,也就是說使用 Iterator 遍歷 HashSet 得到的結果是不確定的;

  • TreeSet:基于紅黑樹實現,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找時間復雜度為 O(1),TreeSet 則為 O(logN);

  • LinkedHashSet:具有 HashSet 的查找效率,且內部使用鏈表維護元素的插入順序。

List

  • ArrayList:基于動態數組實現,支持隨機訪問;

  • Vector:和 ArrayList 類似,但它是線程安全的;

  • LinkedList:基于雙向鏈表實現,只能順序訪問,但是可以快速地在鏈表中間插入和刪除元素。不僅如此,LinkedList 還可以用作棧、隊列和雙向隊列。

Queue

  • LinkedList:可以用它來支持雙向隊列;

  • PriorityQueue:基于堆結構實現,可以用它來實現優先隊列。

優先隊列是一種用來維護一組元素構成的結合S的數據結構,其中每個元素都有一個關鍵字key,元素之間的比較都是通過key來比較的。優先隊列包括最大優先隊列和最小優先隊列,優先隊列的應用比較廣泛,比如作業系統中的調度程序,當一個作業完成后,需要在所有等待調度的作業中選擇一個優先級最高的作業來執行,并且也可以添加一個新的作業到作業的優先隊列中。Java中,PriorityQueue的底層數據結構就是堆(默認是小堆)。

Map

  • HashMap:基于哈希實現;

  • HashTable:和 HashMap 類似,但它是線程安全的,這意味著同一時刻多個線程可以同時寫入 HashTable 并且不會導致數據不一致。它是遺留類,不應該去使用它。現在可以使用 ConcurrentHashMap 來支持線程安全,并且 ConcurrentHashMap 的效率會更高,因為 ConcurrentHashMap 引入了分段鎖。

  • LinkedHashMap:使用鏈表來維護元素的順序,順序為插入順序或者最近最少使用(LRU)順序。

  • TreeMap:基于紅黑樹實現。

Collection 和 Collections的區別

Collection是接口,是List和Set的父接口

Collections是工具類,提供了排序,混淆等等很多實用方法

List、Set 和 Map 的初始容量和加載因子

答:
加載因子的系數小于等于1,意指 即當 元素個數 超過 容量長度*加載因子的系數 時,進行擴容。

  1. List

ArrayList 的初始容量是 10;加載因子為 0.5; 擴容增量:原容量的 0.5 倍 +1;一次擴容后長度為 16。

Vector 初始容量為 10,加載因子是 1。擴容增量:原容量的 1 倍,如 Vector 的容量為 10,一次擴容后是容量為 20。

  1. Set

HashSet,初始容量為 16,加載因子為 0.75; 擴容增量:原容量的 1 倍; 如 HashSet 的容量為 16,一次擴容后容量為 32

  1. Map

HashMap,初始容量 16,加載因子為 0.75; 擴容增量:原容量的 1 倍; 如 HashMap 的容量為 16,一次擴容后容量為 32

Comparable 接口和 Comparator 接口有什么區別?

答:
詳細可以看:https://blog.csdn.net/u011240877/article/details/53399019

  • 對于一些普通的數據類型(比如 String, Integer, Double…),它們默認實現了Comparable 接口,實現了 compareTo 方法,我們可以直接使用。

  • 而對于一些自定義類,它們可能在不同情況下需要實現不同的比較策略,我們可以新創建 Comparator 接口,然后使用特定的 Comparator 實現進行比較。

對比:

  • Comparable簡單,但是如果需要重新定義比較類型時,需要修改源代碼。

  • Comparator不需要修改源代碼,自定義一個比較器,實現自定義的比較方法。

Java 集合的快速失敗機制 “fail-fast”

答:

它是 java 集合的一種錯誤檢測機制,當多個線程對集合進行結構上的改變的操作時,有可能會產生 fail-fast 機制。

可以知道在進行add,remove,clear等涉及到修改集合中的元素個數的操作時,modCount就會發生改變(modCount ++),所以當另一個線程(并發修改)或者同一個線程遍歷過程中,調用相關方法使集合的個數發生改變,就會使modCount發生變化

每當迭代器使用 hashNext()/next() 遍歷下一個元素之前,都會檢測 modCount 變量是否為 expectedmodCount 值,是的話就返回遍歷;否則拋出異常,終止遍歷。

解決辦法:

  1. 在遍歷過程中,所有涉及到改變 modCount 值得地方全部加上 synchronized;

  2. 使用 CopyOnWriteArrayList 來替換 ArrayList。

java.util.concurrent包中包含的并發集合類如下:

詳細:http://raychase.iteye.com/blog/1998965

ConcurrentHashMap

CopyOnWriteArrayList

CopyOnWriteArraySet
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容