ตัวอย่างการสร้าง object โดยใช้ Class.newInstance()

จินตนาการว่าคุณได้รับมอบหมายให้สร้างวัตถุโดยใช้การสะท้อน เรามาเริ่มกันเลยดีไหม?

เราจะเริ่มต้นด้วยการเขียนโค้ดสำหรับคลาสที่เราต้องการยกตัวอย่าง:

public class Employee {
    private String name;
    private String lastName;
    private int age;

    {
        age = -1;
        name = "Rob";
        surname = "Stark";
    }

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

    public String getName() {
        return name;
    }

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

    public String getSurname() {
        return lastName;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public int getAge() {
        return age;
    }

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

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

นี่จะเป็นคลาสของเรา — ที่มีหลายฟิลด์, ตัวสร้างพร้อมพารามิเตอร์, ตัวรับและตัวตั้ง, เมธอด toString()และบล็อกการเริ่มต้น ทีนี้ มาดูส่วนที่สองกัน: การสร้างวัตถุโดยใช้การสะท้อน วิธีแรก ที่เราจะดูจะใช้Class.newInstance()

public class Main {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Employee employee = Employee.class.newInstance();
        System.out.println("age is " + employee.getAge());
    }
}

ยอดเยี่ยม! เรียกใช้โค้ดของเราและคอยดูอายุที่จะแสดง แต่เราได้รับข้อผิดพลาดเกี่ยวกับตัวสร้างเริ่มต้นที่ขาดหายไป ปรากฎว่าวิธีนี้ช่วยให้เราสร้างวัตถุโดยใช้ตัวสร้างเริ่มต้นเท่านั้น มาเพิ่มตัวสร้างเริ่มต้นสำหรับคลาสของเราและทดสอบรหัสอีกครั้ง

ข้อความผิดพลาด:

รหัสของตัวสร้างใหม่

public Employee() { }

หลังจากเพิ่มตัวสร้าง นี่คือผลลัพธ์:

อายุ 1 ขวบ

ยอดเยี่ยม! เราพบว่าวิธีนี้ใช้ได้ผล ทีนี้มาดูใต้ฝากระโปรงกัน เมื่อเปิดเอกสารประกอบ เราพบว่าวิธีการของเราเลิกใช้ แล้ว :

นอกจากนี้ยังสามารถโยนInstantiationExceptionและIllegalAccessExceptionได้ อีกด้วย ดังนั้น เอกสารจึงแนะนำให้เราใช้วิธีอื่นใน การสร้างวัตถุ นั่นคือConstructor.newInstance() มาวิเคราะห์โดยละเอียดว่า คลาส Constructorทำงาน อย่างไร

เมธอด getConstructors และ getDeclaredConstructors

ในการทำงานกับ คลาส Constructorก่อนอื่นเราต้องได้รับอินสแตนซ์ เรามีสองวิธีสำหรับสิ่งนี้: getConstructorsและgetDeclaredConstructors

ตัวแรกส่งคืนอาร์เรย์ของตัวสร้างสาธารณะ และตัวที่สองส่งคืนอาร์เรย์ของตัวสร้างคลาสทั้งหมด

ให้ความเป็นส่วนตัวแก่ชั้นเรียนของเรา หรือสร้างตัวสร้างส่วนตัวเพื่อช่วยสาธิตวิธีการทำงานของเมธอดเหล่านี้

มาเพิ่มตัวสร้างส่วนตัว:

private Employee(String name, String surname) {
    this.name = name;
    this.lastName = lastName;
}

ดูโค้ด โปรดทราบว่าตัวสร้างตัวใดตัวหนึ่งเป็นแบบส่วนตัว:

มาทดสอบวิธีการของเรากัน:

public class Main {
	  public static void main(String[] args) {
	      Class employeeClass = Employee.class;

	      System.out.println("getConstructors:");
	      printAllConstructors(employeeClass);

	      System.out.println("\n" +"getDeclaredConstructors:");
	      printDeclaredConstructors(employeeClass);
	  }

	  static void printDeclaredConstructors(Class<?> c){
	      for (Constructor<?> constructor : c.getDeclaredConstructors()) {
	          System.out.println(constructor);
	      }
	  }

