Javaプログラミングの実践的学習と課題解決

はじめに

本記事では、主に以下の重要な概念と技術について取り扱います。

クラスとオブジェクトの基本概念、コンストラクタ、アクセス修飾子、メンバ変数。

文字列入力と出力の処理方法、文字列の分割、結合、変換操作。

ループ、条件文、配列、リスト。

今回の課題は主にPTAの第7回問題セットと期末試験で構成されており、主に「成績管理システム2」と「3」、および一部のJava高度知識の応用が含まれています。難易度は高く、問題数は適度で、複数の詳細と要件(例外処理や特殊ケースの処理を含む)を網羅しています。総じて、これらの課題は学生のJavaプログラミング能力、オブジェクト指向設計原則、例外処理、文字列処理、基本的な制御フローとデータ構造を包括的に評価することができます。これらの課題を完了することで、Javaプログラミングに対する理解が深まります。

設計と分析

第7回課題:

7-1 コンテナ-HashMap-検索

複数の学生の成績情報(学番、氏名、成績)を入力します。

学番は各学生の唯一の識別番号であり、重複しません。

氏名は重複する可能性があります。

HashMapを使用して学生情報を保存し、学番に基づく検索機能を実装します。

入力形式: 複数の学生の成績情報を入力します。各学生の成績情報の形式:学番+半角スペース+氏名+半角スペース+成績

「end」を入力終了の目印とします。

endの後で特定の学番を入力し、プログラムを実行してその学生の詳細情報を出力します。

出力形式: 検索した学生情報の形式:学番+半角スペース+氏名+半角スペース+成績

検索されなかった場合は、「The student "+検索した学番+" does not exist」と出力します。

コード:

import java.util.HashMap;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        HashMap<String, String[]> hashMap = new HashMap<>();

        while (sc.hasNext()) {
            String line = sc.nextLine().trim();
            if ("end".equals(line)) { // 入力終了の目印
                break;
            }

            String[] infomation = line.split("\\s+"); // 文字列配列に変換、スペースを区切り文字として使用
            String[] value = {infomation[1], infomation[2]}; // 氏名と成績を同じ配列に格納
            hashMap.put(infomation[0], value); // 学番をキーとして、氏名と成績の文字列配列を値として保存
        }

        String studentId = sc.nextLine().trim(); // 検索する学番

        if (hashMap.containsKey(studentId)) { // HashMapにその学番がある場合
            String[] value = hashMap.get(studentId); // 学番に基づいてその学生の氏名と成績の文字列配列を取得
            System.out.printf("%s %s %s\n", studentId, value[0], value[1]); // 学番、氏名、成績を出力
        } else {
            System.out.printf("The student %s does not exist", studentId); // その学番が見つからない場合
        }
    }
}

7-2 コンテナ-HashMap-ソート

複数の学生の成績情報(学番、氏名、成績)を入力します。

学番は各学生の唯一の識別番号であり、重複しません。

氏名は重複する可能性があります。

要件:HashMapを使用して学生情報を保存します。

入力形式: 複数の学生の成績情報を入力します。各学生の成績情報の形式:学番+半角スペース+氏名+半角スペース+成績

「end」を入力終了の目印とします。

出力形式: 学番の降順ですべての学生情報を出力します。各学生情報の出力形式:学番+半角スペース+氏名+半角スペース+成績

コード:

import java.util.*;
public class Main {
    public static void main(String[] args) {
        ArrayList<StudentRecord> list = new ArrayList<>();
        String message;
        StudentRecord temp;
        Scanner in = new Scanner(System.in);
        while(!(message = in.nextLine()).equals("end"))
        {
            String [] keyAndValue = message.split(" ");
            temp = new StudentRecord();
            temp.id = keyAndValue[0];
            temp.name = keyAndValue[1];
            temp.score = keyAndValue[2];
            list.add(temp);
        }
        Collections.sort(list);
        for (StudentRecord record : list) {
            System.out.println(record.id + " " + record.name + " " + record.score);
        }
    }
}
class StudentRecord implements Comparable<StudentRecord>
{
    String id;
    String name;
    String score;
    @Override
    public int compareTo(StudentRecord o) {
        if(this.id.compareTo(o.id) == 0)
            return 0;
        else if (this.id.compareTo(o.id)>0)
            return -1;
        else
            return 1;
    }
}

