20200922のJavaに関する記事は9件です。

JPAのOneToManyのdeleteがupdateになる

JPAのOneToManyのコレクションを削除をする場合、CascadeType.REMOVEorphanRemoval = trueを使用する。このとき、発行されるSQLはdeleteを期待するがupdateになってしまった。これの原因はエンティティ間の定義(OneToManyとかJoinColumn)がおかしい場合になるらしい。具体例を出せなくてアレだが https://stackoverflow.com/questions/17079649/jpa-orphanremoval-true-tries-to-update-not-delete とかが参考例になる、と思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】ライブラリを用いてJson文字列の要素を編集してみる

やりたいこと

String jsonString = "{\"Apple\":{" 
                        + "\"cost\":\"0\"},"
                    + "\"Pen\":{"
                        + "\"cost\":\"0\"}}";

やりたいのは以下の2点です。

  • 上記のJson文字列の"0"にあたる部分を編集したい。
  • 新たなキー"ApplePen"を追加したい。

今回は以下の代表的(?)なJsonを操作するためのライブラリを使ってみます。

  • Gson
  • Jackson

実行環境

Android Studio: 3.4.1
gradle: 5.1.1

ライブラリ追加

追加するライブラリをbuild.gradleのdependenciesブロックに記述します。

dependencies {
    ...
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
    implementation 'org.glassfish:javax.json:1.1.4'
}

Gson

作成したメソッド(editJson)でJson文字列を
Map<String, MAP<String, String>>という形に置き換え(Gson.fromJson)、目的のキーと値を代入しています。
代入後、Mapを再びJson文字列に変換しています(Gson.toJson)。

本題と逸れますが、
[Json => 他のクラス] の変換を「Deserialization」、
[他のクラス => Json] の変換を「Serialization」と呼ぶんですね。

import com.google.gson.Gson;
...

public class GsonExample {

    String jsonString = "{\"Apple\":{" +
                            "\"cost\":\"0\"}," +
                        "\"Pen\":{" + 
                            "\"cost\":\"0\"}}";

    public void main() {
        editJson("Apple", "100");
        /*
            {
                "Apple": {
                    "cost": "100"
                },
                "Pen": {"
                    "cost": "0"
                }
            }"
        */
        editJson("ApplePen", "");
        /*
            {
                "Apple": {
                    "cost": "100"
                },
                "Pen": {"
                    "cost": "0"
                }
                "ApplePen": {"
                    "cost": "0"
                }
            }"
        */
    }

    private void editJson(String key, String str) {
        if (str.isEmpty()) str = "0";
        Map<String, String> innerMap = new HashMap<>();
        innerMap.put("cost", str);

        Gson gson = new Gson();
        // Json文字列をMapに変換
        Map<String, Map<String,String>> jsonMap = gson.fromJson(jsonString, Map.class);
        jsonMap.put(key, innerMap);
        // Mapを再びJson文字列に変換
        jsonString = gson.toJson(jsonMap);
    }
}

Jackson

Gsonの時とほぼ同様に置き換え処理を行なっています。
JacksonではObjectMapperのメソッドを介してJsonを変換します。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
...

public class JacksonExample {
    ...

