1. 程式人生 > 實用技巧 >【面試】Java基礎07

【面試】Java基礎07

【面試】Java基礎07

針對網上提出的常見的Java基礎面試題,在此做下學習筆記,方便後續複習檢視:

注:有些回答可能忘記標出參考出處,侵權請聯絡刪除:-)

  1. & 和 && 的區別?
  2. 兩個二進位制數的異或結果是什麼?
  3. 如何實現物件的克隆?深克隆和淺克隆的區別?
  4. 什麼是 Java 的序列化,如何實現 Java 的序列化?什麼情況下需要序列化?
  5. 為什麼 wait/notify 方法放在 Object 類中而不是 Thread 類中?

31. & 和 && 的區別?

​ Java 中 && 和 & 都是表示與的邏輯運算子,都表示邏輯運輸符 and,當兩邊的表示式都為 true 的時候,整個運算結果才為 true,否則為 false。

&&:有短路功能,當第一個表示式的值為 false 的時候,則不再計算第二個表示式

@Test
public void test02(){
    String str = "a";
    int i = 0;
    if(str!="a" && (i++)==1){
        ;
    }
    System.out.println(i);  // 由於短路緣故,i=0
    if(str!="a" & (i++)==1){
        ;
    }
    System.out.println(i);  // 還是執行i++,故i=1
}

&:不管第一個表示式結果是否為 true,第二個都會執行。除此之外,& 還可以用作位運算子:當 & 兩邊的表示式不是 Boolean 型別的時候,& 表示按位操作。

// 位運算
@Test
public void test03(){
    int a = 1;  // 01
    int b = 3;  // 11
    int c = a & b;  // 01&11=01
    System.out.println(c);
}

32. 兩個二進位制數的異或結果是什麼?

兩個二進位制數異或結果是這兩個二進位制數差的絕對值。表示式如下:a^b = |a-b|

兩個二進位制 a 與 b 異或,即 a 和 b 兩個數按位進行運算。如果對應的位相同,則為 0(相當於對應的算術相減),如果不同即為 1(相當於對應的算術相加)。由於二進位制每個位只有兩種狀態,要麼是 0,要麼是 1,則按位異或操作可表達為按位相減取絕對值,再按位累加。

例如3和5進行異或
3:0011  ^  5:0101 ---> 按位相減取絕對值,再按位累加
0-0=0
0-1=-1  ---> 取絕對值為1
1-0=1
1-1=0
---> 按位累加:0110=6

33. 如何實現物件的克隆?深克隆和淺克隆的區別?

如何實現物件的克隆?

  1. 將A物件的值分別通過set方法加入B物件中;

    Student stu1 = new Student();
    stu1.setName("張三");
    Student stu2 = new Student();
    stu2.setName(stu1.getName());
    
  2. 實現 Cloneable 介面並重寫 Object 類中的 clone() 方法;

    • 淺克隆:

      • 需要複製的類實現 Cloneable 介面(該介面為標記介面,介面中無任何方法)
      • 覆蓋 clone() 方法,方法中呼叫super.clone()方法得到需要的複製物件。
      public class Student implements Cloneable {
          private int name;
      
          public int getName() {
              return name;
          }
      
          public void setName(int name) {
              this.name = name;
          }
      
          @Override
          protected Object clone() throws CloneNotSupportedException {
              return super.clone();
          }
      }
      
      //測試
      Student stu1 = new Student();
      stu1.setName("張三");
      Student stu2 = (Student) stu1.clone();
      stu2.setName("李四");
      
    • 深克隆

      • 通過覆蓋Object類的clone()方法可以實現深克隆,將clone() 方法的修飾符修改為 public
  3. 實現 Serializable 介面,通過物件的序列化和反序列化實現克隆,可以實現真正的深克隆;

    • 序列化就是將物件寫到流的過程,寫到流中的物件是原有物件的一個拷貝,而原物件仍然存在於記憶體中。
    • 通過序列化實現的拷貝不僅可以複製物件本身,而且可以複製其引用的成員物件,因此通過序列化將物件寫到一個流中,再從流裡將其讀出來,可以實現深克隆
  4. 工具類BeanUtils和PropertyUtils進行物件複製

    有待驗證

