20200814のJavaに関する記事は17件です。

ゼロからVSCode+WSL+Java+Gradle環境を構築する

WindowsにJDKをインストールする

  • JDKがインストール済みかコマンドラインから確認する
C:\Users\ユーザ名> javac -version
javac 11.0.7
  • JDKがインストールされていない場合、インストーラーでインストールする

https://adoptopenjdk.net/
「OpenJDK 11 (LTS)」「HotSpot」
※インストール時に環境変数「JAVA_HOME」を設定するようにチェックを入れる

  • 環境変数「JAVA_HOME」が設定されているか確認する
C:\Users\ユーザ名>echo %JAVA_HOME%
C:\Program Files\AdoptOpenJDK\jdk-11.0.7.10-hotspot

空欄が出力された場合は、環境変数「JAVA_HOME」を設定する

WindowsにVSCodeインストール

VSCodeでJavaを扱えるように拡張機能をインストールする

拡張機能名 コード
Java Extension Pack vscjava.vscode-java-pack
Lombok Annotations Support for VS Code gabrielbb.vscode-lombok
Checkstyle for Java shengchen.vscode-checkstyle
SonarLint sonarsource.sonarlint-vscode
Gradle Language Support naco-siren.gradle-language
Markdown All in One yzhang.markdown-all-in-one
Remote - WSL ms-vscode-remote.remote-wsl

WindowsにWSLをインストール

WSL上でJavaを使えるようにする

WSL上でGradleを使えるようにする

VSCode経由のWSL上で開発するため、WSLにVSCodeの拡張機能をインストールする

  1. VSCodeの左下の「><」からWSLを開く run-wsl.png
  2. VSCode画面端のツールバーから拡張機能のアイコンを選ぶ
  3. もっている拡張機能すべてWSLにインストールする vscode-java.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaでsftp (JSchラッパ)

SftpService.java
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SftpService {
    private static final String SFTP_ERRMSG_NO_SUCH_FILE = "No such file"; // 環境依存かも?
    private Session session = null;
    private ChannelSftp channelSftp = null;

    public SftpService(String host, int port, String username, String password) throws Exception {
        log.info("接続します。(host={}, port={}, user={})", host, port, username);
        session = (new JSch()).getSession(username, host, port);
        session.setConfig("StrictHostKeyChecking", "no");
        session.setPassword(password);
        session.connect();
        channelSftp = (ChannelSftp) session.openChannel("sftp");
        channelSftp.connect();
    }

    public void cd(String path) throws Exception {
        log.info("{}ディレクトリに移動します。", path);
        channelSftp.cd(path);
    }

    public List<String> ls(String path) throws Exception {
        @SuppressWarnings({ "unchecked" })
        Vector<LsEntry> lsEntries = channelSftp.ls(path);
        List<String> filenames = new ArrayList<>();
        for (LsEntry lsEntry : lsEntries) {
            filenames.add(lsEntry.getFilename());
        }
        return filenames;
    }

    public InputStream get(String path) throws Exception {
        log.info("{}をgetします。", path);
        return channelSftp.get(path);
    }

    public void put(String localPath, String remotePath) throws Exception {
        log.info("{}を{}にputします。", localPath, remotePath);
        channelSftp.put(localPath, remotePath);
    }

    public void mkdir(String path) throws Exception {
        log.info("{}ディレクトリを作成します。", path);
        channelSftp.mkdir(path);
    }

    public void rmdir(String path) throws Exception {
        log.info("{}ディレクトリを削除します。", path);
        channelSftp.rmdir(path);
    }

    public void mkdirIfNotExists(String path) throws Exception {
        if (!isExists(path)) {
            mkdir(path);
        }
    }

    public void rmdirIfExists(String path) throws Exception {
        if (isExists(path)) {
            rmdir(path);
        }
    }

    public boolean isExists(String path) throws Exception {
        try {
            channelSftp.ls(path);
        } catch (SftpException e) {
            if (SFTP_ERRMSG_NO_SUCH_FILE.equals(e.getMessage())) {
                return false;
            } else {
                throw e;
            }
        }
        return true;
    }

    public void rename(String fromPath, String toPath) throws Exception {
        log.info("{}を{}にリネームします。", fromPath, toPath);
        channelSftp.rename(fromPath, toPath);
    }

    public void disconnect() {
        if (channelSftp != null || session != null) {
            log.info("切断します。");
        }
        if (channelSftp != null && channelSftp.isConnected()) {
            channelSftp.disconnect();
        }
        if (session != null && session.isConnected()) {
            session.disconnect();
        }
        channelSftp = null;
        session = null;
    }
}
XXX.java
        SftpService sftpService = null;
        try {
            sftpService = new SftpService(host, port, username, password);
            sftpService.cd(targetDir);
            List<String> filenames = sftpService.ls("*.csv");
            for (String filename : filenames) {
                try (InputStream inputStream = sftpService.get(filename)) {
                    // do something
                }
            }
        } finally {
            if (sftpService != null) {
                sftpService.disconnect();
            }
        }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

java 固定長文字列の中身の有無を調べる方法

        String sti = "     ";
        //スペースをtrimする
        //その結果、ブランクであるかどうか
        if(sti.trim().isEmpty()) {
            System.out.println("blank");
        }else {
            System.out.println("notBlank");
        }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JDBC ③】プレースホルダと引数を使ってmainメソッドから入力してみた。

前回の記事では、SQLのINSERT文・UPDATE文・DELETE文・SELECT文ごとにメソッドで分けて、mainメソッドでSQL文を実行することに成功しました。
追加や変更・削除したいデータをSQL文の中で固定していたため実用的ではありませんでした。

そこで、mainメソッドからSQL文の各メソッドを呼び出す際に追加したいデータ等も渡すことができれば、より有用性の高いソースコードになると考えたので挑戦してみました。

環境

今回の環境については以下のとおりです。

  • Ubuntu 18.04 LTS
  • OpenJDK 11.0.8
  • SQLite3 3.20.0

前回の振り返り

まずは前回のコードのINSERT文のメソッドを見てみます

前回のINSERT文のメソッド
/**
 * INSERT文
 */
public static void insertData() {
    try {
        // create a database connection
        connection = DriverManager.getConnection(URL);
        Statement statement = connection.createStatement();
        statement.setQueryTimeout(30);  // set timeout to 30 sec.

        statement.executeUpdate("INSERT INTO person VALUES(1, 'Satou')");
        statement.executeUpdate("INSERT INTO person VALUES(2, 'Tanaka')");
        statement.executeUpdate("INSERT INTO person VALUES(3, 'Suzuki')");
    } catch(SQLException e) {
        // if the error message is "out of memory", 
        // it probably means no database file is found
        System.err.println(e.getMessage());
    } finally {
        try {
            if(connection != null)
            connection.close();
        } catch(SQLException e) {
            // connection close failed.
            System.err.println(e);
        }
    }
}

