2015-08-31 28 views
16

Bất kỳ suy nghĩ nào để khởi tạo lớp Paradox mà không sửa đổi chính lớp đó?Tóm tắt nghịch lý

public final class Paradox { 
    private final Paradox paradox; 

    public Paradox(Paradox paradox) { 
     this.paradox = paradox; 
     if (this.paradox == null) throw new InceptionException(); 
    } 

    private static class InceptionException extends RuntimeException { 
     public InceptionException() { 
      super("Paradox requires an instance of paradox to be instantiated"); 
     } 
    } 
} 
+2

Không thể sử dụng sự phản chiếu hoặc' sun.misc.Unsafe'. – aioobe

+1

Ngay cả với sự phản ánh Tôi không chắc chắn bạn có thể làm điều đó – Dici

+0

là chế nhạo một giải pháp? https://objectpartners.com/2012/03/15/how-to-mock-final-classes-in-unit-tests/ – duffy356

Trả lời

16

Như đã chỉ ra trong các ý kiến: Có thể với lớp sun.misc.Unsafe:

import java.lang.reflect.Constructor; 

import sun.misc.Unsafe; 

public class ParadoxTest 
{ 
    public static void main(String[] args) throws Exception 
    { 
     Constructor<Unsafe> unsafeConstructor = 
      Unsafe.class.getDeclaredConstructor(); 
     unsafeConstructor.setAccessible(true); 
     Unsafe unsafe = unsafeConstructor.newInstance(); 
     Paradox paradox = (Paradox) unsafe.allocateInstance(Paradox.class); 
     System.out.println("This is paradox: "+paradox); 
    } 
} 

Ngoài ra, người ta có thể sử dụng JNI function AllocObject, mà cũng phân bổ một đối tượng mới mà không gọi bất kỳ các nhà thầu.

EDIT: Someone found another solution - không sun.misc.Unsafe!

EDIT2: Nhưng lưu ý rằng giải pháp này cũng sử dụng các lớp học độc quyền Sun, đó là không một phần của API công cộng, và do đó giải pháp này vẫn là không di và cần không được sử dụng trong mã sản xuất!

import java.lang.reflect.Constructor; 

import sun.reflect.ReflectionFactory; 

public class AnotherParadoxTest 
{ 
    public static void main(String[] args) throws Exception 
    { 
     ReflectionFactory rf = ReflectionFactory.getReflectionFactory(); 
     Constructor<?> declaredConstructor = 
      Object.class.getDeclaredConstructor(); 
     Constructor<?> constructor = rf.newConstructorForSerialization(
      Paradox.class, declaredConstructor); 
     Paradox paradox = (Paradox) constructor.newInstance(); 
     System.out.println("This is paradox: "+paradox); 
    } 

} 

EDIT3: Chỉ cần cho đầy đủ, và theo yêu cầu trong các ý kiến: Các giải pháp JNI dựa trên

Như đã đề cập ở trên, JNI function AllocObject không gọi một constructor, vì vậy đây có thể cũng được sử dụng để khởi tạo một đối tượng như vậy. Đây là lớp học gọi, trong đó có phương pháp tự nhiên và tải các thư viện nguồn gốc:

public class ParadoxTestWithJni 
{ 
    static 
    { 
     System.loadLibrary("Paradox"); 
    } 
    public static void main(String[] args) 
    { 
     Paradox paradox = createParadox(); 
     System.out.println("This is paradox: "+paradox); 
    } 

    private native static Paradox createParadox(); 
} 

Tiêu đề ParadoxTestWithJni.h đã được tạo ra cho các lớp học này với javah:

#include <jni.h> 
#ifndef _Included_ParadoxTestWithJni 
#define _Included_ParadoxTestWithJni 
#ifdef __cplusplus 
extern "C" { 
#endif 
JNIEXPORT jobject JNICALL Java_ParadoxTestWithJni_createParadox 
    (JNIEnv *, jclass); 
#ifdef __cplusplus 
} 
#endif 
#endif 

Việc thực hiện tương ứng ... ", nơi sự kỳ diệu xảy ra ":

#include "ParadoxTestWithJni.h" 
JNIEXPORT jobject JNICALL Java_ParadoxTestWithJni_createParadox 
(JNIEnv *env, jclass c) 
{ 
    jclass paradoxClass = env->FindClass("Paradox"); 
    jobject paradox = env->AllocObject(paradoxClass); 
    return paradox; 
} 
+17

Chỉ trong trường hợp bất cứ ai đang tự hỏi ** NẾU BẠN VIẾT NÀY TRONG MÃ SẢN XUẤT, DAEMONS S TE TẮT TAY TAY ** – christopher

+4