7-3 科目成績統計プログラム-2

科目成績統計プログラム-2は、初回の内容に実験科目を追加したものです。太字で示した部分が今回の新増内容です。

ある大学の科目は性質上、必修科目、選択科目、実験科目に分かれ、評価方式は試験、調査、実験に分かれます。

試験の総成績は平常点と期末点をそれぞれ重みを掛けて算出されます。例えば、平常点の重みが0.3、期末点の重みが0.7の場合、総成績=平常点×0.3+期末点×0.7となります。

調査の総成績は期末点と直接等しくなります。

実験の総成績は、各回の実験成績の平均点に等しくなります。

必修科目の評価方式は必ず試験でなければならず、選択科目は試験または調査のいずれかの評価方式を選択できます。実験科目の成績は必ず実験でなければなりません。

  1. 入力: 科目情報と科目成績情報の2種類があります。

科目情報には、科目名、科目の性質、評価方式(選択可能。性質が必修科目の場合、評価方式は省略可能)の3つのデータ項目が含まれます。

科目情報の形式:科目名+半角スペース+科目の性質+半角スペース+評価方式

科目の性質の入力項目:必修、選択、実験

評価方式の入力オプション:試験、調査、実験

試験/調査科目の成績情報には、学番、氏名、科目名、平常点(省略可能)、期末点が含まれます。

試験/調査科目の情報形式:学番+半角スペース+氏名+半角スペース+科目名+半角スペース+平常点+半角スペース+期末点

実験科目の成績情報には、学番、氏名、科目名、実験回数、各回の成績が含まれます。

実験回数は最低4回、最大9回です。

実験科目の情報形式:学番+半角スペース+氏名+半角スペース+科目名+半角スペース+実験回数+半角スペース+最初の実験成績+...+半角スペース+最後の実験成績

上記情報に関する制約:

1)平常点と期末点の重みはデフォルトで0.3、0.7です。

2)成績は整数であり、小数部分を含みません。成績の値の範囲は【0,100】です。

3)学番は8桁の数字で構成されます。

4)氏名は10文字以内です。

5)科目名は10文字以内です。

6)クラス情報を特別に入力せず、クラス番号は学番の前6桁です。

  1. 出力: 学生の全科目総成績の平均点、単一科目成績の平均点、クラスの全科目総成績の平均点を含む3つの部分を出力します。

誤差を避けるため、平均点の計算方法は、条件に合致するすべての個別成績を累加し、最後に総数で割る方法です。

1)学生の全科目総成績の平均点は学番の昇順で出力します。

形式:学番+半角スペース+氏名+半角スペース+総成績平均点

特定の学生が成績情報を持っていない場合:学番+半角スペース+氏名+半角スペース+"did not take any exams"

2)単一科目成績の平均点は3つの値に分かれます:平常点の平均点(省略可能)、期末試験の平均点、総成績の平均点。科目名の文字順で出力します。

試験/調査科目の成績形式:科目名+半角スペース+平常点平均点+半角スペース+期末試験平均点+半角スペース+総成績平均点

実験科目の成績形式:科目名+半角スペース+総成績平均点

特定の科目が成績情報を持っていない場合:科目名+半角スペース+"has no grades yet"

3)クラスの全科目総成績の平均点はクラスの昇順で出力します。

形式:クラス番号+半角スペース+総成績平均点

特定のクラスが成績情報を持っていない場合:クラス名+半角スペース+"has no grades yet"

異常状況:

