2010-09-08 43 views
36

Tôi đang cố gắng xây dựng một khung kiểm tra tự động (dựa trên jUnit, nhưng điều đó không quan trọng) đối với bài tập về nhà của học sinh của tôi. Họ sẽ phải tạo ra các hàm tạo cho một số lớp và cũng có thể thêm một số phương thức cho chúng. Sau đó, với các chức năng kiểm tra tôi cung cấp, họ sẽ kiểm tra xem chúng có ổn không.Java: newInstance của lớp không có hàm tạo mặc định

Điều tôi muốn làm là, bằng cách phản ánh, tạo một phiên bản mới của một số lớp tôi muốn kiểm tra. Vấn đề là đôi khi, không có hàm tạo mặc định. Tôi không quan tâm đến điều đó, Tôi muốn tạo một cá thể và tự khởi tạo các biến cá thể. Có cách nào để làm điều này không? Tôi xin lỗi nếu điều này đã được yêu cầu trước đó, nhưng tôi không thể tìm thấy câu trả lời nào.

Xin cảm ơn trước.

Trả lời

47

Gọi Class.getConstructor() và sau đó Constructor.newInstance() chuyển các đối số thích hợp. Mẫu mã:

import java.lang.reflect.*; 

public class Test { 

    public Test(int x) { 
     System.out.println("Constuctor called! x = " + x); 
    } 

    // Don't just declare "throws Exception" in real code! 
    public static void main(String[] args) throws Exception { 
     Class<Test> clazz = Test.class; 
     Constructor<Test> ctor = clazz.getConstructor(int.class); 
     Test instance = ctor.newInstance(5);   
    } 
} 
+2

Nó sẽ liên quan đến một số phản ánh lộn xộn để có được một nhà xây dựng, và đi bộ nó, đưa ra một giá trị thích hợp cho mỗi đối số ... – bwawok

+0

Cảm ơn. Vấn đề là tôi không biết nếu họ đã thêm các nhà xây dựng hay không. Tất nhiên, tôi có thể kiểm tra xem họ đã làm bằng cách bắt các ngoại lệ thích hợp. Nhưng tôi sẽ không biết nếu họ tạo ra các nhà xây dựng với các đối số chính xác. Thậm chí tệ hơn, tôi không biết liệu hàm tạo có hoạt động không. Tôi muốn xây dựng cá thể mà không phụ thuộc vào việc triển khai của chúng. – GermanK

+4

@GermanK: Sử dụng Class.getConstructors() rồi thay vào đó và xem những gì có sẵn. Bạn * có * phụ thuộc vào việc triển khai để khởi tạo một lớp. Nếu bạn tạo một cá thể mà không gọi một trong các hàm tạo của chúng với các đối số thích hợp, bạn sẽ không chơi công bằng với các lớp của chúng, mà sẽ * mong đợi * được khởi tạo đúng cách. Tôi đề nghị bạn * ủy quyền * một chữ ký cụ thể. –

0

Bạn có thể sử dụng Class.getConstructor hoặc Class.getConstructors và sau đó sử dụng phương pháp Constructor.newInstance để khởi tạo đối tượng mà bạn muốn sử dụng.

+1

Với Class.getConstructor hoặc Class.getDeclaredConstructor không có tham số nào bạn nhận được một java.lang.NoSuchMethodException nếu không có hàm tạo mặc định nào được khai báo – GermanK

2

Nếu bạn chưa sử dụng các khuôn khổ mocking (như ezmock), tôi khuyên bạn nên thử một lần.