    private void addValueToJson(String str, String key) {
        if (str.isEmpty()) str = "0";
        Map<String, Object> map = null;
        Map<String, String> innerMap = new HashMap<>();
        innerMap.put("cost", str);

        ObjectMapper mapper = new ObjectMapper();
        try {
            // Json文字列をMapに変換
            map = mapper.readValue(json, 
                                    new TypeReference<Map<String, Object>>() {});
        } catch (IOException e) {
            e.printStackTrace();
        }
        map.put(key, innerMap);
        try {
            // Mapを再びJson文字列に変換
            json = mapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

javax.jsonについて

javax.jsonライブラリでも同様の実装ができるか試してみましたが、行き詰まってしまいました。

  • Json文字列をMapに変換できなそう?
  • JsonObjectに対して書き換え処理ができない。(UnsupportedOperationException発生)

感想

  • Gsonは使いやすい。
  • TypeReferenceがなにもわからない。

参考文献

以下の記事を参考にさせていただきました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

java.util.logging.Loggerのログレベルについて

java.util.logging.Loggerとは

Javaの標準API。エラー情報を内容に応じてログ出力する。

使用方法

まずは基本的な使い方です。主な登場実物は以下の3つです。
1. Logger - ログを出力するオブジェクト。
2. Handler - ログの出力先を制御するオブジェクト。
3. Formatter - ログのフォーマットを制御するオブジェクト。

初めにLoggerインスタンスを作成します。

Logger logger = Logger.getLogger("loggerの名前")

次にHandlerインスタンスを作成し、loggerに登録します。Handlerは複数個登録できます。
今回はC:\sample\sample.logを出力先としたHandlerを作成します。ファイルを出力先とする場合はHandlerインターフェースの実装クラスFileHandlerを利用します。

Handler handler = new FileHandler("C:\\sample\\sample.log");
logger.addHandler(handler);

最後にFormatterインスタンスを作成し、Handlerに登録します。
今回は人が読みやすい形にフォーマットするため、SimpleFormatterを利用します。SimpleFormatterはFormatterインターフェースの実装クラスです。

Formatter formatter =  new SimpleFormatter();
handler.setFormatter(formatter);

これで準備は完了です。loggerにメッセージを渡してログを出力します。

ログレベル

ログを出力する際にログレベルを指定します。
以下の要求レベルを設定することができます。

Loggerメソッド 対応する要求レベル 出力される名称
-- Level.ALL --
Logger.finest() Level.FINEST 最も詳細
Logger.finer() Level.FINER 詳細
Logger.fine() Level.FINE 普通
Logger.config() Level.CONFIG 構成
Logger.info() Level.INFO 情報
Logger.warning() Level.WARNING 警告
Logger.severe() Level.SEVERE 重大

例えば、Level.CONFIGを設定するとログとして出力されるのは、「構成」「情報」「警告」「重大」という風に、設定レベル以下が表示されます。
Level.ALLはその名の通り、全てを表示させます。

実装による確認

以下の実装により実際のログ出力を確認します。

Log
public class Log {

    public static Logger getLog(String name) {
        // loggerの生成
        Logger logger = Logger.getLogger(name);

        try {
            // ハンドラーの生成(true:書き込み,false:上書き)
            Handler handler = new FileHandler("/log/sample.log", false);
            // SimpleFormatよるlogフォーマットの指定
            handler.setFormatter(new SimpleFormatter());
            // ログレベルの指定/ハンドラーの設定
            logger.addHandler(handler);
            logger.setLevel(Level.ALL);

            // 標準出力の設定
            ConsoleHandler consoleHandler = new ConsoleHandler();
            consoleHandler.setLevel(Level.CONFIG);
            logger.addHandler(consoleHandler);

            // 親ロガーの設定(true:親ロガーへ送信,false:親ロガーへ送信しない)
            logger.setUseParentHandlers(false);
            // ログ出力
            logger.finest("FNST");
            logger.finer("FNR");
            logger.fine("FN");
            logger.config("CFG");
            logger.info("INF");
            logger.warning("WNG");
            logger.severe("SVR");

            throw new IOException();

        }  catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
            logger.log(Level.CONFIG, "エラー発生", e);
            logger.warning("WNG:エラー発生");
        }
        return logger;
    }
}

このクラスはjava.util.logging.Loggerをラップしたクラスになります
クラス名を引数にgetLog()メソッドを呼び出すことによりログの挙動を確認します

1.getLogger()を使用してloggerを生成し、ログレベルの設定

java.util.logging.Logger のgetLoggerメソッドを利用しloggerを生成
setLevelメソッドでログレベルを指定する

2.ファイルで出力する設定(FileHandler)

ログファイルの場所、フォーマット、ログレベルを設定する。
FileHandlerを利用し、第一引数:ファイルパス第二引数:上書き・追記の設定をする
trueが上書き、falseが追記になる。今回は一度実行した場合の挙動を見たいためtrueにする
生成したhandlerにログフォーマットの設定をする
標準ではXML型式にてログが出力されるが、見づらいためSimpleFormatter()で日本語にて出力するように設定する
handlerにログレベルを設定して、loggerに一定のログレベル以上のメッセージのみ出力するように設定することが出来ます。
今回はALLを設定することにより全てのログ出力をファイルに記載します

3.標準出力を設定(ConsoleHandler)

ConsoleHandlerを設定することでログの内容がコンソール上でも確認できるようになる。
設定方法はFileHandler同様にaddHandler() メソッドを使用することでできる
標準出力もログレベルを設定し、CONFIG以上のログが出力されるようにします

4.親ロガーの設定

falseにしてますがtrueにすると、要求レベル未満の標準出力が二重に表示されたりレベルが合わなかったりする様です。
二重に表示されるのは、作成したログは親ロガーに送信されるので、親ロガーに登録されたログが出力されるのでしょう。

5.ログの設定

ログ確認のため各ログレベルの出力をしています。

6.エラー発生時のログ確認

IOExceptionを発生させ、最後のエラー発生時のログにつなげています。

maiinクラスを作成し、呼び出すことで挙動を確認します。

実行結果

Sample.log
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
最も詳細: FNST
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
詳細: FNR
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
普通: FN
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
構成: CFG
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
情報: INF
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
警告: WNG
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
重大: SVR
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
構成: エラー発生
java.io.IOException
    at app.abstractDemo.log.Log.getLog(Log.java:43)
    at app.abstractDemo.MainActivity.main(MainActivity.java:10)

9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
警告: WNG:エラー発生

SimpleFormatter()のおかげで、ログがXML型式ではなく日本語にて出力されるように修正されていることが確認できました。
loggerのログレベルをALLにしていることからすべてのlogが出力されていること

標準出力
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
構成: CFG
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
情報: INF
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
警告: WNG
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
重大: SVR
java.io.IOException
    at app.abstractDemo.log.Log.getLog(Log.java:43)
    at app.abstractDemo.MainActivity.main(MainActivity.java:10)
9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
構成: エラー発生
java.io.IOException
    at app.abstractDemo.log.Log.getLog(Log.java:43)
    at app.abstractDemo.MainActivity.main(MainActivity.java:10)

9 21, 2020 2:18:27 午前 app.abstractDemo.log.Log getLog
警告: WNG:エラー発生

consoleHandlerのログレベルをCONFIGで設定してることからCONFIG以上のログが出力されていることが確認できました

フィルター

ロガーにログレベルを設定して一定のログレベル以上のメッセージのみを出力するように制御する方法を紹介しましたが、より詳細な制御を行いたい場合はFilterクラスを利用します。

Filter filter = new Filter() {
    @Override
    public boolean isLoggable(LogRecord record) {
        return record.getMessage().contains("処理件数=");
    }
};
logger.setFilter(filter);

上記のようにFilterを設定した場合、メッセージに"処理件数="を含むログのみが出力されるようになります。
実装に下記レコードを追加して処理を確認します

Log
            // ロガーにフィルターを設定
            Filter filter1 = new Filter() {
                @Override
                public boolean isLoggable(LogRecord record) {
                    return record.getMessage().contains("処理件数=");
                }
            };
            logger.setFilter(filter1);
            logger.info("サンプルメッセージ");                          // 出力されない
            logger.info("サンプルメッセージ, 処理件数=1");               // 出力される

            // ハンドラーにフィルターを設定
            Filter filter2 = (record) -> record.getMessage().contains("更新件数=");
            handler.setFilter(filter2);
            logger.info("サンプルメッセージ");                          // 出力されない
            logger.info("サンプルメッセージ, 処理件数=1");               // 出力されない
            logger.info("サンプルメッセージ, 処理件数=1, 更新件数=1");    // 出力される

            // ハンドラーにログレベルを設定
            handler.setLevel(Level.WARNING);
            logger.info("サンプルメッセージ");                          // 出力されない
            logger.info("サンプルメッセージ, 処理件数=1");               // 出力されない
            logger.info("サンプルメッセージ, 処理件数=1, 更新件数=1");    // 出力されない
            logger.warning("警告メッセージ, 処理件数=1, 更新件数=1");   // 出力される

フィルターとログレベルによりコメントで出力されると書かれたログのみ出力される想定です。

実行結果

sample.log
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
情報: サンプルメッセージ, 処理件数=1
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
情報: サンプルメッセージ, 処理件数=1, 更新件数=1
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
警告: 警告メッセージ, 処理件数=1, 更新件数=1

想定どおり3つのログが出力されています。

標準出力
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
情報: サンプルメッセージ, 処理件数=1
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
情報: サンプルメッセージ, 処理件数=1
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
情報: サンプルメッセージ, 処理件数=1, 更新件数=1
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
情報: サンプルメッセージ, 処理件数=1
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
情報: サンプルメッセージ, 処理件数=1, 更新件数=1
9 21, 2020 12:14:03 午後 app.abstractDemo.log.Log getLog
警告: 警告メッセージ, 処理件数=1, 更新件数=1

consoleHandlerにフィルターを設定してないため、loggerに設定したフィルターの設定のみ適用される。
よって、fillter1に設定した「処理件数=」とconsoleHandlerに設定したログレベル「CONFIG」が適用され出力がされていること

Log出力の制御

ロガーへ設定したログレベル・フィルターのチェックは、直接呼び出されたロガーでのみ行われる。
ハンドラーへ設定したログレベル・フィルターのチェックは、親ロガーのハンドラーも含めて全てのハンドラーでチェックされる。
ログ出力のロジックはざっくり言うと以下の図のようになっています。

image.png

ロガーへ設定したログレベル・フィルターのチェックは呼び出しを受けた最初のロガーでのみチェックされるのに対して、
ハンドラーへ設定したログレベル・フィルターのチェックは全てのハンドラーで行われています。

ロガーの設定は呼び出し時のみ行われるため親ロガーには伝播されず、ログの出しわけが想定どおりに行かないことがありあます。
例えば親ロガー:WARNING、子ロガー:FINE
ハンドラー:ALL

で設定した場合親ロガーではWARNINGでログレベルを設定しているにもかかわらず、FINEで設定しているログが出力されます。
これはハンドラーの設定が引き継がれチェックされていることから親ロガーの設定がされていないように見えてしまうのです。
以下の設定に変更することより設定を適用できます。
親ロガー・子ロガー:ALL
ハンドラー:WRANING

プロパティファイルの読み込み

ソース上にログの設定を記述していくと煩雑になり読み辛くなってしまいます。

設定ファイルをプログラムから読み込む方法は以下の通りです。今回はサンプルプログラムの同じクラスパスに配置しているlogging.propertiesを読み込むようにしました。
設定ファイルはjava.util.Properties形式で読み込む必要があります。

LogManager.getLogManager().readConfiguration(SettingFileSample.class.getResourceAsStream("logging.properties"));

設定ファイルのサンプルは以下の通りです。詳しい説明は{java.home}\lib\logging.propertiesにも記載されていますのでご参照ください。

logging.properties
# ルートロガーのハンドラー・ログレベルの設定 ConsoleHandlerは標準エラー(System.err)へログを出力します。
handlers=java.util.logging.ConsoleHandler
.level=INFO

# サンプルプログラムで利用するロガー"sample.SettingFileSample"の設定
sample.SettingFileSample.level=FINE
sample.SettingFileSample.handlers=java.util.logging.FileHandler

# ConsoleHandlerの設定
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter

# FileHandlerの設定
java.util.logging.FileHandler.pattern=C:/sample/sample.log
java.util.logging.FileHandler.limit=1000
java.util.logging.FileHandler.count=1
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter

上記の設定ファイルを利用したサンプルプログラムを以下に記載します。

SettingFileSample
public class SettingFileSample {

    public static void main(String[] arg) throws SecurityException, IOException {
        LogManager.getLogManager().readConfiguration(SettingFileSample.class.getResourceAsStream("logging.properties"));

        Logger logger = Logger.getLogger("sample.SettingFileSample");
        logger.finer("FINERメッセージ");
        logger.fine("FINEメッセージ");
        logger.info("INFOメッセージ");
    }
}

ロガーやハンドラーの設定がなくなってすっきりしましたね。

参考記事

java.util.loggingの使い方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インターフェースの利点

はじめに

クラス間で共通する操作をひとまとめに定義させたり、空のインターフェースを定義することで実装クラスに何らかの特性をもたせて処理の意図を明確にさせたりと、インターフェースにはクラスの設計を助けてくれる利点が色々とあります。

中でも次に掲げる点は、インターフェースの最大の利点と感じる人が多いのではないのでしょうか。

  • 利用者1がそのクラスの内部構造を知らずして、ある契約2に沿ってそのクラスの機能を利用することを強制させることができる点
  • 実装が変更した場合に生じる不必要な修正を防ぎ、変更に強いプログラムを維持できる点

ただ、これらの意味がいまいち整理できていなかったので、自分なりの言葉で整理することとしました3

インターフェースを使わないと・・・

例えば次のように、Aクラスに3つのメソッドを定義したとします。

class A {
    // コールするメソッド
    void call() {
        System.out.println("たたいて・かぶって・ジャンケンポン");
    }
    // ジャカるメソッド
    void jakajaka() {
        System.out.println(
            "ジャカジャカジャン、ジャカジャカジャン、ジャカジャカジャカジャカ、ジャンケンポーン!"
        );
    }
    // 踊るメソッド
    void odoru() {
        System.out.println("事件は会議室で起きてるの。");
        System.out.println("違う、現場だ。");
        System.out.println("会議室よ。");
        System.out.println("現場、現場。");
        System.out.println("会議室、会議室、会議室。");
        System.out.println("現場、現場、現場、現場、...現場室!!!");
    }
}

そして、このクラスを利用するために、次のようなコードを定義したとします。

A a = new A();
a.call();
a.jakajaka();
a.odoru();

これを実行すれば、当然ながらそれぞれのメソッドによって定義されたメッセージが出力されます。

ここで、次のようにAクラスの実装を変更したBクラスを新設し、利用者に対し今後はAクラスではなくBクラスを利用するよう呼びかけたとします。

実装の変更
class B {
    // callメソッドを削除
    // メソッド名をjakajakaからsingへ変更
    void sing() {
        ... // 同上
    }
    // メソッド名をodoruからgenbashitsuへ変更
    void genbashitsu() {
        ... // 同上
    }
}

利用者はAクラスをやめてBクラスを利用しなければならなくなるため、Bクラスの実装を把握し、今までのコードを次のように修正する必要が生じます。

B b = new B();
b.sing();
b.genbashitsu();

singメソッド、genbashitsuメソッドからは今までと同様のメッセージが出力されるものの、callメソッドが使えず困った利用者が、呼びかけに反し一部Aクラスの機能を利用してしまったとします。

違反例
A a = new A(); // 呼びかけ違反
a.call();      // 呼びかけ違反
B b = new B();
b.sing();
b.genbashitsu();

その場しのぎとしては良いかもしれませんが、もしAクラスが廃止予定であったとしたら、そのうちAクラスにアクセスしている箇所はすべてコンパイルエラーとなるかもしれません。そのときは、将来その箇所を修正しなければならなくなるでしょう。

このようにインターフェースを使わない場合では、何らかの実装の変更が生じたときに、利用者が新しい実装の変更箇所を把握し、今までどおりの実装を維持するためにどのようにコードを修正しなければならないかを考えなければならなくなります。また、利用すべきでない古い機能を利用してしまえば、将来その機能が廃止されたときに生じる修正に手間をかけることとなります。

インターフェースを使うと・・・

インターフェースを使えば、利用者に対し利用できるメソッドを知らせることができます。利用者は、インターフェースに定義されているメソッドと利用する実装クラスの機能さえ把握していれば、たとえ実装が変更されたとしても、利用できるメソッドと利用できないメソッドの確認をとる必要はなくなります。

interface Contract {
  void call();
  void sing();
  void genbashitsu();
}
class B implements Contract {
    // コールするメソッド
    @Override public void call() {}; // 空実装
    // ジャカるメソッド
    @Override public void sing() {
        ... // 同上
    }
    // 踊るメソッド
    @Override public void genbashitsu() {
        ... // 同上
    }
}

また、インターフェースを実装するクラスに空実装されたメソッドが用意されていれば、必要に応じて利用者はそのメソッドを無名クラスを利用して実装することもできるようになるでしょう4

インターフェースの利用例
Contract c = new B() {
    @Override public void call() {
        System.out.println("たたいて・かぶって・ジャンケンポン");
    }
};
c.call();        // たたいて・かぶって・ジャンケンポン
c.sing();        // ジャカジャカジャン、ジャカジャカジャン、ジャカジャカジャカジャカ、ジャンケンポーン!
c.genbashitsu(); // 事件は(中略)現場室!!!

まとめ

インターフェースを使えば、利用者が実装の変更に伴う修正の必要性に怯えることなく、柔軟なプログラムを構築することができるようになります。次のコードのように新しい実装クラスが登場し、そのクラスを利用したくなったとしても、生成するインスタンスを変更するだけでその実装を利用することができます。

新しい実装クラス
class NewB implements Contract {
  // コールするメソッド
  @Override public void call() {}; // 空実装
  // ジャカるメソッド
  @Override public void sing() {
    System.out.println("オウイエア");
  }
  // 踊るメソッド
  @Override public void genbashitsu() {
    System.out.println("ララララブサンバディトゥナイト ネバネバネバネバネバネバネバレッザラブゴー");
  }
}
利用するクラスの切り替え
Contract c = new NewB() {
    @Override public void call() {
        System.out.println("たたいて・かぶって・ジャンケンポン");
    }
};
c.call();        // たたいて・かぶって・ジャンケンポン
c.sing();        // オウイエア
c.genbashitsu(); // ララララブサンバディトゥナイト ネバネバネバネバネバネバネバレッザラブゴー

  1. 定義されたクラスを利用する者とします。以下同じ。 

  2. そのプログラムが満たすべき要件や仕様のことをいいます。 

  3. 実務経験のない、ただプログラミングを趣味としているだけの者であるため、正直自信はありません。解釈に誤りなどございましたら、ご指摘やご提案を頂けますと幸いです。 

  4. 無名クラスを利用すれば、空実装以外であってもアクセスできるメソッドであればオーバーライドすることはできます。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaの処理時間計測

コード

long hogeStart = System.currentTimeMillis();

// 計測したい処理

long hogeEnd = System.currentTimeMillis();
long hogeTime = hogeEnd - hogeStart;
System.out.println("処理時間: " hogeTime + " ms");

実際の使用

  • 以下をClipy等でスニペット登録しておく
long hogeStart = System.currentTimeMillis();
long hogeEnd = System.currentTimeMillis();
long hogeTime = hogeEnd - hogeStart;
  • 計測したい処理を行入れ替え等で間に挟み込む
    d321cd92f73344804aefe4af36029bfa.gif

  • 複数処理を計測したい場合はhogeを計測したい任意の処理名に置換して使う
    212ada11a3dee2dd897f51e7c50139c0.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java]処理時間の計測方法メモ

コード

long hogeStart = System.currentTimeMillis();

// 計測したい処理

long hogeEnd = System.currentTimeMillis();
long hogeTime = hogeEnd - hogeStart;
System.out.println("処理時間: " hogeTime + " ms");

実際の使用