1)特定の成績情報を解析する際に、科目名が入力済みの科目リストに存在しない場合:学番+半角スペース+氏名+半角スペース+":"+科目名+半角スペース+"does not exist"

2)特定の成績情報を解析する際に、入力された成績の数と科目の評価方式が一致しない場合:学番+半角スペース+氏名+半角スペース+": access mode mismatch"

上記の2つの状況が同時に発生した場合は、最初の状況の出力結果に従います。

3)特定の科目情報を解析する際に、入力された科目の性質と科目の評価方式が一致しない場合:科目名+" : course type & access mode mismatch"

4)形式の誤りおよびその他の情報の異常(成績範囲外など)は、すべて形式の誤りとして処理し、"wrong format"を出力します。

5)重複する科目/成績情報が発生した場合、最初の科目情報のみを保持し、後から入力されたものは無視します。

情報の制約:

1)成績の平均点は整数部分のみを取り、小数部分は破棄します。

参考クラス図(初回と同じ、その他の内容は各自で補足)

7-4 動物の鳴き声シミュレーター(多態性)

異なる動物の鳴き声をシミュレートする動物の鳴き声シミュレーターを設計します。例えば、ライオンの咆哮、トラの唸り、犬のワンワン、猫のニャーニャーなど。

抽象クラスAnimalを定義し、2つの抽象メソッドを含みます:動物のカテゴリを取得するgetAnimalClass()、動物が鳴くshout()。

次に、抽象クラスAnimalに基づいて犬クラスDog、猫クラスCat、山羊クラスGoatを定義し、getAnimalClass()メソッドで異なる動物のカテゴリ(猫、犬、山羊など)を返し、shout()メソッドでそれぞれ異なる鳴き声(ニャーニャー、ワンワン、メーメーなど)を出力します。

最後にAnimalShoutTestクラスを作成してテストし、出力します: 猫の鳴き声:ニャーニャー 犬の鳴き声:ワンワン 山羊の鳴き声:メーメー

AnimalShoutTestMainクラスで、speak(Animal animal){}メソッドを使用して動物animalの鳴き声を出力し、main()メソッドでspeak()メソッドを呼び出し、それぞれ猫、犬、山羊オブジェクトの鳴き声を出力します。

【】の部分にコードを追加してください。

コード:

public class AnimalShoutTest2 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        Goat goat = new Goat();
        speak(cat);
        speak(dog);
        speak(goat);
    }
    // 静的メソッドspeak()を定義
    public static void speak(Animal animal)
    {
        animal.shout();
    }
}
// 抽象クラスAnimalを定義
abstract class Animal{
    abstract String getAnimalClass();
    abstract void shout();
}
// Animalクラスに基づいて猫クラスCatを定義し、2つの抽象メソッドをオーバーライド
class Cat extends Animal{
    @Override
    String getAnimalClass() {
        return "猫";
    }

    @Override
    void shout() {
        System.out.println("猫の鳴き声:ニャーニャー");
    }
}
// Animalクラスに基づいて犬クラスDogを定義し、2つの抽象メソッドをオーバーライド
class Dog extends Animal{
    @Override
    String getAnimalClass() {
        return "犬";
    }

    @Override
    void shout() {
        System.out.println("犬の鳴き声:ワンワン");
    }
}
// Animalクラスに基づいて山羊クラスGoatを定義し、2つの抽象メソッドをオーバーライド
class Goat extends Animal {
    @Override
    String getAnimalClass() {
        return "山羊";
    }

    @Override
    void shout() {
        System.out.println("山羊の鳴き声:メーメー");
    }
}

第8回課題:

7-1 コンテナ-ArrayList-ソート

複数の学生の成績情報(学番、氏名、数学成績、物理成績)を入力します。

学番は各学生の唯一の識別番号であり、重複しません。

氏名は重複する可能性があります。

要件:ArrayListを使用して学生情報を保存します。