ご覧の通り、SQL文の中で追加したいデータが固定で入っているため、mainメソッドから呼び出すだけでは任意のデータを追加できません。


任意のデータを渡すために必要なこと

mainメソッドから呼び出す際に追加したいデータを渡すためには、次の②つを満たす必要があリます。

  • プレースホルダを使う
  • Statementの代わりにPreparedStatementを使う

プレースホルダを使うことでSQL文に任意の文字列を渡すことができるようになります。またプレースホルダを使う際、予めSQL文を定義しておく必要があるのでStatementの代わりにPreparedStatementを使って以下の要領でデータを渡すことができるようになりました。

Statementを用いたコード
// ...

Statement statement = connection.createStatement();
statement.setQueryTimeout(30);  // set timeout to 30 sec.

statement.executeUpdate("INSERT INTO person VALUES(1, 'Satou')");
statement.executeUpdate("INSERT INTO person VALUES(2, 'Tanaka')");
statement.executeUpdate("INSERT INTO person VALUES(3, 'Suzuki')");

// ...

PreparedStatementを用いたコード
// ...

String sql = "INSERT INTO person (name) VALUES(?)";
try {
PreparedStatement ps = null;
ps = connection.prepareStatement(sql);
ps.setString(1, "Satou");
ps.executeUpdate();
connection.commit();
ps.close();
} catch (SQLException e){

// ...

注)この時点で、ソースコードを少しでもわかりやすくするためにidにPRIMARY KEY AUTOINCREMENTを付与しています。


メソッドの再利用性を高める

次に、mainメソッドからデータを渡すために、ps.setString(1, "Satou");のデータの部分を引数として渡すことで、mainメソッドからの操作でSQL文のデータ部分を任意に変えることができました。

ソースコードと、実行結果を以下に記しておきます。

今回のゴール
import java.sql.*;

/**
 * TestDataBaseAccess
 */
public class TestDataBaseAccess {
    static Connection connection;
    static PreparedStatement ps;
    static String URL = "jdbc:sqlite:sample.db";

    public static void main(String[] args) throws ClassNotFoundException {
       // load the sqlite-JDBC driver using the current class loader
        Class.forName("org.sqlite.JDBC");
        connection = null;
        ps = null;

        dropTable();
        createTable();
        insertData("Satou");
        insertData("Tanaka");
        insertData("Suzuki");
        loadData();

        System.out.println("---------");

        updateData(1, "Takahashi");
        loadData();

        System.out.println("---------");

        deleteData(3);
        loadData();
    }
    /**
     * SELECT文
     */
    public static void loadData() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            ResultSet rs = statement.executeQuery("SELECT * FROM person");
            while(rs.next()){
                // read the result set
                System.out.println("id = " + rs.getInt("id") + " | name = " + rs.getString("name"));
            }
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * INSERT文
     */
    public static void insertData(String name) {
        String sql = "INSERT INTO person (name) VALUES(?)";

        try {
            connection = DriverManager.getConnection(URL);
            connection.setAutoCommit(false);

            ps = connection.prepareStatement(sql);
            ps.setString(1, name);
            ps.executeUpdate();
            connection.commit();
            ps.close();
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * UPDATE文
     */
    public static void updateData(int id, String name) {
        try {
            String sql = "UPDATE person SET name = ? WHERE id = ?";
            // create a database connection
            connection = DriverManager.getConnection(URL);
            connection.setAutoCommit(false);

            ps = connection.prepareStatement(sql);
            ps.setString(1, name);
            ps.setInt(2, id);
            ps.executeUpdate();
            connection.commit();
            ps.close();
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * DELETE文
     */
    public static void deleteData(int id) {
        try {
            String sql = "DELETE FROM person WHERE id = ?";
            // create a database connection
            connection = DriverManager.getConnection(URL);
            connection.setAutoCommit(false);

            ps = connection.prepareStatement(sql);
            ps.setInt(1, id);
            ps.executeUpdate();
            connection.commit();
            ps.close();
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * テーブル作成
     */
    public static void createTable() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            statement.executeUpdate("CREATE TABLE person (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING)");
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }

    /**
     * テーブル削除
     */
    public static void dropTable() {
        try {
            // create a database connection
            connection = DriverManager.getConnection(URL);
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);  // set timeout to 30 sec.

            statement.executeUpdate("DROP TABLE IF EXISTS person");
        } catch(SQLException e) {
            // if the error message is "out of memory", 
            // it probably means no database file is found
            System.err.println(e.getMessage());
        } finally {
            try {
                if(connection != null)
                connection.close();
            } catch(SQLException e) {
                // connection close failed.
                System.err.println(e);
            }
        }
    }
}
javac TestDataBaseAccess.java && java -cp .:sqlite-jdbc-3.30.1.jar TestDataBaseAccess 
id = 1 | name = Satou
id = 2 | name = Tanaka
id = 3 | name = Suzuki
---------
id = 1 | name = Takahashi
id = 2 | name = Tanaka
id = 3 | name = Suzuki
---------
id = 1 | name = Takahashi
id = 2 | name = Tanaka

まとめ

今回の変更で再利用性が高まっていいコードに近づいてきたのではないでしょうか。
今後は実際にソフトウェアで使ってみて、より良いソースコードにしていきたいと思います。


本記事目次ページ

【データベース】SQLite3・JDBCまとめ


参考サイト

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

大量データを扱うとSQLRecoverableExceptionに苛まれる

数万単位のデータをデータベースから参照、更新を行うと、まれに SQLRecoverableException に遭遇します。
こいつほんまなんやねん...って感じて、再現の仕方もさっぱりだったのですが、つい先日再現もできたので調査内容をシェアしてみます。

実行環境

項目 バージョン
Java amazon Corretto-11.0.3.7.1
Spring Batch 4.2.2.RELEASE
データベース Oracle Database(ドライバはojdbc8)
mybatis 3.3.0
mybatis-spring 1.2.3

アプリは、定期的にデータベースを参照・更新するバッチ処理です。
サーバ上でマルチスレッドで動作し、1スレッド上で数千件ずつ処理が走ってます。

状況

Javaから、ORMに関わらず以下の例外に遭遇します。

 Caused by: java.sql.SQLRecoverableException: クローズされた接続です。
            at oracle.jdbc.driver.PhysicalConnection.getAutoCommit(PhysicalConnection.java:1943)
            at oracle.jdbc.driver.PhysicalConnection.rollback(PhysicalConnection.java:2068)
            at org.apache.commons.dbcp2.DelegatingConnection.rollback(DelegatingConnection.java:492)
            at org.apache.commons.dbcp2.DelegatingConnection.rollback(DelegatingConnection.java:492)
            at org.springframework.jdbc.datasource.DataSourceTransactionManager.doRollback(DataSourceTransactionManager.java:328)
            at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:835)
            at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:809)
            at org.springframework.transaction.support.TransactionTemplate.rollbackOnException(TransactionTemplate.java:168)
            at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:144)
            at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:273)
            at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:82)
            at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:375)
            at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215)
            at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:145)
            at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:258)
            at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:208)
            at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148)
            at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:410)
            at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:136)
            at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:319)
            at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:147)
            at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
            at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:140)
            at org.springframework.batch.core.launch.support.CommandLineJobRunner.start(CommandLineJobRunner.java:376)
            at org.springframework.batch.core.launch.support.CommandLineJobRunner.main(CommandLineJobRunner.java:609)

