代理混乱的优雅解决方案

问题描述:

在Java 8中,我想编写一个类,给定一个或多个侦听器,每当返回一个代理列表(使用通过传入其类的实现List来选择)东西被添加或删除。代码(与我一起)如下:代理混乱的优雅解决方案

public final class EventedList { 
    private EventedList() { 
    } 

    protected static class ListInvocationHandler<T> implements InvocationHandler { 
     private List<T> theList; 
     private ListChangeHandler<T>[] listeners; 

     public ListInvocationHandler(Class<? extends List<T>> listClass, ListChangeHandler<T>[] listeners) throws InstantiationException, IllegalAccessException { 
      this.listeners = listeners; 
      theList = listClass.newInstance(); 
     } 

     public Object invoke(Object self, Method method, Object[] args) 
       throws Throwable { 
      Object ret = method.invoke(theList, args); 
      switch(method.getName()) { 
      case "add": 
       trigger((T)args[0], true); 
       break; 
      case "remove": 
       if(args[0] instanceof Integer) { 
        trigger((T)ret, false); 
       } else { 
        trigger((T)args[0], false); 
       } 
       break; 
      } 
      return ret; 
     } 

     public void trigger(T obj, boolean added) { 
      Arrays.stream(listeners).forEachOrdered(l -> l.onChange(obj, added)); 
     } 
    } 

    public static <T, U extends List<T>> List<T> newList(Class<U> listClass, ListChangeHandler<T> ... listeners) throws IllegalArgumentException, InstantiationException, IllegalAccessException { 
     @SuppressWarnings("unchecked") 
     List<T> obj = (List<T>)Proxy.newProxyInstance(listClass.getClassLoader(), new Class<?>[]{List.class}, new ListInvocationHandler<T>(listClass, listeners)); 
     return obj; 
    } 

    public static <T, U extends List<T>> List<T> newListSafe(Class<U> listClass, ListChangeHandler<T> ... listeners) { 
     List<T> obj = null; 
     try { 
      obj = newList(listClass, listeners); 
     } catch (IllegalArgumentException | InstantiationException 
       | IllegalAccessException e) { 
     } 
     return obj; 
    } 
} 

它的作品,但它肯定不是没有它的问题。

  1. 我最初只有一个类型T,但使用Class<? extends List<T>>错误我得到,所以我用U表示? extends List<T>代替。
  2. 在调用方法ListInvocationHandler时,我不得不将Object强制转换为T。我认为这是不可避免的,但我欢迎任何替代方案。
  3. newProxyInstance正在返回Object我不得不将其投射到List。此外,我相信这是不可避免的,但我欢迎任何替代品。
  4. 我收到“通过varargs参数侦听器的潜在堆污染”警告听众参数大概是因为它们是可变参数参数,但我没有看到这样做的明显风险。

主要我使用如下:

public static void main(String[] args) { 
    List<String> list = EventedList.newListSafe(ArrayList.class, new ListChangeHandler<String>() { 

     @Override 
     public void onChange(String value, boolean added) { 
      System.out.println(value + ", " + (added ? "added" : "removed")); 
     } 

    }); 

    list.add("Badger");         // Badger, added 
    list.add("Badger");         // Badger, added 
    list.add("Badger");         // Badger, added 
    list.add("Badger");         // Badger, added 
    list.remove("Badger");        // Badger, removed 
    list.add("Mushroom");        // Mushroom, added 
    list.remove("Mushroom");        // Mushroom, removed 

    // [Badger, Badger, Badger] 
    System.out.println(Arrays.toString(list.toArray())); 
} 
  1. 主要本身调用该方法有一个很好的类型安全警告,即使参数应该是隐含的。
  2. 如果可能的话,我希望能够调用它,如下所示(虽然我收到一个错误等):

    List<String> list = EventedList.newListSafe(ArrayList.class, (value, added) -> { 
        System.out.println(value + ", " + (added ? "added" : "removed")); 
    }); 
    
  3. 我对文字的墙道歉。我很欣赏任何输入。

开始=“5”>
+1

为什么不提供供应商:'EventedList.newList(ArrayList :: new,...)'并避免反射混乱? – assylias

+1

为什么不使用委托并将新列表传递给构造函数? – Thomas

+0

@assylias我甚至没有考虑过使用供应商,但这是一个体面的想法,谢谢。 – Neil

你可以使用一个装饰器,并可能摆脱(几乎)所有这些警告:

class EventedList<E> implements List<E> { 
    private List<E> delegate; 
    private List<ListChangeHandler<E>> listeners; 

    //using varargs here would still cause the heap pollution warning 
    public EventedList(List<E> d, ListChangeHandler<E>... l) { 
    //set and initialize 
    } 

    public boolean add(E e) { 
    delegate.add(e); 
    trigger(e); 
    } 

    ... //other methods 

    private void trigger(E e) { 
    //trigger listeners 
    } 

然后,只需调用它

List<String> list = new EventedList<String>(new ArrayList<String>(), 
              new ListChangeHandler<String>() { ... }); 

...或创建一个工厂方法。

+0

尽管我很看重这个解决方案,但如果我不得不在工作中编写它,我很可能会这样做,但我有意尝试使用代理来执行此任务,以查看是否存在使用代理的优雅解决方案。 – Neil

+1

@Neil据我了解'java.lang.reflect.Proxy'或多或少是装饰器的一种特殊形式(带有一些额外的属性),但是你仍然需要将它包装在正在创建的列表实例中。如果您无法更改使用您想要代理的列表的旧代码,那么您也不能使用代理 - 因此您与普通装饰器的情况相同。在这种情况下,您必须使用AOP在加载时应用装饰来装饰现有列表。 – Thomas

+0

我想这就是Proxy为什么会过时的原因。 ;) – Neil