入力形式: 複数の学生の成績情報を入力します。各学生の成績情報の形式:学番+半角スペース+氏名+半角スペース+数学成績+半角スペース+物理成績

「end」を入力終了の目印とします。

出力形式: 数学/物理成績の合計点の高い順にすべての学生情報を出力します。各学生情報の出力形式:学番+半角スペース+氏名+半角スペース+数学/物理成績の合計点

成績が同じ場合は、入力された順序で出力します。

コード:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
        List<StudentInfo> students = new ArrayList<>();
        while (reader.hasNextLine()) {
            String line = reader.nextLine().trim();
            if ("end".equals(line)) {
                break;
            } else {
                String[] infos = line.split("\\s+");
                StudentInfo student = new StudentInfo(infos[0], infos[1], Integer.parseInt(infos[2]), Integer.parseInt(infos[3]));
                students.add(student);
            }
        }

        Collections.sort(students, (a, b) -> {
            int sumA = a.math + a.physics;
            int sumB = b.math + b.physics;
            if (sumA != sumB) {
                return sumB - sumA;
            } else {
                return students.indexOf(a) - students.indexOf(b);
            }
        });

        for (StudentInfo student : students) {
            System.out.println(String.format("%s %s %d", student.id, student.name, student.math + student.physics));
        }
    }

    static class StudentInfo {
        String id;
        String name;
        int math;
        int physics;

        public StudentInfo(String id, String name, int math, int physics) {
            this.id = id;
            this.name = name;
            this.math = math;
            this.physics = physics;
        }
    }
}

7-2 科目成績統計プログラム-3

科目成績統計プログラム-3は、第2回の内容を修正し、総成績の計算方法を変更したものです。

要件:クラス構造を修正し、成績クラスの継承関係を組み合わせ関係に変更します。成績情報は科目成績クラスと分項目成績クラスで構成され、科目成績クラスは分項目成績クラスを組み合わせ、分項目成績クラスは成績値と重みの2つの属性で構成されます。

科目成績統計プログラム-2、3を2回完了した後、継承と組み合わせ関係の違いを比較します。どちらの関係がより柔軟で、変更に適応しやすいか考えてみてください。

期末試験:

7-1 立体図形問題

正方体と正三棱錐の表面積と体積をプログラミングで求め、拡張性(継承)と多態性を必ず体現する必要があります。

クラス構造は以下の図のようになります(参考):

上記のクラス設計をプログラミングで完成させ、mainメソッドのソースコードは以下の通りです(直接コピーして使用できます):

public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    double side = input.nextDouble();
    
    display(new Cube(side));
    display(new RegularPyramid(side));
}

ここで、display(Solid solid)メソッドはMainクラスで定義された静的メソッドで、プログラムの多態性を体現する役割を果たします。

注:正三棱錐の体積計算式は底面積×高さ/3です。

入力形式: 実型数を1つ入力し、正方体の辺長と正三棱錐の辺長としてそれぞれ使用します。

出力形式: それぞれ正方体の表面積、体積、正三棱錐の表面積と体積を出力します。小数点以下2桁を保持し、String.format("%.2f",value)を使用して小数桁数を制御することをお勧めします。

7-2 魔方問題

問題説明:この問題の魔方には2種類あり、1つは立方体魔方、もう1つは正三棱錐魔方です。その中で、立方体または正三棱錐魔方は、単位立方体または正三棱錐から構成され、単位立方体または正三棱錐の個数は階数(すなわち層数)によって決まります。つまり、魔方の辺長=階数×単位辺長です。魔方は以下の図のようになります:

「立体図形」問題のソースコードを利用して、以下の機能を実装します:

魔方には3つの属性があります:色、階数、タイプ(立方体魔方、正三棱錐魔方)。プログラムは魔方の色、表面積、体積を出力する必要があります。参考設計クラス図は以下の通りです:

