- PR -

読み取り専用オブジェクトの作成方法

1
投稿者投稿内容
未記入
会議室デビュー日: 2008/07/05
投稿数: 2
投稿日時: 2008-07-05 01:12
こんばんは。JAVA初心者で参考書を見て勉強中なのですが、解決策が思いつけないものがあったので質問させていただきます。

単純にあるオブジェクトを返却するというメソッドの作成なのですが、返却するオブジェクトをコピーして元のオブジェクトの内容が変更されないように工夫しているのですが、
その方法だとメソッドが呼ばれるたびにコピーがされる為、メモリを無駄に消費するので最善ではないと書かれています。
解決策としては、オブジェクトに対する読み取りアクセスのみを提供するインタフェースを返すといった方法があげられますと記載されていますが、具体的にどのように書けばいいのか思いつけません。
どなたかよろしければご教示願います。
また、上記インタフェースを返却する以外で良い方法があれば教えていただければ幸いです。

ちなみにコピーでの方法は以下のようになっています。

public Class XYZ
{
private XXX[][] source = new XXX[4][13];

public XXX[][] getXXX()
{
XXX[][] target = new XXX[4][13];
for (int i = 0; i < 4; i++)
{
System.arraycopy(source[i], 0, target[i], 0, 13);
}
return target;
}
}

以上、よろしくお願いします。
ぴあちゃん
ぬし
会議室デビュー日: 2008/02/07
投稿数: 287
投稿日時: 2008-07-05 02:42
コード:
public interface IReadOnly {
   public int getValue(int x, int y);
}

public class ReadWrite implements IReadOnly {
   private int[][] data = new int[4][4];

   public int getValue(int x, int y) {
      return data[y][x];
   }

   public IReadOnly getData() {
      IReadOnly iro = new IReadOnly() {
         public int getValue(int x,int y) {
             return data[y][x];
         }
      };
      return iro;
      //または return this;
   }

   public static void main(String[] args) {
      System.out.println((new ReadWrite()).getData().getValue(3,2));
   }
}


# こーゆーのかな?
Gio
ぬし
会議室デビュー日: 2003/11/28
投稿数: 350
お住まい・勤務地: 都内から横浜の間に少量発生中
投稿日時: 2008-07-05 03:04
クラス XXX の定義が書かれていないので勝手に定義します。

コード:
public interface ReadOnly {
  int getNum();
}

public interface ReadWrite {
  int getNum();
  void setNum(int n);
}

public class XXX implements ReadOnly, ReadWrite {
  private int num;

  public int getNum() {
    return num;
  }

  public void setNum(int n) {
    num = n;
  }
}

public class XYZ {
  private XXX source = new XXX();

  public ReadOnly getXXXAsReadOnly() {
     return source;
  }

  public ReadWrite getXXXAsReadWrite() {
     return source;
  }
}



XYZ#getXXXAsReadOnly() と XYZ#getXXXAsReadWrite() は同じ物を返しますが、前者では返値の型が ReadOnly としかわかりません。この型では値を変更するメソッドが提供されていないので、実体クラスが XXX であることをわかった上で明示的に XXX にダウンキャストしない限り値は変更できません。
一方、後者のメソッドで取得した場合は setNum() で値を変更できます。

私はこういうことだと解釈しましたが、勘違い等ありましたらご指摘ください。> 識者各位

蛇足:
返値型が配列の場合、「値を変更する」というのは二つの意味を持ちます。

(1) XXX[][] の要素の一つを別のオブジェクトに置き換える。
(例:target[0][0] = new XXX();)
(2) XXX[][] の要素はそのままで、オブジェクトが持つフィールドの値を変更する。
(例:target[0][0].setNum(1);)

(2) の方は上記の方法で変更を抑制できますが、(1) はどうしようもありません。
配列を直に見せずに何らかのクラスで隠蔽して、そのクラスが実装するインタフェースで要素の置き換えを禁止すれば可能です。

(書いている最中に記事を読み返したところ、ぴあちゃんさんのコメントが付いていました。
ソースがかなり被っていますがそのまま出します。
ちなみに、ぴあちゃんさんが書かれたソースは (1) を抑制する方法です。
配列の要素型が int なので、実際は (2) に対応した操作も不可能ですが。)
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-07-05 13:43
引用:

