- 投稿日:2020-08-14T23:45:33+09:00
ゼロから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の拡張機能をインストールする
- 投稿日:2020-08-14T22:20:48+09:00
Javaでsftp (JSchラッパ)
SftpService.javaimport 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.javaSftpService 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(); } }
- 投稿日:2020-08-14T20:54:58+09:00
java 固定長文字列の中身の有無を調べる方法
String sti = " "; //スペースをtrimする //その結果、ブランクであるかどうか if(sti.trim().isEmpty()) { System.out.println("blank"); }else { System.out.println("notBlank"); }
- 投稿日:2020-08-14T19:42:08+09:00
【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 TestDataBaseAccessid = 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まとめ
今回の変更で再利用性が高まっていいコードに近づいてきたのではないでしょうか。
今後は実際にソフトウェアで使ってみて、より良いソースコードにしていきたいと思います。
本記事目次ページ
参考サイト
- 投稿日:2020-08-14T19:00:12+09:00
大量データを扱うと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.javapublic 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.javapublic 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.javapublic 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.javapublic static ResultSet selectFromTable() throws SQLException { try ( final Connection connection = DatabaseResourceManager.openConnection(); final Statement statement = DatabaseResourceManager.createStatement(connection) ) { return statement.executeQuery("select * from table"); } }を、
Repository.javapublic 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発行する部分のサンプルが少ないのが辛いですね...参考
- 投稿日:2020-08-14T15:54:51+09:00
ヒープソートの実装(javaで)
ディレクトリ構造
heap/
├ Heap.java
└ Main.java実行
$ javac Heap.java $ javac Main.java $ java Mainソースコード
Heap.javaimport 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.javaimport 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,参考
- 投稿日:2020-08-14T12:18:20+09:00
Java SE, EE, ME, SDK, JDK, JRE, JVM ややこしい用語を図で解説
Java SE、Java EE、 Java MEはプラットフォームの種類
Java SE、Java EE、 Java MEはJavaのプラットフォームの種類。
プラットフォームとは、Javaのプログラムを作成するための作成キットのことである。
プラットフォームにはAPIが含まれており、SE、EE、MEはそれぞれ含まれているAPIの種類が異なる。SDK、JDK、 JRE、 JVMはJavaの開発や実行に使うソフトウェア
SDK、JDK、 JRE、 JVMはJavaの開発や実行に使うソフトウェアの名称。
それぞれ図のような関係にある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に含まれている
- 投稿日:2020-08-14T12:12:18+09:00
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にした際はゲッター、セッターを使用してフィールドの値を扱う
- 投稿日:2020-08-14T12:07:50+09:00
Java 配列の作り方
配列を作る3ステップ
配列を作るには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};
- 投稿日:2020-08-14T12:04:19+09:00
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
- 投稿日:2020-08-14T12:02:26+09:00
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(); は改行が入る
- 投稿日:2020-08-14T12:00:10+09:00
Java 変数宣言の書き方
- 投稿日:2020-08-14T11:31:24+09:00
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.txthttpcore 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.
@NotThreadSafeorg.apache.http.annotation.Immutable org.apache.http.annotation.NotThreadSafe org.apache.http.annotation.ThreadSafe削除されたこれらの代わりに、
org.apache.http.annotation.Contractとorg.apache.http.annotation.ThreadingBehaviorenum が導入された。よって
@ThreadSafeをorg.apache.http.annotation.Contractに置換し、ThreadingBehaviorenumの値SAFEを渡せば、スレッドセーフな動作規約を指定できる。例
@Contract(threading = org.apache.http.annotation.ThreadingBehavior.SAFE)等々。
同じく
@NotThreadSafeは@Contract(threading = org.apache.http.annotation.ThreadingBehavior.UNSAFE)
@ImmutableはThreadingBehavior.IMMUTABLEあるいはIMMUTABLE_CONDITIONAL以下も参考:
https://hc.apache.org/httpcomponents-core-ga/httpcore/clirr-report.html
- 投稿日:2020-08-14T11:04:45+09:00
Jenkinsを使って、Docker内でビルドしてからDocker imageを作成する。
はじめに
Jenkinsを使って、Mavenコンテナ内でビルドしていました。
どうせならDocker imageまでも一気通貫でやりたくて作成。
思っていたよりも作成に時間がかかった。リポジトリ構成
src Dockerfile Jenkinsfile pom.xmlgitリポジトリにソースと上記のファイルを格納しています。
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のコマンドを呼び出すほうがシンプルだし、
保守性も高いのではと感じた。実際はどっちなのだろうか。参考文献
- 投稿日:2020-08-14T08:25:24+09:00
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:$PATH2) 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.tif4) 画像の表示
showinf tubhiswt_C0.ome.tif5) ImageJ で画像の表示
ijview tubhiswt_C0.ome.tif
- 投稿日:2020-08-14T08:25:24+09:00
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:$PATH2) 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.tif4) 画像の表示
showinf tubhiswt_C0.ome.tif5) ImageJ で画像の表示
ijview tubhiswt_C0.ome.tif
- 投稿日:2020-08-14T06:58:50+09:00
[Java]MinecraftのModを作成しよう 1.16.1【ブロックの追加】
(この記事は一連の解説記事の一つになります)
ブロックの追加
ブロックの追加をします。1.14.4のときと少し書き方を変えました(バージョンアップによって実装の方法が変わったという意味ではないです)。
ブロックの登録
\src\main\java\jp\koteko\liveinwater\ ├ block │ └ Blocks.java ├ item └ LiveInWater.javaBlocks.javapackage 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.javaItems.javapackage 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に投げるようにします。
BlockItemはItemのサブクラスで、アイテムとしてのブロックはこのクラスのインスタンスとします。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 └ itemlang\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]:[テクスチャファイルのパス]
parentはblock/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に与えた各エントリーから結果が選ばれる。エントリーでtypeのnameを返すと指定。確認
ゲームを起動して確認します。
ブロックとそのアイテムの追加ができていること、表示が正しいこと、シルクタッチでブロックそのもの・非シルクタッチで土とアイテムがドロップすることが確認できました。参考
[Java]MinecraftのModを作成しよう 1.14.4【2. ブロックの追加】
Minecraft 1.14.4 Forge Modの作成 その4 【ブロックの追加】
ルートテーブル - Minecraft Wiki
loot_tables_1.14.md · GitHub次の記事