mainメソッドの部分は以下のソースコードを参考にできます(直接コピーして使用できます):

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        
        String color = input.next();
        int layer = input.nextInt();
        double side = input.nextDouble();
        
        RubikCube cube1 = new SquareCube(color, layer, new Cube(side));
        
        color = input.next();
        layer = input.nextInt();
        side = input.nextDouble();
        
        RubikCube cube2 = new RegularPyramidCube(color, layer, new RegularPyramid(side));
        display(cube1);
        display(cube2);
    }
}

ここで、display(RubikCube cube)メソッドはMainクラスで定義された静的メソッドで、魔方の情報を出力するために使用され、多態性を体現します。

入力形式: 第1部:立方体魔方の色、階数、単位立方体の辺長。スペースまたは改行で区切ります。

第2部:正三棱錐魔方の色、階数、単位正三棱錐の辺長。スペースまたは改行で区切ります。

出力形式: 立方体魔方の色 立方体魔方の表面積 立方体魔方の体積 正三棱錐魔方の色 正三棱錐魔方の表面積 正三棱錐魔方の体積

注:小数点以下2桁を保持します。

7-3 魔方ソート問題

魔方問題に基づき、クラス設計をリファクタリングし、リスト内の魔方のソート機能を実装します(魔方の体積に基づいてソート)。

ヒント:問題中のRubikCubeクラスはComparableインターフェースを実装する必要があります。

ここで、Mainクラスのソースコードは以下の通りです(直接コピーして使用できます):

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        
        String color;
        int layer;
        double side;
        RubikCube cube;
        
        ArrayList<RubikCube> list = new ArrayList<>();
        
        int choice = input.nextInt();
        
        while(choice != 0) {
            switch(choice) {
                case 1://SquareCube
                    color = input.next();
                    layer = input.nextInt();
                    side = input.nextDouble();
                    cube = new SquareCube(color, layer, new Cube(side));
                    list.add(cube);
                    break;
                case 2://RegularPyramidCube
                    color = input.next();
                    layer = input.nextInt();
                    side = input.nextDouble();
                    cube = new RegularPyramidCube(color, layer, new RegularPyramid(side));
                    list.add(cube);
                    break;
            }
            choice = input.nextInt();
        }
        
        list.sort(Comparator.naturalOrder());//昇順ソート
        
        for(int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i).getColor() + " " + 
                String.format("%.2f", list.get(i).getArea()) + " " + 
                String.format("%.2f", list.get(i).getVolume()) );
            System.out.println("");
        }
    }
}

入力形式: 魔方タイプを入力します(1:立方体魔方;2:正三棱錐魔方;0:入力終了)

魔方の色、魔方の階数、魔方の単位立方体、正三棱錐の辺長

..ループ..

出力形式: 魔方の体積の昇順でリスト内の各魔方の情報を出力します(実数はすべて小数点以下2桁を保持)。出力スタイルは出力サンプルを参照してください。

ハマったポイントと解決策

コードを記述する際には、可読性と保守性に注意し、適切な命名、コメント、コード構造を使用することを検討してください。一部の機能を関数にカプセル化して、コードの保守性を向上させることもできます。

データ構造の選択:この問題を解決する際には、データを保存および処理するために適切なデータ構造を選択する必要があります。例えば、リストを使用して科目成績と学生情報を保存し、マップを使用してクラスと科目成績の関係を保存します。

配列の境界外アクセス問題:入力を処理する際には、配列の境界外アクセスの問題に注意する必要があります。例えば、学生情報を解析する際には、コースインデックスが有効範囲内にあることを確認する必要があります。そうしないと、配列の境界外アクセス例外が発生する可能性があります。

アルゴリズムの設計と実装:平均成績を計算する際には、科目成績と重みを処理するために適切なアルゴリズムを設計する必要があります。例えば、科目成績と分項目成績を反復処理し、総成績を計算するために2つのループを使用しました。

改善と提案