突如接続がなくなり、データの操作に支障をきたしてました。
このアプリは、JDBCを自分で管理するのではなく、Mybatisから操作するようにしています。

ドキュメントによると SQLRecoverableException とは

アプリケーションが回復手順を実行してトランザクション全体 (分散トランザクションの場合はトランザクションブランチ) を再試行すれば前回失敗した操作が成功する可能性があるときにスローされる SQLException のサブクラスです。

とのこと。曖昧な...
再現もよくわからなくて、 SQLRecoverableException をキャッチしてリトライか...?と考えてましたが...

ある日突然再現できた

ドキュメントを読んでも曖昧だし、単体テストでは全然出てこないしですごい困ってたのですが...
全然関係ないプログラムを作ってたら、解決はできてないものの再現できました。

再現ソース

3種類のクラスを用意します。以下、超プライベートなサンプルなのでプログラムの厳密性は考慮してないです。

  • DBリソースを操作するクラス
  • リポジトリ
  • 呼び出しを行うメインクラス

まずは、コネクションやステートメントを提供するDBリソース関連クラス。

DatabaseResourceManager.java
public final class DatabaseResourceManager {

    private DatabaseResourceManager() { }

    private static final String uri = "jdbc:oracle:thin:@localhost:1521/XEPDB1";
    private static final String user = "user";
    private static final String password = "password";

    // コネクションを生成する
    public static Connection openConnection() throws SQLException {
        DriverManager.registerDriver(new OracleDriver()); // <== ドライバの指定、なんか間違ってるような...
        return DriverManager.getConnection(uri, user, password);
    }

    // ステートメントを生成する
    public static Statement createStatement(Connection connection) throws SQLException {
        if (connection.isClosed()) {
            throw new RuntimeException("閉じられたコネクションです");
        }

        return connection.createStatement();
    }

    // ResultSetをクローズする
    public static void closeResultSetConnection(ResultSet resultSet) throws SQLException {
        if (resultSet.isClosed()) {
            throw new SQLException("閉じられた結果セットです");
        }

        resultSet.close();
    }

次に、コネクションやステートメントを受け取ってSQLを発行する簡易リポジトリクラスを用意します。

Repository.java
public final class Repository {

    private Repository() { }

    // にシンプルなSELECT分を発行したい
    public static ResultSet selectFromTable() throws SQLException {
        try (
            final Connection connection = DatabaseResourceManager.openConnection();
            final Statement statement = DatabaseResourceManager.createStatement(connection)
        ) {
            return statement.executeQuery("select * from table");
        }
    }