  • 以下をClipy等でスニペット登録しておく
long hogeStart = System.currentTimeMillis();
long hogeEnd = System.currentTimeMillis();
long hogeTime = hogeEnd - hogeStart;
  • 計測したい処理を行入れ替え等で間に挟み込む
    d321cd92f73344804aefe4af36029bfa.gif

  • 複数処理を計測したい場合はhogeを計測したい任意の処理名に置換して使う
    212ada11a3dee2dd897f51e7c50139c0.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IKS + Db2 on IBM Cloud で DBアクセスアプリを動かしてみた(6.DBアクセスアプリケーションの準備 (java))

はじめに

IKS + Db2 on IBM Cloud で DBアクセスアプリを動かしてみた」の 第6回です。
ここでは、 サンプルアプリケーションの準備ついて記載します。

実施内容は以下の通りです。

  • コンパイル環境の概要
  • ソースの準備
  • war ファイルの出力

筆者自身がアプリケーション開発者ではないため、より良いやり方や
より品質の高いコードなどがあると思いますが、 当記事では動かすことを主目的とし、
品質やセキュリティ、エラー処理などは考慮しませんのでご注意ください。
個人的にテーブル名等をハードコードしているのも気に入りませんが、まぁ気にしないでください。

コンパイル環境の準備

Java アプリケーションのソースからコンパイルするためには JDK を含む IDE (Integrated development Environment) が必要です。
(厳密には、JDK だけあればコンパイルできますが…)

IDE には Eclipse を利用します。(特に深い意味はありません。なんでもよいです)
Eclipse ですが、初心者ですので Pleiades All in One パッケージ を利用します。
オールインワンで Java EE まで対応できるようです。作ってくださった方には頭が下がります。。

Pleiades All in One パッケージ から Windows 用のファイルをダウンロードします。
(今回は、下図 Eclipse 2020 を利用しています。)

49.png

リンク先で、 Java の Full Edition からダウンロードします。

50.png

ダウンロード後、 zip ファイルを展開します。
(パスが長いとエラーが出ることがありますので、できるだけパスの短いところに配置、展開してください)

展開後、 eclipse フォルダ内にある、 eclipse.exe をダブルクリックします。
ワークスペース選択画面では、デフォルトのままで「起動」ボタンを押します。

51.png

ソースの準備

eclipse 起動後、パッケージ・エクスプローラー画面が表示されるため、
「プロジェクトの作成...」リンクを押します。
表示されるウィンドウの中から、「動的Webプロジェクト」を選択し、「次へ」を押します。

52.png

プロジェクト名(今回は LibertyCounter)を付け、 ターゲット・ランタイムは Java8 を選択します。
(Java8 でなくてもよいですが。。)
その後、「完了」ボタンを押します。

53.png

パッケージ・エクスプローラーが表示されるため、そこに表示されているプロジェクトツリーを展開し
src 部分で 右クリック > 「新規」 > 「クラス・ を選択します。

54.png

ここでは、 パッケージ名を counter とし、クラス名を Counter としました。
そこまで指定し、「完了」を押します。

55.png

パッケージ・エクスプローラーのツリーに Counter.java が表示され、右側のペインに、 Counter.java のソースが表示されます。

56.png
57.png

そのソースウィンドウを、以下のソースで上書きします。
このソース自体は、Liberty への既存の JDBC アプリケーションのデプロイ に記載されているものをベースに修正しています。
まず全文掲載しますが、この後ポイント部分はピックアップして補足します。

Counter.java
package counter;

import java.io.*;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;
import javax.sql.DataSource;

/**
 * Servlet implementation class Counter
 */
@WebServlet("/Counter")
public class Counter extends HttpServlet {
    @Resource(name = "jdbc/sample")
    private DataSource ds1;
    private Connection con = null;
    private String hostname = null;
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public Counter() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub

