基本情報技術者 2017年 春期 午後 問11
次のJavaプログラムの説明及びプログラムを読んで、設問1,2に答えよ。
〔プログラムの説明〕
電気料金プラン(以下、プランという)を比較するためのプログラムである。電気料金は、基本料金、及び電気の使用量(以下、電力量という)から算出される電力量料金から成る。電力量の単位は、キロワット時(kWh)である。電力量料金を算出するための1kWh当たりの料金(以下、料金単価という)は、電力量に応じて段階的に変わる。プランA及びプランBの料金表を表1に示す。

例えば、プランAで、使用した電力量が200.5kWhのとき、120kWh分に対して19.62円/kWh、残りの80.5kWhに対して26.10円/kWhの料金単価が適用される。
(1) 抽象クラスTierTableは、段階的に変化する値のペアをテーブルとして表現する。値は、型doubleで与えられる。
① コンストラクタは、可変長の引数で与えられたdoubleの数値2個ずつをペアとして配列にし、更にその配列を要素とする配列を生成し、フィールドpairsに保持する。引数tiersの長さが奇数のときは、IllegalArgumentExceptionを投げる。
② 抽象メソッドmapは、引数で与えられた数値を別の数値に変換して返す。
(2) クラスTieredRateTableは、料金単価のテーブルを表す。
① コンストラクタは、引数で与えられた電力量とそれに対応する料金単価のペアからテーブルを作成する。
② メソッドmapは、引数で与えられた電力量から電力量料金を計算し、その値を返す。
(3) クラスRatePlanは、プランを表す。
① コンストラクタは、引数で与えられたプラン名、基本料金、料金単価のテーブルで表されるインスタンスを生成する。
② メソッドgetNameは、プラン名を返す。
③ メソッドgetPriceは、引数で与えられた電力量から電力量料金を計算し、その値と基本料金の合計を型intの数値で返す。このとき、小数点以下は切り捨てられる。
(4) クラスMainは、表1を基にプランA及びプランBを表すRatePlanのインスタンスを生成し、電力量が543.0kWhのときの電気料金を比較する。メソッドmainを実行すると、図1の結果が得られた。