深克隆和淺克隆的區別?

  1. 淺克隆:拷貝物件和原始物件的引用型別引用同一個物件。淺克隆只是複製了物件的引用地址,兩個物件指向同一個記憶體地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺克隆。

    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();   //淺複製  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }   
        return stu;  
    }  
    
  2. 深克隆:拷貝物件和原始物件的引用型別引用不同物件。深拷貝是將物件及值複製過來,兩個物件修改其中任意的值另一個值不會改變,這就是深拷貝。

    // 簡單來說,在深克隆中,除了物件本身被複制外,物件所包含的所有成員變數也將複製。
    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();   //淺克隆
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        stu.addr = (Address)addr.clone();   //深克隆
        return stu;  
    }  
    

克隆的補充:

​ 深克隆的實現就是在引用型別所在的類實現 Cloneable 介面,並使用 public 訪問修飾符重寫 clone 方法

​ Java 中定義的 clone 沒有深淺之分,都是統一的呼叫 Object 的 clone 方法。為什麼會有深克隆的概念?是由於我們在實現的過程中刻意的嵌套了 clone 方法的呼叫。也就是說深克隆就是在需要克隆的物件型別的類中重新實現克隆方法 clone()。

參考:https://blog.csdn.net/ztchun/article/details/79110096

34. 什麼是 Java 的序列化,如何實現 Java 的序列化?什麼情況下需要序列化?

概念:

序列化:把Java物件轉換為位元組序列的過程。

反序列化:把位元組序列恢復為Java物件的過程。

解釋:

​ 物件序列化是一個用於將物件狀態轉換為位元組流的過程,可以將其儲存到磁碟檔案中或通過網路傳送到任何其他程式。從位元組流建立物件的相反的過程稱為反序列化。而建立的位元組流是與平臺無關的,在一個平臺上序列化的物件可以在不同的平臺上反序列化。序列化是為了解決在物件流進行讀寫操作時所引發的問題。

​ 序列化的實現:將需要被序列化的類實現 Serializable 介面,該介面沒有需要實現的方法,只是用於標註該物件是可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個 ObjectOutputStream 物件,接著使用 ObjectOutputStream 物件的 writeObject(Object obj) 方法可以將引數為 obj 的物件寫出,要恢復的話則使用輸入流。

序列化/反序列化操作

public class Person implements Serializable{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

// 序列化/反序列化物件
public class SerializableTest {

    public static void main(String[] args) throws Exception {

        Person person = new Person("王麻子", 40);

        // 序列化物件
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./data.obj"));
        out.writeObject("你好!");      //寫入字面值常量
        out.writeObject(new Date());   //寫入匿名Date物件
        out.writeObject(person);       //寫入person物件
        out.close();
        //反序列化物件
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("./data.obj"));
        System.out.println("obj1:" + (String)in.readObject());  //讀取字面值常量
        System.out.println("obj2:" + (Date)in.readObject());  //讀取匿名Date物件
        System.out.println("obj3:" + (Person)in.readObject());  //讀取person物件
        in.close();

    }
}

// 結果:
// obj1:你好!
// obj2:Thu Aug 20 22:02:51 CST 2020
// obj3:Person{name='王麻子', age=40}

什麼情況下需要序列化?

  1. 當你想把的記憶體中的物件狀態儲存到一個檔案中或者資料庫中時候;

  2. 當你想用套接字在網路上傳送物件的時候;

    • 當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種型別的資料,都會以二進位制序列的形式在網路上傳送。傳送方需要把這個Java物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為Java物件。
    • 只能將支援 java.io.Serializable 介面的物件寫入流中。每個 serializable 物件的類都被編碼,編碼內容包括類名和類簽名、物件的欄位值和陣列值,以及從初始物件中引用的其他所有物件的閉包
  3. 當你想通過 RMI 傳輸物件的時候。

    Remote Method Invocation,遠端方法呼叫

    https://www.cnblogs.com/grefr/p/5046313.html

參考:https://www.cnblogs.com/shoshana-kong/p/10538661.html

35. 為什麼 wait/notify 方法放在 Object 類中而不是 Thread 類中?

一個很明顯的原因是 Java 提供的鎖是物件級的而不是執行緒級的,每個物件都有鎖,通過執行緒獲得。如果執行緒需要等待某些鎖,那麼呼叫物件中的 wait() 方法就有意義了。如果 wait() 方法定義在 Thread 類中,執行緒正在等待的是哪個鎖就不明顯了。簡單的說,由於 wait,notify 和 notifyAll 都是鎖級別的操作,所以把他們定義在 Object 類中因為鎖屬於物件。

參考:

https://blog.csdn.net/pulusite/article/details/82287462

https://blog.csdn.net/zl1zl2zl3/article/details/106695410