Java建立對象的幾種方式

前言:原先一直沒怎麼關注Java建立對象的方式,由於基本上只知道用new😂,直到58面試官問我Java有哪些方式能夠建立對象。因此稍微整理了一下幾種建立對象的方式。java

1.使用new關鍵字建立對象

這是Java中最經常使用,且簡單的方式建立對象。其實這種方式是經過調用構造函數(有參、無參……)建立對象的。如:String name = new String("hello"); 那執行這條語句時JVM作了什麼?面試

  1. 首先在方法區的常量池中查看是否有new 後面參數(也就是類名)的符號引用,並檢查是否有類的加載信息也就是是否被加載解析和初始化過。若是已經加載過了就不在加載,不然執行類的加載全過程。
  2. 加載完類後,大體作了以下三件事:

    a、給實例分配內存:此內存中存放對象本身的實例變量和從父類繼承過來的實例變量(即便這些從超類繼承過來的實例變量有可能被隱藏也會被分配空間),同時這些實例變量被賦予默認值(零值); b、調用構造函數,初始化成員字段:在Java對象初始化過程當中,主要涉及三種執行對象初始化的結構,分別是實例變量初始化實例代碼塊初始化以及構造函數初始化; c、user對象指向分配的內存空間: 注意:new操做不是原子操做,b和c的順序可能會調換。express

2.使用clone方法建立對象