設問1:
問題文を見る プログラム中のに入れる正しい答えを、解答群の中から選べ。
aに関する解答群
ア:abstract
イ:final
ウ:private
エ:protected
オ:public
カ:static
bに関する解答群
ア:i
イ:i - 1
ウ:i * 2
エ:i / 2
オ:i / 2 + 1
カ:i + 1
c, dに関する解答群
ア:[i + 1][0]
イ:[i + 1][1]
ウ:[i + 1][i]
エ:[i][0]
オ:[i][1]
カ:[i][i + 1]
模範解答
a:ア、b:エ、c:ア、d:エ
解説
解答の導き方
空欄は、料金の段階を表す配列の作り方と、各段階の料金を計算する式から判断できます。
まず、aを考えます。TierTableには、実装されていないメソッドとして「abstract double map(double amount);」があります。abstract(処理の内容を後から定めることを示す指定)が付いたメソッドを持つクラスは、クラス自体もabstractにする必要があります。実際にTierTableを継承するTieredRateTableがmapの処理を定めるため、aにはabstractが入ります。
次に、bを考えます。tiersには、電力量の境目と、その段階の料金単価が交互に渡されます。例えばプランAでは、0と19.62、120と26.10、300と30.12が順に渡されます。これらを2個ずつまとめると、次の3組になります。
ループの変数iは、2個ずつ読み進めるため、0、2、4と変わります。一方、pairsの行番号は0、1、2です。したがって、tiersの位置iを2で割ったi / 2を行番号にします。ここで使われる整数除算(整数同士の割り算で、小数部分を切り捨てる計算)では、0 / 2、2 / 2、4 / 2の結果がそれぞれ0、1、2になります。よって、bにはi / 2が入ります。
続いて、cとdを考えます。pairsの各行には、ある段階の「電力量の境目」と「料金単価」が入っています。pairs[i][0]が現在の段階の境目、pairs[i][1]が現在の段階の料金単価です。次の段階の境目はpairs[i + 1][0]です。
条件式が成り立つときは、電力量が次の境目を超えているため、現在の段階を最後まで使った分を計算します。現在の段階で課金する電力量は、「次の境目 − 現在の境目」です。そのため、加算する式は次のとおりです。
これに合わせると、cはpairs[i + 1][0]、dはpairs[i][0]です。最後の段階のように次の境目がない場合は、else側で「入力された電力量 − 現在の境目」に料金単価を掛け、そこで計算を終えます。
プログラム全体の動きも、電力量が543.0kWhの場合で確認できます。プランAでは、各段階の電力量料金は次のようになります。
- 120kWhまで:
- 120kWh超過300kWhまで:
- 300kWh超過分:
電力量料金の合計はです。基本料金を加えるととなり、intへのキャスト(値を別の型として扱う変換)で小数部分が切り捨てられて15494円になります。
誤りやすいポイント
- aをpublicやprotectedとしないようにします。クラスの公開範囲ではなく、抽象メソッドを持つクラスであることがポイントです。
- bをiとすると、配列の行番号が0、2、4となり、2行目以降が正しく対応しません。tiersの位置とpairsの行番号は別です。
- cには現在の段階の境目ではなく、次の段階の境目を使います。1段階分の幅を求めるには、次の境目から現在の境目を引きます。
- 最後の段階には次の境目がありません。そのため、最後の段階では入力された電力量から現在の境目を引いて計算します。
- intへの変換は四捨五入ではありません。小数部分を切り捨てます。
FAQ
Q: なぜ料金単価もpairsに一緒に保存するのですか?
A: 電力量の境目と料金単価を同じ行にまとめることで、「どの境目から、どの単価を使うか」を対応付けて扱えるからです。
A: 電力量の境目と料金単価を同じ行にまとめることで、「どの境目から、どの単価を使うか」を対応付けて扱えるからです。
Q: 電力量がちょうど120kWhの場合、どの単価で計算しますか?
A: 最初の段階の料金単価で計算します。2段階目の料金単価が適用されるのは、120kWhを超えた分です。
A: 最初の段階の料金単価で計算します。2段階目の料金単価が適用されるのは、120kWhを超えた分です。
Q: doubleからintに変換すると、端数は四捨五入されますか?
A: いいえ。今回のように値が正の場合、小数部分を切り捨てます。例えば15494.86は15494になります。
A: いいえ。今回のように値が正の場合、小数部分を切り捨てます。例えば15494.86は15494になります。
関連キーワード: 抽象クラス、可変長引数、二次元配列、整数除算、型キャスト
設問2:
問題文を見る 割引プランは、指定した他のサービスを電気と一緒に利用した場合には、プランAやプランBという当初のプランで計算した電気料金を、電気料金の額に応じて割り引くプランである。割引プランの割引率の例を、表2に示す。

プログラム5は、割引率を求めるためのクラスDiscountTableである。メソッドmapは、引数で与えられた電気料金から割引率を求め、その値を返す。ここで、割引率は小数で与えるものとする。例えば、1%は0.01である。

プログラム6は、割引プランを表すためのクラスDiscountPlanである。DiscountPlanは、クラスRatePlanを拡張し、上位クラスであるRatePlanのメソッドgetPriceで求めた電気料金から割引率を求め、割引を適用した金額を電気料金として計算する。プログラム中のに入れる正しい答えを、解答群の中から選べ。