使用するアルゴリズムとデータ構造が効率的であり、プログラムの要件を満たしていることを確認してください。適切な場合は、より効率的なデータ構造またはアルゴリズムを検討して、プログラムのパフォーマンスを向上させることができます。

Javaプログラムはメモリ管理において一定の利点がありますが、依然としてメモリ使用状況に注意する必要があります。使用されなくなったオブジェクトを適時に解放し、メモリリーク問題を回避してください。メモリ分析ツールを使用してメモリを監視し、メモリ問題を解決することができます。

大量の同時リクエストを処理する必要があるか、長時間実行するタスクを実行する必要があるプログラムについては、マルチスレッドまたは並列処理を使用してプログラムのパフォーマンスと応答性を向上させることができます。Javaが提供するスレッドプールと並行ツールを使用すると、並列プログラミングの複雑さを簡素化できます。

例外処理を適切に処理することは、プログラムの安定性と信頼性にとって非常に重要です。try-catchステートメントを使用して例外をキャッチし、特定の状況に応じて適切な例外処理戦略(再試行、ロールバック、またはエラーメッセージの記録など)を選択してください。

学生、科目、成績などの情報をオブジェクトにカプセル化して、コードの可読性と保守性を向上させることを検討してください。Student、Course、Gradeなどのクラスを作成して関連エンティティを表現し、関連操作を処理するために適切なメソッドと属性をクラス内で定義できます。

データの保存と処理に適切なデータ構造を使用することを検討してください。例えば、Setを使用してクラス情報を保存し、Mapを使用して学生と成績の関係を保存することができます。これにより、データの検索と操作がより便利になります。

ストリーム操作とラムダ式の使用:Java 8で導入されたストリーム操作とラムダ式を使用すると、コードを簡素化し、可読性を向上させることができます。例えば、stream()メソッドとfilter()メソッドを使用してデータをフィルタリングし、map()メソッドを使用してデータを変換することができます。

入力を処理する際には、エラー処理と例外処理を考慮する必要があります。例えば、学生情報を解析する際には、配列の境界外アクセス例外などの可能性のある例外をキャッチするためにtry-catchブロックを使用し、対応する処理を行うことができます。

機能を設計および実装する際には、単一責任の原則に従い、各クラスとメソッドが特定の機能のみを担当することを确保してください。これにより、コードの保守性と再利用性が向上します。

まとめ

オブジェクト指向プログラミング(OOP)の概念とJavaでの実装を学びました。Javaはオブジェクト指向プログラミング言語であり、高品質なJavaプログラムを記述するにはオブジェクト指向の思想と原則を理解することが不可欠です。クラス、オブジェクト、継承、カプセル化、多態性などの概念を学び、実践プロジェクトを通じて学習内容を固めました。

次に、JavaのコアAPIを学びました。Javaは豊富なクラスライブラリとAPIを提供しており、様々なアプリケーションを開発するために一般的なAPIを習得することが非常に重要です。文字列処理、コレクションフレームワーク、IO操作、マルチスレッドプログラミングなどの一般的なAPIを学び、インスタンスを通じて理解を深め、使用に慣れました。

さらに、一般的な開発ツールとフレームワークについても理解し学びました。例えば、EclipseやIntelliJ IDEAなどの統合開発環境を使用してJavaプログラムを記述およびデバッグし、Mavenを使用してプロジェクトのビルドと依存関係管理を行い、Springフレームワークの基本的な使い方に慣れました。これらのツールとフレームワークは、開発効率とコード品質を向上させることができます。

Javaを学習する過程で、いくつかの課題に直面しました。時には理解しにくい概念や間違ったコード論理に遭遇しましたが、ドキュメントを参照し、関連書籍を読み、オンラインのチュートリアルを参考にし、フォーラムに質問するなどの方法を通じて、最終的に問題を解決し、考えを整理することができました。

タグ: Javaプログラミング オブジェクト指向 データ構造 アルゴリズム 継承

8月3日 09:43 投稿