	  static void printAllConstructors(Class<?> c){
	      for (Constructor<?> constructor : c.getConstructors()) {
	          System.out.println(constructor);
	      }
	  }
}

และเราได้ผลลัพธ์นี้:

getConstructors:
สาธารณะ com.codegym.Employee(java.lang.String,java.lang.String,int)
public.com.codegym.Employee()

getDeclaredConstructors:
ส่วนตัว com.codegym.Employee(java.lang.String,java.lang .String)
สาธารณะ com.codegym.Employee(java.lang.String,java.lang.String,int)
สาธารณะ com.codegym.Employee()

โอเค นี่คือวิธีที่เราเข้าถึงวัตถุตัวสร้าง ตอนนี้เราสามารถพูดคุยเกี่ยวกับสิ่งที่สามารถทำได้

คลาส java.lang.reflect.Constructor และเมธอดที่สำคัญที่สุด

มาดูวิธีการที่สำคัญที่สุดและวิธีการทำงาน:

วิธี คำอธิบาย
รับชื่อ () ส่งคืนชื่อของตัวสร้างนี้เป็นสตริง
getModifiers() ส่งคืนตัวแก้ไขการเข้าถึง Java ที่เข้ารหัสเป็นตัวเลข
getExceptionTypes() ส่งคืนอาร์เรย์ของวัตถุคลาสที่แสดงถึงประเภทของข้อยกเว้นที่ประกาศโดยตัวสร้าง
รับพารามิเตอร์ () ส่งกลับอาร์เรย์ของ วัตถุ พารามิเตอร์ที่เป็นตัวแทนของพารามิเตอร์ทั้งหมด ส่งกลับอาร์เรย์ของความยาว 0 ถ้าตัวสร้างไม่มีพารามิเตอร์
getParameterTypes() ส่งคืนอาร์เรย์ของวัตถุคลาสที่แสดงถึงประเภทพารามิเตอร์ที่เป็นทางการในลำดับการประกาศ
getGenericParameterTypes() ส่งคืนอาร์เรย์ของวัตถุ Type ที่แสดงถึงประเภทพารามิเตอร์ที่เป็นทางการในลำดับการประกาศ

getName() & getModifiers()

มารวมอาร์เรย์ของเราไว้ในรายการเพื่อให้สะดวกในการทำงาน เราจะเขียนเมธอดgetNameและgetModifiers ด้วย:

static List<Constructor<?>> getAllConstructors(Class<?> c) {
    return new ArrayList<>(Arrays.asList(c.getDeclaredConstructors()));
}

static List<String> getConstructorNames(List<Constructor<?>> constructors) {
    List<String> result = new ArrayList<>();
    for (Constructor<?> constructor : constructors) {
        result.add(constructor.toString());
    }
    return result;
}

static List<String> getConstructorModifiers(List<Constructor<?>> constructors) {
    List<String> result = new ArrayList<>();
    for (Constructor<?> constructor : constructors) {
        result.add(Modifier.toString(constructor.getModifiers()));
    }
    return result;
}

และ วิธีการ หลัก ของเรา ที่เราจะเรียกทุกอย่าง:

public static void main(String[] args) {
    Class employeeClass = Employee.class;
    var constructors = getAllConstructors(employeeClass);
    var constructorNames = getConstructorNames(constructors);
    var constructorModifiers = getConstructorModifiers(constructors);

    System.out.println("Employee class:");
    System.out.println("Constructors :");
    System.out.println(constructorNames);
    System.out.println("Modifiers :");
    System.out.println(constructorModifiers);
}

และตอนนี้เราเห็นข้อมูลทั้งหมดที่เราต้องการแล้ว:

คลาสพนักงาน:
ตัวสร้าง:
[ส่วนตัว com.codegym.Employee(java.lang.String), สาธารณะ
com.codegym.Employee(java.lang.String,java.lang.String,int), สาธารณะ com.codegym.Employee() ]
ตัวดัดแปลง :
[ส่วนตัว, สาธารณะ, สาธารณะ]

getExceptionTypes()

วิธีนี้ช่วยให้เราได้รับอาร์เรย์ของข้อยกเว้นที่ตัวสร้างของเราอาจโยนทิ้ง ลองแก้ไขหนึ่งในคอนสตรัคเตอร์ของเราแล้วเขียนเมธอดใหม่

ที่นี่เราเปลี่ยนตัวสร้างปัจจุบันเล็กน้อย:

private Employee(String name, String surname) throws Exception {
    this.name = name;
    this.lastName = lastName;
}

และที่นี่เรามีวิธีการรับประเภทข้อยกเว้นและเพิ่มไปยังmain :

static List<Class<?>> getConstructorExceptionTypes(Constructor<?> c) {
      return new ArrayList<>(Arrays.asList(c.getExceptionTypes()));
}


var constructorExceptionTypes = getConstructorExceptionTypes(constructors.get(0));
System.out.println("Exception types :");
System.out.println(constructorExceptionTypes);

ด้านบน เราเข้าถึงตัวสร้างแรกในรายการของเรา เราจะหารือเกี่ยวกับวิธีรับตัวสร้างที่เฉพาะเจาะจงในภายหลัง

และดูผลลัพธ์หลังจากที่เราเพิ่มการโยนข้อยกเว้น :

ประเภทข้อยกเว้น:
[คลาส java.lang.Exception]

และก่อนที่จะเพิ่มข้อยกเว้น:

ประเภทข้อยกเว้น :
[]

ทุกอย่างยอดเยี่ยม แต่เราจะทราบได้อย่างไรว่าตัวสร้างของเราต้องการพารามิเตอร์ใด ลองหาสิ่งนี้เช่นกัน

getParameters() & getParameterTypes() & getGenericParameterTypes()

มาเริ่มกันใหม่โดยการปรับแต่งคอนสตรัคเตอร์ส่วนตัวของเรา ตอนนี้จะมีลักษณะดังนี้:

private Employee(String name, String surname, List<String> list) {
    this.name = name;
    this.lastName = lastName;
}

และเรามีสามวิธีเพิ่มเติม: getParameters สำหรับ รับลำดับของพารามิเตอร์และประเภทของพารามิเตอร์, getParameterTypesสำหรับรับประเภทพารามิเตอร์ และgetGenericParameterTypesสำหรับรับประเภทที่รวมอยู่ในgenerics

static List<Parameter> getConstructorParameters(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getParameters()));
}