        hostname = getHostName();

            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            out.println("<H1>Response from : "+ hostname +"</H1>\n");
            try {
               con = ds1.getConnection();
               Statement stmt = null;
               stmt = con.createStatement();

               // check if hostname exists
               ResultSet result = stmt.executeQuery("select distinct hostname from test01 where hostname='"+hostname+"'");

               if( !result.next() ) {
                   out.println("Insert hostname into test01<br>\n");
               stmt.executeUpdate("insert into test01 values ('"+hostname+"','1')");
               } else {
                   out.println("Update hostname access count in test01<br>\n");
               stmt.executeUpdate("update test01 set count=count+1 where hostname='"+hostname+"'");
               }

           ResultSet result2 = stmt.executeQuery("select * from test01");

           while(result2.next()) {
               out.println("<font size=\"+2\">"+result2.getString(1)+" : </font><font size=\"+2\" color=\"red\">"+result2.getInt(2)+"</font><br>\n");
           }
            }       
            catch (SQLException e) {
            e.printStackTrace();
            }
                finally {
                    if (con != null){
                        out.println("<H1>DB2 Output Completed</H1>\n");
                        try{
                            con.close();
                        }
                        catch (SQLException e) {
                           e.printStackTrace();
                        } 
                }
            }
        }

    /**
     *  getHostName
     * 
     */

    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return "UnknownHost";
    }


    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

