Tagbangers Blog

Javaでlsコマンドを作る

こんにちは、藤岡です。

今週も、先週に引き続き「ターミナルのlsコマンドをJavaで作る」ということをやりました。

ようやく完成に近づいたので、それを共有したいと思います。試作品はこちらです。

このプログラムを作るに当たって、ポイントとなったのは

  • オプションが指定された時にそれをどう受け取るのかということ
  • オプションが指定された時にそれぞれどう場合分けして処理させればいいのかということ

ということです。

それぞれどう対処したのかを解説します。


1. オプションをどう受け取るか

これが最初の難関でしたが、Character型の配列に指定されたものを追加していくという方法で解決しました。

具体的には、以下のようにしました。

private ArrayList<Character> opt = new ArrayList<>();
 for(int j = 1; j < args.length; j++) {
     if(args[j].charAt(0) == '-') {
         for(int i = 1; i < args[j].length(); i++) {
             opt.add(args[j].charAt(i));
         }
     } else {
         System.out.println("No such file or directory");
     }
 }

これは、コマンドライン引数の第2引数以降に「-」から始まる文字を指定された時に、それを1文字ずつ配列に追加するという意味のコードです。最初はかなり複雑に考え、様々な場合分けをしてこの処理を行おうとしましたが、結局このようなシンプルな形にまとまりました。


2. オプションをどう処理するか

for(int i = 0; i < opt.size(); i++) {
    switch(opt.get(i)) {
        case 'l':
            break;
        case 'a':
            break;
        case 't':
            arrayFile = new TreeSet<>(new Comparator<File>() {
                @Override
                public int compare(File o1, File o2) {
                    if(o1.lastModified() == o2.lastModified()) {
                        return 1;
                    }
                    return (int)(o2.lastModified() - o1.lastModified());
                }
            });
            for(File f: files) {
                arrayFile.add(f);
            }
            break;
        case 'r':
            break;
        default:
            System.out.println("command not found");
            break;
    }
}

if(opt.contains('r')) {
    list = new ArrayList<>(arrayFile);
    Collections.reverse(list);
}

最終的にはswitch文を使って、それぞれの文字が指定された場合の処理分けをしました。「l」と「a」は表示法の問題なのでいいとして、「t」と「r」が同時に指定された時の処理が問題となりました。なぜなら、TreeSetを使っているので、順序を並び替える時に自然順序付けを定義したComparatorを搭載したクラスをnewしなければならないからです。そのため、「t」と「r」が同時に指定されると、指定される順番で優先される方が決まってしまい、同時に実装することができなくなってしまいました。この場合は2つなので、switchを抜けたところで「r」の配列をnewすることで解決しましたが、これ以上オプションを増やそうとするとどうしようもなくなってしまいます。そういった意味でこのプログラムは問題を孕んでいるので、さらにスキルアップしてから見直し、改善したいと思います。また、さらに本来のlsコマンドに近づけるために、パスを渡してその配下にあるディレクトリやファイルを表示させるということにも対応させるようにするのが、次なる目標です。


3. 遭遇したエクセプション

今回、このプログラムを作る上でいくつかのエクセプションに出くわしたのでその解説を共有しようと思います。

  • NullPointerException

:インスタンスへの参照がない値にアクセスした際に発生する例外

String str1 = "";
String str2 = null;
System.out.println("start ...");
System.out.println("str1.length : " + str1.length());
System.out.println("str2.length : " + str2.length());

具体的には上のような時に、str2.length()が原因で発生します。この場合、str1.length()では例外は発生せず、0が返ってきます。


  • StringIndexOutOfBoundsException

:存在しない文字にアクセスしようとすると発生する例外

String option = "latr"
System.out.println(option.charAt(4));

具体的には、このような時に例外が発生し、それはoptionのインデックス番号の4番目は存在しないからです。


4. その他に学んだこと・感じたこと

  • 配列が0から始まるのは、配列数を取得してforループを回しやすいからだと思った。
  • オーバーロードは引数の型が違っていればOK
  • staticなものはcompileした時点で作成されるが、staticでないものは実行時にはまだ存在しないため、newしなければならない。
  • ゲッターは、他からアクセスされたくない変数をメソッド化して、その戻り値として得る。
  • スラッシュやドットを使ってアクセスするものを相対パス、名前をそのままで取得するものを絶対パスという。