Java 6 を使って作りなさい。
レポートには表紙をつけてください。
提出先: レポートボックス
通貨を使うクラスを考えます。 値を入れると、通貨記号を含めて表示し、また円レートを設定すると、等価な円オブジェクトを返します。
以下の設問において kaitou パッケージを作り、その中に指定されたクラスを 作成しなさい。
なお、テストクラスを用意したので、各設問 において、指示のあるテストクラスを用いてテストを行い、正常であった旨を 報告しなさい。
始めに、コンストラクタに値を入れ、toString で通貨記号付きの文字列を得 られるようにします。 interface は空ですが、 toString は java.lang.Object に登録されているの で使用できます。
package kadai;
public interface Money1 {
}
この時、この Money1 を implements し、次の Yen1, Dollar1 クラスの親クラ スになる kaitou.AbstractMoney1 クラスを定義しなさい。
package kadai;
import kaitou.AbstractMoney1;
public class Yen1 extends AbstractMoney1 {
public Yen1(double value) {
super(value);
}
@Override
protected String getPrefix() {
return "";
}
@Override
protected String getPostfix() {
return "円";
}
}
package kadai;
import kaitou.AbstractMoney1;
public class Dollar1 extends AbstractMoney1 {
public Dollar1(double value) {
super(value);
}
@Override
protected String getPrefix() {
return "$";
}
@Override
protected String getPostfix() {
return "";
}
}
これらに対して、次のようにすると「100.0円」とか「$3.0」が出力されるようにするということです。
import kadai.Dollar1;
import kadai.Money1;
import kadai.Yen1;
public class Test1 {
public static void main(String[] args) {
Money1 m1 = new Yen1(100);
Money1 m2 = new Dollar1(3);
System.out.println(m1);
System.out.println(m2);
}
}
なお、これらをテストするために Yen1Test と Dollar1Test を使用しなさい。
テンプレートメソッドを使用します。
次に、円レートを設定して、円に換算する仕組みを作ります。 Money2 インターフェイスは Money1 を継承し、getRate メソッドと、換算し た Yen2 が得られる getYen メソッドが定義されます。
package kadai;
public interface Money2 extends Money1 {
double getRate();
Money2 getYen();
}
前問同様のクラスを次のように定義します。 このクラスが正常に動作するように kaitou.AbstractMoney2 を作りなさい。 但し、kaitou.AbstractMoney2 は kaitou.AbstractMoney1 を継承し、 kadai.Money2 を implements しなさい。
package kadai;
import kaitou.AbstractMoney2;
public class Yen2 extends AbstractMoney2 {
public Yen2(double value) {
super(value);
}
@Override
protected String getPrefix() {
return "";
}
@Override
protected String getPostfix() {
return "円";
}
@Override
public double getRate() {
return 1.0;
}
public static void setYenRate(double r) {
}
}
package kadai;
import kaitou.AbstractMoney2;
public class Dollar2 extends AbstractMoney2 {
public Dollar2(double value) {
super(value);
}
@Override
protected String getPrefix() {
return "$";
}
@Override
protected String getPostfix() {
return "";
}
private static double rate;
@Override
public double getRate() {
return rate;
}
public static void setYenRate(double r) {
rate=r;
}
}
つまり、これにより次のように 3 ドルを240円に換算できます。
import kadai.Dollar2;
import kadai.Money2;
public class Test2 {
public static void main(String[] args) {
Money2 m = new Dollar2(3);
System.out.println(m);
Dollar2.setYenRate(80);
System.out.println(m.getYen());
}
}
なお、これらをテストするクラス Yen2Test と Dollar2Test を使用しなさい。
次に、大小関係を比較できるようにします。 まず、kadai.Money3 インターフェイスは Complarable を継承します。 そのため、値を比較できるようにするため、 getValue メソッドを追加します。 また、 getYen の戻す値の型を Money3 に上書きします。
package kadai;
public interface Money3 extends Money2 ,Comparable<Money3>{
double getValue();
Money3 getYen();
}
つぎに、前問同様のクラスを次のように定義します。 但し、新たに kadai.Euro クラスを導入しています。 このクラスが正常に動作するように kaitou.AbstractMoney3 を作りなさい。 但し、kaitou.AbstractMoney3 は kaitou.AbstractMoney2 を継承し、 kadai.Money3 を implements しなさい。 また、 java.lang.Comparable のマニュアルにあるように、 equals メソッド、 hashCode メソッドも実装しなさい。 なおこれらに関しては Eclipse の自動生成の機能を利用しても構わない。
equals のシグネチャは boolean equals(Object obj) なので、引数は Object 型です(java.lang.Object のマニュアル参照)。 お金が等しいことを金額が等しいこととするためには、両者を金額比較する必 要があります。 そのためには、 obj を Money3 にキャストする必要があります。 キャストできるかどうかを判定するには、 obj instanceof Money3 でチェッ クします。
AbstractMoney3 そのものは、タダでは equals を自動生成できません。 それはAbstractMoney3 はフィールドを持たないからです。 しかし、実際は親クラスの protected なフィールドを参照するような equals を作成したいわけです。 そのため、Eclipse を騙すと、equals を生成してくれます。 つまり、親クラスの protected なフィールドと同じ名前のフィールドを一旦 AbstractMoney3 クラスに書いてから equals を生成し、そのフィールドを消すと、親クラスのフィールドを参照する equals が生成されます。 また、生成するとき、 instanceof を使用するように指定することを忘れずに。
package kadai;
import kaitou.AbstractMoney3;
public class Yen3 extends AbstractMoney3 {
public Yen3(double value) {
super(value);
}
@Override
protected String getPrefix() {
return "";
}
@Override
protected String getPostfix() {
return "円";
}
@Override
public double getRate() {
return 1.0;
}
public static void setYenRate(double r) {
}
}
package kadai;
import kaitou.AbstractMoney3;
public class Dollar3 extends AbstractMoney3 {
public Dollar3(double value) {
super(value);
}
@Override
protected String getPrefix() {
return "$";
}
@Override
protected String getPostfix() {
return "";
}
private static double rate;
@Override
public double getRate() {
return rate;
}
public static void setYenRate(double r) {
rate=r;
}
}
package kadai;
import kaitou.AbstractMoney3;
public class Euro extends AbstractMoney3 {
public Euro(double value) {
super(value);
}
@Override
protected String getPrefix() {
return "";
}
@Override
protected String getPostfix() {
return "Euro";
}
private static double rate;
@Override
public double getRate() {
return rate;
}
public static void setYenRate(double r) {
rate=r;
}
}
なお、これらをテストするクラス Kadai3Test を使用しなさい。
次のプログラムを動作させ、プログラムの動きを説明しなさい。
package kadai;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import kadai.Dollar3;
import kadai.Euro;
import kadai.Money3;
import kadai.Yen3;
public class Test {
public static void main(String[] args) {
List<Money3> moneyList = new ArrayList<Money3>();
moneyList.add(new Yen3(100));
moneyList.add(new Yen3(150));
moneyList.add(new Yen3(200));
moneyList.add(new Dollar3(1));
moneyList.add(new Dollar3(2));
moneyList.add(new Dollar3(3));
moneyList.add(new Euro(1));
moneyList.add(new Euro(2));
moneyList.add(new Euro(3));
System.out.println(moneyList);
Dollar3.setYenRate(80);
Euro.setYenRate(115);
Collections.sort(moneyList);
System.out.println(moneyList);
List<Money3> moneyList2 = new ArrayList<Money3>();
for(Money3 m : moneyList){
moneyList2.add(m.getYen());
}
System.out.println(moneyList2);
}
}
Eclipse のセッティングの方法
提出先: レポートボックス
順序木を利用し、キーの値で値を整列するデータ構造を作成することを考えま す。 そのためには、java.util.Map を実装し、 java.util.AbstractMap を継承し、 最小限のプログラムで動作させます。 まず、文字列をキーとして、整数値を整列するような TDUMap3 を目標としま す。 完成した後、Generics を利用した任意の型のキーと値を使用できる TDUMap を作成します。
こちらで用意したプログラムはすべて kadai フォルダに入っており、 kadai パッケージに含まれます。 また、テストプログラムを用意したので、活 用すること。 解答のプログラムは kaitou パッケージに作成しなさい。
発展問題の解答は任意です。
はじめに、型 E の配列を受け取って java.util.Set<E> として動作する kadai.TDUSet<E> を作成します。 これは java.util.AbstractSet<E> を継承し、配列を受け取るコンスト ラクタと、 size() メソッドと iterator メソッドを実装するだけで実現できます。 なお、この TDUSet に関しては add や remove などの要素を変更するような実装は 一切しないことにします。 そのため、下記のように実装しました。
package kadai;
import java.util.AbstractSet;
import java.util.Iterator;
import kaitou.TDUIterator;
public class TDUSet<E> extends AbstractSet<E>{
private E[] set;
public TDUSet(E[] array){
set = array;
}
@Override
public int size(){
return set.length;
}
@Override
public Iterator<E> iterator(){
return new TDUIterator<E>(set);
}
}
これが動作するように kaitou.TDUIterator<E> を作成しなさい。 なお、 public void remove() に関しては、何もしないメソッドて構いません。 これをテストするために、 TDUSetTest を利用しなさい。
次に、 java.util.Map<String,Integer> として動作するクラスを作成 することを考えます。 そのため、まず、java.util.Map.Entry<String,Integer> として動作し、 二分木の節となるクラス TDUMapEntry1 を、 java.util.AbstractMap.SimpleEntry<String,Integer>を継承し、 下記のように作成しました。
package kadai;
import java.util.AbstractMap;
public class TDUMapEntry1 extends AbstractMap.SimpleEntry<String,Integer>
{
private static final long serialVersionUID = 1L;
public TDUMapEntry1(String s ,Integer i){
super(s,i);
left=null;
right=null;
}
public TDUMapEntry1 left;
public TDUMapEntry1 right;
}
次に、TDUMap1 を java.util.AbstractMap<String,Integer> を継承し て作成することを考えます。 但し、このクラスは TDUMapEntry1 で作られた二分木に対して、サイズ(節点 の数)を返すメソッド int size() を実装することだけを目標として考えます。 まずkadai.AbstractTDUMap1 を下記のように作成しました。
package kadai;
import java.util.AbstractMap;
import java.util.Set;
import java.util.Map;
public abstract class AbstractTDUMap1 extends AbstractMap<String,Integer> {
protected TDUMapEntry1 root;
protected AbstractTDUMap1(){
root=null;
}
protected AbstractTDUMap1(TDUMapEntry1 root){
this.root = root;
}
@Override
final public int size(){
return size2(root);
}
@Override
public Set<Map.Entry<String,Integer>> entrySet(){
return null;
}
protected abstract int size2(TDUMapEntry1 p);
}
これを継承し、二分木のサイズがきちんと返るように privateprotected int
size2(TDUMapEntry1 p) という関数を実装したクラス kaitou.TDUMap1 を作成しなさい。
これのテストには、 TDUMap1Test を使用しなさい。
なお、レポートに報告するには、この TDUMap1Test の size() メソッド呼び
出し箇所(5箇所)におけるデータの木構造を図示したもの作成し、それぞれ
size がどのように動作するか説明すること。
次に、 TDUMap1 を継承し、 entrySet() を正常に出力するようなクラス TDUMap2 を作ります。 もともと、java.util.AbstractMap は、この entrySet() だけを実装すれば動 作するようになっていますので、これを実装することで正常な java.util.Map として動作するようになります。 entrySet() を作成するために次のアルゴリズムを考えます。 大まかな方針は、まずTDUMapEntry1 の木構造から java.util.Map.Entry<String,Integer> の配列を作り、次に TDUSet に渡 したものを 戻り値として返すことです。
上記に基づいて途中まで作成したのが下記の AbstractTDUMap2 クラスです。 これを継承し、正常に動作するようにTDUMap2を作成しなさい。
package kadai;
import java.util.Map;
import java.util.Set;
import kaitou.TDUMap1;
public abstract class AbstractTDUMap2 extends TDUMap1 {
protected AbstractTDUMap2(){
super();
}
protected AbstractTDUMap2(TDUMapEntry1 root){
super(root);
}
protected Map.Entry<String,Integer>[] array;
protected int index;
@SuppressWarnings("unchecked")
@Override
public Set<Map.Entry<String,Integer>> entrySet(){
array = (Map.Entry<String,Integer>[]) new Map.Entry[size()];
index=0;
traverse(root);
return new TDUSet<Map.Entry<String,Integer>>(array);
}
protected abstract void traverse(TDUMapEntry1 p);
}
これが正常に動作することを確かめるために、TDUMap2Testを使用しなさい。 なお、プログラムの説明には、TDUMap2Testの各 entrySet() を呼び出すとき の木構造と、 traverse で作成する配列をそれぞれ図示したもの作成し、 traverse がどのように動作するか説明すること。
最後に TDUMap3 として put メソッドにより要素を追加できるように実装し ます。 put は次のように実装します。
ここまでを実装したクラス kadai.AbstractTDUMap3 を以下に示します。 これを継承し、 protected Integer put2(TDUMapEntry p, TDUMapEntry e) を実装したクラス TDUMap3 を作りなさい。 なお、この実装する put2(TDUMapeEntry p, TDUMapEntry e) の仕様は次の通りです。
package kadai;
import kaitou.TDUMap2;
public abstract class AbstractTDUMap3 extends TDUMap2 {
protected AbstractTDUMap3(){
super();
}
@Override
public Integer put(String s, Integer i){
final TDUMapEntry1 e = new TDUMapEntry1(s,i);
if(root==null){
root=e;
return null;
}
return put2(root,e);
}
protected abstract Integer put2(TDUMapEntry1 p, TDUMapEntry1 e);
}
作成したプログラムが正常に動作するかどうか、TDUMap3Testを使用して確かめなさい。 なお、TDUMap3Test において TDUMap3 の初期状態、 test("jkl",12) の呼び出し、 test("def",456) の呼び出し、 test("pqr",678) の呼び出し、 test("mno",567) の呼び出しに関して、TDUMap3 の内 部の木の構造を示し、 put2 がどのように動作するかを木の構造を用いて説明 しなさい。
Generics を使用し、任意の型 K, V を登録できる TDUMap<K extends Comparable<? super K>,V> を作ることを考える。 これを実現するため、Generics に対応した kadai.TDUMapEntry<K,V> を次のように定義する。
package kadai;
import java.util.AbstractMap;
public class TDUMapEntry<K,V> extends AbstractMap.SimpleEntry<K,V>
{
private static final long serialVersionUID = 1L;
public TDUMapEntry(K s ,V i){
super(s,i);
left=null;
right=null;
}
public TDUMapEntry<K,V> left;
public TDUMapEntry<K,V> right;
}
これを用いて、TDUMap を作成しなさい。 なお、作成すべきメソッド、フィールドなどは概ね次のようになるはずです。
package kaitou;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Set;
import kadai.TDUMapEntry;
import kadai.TDUSet;
public class TDUMap<K extends Comparable<? super K>, V> extends AbstractMap<K, V> {
private TDUMapEntry<K,V> root;
public TDUMap(){...}
public TDUMap(TDUMapEntry<K,V> root){...}
@Override
public int size(){...}
private int size(TDUMapEntry<K,V> p) {...}
private Entry<K, V>[] array;
private int index;
@SuppressWarnings("unchecked")
@Override
public Set<Entry<K, V>> entrySet(){...}
private void traverse(TDUMapEntry<K,V> p) {...}
@Override
public V put(K s, V i){...}
private V put2(TDUMapEntry<K,V> p, TDUMapEntry<K,V> e) {...}
}
これが正常に動作することを TDUMapTestによりテスト結果を 報告しなさい。
下記のプログラム kadai.TDUMapDemo がどのようなプログラムか説明しなさい。 そして、HashMap, TreeMap, TDUMap についてそれぞれ 5 回測定し、結果を比較しなさい。 また、どうしてそのような結果になったかを考察しなさい。
package kadai;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;
import kaitou.TDUMap;
class StopWatch {
private Date now ;
public StopWatch() {
now=new Date() ;
}
@Override
public String toString(){
Date next=new Date();
double time=(double)(next.getTime()-now.getTime())/1000.0 ;
now=next;
return String.valueOf(time);
}
}
public class TDUMapDemo {
final private static int n = 10000;
final private static int wordLength = 5;
public static void main(String[] args) {
int k = Integer.valueOf(args[0]);
@SuppressWarnings("unchecked")
Map<String,Double>[] maps = (Map<String, Double>[])(new Map[]{
new HashMap<String,Double>(),
new TreeMap<String,Double>(),
new TDUMap<String,Double>()
});
String[] testWords = generateStrings(n);
double[] testDoubles = generateDouble(n);
StopWatch sw = new StopWatch();
test(maps[k],testWords,testDoubles);
System.out.println(maps[k].getClass().getName()+":"+sw);
}
private static void test(Map<String, Double> m, String[] testWords,
double[] testDoubles) {
for(int i=0; i<testWords.length; i++){
m.put(testWords[i], testDoubles[i]);
m.entrySet();
}
}
private static double[] generateDouble(int n2) {
double[] result = new double[n2];
for(int i=0 ; i<n2; i++){
result[i]=Math.random();
}
return result;
}
private static String[] generateStrings(int n2) {
String[] result = new String[n2];
for(int i=0; i<n2; i++){
result[i]=generateString(wordLength);
}
return result;
}
final private static String alphabets = "abcdefghijklmnopqrstuvwxyz";
private static String generateString(int i) {
StringBuilder result = new StringBuilder();
for(int j=0; j<i; j++){
result.append(alphabets.charAt((int)(Math.random()*alphabets.length())));
}
return result.toString();
}
}
上記のプログラムをまとめたものを ダウンロード できます。
プログラムの動作の説明において、特定のデータ構造を例示し、図示して説明 すること。 但し、図とプログラムに矛盾がある場合、不合格になります。
今回の合否の分水嶺は、課題 2-2, 2-3, 2-4 の再帰のプログラムの説明にお ける終了条件の説明がプログラムと一致しているか否かという点であろうと考えております。 ご注意下さい。
なお、写したと思われるほど酷似したレポートが複数提出された場合、原著が どれかの調査を行わず、抽選で一通のレポートのみを評価 の対象とし、他は提出済みの不合格レポートとして再提出は課しません。 自分で意図せずに他人にコピーされてしまった場合も同様ですので、レポート の取り扱いについては十分に注意して下さい。