我正在实现有理数的类,但问题和问题对于复数以及打算用于具有相当数量的应用程序的其他类基本相同在给定的数学对象上执行的计算。在Java中实现数字系统:可变与不可变
在与JRE一起发布的库以及许多第三方库中,数字类是不可变的。这具有“等于”和“散列码”可以按照预期一起可靠地实现的优点。这将使实例可以用作各种集合中的键和值。事实上,实例在整个生命周期中作为集合中的关键值的不变性必须保持在集合上的可靠操作。如果类一旦创建实例时阻止可能会改变哈希码方法依赖的内部状态的操作,而不是由开发人员和代码的后续维护者遵守删除约定在修改其状态之前从集合中获取实例,然后将实例添加回它们必须属于的集合中。然而,如果类设计强制 - 在语言的限制范围内 - 不变性,数学表达式在执行简单的数学运算时就会承担过多的对象分配和随后的垃圾回收。考虑以下作为什么在复杂的计算重复出现的明确示例:
Rational result = new Rational(13L, 989L).divide(new Rational(-250L, 768L));
表达包括三个分配 - 其中两个是很快丢弃。为了避免一些开销,类通常预分配常用的“常量”,甚至可以维护一个经常使用的“数字”的散列表。当然,这样的哈希表可能不如简单地分配所有必需的不可变对象,并且依赖于Java编译器和JVM尽可能高效地管理堆,而不仅仅是性能。
另一种方法是创建支持可变实例的类。通过以流畅样式实现类的方法,可以评估在功能上类似于上述的简洁表达式,而不将从“分割”方法返回的第三个对象分配为“结果”。再次,这对于这个表达式并不特别重要。然而,通过对矩阵进行操作来解决复杂的线性代数问题,对于更好地作为可变对象进行处理的数学对象而言,是一种更现实的情况,而不必操作不可变实例。对于有理数的矩阵,一个可变的有理数类似乎更容易被证明。
有了这一切,我有两个相关的问题:
是否有有关Sun/Oracle的Java编译器,JIT,或JVM这将决定性地推荐了可变类的不可变的理性或复数类东西吗?
如果不是,在实现可变类时应如何处理“hashcode”?我倾向于通过抛出一个不支持的操作异常来“快速失败”,而不是提供一个易于滥用和不必要的调试会话的实现,或者即使在不可变对象的状态发生变化时仍然健壮的实现,但本质上会将哈希表变成链接名单。
测试代码:
对于那些想知道是否一成不变的数字此事进行计算大致相似于那些我需要实现的时候:
import java.util.Arrays;
public class MutableOrImmutable
{
private int[] pseudomatrix = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 3, 4, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 5, 5, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 4, 3, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 2, 1 };
private int[] scalars = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
private static final int ITERATIONS = 500;
private void testMutablePrimitives()
{
int[] matrix = Arrays.copyOf(pseudomatrix, pseudomatrix.length);
long startTime = System.currentTimeMillis();
for (int iteration = 0 ; iteration < ITERATIONS ; ++iteration)
{
for (int scalar : scalars)
{
for (int index = 0 ; index < matrix.length ; ++index)
{
matrix[ index ] *= scalar;
}
}
for (int scalar : scalars)
{
for (int index = 0 ; index < matrix.length ; ++index)
{
matrix[ index ] /= scalar;
}
}
}
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("Elapsed time for mutable primitives: " + elapsedTime);
assert Arrays.equals(matrix, pseudomatrix) : "The matrices are not equal.";
}
private void testImmutableIntegers()
{
// Integers are autoboxed and autounboxed within this method.
Integer[] matrix = new Integer[ pseudomatrix.length ];
for (int index = 0 ; index < pseudomatrix.length ; ++index)
{
matrix[ index ] = pseudomatrix[ index ];
}
long startTime = System.currentTimeMillis();
for (int iteration = 0 ; iteration < ITERATIONS ; ++iteration)
{
for (int scalar : scalars)
{
for (int index = 0 ; index < matrix.length ; ++index)
{
matrix[ index ] = matrix[ index ] * scalar;
}
}
for (int scalar : scalars)
{
for (int index = 0 ; index < matrix.length ; ++index)
{
matrix[ index ] = matrix[ index ]/scalar;
}
}
}
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("Elapsed time for immutable integers: " + elapsedTime);
for (int index = 0 ; index < matrix.length ; ++index)
{
if (matrix[ index ] != pseudomatrix[ index ])
{
// When properly implemented, this message should never be printed.
System.out.println("The matrices are not equal.");
break;
}
}
}
private static class PseudoRational
{
private int value;
public PseudoRational(int value)
{
this.value = value;
}
public PseudoRational multiply(PseudoRational that)
{
return new PseudoRational(this.value * that.value);
}
public PseudoRational divide(PseudoRational that)
{
return new PseudoRational(this.value/that.value);
}
}
private void testImmutablePseudoRationals()
{
PseudoRational[] matrix = new PseudoRational[ pseudomatrix.length ];
for (int index = 0 ; index < pseudomatrix.length ; ++index)
{
matrix[ index ] = new PseudoRational(pseudomatrix[ index ]);
}
long startTime = System.currentTimeMillis();
for (int iteration = 0 ; iteration < ITERATIONS ; ++iteration)
{
for (int scalar : scalars)
{
for (int index = 0 ; index < matrix.length ; ++index)
{
matrix[ index ] = matrix[ index ].multiply(new PseudoRational(scalar));
}
}
for (int scalar : scalars)
{
for (int index = 0 ; index < matrix.length ; ++index)
{
matrix[ index ] = matrix[ index ].divide(new PseudoRational(scalar));
}
}
}
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("Elapsed time for immutable pseudo-rational numbers: " + elapsedTime);
for (int index = 0 ; index < matrix.length ; ++index)
{
if (matrix[ index ].value != pseudomatrix[ index ])
{
// When properly implemented, this message should never be printed.
System.out.println("The matrices are not equal.");
break;
}
}
}
private static class PseudoRationalVariable
{
private int value;
public PseudoRationalVariable(int value)
{
this.value = value;
}
public void multiply(PseudoRationalVariable that)
{
this.value *= that.value;
}
public void divide(PseudoRationalVariable that)
{
this.value /= that.value;
}
}
private void testMutablePseudoRationalVariables()
{
PseudoRationalVariable[] matrix = new PseudoRationalVariable[ pseudomatrix.length ];
for (int index = 0 ; index < pseudomatrix.length ; ++index)
{
matrix[ index ] = new PseudoRationalVariable(pseudomatrix[ index ]);
}
long startTime = System.currentTimeMillis();
for (int iteration = 0 ; iteration < ITERATIONS ; ++iteration)
{
for (int scalar : scalars)
{
for (PseudoRationalVariable variable : matrix)
{
variable.multiply(new PseudoRationalVariable(scalar));
}
}
for (int scalar : scalars)
{
for (PseudoRationalVariable variable : matrix)
{
variable.divide(new PseudoRationalVariable(scalar));
}
}
}
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("Elapsed time for mutable pseudo-rational variables: " + elapsedTime);
for (int index = 0 ; index < matrix.length ; ++index)
{
if (matrix[ index ].value != pseudomatrix[ index ])
{
// When properly implemented, this message should never be printed.
System.out.println("The matrices are not equal.");
break;
}
}
}
public static void main(String [ ] args)
{
MutableOrImmutable object = new MutableOrImmutable();
object.testMutablePrimitives();
object.testImmutableIntegers();
object.testImmutablePseudoRationals();
object.testMutablePseudoRationalVariables();
}
}
脚注:
可变类与不可变类的核心问题是 - hig溶血素怀疑 - 在Object“散列码”的方法:
hashCode的一般合同是:
每当它是一个Java应用程序的执行过程中调用同一个对象上不止一次, hashCode方法必须始终返回相同的整数,前提是在修改对象的等于比较时不使用任何信息。该整数不需要从应用程序的一次执行到同一应用程序的另一次执行保持一致。
如果两个对象根据equals(Object)方法相等,则对两个对象中的每个对象调用hashCode方法必须产生相同的整数结果。
根据equals(java.lang.Object)方法,如果两个对象不相等,则不要求对这两个对象中的每一个调用hashCode方法都必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
但是,一旦一个物体被添加到用于确定依赖于来自其内部状态导出的其哈希码值的集合“平等,”它不再是正确散列到收集时,它的状态变化。是的,程序员的负担是为了确保可变对象没有被不正确地存储在集合中,但是维护程序员的负担更重,除非不恰当地使用可变类不会首先被阻止。这就是为什么我认为对可变对象的“hashcode”正确的“回答”是总是抛出一个UnsupportedOperationException,同时仍然执行“equals”来确定对象的相等性 - 想想你想要比较的矩阵是否相等,但不会想到添加到Set。然而,可能有人认为抛出异常违反了上述“合同”,并带有其自身的可怕后果。在这种情况下,尽管执行的性质很差,但将可变类的所有实例散列为相同的值可能是维持合同的“正确”方法。返回一个常量值 - 可能是通过散列类名生成的 - 通过抛出异常推荐?
'hashCode '和'equals'在可变类中实现没有问题。可变类的问题是当你在可能依赖于这些方法的'Collection'实例中使用时。 – SJuan76
在你确实有数据表明分配对你来说是一个瓶颈之前,绝对要使用不可变类型。分配很便宜。 –
如果你让它们变为可变的,你会遇到很大的麻烦,因为Java中的Number是不可变的。你可以在一个可变对象上实现'hashcode'和'equals'并且很好地解决这个问题,但是问题是其他人可能没有那么小心,并且遇到非常棘手的调试情况。就像路易斯提到的那样,你是否确定这是瓶颈的实际来源? –