@christopher Và nó sẽ kỳ diệu ngừng chạy với Java 9. Nhưng daemon thread choppy điều là đáng sợ, quá. – Zhedar

+1

@christopher ... vâng, và mỗi khi bạn viết điều này, thần chết một con kỳ lân. Nhưng nghiêm túc, tôi nghĩ rằng câu hỏi này là khá dự định như một câu đố ... – Marco13

13

Hãy nói rằng lớp Paradox là trong một gói test, như thế này (tất nhiên nó có thể là trong gói mặc định, nhưng câu hỏi không thực sự xác định nó):

package test; 

public final class Paradox { 
    private final Paradox paradox; 

    public Paradox(Paradox paradox) { 
     this.paradox = paradox; 
     if (this.paradox == null) throw new InceptionException(); 
    } 

    private static class InceptionException extends RuntimeException { 
     public InceptionException() { 
      super("Paradox requires an instance of paradox to be instantiated"); 
     } 
    } 
} 

một đại diện XML, nếu nó đã được xuất bản bởi XStream, của lớp sẽ trông như thế này:

<test.Paradox/> 

với XML này, nó có thể được deserialized với ra gọi constructor:

public static void main(String[] args) { 
    XStream xstream = new XStream(new DomDriver()); 
    Paradox paradoxFromXml = (Paradox) xstream.fromXML("<test.Paradox/>"); 
    System.out.println(paradoxFromXml); 
} 

Đầu ra là:

[email protected]

câu trả lời này có lợi thế của một thực tế rằng xStream không gọi một constructor trong deserialization , như đã nêu trong số FAQ:

XStream không gọi hàm tạo mặc định trong quá trình deserialization.
Đây là, trên thực tế, cùng trường hợp như trên. XStream sử dụng cùng một cơ chế như serialization JDK. Khi sử dụng chế độ nâng cao với API phản chiếu được tối ưu hóa, nó không gọi hàm khởi tạo mặc định. Giải pháp là để thực hiện readResolve hoặc readObject như được minh họa với câu hỏi cuối cùng.

Vì vậy, bằng cách sử dụng biểu diễn XML đơn giản XStream có theo mặc định, lớp có thể được khởi tạo thông qua quá trình deserialization, mặc dù thực tế là lớp không thể tuần tự hóa được.

Là một giải thích thêm vào đó, XStream trong trường hợp này có thể khởi tạo các lớp bằng cách sử dụng SunLimitedUnsafeReflectionProvider hoặc SunUnsafeReflectionProvider, mà trong nội bộ sử dụng sun.misc.Unsafe, như bang Javadoc của họ (nhờ marco13 cho trỏ này ra):

SunLimitedUnsafeReflectionProvider :

/** 
* Instantiates a new object bypassing the constructor using undocumented internal JDK features. 
* <p> 
* The code in the constructor will never be executed and parameters do not have to be known. This is the same method 
* used by the internals of standard Java serialization, but relies on internal code (sun.misc.Unsafe) that may not be 
* present on all JVMs. 
* <p> 
* <p> 
* The implementation will use standard Java functionality to write any fields. This requires Java 5 as minimum runtime 
* and is used as fallback on platforms that do not provide the complete implementation level for the internals (like 
* Dalvik). 
* <p> 
* 
* @author J&ouml;rg Schaible 
* @author Joe Walnes 
* @author Brian Slesinsky 
* @since 1.4.7 
*/ 

SunUnsafeReflectionProvider:

/** 
* Instantiates a new object bypassing the constructor using undocumented internal JDK features. 
* <p> 
* The code in the constructor will never be executed and parameters do not have to be known. This is the same method 
* used by the internals of standard Java serialization, but relies on internal code (sun.misc.Unsafe) that may not be 
* present on all JVMs. 
* <p> 
* <p> 
* The implementation will use the same internals to write into fields. This is a lot faster and was additionally the 
* only possibility to set final fields prior to Java 5. 
* <p> 
* 
* @author Joe Walnes 
* @author Brian Slesinsky 
* @author J&ouml;rg Schaible 
* @since 1.4.7 
*/ 

Nói chung, tôi nhớ rằng XStream có khả năng khởi tạo đối tượng mà không cần gọi hàm tạo, mà không có thời gian trả lời câu hỏi để tìm hiểu kỹ hơn.

+3

Tôi nghĩ mọi người quan tâm hơn đến cách hoạt động của nó hơn là nhìn thấy một số thư viện ma thuật làm điều đúng – Dici

+1

@Dici Đủ công bằng. Tôi đã thêm một lời giải thích nhỏ cho câu trả lời của tôi. – Magnilex

+0

Họ chỉ làm việc phản chiếu cho bạn ... –