static List<Class<?>> getConstructorParameterTypes(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getParameterTypes()));
}

static List<Type> getConstructorParametersGenerics(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getGenericParameterTypes()));
}

และเราเพิ่มข้อมูลเพิ่มเติมให้กับ วิธี การหลัก ของเราที่ไม่น้อยอยู่แล้ว :

var constructorParameterTypes = getConstructorParameterTypes(constructors.get(0));
var constructorParameters = getConstructorParameters(constructors.get(0));
var constructorParametersGenerics = getConstructorParametersGenerics(constructors.get(0));

System.out.println("Constructor parameters :");
System.out.println(constructorParameters);

System.out.println("Parameter types :");
System.out.println(constructorParameterTypes);

System.out.println("Constructor parameter types :");
System.out.println(constructorParametersGenerics);

เมื่อดูที่ผลลัพธ์ เราจะเห็นข้อมูลโดยละเอียดเกี่ยวกับพารามิเตอร์ของตัวสร้างของเรา:

พารามิเตอร์ตัวสร้าง :
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
ประเภทพารามิเตอร์ :
[คลาส java.lang.String, คลาส java.lang.String, อินเทอร์เฟซ java.util.List]
ประเภทพารามิเตอร์ตัวสร้าง:
[คลาส java.lang.String, คลาส java.lang.String, java.util.List<java.lang.String>]

สิ่งนี้แสดงให้เห็นถึงความแตกต่างระหว่างแต่ละวิธีอย่างชัดเจน เราเห็นว่าเรามีตัวเลือกแยกต่างหากเพื่อรับข้อมูลเกี่ยวกับประเภทพารามิเตอร์ ประเภทที่รวมไว้ และทุกอย่างโดยทั่วไป สุดยอด! เมื่อเราคุ้นเคยกับ คลาส Constructorแล้ว เราสามารถกลับไปที่หัวข้อหลักของบทความของเรา นั่นคือการสร้างวัตถุ

การสร้างวัตถุโดยใช้ Constructor.newInstance()

วิธีที่สองในการสร้างวัตถุคือการเรียก เมธอด newInstanceบนตัวสร้าง ลองมาดูตัวอย่างการทำงานและดูว่าเราจะได้ตัวสร้างเฉพาะได้อย่างไร

