Java 8 を使用すること(なお、発展課題は解答しなくても不合格の原因にはならない)。 本課題のプログラムは Java 7 以前のバージョンでは動作しない。
レポートには表紙をつけること。
提出先: 2号館レポートボックス
以下の設問において kotae パッケージを作り、 その中に指定されたクラスを作成せよ。
Yen, Dollar, Euro という通貨単位を統一して扱うことを考えます。 そのためには、これらを総称するために、 Money というインタフェースを考 えます。 これにより、例えば Money 型の配列に {50円, $3, 2Euro} のように、各 通貨単位の金額を混在させることができるようになります。 これらのオブジェクトが管理するのは、金額と通貨記号付きの表現です。
これを実現させるために、Money インターフェイスを定義します。 さらに、 AbstractMoney1 クラスとしてテンプレートデザインパターンを使い、 toString メソッドを定義しています。 通貨記号を表示する場合、金額の前に置く場合と、金額の後ろに置く場合があ ります。 そのため prefix() と postfix() で金額をはさむようにして文字列を作成し ます。 Yen, Dollar, Euro インターフェイスはそれぞれ default で prefix, postfix を定義します。 Yen1, Dollar1, Euro1 クラスは AbstractMoney1 クラスを継承し、 Yen, Dollar, Euro インターフェイスを実装することで、クラス自体にはほとんど コードを書かずに、目標を実現します。
package kadai;
public interface Money {
String prefix();
String postfix();
double getValue();
}
package kadai;
public abstract class AbstractMoney1 implements Money {
private double value;
public AbstractMoney1(double i) {
value = i;
}
@Override
final public String toString(){
return prefix()+getValue()+postfix();
}
@Override
final public double getValue(){
return value;
}
}
package kadai;
public interface Dollar extends Money{
default String prefix() {
return "$";
}
default String postfix() {
return "";
}
}
package kadai;
public interface Euro extends Money{
default String prefix() {
return "";
}
default String postfix() {
return "Euro";
}
}
package kadai;
public interface Yen extends Money{
default public String prefix() {
return "";
}
default public String postfix() {
return "円";
}
}
package kadai;
public class Dollar1 extends AbstractMoney1 implements Dollar {
public Dollar1(double i) {
super(i);
}
}
package kadai;
public class Euro1 extends AbstractMoney1 implements Euro {
public Euro1(double value) {
super(value);
}
}
package kadai;
public class Yen1 extends AbstractMoney1 implements Yen {
public Yen1(double i) {
super(i);
}
}
また、異なる各通貨単位の金額が混在した List を作るため、Abstract Factory デザインパターンを利用した MoneyFactory.getList メソッドを作成 しました。
package test;
import java.util.ArrayList;
import java.util.List;
import kadai.Money;
public interface MoneyFactory<T extends Money> {
T getYen(double value);
T getDollar(double value);
T getEuro(double value);
static <T extends Money> List<T> getList(MoneyFactory<T> f) {
List<T> moneyList = new ArrayList<>();
moneyList.add(f.getEuro(1));
moneyList.add(f.getYen(200));
moneyList.add(f.getDollar(2));
moneyList.add(f.getYen(100));
moneyList.add(f.getYen(150));
moneyList.add(f.getDollar(5));
moneyList.add(f.getDollar(1));
moneyList.add(f.getEuro(2));
moneyList.add(f.getEuro(3));
return moneyList;
}
}
package test;
import kadai.Dollar1;
import kadai.Euro1;
import kadai.Money;
import kadai.Yen1;
public class Money1Factory implements MoneyFactory<Money> {
@Override
public Money getYen(double value){
return new Yen1(value);
}
@Override
public Money getDollar(double value){
return new Dollar1(value);
}
@Override
public Money getEuro(double value){
return new Euro1(value);
}
}
MoneyFactory.getList(new Money1Factory()) で得られるリストの内訳をすべ て表示するプログラムを作成しなさい。 但し、java.util.stream パッケージに含まれるクラス、インターフェイスを 使用してはいけない。
なお、使用したプログラム例は次の様になる。
package test;
import java.util.List;
import kadai.Money;
public class Test1 {
public static void main(String[] args) {
List<Money> list = MoneyFactory.getList(new Money1Factory());
list.stream()
.forEach(System.out::println);
}
}
次に、このオブジェクトたちを比較可能にしたい。 これを行うには、比較可能なメソッドを導入する事と、日本円に換算する事が 必要です。 そのため、 Money2 として、 Money と Comparable を継承し、 getYenRate メソッドと getYen メソッドを追加します。
package kadai;
public interface Money2 extends Money , Comparable<Money2> {
<T extends Money2> T getYen();
double getYenRate();
}
さらに各 Dollar2, Euro2, Yen2 はそれぞれ AbstractMoney2 を継承し、 static な setYenRate メソッドを持ち、 getYenRate を実装します。
package kadai;
import kotae.AbstractMoney2;
public class Dollar2 extends AbstractMoney2 implements Dollar {
private static double yenRate;
public Dollar2(double value) {
super(value);
}
public static void setYenRate(double value){
yenRate = value;
}
@Override
public double getYenRate() {
return yenRate;
}
}
package kadai;
import kotae.AbstractMoney2;
public class Euro2 extends AbstractMoney2 implements Euro {
public Euro2(double value) {
super(value);
}
private static double yenRate;
public static void setYenRate(double value){
yenRate = value;
}
@Override
public double getYenRate() {
return yenRate;
}
}
package kadai;
import kotae.AbstractMoney2;
public class Yen2 extends AbstractMoney2 implements Yen {
public Yen2(double value) {
super(value);
}
@Override
public double getYenRate() {
return 1;
}
public static void setYenRate(double i) {
}
}
さて、getYen メソッドだけを実装した次の AbstractMoney20 を作成したので、 これを継承して、比較に関するメソッド equals, hashCode, compareTo を実 装した AbstractMoney2 を作成しなさい。
package kadai;
public abstract class AbstractMoney20 extends AbstractMoney1 implements Money2{
public AbstractMoney20(double i) {
super(i);
}
@SuppressWarnings("unchecked")
@Override
public <T extends Money2> T getYen() {
if(this instanceof Yen) return (T) this;
return (T) new Yen2(getValue()*getYenRate());
}
}
なお、テストプログラム
Dollar2Test,
Euro2Test,
Yen2Test
,Ex2supplement
,Ex2supplement1
,Ex2supplement2
を作成したので、
活用すること。
レポートにはこのプログラムが正常に動作したことを示すこと。
なお、ヒントとしては、オブジェクトをすべて Money2 型とみなして、円レー トですべて比較すること。 Eclipse で自動的に作ると、AbstractMoney2 型として比較を行うコードが生 成されますが、 Money2 に直すこと(この件を検出するテストは作成していな い)。
次に、金額の集計を考えます。1円+2円を 3円にするなど、同一通貨の二つの お金に対して、それを合計した同一通貨のお金を作るメソッド plus を作成し ます。
さらに、これを実現するため、オブジェクトから同一通貨のオブジェクトを作 成する newInstance メソッドも追加します。 なお、この newInstance メソッドはリフレクションという、実行プログラム からコンパイル前のプログラムの情報を得る機能を使っています。
package kadai;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public interface Money3 extends Money2 {
@SuppressWarnings("unchecked")
default <T extends Money> T getNewInstance(double value) {
Constructor<? extends Money> c = null;
try {
c = this.getClass().getConstructor(double.class);
} catch (NoSuchMethodException | SecurityException e) {
return null;
}
T result = null;
try {
result = (T) c.newInstance(value);
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
return null;
}
return result;
}
<T extends Money3> T plus(T x);
}
package kadai;
import kotae.AbstractMoney2;
public abstract class AbstractMoney3 extends AbstractMoney2 implements Money3 {
public AbstractMoney3(double i) {
super(i);
}
@SuppressWarnings("unchecked")
@Override
public <T extends Money2> T getYen(){
if(this instanceof Yen) return (T) this;
return (T) new Yen3(getValue()*getYenRate());
}
@Override
public <T extends Money3> T plus(T x) {
if(this.getClass() != x.getClass()){
throw new IllegalArgumentException();
}
return this.getNewInstance(getValue()+x.getValue());
}
}
package kadai;
public class Dollar3 extends AbstractMoney3 implements Dollar {
private static double yenRate;
public Dollar3(double value) {
super(value);
}
public static void setYenRate(double value){
yenRate = value;
}
@Override
public double getYenRate() {
return yenRate;
}
}
package kadai;
public class Euro3 extends AbstractMoney3 implements Euro {
public Euro3(double value) {
super(value);
}
private static double yenRate;
public static void setYenRate(double value){
yenRate = value;
}
@Override
public double getYenRate() {
return yenRate;
}
}
package kadai;
public class Yen3 extends AbstractMoney3 implements Yen {
public Yen3(double value) {
super(value);
}
@Override
public double getYenRate() {
return 1;
}
public static void setYenRate(double i) {
}
}
さて、MoneyFactory.getList(new Money3Factory()) で得られるお金のリスト が総額で日本円でいくらか、ドル円レート 100円、ユーロ円レート140円で求 めるプログラムを java.util.stream パッケージを使用しないで作成しなさい。 なお、 stream パッケージを使ったプログラムを次に示す。
package test;
import java.util.List;
import kadai.Dollar3;
import kadai.Euro3;
import kadai.Money3;
public class Test3 {
public static void main(String[] args) {
Dollar3.setYenRate(100);
Euro3.setYenRate(140);
List<Money3> list = MoneyFactory.getList(new Money3Factory());
list.stream()
.map(x->(Money3)x.getYen())
.reduce((x,y)->x.plus(y))
.ifPresent(x->System.out.println(x));
}
}
MoneyFactory.getList(new Money3Factory()) で得られるお金のリスト を通貨単位ごとに合算するプログラムを java.util.stream パッケージを使用 しないで作成しなさい。 なお、 stream パッケージを使ったプログラムを次に示す。
package test;
import java.util.List;
import java.util.stream.Collectors;
import kadai.Money3;
public class Test4 {
public static void main(String[] args){
List<Money3> list = MoneyFactory.getList(new Money3Factory());
list.stream()
.collect(Collectors.groupingBy(x->x.getClass()))
.forEach((k, v)->v.stream()
.reduce((x, y)->x.plus(y))
.ifPresent(System.out::println));
}
}
なお、以上の提供したファイルをすべてまとめた 課題ファイル集 を用意した。
Eclipse のセッティングの方法
なお、問題訂正などでプログラムが変更になった場合、上記の最新のファイル 集をダウンロードした後で、作業中のプロジェクトでもう一度importして下さ い。 上書きするファイルが検出されるとダイアログが表示されますが、 Yes ALL で問題だけ最新の状態になるはずです。
提出先: 2号館レポートボックス
以下の設問において kotae パッケージを作り、 その中に指定されたクラスを作成せよ。
データをいくつでも入れられるリストクラス TDUList を作ります。 なお、各メソッドの細かい仕様はこちらが特に指定する以外は、Java8 の API マニュアルに準拠します。
各設問に付されているテストに関しては結果をレポートに添付すること。
なお、テストクラスを用意したので、各設問 において、指示のあるテストクラスを用いてテストを行い、正常であった旨を 報告しなさい。
なお、この課題も 課題ファイル集(5月30日版) を用意した。 インストール方法は課題1と同様である。
どんなオブジェクトでも一つ入れられるノードクラス kotae.Node<E> を作成しなさい。 線形リストに使用するため、内部に Node<E> 型の変数 next を public で持たせます。 作成すべきメソッドは次の通りです。
なお、test.NodeTest を使用して、 正常であるかどうかを確かめること。
TDUList は Node オブジェクトの線形リストを内部に格納し、様々な操作を行 えるようにすることを目標とします。 以下の課題ではまず、文字列を入れられる線形リストのライブラリを作成し、 最終的に Generics を使用してどのようなオブジェクトも入れられるように改 良します。
さて、下記のクラス kadai.TDUList1 と kadai.TDUListIterator1はもっとも 基本的な雛形です。 TDUList1 において node メンバ変数は Node オブジェクトの線形リストの先 頭のオブジェクトを参照するためのメンバ変数です。 なお、常に線形リストの最後のオブジェクトは空の Node オブジェクトを持つ こととします。 この TDUList1 を継承し、Node オブジェクトの線形リストの要素数を数える size メソッドを実装したクラス kotae.TDUList2 を作成しなさい。 但し、空の Node オブジェクトだけの時は 0 を返し、値の入った Node オブ ジェクトが k 個連続したのちに空の Node オブジェクトがつながっている場 合は k を出力するようにしなさい。
なお、アルゴリズムの説明をする際は、データ例を図示し、数えている対象と 終了条件がわかるように印を付けなさい。 なお、 test.TDUList2Test を利用し、テスト結果も示すこと。
package kadai;
import java.util.AbstractSequentialList;
import java.util.ListIterator;
import kotae.Node;
public class TDUList1 extends AbstractSequentialList<String> {
public Node<String> node;
public TDUList1(){
super();
node = new Node<>();
}
@Override
public ListIterator<String> listIterator(int arg) {
return new TDUListIterator1(node,arg);
}
@Override
public int size() {
return 0;
}
}
package kadai;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import kotae.Node;
public class TDUListIterator1 implements ListIterator<String> {
public TDUListIterator1(Node<String> node, int arg) {
super();
}
@Override
public void add(String e) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasNext() {
return false;
}
@Override
final public boolean hasPrevious() {
return false;
}
@Override
public String next() {
throw new NoSuchElementException();
}
@Override
final public int nextIndex() {
return 0;
}
@Override
final public String previous() {
throw new NoSuchElementException();
}
@Override
final public int previousIndex() {
return -1;
}
@Override
final public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void set(String e) {
throw new UnsupportedOperationException();
}
}
kadai.TDUList3 を下記のようにします。 このクラスが機能するように kadai.TDUListIterator1 クラスを継承した kotae.TDUListIterator3 クラスを作成し、kotae.Node<String> と int の値 を引数に持つコンストラクタと String オブジェクトを引数にする add メソッ ドを実装しなさい。 但し、このコンストラクタの int の値の意味は、java.util.ListIterator の APIマニュアルの通りで、与えられた int の値にカーソルをセットします。 そして、 add メソッドでカーソルの位置に要素を追加しなさい。 これに対して test.TDUList3Testと test.TDUListIterator3Test を活用しなさい。
この add メソッドはどのようにデータが加わっていくかを図示してください。 また、 TDUListIterator3 の add メソッドと、 TDUList3 の add メソッドの 関わりについて考察しなさい。
package kadai;
import java.util.ListIterator;
import kotae.TDUList2;
import kotae.TDUListIterator3;
public class TDUList3 extends TDUList2 {
public TDUList3(){
super();
}
@Override
public ListIterator<String> listIterator(int arg0){
return new TDUListIterator3(node,arg0);
}
}
kadai.TDUList4 を下記のようにします。 この時、kotae.TDUListIterator3 を継承し、 public String next() と public boolean hasNext() メソッドを 実装したクラス kotae.TDUListIterator4 を作成しなさい。 これに対しては、 test.TDUList4Test と test.TDUListIterator4Test を 活用しなさい。
package kadai;
import java.util.ListIterator;
import kotae.TDUListIterator4;
public class TDUList4 extends TDUList3 {
public TDUList4() {
super();
}
@Override
public ListIterator<String> listIterator(int arg){
return new TDUListIterator4(node,arg);
}
}
kadai.TDUList5 を下記のようにします。 このとき、 kotae.TDUListItrator4 を継承し、set メソッドを実装したクラス kotae.TDUListIterator5 を作成しなさい。 これに対しては、 test.TDUList5Testと test.TDUListIterator5Testを活用し なさい。 なお、親メソッドと同等のことをやろうとする場合において、プログラムのコ ピーを避けてください。
package kadai;
import java.util.ListIterator;
import kotae.TDUListIterator5;
public class TDUList5 extends TDUList4 {
public TDUList5() {
super();
}
@Override
public ListIterator<String> listIterator(int arg){
return new TDUListIterator5(node,arg);
}
}
package test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
class Keisoku {
private static final int changes = 100;
private static final int max=10000;
private static final int num = 3;
private static final String[] ave = {"for","stream"};
private static double[] result = new double[num];
public static void main(String[] args) {
switch((int) (Math.random()*3)){
case 0: test(new kotae.TDUList<>());
break;
case 1: test(new LinkedList<>());
break;
case 2: test(new ArrayList<>());
break;
}
}
private static void test( List<Integer> list) {
StopWatch sw = new StopWatch();
for(int i=0;i<max;i++){
list.add((int) (Math.random()*1000));
}
System.out.println("Addition:\t"+sw);
for(int i=0;i<changes;i++){
list.set((int) (Math.random()*max),(int) (Math.random()*1000));
}
System.out.println("Set:\t"+sw);
int method = (int)(Math.random()*2);
System.out.println("Calculating the average by using "+ave[method]);
for(int i=0;i<num;i++){
switch(method){
case 0:
result[i]=0;
for(Integer x : list){
result[i]+=x;
}
result[i]/=max;
break;
case 1:
result[i]= list.stream()
.mapToInt(x->x.intValue())
.average()
.getAsDouble();
break;
}
System.out.println(i+":\t"+sw);
}
System.out.println(Arrays.toString(result));
System.out.println(list.getClass().getName());
}
}
package test;
import java.util.Date;
public class StopWatch {
private Date now;
public StopWatch() {
now = new Date();
}
@Override
public String toString() {
final Date next = new Date();
double result = (double)( next.getTime()-now.getTime())/1000;
now = next;
return String.valueOf(result);
}
}
なお、写したと思われるほど酷似したレポートが複数提出された場合、原著が どれかの調査を行わず、抽選で一通のレポートのみを評価 の対象とし、他は提出済みの不合格レポートとして再提出は課しません。 自分で意図せずに他人にコピーされてしまった場合も同様ですので、レポート の取り扱いについては十分に注意して下さい。