    // ResultSetの中身を表示するだけ
    public static void printTableRow(ResultSet resultSet) throws SQLException {
        try {
            while (resultSet.next()) {
                System.out.println(new ObjectMapper().writeValueAsString(resultSet.getCursorName()));
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

最後に、リソースを取得してSQLの発行、操作を司るメインクラスを用意。

MainHandler.java
public class MainHandler {
    public static void main(String[] args) {
        try {
            final ResultSet set = Repository.selectFromExclusionMail();

            Repository.printTableRow(set);

            DatabaseResourceManager.closeResultSetConnection(set);
        } catch (SQLException throwables) {
            DatabaseResourceManager.handleSQLException(throwables);
        }
    }
}

で実行していく。すると...

java.sql.SQLRecoverableException: クローズされた接続です。: next
    at oracle.jdbc.driver.InsensitiveScrollableResultSet.ensureOpen(InsensitiveScrollableResultSet.java:108)
    at oracle.jdbc.driver.InsensitiveScrollableResultSet.next(InsensitiveScrollableResultSet.java:402)
    at Repository.printTableRow(Repository.java:40)
    at MainHandler.main(MainHandler.java:36)
8:53:19: Task execution finished 'MainHandler.main()'.

おおおお...本番でたまに出てきてわけわからんあいつでたーーーー
たしかに、whileでカーソルをくるくる回す部分で接続がクローズされている。

原因

再現方法がわかっただけで、原因はわからないです...
が、結果から言えることとしてResultSetを取得して操作する前にリソースをcloseしてはならないらしい。

解決?

サンプルの、生JDBCではあっさり解決できました。リポジトリクラスのSQLを発行する部分で、 try-with-resource をやめてクローズするタイミングを手動にします。つまり、

Repository.java
public static ResultSet selectFromTable() throws SQLException {
    try (
        final Connection connection = DatabaseResourceManager.openConnection();
            final Statement statement = DatabaseResourceManager.createStatement(connection)
    ) {
        return statement.executeQuery("select * from table");
    }
}

を、

Repository.java
public static ResultSet selectFromTable() throws SQLException {
    // ステートメントを生成
    final Statement statement = DatabaseResourceManager.createStatement(DatabaseResourceManager.openConnection())

    // SQLを発行
    return statement.executeQuery("select * from table");
}

に。これでとりあえずサンプルは正常終了できました。

本題のバッチは、Mybatisが提供している SqlSession というセッションを管理するクラスのインスタンスをSQL実行前に取得し、実行後にクローズするように修正してみました。
これでしばらく様子を見てみようかと。
Mybatisのドキュメント、 SqlSessionFactory からセッション開いてSQL発行する部分のサンプルが少ないのが辛いですね...

参考

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

ヒープソートの実装(javaで)

ディレクトリ構造

heap/
 ├ Heap.java
 └ Main.java

実行
$ javac Heap.java 
$ javac Main.java 
$ java Main
ソースコード
Heap.java
import java.util.*;
/**
 * ヒープソート用のクラス
 */
public class Heap {
    int[] origArray;
    int[] origHeap;
    int[] forSortHeap;
    int[] sortedHeap;
    int arrayLength;
    /**
     * ヒープソート用の配列を取得する.
     */
    public void getForSortHeap(){
        for (int i: forSortHeap){
            System.out.print(i+", ");
        }
        System.out.println();
    }
    /**
     * ヒープソートされた配列を取得する
     */
    public void getSortedHeap(){
        for (int i: sortedHeap){
            System.out.print(i+", ");
        }
        System.out.println();
    }
    /**
     * コンストラクタ.配列データをヒープ配列に格納する
     * @param array ソート前の初期配列
     */
    public Heap(int[] array){
        arrayLength = array.length;
        this.origArray = new int[array.length];
        this.origArray = array;
        this.origHeap = new int[array.length];
        this.forSortHeap = new int[array.length];
        this.sortedHeap = new int[array.length];
        for (int i = 0; i < array.length; i++){
            insert(i);
        }
    }
    /**
     * idxN番目の要素をヒープ配列に格納する
     * @param idxN
     */
    public void insert(int idxN){
        int idxParent = idxN;
        int nextIdxParent = 0;
        int tmp = 0;
        this.forSortHeap[idxN] = this.origArray[idxN];
        while (idxParent >= 0){
            nextIdxParent = idxParent / 2;
            if (forSortHeap[nextIdxParent] < forSortHeap[idxParent]){
                swap(forSortHeap, nextIdxParent, idxParent);
            } else {
                break;
            }
            idxParent = nextIdxParent;
        }
    }
    /**
     * ヒープソートを実行(ヒープの最大値をヒープ配列から削除して,順に配列に追加する)
     */
    public void sort(){
        for (int i = 0; i < arrayLength; i++){
            sortedHeap[i] = getMax();
            removeMax(i);
        }
    }
    /**
     * ヒープ内の最大値をpopし,ヒープ用の新しい配列に再格納
     * @param idxNmax forSortHeapのidx (n-th max value in array will be deleted. )
     */
    public void removeMax(int idxNmax){ // n-th max value in array will be deleted.
        int idxChild = 0;
        forSortHeap[0] = forSortHeap[arrayLength - idxNmax - 1];
        forSortHeap[arrayLength - idxNmax - 1] = 0;
        while (2 * idxChild + 2 < arrayLength){
            if ((forSortHeap[idxChild] > forSortHeap[2 * idxChild + 1]) && (forSortHeap[idxChild] > forSortHeap[2 * idxChild + 2])){
                break;
            }
            if (forSortHeap[2 * idxChild + 1] >= forSortHeap[2 * idxChild + 2]){
                swap(forSortHeap, 2 * idxChild + 1, idxChild);
                idxChild = 2 * idxChild + 1;
            } else {
                swap(forSortHeap, 2 * idxChild + 2, idxChild);
                idxChild = 2 * idxChild + 2;
            }
        }
    }
    /**
     * ヒープの最大値を取得
     * @return 最大値
     */
    public int getMax(){
        return forSortHeap[0];
    }
    /**
     * 配列の要素の入れ替えを行う
     * @param arr swap対象の配列
     * @param a index 1つ目
     * @param b index 2つ目
     */
    public void swap(int[] arr, int a, int b){
        int tmp = 0;
        tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;
    }
}

実行用クラス

Main.java
import java.util.*;
public class Main {
    public static void main(String[] args){
        int[] a = {3,9,1,6,10,13,2,18,4,7,20};
        Heap heap = new Heap(a);
        System.out.println("getForSortHeap>>> ");
        heap.getForSortHeap();
        heap.sort();
        System.out.println("getSortedHeap>>> ");
        heap.getSortedHeap();
    }
}
標準出力
getForSortHeap>>> 
20, 18, 13, 10, 7, 9, 2, 3, 1, 4, 6, 
getSortedHeap>>> 
20, 18, 13, 10, 9, 7, 6, 4, 3, 2, 1, 
参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java SE, EE, ME, SDK, JDK, JRE, JVM ややこしい用語を図で解説

Java SE、Java EE、 Java MEはプラットフォームの種類

001.jpeg

Java SE、Java EE、 Java MEはJavaのプラットフォームの種類。
プラットフォームとは、Javaのプログラムを作成するための作成キットのことである。
プラットフォームにはAPIが含まれており、SE、EE、MEはそれぞれ含まれているAPIの種類が異なる。

SDK、JDK、 JRE、 JVMはJavaの開発や実行に使うソフトウェア

SDK、JDK、 JRE、 JVMはJavaの開発や実行に使うソフトウェアの名称。
それぞれ図のような関係にある

002.jpeg

JDKとSDKはプログラムを開発するキット

まず、プログラムを開発するのに必要なものが一通り含まれた開発キットのことをSDK (Software Development Kit)と言う。

前述のプラットフォームは作成キットなのでプログラムを作成することしかできないが、SDKはプログラム作成する際に便利な機能が使えたり、作成したプログラムの実行、デバッグなどができる。

その中でも、Javaプログラムを開発するための開発キットをJDK(Java Development Kit)と言う。つまり、JDKはJavaのSDKとも言える。

JREはJavaプログラムを実行するためのソフトウェア

JDKの中にはJavaプログラムを実行するためのソフトウェアが含まれており、そのソフトウェアをJRE(Java Runtime Environment)と言う。

JVMはOSの違いに対応するための仮想マシン

JREの中には、どのOS上でもJavaプログラムが実行できるようにするための仮想マシンが含まれている。その仮想マシンをJVM(Java virtual machine)と言う。

まとめ

  • Javaのプログラムを作成するキットをプラットフォームと言う
  • Java SE、Java EE、 Java MEはJavaプラットフォームの種類で、含まれるAPIが異なる
  • プログラム開発キットをSDKと言う
  • JavaのSDKがJDK
  • JREはJavaプログラムを実行するためのソフトウェアでJDKに含まれている
  • JVMはどのOS上でもJavaプログラムが実行できるようにするための仮想マシンでJREに含まれている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java カプセル化とは?

カプセル化とは?

クラスのフィールドやメソッドへのアクセスを制限する機能のこと。

フィールドやメソッドが想定しない用途で利用されるのを防ぐ役割がある。

//例:フィールドに文字列のname、整数のmoneyを持つUserクラス
public class User {
    String name;
    int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

Userクラスのインスタンスはname、moneyが自由に変えられてしまう。

public class Main {
    public static void main(String[] args) {
        User user = new User("鈴木太郎", 100);
        System.out.println(user.name + "の所持金は" + user.money + "円");
//空白の名前にされてしまうことや
        user.name = "";
//金額がマイナスにされてしまう可能性がある
        user.money = -1000;
        System.out.println(user.name + "の所持金は" + user.money + "円");
    }
}

実行結果

鈴木太郎の所持金は100円
の所持金は-1000円

カプセル化の方法

カプセル化するには他のクラスから変更されたくないフィールドやメソッドのアクセス修飾子をprivate にする。

こうすることで他のクラスから変更されるのを防ぐことができる。

フィールドはprivate、メソッドはpublicにするのが定石。

//例:Userクラスをカプセル化
public class User {
    private String name;
    private int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

privateフィールドへのアクセス方法

フィールドをprivateにすると、他のクラスからはフィールドの値を取得、変更することができなくなる。

public class User {
//このままだとインスタンスのname, moneyが変更できない
    private String name;
    private int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

このようなときはフィールドの値を取得、変更するメソッドをクラスに定義することでクラス外から安全にフィールドの値を取得、変更することができる。

フィールドの値を取得するメソッドをゲッター、変更するメソッドをセッターと呼ぶ。

ゲッターの定義方法

ゲッターのメソッド名は「getフィールド名」のようにするのが一般的。

返り値としてフィールドの値を返す。

//例:Userクラスにname、moneyのゲッターを追加
public class User {
    private String name;
    private int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }

//nameのゲッター
    public String getName() {
        return this.name;
    }

//moneyのゲッター
    public int getMoney() {
        return this.money;
    }
}
//ゲッターを使ってnameとmoneyを取得
public class Main {
    public static void main(String[] args) {
        User user = new User("鈴木太郎", 100);
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
    }
}

実行結果

鈴木太郎の所持金は100円

セッターの定義方法

セッターのメソッド名は「setフィールド名」のようにするのが一般的。

引数として受け取った値をフィールドの値に代入する。

//例:Userクラスにname、moneyのセッターを追加
public class User {
    private String name;
    private int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }

//nameのゲッター
    public String getName() {
        return this.name;
    }

//nameのセッター
    public void setName(String name) {
        this.name = name;
    }

//moneyのゲッター
    public int getMoney() {
        return this.money;
    }

//moneyのセッター
    public void setMoney(int money) {
        this.money = money;
    }
}
public class Main {
    public static void main(String[] args) {
        User user = new User("鈴木太郎", 100);
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
        user.setName("佐藤太郎");
        user.setMoney(500);
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
    }
}

実行結果

鈴木太郎の所持金は100円
佐藤太郎の所持金は500円

ゲッター、セッターを使うメリット

ゲッターとセッターを使うとprivateにしたフィールドも取得、変更できるのでpublicと変わらないのでは?と思うかもしれないが、ゲッター、セッターを使うと以下のようなメリットがある。

1. フィールドを読み取り専用、書き込み専用にすることができる

フィールドに対してゲッターのみ定義し、セッターを定義しなければそのフィールドは読み取り専用になり、他のクラスから変更されるのを防ぎつつ、フィールドの値を使用できる。

また、フィールドに対してセッターのみ定義し、ゲッターを定義しなければそのフィールドは書き込み専用になる。(このような使い方はあまりない)

//例:Userクラスのmoneyのセッターを無効にしてmoneyを読み取り専用にする
public class User {
    private String name;
    private int money;

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }

//nameのゲッター
    public String getName() {
        return this.name;
    }

//nameのセッター
    public void setName(String name) {
        this.name = name;
    }

//moneyのゲッター
    public int getMoney() {
        return this.money;
    }

//moneyのセッターが無ければ
//他のクラスからmoneyが変更されるのを防げる
//
//  public void setMoney(int money) {
//      this.money = money;
//  }
}

2. フィールドの名称を変更しても他のクラスに影響しない

フィールドの名称を後から変更する際、ゲッター、セッターを使用していない場合はフィールドを使用しているすべてのクラスのコードを修正する必要がある。

しかし、ゲッター、セッターを使用している場合、ゲッター、セッターのメソッド内のコードを修正するだけで済む。

//例:UserクラスのnameをuserNameに変更してもメインメソッドは変更不要
public class Main {
    public static void main(String[] args) {
        User user = new User("鈴木太郎", 100);
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
        user.setName("佐藤太郎");
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
    }
}
public class User {
    private String userName; //name -> userNameに変更
    private int money;

    public User(String userName, int money) {
        this.userName = userName;
        this.money = money;
    }

//userNameのゲッター
    public String getName() {
        return this.userName;
    }

//userNameのセッター
    public void setName(String userName) {
        this.userName = userName;
    }
//メインメソッドにuserNameという変数が登場しないのでコード変更不要
public class Main {
    public static void main(String[] args) {
        User user = new User("鈴木太郎", 100);
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
        user.setName("佐藤太郎");
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
    }
}

実行結果

鈴木太郎の所持金は100円
佐藤太郎の所持金は100円

3. フィールドの値を変更する前に値を検査できる

セッターのメソッド内に、引数の値を検査するコードを追加することでフィールドの値が想定しない値に変更されるのを防ぐことができる。

例:UserクラスのuserNameが変更される前に適切な文字数か検査

//userNameのセッター
//引数の文字列が3文字以上10文字以内ならuserNameを変更
    public void setName(String userName) {
        if (userName.length() < 3) {
            System.out.println("名前が短過ぎます");
        } else if (userName.length() > 11) {
            System.out.println("名前が長過ぎます");
        } else {
            this.userName = userName;
            System.out.println("名前を「" + this.userName + "」に変更しました");
        }
    }
public class Main {
    public static void main(String[] args) {
        User user = new User("鈴木太郎", 100);
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
        user.setName("鈴木");
        user.setName("俺の名前は鈴木太郎!!");
        user.setName("佐藤太郎");
        System.out.println(user.getName() + "の所持金は" + user.getMoney() + "円");
    }
}

実行結果

鈴木太郎の所持金は100円
名前が短過ぎます
名前が長過ぎます
名前を「佐藤太郎」に変更しました
佐藤太郎の所持金は100円

まとめ

  • カプセル化とはクラスのフィールドやインスタンスの使用を制限する機能
  • 外部のクラスから想定外のフィールドの書き込みやメソッドの使用を防ぐことができる
  • フィールドはprivate、メソッドはpublicにするのが定石
  • フィールドをprivateにした際はゲッター、セッターを使用してフィールドの値を扱う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 配列の作り方

配列を作る3ステップ

配列を作るには3つのステップが必要。

  1. 配列の変数を宣言
  2. 配列の要素を作成
  3. 値を代入

イメージとしては

  1. 大枠の箱(配列の変数)を作る
  2. 箱の中に仕切り(配列の要素)を作る
  3. 仕切りの中にモノ(値)を入れる

といったところ。

1. 配列の変数を宣言

配列の変数を宣言するには以下のように記述する。

配列要素のデータ型[] 配列の変数名;

配列要素のデータ型は、配列の中に代入する要素のデータ型。
整数なら int[]、少数なら double[]、文字列ならString[]のようになる。

配列の変数名は、配列の中身を表す英語の複数形にするのが基本。
配列の中身がテストの点数の数字なら scores、色の名前の文字列なら colorsなど。

例:整数の配列 numbers を定義する

//整数なのでデータ型は int、データ型の後ろに[]をつけて定義する
int[] numbers;

2. 要素を作成

要素を代入するには以下のように記述する。

配列の変数名 = new 要素のデータ型[要素の数];

先程宣言した変数に対して、配列の要素を作成する。

要素のデータ型は変数を宣言したときのデータ型と同じもの。
要素の数は配列に入れたい値の数。

例:先程定義した配列 numbersに3つの要素を代入

// numbersは整数の配列なのでデータ型はint
// 要素は3つなので要素の数は[3]
numbers = new int[3];

3. 値を代入

配列に値を代入するには以下のように記述する。

配列の変数名[index番号] = 代入したい値;

例:numbersに整数10, 20, 30を代入する

// index番号は0から始まるので注意
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

尚、配列の要素を作成した後、値が入っていない場合はコンパイルした際にデータ型に応じて自動で値が代入される。(int なら0、String ならnull など)

配列作成と値の代入をまとめて記述する

上記の3ステップを踏んで配列を作成すると以下のようになる。

例:整数の配列 numbersを作り、整数10, 20, 30を代入

// 変数を宣言
int[] numbers;

// 要素を作成
numbers = new int[3];

// 値を代入
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

しかし、これらは以下のようにまとめて記述することも可能。

配列要素のデータ型[] 配列の変数名 = {値1, 値2, 値3, ...};
//例:整数の配列 numbersを作り、整数10, 20, 30を代入
int[] numbers = {10, 20, 30};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java よく使う命令一覧(初心者、入門者向け)

よく使う命令一覧

コメントアウト

// 1行ならスラッシュ2つ

/* 複数行なら
  スラッシュと
  アスタリスクで囲む
*/

キーボードから1行の文字列を受け取る

// 入力した文字列を変数wordsに代入
String words = new java.util.Scanner(System.in).nextLine();

キーボードから1つの整数を受け取る

// 入力した整数を変数valueに代入
int value = new java.util.Scanner(System.in).nextInt();

文字列を表示(改行あり)

System.out.println("表示する文字列");

文字列を表示(改行なし)

System.out.print("表示する文字列");

文字列を整数に変換

int n = Integer.parseInt(stringNumber);

例:文字列の1, 2を整数に変換して足し算する

String s_1 = "1";
String s_2 = "2";

//文字列のまま足す -> 12が出力される
System.out.println(s_1 + s_2);

//整数に変換
int i_1 = Integer.parseInt(s_1);
int i_2 = Integer.parseInt(s_2);

//整数に変換してから足す -> 3が出力される
System.out.println(i_1 + i_2);
<実行結果>
12
3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 文字列をコンソール画面に出力する方法

文字列をコンソール画面に表示する命令

Javaで文字列をコンソール画面に表示する命令は2種類ある

//命令1
System.out.print("Hello World!");

//命令2
System.out.println("Hello World!");

命令1 System.out.print();

System.out.print(); を使用するとコンソールに表示した文字列の後に改行が入らない

//例:System.out.print(); で Hello World! を2つ表示する
System.out.print("Hello World!");
System.out.print("Hello World!");

実行結果は以下のようになる。

Hello World!Hello World!

命令2 System.out.println();

System.out.println(); を使用するとコンソールに表示した文字列の後に改行が入る

//例:System.out.println(); で Hello World! を2つ表示する
System.out.println("Hello World!");
System.out.println("Hello World!");

実行結果は以下のようになる。

Hello World!
Hello World!

まとめ

  • Javaで文字列をコンソールに表示する命令はSystem.out.print(); と System.out.println();の2種類ある
  • 2つの違いは文字列の後に改行が入るか入らないか
  • System.out.print(); は改行が入らない
  • System.out.println(); は改行が入る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 変数宣言の書き方

変数宣言の書き方

Javaで変数宣言をするときは以下のように記述する

データ型 変数名;

//例:整数型の変数 age と文字列の変数 name を宣言

int age; //整数型の変数 age
String name; //文字列の変数 name

変数名の付け方

変数名を付ける際には以下のような注意点がある

  • 予約語(Javaの構文で使用する単語 if や void など)を使用しない
  • すでに利用している変数名は使わない
  • 大文字、小文字は区別される(例:name と Name は別の変数とみなされる)
  • キャメルケースを利用することが望ましい(例:userName)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

httpcore-4.4.5 で削除されたannotation ThreadSafe/NotThreadSafeの置換

httpcore-4.4.4 では使用可能だが httpcore-4.4.5 から削除されたannotation

@ThreadSafe, @NotThreadSafe に関する置換メモ。

Release Note:
https://archive.apache.org/dist/httpcomponents/httpcore/RELEASE_NOTES-4.4.x.txt

httpcore ThreadSafe class not found solution:
https://dev-aux.com/java/org-apache-http-annotation-threadsafe-class-not-found

置換

@ThreadSafe class not found compilation error occurs after updating your org.apache.httpcomponents:httpcore dependency version to 4.4.11 or above.
@NotThreadSafe

org.apache.http.annotation.Immutable
org.apache.http.annotation.NotThreadSafe
org.apache.http.annotation.ThreadSafe

削除されたこれらの代わりに、org.apache.http.annotation.Contractorg.apache.http.annotation.ThreadingBehavior enum が導入された。

よって @ThreadSafeorg.apache.http.annotation.Contract に置換し、ThreadingBehavior enumの値SAFEを渡せば、スレッドセーフな動作規約を指定できる。

@Contract(threading = org.apache.http.annotation.ThreadingBehavior.SAFE)

等々。

同じく @NotThreadSafe@Contract(threading = org.apache.http.annotation.ThreadingBehavior.UNSAFE)

@ImmutableThreadingBehavior.IMMUTABLE あるいは IMMUTABLE_CONDITIONAL

以下も参考:
https://hc.apache.org/httpcomponents-core-ga/httpcore/clirr-report.html

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

Jenkinsを使って、Docker内でビルドしてからDocker imageを作成する。

はじめに

Jenkinsを使って、Mavenコンテナ内でビルドしていました。
どうせならDocker imageまでも一気通貫でやりたくて作成。
思っていたよりも作成に時間がかかった。

リポジトリ構成

src
Dockerfile
Jenkinsfile
pom.xml

gitリポジトリにソースと上記のファイルを格納しています。

Dockerfile

FROM tomcat:jdk14-openjdk

COPY target/*.war /usr/local/tomcat/webapps/ 

ビルドでできたwarファイルをwebapps配下に格納しています。
コードと同じリポジトリで管理します。

Jenkinsfile

pipeline {
    agent any
    stages {
        stage('delete_workspace') {
            steps {
                 deleteDir()
            }
        }
        stage('build') {
            agent {
                docker {
                    label 'master'
                    image 'maven:3.6.3-openjdk-14'
                }
            }
            steps {
                // Run Maven on a Unix agent.
                sh "mvn clean package"
            }
            post {
                success { 
                archiveArtifacts 'target/*.war'          
                }
            }
        }
        stage('docker build') {
            agent { label 'master'}
            steps {
                sh 'docker build -t test-tomcat:0.1 .'
            }
            ////   事後に削除する場合。
            //     post {
            //     always {
            //         deleteDir()
            //     }
            // }
        }
    }
}

Declarative Pipelineで作成。

stage

エージェント指定

pipeline {
    agent any
    stages {

agent any がないとジョブが失敗したので記載している。

delete_workspace

stage('delete_workspace') {
    steps {
         deleteDir()
    }
}

事前にworkspaceを削除、事後に必ずやるパターンもあるがジョブにエラーが出たとき、削除されると個人的に切り分けが困るので、ジョブの事前にやる派です。

build

agent {
    docker {
        label 'master'
        image 'maven:3.6.3-openjdk-14'
    }
}

マスターノードにてでmavenコンテナを起動しています。
地味にノードの指定がこの位置だと気づくのに時間がかりました。

steps {
         // Run Maven on a Unix agent.
         sh "mvn clean package"
    }
    post {
        success { 
        archiveArtifacts 'target/*.war'

        }
    }

maven ビルドして、成果物を保存しています。

docker build

    stage('docker build') {
        agent { label 'master'}
        steps {
            sh 'docker build -t test-tomcat:0.1 .'
        }
        ////   事後に削除する場合。
        //     post {
        //     always {
        //         deleteDir()
        //     }
        // }
    }

マスターでdockerを起動したので、agentをマスターに指定。
ホストのworkspace配下にmavenコンテナでビルドした成果物とDockerfileが格納されているので、
そのままdocker build を実行。

まとめ

コンテナないでビルドしてもホストのworkspaceにビルド結果が残るのを知らなかったので、
すっごく作成に時間がかかった。
また、jenkinsでdockerを利用して色々やるにはScripted Pipelineで書かなければいけないことがわかった。
jenkinsに限っては、pipelineで頑張るより、Ansibleやdocker-composeのコマンドを呼び出すほうがシンプルだし、
保守性も高いのではと感じた。実際はどっちなのだろうか。

参考文献

https://www.jenkins.io/doc/book/pipeline/docker/

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

Ubuntu 20.04 で Bio-Formats の使い方

Bio-Formatsの使い方です。

1) コマンドラインツールのダウンロード

wget https://downloads.openmicroscopy.org/bio-formats/6.5.1/artifacts/bftools.zip

解凍して、例えば、$HOME/bin/bftools とする

Path を通す

.bash_profile
(省略)
export PATH=$HOME/bin/bftools:$PATH

2) ij.jar のダウンロード

wget http://wsr.imagej.net/distros/linux/ij153-linux64-java8.zip

解凍して、ij.jar を $HOME/bin/bftools に入れる

3) サンプルデータのダウンロード

wget https://downloads.openmicroscopy.org/images/OME-TIFF/2016-06/tubhiswt-2D.zip

解凍すると次の2つのファイルがある

$ tree tubhiswt-2D
tubhiswt-2D
├── tubhiswt_C0.ome.tif
└── tubhiswt_C1.ome.tif

4) 画像の表示

showinf tubhiswt_C0.ome.tif

show_inf_aug14.png

5) ImageJ で画像の表示

ijview tubhiswt_C0.ome.tif

ijview_aug14.png

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

Bio-Formats の使い方

Bio-Formatsの使い方です。

1) コマンドラインツールのダウンロード

wget https://downloads.openmicroscopy.org/bio-formats/6.5.1/artifacts/bftools.zip

解凍して、例えば、$HOME/bin/bftools とする

Path を通す

.bash_profile
(省略)
export PATH=$HOME/bin/bftools:$PATH

2) ij.jar のダウンロード

wget http://wsr.imagej.net/distros/linux/ij153-linux64-java8.zip

解凍して、ij.jar を $HOME/bin/bftools に入れる

3) サンプルデータのダウンロード

wget https://downloads.openmicroscopy.org/images/OME-TIFF/2016-06/tubhiswt-2D.zip

解凍すると次の2つのファイルがある

$ tree tubhiswt-2D
tubhiswt-2D
├── tubhiswt_C0.ome.tif
└── tubhiswt_C1.ome.tif

4) 画像の表示

showinf tubhiswt_C0.ome.tif

show_inf_aug14.png

5) ImageJ で画像の表示

ijview tubhiswt_C0.ome.tif

ijview_aug14.png

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

[Java]MinecraftのModを作成しよう 1.16.1【ブロックの追加】

(この記事は一連の解説記事の一つになります)

先頭記事:入門編
前の記事:アイテムの追加
次の記事:

ブロックの追加

ブロックの追加をします。1.14.4のときと少し書き方を変えました(バージョンアップによって実装の方法が変わったという意味ではないです)。

ブロックの登録

\src\main\java\jp\koteko\liveinwater\
   ├ block
   │   └ Blocks.java
   ├ item
   └ LiveInWater.java
Blocks.java
package jp.koteko.liveinwater.block;

import jp.koteko.liveinwater.LiveInWater;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

import java.util.ArrayList;
import java.util.List;

@Mod.EventBusSubscriber(modid = LiveInWater.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Blocks {
    public static List<Block> blockList = new ArrayList<Block>();
    public static Block WATERTREE_ROOT_BLOCK = register("watertree_root_block", new Block(AbstractBlock.Properties.create(Material.WOOD).hardnessAndResistance(2.5F).sound(SoundType.WOOD)));

    private static Block register(String key, Block blockIn){
        blockList.add(blockIn);
        return blockIn.setRegistryName(LiveInWater.MOD_ID, key);
    }

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        for (Block block : blockList) {
            event.getRegistry().register(block);
        }
    }
}

前回のアイテム登録と同様のことをブロックについて行います。
宣言・初期化と同時にListにアイテムを追加し、forループでリストのアイテムをすべてregister()します。
単なる無機能ブロックの場合Blockクラスのインスタンスとします。コンストラクタの引数にはAbstractBlock.Propertiesのインスタンスを与えます。

アイテムの登録

\src\main\java\jp\koteko\liveinwater\
   ├ block
   ├ item
   │   └ Items.java
   └ LiveInWater.java
Items.java
package jp.koteko.liveinwater.item;

import jp.koteko.liveinwater.LiveInWater;
import jp.koteko.liveinwater.block.Blocks;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

import java.util.ArrayList;
import java.util.List;

@Mod.EventBusSubscriber(modid = LiveInWater.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Items {
    public static List<Item> itemList = new ArrayList<Item>();
    public static final Item WATERTREE_ROOT = register("watertree_root", new Item((new Item.Properties()).group(ItemGroup.MATERIALS)));
    public static final Item WATERTREE_ROOT_BLOCK = register("watertree_root_block", Blocks.WATERTREE_ROOT_BLOCK, ItemGroup.BUILDING_BLOCKS);

    private static Item register(String key, Item itemIn) {
        itemList.add(itemIn);
        return itemIn.setRegistryName(LiveInWater.MOD_ID, key);
    }
    private static Item register(String key, Block blockIn, ItemGroup itemGroupIn) {
        return register(key, new BlockItem(blockIn, (new Item.Properties()).group(itemGroupIn)));
    }

    @SubscribeEvent
    public static void registerItems(RegistryEvent.Register<Item> event) {
        for (Item item : itemList) {
            event.getRegistry().register(item);
        }
    }
}

ブロックは同時にアイテムとしても存在するので、アイテムも登録します。
registerメソッドをオーバーロード(異なる引数による同名メソッドの定義)してブロックアイテムの登録が行いやすいようにしました。登録名、ブロック、アイテムグループを渡すと、中でグループを設定したBlockItemをつくり、アイテムのregisterに投げるようにします。
BlockItemItemのサブクラスで、アイテムとしてのブロックはこのクラスのインスタンスとします。

resourcesの設定

\src\main\resources
   └ assets
      └ liveinwater
         ├ blockstates
         │  └ watertree_root_block.json
         ├ lang
         │  └ en_us.json
         │  └ ja_jp.json
         ├ models
         │  ├ block
         │  │  └ watertree_root_block.json
         │  └ item
         │     └ watertree_root_block.json
         └ textures
            ├ block
            │  └ watertree_root_block.png
            └ item
lang\en_us.json
{
  "item.liveinwater.watertree_root_block": "WaterTree Root"
}
lang\ja_jp.json
{
  "item.liveinwater.watertree_root_block": "ウォーターツリーの根"
}
blockstates\watertree_root_block.json
{
  "variants": {
    "": { "model": "liveinwater:block/watertree_root_block" }
  }
}

"model": "[MOD_ID]/[モデルファイルのパス]"
ブロックの状態によってモデルを変えるような場合(たとえば、方向を持つブロックや、フェンスなどの連結するブロック)にはこのファイルを編集しますが、不要な場合このような形です。

models\block\watertree_root_block.json
{
  "parent": "block/cube_all",
  "textures": {
    "all": "liveinwater:block/watertree_root_block"
  }
}

[MOD_ID]:[テクスチャファイルのパス]
parentblock/cube_allを指定します。
これは単純な立方体のモデルで、全面に同じテクスチャを反映します。

models\item\watertree_root_block.json
{
  "parent": "liveinwater:block/watertree_root_block"
}

ブロックアイテムはparentにブロックのモデルファイルを指定すればよいです。

watertree_root_block.png
テクスチャを作成し、配置します。

ルートテーブルの設定

Minecraft 1.9以降ではドロップを管理する仕組みとしてルートテーブルが導入されました。ブロックのドロップもこれを使って設定します。

\src\main\resources
   ├ assets
   └ data
      └ liveinwater
         └ loot_tables
            └ blocks
               └ watertree_root_block.json

前の記事で用意しておいたdata\liveinwaterフォルダ内にblocks\watertree_root_block.jsonを配置します。

watertree_root_block.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "conditions": [
        {
          "condition": "minecraft:match_tool",
          "predicate": {
            "enchantments": [
              {
                "enchantment": "minecraft:silk_touch",
                "levels": {
                  "min": 1
                }
              }
            ]
          }
        }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "liveinwater:watertree_root_block"
        }
      ]
    },
    {
      "rolls": 1,
      "conditions": [
        {
          "condition": "minecraft:inverted",
          "term": {
            "condition": "minecraft:match_tool",
            "predicate": {
              "enchantments": [
                {
                  "enchantment": "minecraft:silk_touch",
                  "levels": {
                    "min": 1
                  }
                }
              ]
            }
          }
        }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "functions": [
            {
              "function": "minecraft:apply_bonus",
              "enchantment": "minecraft:fortune",
              "formula": "minecraft:binomial_with_bonus_count",
              "parameters": {
                "extra": 3,
                "probability": 0.5714286
              }
            }
          ],
          "name": "liveinwater:watertree_root"
        }
      ]
    },
    {
      "rolls": 1,
      "conditions": [
        {
          "condition": "minecraft:inverted",
          "term": {
            "condition": "minecraft:match_tool",
            "predicate": {
              "enchantments": [
                {
                  "enchantment": "minecraft:silk_touch",
                  "levels": {
                    "min": 1
                  }
                }
              ]
            }
          }
        }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:dirt"
        }
      ]
    }
  ]
}

「シルクタッチのツールで破壊した場合ブロックそのもの(watertree_root_block)を、それ以外の場合は土ブロック(dirt)1つとアイテム(watertree_root)をランダム個ドロップする」という例です。詳しい説明は参考ページに譲ります。
(色々と試しましたが重複する記述があり冗長な書き方である可能性があります。)

最小の構成はこのようになります。
ブロック自身をドロップする最小構成のルートテーブル
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "[MOD_ID]:[ITEM_NAME]"
        }
      ]
    }
  ]
}

typeは何に関するルートテーブルかを選択する。poolsに与えた各プールが順次適応され、rolls回の抽選によってentriesに与えた各エントリーから結果が選ばれる。エントリーでtypenameを返すと指定。

確認

ゲームを起動して確認します。
キャプチャ.PNG
ブロックとそのアイテムの追加ができていること、表示が正しいこと、シルクタッチでブロックそのもの・非シルクタッチで土とアイテムがドロップすることが確認できました。

参考

[Java]MinecraftのModを作成しよう 1.14.4【2. ブロックの追加】
Minecraft 1.14.4 Forge Modの作成 その4 【ブロックの追加】
ルートテーブル - Minecraft Wiki
loot_tables_1.14.md · GitHub

次の記事

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