顧名思義,clone就是克隆,也就是複製;使用某個對象的clone()方法時(前提是此對象的對應的類中已經實現clone方法),JVM根據被拷貝的對象分配內存、建立新的對象,而後會把被clone的對象的值全都拷貝進去;clone()方法是屬於Object類的,clone是在堆內存中用二進制的方式進行拷貝,從新分配給對象一塊內存;Object類的clone方法是一個native方法,它的註釋中寫着:Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object. The general intent is that, for any object {@code x}, the expression:安全

  1. 要想讓一個對象支持clone,必須讓這個對象對應的類實現Cloneable接口(標識接口),同時此類中也要重寫clone方法;其實Cloneable接口至關於一個合法的證實,代表此類能夠合法的進行clone;Cloneable接口的註釋中寫着:A class implements the Cloneable interface to indicate to the {@link java.lang.Object#clone()} method that it is legal for that method to make a field-for-field copy of instances of that class.
  2. Object類的clone()方法是線程不安全的;
  3. clone()有淺拷貝和深拷貝兩種模式;

    淺拷貝是拷貝被拷貝對象的值(表層),若被拷貝對象的屬性有引用類型的,則只拷貝引用的地址;深拷貝是拷貝被拷貝對象的全部值(深層),如有被拷貝對象有引用類型的屬性,則也要拷貝其引用類型的屬性所對應的對象;深拷貝還有完全深拷貝和未完全深拷貝等狀況,其實完全深拷貝是很難的;bash

clone詳情可查閱大佬博客:詳解Java中的clone方法 舉個簡單的clone對象的例子:網絡

public class Animal implements Cloneable{
    private String name = null;
    private int age = 0;

    public Animal(){ }

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

    public String getName(){
        return name;
    }

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

    public int getAge(){
        return age;
    }

    public void setAge(int age){
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Animal animal = (Animal) super.clone();
        animal.name = new String(name);
        return animal;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Animal dog = new Animal("Dog", 1);
        Animal smallDog = (Animal)dog.clone();
        System.out.println("dog:" + dog.getName() + ", " + dog.getAge());
        System.out.println("smallDog:" + smallDog.getName() + ", " + smallDog.getAge());
        System.out.println(dog);
        System.out.println(smallDog);
    }
}
複製代碼

程序運行的結果爲: dog:Dog, 1 smallDog:Dog, 1 createobject.Animal@4554617c createobject.Animal@74a14482 可見,兩個對象的屬性值同樣,而地址不同,因此clone dog對象成功;app

3.使用反射建立對象

3.1Java反射機制

首先解釋一下什麼是Java的反射機制;Java 反射機制在程序運行時,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性。這種動態的獲取信息以及動態調用對象的方法的功能稱爲java 的反射機制。 反射機制很重要的一點就是「運行時」,其使得咱們能夠在程序運行時加載、探索以及使用編譯期間徹底未知的 .class 文件。換句話說,Java 程序能夠加載一個運行時才得知名稱的 .class 文件,而後獲悉其完整構造,並生成其對象實體、或對其 fields(變量)設值、或調用其 methods(方法)。ide

3.2使用反射建立對象

主要包括兩個步驟:函數

  1. 獲取類的Class對象實例,獲取方式主要有:
    • Class.forName("類全路徑");
    • 類名.class; 如:Animal.class;
    • 對象名.getClass();
  2. 經過反射建立類對象的實例對象;獲取Class對象實例後,能夠經過Java反射機制建立類對象實例對象;主要有兩種方式:
    • Class.newInstance():調用無參的構造方法,必需確保類中有無參數的可見的構造函數,不然將會拋出異常;
    • 調用類對象的構造方法:
  3. 強制轉換成用戶所需類型;

舉個簡單的例子:測試

public class Apple {
    private int price;
    protected String color;
    public String name;

    public Apple(){}

    public Apple(String name, int price, String color){
        this.name = name;
        this.price = price;
        this.color = color;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price){
        this.price = price;
    }

    public String getColor(){
        return color;
    }

    public void setColor(String color){
        this.color = color;
    }

    public String getName(){
        return name;
    }

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

    public String printMsg(){
        return "name:" + name + ", price:" + price + ", color:" + color;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //正常new對象
        Apple apple1 = new Apple("Apple1", 6666, "red");
        System.out.println("Apple1:" + apple1);
        System.out.println("Apple1: " + apple1.printMsg());
        
        //使用反射建立對象
        //獲取類的Class對象實例
        Class cls = Class.forName("reflect.Apple");
        //根據Class對象實例獲取Constructor對象
        Constructor appleConstructor = cls.getConstructor();
        //根據Constructor對象的newInstance方法獲取反射類對象
        Object appleObject = appleConstructor.newInstance();
        //強制類型轉換
        Apple apple2 = (Apple)appleObject;
        System.out.println("Apple2:" + apple2);
//        System.out.println("Apple2:" + appleObject);
        System.out.println("Apple2: " + apple2.printMsg());
    }
}
複製代碼

運行結果爲: Apple1:reflect.Apple@4554617c Apple1: name:Apple1, price:6666, color:red Apple2:reflect.Apple@74a14482 Apple2: name:null, price:0, color:null apple1和apple2保存的地址不同,成功反射一個Apple對象,反射時使用的是無參構造函數,因此apple2引用指向的對象屬性值爲默認值;關於反射詳細的內容後續單獨寫篇文章介紹;

4.使用反序列化建立對象

  1. 序列化:

    提及反序列化,首先得介紹序列化;什麼是序列化呢?咱們把變量從內存中變成可存儲或傳輸的過程稱之爲序列化(廖雪峯老師官網給出的解釋),其實就是把對象寫入IO流中;Java中要序列化的類必須實現Serializable接口;

  2. 序列化場景:

    • 全部可在網絡上傳輸的對象都必須是可序列化的;如RMI(remote method invoke,即遠程方法調用),傳入的參數或返回的對象都是可序列化的,不然會出錯;
    • 全部須要保存到磁盤的java對象都必須是可序列化的;一般建議:程序建立的每一個JavaBean類都實現Serializeable接口;
  3. 序列化與反序列化實現方式:序列化的對象所對應的類必須實現Serializable接口或Externalizable接口;

    • Serializable接口序列化舉例:Serializable接口是一個標記接口,不用實現任何方法。一旦實現了此接口,該類的對象就是可序列化的;固然還有Externalizable接口序列化方式,詳細的狀況另行介紹;
    • 反序列化:從IO流中恢復對象
import java.io.Serializable;

/**
 * 需序列化的對象對應的類
 */
public class Person implements Serializable {
    private String name;
    private int age;
    //沒有無參構造方法
    public Person(String name, int age){
//        System.out.println("反序列化,你調用我了麼?");
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString(){
        return "Person{ " + "name = '" + name + '\'' + ", age = " + age + '}'; } } 複製代碼
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

/**
 * 序列化步驟:
 * ①建立一個ObjectOutputStream輸出流
 * ②調用ObjectOutputStream對象的writeObject輸出可序列化對象
 */
public class WriteObject {
    public static void main(String[] args){
        try{
            //建立ObjectOutputStream輸出流
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person.txt"));
            //將對象序列化到文件s
            Person person = new Person("馮澍瀅", 25);
            objectOutputStream.writeObject(person);
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println("success~");
    }
}
複製代碼
import java.io.FileInputStream;
import java.io.ObjectInputStream;

/**
 * 反序列化步驟:
 * ①建立一個ObjectInputStream輸入流
 * ②調用ObjectInputStream對象的readObject()獲得序列化的對象
 */
public class ReadObject {
    public static void main(String[] args){
        try{
            //建立一個ObjectInputStream輸入流
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person.txt"));
            Person person = (Person)objectInputStream.readObject();
            System.out.println(person);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
複製代碼

反序列化時能夠在對應類的構造方法中添加輸出語句測試反序列化時有沒有調用構造方法;結果代表,反序列化並不會調用構造方法;反序列的對象是由JVM本身生成的對象,不經過構造方法生成;

相關文章
相關標籤/搜索