Java:提供类的过滤视图



我有一个包含大量数据的类,我想使用相同的方法在此对象上公开一个"过滤视图",但输出经过修改。举一个非常基本的例子,假设我有这样一个类:

public class Booleans {
    private boolean[] data;
    public Booleans(boolean[] data) {
        this.data = data;
    }
    public boolean getDataAt(int i) {
        return data[i];
    }
    public Booleans opposite() {
        // What to return here? b.opposite().getDataAt(i) should be !b.getDataAt(i)
    }
}

有没有写相反方法的好模式?我需要尽可能提高内存效率:数据不能复制,理想情况下,对"相反"的调用不应创建任何对象,因为它将被调用多次。

例如,在

布尔值的构造函数中创建一个小对象会很好,但那时我不能引用"这个"......

没有对象创建就无法逃脱。但是,您可以通过非常便宜的对象创建来侥幸逃脱:

private transient Booleans opposite;
static class BooleansOpposite extends Booleans {
     Booleans original;
     BooleansOpposite(Booleans original) {
         super(null);
         this.original = original;
     }
     public Booleans opposite() {
         return original;
     }
     public boolean getDataAt(int i) {
          return !original.getDataAt(i);
     }
}
public Booleans opposite() {
    if (opposite == null) {
        opposite = new BooleansOpposite(this);
    }
    return opposite;
}

这基本上使用 Decorator 模式来更改 getDataAt 方法的行为。尽管 opposite 的第一个调用会创建一个对象,但您支付的唯一成本是 BooleansOpposite 不保存任何数据的成本,因为它引用回其父级。如果您更喜欢预先初始化,您还可以在构造函数中提前创建相反的实例。

如果布尔值只是一个接口或一个不定义任何成员的纯抽象类,那么 BooleansOpposite 实现就不需要继承无用的字段,那就更好了。

如果我理解正确,您希望提供一种保持类接口相同的方法,但就某些方法而言,实现应该不同(在这种情况下相反)。目前尚不清楚您有什么样的约束,除了您不想创建太多对象的明显事实。

我建议的解决方案最初可能会不受欢迎,但实际上可能会有所帮助,以防您有许多这样的方法,其中一些应该通过,而另一些则不然。这称为动态代理模式。它的实现比更直接的方法慢一些,但它可能适合您的需求。我担心复杂性略高于您想要的,但我认为最好有您可以选择的选项。

我建议你先解压缩接口:

interface BooleanArray {
    /** Returns the boolean at given index */
    boolean getDataAt(int i);
    /** Returns a BooleanArray implementation that is "opposite" */
    BooleanArray opposite();
}

然后提供一个具体的实现:Booleans 。现在,对于opposite()方法,我们将利用现有的目标实现,然后补充结果。

下面是演示这个想法的完整(工作)代码。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/** Proxy-based attempt to: http://stackoverflow.com/questions/35805928/java-offer-a-filtered-view-on-a-class
 */
interface BooleanArray {
    boolean getDataAt(int i);
    BooleanArray opposite();
}
public class Booleans implements BooleanArray {
    private final boolean[] data;
    private volatile BooleanArray OPPOSITE;
    public Booleans (boolean[] data) {
        this.data = data;
    }
    private Object initOpposite(BooleanArray target) {
        return Proxy.newProxyInstance(Booleans.class.getClassLoader(),
                new Class[]{BooleanArray.class},
                new ComplementHandler(target));
    }
    public boolean getDataAt(int i) {
        return data[i];
    }
    public BooleanArray opposite() {
        if (OPPOSITE == null)
            OPPOSITE = (BooleanArray) initOpposite(this);
        return OPPOSITE;
    }
    public static void main(String[] args) {
        BooleanArray ab = new Booleans(new boolean[]{true, false, true});
        BooleanArray ba = ab.opposite();
        for (int i = 0; i < 3; i ++)
            System.out.println(ab.getDataAt(i) + " and: " + ba.getDataAt(i));
    }
    private static final class ComplementHandler implements InvocationHandler {
        private final BooleanArray target;
        public ComplementHandler(BooleanArray target) {
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("getDataAt".equals(method.getName())) {
                Boolean orig = (Boolean) method.invoke(target, args);
                if (orig)
                    return Boolean.FALSE;
                return Boolean.TRUE;
            }
            return method.invoke(target, args);
        }
    }
}

可能返回一个Iterator并对迭代器进行编程以跳过您关注的值。O(#跳过的项目)内存,我认为这是最佳的。

该方法应返回一个新的对象布尔值,但**该数组的所有元素都只是实际实例的否定或补码。

例:

如果不创建单个对象,就无法做到这一点(您必须返回未this 的内容),但您可以在不重新创建数据的情况下执行此操作。(因此 O(1) 空间)。

一种解决方案是创建一个不存储任何数据的新实例(因此无需复制数据),但让它始终返回与此数据相反的数据。

  public Booleans opposite() {
    return new Booleans(){
      @Override
      public boolean getDataAt(int i) {
        return !vals[i];
      }
    };
  }

如果您关心的是内存效率,为什么不使用 BitSet 而不是数组?

布尔 [] 数组中的每个元素占用 1 个字节的内存,而 BitSet 中的每个布尔值占用 1 位内存,四舍五入到下一个字节。

另外,我只会创建第二个相反的BitSet,并且每当您使用值修改原始BitSet时,只需使用!value修改相反的BitSet即可。

并且,位集具有

void flip(int fromIndex, int toIndex)设置从指定的 fromIndex(包括)到指定的 toIndex(不包括)再到其当前值的补码的每个位。

**我刚刚开始研究集合,所以如果这里的内容不正确或为什么不建议使用 bitSets,请告诉我。

最新更新