未記入さんの書き込み (2008-07-05 01:12) より:
単純にあるオブジェクトを返却するというメソッドの作成なのですが、返却するオブジェクトをコピーして元のオブジェクトの内容が変更されないように工夫しているのですが、
その方法だとメソッドが呼ばれるたびにコピーがされる為、メモリを無駄に消費するので最善ではないと書かれています。
解決策としては、オブジェクトに対する読み取りアクセスのみを提供するインタフェースを返すといった方法があげられますと記載されていますが、具体的にどのように書けばいいのか思いつけません。


配列ではなくコレクションとして、たとえばリストだったら、Java に標準で備わっているものとしては UnmodifiableList クラス(アクセス修飾子は public ではない)があります。
これは、
http://java.sun.com/javase/ja/6/docs/ja/api/java/util/Collections.html#unmodifiableList(java.util.List)
のメソッドの、戻り値となる実行時に得られる型です。

ただ、こういうのは、仕組みが難しい割には、使ってもそれほど得られるメリットがないような感じもします。ただの縛りでしかないので、そういう縛りを付けなくても、要は、「戻り値を書き換えたらダメ」や「戻り値はメソッド内で深いコピーを作って返します」のような決めを作っておけばそれでも良いように感じます。
また、メソッド getXXX の戻り値を unmodifiable にしてメソッド getYYY を unmodifiable にしないとしたら、一貫性がないことにもなります。
わたなべ
大ベテラン
会議室デビュー日: 2007/12/09
投稿数: 123
お住まい・勤務地: 札幌
投稿日時: 2008-07-05 14:01
参考書をみての話ならば、参考書のタイトルと参考ページを書いたほうがいいですね。

>単純にあるオブジェクトを返却するというメソッドの作成なのですが、返却するオブジェクトをコピーして元のオブジェクトの内容が変更されないように工夫しているのですが、
>その方法だとメソッドが呼ばれるたびにコピーがされる為、メモリを無駄に消費するので最善ではないと書かれています。

ケースバイケースなので「最善ではない」と断言できないと思います。
メモリを無駄に消費するといっても、設計(実装)が複雑になるので一長一短ですから。

>解決策としては、オブジェクトに対する読み取りアクセスのみを提供するインタフェースを返すといった方法があげられますと記載されていますが、具体的にどのように書けばいいのか思いつけません。

結局、それは解決策の1つでしかありません。
#そこまで複雑にするのはあまり良くないと「個人的には」思います。
他の解決方法として・・・
・変更されても影響がないような設計にする
・ドキュメントで明示する(内部であれば、他の開発者を信用するということです)
・コピーを返す
・同一インターフェイスで、変更をできないようにしたラッパーを作り、包んで返す
・最初から変更不可なオブジェクトにしておく

など、いろいろな方法があります。
ぶっちゃけ、すべて使われるときの事を信用しないで作っていたらとんでもないことになるので、本当に変更された場合はヤバイ所しか適用しないですけど。
かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2008-07-05 15:25
内部の配列や可変オブジェクトを返すメソッドを実装する場合、
破壊されないような返し方をするために、クローンを生成して返すというのは、
ごく普通な方法かと思います。

メモリの無駄な消費といっても、クローンされたオブジェクトを、
リークするような参照の方法をしない限り、
GCされるのであまり気にする必要はないでしょう。

呼び出し元がよっぽど信頼できないのであれば、
(内部配列の破壊、クローンのリーク等・・・)
ぴあちゃんさんのような、インデックスによるアクセスのみを公開するなど、
色々と方法があるかと思いますが、バランスの問題でしょうね。
未記入
会議室デビュー日: 2008/07/05
投稿数: 2
投稿日時: 2008-07-08 00:08
みなさん親切に回答いただきありがとうございます。
インタフェースに参照用のメソッドだけ定義して、そのインタフェースを返却すれば、
更新できないということですね。とても参考になりました。
参考書の記載でも常にこういった対応をするのではなくアプリケーションの要件によって使い分ける必要があると記載されていました。いろいろな方法を知っておかなければ選択も出来ない為、今回教えていただいたことはとてもためになりました。
ありがとうございました。
1

スキルアップ/キャリアアップ(JOB@IT)