eに関する解答群
ア:extends
イ:implements
ウ:imports
エ:public
オ:throws
fに関する解答群
ア:((RatePlan) this)
イ:RatePlan
ウ:super
エ:this
オ:TieredRateTable
カ:TierTable
gに関する解答群
ア:(1.0 - discountTiers.map(amount))
イ:(1.0 - discountTiers.map(basicCharge))
ウ:(1.0 - discountTiers.map(price))
エ:discountTiers.map(amount)
オ:discountTiers.map(basicCharge)
カ:discountTiers.map(price)
模範解答
e:ア、f:ウ、g:ウ
解説
解答の導き方
この問題では、割引前の電気料金を求め、その料金に応じた割引率を使って割引後の金額を計算します。プログラム6の空欄は、この処理の順番に沿って考えると判断できます。
まず、DiscountPlanはRatePlanの機能を引き継ぎながら、割引の処理を加えるクラスです。あるクラスの機能を引き継ぐことを継承といいます。Javaでは、クラスを継承するときにextendsを使うため、eにはextendsが入ります。implementsはインタフェースを実装するときに使うもので、この場合には当てはまりません。
次に、DiscountPlanのgetPriceは、割引前の料金を求める必要があります。RatePlanには、電力量から基本料金を含む電気料金を計算するgetPriceがあります。そこで、
- 上位クラスRatePlanのgetPriceを呼び出す
- その結果をpriceに入れる
- priceに応じた割引率を求める
- 割引後の金額を返す
という順に処理します。
DiscountPlanにもgetPriceがあるため、this.getPrice(amount)とすると、DiscountPlan自身のgetPriceを再び呼び出してしまいます。これは同じ処理を繰り返すことにつながります。上位クラスのメソッドを呼び出すときはsuperを使うので、fにはsuperが入ります。
最後に、割引率を求めるときに何を割引率表へ渡すかを考えます。問題文では、DiscountTableのmapは「電気料金から割引率を求め」ます。amountは電力量であり、basicChargeは基本料金だけです。割引率を決める基準になるのは、RatePlanのgetPriceで計算した割引前の電気料金priceです。したがって、割引率はdiscountTiers.map(price)で求めます。
map(price)が返すのは割引率そのものです。例えば割引率が3%なら、小数では0.03です。支払う金額は料金全体ではなく、料金から割引分を引いた額なので、掛ける割合は1.0から割引率を引いた値になります。よって、gには(1.0 - discountTiers.map(price))が入ります。
その結果、計算は次の形になります。
- 割引前の料金:super.getPrice(amount)
- 割引後の料金:price × (1.0 − 割引率)
最後に(int)で整数に変換するため、小数点以下は切り捨てられます。
誤りやすいポイント
-
extendsとimplementsを混同すること
DiscountPlanはクラスRatePlanを継承しています。クラスを継承するキーワードはextendsです。 -
this.getPrice(amount)を使うこと
DiscountPlanのgetPriceを呼び直すことになり、割引前の料金を計算する目的に合いません。上位クラスの処理を使うにはsuper.getPrice(amount)とします。 -
割引率表にamountを渡すこと
amountは電力量です。表2の割引率は電気料金に応じて決まるため、割引前の料金priceを渡します。 -
割引率をそのまま料金に掛けること
割引率が0.03なら、0.03を掛けると割引分だけの金額になります。支払う金額を求めるには、1.0から割引率を引いた値を掛けます。
FAQ
Q: super.getPrice(amount)とRatePlanのgetPrice(amount)は、どう違いますか?
A: superは、上位クラスにあるメソッドを呼び出すための指定です。ここでは、DiscountPlanが上書きしたgetPriceではなく、RatePlanのgetPriceを実行できます。
A: superは、上位クラスにあるメソッドを呼び出すための指定です。ここでは、DiscountPlanが上書きしたgetPriceではなく、RatePlanのgetPriceを実行できます。
Q: priceには、電力量料金だけが入りますか?
A: いいえ。RatePlanのgetPriceは、電力量料金と基本料金を合計して整数にした値を返します。そのため、priceは割引前の電気料金です。
A: いいえ。RatePlanのgetPriceは、電力量料金と基本料金を合計して整数にした値を返します。そのため、priceは割引前の電気料金です。
Q: なぜ割引後の料金にも(int)を付けるのですか?
A: 割引後の計算結果に小数点以下が生じる場合があるためです。(int)に変換すると、小数点以下が切り捨てられます。
A: 割引後の計算結果に小数点以下が生じる場合があるためです。(int)に変換すると、小数点以下が切り捨てられます。
関連キーワード: 継承、メソッドのオーバーライド、super、型変換、割引率



