Portable Object Format(POF)では、オブジェクト値の元のプラットフォーム/言語とは関係なく、それらが未知であっても、そのオブジェクト値をバイナリ・ストリームにエンコードできます。Portable Invocation Format(PIF)では、メソッド呼出しを同様にバイナリ・ストリームにエンコードできます。これら2つの形式(PIF-POFと呼ばれます)は、共通のバイナリ・エンコード基盤から派生したものです。ここではバイナリ形式を参考のために記載してありますが、PIF-POF使用時の要件を示すものではありません。このPIF-POFの使用の詳細は、第19章「Portable Object Formatの使用」を参照してください。
この付録は、次の各項で構成されています。
PIF-POFストリーム形式はオクテットを基準としており、PIF-POFストリームはオクテット値のシーケンスです。あいまいさを回避するため、このドキュメントではすべてのオクテットを0x00から0xFF(10進法の0から255に相当)の範囲の無署名の8ビット整数値として扱います。(PIF-POFでは)無署名の8ビット整数値で表される特定のオクテット値が、常に同じ無署名の8ビット整数値として書き込まれたり読み込まれたりするため、バイト順序はまったく重要ではありません。
PIFストリームには呼出しが1つだけ格納されます。呼出しは、その呼出しの残りの部分の長さを表す整数値を格納する最初のPOFストリーム、その直後に続く、対話IDの整数値を格納するPOFストリーム、さらにその直後に続く、メッセージ・オブジェクトのユーザー定義型値を格納するPOFストリームで構成されます。残りの部分の長さは、対話IDとメッセージ・オブジェクトのエンコードに使用されるオクテットの合計数を示しており、それによって、呼出しの受信プロセスでその呼出しの受信完了を判別できるようになります。TCP/IPでは特定のIPアドレスに対して複数の論理ポート番号を割り当てられますが、それと同様に対話IDは、1つの接続を通じて多重化される複数の論理クライアントおよびサービスをサポートするために使用されます。メッセージ・オブジェクトは、特定の高水準の対話プロトコルによって定義されます。
1つのPOFストリームには値が1つだけ格納されます。この値には型IDが格納され、型IDが値を示さない場合は、その後に型IDによって形式が定義されたデータ構造が続きます。
ストリーム形式は、圧縮形式での整数値のエンコード機能に大きく依存します。Coherenceでは、この整数バイナリ形式をパック整数と呼んでいます。この形式は、最初のオクテットと1つ以上の後続オクテットが必要に応じて使用される、可変長形式です。
表E-1は、最初のオクテットの3つの領域を示しています。
表E-2は、後続オクテットの2つの領域を示しています。
例E-1は、Coherenceでサポートされるオクテット・ストリームに対する32ビット整数値の書込みを示しています。
例E-1 オクテット・ストリームに対する32ビット整数値の書込み
public static void writeInt(DataOutput out, int n) throws IOException { int b = 0; if (n < 0) { b = 0x40; n = ~n; } b |= (byte) (n & 0x3F); n >>>= 6; while (n != 0) { b |= 0x80; out.writeByte(b); b = (n & 0x7F); n >>>= 7; } out.writeByte(b); }
例E-2は、Coherenceでサポートされるオクテット・ストリームからの32ビット整数値の読込みを示しています。
例E-2 オクテット・ストリームからの32ビット整数値の読込み
public static int readInt(DataInput in) throws IOException { int b = in.readUnsignedByte(); int n = b & 0x3F; int cBits = 6; boolean fNeg = (b & 0x40) != 0; while ((b & 0x80) != 0) { b = in.readUnsignedByte(); n |= ((b & 0x7F) << cBits); cBits += 7; } if (fNeg) { n = ~n; } return n; }
このドキュメントで使用される整数値で、明示的な型IDがない場合は、10進数-231から231-1の範囲の32ビット整数値と見なされます。
表E-3は、いくつかの整数値の例を示しています。
型IDは、整数値としてバイナリ・ストリームにエンコードされます。ゼロ以上の型IDは、ユーザー定義型のIDです。ゼロ未満の型IDは、事前定義(固有)型のIDです。
表E-4は、事前定義型のIDのリストです。
表E-4 事前定義型のID
型ID | 説明 |
---|---|
-1(0x40) |
int16 |
-2(0x41) |
int32 |
-3(0x42) |
int64 |
-4(0x43) |
int128* |
-5(0x44) |
float32 |
-6(0x45) |
float64 |
-7(0x46) |
float128* |
-8(0x47) |
decimal32* |
-9(0x48) |
decimal64* |
-10(0x49) |
decimal128* |
-11(0x4A) |
boolean |
-12(0x4B) |
octet |
-13(0x4C) |
octet-string |
-14(0x4D) |
char |
-15(0x4E) |
char-string |
-16(0x4F) |
date |
-17(0x50) |
year-month-interval* |
-18(0x51) |
time |
-19(0x52) |
time-interval* |
-20(0x53) |
datetime |
-21(0x54) |
day-time-interval* |
-22(0x55) |
collection |
-23(0x56) |
uniform-collection |
-24(0x57) |
array |
-25(0x58) |
uniform-array |
-26(0x59) |
sparse-array |
-27(0x5A) |
uniform-sparse-array |
-28(0x5B) |
map |
-29(0x5C) |
uniform-keys-map |
-30(0x5D) |
uniform-map |
-31(0x5E) |
identity |
-32(0x5F) |
reference |
-33以下の型IDは、型と値の組合せです。この形式は、通常使用される値のスペースを削減するために使用されます。
表E-5は、型と値を組み合せた型IDのリストです。
表E-5 型と値を組み合せた型ID
型ID | 説明 |
---|---|
-33(0x60) |
boolean:false |
-34(0x61) |
boolean:true |
-35(0x62) |
string:zero-length |
-36(0x63) |
collection:empty |
-37(0x64) |
reference:null |
-38(0x65) |
floating-point:+infinity |
-39(0x66) |
floating-point:-infinity |
-40(0x67) |
floating-point:NaN |
-41(0x68) |
int:-1 |
-42(0x69) |
int:0 |
-43(0x6A) |
int:1 |
-44(0x6B) |
int:2 |
-45(0x6C) |
int:3 |
-46(0x6D) |
int:4 |
-47(0x6E) |
int:5 |
-48(0x6F) |
int:6 |
-49(0x70) |
int:7 |
-50(0x71) |
int:8 |
-51(0x72) |
int:9 |
-52(0x73) |
int:10 |
-53(0x74) |
int:11 |
-54(0x75) |
int:12 |
-55(0x76) |
int:13 |
-56(0x77) |
int:14 |
-57(0x78) |
int:15 |
-58(0x79) |
int:16 |
-59(0x7A) |
int:17 |
-60(0x7B) |
int:18 |
-61(0x7C) |
int:19 |
-62(0x7D) |
int:20 |
-63(0x7E) |
int:21 |
-64(0x7F) |
int:22 |
ここでは、PIF-POFでサポートされる事前定義(固有)型IDのバイナリ形式について説明します。対象の型は、int、Decimal、Floating Point、Boolean、Octet、Octet String、Char、Char String、Date、Year-Month Interval、Time、Time Interval、Date-Time、Date-Time Interval、Collection、Array、Sparse Array、Key-Value Map(ディクショナリ)、IdentityおよびReferenceです。
int16
、int32
、int64
およびint128
の4つの符号付き整数型がサポートされます。ストリーム内に整数型の型IDが存在する場合は、その直後に整数値が続きます。
この4つの符号付き整数型は、その型の最大値をサポートするために必要な長さのみが異なり、共通の2の補数バイナリ形式を使用します。ストリーム内では、int16
、int32
、int64
またはint128
のいずれかの型IDの後ろに整数値が続きます。整数値が、型でサポートされる範囲(int16
は、-215から215-1、int32
は、-231から231-1、int64
は、-263から263-1、int128
は-2127から2127-1)に含まれない場合、結果は未定義になり、ビット単位の切捨てまたは例外が発生することがあります。
さらに簡潔さを保つためにint
の指定と値をシングル・バイトに結合する型IDがいくつかあります。その結果、それらの型IDに値が組み込まれるため、ストリーム内で型IDの後に整数値が続くことはなくなります。
表E-6は、これらの型IDを示しています。
表E-6 intデータ型と値を結合する型ID
値 | int16 | int32 | int64 | int128 |
---|---|---|---|---|
0 |
0x69 |
0x69 |
0x69 |
0x69 |
1 |
0x6A |
0x6A |
0x6A |
0x6A |
2 |
0x6B |
0x6B |
0x6B |
0x6B |
99 |
0x40A301 |
0x41A301 |
0x42A301 |
0x43A301 |
9999 |
0x408F9C01 |
0x418F9C01 |
0x428F9C01 |
0x438F9C01 |
-1 |
0x68 |
0x68 |
0x68 |
0x68 |
-2 |
0x4041 |
0x4141 |
0x4241 |
0x4341 |
-99 |
0x40E201 |
0x41E201 |
0x42E201 |
0x43E201 |
-9999 |
0x40CE9C01 |
0x41CE9C01 |
0x42CE9C01 |
0x43CE9C01 |
対応するJavaデータ型は、short
(int16
)、int
(int32
)、long
(int64
)およびBigInteger
(int128
)です。BigInteger
は、非常に大きい値を表すことができるため、すべてのBigInteger
値をint128
形式でエンコードすることはできません。int128
の範囲に含まれない値は基本的にサポートされないため、例外が発生します。それを避けるには、文字列エンコードなどの別のエンコードを使用します。
数値データ型を効率的に表せるようにするため、ストリームの受信側によって整数型が次のいずれかの型に強制変換されます。
表E-7 他の型に強制変換される可能性がある整数型の型ID
型ID | 説明 |
---|---|
-1(0x40) |
int16 |
-2(0x41) |
int32 |
-3(0x42) |
int64 |
-4(0x43) |
int128 |
-5(0x44) |
float32 |
-6(0x45) |
float64 |
-7(0x46) |
float128 |
-8(0x47) |
decimal32 |
-9(0x48) |
decimal64 |
-10(0x49) |
decimal128 |
-12(0x4B) |
octet |
-14(0x4D) |
char |
言い換えると、受信側でストリームから前述のいずれかの型が読み込まれ、エンコードされた整数値が検出されると、その値が所定の型に自動的に変換されることになります。この機能によって、一連の一般的な(つまり、重要度が大きくない)オクテット型、文字型、整数型、10進型および浮動小数点型の値は、単一オクテット整数型(-41から-64の範囲の型ID)を使用してエンコードされるようになります。
無署名のデータ型については、整数値-1が、octet
型では0XFFに、char
型では0xFFFFに変換されます(char
型の場合、これは、UTF-16プラットフォームのエンコードを意味するように思われますが、ストリーム形式の明示的な要件のいずれにも違反していません)。
decimal32
、decimal64
およびdecimal128
の3つの浮動小数点10進型がサポートされます。ストリーム内に10進型の型IDが存在する場合は、その直後に2つのパック整数値が続きます。最初の整数値は非スケール値で、その次の値はスケール値です。これらの値は、JavaのBigDecimal
クラスのコンストラクタjava.math.BigDecimal(BigInteger unscaledVal, int scale)
のパラメータに相当します。
「整数型の強制変換」で説明している、整数値からサポートされる10進値への強制変換に加え、表E-8にリストされた定数型と値IDも、IEEE 754rでサポートされる特別な値の指定に使用されます。
表E-8 10進値を示すことが可能な型ID
型ID | 説明 |
---|---|
-38(0x65) |
floating-point:+infinity |
-39(0x66) |
floating-point:-infinity |
-40(0x67) |
floating-point:NaN |
Javaには標準の(つまり、移植可能な)10進型はなく、BigDecimal
が実装されていますが、これは本来Javaの暗号インフラストラクチャでの内部使用を目的としたものです。Javaでは、正および負の無限大の10進値および非数(NaN
)はサポートされません。
float32
、float64
およびfloat128
の3つのbase2浮動小数点型がサポートされます。ストリーム内に浮動小数点型の型IDが存在する場合は、その直後に、バイナリ形式がIEEE 754/IEEE754rで定義された固定長の浮動小数点値が続きます。浮動小数点数は、IEEE754形式を使用してストリームに書き込まれ、float128
型の場合はIEEE754r形式が使用されます。
「整数型の強制変換」で説明している、整数値から10進値への強制変換に加え、表E-9の定数も、IEEE-754でサポートされる特殊な値の指定に使用されます
表E-9 IEEE 754の特殊な値を示すことが可能な型ID
型ID | 説明 |
---|---|
-38(0x65) |
floating-point:+infinity |
-39(0x66) |
floating-point:-infinity |
-40(0x67) |
floating-point:NaN |
IEEE-754で定義されるその他の特殊な値は、完全な32ビット、64ビットまたは128ビット形式を使用してエンコードされますが、すべてのプラットフォームでサポートされるわけではありません。特に、Javaでは、これらを区別する手段がなく、NaN
値が1つだけサポートされます。
ストリーム内にBooleanの型IDが存在する場合は、その後に整数値が続きます。整数値ゼロはブール値false
を、それ以外の整数値はすべてtrue
を表します。
「整数型の強制変換」で説明しているように、ブール値をエンコードすることはできますが、ブール型の値はtrue
とfalse
のみです。したがって、ブール値に対して予期されるバイナリ形式は、表E-10に示された事前定義(および圧縮)された形式のみになります。
ストリーム内にOctetの型IDが存在する場合は、その後に範囲0から255(0x00から0xFF)で定義されたオクテット値自体が続きます。「整数型の強制変換」で説明しているように、オクテット値には整数値の圧縮形式を使用可能であり、整数値-1は0xFFに変換されます。
表E-11は、オクテット値として使用可能な整数値のリストです。
ストリーム内にOctet Stringの型IDが存在する場合は、その後に文字列の長さを表す整数値nが続き、さらにその後にn個のオクテット値が続きます。
長さゼロのOctet Stringは、型IDのstring:zero-lengthを使用してエンコードされます。
ストリーム内にCharの型IDが存在する場合は、その後にUTF-8エンコード文字が続きます。「整数型の強制変換」で説明しているように、文字型値には整数値の圧縮形式を使用可能であり、整数値-1は0xFFFFに変換されます。
例E-3は、オクテット・ストリームへの文字型値の書込みを示しています。
例E-3 オクテット・ストリームへの文字型値の書込み
public static void writeChar(DataOutput out, int ch) throws IOException { if (ch >= 0x0001 && ch <= 0x007F) { // 1-byte format: 0xxx xxxx out.write((byte) ch); } else if (ch <= 0x07FF) { // 2-byte format: 110x xxxx, 10xx xxxx out.write((byte) (0xC0 | ((ch >>> 6) & 0x1F))); out.write((byte) (0x80 | ((ch ) & 0x3F))); } else { // 3-byte format: 1110 xxxx, 10xx xxxx, 10xx xxxx out.write((byte) (0xE0 | ((ch >>> 12) & 0x0F))); out.write((byte) (0x80 | ((ch >>> 6) & 0x3F))); out.write((byte) (0x80 | ((ch ) & 0x3F))); } }
例E-4は、オクテット・ストリームからの文字型値の読込みを示しています。
例E-4 オクテット・ストリームからの文字型値の読込み
public static char readChar(DataInput in) throws IOException { char ch; int b = in.readUnsignedByte(); switch ((b & 0xF0) >>> 4) { case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: // 1-byte format: 0xxx xxxx ch = (char) b; break; case 0xC: case 0xD: { // 2-byte format: 110x xxxx, 10xx xxxx int b2 = in.readUnsignedByte(); if ((b2 & 0xC0) != 0x80) { throw new UTFDataFormatException(); } ch = (char) (((b & 0x1F) << 6) | b2 & 0x3F); break; } case 0xE: { // 3-byte format: 1110 xxxx, 10xx xxxx, 10xx xxxx int n = in.readUnsignedShort(); int b2 = n >>> 8; int b3 = n & 0xFF; if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) { throw new UTFDataFormatException(); } ch = (char) (((b & 0x0F) << 12) | ((b2 & 0x3F) << 6) | b3 & 0x3F); break; } default: throw new UTFDataFormatException( "illegal leading UTF byte: " + b); } return ch; }
ストリーム内にChar Stringの型IDが存在する場合は、その後にUTF-8で表される文字列の長さnをオクテットで表した整数と、このUTF-8のエンコードを構成するn個のオクテット値が続きます。文字列-エンコード形式の長さは、文字の長さではなくオクテットの長さであることに注意してください。
長さゼロのChar Stringは、string:zero-length
型IDによってエンコードされます。表E-12は、Char String形式を示しています。
Date値は、ISO8601セマンティックを使用して渡されます。ストリーム内にDateの型IDが存在する場合は、その後にISO8601で定義されている範囲の年、月および日を表す3つの整数値が続きます。
Time値は、ISO8601セマンティックを使用して渡されます。ストリーム内にTimeの型IDが存在する場合は、その後に5つの整数値が続き、さらにその後に2つの整数値が続く場合もあります。最初の4つの整数値は、時間、分、秒および小数秒を示します。小数秒は、次の3つの方法のいずれかでエンコードされます。
0は、小数秒がないことを示します。
[1..999]は、ミリ秒数を示します。
[-1..-999999999]は、ナノ秒の負の数値を示します。
5番目の整数値は、タイム・ゾーンのインジケータであり、次の3つの方法のいずれかでエンコードされます。
0は、タイム・ゾーンがないことを示します。
1は、協定世界時(UTC)を示します。
2は、タイム・ゾーンのオフセットを示し、その後にISO8601に従って時間のオフセットと分のオフセットを表す2つの整数値が続きます。
小数部とタイム・ゾーンを様々な方法でエンコードできると、Time値の解析は複雑になりますが、バイナリが非常に簡潔になるだけではなく、ISO8601標準および時計の精度のばらつきにより完全に対応できるようになります。Time値には小数エンコードやミリ秒エンコードがないことも多くありますが、長期的には時間分解能は精密化される傾向にあります。
Date-Time値は、ISO8601セマンティックを使用して渡されます。ストリーム内にDate-Timeの型IDが存在する場合は、その後にDate値およびTime値を構成する整数値に対応する8から10の整数値が続きます。
ストリーム内にDay-Time Intervalの型IDが存在する場合は、その後に日、時間、分、秒およびナノ秒単位の間隔を表す5つの整数値が続きます。
バッグ、セットまたはリストなどの値のコレクションは、POFストリームでCollection型を使用してエンコードされます。ストリーム内では、型IDの直後に、コレクション内の値の数を示す0以上の整数で表されたコレクション・サイズが続きます。コレクション・サイズの後ろには、コレクションの最初の値(存在する場合)自体が、値としてエンコードされて続きます。コレクションの値は連続しており、ストリームにはちょうどn個の値が存在します。nは、コレクションのサイズと同じです。
コレクションのすべての値が同じ型である場合は、共通コレクション形式が使用されます。ストリームでは、型ID(uniform-collection)の直後にコレクションの共通型の値が書き込まれ、その後ろにコレクションのサイズを表す整数値nが続き、さらにn個の値が型IDなしで続きます。共通コレクション内の値にはIDを割り当てることはできず、(明示的な型エンコードの副作用として)空の共通コレクションにはコンテンツの型が明示的に指定されることに注意してください。
表E-13は、複数値のコレクションおよび共通コレクション形式の例を示しています。
POFストリームでは、値の索引付き配列が、Array型を使用してエンコードされます。ストリーム内では、型IDの直後に、配列内の要素数を示す0以上の整数で表された配列サイズが続きます。配列サイズの後ろには、配列内に1つ以上の要素が存在する場合は、配列の最初の要素の値(ゼロ索引)が続きますが、それ自体も値としてエンコードされています。配列の要素の値は連続しており、ストリームにはちょうどn個の値が存在します。nは、配列のサイズと同じです。
配列の要素のすべての値が同じ型である場合は、共通配列形式が使用されます。ストリームでは、型ID(uniform-array)の直後に配列の要素の共通型の値が書き込まれ、その後ろに配列のサイズを表す整数値nが続き、さらにn個の値が型IDなしで続きます。共通配列内の値にはIDを割り当てることはできず、(明示的な型エンコードの副作用として)空の共通配列には配列の要素の型が明示的に指定されることに注意してください。
表E-14は、複数値の配列および共通配列形式の例を示しています。
要素値がスパースである配列には、Sparse Array形式を使用すると索引を明示的にエンコードできるほか、欠落した索引にはデフォルト値が設定されます。デフォルト値は、ブール型の場合はfalse、数値型、オクテット型および文字型の場合はゼロ、参照型の場合はすべてnullになります。スパース配列は、型ID(sparse-array)の後ろに、配列サイズを表す整数値nが続き、その後にn個以内の索引/値のペアが続く形式になっています。このペアはそれぞれ、直前の要素の配列索引より大きな値の整数値i(0 <= i < n)としてエンコードされた配列索引と、値としてエンコードされた要素値で構成されます。スパース配列は最終的に無効索引-1で終了します。
スパース配列の要素のすべての値が同じ型である場合は、共通スパース配列形式が使用されます。ストリームでは、型ID(uniform-sparse-array)の直後に、スパース配列の要素の共通型の値が書き込まれ、その後に配列サイズを表す整数値nが続き、さらにその後にn個以内の索引/値のペアが続きます。このペアはそれぞれ、直前の要素の配列索引より大きな値の整数値i(0 <= i < n)としてエンコードされた配列索引と、型IDなしの値としてエンコードされた要素値で構成されます。共通スパース配列は最終的に無効索引-1で終了します。共通スパース配列内の値にはIDを割り当てることはできず、(明示的な型エンコードの副作用として)空の共通スパース配列には配列の要素の型が明示的に指定されることに注意してください。
表E-15は、複数値のスパース配列および共通スパース配列の例を示しています。
キー/値のペアには、Key-Value Map(ディクショナリ・データ構造とも呼ばれます)形式が使用されます。Key-Value Mapバイナリ・エンコードには次の3つの形式があります。
汎用的なmap
エンコードは、キーと値のシーケンスです。
uniform-keys-map
エンコードは、共通型のキーとそれに対応する値のシーケンスです。
uniform-map
エンコードは、共通型のキーと、共通型の対応値のシーケンスです。
Key-Value Mapは、型ID(map)の後に、キー-値マップのサイズを表す整数値nが続き、さらにその後に、値としてエンコードされたキーと値としてエンコードされた対応値で構成される、n個のキー/値のペアが続く形式になっています。
表E-16は、キー/値のペアとそれに対応するバイナリ形式のいくつかの例を示しています。
表E-16 キー/値のペアのバイナリ形式
値 | バイナリ形式 |
---|---|
0x63(または0x5B00) |
|
1="ok" |
0x5B016A4E026F6B |
1="ok", 2="no" |
0x5B026A4E026F6B6B4E026E6F |
Key-Value Mapのすべてのキーの型が共通の場合、エンコードではさらに簡潔な形式が使用されます。型ID(uniform-keys-map)で始まり、その後にKey-Value Mapのキーの共通型の型IDが続き、さらにその後にKey-Value Mapのサイズを表す整数値n、値としてエンコードされたキー(型IDなし)および値としてエンコードされた対応値で構成されるn個のキー/値ペアが続きます。
表E-17は、キーの型が共通の場合の、キー/値のペアのバイナリ形式の例をいくつか示しています。
表E-17 キーの型が共通の場合の、キー/値のペアのバイナリ形式
値 | バイナリ形式 |
---|---|
0x63(または0x5C4100) |
|
1="ok" |
0x5C4101014E026F6B |
1="ok", 2="no" |
0x5C4102014E026F6B024E026E6F |
Key-Value Mapのすべてのキーの型が共通で、マップのすべての対応値の型も共通である場合、エンコードではさらに簡潔な形式が使用されます。型ID(uniform-map)で始まり、その後にKey-Value Mapのキーの共通型の型IDが続き、さらにその後にKey-Value Mapの値の共通型の型ID、Key-Value Mapのサイズを表す整数値n、型IDなしで値としてエンコードされたキーと型IDなしで値としてエンコードされた対応値で構成されるn個のキー/値のペアの順に続きます。
表E-18は、キーと値の型が共通の場合の、キー/値のペアのバイナリ形式の例をいくつか示しています。
ストリーム内にIdentityの型IDが存在する場合は、その後にIDを表す整数値が続きます。IDの後には識別される値が続きますが、それ自体が値としてエンコードされています。
POFストリーム内で複数回出現する値にはすべてIDのラベルが付けられ、同じPOFストリーム内の2回目以降に出現する値は、参照に置き換えられます。「by reference」セマンティックをサポートするプラットフォームの場合、IDは、実際のオブジェクトIDのシリアライズ形式を表します。
IDは、ゼロ以上の整数値です。POFストリーム内の値には最大で1つのIDがあります。共通データ構造内の値にはIDを割り当てることができます。
Referenceは、現在のPOFストリーム内ですでに出現しているIDに対するポインタまたはnullポインタです。
「by reference」セマンティックをサポートするプラットフォームの場合、POFストリーム内の参照は、実現された(デシリアライズされた)オブジェクト内の参照となり、POFストリーム内のnull参照は、実現されたオブジェクト内のnull参照になります。「by reference」セマンティックをサポートしないプラットフォームの場合や、POFストリームで非参照値(例: Javaのプリミティブ・プロパティ)に対するnull参照が出現した場合には、その型の値のデフォルト値が使用されます。
表E-19は、いくつかの「by reference」セマンティックのバイナリ形式の例を示しています。
前方参照および外部参照のサポートは、POFでは必要ありません。POFでは、参照されるIDとそのIDが参照する値の両方が、そのPOFストリーム内にすでに出現しています。前者の場合、未出現のIDは参照されません。また、後者の場合は、複雑な値(コレクションやユーザー定義型など)内からその複雑な値自体が参照されることはありません。
非固有の型はすべてユーザー定義型と呼ばれます。ユーザー定義型は、それぞれが型IDを持つゼロ個以上の索引付きの値(フィールド、プロパティおよび属性とも呼ばれます)で構成されます。さらに、ユーザー定義型は、バージョニングされており、上位および下位互換性をサポートしています。
ユーザー定義型には、ゼロ以上の値の型IDが指定されます。型ID自体は、ストリーム内で明示的または自己記述的な意味を持つわけではありません。つまり、値には型(またはクラス)定義は含まれません。そのかわりに、エンコーダ(送信側)とデコーダ(受信側)がコンテキストと呼ばれる情報を暗黙のうちに共有します。このコンテキストには、ユーザー定義型の定義などの必要なメタデータが含まれます。
ユーザー定義型のバイナリ形式は、スパース配列と非常によく似ています。ユーザー定義型は、概念的にはプロパティ値のスパース配列と見なすことができます。ユーザー定義型は、型ID(ゼロ以上の整数値)の後ろに、バージョンID(ゼロ以上の整数値)が続き、その後に直前のプロパティ索引より大きい値の整数値i(0 <= i)としてエンコードされるプロパティ索引と、値としてエンコードされるプロパティ値で構成される、索引/値のペアが続く形式になっています。ユーザー定義型は、最終的に-1の無効なプロパティ索引で終了します。
スパース配列と同様に、ユーザー定義型のエンコードに含まれないプロパティには、デフォルト値が設定されます。デフォルト値は、ブール型の場合はfalse、数値型、オクテット型および文字型の場合はゼロ、参照型の場合はすべてnullになります。
ユーザー定義型のバージョニングでは、ユーザー定義型へのプロパティの追加がサポートされますが、以前のバージョンの既存プロパティの置換や削除はサポートされません。一般的なバイナリ規定にバージョニング機能を組み込むことによって、下位および上位の互換性のサポートが可能になります。
送信側がバージョンv1のユーザー定義型値を、それに相当するバージョンv2のユーザー定義型をサポートする受信側に送信した場合、受信側は、v2のユーザー定義型に存在し、v1には存在しない追加プロパティに対して、デフォルト値を使用します。
送信側がバージョンv2のユーザー定義型値を、それに相当するバージョンv1のユーザー定義型しかサポートしない受信側に送信した場合、受信側は、v2のユーザー定義型に存在し、v1には存在しない追加プロパティを、不明なプロパティとして処理します。受信側で、値を(永続的に)保存する必要がある場合や、値を後で送信する可能性がある場合、受信側では、後日エンコードできるように、それらの不明な追加プロパティを保存しておきます。その場合、受信側が型指定された形式またはバイナリ形式で不明なプロパティ値を取り出すことができるようにするための十分な型情報が含まれます。受信側がこのユーザー定義型を再エンコードする場合は、元のままの状態のv2のプロパティが含まれているため、バージョン・インジケータv2を使用する必要があります。