Tôi có thể sai và có thể không giúp bạn chút nào, nhưng từ những gì tôi có thể thu thập từ bài đăng của bạn, có vẻ như việc chế nhạo có thể là chính xác những gì bạn đang tìm kiếm (mặc dù tôi nhận ra rằng nó không có gì để làm với những gì bạn hỏi cho

Edit:. để đối phó với bình luận

Không, mocking frameworks hiện đại cho phép bạn tạo một "Fake" thể hiện của bất kỳ lớp từ "không có gì" và vượt qua nó xung quanh như thể. Nó là một thể hiện của lớp, không cần giao diện, nó có thể là bất kỳ lớp nào. Các phương thức cũng có thể được viết để trả về một chuỗi các giá trị từ một đơn giản luôn luôn trả về "7" đến " Khi được gọi với một arg = 7 trở lại 5 cuộc gọi đầu tiên, 6 thứ hai và 7 thứ ba ".

Nó thường được sử dụng kết hợp với các khuôn khổ thử nghiệm để cung cấp cho một lớp tham chiếu để vượt qua lớp bạn đang thử nghiệm.

Đây có thể không chính xác những gì bạn đang tìm kiếm, nhưng bạn đã đề cập đến thử nghiệm đơn vị và khởi tạo biến theo cách thủ công để có vẻ như đây là thứ có thể hữu ích.

+0

tôi nghĩ điều này đòi hỏi một số giao diện mà khung mocking sẽ thực hiện, đúng không? Bởi vì tôi không có giao diện ... Họ là những lớp học rất đơn giản mà những sinh viên sẽ thực hiện. – GermanK

+0

OK, cảm ơn, nó có thể hữu ích một số thời gian khác. – GermanK

0

Bạn có thể phân phối mã nguồn sau đây với bài tập của bạn. Yêu cầu học sinh bao gồm nó trong mã nguồn của họ. Mã của chúng sẽ không biên dịch trừ khi chúng mã hóa một lớp Assignment với chữ ký thích hợp. Trình biên dịch thực hiện kiểm tra signaure cho bạn.

Sau đó, chương trình thử nghiệm của bạn không cần sử dụng sự phản chiếu. Chỉ cần khởi tạo một AssignmentFactory và gọi phương thức make với các đối số thích hợp.

Nếu bạn sử dụng ý tưởng này, thử thách mới của bạn sẽ là một số sinh viên sửa đổi AssignmentFactory để phù hợp với lớp Bài tập của họ (phá vỡ chương trình kiểm tra của bạn).

package assignment ; 

public class AssignmentFactory 
{ 
    public AssignmentFactory () 
    { 
      super () ; 
    } 

    public AssignmentFactory make (.... parameters) 
    { 
      return new Assignment (.... arguments) ; 
    } 
} 
+0

Điều này sẽ chỉ là một phần của thử nghiệm (chữ ký chính xác) để biên dịch thời gian ... Điều gì sẽ xảy ra nếu chúng không khởi tạo đúng các biến mẫu? Tôi vẫn cần phải kiểm tra chúng. Mặt khác, tôi không muốn thêm bất cứ thứ gì làm họ phân tâm khỏi mục tiêu chính của họ trong nhiệm vụ của họ. – GermanK

+0

Có, bạn vẫn cần đánh giá bài tập của họ. Mục đích của AssignmentFactory là cố gắng buộc họ gửi bài tập của mình theo một định dạng phù hợp cho việc đánh giá có lập trình. – emory

5

Đây là giải pháp chung không yêu cầu javassist hoặc một "thao tác" bytecode khác.Mặc dù, nó giả định rằng các hàm tạo không làm bất cứ điều gì khác ngoài việc chỉ định các đối số cho các trường tương ứng, vì vậy nó chỉ đơn giản là chọn hàm tạo đầu tiên và tạo một cá thể với các giá trị mặc định (tức là 0 cho int, null cho Object, v.v.).

private <T> T instantiate(Class<T> cls, Map<String, ? extends Object> args) throws Exception 
{ 
    // Create instance of the given class 
    final Constructor<T> constr = (Constructor<T>) cls.getConstructors()[0]; 
    final List<Object> params = new ArrayList<Object>(); 
    for (Class<?> pType : constr.getParameterTypes()) 
    { 
     params.add((pType.isPrimitive()) ? ClassUtils.primitiveToWrapper(pType).newInstance() : null); 
    } 
    final T instance = constr.newInstance(params.toArray()); 

    // Set separate fields 
    for (Map.Entry<String, ? extends Object> arg : args.entrySet()) { 
     Field f = cls.getDeclaredField(arg.getKey()); 
     f.setAccessible(true); 
     f.set(instance, arg.getValue()); 
    } 

    return instance; 
} 

P.S. Làm việc với Java 1.5+. Giải pháp cũng giả định không có trình quản lý SecurityManager nào có thể ngăn chặn yêu cầu f.setAccessible(true).

+0

điều này là tốt đẹp nhưng tôi nghĩ rằng nó nên là: params.add ((pType.isPrimitive())? 0: null); –

+0

@NT_ Vị trí tốt. Mặc dù chỉ cần vượt qua số không sẽ không hoạt động, vì cần phải có loại chính xác. newInstance() sẽ hoạt động sau khi chuyển đổi pType thành lớp trình bao bọc (ví dụ: ClassUtils từ apache-commons có thể được sử dụng). – Vlad

+0

Uhm, ý bạn là gì? Nó dường như làm việc cho tôi. Trình biên dịch sẽ thực hiện việc thu hẹp/mở rộng và yêu cầu boxing cần thiết và 0 sẽ chuyển đổi thành tất cả giá trị mặc định của nguyên thủy. Tôi đang sử dụng điều này trong một thời gian mà không gặp vấn đề gì ... –

1

Tôi đã sử dụng đoạn mã sau để tạo danh sách các đối tượng chung của bất kỳ loại tên lớp nào được truyền. Nó sử dụng tất cả các phương thức đã đặt trong lớp để đặt tất cả các giá trị được truyền qua tập kết quả. Tôi đăng bài này trong trường hợp ai cũng quan tâm đến nó.

protected List<Object> FillObject(ResultSet rs, String className) 
    { 
     List<Object> dList = new ArrayList<Object>(); 

     try 
     { 
      ClassLoader classLoader = GenericModel.class.getClassLoader(); 

      while (rs.next()) 
      { 
       Class reflectionClass = classLoader.loadClass("models." + className); 

       Object objectClass = reflectionClass.newInstance(); 

       Method[] methods = reflectionClass.getMethods(); 

       for(Method method: methods) 
       { 
        if (method.getName().indexOf("set") > -1) 
        { 
         Class[] parameterTypes = method.getParameterTypes(); 

         for(Class pT: parameterTypes) 
         { 
          Method setMethod = reflectionClass.getMethod(method.getName(), pT); 

          switch(pT.getName()) 
          { 
           case "int": 
            int intValue = rs.getInt(method.getName().replace("set", "")); 
            setMethod.invoke(objectClass, intValue); 
            break; 

           case "java.util.Date": 
            Date dateValue = rs.getDate(method.getName().replace("set", "")); 
            setMethod.invoke(objectClass, dateValue); 
            break; 

           case "boolean": 
            boolean boolValue = rs.getBoolean(method.getName().replace("set", "")); 
            setMethod.invoke(objectClass, boolValue); 
            break; 

           default: 
            String stringValue = rs.getString(method.getName().replace("set", "")); 
            setMethod.invoke(objectClass, stringValue); 
            break; 
          } 
         } 
        } 
       } 

       dList.add(objectClass); 
      } 
     } 
     catch (Exception e) 
     { 
      this.setConnectionMessage("ERROR: reflection class loading: " + e.getMessage()); 
     } 

     return dList; 
    } 
Các vấn đề liên quan