7月 132012
 

らしい。
要するに定数の値を変更しても、それを参照してる側も再コンパイルしないと値が反映されない。
共通ライブラリに定数を定義し、他のプログラムで参照している場合などがヤバイ。

final Fields and Constants

  • finalなフィールド変数は、値を変更しても、その値を参照している既存バイナリは再コンパイルしない限り新しい値が適用されない。
  • 定数式で初期化しなくとも、同じ動作となる。
  • この動作は条件付コンパイルをサポートした結果の副作用である。
  • 定数は絶対に変更されない値でのみ宣言するべき。
  • 単に読み込み専用としたい場合はprivate staticな変数のgetterのみ宣言する方が良い。
  • 定数値を持つstatic finalなフィールドはデフォルト初期値を持つように宣言してはならない。必ず定数式で初期化する。
    • × static final int a;
    • static final int a = 0;

ブランクfinalはインライン展開されない模様。
言語仕様では明確な記述は発見できなかった。

[java]
class Constant1
{
public static final String A;

static
{
A = “test”;
}

}
class Constant2
{
public static final String A = “test”;
}

public class Main
{
public static void main(String[] args)
{
System.out.println(Constant1.A);
//decompile=>System.out.println(Constant1.A)

System.out.println(Constant2.A);
//decompile=>System.out.println(“test”)
}
}
[/java]

7月 132012
 

バグ?

Checkstyle4.3.2で確認。

  • Checkstyleでは8文字カウントがデフォルト値
  • Eclipse上での設定画面では4文字がデフォルト値として認識している(※バグぽ)
  • Eclipse上での設定画面で4文字指定を行うと、デフォルト値と認識されて設定ファイルからカウント数設定を削除する
  • 設定ファイルはXMLだが、タブ文字カウント数の設定が存在しないとデフォルト値の8文字としてカウントする
解決策1

Checkstyle設定画面にてOther>TreeWalker>tabWidthを4にする。
SizeViolations>MaximumLineLength>tabWidthはバグにより4文字指定不可

解決策2

設定ファイルをテキストエディタで開き、TreeWalkerモジュールか、LineLengthモジュールに以下のプロパティを設定する。

[xml][/xml]

TreeWalkerモジュールに設定した場合は解決策1と同様の設定になる。

7月 132012
 

Each class variable, instance variable, or array component is initialized with a default value when it is created (§15.9, §15.10):

For type byte, the default value is zero, that is, the value of (byte)0.

For type short, the default value is zero, that is, the value of (short)0.

For type int, the default value is zero, that is, 0.

For type long, the default value is zero, that is, 0L.

For type float, the default value is positive zero, that is, 0.0f.

For type double, the default value is positive zero, that is, 0.0d.

For type char, the default value is the null character, that is, ‘\u0000’.

For type boolean, the default value is false.

For all reference types (§4.3), the default value is null.

booleanはfalse、参照型はnull、その他プリミティブ型は0がデフォルト値。

7月 132012
 
  • JDBC APIではPreparedStatementインタフェースを用いてパラメタライズドクエリを実行する
  • OracleJDBCドライバではOraclePreparedStatementクラスを用いてパラメタライズドクエリを実行する
  • OraclePreparedStatementのsetStringメソッドおよびsetCharメソッドは空白埋めを行わない
  • OraclePreparedStatementのsetFixedCharメソッドは空白埋めを行う
  • JDBCのPreparedStatementインタフェースにはsetCharおよびsetFixedCharメソッドは定義されていない
    (これらはOracleのJDBC拡張機能を用いる事で実装できる)

要するに・・・

CHAR(5)型のCOL1列を持ったテーブルTABLE_Aに対して
[sql]SELECT * FROM TABLE_A WHERE COL1=?[/sql]
というSQLに比較文字列をバインドしてを発行する時、setString(1, "ABC")とするとCOL1='ABC'という比較がされ、確実に0件。
setString(1, "ABC ")としなければならない。

OracleParameterdStatement.setFixedChar(int, String)を使用することにより、空白埋め比較でバインドする事も出来るが、DBに依存したコードとなる。

ちなみにバインドをしないでリテラルで比較するとヒットする。
[sql]SELECT * FROM TABLE_A WHERE COL1=’ABC'[/sql]
これはOracleでは両辺にVARCHAR2型またはNVARCHAR2型が使用されている場合以外は、空白埋めで比較が行われるためである。

そもそも可変文字列の格納にCHAR型を使うのをやめればすむ話。。

参考:

7月 132012
 

※憶測入り。特にデシリアライズの時の判断基準とか適当。

結論から言うと、普通は指定しなくて大丈夫。
シリアライズする必要性のあるクラスの場合、指定したほうが環境問題に悩まされずにすむのでベター。

serialVersionUIDとわ

端的にいえば、シリアライズされたオブジェクトを送る側と受け取る側で、どのクラスのインスタンスなのかを識別するためのID。

