私は有理数のクラスを実装していますが、問題と問題は、複素数だけでなく、特定の数学的オブジェクトで実行される多数の計算を伴うアプリケーションで使用されることを意図した他のクラスでも本質的に同じです。
JRE と共に配布されるライブラリと多くのサードパーティ ライブラリでは、number クラスは不変です。これには、「equals」と「hashcode」を意図したとおりに確実に一緒に実装できるという利点があります。これにより、さまざまなコレクションでインスタンスをキーと値の両方として使用できるようになります。実際、コレクションでの信頼できる操作のためには、コレクション内のキー値としてのインスタンスの存続期間全体にわたる不変性を維持する必要があります。
しかし、クラスの設計が (言語の制限内で) 不変性を強制する場合、単純な数学演算を実行する場合でも、数式は過剰なオブジェクト割り当てとその後のガベージ コレクションの負担になります。複雑な計算で繰り返し発生するものの明示的な例として、次のことを検討してください。
Rational result = new Rational( 13L, 989L ).divide( new Rational( -250L, 768L ) );
この式には 3 つの割り当てが含まれており、そのうちの 2 つはすぐに破棄されます。オーバーヘッドの一部を回避するために、クラスは通常、一般的に使用される「定数」を事前に割り当て、頻繁に使用される「数値」のハッシュ テーブルを保持することさえあります。もちろん、このようなハッシュ テーブルは、必要なすべての不変オブジェクトを単純に割り当て、Java コンパイラと JVM に依存してヒープをできるだけ効率的に管理するよりもパフォーマンスが低い可能性があります。
別の方法は、可変インスタンスをサポートするクラスを作成することです。クラスのメソッドを流暢なスタイルで実装することにより、「結果」として「divide」メソッドから返される 3 番目のオブジェクトを割り当てることなく、上記と機能的に同様の簡潔な式を評価することができます。繰り返しますが、これはこの 1 つの式では特に重要ではありません。ただし、行列を操作して複雑な線形代数の問題を解決することは、不変のインスタンスを操作するよりも、可変オブジェクトとしてより適切に処理される数学的オブジェクトのより現実的なケースです。また、有理数の行列の場合、可変有理数クラスは、はるかに簡単に正当化されるように思われます。
以上のことから、関連する 2 つの質問があります。
Sun/Oracle Java コンパイラ、JIT、または JVM について、変更可能なクラスよりも不変の有理数または複素数クラスを決定的に推奨するものはありますか?
そうでない場合、可変クラスを実装するときに「ハッシュコード」をどのように処理する必要がありますか? 私は、誤用されやすい実装や不必要なデバッグ セッションを提供するのではなく、サポートされていない操作の例外をスローすることによって「フェイル ファスト」する傾向があります。リスト。
テストコード:
私が実装する必要があるものとほぼ同様の計算を実行するときに、不変の数値が重要かどうか疑問に思っている人のために:
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();
}
}
脚注:
可変クラスと不変クラスの中心的な問題は、非常に疑わしいObjectの「ハッシュコード」メソッドです。
hashCode の一般的な契約は次のとおりです。
Java アプリケーションの実行中に同じオブジェクトに対して複数回呼び出された場合は常に、オブジェクトの equals 比較で使用される情報が変更されていない限り、hashCode メソッドは一貫して同じ整数を返す必要があります。この整数は、あるアプリケーションの実行から同じアプリケーションの別の実行まで一貫性を保つ必要はありません。
equals(Object) メソッドに従って 2 つのオブジェクトが等しい場合、2 つのオブジェクトのそれぞれで hashCode メソッドを呼び出すと、同じ整数結果が生成される必要があります。
equals(java.lang.Object) メソッドに従って 2 つのオブジェクトが等しくない場合、2 つのオブジェクトのそれぞれで hashCode メソッドを呼び出すと、異なる整数結果が生成される必要はありません。ただし、プログラマーは、等しくないオブジェクトに対して個別の整数結果を生成すると、ハッシュテーブルのパフォーマンスが向上する可能性があることに注意する必要があります。
しかし、オブジェクトがコレクションに追加されると、「同等性」を判断するために使用される内部状態から派生したハッシュ コードの値に依存するようになると、状態が変化したときにコレクションに適切にハッシュされなくなります。はい、可変オブジェクトがコレクションに不適切に格納されないようにすることはプログラマーの負担ですが、可変クラスの不適切な使用が最初から防止されない限り、メンテナンス プログラマーの負担はさらに大きくなります。これが、可変オブジェクトの「ハッシュコード」に対する正しい「答え」は、オブジェクトの等価性を判断するために「equals」を実装しながら、常に UnsupportedOperationException をスローすることだと私が信じている理由です。セットに追加します。でも、例外をスローすることは、上記の「契約」に違反し、それ自体が悲惨な結果をもたらすという議論があるかもしれません。その場合、可変クラスのすべてのインスタンスを同じ値にハッシュすることが、実装の性質が非常に悪いにもかかわらず、コントラクトを維持する「正しい」方法である可能性があります。おそらくクラス名のハッシュから生成された定数値を返すことは、例外をスローするよりも推奨されていますか?