หากคุณต้องการรับคอนสตรัคเตอร์เดียว คุณควรใช้ เมธอด getConstructor (อย่าสับสนกับgetConstructorsซึ่งจะคืนค่าอาร์เรย์ของคอนสตรัคเตอร์ทั้งหมด) เมธอดgetConstructorส่งคืนตัวสร้างเริ่มต้น

public static void main(String[] args) throws NoSuchMethodException {
    Class employeeClass = Employee.class;
    Constructor<?> employeeConstructor = employeeClass.getConstructor();
    System.out.println(employeeConstructor);
}
สาธารณะ com.codegym.Employee()

และถ้าเราต้องการรับคอนสตรัคเตอร์เฉพาะ เราต้องส่งพารามิเตอร์ประเภทคอนสตรัคเตอร์ไปยังเมธอดนี้

อย่าลืมว่าเราสามารถรับตัวสร้างส่วนตัวของเราได้โดยใช้เมธอดgetDeclaredConstructor

Constructor<?> employeeConstructor2 = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(employeeConstructor2);

นี่คือวิธีที่เราจะได้รับตัวสร้างเฉพาะ ตอนนี้ลองสร้างวัตถุโดยใช้ตัวสร้างส่วนตัวและสาธารณะ

ตัวสร้างสาธารณะ:

Class employeeClass = Employee.class;
Constructor<?> employeeConstructor = employeeClass.getConstructor(String.class, String.class, int.class);
System.out.println(employeeConstructor);

Employee newInstance = (Employee) employeeConstructor.newInstance("Rob", "Stark", 10);
System.out.println(newInstance);

ผลลัพธ์คือวัตถุที่เราสามารถทำงานได้:

สาธารณะ com.codegym.Employee(java.lang.String,java.lang.String,int)
พนักงาน{ชื่อ='Rob' นามสกุล='Stark' อายุ=10}

ทุกอย่างใช้งานได้ดี! ตอนนี้เราจะลองกับตัวสร้างส่วนตัว:

Constructor<?> declaredConstructor = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(declaredConstructor);

Employee newInstance2 = (Employee) declaredConstructor.newInstance("Rob", "Stark", new ArrayList<>());
System.out.printf(newInstance2.toString());

ผลลัพธ์คือข้อผิดพลาดเกี่ยวกับความเป็นส่วนตัวของตัวสร้างของเรา:

Java ไม่สามารถสร้างวัตถุโดยใช้ตัวสร้างนี้ แต่จริง ๆ แล้วมีบางสิ่งที่มหัศจรรย์ที่เราสามารถทำได้ในเมธอดหลัก เราสามารถเข้าถึงระดับคอนสตรัคเตอร์ของเราได้ ทำให้สามารถสร้างอ็อบเจกต์ของคลาสของเราได้:

declaredConstructor.setAccessible(true);

ผลลัพธ์ของการสร้างวัตถุ

com.codegym.Employee ส่วนตัว (java.lang.String,java.lang.String,java.util.List)
พนักงาน{name='Rob', นามสกุล='Stark', อายุ=-1}

เราไม่ได้กำหนดอายุในตัวสร้างของเรา ดังนั้นมันจึงยังคงเหมือนเดิมกับตอนที่เริ่มต้น

ยอดเยี่ยม มาสรุปกันเถอะ!

ประโยชน์ของการสร้างวัตถุโดยใช้ Constructor.newInstance()

ทั้งสองวิธีใช้ชื่อเดียวกัน แต่มีความแตกต่างระหว่างพวกเขา:

Class.newInstance() Constructor.newInstance()
สามารถเรียกตัวสร้างno-arg เท่านั้น สามารถเรียกใช้ตัวสร้างใดก็ได้โดยไม่คำนึงถึงจำนวนพารามิเตอร์
กำหนดให้มองเห็นตัวสร้างได้ นอกจากนี้ยังสามารถเรียกตัวสร้างส่วนตัวได้ในบางกรณี
ส่งข้อยกเว้นใด ๆ (ตรวจสอบหรือไม่) ที่ประกาศโดยตัวสร้าง ล้อมรอบข้อยกเว้นที่เกิดขึ้นด้วยInvocationTargetException เสมอ

ด้วยเหตุผลเหล่านี้Constructor.newInstance()จึงเป็นที่นิยมมากกว่าClass.newInstance()และเป็นวิธีที่ใช้โดยเฟรมเวิร์กและ API ต่างๆ เช่น Spring, Guava, Zookeeper, Jackson, Servlet เป็นต้น