以下、抜粋です。

/Counter でアクセスします。 また、DB接続の際の JDBC リソースとして jdbc/sample を利用します。(ハードコード…)

@WebServlet("/Counter")
public class Counter extends HttpServlet {
    @Resource(name = "jdbc/sample")

doGet メソッド内では、 private メソッドの getHostName を用いて、
ホスト名を取得しています。
また、 response として、 HTML をべた書きする形で文字列を格納しています。

        hostname = getHostName();

            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            out.println("<H1>Response from : "+ hostname +"</H1>\n");

DBへのコネクションを取得します。この辺りはもうおまじない的です。

            try {
               con = ds1.getConnection();
               Statement stmt = null;
               stmt = con.createStatement();

まず最初に select distinct ... のSQL 文を使い、 hostname がすでに登録されているかどうかを確認しています。
取得できなかった(つまりは初回アクセスの)場合、 Insert文で ホスト名と 1 というデータをテーブルに格納します。
取得できた場合は、 Update 文で count 列を +1 しています。

               // check if hostname exists
               ResultSet result = stmt.executeQuery("select distinct hostname from test01 where hostname='"+hostname+"'");

               if( !result.next() ) {
                   out.println("Insert hostname into test01<br>\n");
               stmt.executeUpdate("insert into test01 values ('"+hostname+"','1')");
               } else {
                   out.println("Update hostname access count in test01<br>\n");
               stmt.executeUpdate("update test01 set count=count+1 where hostname='"+hostname+"'");
               }

次に、テーブルの全データを取得し、 response 変数に HTML 形式で格納します。

           ResultSet result2 = stmt.executeQuery("select * from test01");

           while(result2.next()) {
               out.println("<font size=\"+2\">"+result2.getString(1)+" : </font><font size=\"+2\" color=\"red\">"+result2.getInt(2)+"</font><br>\n");

ロジック的な部分は以上です。 他の部分は興味があればご確認ください。

war ファイルの出力

本来であれば、eclipse に登録した tomcat 等でテストをするところではありますが、
そのあたりは飛ばして、 war ファイルを出力します。

LibertyCounter プロジェクトを右クリックし、「エクスポート」を選択します。

58.png

Web のフォルダを開き、 「WARファイル」を選択し、「次へ」を押します。

59.png

「参照」ボタンを押し、出力先フォルダ・ファイル名を指定したのち、「完了」を押します。
WAR ファイルが出力されます。

60.png


お疲れ様でした。
今回はここまでとし、次回は [7.WebSphere Liberty コンテナの準備] を実施します。

←:IKS + Db2 on IBM Cloud で DBアクセスアプリを動かしてみた(5.Db2 on IBM Cloud の準備)
↑:IKS + Db2 on IBM Cloud で DBアクセスアプリを動かしてみた(1.概要)
→:IKS + Db2 on IBM Cloud で DBアクセスアプリを動かしてみた(7.WebSphere Liberty コンテナの準備)


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java基礎】for文で三角形を作ろう

会社の先輩から新米のぼくに出された、Javaでfor文を使い色んな三角形を作る問題です。
こちらはJavaですが、単純なfor文で作れるので他の言語でも挑戦してみてください。

扱う形は以下の画像のとおり。

triangles.PNG

対象読者

・プログラミングを勉強中の方
・最近エンジニアになった方
・Javaを勉強したい方

問題① 正三角形 ▲

public class Main {
    public static void main(String[] args) {
        int max = 5;
        String star = "*";

        for (int i = 0; i < max; i++) {
            for(int j = 0; j < max-i; j++) {
                System.out.print(" ");
            }
            for(int k = 0; k <= (i*2); k++) {
                System.out.print(star);
            }
            System.out.println("\n");
       }
    }
}

出力結果

image.png

解説

・一つ目のfor文(i)で三角形の高さを定義する(今回は事前に変数max = 5としています。)
・2つ目のfor文(j)で空白("")を定義。
 三角形のため、最初は空欄が最大、*の数と反比例して徐々に空欄を少なくするので
 条件式はj < max-i;となります。
・3つ目のfor文(k)で*を出力。
・最後に1ループが終わる毎にprintlnで改行してあげる。(改行しないとずっと横に出力される)

問題② 逆三角形 ▼

public class Main {
    public static void main(String[] args) {
        int max = 5;
        String star = "*";

        for (int i = 0; i < max; i++) {
            for(int j = 0; j < i; j++) {
                System.out.print(" ");
            }
            for(int k = 0; k <= max*2-(i*2)-2; k++){
                System.out.print(star);
            }
            System.out.println("\n");
        }
    }
}

出力結果

image.png

解説

・一つ目のfor文(i)で三角形の高さを定義する
・2つ目のfor文(j)で空白("")を定義。
 ①とは異なり、今回は単純に1ずつ増やしてあげる。
・3つ目のfor文(k)で*を出力。
 ※ごり押しで解いてます・・・ もっといい手があれば教えてください・・orz

問題③ 直角三角形ver.1  ◣

public class Main {
    public static void main(String[] args) {
     int max = 5;
        String star = "*";

        for (int i = 0; i < max; i++) {
            for (int j = 0; j <= i; j++) {
                System.out.print(star);
            }
            System.out.println("\n");
        }
    }
}

出力結果

image.png

解説

恐らくこれが一番単純でしょう。

・一つ目のfor文(i)で三角形の高さを定義する
・2つ目のfor文(j)で一つずつ増えるよう*を出力。

問題④ 直角三角形ver.2 ◢

public class Main {
    public static void main(String[] args) {
        int max = 5;
        String star = "*";

        for (int i = 0; i < max; i++) {
            for (int j = 0; j < max-i; j++) {
                System.out.print(" ");
            }
            for (int k = 0; k <= i; k++){
                System.out.print(star);
            }
            System.out.println("\n");
        }
    }
}

出力結果

image.png

解説

これは②逆三角形ができれば簡単だとおもいます。

・一つ目のfor文(i)で三角形の高さを定義する
・2つ目のfor文(j)で空白("")を定義。
 最初は空欄が最大、*の数と反比例して徐々に空欄を少なくするので
 条件式はj < max-i;となります。
・3つ目のfor文(k)で*を出力。ひとつずつ増やすのでk <= iとなります。

問題⑤ ひし形 ◇

public class Main {
    public static void main(String[] args) {
        int max = 5;
        String star = "*";

        for (int i = 0; i < max; i++) {
            for (int j = 0; j < max-i; j++) {
                System.out.print(" ");
            }
            for (int k = 0; k <= ((i-1)*2); k++) {
                System.out.print(star);
            }
            System.out.println("\n");
            if (i == 4){
                for (int a = 0; a < max; a++) {
                    for(int b = 0; b < a; b++) {
                        System.out.print(" ");
                    }
                    for(int c = 0; c <= max*2-(a*2)-2; c++){
                        System.out.print(star);
                    }
                    System.out.println("\n");
                }
            }
        }
    }
}

出力結果

image.png

解説

これはしばらく解くのに考えました。結果としてはif文を使って、正三角形と逆三角形の両方を
出力するという手を使いましたが、恐らくもっといい方法がある気がします・・・

まずは、*が1~7個の間は正三角形の方法で出力し、*が9~1個にかけて逆三角形で表します。
if (i == 4)とし、iのループが4回回ったタイミングで▲と▼を切り替えましょう。

あとは、①~④のコードを再利用すれば解けます。

まとめ

for文を使って色んな形を出力する方法をまとめました。
for文は実務でも頻出ですし、コードの構造を考える練習になるのでぜひやってみてください (^^)/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

競プロ(AtCorder)の標準入力を楽にするためmakeを使う  C++/Java/PythonのサンプルMakefile掲載

はじめに

 最近、学生プログラマや現役プログラマの間で競技プログラミングが流行ってますね(体感)。
私はコンテストに参加はしませんが、新しい言語を覚えるときの試し打ちとして競プロの問題を解くことが多いです。なかなか面白いです。

 問題はたいてい、入力例が提示されていますが、それを標準入力するのって面倒ではありませんか?
できれば自動化したいです。自動化すればトライアンドエラーの効率が上がって得点アップが期待できますね!
自動化する方法はいろいろありますが、この記事ではmakeコマンドを用いるものを紹介します。
MakefileはサンプルとしてC++,Java,Python用のものを載せます。
コンパイル言語、Java系言語、スクリプト言語のサンプルを提示すれば、後は使用者が言語に合わせて書き換えてくれるだろう
という魂胆があります。

本記事のMakefileやディレクトリ構造をgithubに上げました。よかったら使ってください。
https://github.com/aoi-stoic/AtcorderTemplate

想定読者

  • makeコマンドを使ったことがある方
  • Linux/Macユーザ
  • 入力例ごとにいちいち標準入力している方→「自動でできたら楽だなあ」と考えている方

想定していない読者

  • Windowsユーザ(私がwindowsを使わないため)
  • Makefileを自分で書ける方
  • すでに別の方法で"標準入力"を自動化できている方

検証環境

OS

$ cat /etc/os-release | head -n 2
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"

ubuntu使ってます。他OS、ディストリビューションについては割愛するので適宜読み替えてください。

C++

gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)

Java

$ echo `javac -version` `java -version`
javac 1.8.0_265
openjdk version "1.8.0_265"
OpenJDK Runtime Environment (build 1.8.0_265-8u265-b01-0ubuntu2~20.04-b01)
OpenJDK 64-Bit Server VM (build 25.265-b01, mixed mode)

Javaはほとんど書いたことがないので適当です。Javaでコンテストに挑戦する人は、コンテストに合わせたバージョンを選んでください。

Python3

$ python3 --version
Python 3.8.2

仮想環境等使っていないもの

使い方

疑似コンテストABCXXXのA問題「4つの標準入力を受け取ってその和を標準出力せよ」を題材として、作業ディレクトリの構築やmakeコマンド実行例を提示します。
C++で解きます。

A問題の入力例と出力例は次の通り
入力例1
1 1
1 1
出力例1
4

入力例2
1 2
3 4
出力例2
10

入力例3
1000 100
10 1
出力例3
1111

0. 前準備

makeコマンドインストール

sudo apt isntall make

作業ディレクトリの構築

# 作業ディレクトリの作成&移動
mkdir ABCXXX && cd ABCXXX   
# テンプレートディレクトリのクローン(--depth=1で最小限の履歴だけクローン)
git clone --depth=1  https://github.com/aoi-stoic/AtcorderTemplate.git
#  A問題用にAtcorderTemplate/TemplateCppをコピー&移動
cp AtcorderTemplate/TemplateCpp/ problemA -r && cd problemA

この時点で、problemAディレクトリの中身はこうなってます
この構成を再現するのであればgitからcloneせずに手作業で構築し、Makefileだけこの記事からコピペしてもよいです。

$ tree 
.
├── main.cpp  # 解答用ソースファイル
├── Makefile  # C++用Makefile
└── Testfiles # テストファイルディレクトリ
    ├── test1.test  # 入力例1のテストファイル
    ├── test2.test
    ├── test3.test
    └── test4.test

1. 入力例のコピペ

入力例をそれぞれのテストファイルにコピペします。
結果として次のようになります。
ABCXXXのA問題の入力例は1~3しかないので、test4.testは空ファイルになっています。

$ cat Testfiles/test1.test
1 1
1 1
$ cat Testfiles/test2.test
1 2
3 4
$ cat Testfiles/test3.test
1000 100
10 1
$ cat Testfiles/test4.test

2. コーディング

いつもどおり、main.cppに解答コードを書きました。

// main.cpp
#include <iostream>

int main(){
    int a, b, c, d;
    std::cin >> a >> b >> c >> d;
    std::cout << a + b + c + d << std::endl;
}

3. makeコマンドの実行

problemAディレクトリでmakeコマンドを実行すると、
コンパイル後、入力例ごとに実行を自動で行ってくれます。
test4.testは空ですから、入力例4は実行されません。

$ make 
g++ -std=c++11 -Wall main.cpp -o main.out
----------------------
exec Testfiles/test1.test
input:
1 1
1 1

output:
4
----------------------
exec Testfiles/test2.test
input:
1 2
3 4

output:
10
----------------------
exec Testfiles/test3.test
input:
1000 100
10 1

output:
1111

4. 提出 or 修正

入力例と出力例の対応が正しいことを確認し、コードが正しいことに確信が持てたら提出しましょう。
次のB問題へ進む場合は、同様にTemplateCppディレクトリをコピーしてproblemBを作成し、そのディレクトリ内で作業しましょう。

makeターゲット

今回掲載したMakefileには以下のターゲットを用意しています
pythonはコンパイル作業がないため、buildやcleanといったターゲットを設けていません。

ターゲット コマンド 概要
(なし) make make testを実行
build make build コンパイルのみ実行
run make run 必要に応じコンパイルし、実行する。
標準入力は手作業で行う。
test make test 必要に応じコンパイルし、実行する。
テストファイルの中身を標準入力する。
clean make clean コンパイル後の生成物を削除する。
clean_test make clean_test テストファイルの中身を空にする。
A問題のディレクトリをコピーしてB問題の解答に再利用するときに使ったりする。

Makefile

C++用Makefile

COMP = g++ -std=c++11 -Wall
SRC = main.cpp
BIN = main.out
TESTDIR = Testfiles
# 暗黙ルールを無効にする, ディレクトリ移動時の出力を無効にする
MAKEFLAGS :=+ --no-builtin-rules --no-print-directory

# 引数(ターゲット)なし実行でtestをターゲットにする
all: test

$(BIN): $(SRC)
    $(COMP) $(SRC) -o $(BIN)

# make run は手作業で標準入力を行う実行
.phony:run
run: $(BIN)
    ./$(BIN)

# make test
# TESTDIR内のtestファイルの内容を標準入力する実行(空のファイルは無視する)
.phony:test
test: $(BIN)
    @for testfile in $(wildcard $(TESTDIR)/*.test) ; \
    do \
        if [ -s $$testfile ]; then \
            echo ---------------------- ;\
            echo "exec $$testfile " ;\
            echo "input: "; \
            cat $$testfile ; \
            echo "" ;\
            echo "output: " ;\
            cat $$testfile | ./$(BIN) ;\
        fi \
    done

.phony:clean
clean:
    -rm $(BIN)

# testファイルをすべて空にする
.phony:clean_test
clean_test:
    @for testfile in $(wildcard $(TESTDIR)/*.test) ; \
    do \
        : > $$testfile ;\
    done

Java用Makefile

Javaはほとんど書いたことがないです。

COMP = javac
EXEC = java
SRC = Main.java
CLASS = Main.class
# Main.class -> Main
APP = $(basename $(CLASS) .class)
TESTDIR = Testfiles
# 暗黙ルールを無効にする, ディレクトリ移動時の出力を無効にする
MAKEFLAGS :=+ --no-builtin-rules --no-print-directory
#
# 引数(ターゲット)なし実行でtestをターゲットにする
all: test

$(CLASS): $(SRC)
    $(COMP) $(SRC)

# make run は手作業で標準入力を行う実行
run: $(CLASS)
    $(EXEC) $(APP)

# make test
# TESTDIR内のtestファイルの内容を標準入力する実行(空のファイルは無視する)
test: $(CLASS)
    @for testfile in $(wildcard $(TESTDIR)/*.test) ; \
    do \
        if [ -s $$testfile ]; \
            then \
            echo ---------------------- ;\
            echo "exec $$testfile" ;\
            echo "input: " ; \
            cat $$testfile ; \
            echo "" ;\
            echo "output: " ;\
            cat $$testfile | $(EXEC) $(APP) ;\
        fi \
    done \

.phony:clean
clean:
    -rm $(CLASS)

# testファイルをすべて空にする
.phony:clean_test
clean_test:
    @for testfile in $(wildcard $(TESTDIR)/*.test) ; \
    do \
        : > $$testfile ;\
    done

Python用Makefile

Makefileのあり方的に、スクリプト言語のMakefileというのは違和感がありますが。

SRC = main.py
EXEC = python3
TESTDIR = Testfiles
# 暗黙ルールを無効にする, ディレクトリ移動時の出力を無効にする
MAKEFLAGS :=+ --no-builtin-rules --no-print-directory

# 引数(ターゲット)なし実行でtestをターゲットにする
all: test

# make run は手作業で標準入力を行う実行
.phony:run
run: 
    $(EXEC) $(SRC)

# make test
# TESTDIR内のtestファイルの内容を標準入力する実行(空のファイルは無視する)
.phony:test
test: 
    @for testfile in $(wildcard $(TESTDIR)/*.test) ; \
    do \
        if [ -s $$testfile ]; then \
            echo ---------------------- ;\
            echo "exec $$testfile " ;\
            echo "input: "; \
            cat $$testfile ; \
            echo "" ;\
            echo "output: " ;\
            cat $$testfile | $(EXEC) $(SRC) ;\
        fi \
    done

# testファイルをすべて空にする
.phony:clean_test
clean_test:
    @for testfile in $(wildcard $(TESTDIR)/*.test) ; \
    do \
        : > $$testfile ;\
    done

余談

パイプライン

パイプライン(|)を使ってコマンドの出力を次のコマンドの入力に橋渡しすることができます。

$ cat Testfiles/test1.test
1 1
1 1
$ cat Testfiles/test1.test | ./main.out  # catコマンドの出力を./main.out入力にしている
4

これを使えば、実行ファイルの標準入力をスクリプト化することができます。
make testはこの機能を活用しているわけです。

既存ファイルの中身を空にする方法

新規に空ファイルを作る場合は

touch empty_file

で十分なわけですが、この方法は既存ファイルには使えませんね。
下のように"削除してtouch"という手段は私の趣味じゃないです

rm empty_file & touch empty_file  # これは趣向に合わない

私がよく使うのは以下の2通りです。

# 何もしないコマンド(:) の結果をリダイレクトする
: > empty_file
# /dev/null をcpする
cp /dev/null empty_file

他にもいろいろ方法がありそうです。

この規模ならシェルスクリプトで十分じゃない?

そのとおりです。
しかしエディタに実行コマンドのショートカットを設定することを考えると、シェルスクリプトの実行よりも汎用性の高いmakeコマンドに対してショートカット設定するほうがベターだと考えています。

echoの出力に色をつけたら見やすいんじゃない?

そのとおりです。環境に合わせて色つけしてください。

ubuntu系ディストリビューションではシェルスクリプトの実行にbashではなくdashを用いているものがあります。
bashとdashでは一部コマンドの動作に違いがあります。
例えば、bashではechoの出力に色をつけるにはエスケープコードを用いて echo -e "\033[31m GREEN \033[m"としますが、dashでは-eのオプションが不要でオプションの解析が行われません。
今回は公開するにあたり、この違いが問題になることが予想されたので今回はecho出力に色をつけませんでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む