6

我有一些类存储带有重要信息的键。不允许其他人创建密钥,因为密钥依赖于静态信息(如某些目录结构等)。

public final class KeyConstants
{

    private KeyConstants()
    {
        // could throw an exception to prevent instantiation
    }

    public static final Key<MyClass> MY_CLASS_DATA = new Key<MyClass>("someId", MyClass.class);

    public static class Key<T>
    {
        public final String ID;
        public final Class<T> CLAZZ;

        private Key(String id, Class<T> clazz)
        {
            this.ID = id;
            this.CLAZZ = clazz;
        }
    }

}

这个例子很简单。

我想测试错误键的后果(异常处理等)并通过 JUnit 测试用例中的反射实例化该类。

Constructor<?> c = KeyConstants.Key.class.getDeclaredConstructor(String.class, Class.class);
c.setAccessible(true);
@SuppressWarnings ("unchecked")
KeyConstants.Key<MyClass> r = (KeyConstants.Key<MyClass>) c.newInstance("wrongId", MyClass.class);

然后我问自己如何防止键类的进一步实例化(即防止通过反射进一步创建对象)?

enums我想到了,但它们不适用于泛型。

public enum Key<T>
{
    //... Syntax error, enum declaration cannot have type parameters
}

那么如何保留一组n泛型类的实例并防止进一步实例化呢?

4

6 回答 6

3

那么如何保留一组泛型类的 n 个实例并防止进一步实例化呢?

如果你真的想使用这种模式,那么没有人(包括你)应该能够实例化一个Key对象。为了在具有这种模式的类中保留一组 n 个实例,您可以拥有一个私有构造函数、一个用于访问的静态方法和一个用于防止反射的SecurityManager 。而且由于您希望能够将密钥作为公共常量访问,因此我会尝试这样的事情..

public class KeyConstants{

    // Here are your n instances for public access
    public static final int KEY_1 = 1;
    public static final int KEY_2 = 2;
    .
    .
    .
    public static final int KEY_N = 'n';


    // now you can call this method like this..
    // Key mKey = KeyConstants.getKey(KeyConstants.KEY_1);
    public static Key getKey(int key){

         List keys = Key.getInstances();

         switch(key){

         case KEY_1:
                     return keys.get(0);
         case KEY_2:
                     return keys.get(1);
         .
         .
         .
         case KEY_N:
                     return keys.get(n);
         default:
                     // not index out of bounds.. this means
                     // they didn't use a constant
                     throw new IllegalArgumentException();
         }

    }

    static class Key<T>{
        private static List<Key> instances;
        private String ID;
        private Class<T> CLAZZ;

        private Key(String id, Class<T> clazz){
                      this.ID = id;
                      this.CLAZZ = clazz;
         }

        public static List<Key> getInstances(){
            if(instances == null){

                            instances = new ArrayList<Key>();
                //populate instances list
            }

                    return instances;
        }
    }
}

用于SecurityManager防止反射访问。

//attempt to set your own security manager to prevent reflection
    try {
        System.setSecurityManager(new MySecurityManager());
    } catch (SecurityException se) { 
    }

class MySecurityManager extends SecurityManager {

    public void checkPermission(Permission perm) {
        if (perm.getName().equals("suppressAccessChecks"))
            throw new SecurityException("Invalid Access");
    }

}

这将在SecurityException任何时候有人尝试访问您的类中的私有变量或字段(包括通过反射进行的访问尝试)。

于 2013-08-01T13:43:00.363 回答
1

我不确定我是否完全理解您的问题,但是如果私有构造函数还不够,您可以使用更动态的方法并在给出信号后在构造函数中抛出异常吗?例如:

public static class Key<T>
{
  private static boolean isLocked = false;

  // Call this method when you want no more keys to be created
  public static void lock() { isLocked = true; }

  ...

      private Key(String id, Class<T> clazz)
      {
          if (isLocked) throw new IllegalStateException("Cannot create instances of Key");
          this.ID = id;
          this.CLAZZ = clazz;
      }
}

Key.lock()然后 - 这是缺点 -一旦你想阻止创建更多实例,你将不得不调用。

于 2013-08-01T13:35:14.357 回答
1

我最近遇到了一些 Multiton 模式,我试图用唯一的枚举键来处理问题,这让我想到了另一种方法。

这些键可以按我的意图用于信息流,甚至可以用作类型安全异构容器的键,它们可以在其中执行编译时转换。

键定义类

public class KeyConstants
{

  public static final KeysForIntegers SOME_INT_KEY = KeysForIntegers.KEY_2;

  public static final KeysForStrings SOME_STRING_KEY = KeysForStrings.KEY_1;

  public interface Key<Type>
  {
    public Class<Type> getType();
  }

  /* Define methods that classes working with the keys expect from them */
  public interface KeyInformation
  {
    public String getInfo1();
    // and so on...
  }

  public enum KeysForStrings implements Key<String>, KeyInformation
  {
    KEY_1("someId");