いろいろ
  • どうやらクラス名とクラスメンバとserialVersionUID(以下SUID)で判別してるようだ。
  • 送られてきたクラス定義とSUIDが、受け取る側が参照できるクラスと同じなら、互換性があるクラスだと判断して、オブジェクトをそのクラスを使用して解析するんだと思う。
  • 定義とSUIDが同じだけど実は互換性がない(実装が違う)場合、変換時に例外が出る。
  • 指定しないとSHA-1でクラスのハッシュから計算されるっぽい。
  • SUID自動計算の方法は環境によって違うっぽいので自分で指定したほうがベター。
  • Serializableインタフェースだと勝手に計算してくれる。
  • Externalizableインタフェースだと自分で指定しないとだめぽい。
  • Exceptionの場合、ThrowableインタフェースがSerializableインタフェースを実装してるので、SUIDが求められる。
おまけ

serialverコマンドでSUID自動計算の結果が見れる。

ざっと試した感じ、以下の情報できまるっぽい。

  • クラス名
  • クラスのフィールド(プロパティ、メンバ変数)全部
  • private以外のメソッド

実装コードは関係なかった。
まぁシリアライズする必要性のあるクラスにはやはり自分で指定したほうがいいと思う。

参照
7月 132012
 
  • Oracle10gのJDBCは、DATE型項目の場合java.sql.Date型で返却するために時刻が欠落する。
  • Oracle9.0.1より前のJDBCではjava.sql.Timestamp型で返却していたため時刻は欠落しない。
  • Oracle11gもjava.sql.Timestamp型で返却する仕様をデフォルトとしているため時刻は欠落しない。

  • Oracle10gの場合、oracle.jdbc.V8Compatible=trueを指定することでjava.sql.Timestamp型で返却する動作となる。

参考:

Oracleオブジェクト型からSQL DATEデータ型へのマッピング

Oracle Database 8i以下のバージョンではTIMESTAMPデータがサポートされていませんでしたが、SQL標準への拡張機能として、Oracle DATEデータに時刻コンポーネントが含まれていました。このため、Oracle Database 8i以下のバージョンのJDBCドライバは、oracle.sql.DATEをjava.sql.Timestampにマップして時刻コンポーネントを保持していました。Oracle Database 9.0.1以上ではTIMESTAMPがサポートされ、9iのJDBCドライバがoracle.sql.DATEをjava.sql.Dateにマッピングするようになりました。このマッピングはOracle DATEデータの時刻コンポーネントを切り捨てたため、不正確でした。この問題を克服するために、Oracle Database 11.1では新しいフラグmapDateToTimestampが導入されました。このフラグのデフォルト値はtrueです。これは、デフォルトで、ドライバがoracle.sql.DATEをjava.sql.Timestampに正確にマップして時刻情報を保持することを意味します。不正確でも10gと互換性のあるoracle.sql.DATEからjava.sql.Dateへのマッピングが必要な場合は、mapDateToTimestampフラグの値をfalseに設定します。

注意:
oracle.sql.DATEからjava.sql.Dateへのマッピングの問題を克服するために、Oracle Database 9.2でフラグV8Compatibleが導入されました。このフラグのデフォルト値はfalseで、これはOracle DATEデータのjava.sql.Dateデータへのマッピングが許可されることを意味します。ただし、このフラグの値をtrueに設定することによって、ユーザーはOracle DATEデータの時刻コンポーネントを保持することができました。このフラグは11gでは非推奨です。これは、Oracle Database 8iとの互換性がサポートされなくなったためです。

Oracleからの回答:

QUESTION
======
oracle.jdbc.V8Compatible=trueにすることで getObject()の戻り値の型がDate→Timestamp と変更される以外に動作が変わることがあるか。

ANSWER
======
ございません。

oracle.jdbc.V8Compatible=true に設定することによる動作の変更点はgetObject()の戻り値の型が Date→Timestamp と変更されるだけでございます。

7月 132012
 

DBへの型のマッピングは気をつけないとダメだよという話。

予備知識とか
  • ANSI SQLのDATE型は日付のみ保持する。
  • OracleのDATE型は日付だけでなく時刻まで保持する。(秒未満の精度とタイムゾーンは保持しない)
DATE型@Java
  • java.sql.DateクラスはANSI準拠。日付までしか保持しない
  • java.util.Dateクラスはミリ秒まで保持する。
  • JDBCではjava.sql.DateクラスはDATE型(JDBC)にマッピングされる。
  • JDBCではjava.util.DateクラスはDATE、TIME、TIMESTAMP型(JDBC)にマッピングされる。
問題例とか
  • JavaでOracleに日付を登録する場合、やり方によっては時刻情報が落ちる。
  • java.sql.Dateのコンストラクタはlong型なミリ秒を受け取れるが、残るのは日付のみ。

参考: