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