    public final String ID;

    private KeysForStrings(String id)
    {
      ID = id;
    }

    @Override
    public String getInfo1()
    {
      return "Good piece of information on " + ID + ".";
    }

    @Override
    public Class<String> getType()
    {
      return String.class;
    }
  }

  public enum KeysForIntegers implements Key<Integer>, KeyInformation
  {
    KEY_2("bla");

    public final String ID;

    private KeysForIntegers(String id)
    {
      this.ID = id;
    }

    @Override
    public String getInfo1()
    {
      return "Some info on " + ID + ".";
    }

    @Override
    public Class<Integer> getType()
    {
      return Integer.class;
    }
  }
}

示例密钥使用类

public class KeyUser
{
  public static void main(String[] args)
  {
    KeysForIntegers k1 = KeyConstants.SOME_INT_KEY;
    KeysForStrings k2 = KeyConstants.SOME_STRING_KEY;

    processStringKey(k2);
    useIntKey(k1);

    Integer i = useIntKey(KeyConstants.SOME_INT_KEY);
    processStringKey(KeyConstants.SOME_STRING_KEY);
  }

  /* My methods should just work with my keys */

  @SuppressWarnings ("unchecked")
  public static <TYPE, KEY extends Enum<KeysForIntegers> & Key<TYPE> & KeyInformation> TYPE useIntKey(KEY k)
  {
    System.out.println(k.getInfo1());
    return (TYPE) new Object();
  }

  public static <KEY extends Enum<KeysForStrings> & KeyInformation> void processStringKey(KEY k)
  {
    System.out.println(k.getInfo1());
    // process stuff
  }
}
于 2013-08-09T14:54:09.020 回答
1

我有另一种方法,您可以将接口绑定为仅由enum. 使用这种方法,您在编译时拥有一组固定的实例。

如果要添加延迟加载,则实现它的枚举应该是代理,如果请求则加载所需的对象。隐藏在代理后面的一个或多个类应该只对它们可见,这样它们就可以独占访问构造函数。

public class User {

  public static <S> S handleKey(FixedInstanceSet<S,?> key) {
    return key.getKey();
  }
}

interface FixedInstanceSet<S, T extends Enum<T> & FixedInstanceSet<S,T>>
{
  public S getKey();
}

enum StringKeys implements FixedInstanceSet<String, StringKeys> {
  TOP, DOWN, LEFT, RIGHT;
  @Override
  public String getKey() { return null; }
}

enum IntKeys implements FixedInstanceSet<Integer, IntKeys > {
  TOP, DOWN, LEFT, RIGHT;
  @Override
  public Integer getKey() { return null; }
}

/*
 * Bound mismatch: The type NotWorking is not a valid substitute for the bounded
 * parameter <T extends Enum<T> & FixedInstanceSet<S,T>> of the type
 * FixedInstanceSet<S,T>
 */
//class NotCompiling implements FixedInstanceSet<String, NotCompiling> {
//
//  @Override
//  public String getKey() { return null; }
//}
于 2013-09-05T11:11:02.763 回答
1

正如您在代码中显示的以防止实例化KeyConstants,您可以在私有非参数构造函数中抛出一些异常。

更难的部分是阻止从KeyConstants类外部创建 KeyConstants.Key 构造函数。

一些疯狂的想法

也许在您的构造函数中创建 Exception 并检查其堆栈跟踪的样子。当我将此代码添加到构造函数时

private Key(String id, Class<T> clazz) {

    StackTraceElement[] stack = new Exception().getStackTrace();
    for (int i=0; i<stack.length; i++){
        System.out.println(i+") "+stack[i]);
    }

    this.ID = id;
    this.CLAZZ = clazz;
}

并使用反射创建 Key 实例

Constructor<?> c = KeyConstants.Key.class.getDeclaredConstructor(
        String.class, Class.class);
c.setAccessible(true);
KeyConstants.Key<MyClass> r = (KeyConstants.Key<MyClass>) c
        .newInstance("wrongId", MyClass.class);

我明白了

0) KeyConstants$Key.<init>(Test.java:38)
1) sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
2) sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
3) sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
4) java.lang.reflect.Constructor.newInstance(Constructor.java:525)

所以也许只是堆栈的第 4 个元素是java.lang.reflect.Constructor.newInstancethrow Exception 以防止执行其余的构造函数代码,例如:

if (stack.length>=4 && stack[4].toString().startsWith("java.lang.reflect.Constructor.newInstance")){
    throw new RuntimeException("cant create object with reflection");
}
于 2013-08-01T14:35:33.283 回答
0

如果我理解正确,你不希望你的类被实例化。您可以将默认构造函数设置为私有

private Key() throws IllegalStateException //handle default constructor
    {
        throw new IllegalStateException();
    }

这将防止其不正确的实例化。

更新:添加了 throw IllegalStateException

于 2013-08-01T13:27:44.817 回答