001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.util; 017 018import java.util.MissingResourceException; 019import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04) 020import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring 021import java.util.List; 022import java.util.ArrayList; 023import java.util.Iterator; 024import java.util.Collections; // 6.3.9.0 (2015/11/06) 025import java.util.Objects; // 8.4.2.2 (2023/03/17) ハッシュコード求め 026 027import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 028import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 029import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 030import org.opengion.fukurou.system.DateSet; // 6.4.2.0 (2016/01/29) 031 032/** 033 * AbstractObjectPool は、生成された Object をプールするキャッシュクラスです。 034 * サブクラスで、各クラスごとにオブジェクトを生成/初期化/終了するように各メソッドを 035 * コーディングしなおしてください。 036 * サブクラスでは、Object createInstance() と、<del>void objectInitial( Object obj )、8.5.4.2 (2024/01/12) </del> 037 * void objectFinal( Object obj ) を オーバーライドしてください。 038 * 039 * ※ 8.5.4.2 (2024/01/12) objectInitial の廃止(実質 使われていなかった)と、objectFinal の abstract 化 040 * 041 * @version 4.0 042 * @author Kazuhiko Hasegawa 043 * @since JDK5.0, 044 * 045 * @param <E> 型オブジェクト 046 */ 047public abstract class AbstractObjectPool<E> { 048 049 /** 内部でオブジェクトをプールしている配列。 */ 050 private List<E> pool ; // プールしているオブジェクト 051 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 052 private final ConcurrentMap<Integer,TimeStampObject> poolBkMap = new ConcurrentHashMap<>(); // 作成したオブジェクトのタイムスタンプ管理 053 private final Object lock = new Object(); // 6.3.9.0 (2015/11/06) ロック用のオブジェクト。poolとpoolBkMapを同時にロックする。 054 055 /** プール自体を拡張可能かどうかを決める変数。拡張制限(true)/無制限(false) */ 056 private boolean limit ; 057 058 /** 最大オブジェクト数 */ 059 private int maxsize ; 060 061 /** 生成したオブジェクトの寿命(秒)を指定します。 0 は、制限なしです。*/ 062 private int limitTime ; // 3.5.4.3 (2004/01/05) キャッシュの寿命を指定します。 063 064 /** 制限なしの場合でも、実質この値以上のキャッシュは、許可しません。*/ 065 private static final int MAX_LIMIT_COUNT = 1000 ; // 3.6.0.8 (2004/11/19) 066 067 /** 068 * デフォルトコンストラクター 069 * 070 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 071 */ 072 protected AbstractObjectPool() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 073 074 /** 075 * 初期化メソッド 076 * 077 * 初期オブジェクト数、最大オブジェクト数、拡張制限を指定します。 078 * 079 * 初期オブジェクト数は、プールを作成すると同時に確保するオブジェクトの個数です。 080 * オブジェクトの生成に時間がかかり、かつ、必ず複数使用するのであれば、 081 * 予め複数確保しておけば、パフォーマンスが向上します。 082 * 最大オブジェクト数は、拡張制限が、無制限(limit = false )の場合は、 083 * 無視されます。制限ありの場合は、この値を上限に、オブジェクトを増やします。 084 * 拡張制限は、生成するオブジェクト数に制限をかけるかどうかを指定します。 085 * 一般に、コネクション等のリソースを確保する場合は、拡張制限を加えて、 086 * 生成するオブジェクト数を制限します。 087 * 088 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 089 * 090 * @param minsize 初期オブジェクト数 091 * @param maxsize 最大オブジェクト数 092 * @param limit 拡張制限(true)/無制限(false) 093 */ 094 protected void init( final int minsize, final int maxsize, final boolean limit ) { 095 init( minsize, maxsize, limit,0 ) ; 096 } 097 098 /** 099 * 初期化メソッド 100 * 101 * 初期オブジェクト数、初期配列数、拡張制限、オブジェクトの寿命を指定します。 102 * 103 * 初期オブジェクト数、初期配列数、拡張制限、までは、{@link #init( int , int , boolean ) init} 104 * を参照してください。 105 * オブジェクトの寿命は、生成された時間からの経過時間(秒)だけ、キャッシュしておく 106 * 場合に使用します。 107 * 例えば、コネクション等で、長期間のプーリングがリソースを圧迫する場合や、 108 * 接続側自身が、タイマーで切断する場合など、オブジェクトの生存期間を 109 * 指定して管理する必要があります。 110 * 111 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 112 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 113 * 114 * @param minsize 初期オブジェクト数 115 * @param maxsize 初期配列数 116 * @param limit 拡張制限(true)/無制限(false) 117 * @param limitTime オブジェクトの寿命の時間制限値(秒) 118 * @see #init( int , int , boolean ) 119 */ 120 protected void init( final int minsize, final int maxsize,final boolean limit,final int limitTime ) { 121 this.maxsize = maxsize; 122 this.limit = limit; 123 this.limitTime = limitTime; 124 synchronized( lock ) { 125 pool = Collections.synchronizedList( new ArrayList<>( maxsize ) ); // 6.3.9.0 (2015/11/06) 126 poolBkMap.clear(); // 6.4.3.1 (2016/02/12) 127 for( int i=0; i<minsize; i++ ) { 128 final E obj = createInstance(); 129 pool.add( obj ); 130 131 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 132// final Integer key = Integer.valueOf( obj.hashCode() ); 133 final Integer key = obj.hashCode(); 134 poolBkMap.put( key,new TimeStampObject( obj,limitTime ) ); 135 } 136 } 137 } 138 139 /** 140 * キャッシュのインスタンスを返します。 141 * 142 * なお、拡張制限をしている場合に、最初に確保した数以上のオブジェクト生成の 143 * 要求があった場合は、 MissingResourceException が throw されます。 144 * また、オブジェクトが寿命を超えている場合は、削除した後、新たに次の 145 * オブジェクトの生成を行います。 146 * 147 * @og.rev 4.0.0.1 (2007/12/03) 生成リミットチェックを厳密に行う。 148 * @og.rev 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。 149 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 150 * 151 * @return キャッシュのインスタンス 152 * @throws MissingResourceException 拡張制限により、新しいインスタンスを生成できない場合 153 */ 154// public E newInstance() throws MissingResourceException { 155 public E newInstance() { // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidUncheckedExceptionsInSignatures 156 final E rtnobj ; 157 synchronized( lock ) { 158 if( pool.isEmpty() ) { 159 if( limit && poolBkMap.size() >= maxsize ) { 160 final String errMsg = "生成リミットいっぱいで新たに生成できません。[" 161 + poolBkMap.size() + "]"; 162 163 // 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。 164 final Iterator<TimeStampObject> itr = poolBkMap.values().iterator(); 165 while( itr.hasNext() ) { 166 final TimeStampObject tso = itr.next(); 167 if( tso == null || tso.isTimeOver() ) { 168 itr.remove(); 169 } 170 } 171 172 throw new MissingResourceException( errMsg,getClass().getName(),"limit" ); 173 } 174 else if( poolBkMap.size() > MAX_LIMIT_COUNT ) { 175 clear(); // 全件キャッシュを破棄します。 176 final String errMsg = "ObjectPool で、メモリリークの可能性があります。size=[" 177 + poolBkMap.size() + "]"; 178 throw new OgRuntimeException( errMsg ); 179 } 180 // 新規作成 181 rtnobj = createInstance(); 182 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 183// final Integer key = Integer.valueOf( rtnobj.hashCode() ); 184 final Integer key = rtnobj.hashCode(); 185 poolBkMap.put( key,new TimeStampObject( rtnobj,limitTime ) ); 186 } 187 else { 188 // 既存取り出し 189 rtnobj = pool.remove(0); 190 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 191 if( rtnobj == null ) { 192 // 通常ありえない。 193 final String errMsg = "オブジェクトの取得に失敗しました。" ; 194 throw new MissingResourceException( errMsg,getClass().getName(),"pool" ); 195 } 196 197 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 198// final Integer key = Integer.valueOf( rtnobj.hashCode() ); 199 final Integer key = rtnobj.hashCode(); 200 final TimeStampObject tso = poolBkMap.get( key ); 201 if( tso == null || tso.isTimeOver() ) { 202 remove( rtnobj ); 203 return newInstance(); 204 } 205 } 206 } 207 return rtnobj; 208 } 209 210 /** 211 * 具体的に新しいインスタンスを生成するメソッド。 212 * 213 * サブクラスで具体的に記述する必要があります。 214 * 215 * @return 新しいインスタンス 216 */ 217 protected abstract E createInstance(); 218 219 /** 220 * オブジェクトを、オブジェクトプールに戻します。 221 * 戻すべきオブジェクトが null の場合は、削除されたと判断します。 222 * 223 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 224 * @og.rev 8.5.4.2 (2024/01/12) objectInitial 廃止 225 * 226 * @param obj2 オブジェクトプールに戻すオブジェクト 227 */ 228 public void release( final E obj2 ) { 229// final E obj2 = objectInitial( obj ); 230 if( obj2 != null ) { 231 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 232// final Integer key = Integer.valueOf( obj2.hashCode() ); 233 final Integer key = obj2.hashCode(); 234 synchronized( lock ) { 235 final TimeStampObject tso = poolBkMap.get( key ); 236 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 237 if( tso == null ) { 238 // 6.0.2.5 (2014/10/31) Ctrl-C で終了させると、なぜか poolBkMap から、オブジェクトが消える。原因不明???? 239 // LogWriter.log( "ObjectPool で、メモリリークの可能性がある。obj=[" + obj + "]" ); 240 remove( obj2 ); 241 } 242 else { 243 pool.add( obj2 ); 244 } 245 } 246 } 247 } 248 249 /** 250 * オブジェクトを、オブジェクトプールから削除します。 251 * remove されるオブジェクトは、すでにキャッシュから取り出された後なので、 252 * そのまま、何もしなければ自然消滅(GC)されます。 253 * 自然消滅する前に、objectFinal( Object ) が呼ばれます。 254 * 生成されたオブジェクトの総数も、ひとつ減らします。 255 * 256 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 257 * 258 * @param obj 削除するオブジェクト 259 */ 260 public void remove( final E obj ) { 261 if( obj != null ) { 262 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 263// final Integer key = Integer.valueOf( obj.hashCode() ); 264 final Integer key = obj.hashCode(); 265 synchronized( lock ) { 266 poolBkMap.remove( key ); 267 } 268 } 269 270 objectFinal( obj ); 271 } 272 273 /** 274 * オブジェクトプールの要素数を返します。 275 * 276 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 277 * 278 * @return プールの要素数 279 */ 280 public int size() { 281 synchronized( lock ) { 282 return poolBkMap.size(); 283 } 284 } 285 286 /** 287 * オブジェクトプールが要素を持たないかどうかを判定します。 288 * 289 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 290 * 291 * @return オブジェクトプールが要素を持っていない、つまりそのサイズが 0 の場合にだけ true、そうでない場合は false 292 */ 293 public boolean isEmpty() { 294 synchronized( lock ) { 295 return poolBkMap.isEmpty() ; 296 } 297 } 298 299 /** 300 * すべての要素を オブジェクトプールから削除します。 301 * 貸し出し中のオブジェクトは、クリアしません。よって、返り値は、 302 * すべてのオブジェクトをクリアできた場合は、true、貸し出し中の 303 * オブジェクトが存在した場合(クリアできなかった場合)は、false です。 304 * 305 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 306 * 307 * @return すべてクリア(true)/貸し出し中のオブジェクトが残っている(false) 308 */ 309 public boolean clear() { 310 synchronized( lock ) { 311 final Iterator<E> itr = pool.iterator(); 312 while( itr.hasNext() ) { 313 remove( itr.next() ); 314 } 315 pool.clear(); 316 317 // 貸し出し中の場合は、remove 出来ない為、poolBkMap に残っている。 318 // それでも、poolBkMap をクリアすることで、release 返却時にも、 319 // remove されるようになります。 320 // ただし、作成オブジェクト数が、一旦 0 にリセットされる為、 321 // 最大貸し出し可能数が、一時的に増えてしまいます。 322 final boolean flag = poolBkMap.isEmpty(); 323 poolBkMap.clear(); 324 325 return flag; 326 } 327 } 328 329 /** 330 * オブジェクトプールから削除するときに呼ばれます。 331 * このメソッドで各オブジェクトごとの終了処理を行います。 332 * 例えば、データベースコネクションであれば、close() 処理などです。 333 * 334 * デフォルトでは、なにも行いません。 335 * 336 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 337 * @og.rev 8.5.4.2 (2024/01/12) abstract メソッド化 338 * 339 * @param obj 終了処理を行うオブジェクト 340 */ 341 protected abstract void objectFinal( final E obj ); 342// protected void objectFinal( final E obj ) { 343// // ここでは処理を行いません。 344// } 345 346// /** 347// * オブジェクトプールに戻すとき(release するとき)に呼ばれます。 348// * このメソッドで各オブジェクトごとの初期処理を行います。 349// * オブジェクトプールに戻すときには、初期化して、次の貸し出しに 350// * 対応できるように、初期処理しておく必要があります。 351// * 352// * デフォルトでは、引数のオブジェクトをそのまま返します。 353// * 354// * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 355// * @og.rev 8.5.4.2 (2024/01/12) objectInitial 廃止 356// * 357// * @param obj 初期処理を行うオブジェクト 358// * 359// * @return 初期処理を行ったオブジェクト 360// */ 361// protected E objectInitial( final E obj ) { 362// return obj; 363// } 364 365 /** 366 * 内部状況を簡易的に表現した文字列を返します。 367 * 368 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD) 369 * 370 * @return このオブジェクトプールの文字列表現 371 * @og.rtnNotNull 372 */ 373 @Override // Object 374 public String toString() { 375 synchronized( lock ) { 376 // 6.0.2.5 (2014/10/31) char を append する。 377 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ConsecutiveLiteralAppends 対応 378 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 379 .append( " freeCount = [" ).append( pool.size() ).append( ']' ).append( CR ) 380 .append( " createCount = [" ).append( poolBkMap.size() ) 381 .append( "] ( max=[" ).append( maxsize ).append( "] )" ).append( CR ) 382 .append( " limiter = [" ).append( limit ).append( ']' ).append( CR ) 383 .append( " limitTime = [" ).append( limitTime ).append( "](s)" ).append( CR ); 384 385 final Iterator<E> itr = pool.iterator(); 386 buf.append( "Free Objects " ).append( CR ); 387 while( itr.hasNext() ) { 388 final E obj = itr.next(); 389 if( obj != null ) { 390 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 391// final Integer key = Integer.valueOf( obj.hashCode() ); 392 final Integer key = obj.hashCode(); 393 buf.append( ' ' ).append( poolBkMap.get( key ) ) 394 .append( ' ' ).append( obj ).append( CR ); 395 } 396 } 397 return buf.toString(); 398 } 399 } 400} 401 402/** 403 * TimeStampObject は、生成された Object を、生成時刻とともに管理するクラスです。 404 * 内部のハッシュキーは、登録するオブジェクトと同一で、管理できるのは、異なるオブジェクト 405 * のみです。 406 * 407 * @version 4.0 408 * @author Kazuhiko Hasegawa 409 * @since JDK5.0, 410 */ 411// 8.5.5.1 (2024/02/29) spotbugs CT_CONSTRUCTOR_THROW(コンストラクタで、Excweptionを出さない) class を final にすれば、警告は消える。 412// class TimeStampObject implements Comparable<TimeStampObject> { // 4.3.3.6 (2008/11/15) Generics警告対応 413final class TimeStampObject implements Comparable<TimeStampObject> { // 4.3.3.6 (2008/11/15) Generics警告対応 414 private final long timeStamp ; 415 private final long limitTime ; 416 private final String objStr ; // 6.0.2.5 (2014/10/31) 表示用 417 private final int hcode ; 418 419 /** 420 * コンストラクター。 421 * 422 * @og.rev 8.4.2.2 (2023/03/17) ハッシュコード求めに、java.util.Objects#hash を使用します。 423 * 424 * @param obj 管理するオブジェクト 425 * @param limit オブジェクトの寿命(秒) 426 * @throws IllegalArgumentException TimeStampObject のインスタンスに、NULL はセットできません。 427 */ 428 public TimeStampObject( final Object obj,final int limit ) { 429 if( obj == null ) { 430 final String errMsg = "TimeStampObject のインスタンスに、NULL はセットできません。" ; 431 throw new IllegalArgumentException( errMsg ); 432 } 433 objStr = String.valueOf( obj ); // 6.0.2.5 (2014/10/31) 表示用 434 435 timeStamp = System.currentTimeMillis(); 436 if( limit > 0 ) { 437 limitTime = timeStamp + limit * 1000L ; 438 } 439 else { 440 limitTime = Long.MAX_VALUE ; 441 } 442 443 // 8.4.2.2 (2023/03/17) ハッシュコード求めに、java.util.Objects#hash を使用します。 444// hcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) ; 445 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 446// hcode = Objects.hash( objStr, Long.valueOf( timeStamp ) ); 447 hcode = Objects.hash( objStr, timeStamp ); 448 } 449 450 /** 451 * 内部管理しているオブジェクトの生成時刻を返します。 452 * 453 * @return 生成時刻(ms) 454 */ 455 public long getTimeStamp() { 456 return timeStamp; 457 } 458 459 /** 460 * オブジェクトの寿命がきたかどうかを返します。 461 * 462 * @return 寿命判定(true:寿命/false:まだ使える) 463 */ 464 public boolean isTimeOver() { 465 return System.currentTimeMillis() > limitTime ; 466 } 467 468 /** 469 * オブジェクトが同じかどうかを判定します。 470 * 471 * 内部オブジェクトの equals() メソッドと、作成時刻の両方を判断します。 472 * 内部オブジェクトの equals() が同じでも、作成時刻が異なると、 473 * false を返します。これは、全く同一オブジェクトを管理する場合でも、 474 * タイムスタンプを差し替える事で、異なるオブジェクトとして 475 * 認識させるということです。 476 * 477 * @og.rev 8.4.2.2 (2023/03/17) ハッシュコード求めに対応した、equals に変更します。 478 * 479 * @param obj オブジェクト 480 * 481 * @return true:同じ/false:異なる。 482 */ 483 @Override // Object 484 public boolean equals( final Object obj ) { 485 if( obj instanceof TimeStampObject ) { 486 final TimeStampObject other = (TimeStampObject)obj ; 487// return hcode == other.hcode && timeStamp == other.timeStamp ; 488 return objStr.equals( other.objStr ) && timeStamp == other.timeStamp ; 489 } 490 return false ; 491 } 492 493 /** 494 * ハッシュコードを返します。 495 * 496 * ここで返すのは、自分自身のハッシュコードではなく、 497 * 内部管理のオブジェクトのハッシュコードです。 498 * 499 * <del> hashcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) </del> 500 * hcode = Objects.hash( objStr, Long.valueOf( timeStamp ) ); 501 * 502 * この計算式は、変更される可能性があります。 503 * 504 * @return 内部管理のオブジェクトのハッシュコード 505 */ 506 @Override // Object 507 public int hashCode() { return hcode; } 508 509 /** 510 * このオブジェクトと指定されたオブジェクトの順序を比較します。 511 * 512 * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、 513 * 等しい場合はゼロ、大きい場合は正の整数を返します。 514 * 515 * @param other TimeStampObject オブジェクト 516 * 517 * @return 順序比較の値 518 * @throws ClassCastException 指定されたオブジェクトがキャストできない場合。 519 * @see Comparable#compareTo(Object) 520 */ 521 @Override // Comparable 522 public int compareTo( final TimeStampObject other ) { // 4.3.3.6 (2008/11/15) Generics警告対応 523 // 8.5.5.1 (2024/02/29) 比較方法を変更します。 524 final int diff = Long.compare( timeStamp,other.timeStamp ); 525 return diff == 0 526 ? ( objStr.equals( other.objStr ) ? 0 : hcode - other.hcode ) 527 : diff ; 528 529// final long diff = timeStamp - other.timeStamp; 530// 531// if( diff > 0 ) { return 1; } 532// else if( diff < 0 ) { return -1; } 533// else { 534// if( equals( other ) ) { return 0; } 535// else { return hcode - other.hcode; } 536// } 537 } 538 539 /** 540 * このオブジェクトの内部表現を返します。 541 * 542 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。 543 * 544 * @return オブジェクトの内部表現文字列 545 * @og.rtnNotNull 546 */ 547 @Override // Object 548 public String toString() { 549 // Create Timeは、一度求めれば変わらないので、キャッシュしても良い。 550 return "[CreateTime=" + DateSet.getDate( timeStamp,"yyyy/MM/dd HH:mm:ss" ) 551 + " , TimeOver=" + (int)((limitTime - System.currentTimeMillis())/1000.0) + "(s)" 552 + " , object=" + objStr + "]" ; // 6.0.2.5 (2014/10/31) 表示用 553 } 554}