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

Minecraft modding 1.12.2で参考になるサイトまとめ

導入

intelij IDEAのプラグインを利用してコマンドを一切打たずに簡単に導入する方法

https://qiita.com/Hiroya_W/items/7ad9bad0387ef3688d1e

動画で基本的なMOD製作が学べる(英語)

https://www.youtube.com/watch?v=rmWBP5ifDlw&list=PLDhiRTZ_vnoX4bx_BJccGV7MjpXUfVJSn

ブロックの追加からTileEntitySpecialRenderまで、幅広く学べるサイト

https://shadowfacts.net/tutorials/forge-modding-112/

サイトのコード

https://github.com/shadowfacts/TutorialMod
1.12.2では、setUnlocalizedNameをsetTranslationKeyにする、などの変更が必要です、またCreativeTabの追加はエラーが多いので、削除して、他のサイトをもとに作成するのがいいかもしれません。面倒な変更はありますが、非常におすすめのサイトなのでぜひチェックしてみてください。

公開

Curse Forgeでの公開
https://qiita.com/C6H2Cl2/items/a972d6c25cee66b032c5
プロジェクト作成はこちらのアドレスに変更されています。くれぐれもご注意ください。
https://www.curseforge.com/project/create

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

初心者から始めるJava、継承

追記2020/3/16
クラス名のつけ方で間違いがあったことを、コメントで指摘いただいたので訂正。

はじめに

この記事は備忘録である。
参考書レベルの内容だが、本記事に掲載するコードについては、
間違えたものが中心となる。これは実際にコーディング中に間違えた部分を掲載し、自分で反省するために投稿するという目的によるもの。
また、後日にJavaSilver試験問題の勉強を兼ねて復習するため、深い部分の話はここでは触れない。

環境

言語:Java11、JDK13.0.2
動作環境:Windows10

クラスの拡張

今まで書いてきたクラスはどれも独立して書かれたものだったが、毎回メンバ(フィールドとメソッド)を設定する必要があった。Catクラス程度ならば頑張れば作れるだろうが、膨大なメンバ(フィールドとメソッド)を持つクラスを毎回すべて書くのは現実的ではない。
Javaは、既存のクラスが持っているメンバをそのまま引き継ぎつつ、独自のクラスを作るためのクラスの拡張(extend)機能がある。

extendedHousecat
class WildCat
{
  protected double weight;
  protected double height;
  protected String name;

  public static int sumCats = 0;

  public wildCat()
  {
    weight = 0.0;
    height = 0.0;
    sumCats++;
    System.out.println("ニャーゴ、猫をまた一匹見つけたよ。");
  }

  public WildCat(double we,double he)
  {
    weight = we;
    height = he;
    sumCats++;
    System.out.println("ニャーゴ、猫をまた一匹見つけたよ。");
    System.out.println("見つけた猫は、体重" + weight + "kg、身長" + height + "cmみたい。");
  }

  public void measureWeightHeight(double w,double h)
  {
    weight = w;
    height = h;

    System.out.println("この猫を計測してみました。");
    System.out.println("体重は" + weight + "kg、身長は" + height + "cmあります。");
  }

  public static void countCats()
  {
    System.out.println("全部で" + sumCats + "匹の猫を見つけたよ。");
  }
}

//ヤマネコ(Wildcat)のクラスをベースに、イエネコ(Housecat)のクラスを作ろう。

class HouseCat extends WildCat
{
  private String collorColor;
  private String nickname;

  public void callNickname(String s)
  {
    nickname = s;
    System.out.printlm("この猫に、新しく" + nickname + "ってニックネームを付けたよ。");
  }
}

ヤマネコ(WildCat)クラスを拡張して、イエネコ(HouseCat,Cat……色々呼び方はある)クラスを作った。このとき、WildcatHouseCatは親と子の関係になり、それぞれsuper classsub classになる。

スーパーなのかサブなのか

WildCatクラスを拡張したHouseCatクラスには、親であるWildCatクラスが持っていたメンバ(フィールドとメソッド)が引き継がれているので、

HouseCat cat1 = new HouseCat();
cat1.measureWeightHeight(5.0,50.5);

とすることで、体重5.0kgで身長50.5cmのHouseCatオブジェクトを生成できる。
もちろん、collorColor(首輪の色)とnickname(あだ名)フィールドも持っているし、
public void callNickname()メソッドであだ名をつけることもできる。

あえて親のコンストラクタを使いたい

HouseCatクラスにはコンストラクタがないが、sub classでコンストラクタを用意しなかった場合、super classのコンストラクタのうち引数のないコンストラクタが、HouseCatオブジェクトを生成したときに自動的に呼び出される。今回はpublic WildCat()の方だ。
だが例えば、super(8.0,60.8)としてコンストラクタ内で呼び出してやれば、8.0kg、60.8cmのイエネコとして呼び出されることになる。

privateより内向的で、privateより社交的

WildCatクラスのフィールドがいつもと違うのに気が付いただろうか?(実はここまで書き進めてから直した)。protectedは、「同じクラスか、子であるサブクラスからであればアクセスできる」という意味の修飾子になる。
サブクラスをどんどん作って発展させたいスーパークラスになると予想できるときは、protectedにしておくと後から扱いやすいだろう。
アクセス制限としてはパッケージも控えているが、ここでは割愛。

終わりに

継承後のサブクラスが持っていなければならないもの、スーパークラスのメンバがどこからまでのアクセスを認めているのか。こういった問題にまだ悩まされているので、ここの理解はまだまだ甘いのが現状。
以前扱ったJava11のリファレンスも、少し覗いただけでextendsがわちゃわちゃと出てきている。この家系図を理解したとき、本当のJavaが見えてくるのだろう、多分。

参考

出来るだけ自分で変数や式を書いてコンパイルしているので、完全に引用する場合はその旨記述する。

やさしいJava 第7版
Java SE11 Silver 問題集(通称黒本)

Java® Platform, Standard Edition & Java Development Kit
バージョン11 API仕様

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

EclipseのCheckStyleでCheckstyle execution failed due to an internal error.となった時の取あえずの対応方法

※. このページには、根本的な解決方法は書いていません。解決方法のわかる方は教えてください。

EclipseのProblemビューにエラーが出た。
何年も続いているプロジェクトをインポートするとちょくちょく出る。

Discription ...省略...
Checkstyle execution failed due to an internal error. Please check the error log for details Checkstyle 問題

Javaのソースで見つけた CHECKSTYLE:OFF って何?から知るCheckstyleなんてQiitaに投稿しちゃうぐらいCheckStyleがわかっていない。
このエラーが出ていても動く・・・がプロジェクトに赤×印が出ているのは嫌。

このエラーが出たら取あえず{ワークスペースのディレクトリ}/.metadata/.logを見てみます。

事象 : Property 'allowMissingPropertyJavadoc' does not exist

  • 環境
    • Eclipse IDE for Enterprise Java Developers.Version: 2018-12 (4.10.0)
    • Eclipse Checkstyle 8.28.0.202001092018
.log
!ENTRY net.sf.eclipsecs.core 4 0 2020-03-16 10:17:02.345
!MESSAGE Checkstyle-Plugin: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'allowMissingPropertyJavadoc' does not exist, please check the documentation
!STACK 0
com.puppycrawl.tools.checkstyle.api.CheckstyleException: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'allowMissingPropertyJavadoc' does not exist, please check the documentation
at com.puppycrawl.tools.checkstyle.Checker.setupChild(Checker.java:473)
...省略...
checkstye.xml
<module name="Checker">
  <property name="severity" value="warning"/>
  <module name="TreeWalker">
...省略...
    <module name="JavadocMethod">
      <!-- ↓これがエラー -->
      <property name="allowMissingPropertyJavadoc" value="true"/>
      <property name="suppressLoadErrors" value="true"/>
    </module>

原因 : allowMissingPropertyJavadocがバージョン8.25で削除されたから

Release 8.25
JavadocMethodCheck: remove deprecated properties ignoreMethodNamesRegex, minLineCount, allowMissingJavadoc, allowMissingPropertyJavadoc. Author: rnveach #7096
checkstyle – Release Notes

対応 : allowMissingPropertyJavadocを削除してMissingJavadocMethodCheckを追加する

(ざっくり訳)
このプロパティーは、8.20でMissingJavadocMethodCheckに移行したため、8.25で削除されました。前と同じ動作を継続するには、MissingJavadocMethodCheckを追加することをお勧めします。
checkstyle - Gradle checkstyleTest fails "CheckstyleException: Property 'allowMissingPropertyJavadoc' does not exist" - Stack Overflow

書き方これで本当にあっているのだろうか・・・・

checkstye.xml
<module name="Checker">
  <property name="severity" value="warning"/>
  <module name="TreeWalker">
...省略...
    <module name="JavadocMethod">
      <property name="suppressLoadErrors" value="true"/>
    </module>
    <!-- ↓追加してみた -->
    <module name="MissingJavadocMethodCheck">
      <property name="allowMissingPropertyJavadoc" value="true"/>
    </module>

事象 : Property 'suppressLoadErrors' does not exist

  • 環境
    • Eclipse IDE for Enterprise Java Developers.Version: 2018-12 (4.10.0)
    • Eclipse Checkstyle 8.28.0.202001092018
.log
!ENTRY net.sf.eclipsecs.core 4 0 2020-03-16 10:53:47.566
!MESSAGE Checkstyle-Plugin: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'suppressLoadErrors' does not exist, please check the documentation
!STACK 0
com.puppycrawl.tools.checkstyle.api.CheckstyleException: cannot initialize module TreeWalker - cannot initialize module JavadocMethod - Property 'suppressLoadErrors' does not exist, please check the documentation
at com.puppycrawl.tools.checkstyle.Checker.setupChild(Checker.java:473)
...省略...
checkstye.xml
<module name="Checker">
  <property name="severity" value="warning"/>
  <module name="TreeWalker">
...省略...
    <module name="JavadocMethod">
      <!-- ↓これがエラー -->
      <property name="suppressLoadErrors" value="true"/>
    </module>
    <module name="MissingJavadocMethodCheck">
      <property name="allowMissingPropertyJavadoc" value="true"/>
    </module>

原因 : suppressLoadErrorsは削除された、かもしれないから

checkstyle – Release NotessuppressLoadErrorsで検索しても削除された的な記述はない。
が、checkstyle – Javadoc CommentsJavadocMethodsuppressLoadErrorsはない・・・なんだろう・・・きっと削除されたんだろう・・・8.26あたりで。

(雑な訳)
プロパティーlogLoadErrorssuppressLoadErrorsを廃止し、これらの機能を削除したようです。
upgrade to checkstyle 8.26 · Issue #246 · checkstyle/sonar-checkstyle

対応 : 取あえずsuppressLoadErrorsを削除する

代わりに何かを定義したほうがいい気がするが、どうすればいいのかわからないので取あえず削除しておく

checkstye.xml
<module name="Checker">
  <property name="severity" value="warning"/>
  <module name="TreeWalker">
...省略...
    <module name="JavadocMethod" />
    <module name="MissingJavadocMethodCheck">
      <property name="allowMissingPropertyJavadoc" value="true"/>
    </module>

事象 : TreeWalker is not allowed as a parent of LineLength

  • 環境
    • Eclipse IDE for Enterprise Java Developers.Version: 2018-12 (4.10.0)
    • Eclipse Checkstyle 8.28.0.202001092018
!ENTRY net.sf.eclipsecs.core 4 0 2020-03-16 12:25:10.250
!MESSAGE Checkstyle-Plugin: cannot initialize module TreeWalker - TreeWalker is not allowed as a parent of LineLength Please review 'Parent Module' section for this Check in web documentation if Check is standard.
!STACK 0
com.puppycrawl.tools.checkstyle.api.CheckstyleException: cannot initialize module TreeWalker - TreeWalker is not allowed as a parent of LineLength Please review 'Parent Module' section for this Check in web documentation if Check is standard.
at com.puppycrawl.tools.checkstyle.Checker.setupChild(Checker.java:473)
...省略...
checkstye.xml
<module name="Checker">
  <property name="severity" value="warning"/>
  <module name="TreeWalker">
...省略...
    <module name="LineLength">
      <property name="max" value="120"/>
    </module>
...省略...

原因 : 8.24からLineLengthの親モジュールがTreeWalkerからCheckerになったから

Release 8.24
Change LineLength Check parent from TreeWalker to Checker. Author: rnveach, Roman Ivanov #2116
checkstyle – Release Notes

対応 : LineLengthChecker直下に移動する

checkstye.xml
<module name="Checker">
  <property name="severity" value="warning"/>
  <module name="LineLength">
    <property name="max" value="120"/>
  </module>
  <module name="TreeWalker">
...省略...

CheckStyleのチェック項目で関わったのぐらいは調べてみた

module property 説明 削除と追加情報
JavadocMethod - メソッドまたはコンストラクタのJavadocをチェックする 3.0追加
JavadocMethod allowMissingPropertyJavadoc プロパティメソッドのJavadocそのものが存在しない事を許可するかどうかを設定します。
プロパティメソッドとは、いわゆるGetter/Setterメソッドの事を指します。
8.25削除
JavadocMethod logLoadErrors 英語が難しくてよくわからない・・・ 8.26削除?
JavadocMethod suppressLoadErrors logLoadErrorsがtrueに設定されている場合、
logLoadErrorsがtrueに設定されたときに生成された違反は、
チェックスタイルレポートで違反として報告されなくなる。
8.26削除?
MissingJavadocMethod - メソッドまたはコンストラクタの欠落したJavadocコメントをチェックします。 8.21追加
MissingJavadocMethod allowMissingPropertyJavadoc プロパティ(セッターとゲッター)の
アクセサー・メソッドで欠落しているJavadocを許可するかどうかを制御します。
8.21追加
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【アルゴリズム入門】Javaで挿入ソートを実装する

スケベな物の見過ぎだと思いますが挿入という文字列を見るだけでよからぬ気持ちになりますね。

挿入ソートは基本のソートアルゴリズムの一つで、配列を整列済みの部分と未整列の部分に分けてソートを行うものです。

ソースコード

InsertSort.java
clas InsertSort {
  public static void main(String[] args) {
    int[] data = {30, 60, 70, 90, 20};
    sort(data);
    for(int element : data) {
      System.out.println(element);
    }
  }

  public static void sort(int[] data) {
    for(int i = 1; i < data.length; i ++) {
      int temp = data[i];
      int j = i;
      while(i > 0 && temp < data[j - 1]) {
        data[j] = data[j - 1];
        j --;
      }
      data[j] = temp;
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Visual Studio CodeによるSpring5 MVC Webアプリ開発 SQLServer編

はじめに

HelloWorld作成編で作成したプロジェクトを拡張していきます。
SQLServerはこちらで作成したDB、テーブルを流用します。

環境

OS:Windows 10 Pro 64bit
Editor:Visual Studio Code 1.42.1
JDK:AdoptOpenJDK 11.0.6+10 x64
Apache Maven:v3.6.3
Apache Tomcat:v9.0.31

プロジェクト作成

HelloWorldで作成したプロジェクトを「D:\JAVA\Project\sqlSample」にコピーして作成しました。

pom.xml

SQLServerにアクセスする為に必要なRepositoryを追加する。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <version>8.2.1.jre11</version>
        <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.4.2</version>
    <scope>compile</scope>
</dependency>

pom.xml全体です。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>sqlSample1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>11</java.version>
        <spring.version>5.2.4.RELEASE</spring.version>
        <!-- web.xmlが無い場合でもビルドを実行する -->
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>8.2.1.jre11</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>3.4.2</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.11.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
            <version>3.0.4.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Model作成

ビジネスロジックを記述するservice部とDB接続(SQL文発行)するpersistence部の作成、及びconfigファイルを作成します。

service部作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example」serviceフォルダを作成します。
その直下にconfigフォルダを作成します。
configフォルダにServiceConfig.javaを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example
└─service
    └─config
        └ServiceConfig.java
ServiceConfig.java
package com.example.service.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = "com.example.service")
@EnableTransactionManagement
public class ServiceConfig {
     @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            DataSourceTransactionManager transactionManager =
                    new DataSourceTransactionManager(dataSource);
            return transactionManager;
        }
}

persistence部作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example」persistenceフォルダを作成します。
その直下にconfigフォルダ、entityフォルダ、repositoryフォルダを作成します。
configフォルダにPersistenceConfig.javaを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example
└─persistence
    ├─config
    │ └─PersistenceConfig.java
    ├─entity
    └─repository

PersistenceConfig.javaにはSQLServerに接続するための設定を記述します。

PersistenceConfig.java
package com.example.persistence.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
@ComponentScan(basePackages = "com.example.persistence.repository")
@PropertySource("classpath:jdbc.properties")
public class PersistenceConfig {
    @Bean
    public DataSource dataSource(@Value("${jdbc.driverClassName}") String driverClassName,
                                 @Value("${jdbc.url}") String url,
                                 @Value("${jdbc.username}") String username,
                                 @Value("${jdbc.password}") String password) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setJdbcUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        dataSource.setMinimumIdle(10);
        dataSource.setMaximumPoolSize(300);
        dataSource.setConnectionInitSql("SELECT 0");

        return dataSource;
    }

    @Bean
    public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) {
        NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
        return jdbcTemplate;
    }
}

resources部作成

「D:\JAVA\Project\sqlSample\src\main」にresourcesフォルダを作成する
resourcesフォルダにjdbc.propertiesを作成します。

D:\JAVA\Project\sqlSample\src\main
└─resources
  └─jdbc.properties

SQLServerに接続するための接続情報を記述します。

jdbc.properties
jdbc.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://xxx:1433;databaseName=Training01;QuotedID=NO // SQLServerのホスト名もしくはIPアドレスを指定する。
jdbc.username=xxx // 接続ユーザー名を指定する。
jdbc.password=xxx // 接続ユーザーのパスワードを指定する。

WebAppInitializer.java変更

DB接続とビジネスロジック用のModelが使用出来るようにgetRootConfigClassesにPersistenceConfig.classとServiceConfig.classを追加する。

@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class};
}

WebAppInitializer.java全体です。

WebAppInitializer.java
package com.example.web.config;

import java.nio.charset.StandardCharsets;

import javax.servlet.Filter;

import com.example.persistence.config.PersistenceConfig;
import com.example.service.config.ServiceConfig;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * ビジネスロジックなど、Spring MVC以外に関するJava Configクラスを指定します。
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class};
    }

    /**
     * Spring MVCに関するJava Configクラスを指定します。
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {MvcConfig.class};
    }

    /**
     * DispatcherServletに対するURLパターンを指定します。
     * "/"を指定することで、全リクエストをDispatcherServletが受け取ります。
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * サーブレットフィルターを指定します。
     * 複数のフィルターがあった場合、配列に指定した順番に実行されます。
     */
    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{
                new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)};
    }
}

ここまでが前準備になります。

entity作成

DBからのデータを受けるentityを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence
└─entity
  └─ProductsMaster.java
ProductsMaster.java
package com.example.persistence.entity;

import lombok.Data;

@Data
public class ProductsMaster {
    private String ProductsCode;
    private String ProductsName;
    private Integer UnitPrice;

    public ProductsMaster() {}

    public ProductsMaster(
        String ProductsCode,
        String ProductsName, 
        Integer UnitPrice
        ) {
            this.ProductsCode = ProductsCode;
            this.ProductsName = ProductsName;
            this.UnitPrice = UnitPrice;
        }   
}

repository作成

DBに接続してSQL文を発行するrepositoryを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence
└─repository
  ├─ProductsMasterRepository.java
  └─ProductsMasterRepositoryImpl.java
ProductsMasterRepository.java
package com.example.persistence.repository;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

public interface ProductsMasterRepository {
    List<ProductsMaster> productsMasterList();
}
ProductsMasterRepositoryImpl.java
package com.example.persistence.repository;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class ProductsMasterRepositoryImpl implements ProductsMasterRepository {
    @Autowired
    NamedParameterJdbcTemplate jdbcTemplate;

    @Override
    public List<ProductsMaster> productsMasterList() {
        String strSQL = "SELECT * FROM TB_TestTable ORDER BY ID";

        List<ProductsMaster> pMList = jdbcTemplate.query(strSQL,
                (rs, rowNum) -> new ProductsMaster(
                        rs.getString("ProductsName"),
                        rs.getString("ProductsCode"),
                        rs.getInt("UnitPrice")                 
                        )
                );

        return pMList;
    }
}

service作成

ビジネスロジックとなるserviceを作成します。
今回の内容ではわざわざ作成する必要性はないのですが、1例として作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example
└─service
  ├─ProductsMasterService.java
  └─ProductsMasterServiceImpl.java
ProductsMasterService.java
package com.example.service;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

public interface ProductsMasterService {
    List<ProductsMaster> productsMasterList();
}
ProductsMasterServiceImpl.java
package com.example.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.persistence.entity.ProductsMaster;
import com.example.persistence.repository.ProductsMasterRepository;

@Service
public class ProductsMasterServiceImpl implements ProductsMasterService {
    @Autowired
    ProductsMasterRepository productsMasterRepository;

    @Override
    public List<ProductsMaster> productsMasterList() {
        List<ProductsMaster> pMList = productsMasterRepository.productsMasterList();

        return pMList;
    }
}

form作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example\web」にresourcesフォルダを作成します。
resourcesフォルダにjdbc.propertiesを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\web
└─form
  └─ProductsMasterForm.java
ProductsMasterForm.java
package com.example.web.form;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

import lombok.Data;

@Data
public class ProductsMasterForm {
    private List<ProductsMaster> helloList;
}

Controllerの作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example\web\controller」にProductsMasterController.javaを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\web
└─controller
  └─ProductsMasterController.java
ProductsMasterController.java
package com.example.web.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.service.ProductsMasterService;
import com.example.web.form.ProductsMasterForm;

@Controller
@RequestMapping("/hello")
public class ProductsMasterController {
    @Autowired
    ProductsMasterService productsMasterService;

    @GetMapping("/index")
    public String indexGet(Model model) {
        ProductsMasterForm productsMasterForm = new ProductsMasterForm();
        productsMasterForm.setHelloList(productsMasterService.productsMasterList());

        model.addAttribute("productsMasterForm", productsMasterForm);
        return "productsMasterForm/index";
    }

}

Viewの作成

「D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates」にproductsMasterフォルダを作成します。
productsMasterフォルダにindex.htmlを作成します。

D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates
└─productsMaster
  └─index.html
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Spring5 MVC sqlSample01</title>
</head>
<body>
    <h1>Hello Spring Products List</h1>
    <table border="1">
        <thead>
            <tr>
                <th>製品コード</th>
                <th>製品名</th>
                <th>単価</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="pm : ${productsMasterForm.pmList}" th:object="${pm}">
                <td th:text="*{ProductsCode}"></td>
                <td th:text="*{ProductsName}"></td>
                <td th:text="*{UnitPrice}"></td>
            </tr>
        </tbody>
    </table>
</body>
</html>

コンパイル/パッケージ

次のコマンドでコンパイル及びwarファイルの作成が出来ます。

mvn package

動作確認

コマンドパレット

Tomcat: Add Tomcat Server

Tomcatフォルダ選択ダイアログが表示されますので、Tomcatフォルダ(D:\JAVA\Tomcat\apache-tomcat-9.0.31)を選択します。

コマンドパレット

Tomcat: Run on Tomcat Server

warファイル選択ダイアログが表示されますので、「mvn package」で作成されたwarファイル(D:\JAVA\Project\SpringSample01\target\sqlSample1-1.0-SNAPSHOT.war)を選択します。

http://localhost:8080/」にアクセスして下さい。
tomcat4.jpg

sqlSample-1.0-SNAPSHOTをクリックして下さい。

sqlSample1.jpg

コード修正→動作確認の際、Tomactに配置したwarファイルを都度削除しないと上手く反映されませんでした。
hot deployが出来れば良いのですか、見つけられませんでした。

今回のサンプルソース

GitHubにアップしました。
https://github.com/t-skri1/SpringSample02

まとめ

簡易的ですが、Webアプリになりました。
次回はSpring Securityを組み込みます。

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

Visual Studio CodeによるSpring5 MVC Webアプリ開発 SQLServer接続編

はじめに

HelloWorld作成編で作成したプロジェクトを拡張していきます。
SQLServerはこちらで作成したDB、テーブルを流用します。

環境

OS:Windows 10 Pro 64bit
Editor:Visual Studio Code 1.42.1
JDK:AdoptOpenJDK 11.0.6+10 x64
Apache Maven:v3.6.3
Apache Tomcat:v9.0.31

プロジェクト作成

HelloWorldで作成したプロジェクトを「D:\JAVA\Project\sqlSample」にコピーして作成しました。

pom.xml

SQLServerにアクセスする為に必要なRepositoryを追加する。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <version>8.2.1.jre11</version>
        <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.4.2</version>
    <scope>compile</scope>
</dependency>

pom.xml全体です。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>sqlSample1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>11</java.version>
        <spring.version>5.2.4.RELEASE</spring.version>
        <!-- web.xmlが無い場合でもビルドを実行する -->
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>8.2.1.jre11</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>3.4.2</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.11.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
            <version>3.0.4.RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Model作成

ビジネスロジックを記述するservice部とDB接続(SQL文発行)するpersistence部の作成、及びconfigファイルを作成します。

service部作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example」serviceフォルダを作成します。
その直下にconfigフォルダを作成します。
configフォルダにServiceConfig.javaを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example
└─service
    └─config
        └ServiceConfig.java
ServiceConfig.java
package com.example.service.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = "com.example.service")
@EnableTransactionManagement
public class ServiceConfig {
     @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            DataSourceTransactionManager transactionManager =
                    new DataSourceTransactionManager(dataSource);
            return transactionManager;
        }
}

persistence部作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example」persistenceフォルダを作成します。
その直下にconfigフォルダ、entityフォルダ、repositoryフォルダを作成します。
configフォルダにPersistenceConfig.javaを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example
└─persistence
    ├─config
    │ └─PersistenceConfig.java
    ├─entity
    └─repository

PersistenceConfig.javaにはSQLServerに接続するための設定を記述します。

PersistenceConfig.java
package com.example.persistence.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
@ComponentScan(basePackages = "com.example.persistence.repository")
@PropertySource("classpath:jdbc.properties")
public class PersistenceConfig {
    @Bean
    public DataSource dataSource(@Value("${jdbc.driverClassName}") String driverClassName,
                                 @Value("${jdbc.url}") String url,
                                 @Value("${jdbc.username}") String username,
                                 @Value("${jdbc.password}") String password) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setJdbcUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        dataSource.setMinimumIdle(10);
        dataSource.setMaximumPoolSize(300);
        dataSource.setConnectionInitSql("SELECT 0");

        return dataSource;
    }

    @Bean
    public NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) {
        NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
        return jdbcTemplate;
    }
}

resources部作成

「D:\JAVA\Project\sqlSample\src\main」にresourcesフォルダを作成する
resourcesフォルダにjdbc.propertiesを作成します。

D:\JAVA\Project\sqlSample\src\main
└─resources
  └─jdbc.properties

SQLServerに接続するための接続情報を記述します。

jdbc.properties
jdbc.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://xxx:1433;databaseName=Training01;QuotedID=NO // SQLServerのホスト名もしくはIPアドレスを指定する。
jdbc.username=xxx // 接続ユーザー名を指定する。
jdbc.password=xxx // 接続ユーザーのパスワードを指定する。

WebAppInitializer.java変更

DB接続とビジネスロジック用のModelが使用出来るようにgetRootConfigClassesにPersistenceConfig.classとServiceConfig.classを追加する。

@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class};
}

WebAppInitializer.java全体です。

WebAppInitializer.java
package com.example.web.config;

import java.nio.charset.StandardCharsets;

import javax.servlet.Filter;

import com.example.persistence.config.PersistenceConfig;
import com.example.service.config.ServiceConfig;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * ビジネスロジックなど、Spring MVC以外に関するJava Configクラスを指定します。
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {PersistenceConfig.class, ServiceConfig.class};
    }

    /**
     * Spring MVCに関するJava Configクラスを指定します。
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {MvcConfig.class};
    }

    /**
     * DispatcherServletに対するURLパターンを指定します。
     * "/"を指定することで、全リクエストをDispatcherServletが受け取ります。
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * サーブレットフィルターを指定します。
     * 複数のフィルターがあった場合、配列に指定した順番に実行されます。
     */
    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{
                new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)};
    }
}

ここまでが前準備になります。

entity作成

DBからのデータを受けるentityを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence
└─entity
  └─ProductsMaster.java
ProductsMaster.java
package com.example.persistence.entity;

import lombok.Data;

@Data
public class ProductsMaster {
    private String ProductsCode;
    private String ProductsName;
    private Integer UnitPrice;

    public ProductsMaster() {}

    public ProductsMaster(
        String ProductsCode,
        String ProductsName, 
        Integer UnitPrice
        ) {
            this.ProductsCode = ProductsCode;
            this.ProductsName = ProductsName;
            this.UnitPrice = UnitPrice;
        }   
}

repository作成

DBに接続してSQL文を発行するrepositoryを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\persistence
└─repository
  ├─ProductsMasterRepository.java
  └─ProductsMasterRepositoryImpl.java
ProductsMasterRepository.java
package com.example.persistence.repository;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

public interface ProductsMasterRepository {
    List<ProductsMaster> productsMasterList();
}
ProductsMasterRepositoryImpl.java
package com.example.persistence.repository;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class ProductsMasterRepositoryImpl implements ProductsMasterRepository {
    @Autowired
    NamedParameterJdbcTemplate jdbcTemplate;

    @Override
    public List<ProductsMaster> productsMasterList() {
        String strSQL = "SELECT * FROM TB_TestTable ORDER BY ID";

        List<ProductsMaster> pMList = jdbcTemplate.query(strSQL,
                (rs, rowNum) -> new ProductsMaster(
                        rs.getString("ProductsName"),
                        rs.getString("ProductsCode"),
                        rs.getInt("UnitPrice")                 
                        )
                );

        return pMList;
    }
}

service作成

ビジネスロジックとなるserviceを作成します。
今回の内容ではわざわざ作成する必要性はないのですが、1例として作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example
└─service
  ├─ProductsMasterService.java
  └─ProductsMasterServiceImpl.java
ProductsMasterService.java
package com.example.service;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

public interface ProductsMasterService {
    List<ProductsMaster> productsMasterList();
}
ProductsMasterServiceImpl.java
package com.example.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.persistence.entity.ProductsMaster;
import com.example.persistence.repository.ProductsMasterRepository;

@Service
public class ProductsMasterServiceImpl implements ProductsMasterService {
    @Autowired
    ProductsMasterRepository productsMasterRepository;

    @Override
    public List<ProductsMaster> productsMasterList() {
        List<ProductsMaster> pMList = productsMasterRepository.productsMasterList();

        return pMList;
    }
}

form作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example\web」にresourcesフォルダを作成します。
resourcesフォルダにjdbc.propertiesを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\web
└─form
  └─ProductsMasterForm.java
ProductsMasterForm.java
package com.example.web.form;

import java.util.List;

import com.example.persistence.entity.ProductsMaster;

import lombok.Data;

@Data
public class ProductsMasterForm {
    private List<ProductsMaster> helloList;
}

Controllerの作成

「D:\JAVA\Project\sqlSample\src\main\java\com\example\web\controller」にProductsMasterController.javaを作成します。

D:\JAVA\Project\sqlSample\src\main\java\com\example\web
└─controller
  └─ProductsMasterController.java
ProductsMasterController.java
package com.example.web.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.service.ProductsMasterService;
import com.example.web.form.ProductsMasterForm;

@Controller
@RequestMapping("/hello")
public class ProductsMasterController {
    @Autowired
    ProductsMasterService productsMasterService;

    @GetMapping("/index")
    public String indexGet(Model model) {
        ProductsMasterForm productsMasterForm = new ProductsMasterForm();
        productsMasterForm.setHelloList(productsMasterService.productsMasterList());

        model.addAttribute("productsMasterForm", productsMasterForm);
        return "productsMasterForm/index";
    }

}

Viewの作成

「D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates」にproductsMasterフォルダを作成します。
productsMasterフォルダにindex.htmlを作成します。

D:\JAVA\Project\sqlSample\src\main\webapp\WEB-INF\templates
└─productsMaster
  └─index.html
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Spring5 MVC sqlSample01</title>
</head>
<body>
    <h1>Hello Spring Products List</h1>
    <table border="1">
        <thead>
            <tr>
                <th>製品コード</th>
                <th>製品名</th>
                <th>単価</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="pm : ${productsMasterForm.pmList}" th:object="${pm}">
                <td th:text="*{ProductsCode}"></td>
                <td th:text="*{ProductsName}"></td>
                <td th:text="*{UnitPrice}"></td>
            </tr>
        </tbody>
    </table>
</body>
</html>

コンパイル/パッケージ

次のコマンドでコンパイル及びwarファイルの作成が出来ます。

mvn package

動作確認

コマンドパレット

Tomcat: Add Tomcat Server

Tomcatフォルダ選択ダイアログが表示されますので、Tomcatフォルダ(D:\JAVA\Tomcat\apache-tomcat-9.0.31)を選択します。

コマンドパレット

Tomcat: Run on Tomcat Server

warファイル選択ダイアログが表示されますので、「mvn package」で作成されたwarファイル(D:\JAVA\Project\SpringSample01\target\sqlSample1-1.0-SNAPSHOT.war)を選択します。

http://localhost:8080/」にアクセスして下さい。
tomcat4.jpg

sqlSample-1.0-SNAPSHOTをクリックして下さい。

sqlSample1.jpg

コード修正→動作確認の際、Tomactに配置したwarファイルを都度削除しないと上手く反映されませんでした。
hot deployが出来れば良いのですか、見つけられませんでした。

今回のサンプルソース

GitHubにアップしました。
https://github.com/t-skri1/SpringSample02

まとめ

簡易的ですが、Webアプリになりました。
次回はSpring Securityを組み込みます。

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

Tomcatにおけるシングルテナント対応非同期処理の実装

Tomcatにおけるシングルテナント対応非同期処理の実装

この記事は、「Tomcatにおける非同期処理の実装」の一部分です。

はじめに

ここでは、シングルテナントにおいて、スレッドプールを利用した非同期タスクの流動制限を行うTomcatアプリケーションを実装していきます。

動作確認ソフトウェア

次のソフトウェアおよびライブラリの環境で動作を確認しています。
その他の依存ライブラリについては、後述のpom.xmlを参考にしてご確認してください。

  • Windows 10
  • Java SE 8
  • Maven 3.6.2
  • Eclipse IDE for Enterprise Java Developer 2019-06 (4.12.0)
  • Tomcat 8.5.43
  • Spring Boot 1.5.22
  • Spring Framework 4.3.25

pom.xmlの作成

EclipseかMavenを使ってMavenプロジェクトを作成し、pom.xmlに下図の内容を追加します。
なお、dependencyのspring-boot-starter-actuatorはTomcatモニタリング用のライブラリですが、運用環境ではセキュリティリスクとなるので外します。

<project xmlns=...>
    :
  <build>
      :
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.1.4.RELEASE</version>
      </plugin>
    </plugins>
      :
  </build>

  <dependencies>
      :
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>1.5.22.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      <version>1.5.22.RELEASE</version>
    </dependency>
      :
  </dependencies>
</project>

図3.2 pom.xmlへの追加内容

設定ファイルの作成

Spring Bootの設定ファイルである「application.properties」を作成します。

# Tomcatモニタリング
# Tomcatコンテナの監視やシャットダウンするなどの運用監視向けの設定
# 運用環境ではセキュリティリスクとなるので外す
endpoints.shutdown.enabled=true
management.security.enabled=false
management.endpoints.web.exposure.include=**

# スレッドプール
# 非同期タスクの受理数と同時実行数を指定
# queueCapacityの数だけタスクをキューイングし、maxPoolSizeの数だけバックグランドで動作
# 最大「maxPoolSize + queueCapacity」の数だけ非同期タスクを受け入れ、その数を超えたらRejectされる(受け付けない)
#  corePoolSize  = 初期のThread数
#  queueCapacity = corePoolSizeが一杯になったときにキューイングする数
#  maxPoolSize   = queueCapacityを越えたときの最大Thread数
threadPool.corePoolSize=1
threadPool.queueCapacity=2
threadPool.maxPoolSize=2
threadPool.keepAliveSeconds=1

# Loggingのログ出力レベルを指定
# Loggingの出力レベルを指定
# https://docs.oracle.com/javase/jp/8/docs/api/java/util/logging/Level.html
logging.level.root=INFO
logging.level.org.springframework.web=INFO
logging.level.web01=INFO

図3.3 application.propertiesの内容

スレッドプール設定のPOJOクラスの作成

スレッドプール設定を保持するPOJOクラスを作成します。
Spring Frameworkによりapplication.propertiesの"threadPool"で始まる設定値が、自動的にこのクラスに注入されます。

@Component
@ConfigurationProperties(prefix = "threadPool")
public class ThreadPoolSettings {
    private String corePoolSize;
    private String queueCapacity;
    private String maxPoolSize;
    private String keepAliveSeconds;

    public void setCorePoolSize(String corePoolSize)         {this.corePoolSize = corePoolSize;}
    public void setQueueCapacity(String queueCapacity)       {this.queueCapacity = queueCapacity;}
    public void setMaxPoolSize(String maxPoolSize)           {this.maxPoolSize = maxPoolSize;}
    public void setKeepAliveSeconds(String keepAliveSeconds) {this.keepAliveSeconds = keepAliveSeconds;}

    public int getCorePoolSize()     {return Integer.valueOf(corePoolSize);}
    public int getQueueCapacity()    {return Integer.valueOf(queueCapacity);}
    public int getMaxPoolSize()      {return Integer.valueOf(maxPoolSize);}
    public int getKeepAliveSeconds() {return Integer.valueOf(keepAliveSeconds);}
}

図3.4 ThreadPoolSettingsクラスの内容

Tomcat起動クラスの作成

組み込みのTomcatコンテナを起動するApplicationクラスを作成します。

@SpringBootApplicationは、Spring Frameworkに自動的に@Configuration@Componentなどのクラスを走査するよう指示するものです。@Repository@Service@Controller@Componentの一つなので、これらも検出されます。ただし、デフォルトでは走査対象は、同一パッケージかその配下のパッケージに限るので、注意が必要です。

@SpringBootApplication
public class Application {
    public static void main(final String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

図3.5 Applicationクラスの内容

@Configurationクラスの作成

Tomcat起動時にアプリケーションの初期設定を行うSampleConfigクラスを作成します。

このクラスには、初期設定を行うクラスであることを宣言する@Configurationと、非同期呼び出しを有効化するための@EnableAsyncがあります。taskExecutor1()にある@Beanは、Spring FrameworkにBeanを登録するものです。デフォルトでは、Bean名はメソッド名と同じで、ここでは「taskExecutor」がBean名となります。

taskExecutor1()では、キューを持つスレッドプールであるThreadPoolTaskExecutorを生成します。運用環境のコア数とメモリ容量に応じて、前述のapplication.propertiesの設定値を変えることにより、キューとスレッドプールの容量をチューニングできるというわけです。

また、Tomcat終了時に、動作中のスレッドを終了させないよう、スレッドプールが保持するスレッドは「非デーモン」にします。ThreadPoolTaskExecutorはデフォルトで非デーモンですが、ここでは明示的に指定しています。ただし、スレッドプールのシャットダウンでは、キューに滞留しているタスクはすべて破棄されてしまいます。Tomcatアプリケーションを再起動したときに、キューに滞留していたタスクを実行させたい場合は、追加の実装が必要ですが、ここでは割愛します。

@Configuration
@EnableAsync
public class SampleConfig {
    //application.propertiesの"threadPool"で始まる設定値を保持する変数
    @Autowired
    ThreadPoolSettings threadPoolSettings;

    //@Async向けのスレッドプールをSpring Beanとして登録する
    //Bean名はメソッド名と同じ「taskExecutor1」となる
    @Bean
    public ThreadPoolTaskExecutor taskExecutor1() {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(threadPoolSettings.getCorePoolSize());
        executor.setQueueCapacity(threadPoolSettings.getQueueCapacity());
        executor.setMaxPoolSize(threadPoolSettings.getMaxPoolSize());
        executor.setKeepAliveSeconds(threadPoolSettings.getKeepAliveSeconds());
        executor.setThreadNamePrefix("Executor1-");
        executor.setDaemon(false);
        executor.initialize();
        return executor;
    }
}

図3.6 SampleConfigクラスの内容

@Serviceクラスの作成

下図のSampleServiceクラスを作成します。

このクラスの@Serviceは、DDD(Domain-Driven Design)の構成要素の一つであり、ビジネスロジックを処理する債務を担います。

cmd()では、引数で指定されたコマンドを起動し、コマンドの実行状況をノンブロッキングで確認できるFutureオブジェクトを返します。非同期呼び出しの対象であることを宣言するために@Asyncを付加してあり、"taskExcecutor1"パラメータを付けてSampleConfigクラスで定義したスレッドプールへ紐付けています。

@Service
public class SampleService {
    @Async("taskExecutor1")
    public CompletableFuture<String> cmd(final String[] command) {
        final int exitValue = run(command);
        return CompletableFuture.completedFuture("exitValue="+exitValue);
    }

    //外部コマンドを実行
    public static int run(final String[] command) {
        try{
            final Process p = new ProcessBuilder(command).redirectErrorStream(true).start();
            try(final BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))){
                String str;
                while((str = r.readLine()) != null) {
                    System.out.println("Command output: "+str);
                }
            }
            return p.waitFor();
        }catch(IOException | InterruptedException e) {
            e.printStackTrace();
            return -1;
        }
    }
}

図3.7 SampleServiceクラスの内容

@Controllerクラスの作成

下図のSampleControllerクラスを作成します。

@Controllerは、MVCモデルのControllerに該当し、クライアントからの要求に応対する債務を担います。

このクラスには、@Autowiredを付加したフィールドがありますが、@Component@Beanなどで定義された部品を自動的に注入するようSpring Frameworkへ指示するするものです。

cmd()は「GET http://.../cmd」に応対して、SampleServiceのcmd()を呼び出しています。同期呼び出しのように見えますが、呼び出し先には@Asyncを付加してあるためSpring Frameworkが自動的に非同期呼び出しに変えてくれるわけです。

スレッドプールのキューやスレッドが満杯になり非同期処理を受理できない場合は、RejectedExecutionExceptionがスローされます。これにより非同期処理の流量制御を実現できるわけです。ここでは簡単のため、「Rejected」の文字列をクライアントに返していますが、本来はHTTPの「429 Too Many Requests」といったステータスコードを返すのが筋でしょう。

status()は「GET http://.../status」に応対して、SampleServiceのcmd()が返してきたFutureオブジェクトを走査し、非同期呼び出しの実行状況を文字列で返します。

@Controller
public class SampleController {
    private static final String[] COMMAND = new String[] {"cmd", "/c", "ping", "-n", "10", "localhost", ">", "nul"};
    private AtomicInteger counter = new AtomicInteger(1);
    private HashMap<String, Future<String>> futures = new HashMap<>();

    @Autowired
    private SampleService sampleService;

    @RequestMapping(value="/cmd", method=RequestMethod.GET)
    @ResponseBody
    public String cmd() {
        final String id = String.format("<noTenantId>#%03d", counter.getAndIncrement());
        try{
            final CompletableFuture<String> future = sampleService.cmd(COMMAND)
                .exceptionally(ex -> id+": Exception: "+ex.getMessage()+"\n");
            synchronized(this.futures) {
                this.futures.put(id, future);
            }
            return "Accepted: id="+id+"\n";
        }catch(RejectedExecutionException e) {
            final CompletableFuture<String> future = new CompletableFuture<>();
            future.complete("Rejected");
            synchronized(this.futures) {
                this.futures.put(id, future);
            }
            return "Rejected: id="+id+"\n";
        }
    }

    @RequestMapping(value="/status", method=RequestMethod.GET)
    @ResponseBody
    public String status() {
        final Map<String, Future<String>> map;
        synchronized(this.futures) {
            map = (Map<String, Future<String>>)this.futures.clone();
        }
        return map.entrySet().stream()
            .map(entry->SampleController.futureGet(entry.getKey(), entry.getValue()))
            .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
            .toString();
    }

    //非同期タスクの状況を文字列に変換
    private static String futureGet(String tenantId, Future<String> future) {
        if(future.isDone()==false && future.isCancelled()==false) {
            return tenantId+": Running\n";
        }else if(future.isCancelled()) {
            return tenantId+": Canceled\n";
        }
        try {
            return tenantId+": "+future.get()+"\n";
        } catch (InterruptedException | ExecutionException | CancellationException e) {
            return tenantId+": Exception\n";
        }
    }
}

図3.8 SampleControllerクラスの内容

動作確認

ローカルでTomcatコンテナを起動し、次のようにコマンドを実行して動作確認できます。

$ curl -X GET http://localhost:8080/cmd
Accepted: id=<noTenantId>#001

# 10秒強待ってから/statusをGETする
$ curl -X GET http://localhost:8080/status
<noTenantId>#001: exitValue=0

# Tomcatコンテナを通常終了させる
$ curl -X POST http://localhost:8080/shutdown

図3.9 非同期タスクの動作確認

Tomcatにおけるマルチテナント対応非同期処理の実装」へ続く...

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

Tomcatにおけるマルチテナント対応非同期処理の実装

Tomcatにおけるマルチテナント対応非同期処理の実装

この記事は、「Tomcatにおける非同期処理の実装」の一部分です。

はじめに

Tomcatにおける非同期処理の実装」では、@Asyncとスレッドプールの組み合わせで、非同期タスクの流動制御ができることを紹介しました。しかし、@Asyncとスレッドプールは一対一の対応であるため、マルチテナントには対応できないわけです。

マルチテナントとは、1つのアプリケーションのインスタンスで、複数のお客様向けにサービスを提供する仕組みです。たとえば、次のようにテナントIDを指定した場合に、テナント「t1」と「t2」を異なるスレッドプールに割り当て、データストアの異なるスキーマへアクセスするというふうに、テナントごとに異なる資源・資産を利用するようなイメージです。

- 「GET http://localhost/async?tenantId=t1」
- 「GET http://localhost/async?tenantId=t2」

図4.0 マルチテナント対応のHTTPリクエスト

そこで、マルチテナント対応の非同期処理を実装する方法を考えてみます。また、Spring Frameworkの管理から外れる部分があり、スレッドプールのライフサイクル管理を自前で実装する必要があることも紹介します。

スレッドプール管理クラスを追加

マルチテナント対応のスレッドプールを管理するThreadPoolTaskExecutorsクラスを作成します。

このクラスでは、テナントIDとスレッドプールをkey-valueとしたMap型をフィールドで保持し、getExecutor()ではテナントIDに対応したスレッドプールを返しています。ところが、このクラスをBeanとして登録しても、Spring Frameworkはスレッドプールを内包していることを認識するわけではありません。

そこで、自前でスレッドプールのシャットダウンを行うために、@PreDestroyを付加したdestroy()を追加しています。Tomcat通常終了時に@PreDestroyのメソッドがコールバックされるため、そのタイミングでスレッドプールをシャットダウンさせるわけです。ただし、@Configurationクラスの作成で述べたように、このシャットダウンでもキューに滞留しているタスクが破棄される点に注意してください。

public class ThreadPoolTaskExecutors {
    private Map<String, ThreadPoolTaskExecutor> map = new HashMap<>();

    public ThreadPoolTaskExecutors(ThreadPoolSettings settings) {
        //テナントごとにスレッドプールを生成
        map.put("Tenant1", newExecutor("Tenant1", settings));
        map.put("Tenant2", newExecutor("Tenant2", settings));
        map.put("Tenant3", newExecutor("Tenant3", settings));
    }

    //スレッドプールを生成
    private static ThreadPoolTaskExecutor newExecutor(String tenantId, ThreadPoolSettings settings) {
        final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(settings.getCorePoolSize());
        executor.setQueueCapacity(settings.getQueueCapacity());
        executor.setMaxPoolSize(settings.getMaxPoolSize());
        executor.setKeepAliveSeconds(settings.getKeepAliveSeconds());
        executor.setThreadNamePrefix("Executor2-"+tenantId+"-");
        executor.setDaemon(false);
        executor.initialize();
        return executor;
    }

    //テナントIDに対応したスレッドプールを返す
    public ThreadPoolTaskExecutor getExecutor(String tenantId) {
        return map.get(tenantId);
    }

    //スレッドプールをシャットダウンさせる
    @PreDestroy
    private void destroy() {
        this.map.values().forEach(ThreadPoolTaskExecutor::shutdown);
    }
}

図4.1 ThreadPoolTaskExecutorsクラスの内容

@Configurationクラスの改修

前述のSampleConfigクラスに、下図のようにtaskExecutors2()を追加します。
このメソッドでは@Beanを利用して、マルチテナント用スレッドプール管理オブジェクトをBeanとして登録します。

public class SampleConfig {
        :
    @Bean
    public ThreadPoolTaskExecutors taskExecutors2() {
        return new ThreadPoolTaskExecutors(threadPoolSettings);
    }

図4.2 SampleConfig#taskExecutors2()の内容

@Serviceクラスの改修

前述のSampleServiceクラスに、下図のようにcmd2()を追加します。
cmd()と異なり、@Asyncは付けず、復帰値はFutureではなくStringのオブジェクトです。

public class SampleService {
        :
    public String cmd2(final String[] command) {
        final int exitValue = run(command);
        return "exitValue="+exitValue;
    }
        :
}

図4.3 SampleService#cmd2()の内容

@Controllerクラスの改修

前述のSampleControllerクラスを改修します。
まず、taskExecutors2フィールドを追加します。@AutowiredがあるのでSampleConfigクラスで登録したBeanが自動的に注入されます。

public class SampleController {
        :
    @Autowired
    private ThreadPoolTaskExecutors taskExecutors2;
        :
}

図4.4.1 SampleService#taskExecutors2の内容

次に、SampleServiceのcmd2()を呼び出すメソッドを追加します。
このメソッドの処理の流れはcmd()メソッドと同じですが、スレッドプールを取得して明に非同期呼び出しを行っている点で異なります。

public class SampleController {
        :
    @RequestMapping(value="/cmd2", method=RequestMethod.GET)
    @ResponseBody
    public String cmd2(@RequestParam(value="id", required=true)final String id) {
        final String tenantId = String.format("%s#%03d", id, counter.getAndIncrement());

        //ThreadPoolを取得
        final ThreadPoolTaskExecutor executor = taskExecutors2.getExecutor(id);
        if(executor == null) {
            return "Invalid id: id="+id+"\n";
        }

        try{
            final Future<String> future = executor.submit(()->{
                return sampleService.cmd2(COMMAND); //Serviceを呼び出す
            });

            synchronized(this.futures) {
                this.futures.put(tenantId, future);
            }
            return "Accepted: id="+tenantId+"\n";
        }catch(RejectedExecutionException e) {
            final CompletableFuture<String> future = new CompletableFuture<>();
            future.complete("Rejected");
            synchronized(this.futures) {
                this.futures.put(tenantId, future);
            }
            return "Rejected: id="+tenantId+"\n";
        }
    }

図4.4.2 SampleService#cmd2()の内容

動作確認

ローカルでTomcatコンテナを起動し、次のようにコマンドを実行して動作確認できます。

$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Accepted: id=Tenant1#001

$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Accepted: id=Tenant1#001

$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Accepted: id=Tenant1#001
$ curl -X GET http://localhost:8080/cmd2?id=Tenant1

$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Rejected: id=Tenant1#001

$ curl -X GET http://localhost:8080/cmd2?id=Tenant1
Rejected: id=Tenant1#001

# スレッドプールが「maxPoolSize=2」、「queueCapacity=2」のとき
# 10秒強待ってから/statusをGETすると
# 実行が終了したタスクが2個、実行中のタスクが2個、Rejectされたタスクが2個
# であることが出力される
$ curl -X GET http://localhost:8080/status
Tenant1#001: exitValue=0
Tenant1#002: Running
Tenant1#003: Running
Tenant1#004: exitValue=0
Tenant1#005: Rejected
Tenant1#006: Rejected

# Tomcatコンテナを通常終了させる
$ curl -X POST http://localhost:8080/shutdown

図4.5 マルチテナント対応非同期タスクの動作確認

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

Tomcatにおける非同期処理の実装

Tomcatにおける非同期処理

はじめに

Webアプリケーションでは、クライアントからのHTTP要求に対して長時間を要する処理を行うとHTTP応答がタイムアウトになり、HTTP要求を正常に受け付けられたかどうかを確認しづらくなります。また、長時間を要する処理をバックグランドで行う場合でも、非同期タスクの同時実行数を制限しなければ、スレッド数がどんどん増え運用環境の資源が枯渇すると、動作不能に陥るおそれがあります。

Tomcatコンテナ上で動作するTomcatアプリケーションで、下図のように長時間を要する処理を非同期で呼び出す方法を考えます。

非同期処理の流れ

図1 非同期処理の流れ

図1では、Tomcatコンテナの中にController、Service、Entityの3つがあります。クライアントからHTTP要求が来たときに、Tomcatコンテナを介してControllerからServiceを非同期で呼び出しています。非同期呼び出し後に、すぐクライアントへHTTP応答を返せるため、HTTP要求を受け付けたのかそれとも拒否したのかをクライアントへ通知できます。非同期タスクの実行状況をEntityへ反映しておけば、後から別のHTTP要求を出してその実行状況を確認できます。

非同期呼び出しの実装にあたっては、冒頭で述べたとおり非同期タスクの流動制御が必要ですが、ここで知っておきたいのは、アプリケーションが「適当に」スレッドを生成するのは推奨されていない点です。なぜなら、アプリケーションが生成したスレッドをTomcatコンテナやJava EEコンテナが認識しないため、スレッドのライフサイクルを適切に管理できなくなるためです。そこで、スレッドの個数やライフサイクルなどの管理を行う仕様である「Concurrency Utilities for Java EE(JSR 236)」が提唱され、Java EE 7に導入されました。

TomcatコンテナはJava EEコンテナのサブセットのようなものであり、JSR 236をサポートしているわけではありませんが、Spring Frameworkを導入すればスレッドのライフサイクル管理ができるようになります。ここでは、Spring Frameworkを活用した「シングルテナント対応」および「マルチテナント対応」の非同時処理の流動制御の実装方法を考えていきます。

記事構成

この記事は3つの記事で構成しています。

サンプルソース

この記事で作成したソースコードは、GitHubにあります。

まとめ

  • シングルテナントの場合
    • 非同期メソッドとリクエストが1対1の対応なら、Spring Frameworkの機能で対応可能
  • マルチテナントの場合
    • 非同期メソッドとリクエストが1対多の対応なら、自前でスレッドプールとそのライフサイクルを用意する必要あり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】初めてのJava体験(基礎・コード)

最初に

自身で学習してきた内容を書き出していきます。
基本的には自分自身でわかるような内容になりますので、ご容赦ください。
また、誤っている点がありましたら、コメントにてご指摘ください。

(要確認)マークがあるものは、「実行前に必ずググる」べき内容。

⚠️今回はアウトプットを含む、インプット寄りの投稿です⚠️

今回は、外部サイト「Progate」にて学習した内容を吐き出します。

index

  • Javaってなんぞ
  • 試しにコードを書いてみた
  • データ型
  • 変数

Javaってなんぞ

世界中にたくさんの開発者がいる有名な言語のなかの一つ!
大規模システム、Webアプリケーション、スマートフォンアプリなど、Javaはあなたの周りの様々な場所で使われている。

そして・・・

⚠️JavaとJavaScriptは全くの無関係である⚠️

試しにコードを書いてみた

文字列

やりたいこと:「文字列」を、コンソールに出力させたい。

Main.java
class Main { // ここにクラスを記述
  public static void main(String[] args) { // ここにメゾットを記述
    System.out.println("Hello,World!"); // ここに処理を記述
  }
}
  • 文字列は「"(ダブルクォーテーション)」で囲んであげる。
  • 「;(セミコロン)」は忘れがちになる!
  • System.out.println()というのは、「()の中身を出力(表示)せよ」という「命令」
    • print「l」nは小文字のエルだぞ
  • クラス>メゾット>処理の構成で記述するようだ

文字列の連結

やりたいこと:「文字列」を、文字列の連結させてコンソールに出力させたい。

Main.java
class Main {
  public static void main(String[] args) {
    System.out.println("Hello" + "World!");

    System.out.println("5" + "3"); //結果:53
  }
}
  • 後述の「数値」と「文字列」の違いをイメージすると良い!

数値(四則演算処理)

やりたいこと:「数値」を、コンソールに出力させたい。

Main.java
class Main { // ここにクラスを記述
  public static void main(String[] args) { // ここにメゾットを記述
    System.out.println(3); // ここに処理を記述
    System.out.println(1 + 2);
    System.out.println(5 - 2);
  }
}
  • 数値はそのままでOK。
  • 四則演算もできちゃう!
  • 今回の記述だと、全て「3」だが処理内容が違うぞ。

やりたいこと:いろんな計算結果を出力させたい。

Main.java
class Main { // ここにクラスを記述
  public static void main(String[] args) { // ここにメゾットを記述
    System.out.println(5 * 2); // 結果:10
    System.out.println(6 / 2); // 結果:3
    System.out.println(8 % 5); // 結果:3
  }
}
  • 「*」は掛け算、「/」は割り算
  • 「%」は割り算の余りを計算できるぞ!(今回の場合は「1余り3」なので出力は「3」となる)
  • 四則演算処理を見やすくする為に、半角スペースを入れると良き♪

文字列と数値の違いがわかる例

Main.java
System.out.println(3); // 結果:3
System.out.println("1 + 2"); // 結果:1 + 2

データ型

String型とint(integer)型

Ruby on Railsで開発をしていた時にも、テーブル設計時にお世話になりました、String型とint(integer)型についてです。

String型 int型
"Hello,World"(文字列) 3(整数)
"イロハ" 1995
  • String型
    • 文字の並び
  • int(integer)型
    • 整数
    • (iは小文字)

後日、様々なデータ型をまとめた記事を投稿予定です。

変数

データのやりとりをやりやすくするための、定義できる書類BOXや名札みたいな感じ。
投稿者の過去記事に、rubyでの簡単な説明があるので、よければ読んでみてね!
Rubyについて(基礎)

Javaにおいての変数

まずは定義付けが必要!

Javaで変数を定義するには下記の記述が必須!
①変数にいれる値のデータ型を指定
②変数の名前を指定
③値を代入する

スクリーンショット 2020-03-16 17.28.14.png

ここでのポイントは「=」は「等しい」を意味するのではないこと!

2020/3/16 15:00 執筆中・・・
2020/3/18 ??:?? 記事完成予定

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

【Java】初めてのJava学習:その1 Javaってなんぞ?から変数の基礎まで(基礎・コード)

最初に

自身で学習してきた内容を書き出していきます。
基本的には自分自身でわかるような内容になりますので、ご容赦ください。
また、誤っている点がありましたら、コメントにてご指摘ください。

(要確認)マークがあるものは、「実行前に必ずググる」べき内容。

⚠️今回はインプット寄りの投稿です⚠️

今回は、外部サイト「Progate」にて学習した内容を吐き出します。

index

  • Javaってなんぞ
  • 試しにコードを書いてみた
  • データ型
  • Javaでの変数

Javaってなんぞ

世界中にたくさんの開発者がいる有名な言語のなかの一つ!
大規模システム、Webアプリケーション、スマートフォンアプリなど、Javaはあなたの周りの様々な場所で使われている。

そして・・・
⚠️JavaとJavaScriptは全くの無関係である⚠️

試しにコードを書いてみた

やりたいこと:「文字列」を、コンソールに出力させたい。

Main.java
class Main { // ここにクラスを記述
  public static void main(String[] args) { // ここにメゾットを記述
    System.out.println("Hello,World!"); // ここに処理を記述
  }
}
  • 文字列は「"(ダブルクォーテーション)」で囲んであげる。
  • 「;(セミコロン)」は忘れがちになる!
  • System.out.println()というのは、「()の中身を出力(表示)せよ」という「命令」
    • print「l」nは小文字のエルだぞ
  • クラス>メゾット>処理の構成で記述するようだ

文字列の連結

やりたいこと:「文字列」を、文字列の連結させてコンソールに出力させたい。

Main.java
class Main {
  public static void main(String[] args) {
    System.out.println("Hello" + "World!");

    System.out.println("5" + "3"); //結果:53
  }
}
  • 後述の「数値」と「文字列」の違いをイメージすると良い!

数値(四則演算処理)

やりたいこと:「数値」を、コンソールに出力させたい。

Main.java
class Main { // ここにクラスを記述
  public static void main(String[] args) { // ここにメゾットを記述
    System.out.println(3); // ここに処理を記述
    System.out.println(1 + 2);
    System.out.println(5 - 2);
  }
}
  • 数値はそのままでOK。
  • 四則演算もできちゃう!
  • 今回の記述だと、全て「3」だが処理内容が違うぞ。

やりたいこと:いろんな計算結果を出力させたい。

Main.java
class Main { // ここにクラスを記述
  public static void main(String[] args) { // ここにメゾットを記述
    System.out.println(5 * 2); // 結果:10
    System.out.println(6 / 2); // 結果:3
    System.out.println(8 % 5); // 結果:3
  }
}
  • 「*」は掛け算、「/」は割り算
  • 「%」は割り算の余りを計算できるぞ!(今回の場合は「1余り3」なので出力は「3」となる)
  • 四則演算処理を見やすくする為に、半角スペースを入れると良き♪

文字列と数値の違いがわかる例

System.out.println(3); // 結果:3
System.out.println("1 + 2"); // 結果:1 + 2

データ型

String型とint(integer)型

Ruby on Railsで開発をしていた時にも、テーブル設計時にお世話になりました、String型とint(integer)型についてです。

String型 int型
"Hello,World"(文字列) 3(整数)
"イロハ" 1995
  • String型
    • 文字の並び
  • int(integer)型
    • 整数
    • (iは小文字)

後日、様々なデータ型をまとめた記事を投稿予定です。

Javaでの変数

データのやりとりをやりやすくするための、定義できる書類BOXや名札みたいな感じ。
投稿者の過去記事に、rubyでの簡単な説明があるので、よければ読んでみてね!
Rubyについて(基礎)

やりたいこと:Javaで変数を定義させ、定義した変数から値を取り出したい

①変数にいれる値のデータ型を指定
②変数の名前を指定
③値を代入する

スクリーンショット 2020-03-16 17.28.14.png

「=」は「等しい」を意味するのではない!

あとは変数から値を取り出せばOK!!

Main.java
class Main {
  public static void main(String[] args) {

    int a; // 整数として「a」の名札がついた箱を用意
    a = 10; // その箱に「10」を代入
    System.out.println(a); // 結果:10

    String b; // 整数として「b」の名札がついた箱を用意
    b = "hello,world!" // その箱に「"hello,world!"」を代入
    System.out.println(b); // 結果:"hello,world!"

  }
}

やりたいこと:変数の初期化をしてみる

変数の定義と代入を一行で表現できる!
上記のコードにある変数を初期化してみる。

Main.java
class Main {
  public static void main(String[] args) {

    int a = 10; // 変数の初期化
    System.out.println(a); // 結果:10

    String b = "hello,world!" // 変数の初期化
    System.out.println(b); // 結果:"hello,world!"

  }
}

✨見ていて気持ちいいコードになった✨

やりたいこと:「int型の計算」と「String型の連結」

Main.java
class Main {
  public static void main(String[] args) {

    // 変数を用いたint型の計算
    int hoge1 = 100;
    System.out.println(hoge1 + 200); // 結果:300

    int hoge2 = 10;
    System.out.println(hoge1 + hoge2); // 結果:110


    // 変数を用いたString型の連結
    String hello = "こんにちは!";
    System.out.println(hello + "hoge1"); // 結果:こんにちは!hoge1

    String name = "hoge2";
    System.out.println(hello + name); // 結果:こんにちは!hoge2

  }
}

変数の上書きと注意点

中に入っている値を更新すること(上書き)も可能。
ただし、注意として、「同じ処理内で同一名の変数を定義できない」

やりたいこと:変数の上書きとエラーの出る記述

String型の変数を呼び出す時も、変数名に「"」は記述しない。

Main.java
class Main {
  public static void main(String[] args) {

    // 変数を用いたint型の計算
    int hoge1 = 100;
    System.out.println(hoge1 + 200); // 結果:300

    hoge1 = 400; // 上書き
    System.out.println(hoge1 + 200); // 結果:600

    int hoge1 = 700; //結果を900にしたいが「int」を記述
    System.out.println(hoge1 + 200); // 結果:同じ処理内で同一名の変数を定義しているのでエラーが出る

  }
}

ここで地味なポイント
コードは基本的に上から処理されていく。

Main.java
class Main {
  public static void main(String[] args) {

    int number = 10;
    String text = "Ruby";
    System.out.println(number); // ここで一度「10」が出力される
    System.out.println(text); // ここで一度「Ruby」が出力される

    number = 5;
    text = "Java";
    System.out.println(number); // 上書きされた「5」が出力される
    System.out.println(text); // 上書きされた「Java」が出力される

  }
}

結果

10
Ruby
5
Java

自己代入

やりたいこと:変数xに10が入っている時、40を足して、変数xを上書きしたい

代入の「=」は等しいわけではないことがPOINT!

Main.java
class Main {
  public static void main(String[] args) {

    int number = 10;
    System.out.println(number); // 結果:10

    number = number + 40; // number = 10に40を足して、numberを上書き
    System.out.println(number); // 結果:50

  }
}

上記の記述を省略してみる。

Main.java
class Main {
  public static void main(String[] args) {

    int number = 10;
    System.out.println(number); // 結果:10

    number += 40; // 省略記述
    System.out.println(number); // 結果:50

  }
}

やりたいこと:自己代入の省略記述

int number = 10;

number += 40;
number -= 40;
number *= 40;
number /= 40;
number %= 40;

//変数に「1」を足す、もしくは「1」を引く場合はさらに省略できる
number++;
number--;

変数の命名規則(Javaの場合)

変数は自由に命名することができる・・・が!!!
一定のルールと理由がある。

GOOD?

理由
date 英単語を用いている
userName 2語以上の場合は大文字で区切る(キャメルケース)

BAD?

理由
1name 数字開始はエラーが出るぞ
user_name アンダーバーは望ましくない(スネークケース)
ohisama ローマ字もわかりにくいので良くない
名前 日本語はよろしくない(プログラミングでは日本語良くないぞ...)

誰が見てもわかりやすく、短い変数名を定義してあげよう!!
命名に悩んだら下記参照。

codic

小数点について

やりたいこと:小数点を用いて計算をしてみたい

小数点のデータ型は「double型」。

Main.java
class Main {
  public static void main(String[] args) {

    double number1 = 10.0;
    double number2 = 3.3;

    System.out.println(number1 + number2); // 結果:13.3
    System.out.println(number1 - number2); // 結果:6.7

  }
}

やりたいこと:自動型変換を用いて整数と小数点で計算したい

Main.java
class Main {
  public static void main(String[] args) {

    System.out.println(5 / 2.0); // 結果:2.5

    //因みに
    System.out.println(5 / 2); // 結果:2
    System.out.println(5.0 / 2.0); // 結果:2.5

  }
}
  • 「+」などの操作は、本来であれば同じデータ型同士でないとできない。
  • 「自動型変換」で対応してみる。(手動もあるようだ)
  • 因みに「String型」と「int型」で「+」すると、String型で出力される。
    • このことから、値をわかりやすく表現できるデータ型に自動変換してくれていることがわかる!!
    • 整数の「5」は、文字列でも"5"の表現ができるし、小数点でも5.0の表現ができるから。
  • また、int型の計算結果とdouble型の計算結果で変化することがあるので注意した方が良いぞ。

やりたいこと:強制型変換を用いて整数と小数点で計算したい

Main.java
class Main {
  public static void main(String[] args) {

    int x = 13;
    int y = 4;
    System.out.println(x / y); // 結果:3(3.25ではない)

    System.out.println((double)x / y); // 結果:3.25

  }
}
  • 変数xのint型を「double型」に強制変換させたので、自動型変換が発火して、変数yも「double型」になったぞ!!

追加あれば、どんどん更新していきます。

駆け出しとして頑張ります。

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

JdbcTemplateで@OneToMany的な振る舞いをさせる

TL;DR

ORMにを頼らずNamedParameterJdbcTemplateとRowMapperを組み合わせて結果を取得するシステムでOneToManyなデータの持ち方を実現したい場合はResultSetExtractorを実装したクラスを作ればいいが、構造が複雑になりがちなので新しいシステムを作る時は素直にJPA等に頼ったほうが良い

問題点

Spring JDBCには NamedParameterJdbcTemplate等のクラスをDIすることで気軽にクエリを発行できる仕組みがある。

@Repository
public class HogeRepository {

    @Resource
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    @Override
    public HogeWithPiyo findAll() {
        return namedParameterJdbcTemplate.query("SELECT * FROM hoge LEFT OUTER JOIN piyo USING(hoge_id)",
                new BeanPropertyRowMapper<>(HogeWithPiyo.class));
    }
}
public class HogeWithPiyo {
    Integer hogeId;
    List<Piyo> piyoList;

    public Integer getHogeId() {
        return hogeId;
    }

    public void setHogeId(Integer hogeId) {
        this.hogeId = hogeId;
    }

    public List<Piyo> getPiyoList() {
        return piyoList;
    }

    public void setPiyoList(List<Piyo> piyoList) {
        this.piyoList = piyoList;
    }

    public class Piyo {
        Integer piyoId;

        public Integer getPiyoId() {
            return piyoId;
        }

        public void setPiyoId(Integer piyoId) {
            this.piyoId = piyoId;
        }
    }

問題

例えば、 hoge , piyo テーブルに対して結果セットが次のように帰ってくるとする。

hoge.hoge_id piyo.hoge_id piyo.piyo_id
1 1 1
1 1 2

この時、BeanPropertyRowMapper等のRowMapper系でデータを取得するしかないシステムで Hogeリストとそれに紐づくPiyoリスト を取得するために、ビジネスロジックでPiyoを取得するためにクエリを発行しているがそもそものHogeの件数自体がめちゃくちゃ多いので件数分クエリを発行するとスループットがめちゃくちゃ遅い、という問題があったと仮定する。BeanPropertyRowMapperを使うとどうしても1行ずつ読んでしまうため、結果は2行のリストになってしまう。

public class HogeExtractor<T extends Hoge> implements ResultSetExtractor<List<T>> {
    BeanPropertyRowMapper<T> parentRowMapper;
    BeanPropertyRowMapper<Piyo> childRowMapper = new BeanPropertyRowMapper<>(Piyo.class);
    String uniqueColumn = "hoge_id";

    @SafeVarargs
    public HogeExtractor(T... e) {
        @SuppressWarnings("unchecked")
        Class<T> type = (Class<T>) e.getClass().getComponentType();
        this.parentRowMapper = new BeanPropertyRowMapper<>(type);
    }

    @Override
    public List<T> extractData(ResultSet rs) throws SQLException, DataAccessException {
        List<T> rows = new ArrayList<>(); // 結果のリスト
        List<Object> childs = null;
        Long key = null;
        T current = null;
        int parentIdx = 0;
        int childIdx = 0;
        while (rs.next()) {
            if (current == null || !key.equals(rs.getLong(uniqueColumn))) {
                // 親テーブルのデータが無い時やJOINするカラムの値が変わったら親テーブルのオブジェクトを作る
                key = rs.getLong(uniqueColumn);
                current = parentRowMapper.mapRow(rs, parentIdx++);
                childs = new ArrayList<>();
                current.setPiyo(childs);
                childIdx = 0;
                rows.add(current);
            }
            // 毎行、小テーブルの要素を親テーブルのオブジェクトに追加する
            childs.add(childRowMapper.mapRow(rs, itemIdx++));
        }
        return rows;
    }
}
    @Override
    public HogeWithPiyo findAll() {
        return namedParameterJdbcTemplate.query(
                "SELECT * FROM hoge LEFT OUTER JOIN piyo USING(hoge_id)",
                new HogeExtractor<>());
    }

そんな時はこんな感じでHoge専用のResultSetExtractorを実装するクラスを無理やり作っちゃう。

問題点

  • 取得したいオブジェクトごとにResultSetExtractorを実装しなきゃいけないのでしんどい
    • 継承したクラスならジェネリクス使えばなんとかなる
    • リストの要素を追加するメソッド名をリフレクションで解決すればなんとなく1つのクラスで済む気がするけどしんどい
    • OneToManyしたいフィールドが2箇所以上ある時とかもしんどい
    • JOINするキーが2個以上ある時もしんどい

結論

  • 既存の仕組みを守りつつ最小のコストでスループットを上げるためのテクニックとしてならアリなんじゃないかな……と思っています
  • ちゃんとテストが書ける前提で負債を最小限にコントロールできるなら良いと思うけど、どちらかというと負債を作る行為
    • 実運用した事は無いのでベンチマークはありません

参考にしました

java - ParameterizedRowMapper That Maps Object List to Object - Stack Overflow

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

オブジェクト指向プログラミング(素人が浅い知識でまとめさせていただきました)

クラスとインスタンス(オブジェクト指向)

クラスはくくり、インスタンスはくくりのルールで定義された物。
イメージは車というクラスからプリウスというインスタンスを生み出すって感じ。

クラス内でやる事

インスタンスが持っておくべき変数や関数を定義する。
またインスタンス作成時にどのように初期設定をするか書かれる。
→コンストラクタ関数を定義し、引数をもらって初期値を作れるようにする。
→インスタンス作成時に引数として値を渡して、各インスタンスに変数の初期値を入れる。

class Person {

 private Stirng name; //ここではインスタンスに必要な変数が用意されている。

 Person(String name, ...) { ////()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる...
 }

 public void detailData() { //ここでインスタンスに必要な関数が書かれる
 }
}
補足

・クラス内で書かれているthisは、作られたこのインスタンス自身という意味になる。

private Stirng name; //このクラスでnameという変数を定義

public void nameInput(String input) { //文字列の変数inputを引数にnameInputを実行すると
 this.name = input;  //このインスタンスの変数nameは受け取ったinputと同じ値になる。
}

・インスタンスのための変数(インスタンスフィード)もあるがクラスのための変数(クラスフィード)も存在する。クラスフィードはクラスの中の変数のため、何かあるたび(例 インスタンスが作られるたび)に変更されるようにしたりする。

Person.Java
class Person {

 private static int count;  //これはクラスのための変数
 private Stirng name; //これはインスタンスのための変数

 Person(String name, ...) { ////インスタンス作成のための初期設定
 Person.count ++; //これでPersonインスタンスが作成されるたびに、Personクラスのcountが+1される
 }
} 

初期値設定、コンストラクタメソッド

これはインスタンス作成時、引数を受け取り初期値設定するのに必要な関数。
しかし初期値設定の引数入力パターンは1種類ではない。例えばmiddle nameがある人は、普通の人より入力する項目が増える。そのためmiddle nameがある人用の入力関数も必要。

引数の入力パターンによって、コンストラクタメソッドもパターンを設ける。
overloadという方法で、複数の引数入力パターンに対応した同名のコンストラクタメソッドを作ることができる。

Person.Java
class Person {

 private Stirng firstname; //ここではインスタンスに必要な変数が用意されている。
 private Stirng lastname;
 private Stirng middlename;

 Person(String firstname, String lastname) { //firstnameとlastnameだけの人の初期設定
 this.firstname = firstname; //引数に使用された文字列であるfirstnameの値ががこのインスタンスのfirstnameになる。左と右で示しているfirstnameは違う。
 this.lastname = lastname;
 }
//同じ名前の関数を書く
 Person(String firstname, String middlename, String lastname) { //firstnameとlastnameとmiddlenameの人の初期設定
 this.firstname = firstname;
 this.lastname = lastname;
 this.middlename = middlename;
 }
}

overloadとoverride

overload

overloadは同名の関数を複数作る時、名前は同名でも引数の入力パターンは異なるため、そのパターンから最適な関数を選択してくれること。主に初期設定の関数に行われることが多い。

初期設定関数作成時、オーバーロードで作った関数同士の処理内容が重複している時がある。

例えば初期設定関数である関数Aがある。
関数Aの中で、this(name等)を実装すると
関数Aと同名の初期設定関数を探し、その中でnameの処理をしている関数Bを探してきて、nameの処理を行ってくれる。以下のコードでは一つ前のコードをきれいにしている。

Person.Java
class Person {

 private Stirng firstname; //ここではインスタンスに必要な変数が用意されている。
 private Stirng lastname;
 private Stirng middlename;

 Person(String firstname, String lastname) { 
 this.firstname = firstname; //引数に使用された文字列であるfirstnameの値ががこのインスタンスのfirstnameになる。左と右で示しているfirstnameは違う。
 this.lastname = lastname;
 }

 Person(String firstname, String middlename, String lastname) { //firstnameとlastnameとmiddlenameの人の初期設定
 this(firstname, lastname); //ここが変わった。同名関数に存在するfirst,lastnameの処理を行った。
 this.middlename = middlename;
 }
}
override

overrideは親クラスと同名の関数を定義する時に、子クラスの内容を上書きすること。

普通に親クラスの関数と同名の関数を子クラスで定義してしまうと、その関数は親クラスの時の処理内容を引き継がず子クラスの処理内容だけを実行することになる。

親クラスの時の処理内容も子クラスの関数に引き継ぎたいならば、子クラスの関数の中でsuper.メソッド名をしてやらないといけない。

Person.Java
class Person {
 .
 .
 .

 public void detailData() { //インスタンスの詳細な情報を表示する関数
 System.out.println("名前は" + this.name); //インスタンスの変数を表示
 System.out.println("年齢は" + this.age);
 }
} 
Child.Java
class Child extends Person { //ChildクラスはPersonクラスを継承
 .
 .
 .

 public void detailData() { //親であるPersonクラスに同名の関数あり
 super.detailData(); //これで親であるPersonクラスにあるdetailData()の処理内容を引き継げる
 //ここからchildクラス特有の処理内容を書く
 .
 .
 }
} 

カプセル化

カプセル化とは無駄を省いてわかりやすい物を作ることだそうです(詳しくは理解できていません。)

カプセル化の中の一つの機能をここでは書かせていただきます。

それはあるクラスで定義した変数や関数へのアクセス回路を制限する事(セキュリティーのため?)

変数や関数定義の時に、直前にこれをつけるとアクセス制限ができる。
・public→外部のクラスからのアクセスが可能。
・private→外部のクラスからのアクセスが不可能。その変数、関数を定義したクラスの内部にあるメソッドを使ったアクセスなら可能。
・protected→基本的にprivateと同様に外部のクラスからのアクセスが不可能。しかしその変数、関数を定義したクラスを継承した子クラスならばアクセス可能。

お作法

・インスタンスの情報となるクラスの変数はprivateにして、インスタンスの命令となるクラスの関数はpublicにして外部からアクセスできるようにする。変数を取得、変更するような関数(例 get~()やset~()とか)をクラス内で定義してやる事で、インスタンスの変数の取得、変更を可能にする。

House.Java
class House{

 private Stirng name; //ここでは必要な変数が用意されている。
 private Person owner; //別のクラスPersonのインスタンスをownerという変数に格納できる。

 House(String name, ...) { //インスタンス初期設定
 }

//ここから関数が書かれていく。
 public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
  this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
 }

 public getOwner() {
  return this.owner; //このインスタンスのowner情報を返す。情報を返すだけなので単体では何も表示されない
 }
}

クラスの継承(親クラス、子クラス)

extendを使って親クラスを継承した子クラスを作ることができる。
また子クラス内でインスタンスに対する変数の初期値や初期関数の設定を変更できる。
クラスの継承はいくらでもできるのでクラスのクラスのクラスの...ができる。

お作法

・クラス毎にファイルは分割して読みやすくするのが基本。
・継承とは異なるかもしれないがimportをすると外部のライブラリーやパッケージを読み込める。

抽象クラス

様々な子クラスで必ず必要だけれど、クラスによって処理内容が異なる関数がある場合。

親クラスで抽象メソッド(あえて何も処理が書かれていない関数)を定義する。
すると子クラスでは必ずoverrideしてその関数定義をしなければいけない。
また抽象メソッドを持つクラスは抽象クラスと呼ばれ、そのクラス自身のインスタンスは作成できない。

多重継承

javaでは禁止されているが、子クラスが複数の親クラスを継承することを多重継承と呼ぶ

クラス間での交わり

Aクラス内で別のBクラスのインスタンスを変数として定義することができる。例えばHouseクラス内で、所有者を示す変数ownerとしてPersonクラスのインスタンスを紐づけることができる。

House.Java
class House{

 private Stirng name; //ここでは必要な変数が用意されている。
 private Person owner; //別のクラスPersonのインスタンスをownerという変数に格納できる。

 House(String name, ...) { //()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる...
 }

//ここから関数が書かれていく。
 public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
  this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
 }

 public getOwner() {
  return this.owner; //このインスタンスのowner情報を返す。情報を返すだけなので単体では何も表示されない
 }
}
Person.Java
class Person {
 private Stirng name; //ここでは必要な変数が用意されている。

 Person(String name, ...) { //()内に必要な引数を入れられる。インスタンス作成のための初期設定がいろいろ書かれる...
 }
//ここから関数が書かれていく。
 public void detailData() { //ここでインスタンスの詳細な情報を表示するようにする
 }
}
Main.Java
house1 = New House("Aハウス", ...);
person1 = New Person("山本", ...); //引数を受け取って、それぞれのクラスのインスタンスを作成

house1.setOwner(person1); //これでhouse1のownerがperson1の値と同じになる。
house1.getOwner().detailData(); //これでhouse1のownerの詳細な情報が得られる。
//getOwnerでownerの情報をreturnする(単体では何も表示されない)。その後にPersonで定義した関数を使うことで、ownerの情報を知れる。
変数にインスタンスを入れるということ

newでインスタンスを作る。
引数の値を受け取って、メモリにそのインスタンスのための領域をを確保して、そこにインスタンスの情報を置いておく。(情報は二進数で書かれる)

そいつを変数に代入する。
インスタンスのための変数を作ると、その変数のための領域がメモリ内で確保される。その領域にはインスタンスの領域がどこにあるかという情報だけを残す。

多態性

Aクラスを継承しているBクラスのインスタンスは、Aクラスのインスタンスとしても扱えるしBクラスのインスタンスとしても扱えるぞって感じかな?。

例えば同じ関数処理を別のクラスのインスタンスに実行する場合、それぞれのクラス毎に関数を設定してしまいがち。しかしその異なったクラスが共通の親クラスを継承している場合、その親クラスに対する関数処理を書くだけで良くなる。これにより、その親クラスを継承する子クラスが増えても関数処理を書かなくて良くなる。

やりがちなコード
House.Java
class House {
 .
 .
 .

 public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
  this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
 }
}
Villa.Java
class Villa { //villaは別荘という意味
 .
 .
 .

 public setOwner(Person person) { //Personクラスのインスタンスである変数personを引数にする
  this.owner = person; //このインスタンスのownerは入力された変数personと同じになる。
 }
}
Person.Java
class Person { 
 .
 .
 .

 public void buy(House house) { //引数としてHouseクラスのインスタンスである変数houseを受け取れば
  house.setOwner(this); //変数houseのownerには、このインスタンスがなる。
 }

 public void buy(Villa villa) { //引数としてVillaクラスのインスタンスである変数villaを受け取れば
  villa.setOwner(this); //変数villaのownerには、このインスタンスがなる。
 }
}
多態性を活かしたコード

(Houseクラスとvillaクラスの共通の親クラスはBuildingクラス)

Person.Java
class Person { 
 .
 .
 .

 public void buy(Building building) { //引数としてBuildingクラスのインスタンスである変数buildingを受け取れば
  building.setOwner(this); //変数buildingのownerには、このインスタンスがなる。
  //親クラスに対する関数定義をしてやると子クラスに対する定義が不要になる
 }
}

参考にさせていただいた参考文献

オブジェクト指向と10年戦ってわかったこと

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

【初心者向け】今度こそわかるDI〜DIの基礎とSpringにおけるDI〜

はじめに

DIとはDependency Injectionの略であり、日本語では「依存性の注入」と訳されることが多いです。

新卒だったころにSpringに触れた私は、初めてこの概念を聞いた時に頭に浮かんだのは、はてなマークでした。DIの概念やDIコンテナ、Springにおけるアノテーションとの結びつきがピンとこなかったのです。

今回は初心者だったころの自分にあてて、DIの概念およびSpringにおけるDIについて自分なりに整理しつつ、簡単にまとめていきたいと思います。

依存性(Dependency)とは

依存(性)とは何かということはソースコードで示したほうがわかりやすいと思います。

Main.java
public class Main {
    public int hoge() {
        var sub = new Sub();
        return sub.calculate();
    }
}

Mainクラスのhogeメソッド内で、Subクラスのインスタンスを生成し、利用しています。
このようなMainクラスがあったとき、「MainクラスはSubクラスに依存している」と表現します。

依存に関しては比較的わかりやすいと思います。

注入(Injection)とは

先程の例では、Subクラスのインスタンスを内部で生成していましたが、インスタンスを外部で生成し、それを用いるクラスに渡してあげることを注入と呼びます。

ここでは、コンストラクタを用いた例(コンストラクタインジェクション)を示します。

Main.java
public class Main {

    private Sub sub;

    public int hoge() {
        return sub.calculate();
    }

    // コンストラクタインジェクション
    public Main(Sub sub) {
        this.sub = sub;
    }
}

先程はhogeメソッド内でSubクラスのインスタンスを生成していましたが、今回の例では、インスタンスをコンストラクタから受け取るようになっています。

このようにすることで、Mainクラスを実際に用いる際は、

var sub = new Sub();
var main = new Main(sub);

といった形で、SubクラスのインスタンスをMainクラスの外から渡す形になります。

これによるメリットは後述します。

DIコンテナとは

コンテナと聞くと現代ではDockerを連想するかもしれませんが、DIコンテナにおけるコンテナはDockerなどの文脈で用いられるコンテナと異なります。

誤解を恐れず一言で書くとすると、DIコンテナは「注入するインスタンスを管理してくれる入れ物」の役割をしてくれます。

先程のようにDIコンテナを用いずに、Mainクラスを用いようとすると、用いるたびにSubクラスのインスタンスが必要になります。

DIコンテナを用いると、このインスタンスを管理してくれるので、いちいちインスタンスを生成する必要がなくなります。

SpringにおけるDIコンテナ

Springではアノテーションでクラスを指定することで、そのクラスのインスタンスをDIコンテナで管理する対象として指定することができます。

具体的には以下のようなアノテーションです。

  • @Controller
  • @Service
  • @Repository
  • @Bean

ちなみに、DIコンテナで管理されているインスタンスはApplicationContextクラスのgetBeanメソッドを用いることで、取得することができます。

また、SpringではDIコンテナがインスタンスをいつまで管理するかという、スコープを設定することができます。

設定できるスコープは下記のとおりで、設定する際はScopeアノテーションを用います。

  • singleton
  • prototype
  • request
  • session
  • global session

Springのデフォルトはsingletonになっていますが、設定を変更することにより、例えばsession単位で、コンテナに管理されているインスタンスを破棄し、再設定するといったことが可能になります。

初心者がやりがちな注意点としては、singletonにもかかわらず、クラスに変化しうるフィールドを持たせてしまうことです。以下に例を示します。

hogeService
@Service
public class hogeService {

    private String result;

    public int fuga(String suffix) {
        result = "test" + suffix;        
        return result;
    }
}

hogeServiceにはServiceアノテーションが付与されており、特にスコープを設定していないので、スコープがsingletonになります。

そのため、resultフィールドを別のセッションで変更してしまう危険性があり、スレッドセーフでなくなってしまいます。

下記のようにクラスフィールドではなく、変数にしてあげることで、スレッドセーフな実装になります。

hogeService
@Service
public class hogeService {

    public int fuga(String suffix) {
        String result = "test" + suffix;        
        return result;
    }
}

SpringにおけるDI

Springでは、Autowiredアノテーションを用いることで、DIすることができます。

インジェクションの方法には先程のコンストラクタインジェクションも含め、下記の3つがあります。

  • フィールドインジェクション
  • セッターインジェクション
  • コンストラクタインジェクション

それぞれ具体的には、このようになります。

Main.java
public class Main {

    // フィールドインジェクション
    @Autowired
    private Sub sub;

    public int hoge() {
        return sub.calculate();
    }
}
Main.java
public class Main {

    private Sub sub;

    public int hoge() {
        return sub.calculate();
    }

    // セッターインジェクション
    @Autowired
    public setSub(Sub sub) {
        this.sub = sub;
    }
}
Main.java
public class Main {

    private Sub sub;

    public int hoge() {
        return sub.calculate();
    }

    // コンストラクタインジェクション
    @Autowired
    public Main(Sub sub) {
        this.sub = sub;
    }
}

Autowiredアノテーションを付与することで、フィールド、メソッドおよびコンストラクタの引数にDIコンテナからインスタンスを注入してくれます。

ちなみに自分がインジェクションする際は、こちらの記事にあるように、ライブラリのLombokのRequiredArgsConstructorアノテーションを用いて書いています。

DIのメリット

よく言われるメリットとして、単体テストが書きやすくなることが挙げられますが、
これはそのとおりだと私も思います。

冒頭の例をもう一度示します。

Main.java
public class Main {
    public int hoge() {
        var sub = new Sub();
        return sub.calculate();
    }
}

仮にこのSubクラスのcalculateメソッドが、DBにアクセスする必要があったとすると、Mainクラスの単体テストは非常に難しくなります。

しかし、DIによって注入するインスタンスをモックにすることで、テストすることができます。
ちなみに私はモックを用いる際はMockitoを用いています。

まとめ

最後に、改めてDIおよびDIコンテナについて一言でまとめると下記のようになります。

DI:あるクラスに対して依存しているクラスのインスタンスを外部から渡してあげること
DIコンテナ:注入するインスタンスを管理してくれる入れ物

これらを用いることで、テストが書きやすくなったり、スコープの管理がしやすくなったりというメリットがあります。

内容は以上です。本記事が少しでもお役に立てば幸いです。
最後までお読み頂き、ありがとうございました。

参考文献

公式ドキュメント
Core Technologies

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

デプロイ失敗

エラー内容

   [INFO] ------------------------------------------------------------------------
   [INFO] BUILD FAILURE
   [INFO] ------------------------------------------------------------------------
   [INFO] Total time:  2.360 s
   [INFO] Finished at: 2020-03-15T14:42:24Z
   [INFO] ------------------------------------------------------------------------
   [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project sf5-lounge: Compilation failure: Compilation failure: 
   [ERROR] /tmp/build_4af038e236977e9ba2fca9b5a329339f/src/main/java/dev/GetTweet.java:[129,43] diamond operator is not supported in -source 1.5
   [ERROR]   (use -source 7 or higher to enable diamond operator)
   [ERROR] /tmp/build_4af038e236977e9ba2fca9b5a329339f/src/main/java/dev/GetTweetTest.java:[111,44] diamond operator is not supported in -source 1.5
   [ERROR]   (use -source 7 or higher to enable diamond operator)

原因

pom.xmlに記載不備

対処

pom.xmlに以下追加

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.1</version>
  <configuration>
    <source>1.7</source>
    <target>1.7</target>
  </configuration>
</plugin>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

twitter4j使用ソースをデプロイしたら失敗した

■twitter4jをデプロイしたら失敗した

■エラー

[INFO] -------------------------------------------------------------
   [ERROR] COMPILATION ERROR : 
   [INFO] -------------------------------------------------------------
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[9,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[10,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[11,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[12,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[13,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[14,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweetTest.java:[15,22] package twitter4j.conf does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[16,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[17,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[18,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[19,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[20,17] package twitter4j does not exist
   [ERROR] /tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[21,17] package twitter4j does not exist
   [ERROR]/tmp/build_a4ed9f5ff44d78cc39ef296374348ae6/src/main/java/dev/GetTweet.java:[22,22] package twitter4j.conf does not exist

■原因

Mavenが参照できてなかった

■対処

pom.xmlに以下を追加した。

<dependency>
    <groupId>org.twitter4j</groupId>
    <artifactId>twitter4j-stream</artifactId>
    <version>4.0.6</version>
</dependency>

■参考

Twitter4Jの紹介

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

java の int と Integer と BigInteger の違いや float や double を理解する

先日プログラミングコンテストの過去問を haskell でやった時に、べき乗をなんやかんやするロジックを書いた時に最終結果が12340000で欲しかったのに12340000.0になってしまって通りませんでした。恥ずかしい。

恥ずかしいけど、聞くは一時の恥聞かぬは一生の恥の解説とも言うし、知らないことは素直に学んで修めればなかったことにできるんです。できんの?

よくわからんけど、要するに曖昧なまま使ってたintみたいなものをきっちりまとめるぜって、そんな話。

かるーく背景

普段は java / DDD で契約管理のシステムを作ってるんだけど、扱う数字なんてたかだか数万の整数くらいなんだよね。ちょっとした円だとか契約の数を数えたりだとか、その程度。
なんと月の請求に日割りがないしね。驚きだね。

DDD の value object とかのおかげで、生のintとかを触ることもあんまりないしね。

なのでこの記事を書く直前の例えば java の理解度はだいたいこんな感じ。

byte? よくわからんけど怖い」
long? 長そ〜」
BigInteger? でかそ〜」
float? ふわふわ〜」
double? 何が倍なの〜?とうおるるるるるるるる」

ってくらいの理解度。これ大マジ。

(double のイタリア語が doppio ってことだけは知ってたんだよォォォ)

なのでそれくらいの人がまとめたんだよってことだけ了承してね!

haskell 版もよろしく

この記事の確認言語には java を使っています。

もともと haskell で確認してたのですが、いくつかの言語を確認した方がより理解が進むかと思って java でも試してみました。

python でも少し確認したりしたのですが、記事は本懐の haskell と仕事で使ってる java にしようかと思います。

というわけで、こっちもよろしく。→ haskell の Int と Integer の違いや Float や Double や Rational を理解する

まずは概要

プログラミングに入る前に、数学の話です。

数学といっても代数とか圏論とかの怖いやつはでてこないです。僕も怖いので。
だいたいが中学校くらいまでの話。

まずはこれを見てください。

スクリーンショット 2020-03-15 23.42.35.png

ざっくり説明します。

実数と虚数

普段目にする数はだいたい実数です。

対して虚数は便利なので発明された数ですが、現実には存在しません。
代表的なのが√-1もしくはそれをiと表現したものですね。わかりやすいですね。僕はよくわかりません。

虚数二乗して 0 未満の実数になる数で、実数それ以外と定義されます。

ここを細かく考える気はないので、「だいたい実数」くらいで大丈夫です。

有理数と無理数

実数の分類は真剣に考えます。

実数有理数無理数に大別されますが、有理数整数の比で表現できる数です

対してそれ以外の整数の比で表現できない数無理数と言います。

整数と有限小数と循環小数

いずれも有理数です。

整数は説明するまでもありませんね。
33/1と表現できるので有理数です。

有限小数は0.5の様な終わりのある小数です。
1/2の様に整数の比で表現できます。

対して0.333...0.142857142857142857...の様に同じ数字の繰り返しが無限に続く小数循環小数と言います。
これも1/31/7の様に整数の比で表現できます。

負の整数と正の整数とゼロと自然数

整数は一番馴染みがあるのであまり問題ないと思いますが、一応。

負の整数-1-5のことで、-5/1の形で表現できます。

00/1ですね。

正の整数についても同様です。

また、正の整数自然数とも言います。(0を含めるかは本記事では問いません)

小数と分数

少数分数についての補足です。

分数数の比で表現される数であり、一見有理数と同じな気がします。
が、有理数整数の比なので、分数の方が広い概念です。

例えば1/√2なんてのもありです。これは整数の比ではないので無理数です。
(だいたい 0.7 なので二乗するとだいたい0.50より大きいので虚数ではないですね。)

また、例えば無限小数というものがありますが、先ほどの図で言うと、有理数循環小数無理数無限小数です。
循環しているかしていないかの違いですね。

押さえておきたい英語

以上の要点を押さえつつ、我々プログラマは英単語も知らないと困るので、ざっくり整理しておきます。
(と言っても絵には英語も入ってますが。)

実数real number虚数imaginary numberです、イメージつきやすいですね。

あまり馴染みはないですが、有理数rational numberです。コピペで書いてると稀にぶち当たります。

ratio比率という意味なので変数名で使ったことがある人もいるのではないでしょうか。

また、絵にはないですが小数decimal分数fractionです。

プログラミングの世界へ ( java )

さっそく java でサンプルコードを見たいところですが、人間の世界とコンピュータの世界では大きく違うことがあります。

それは「メモリが有限」ということです。

どこにそれが関係するかと言うと、例えば「すげーでけー数」と「無限小数」です。

固定長整数

例えば java のintは 32bit 固定の整数です。

コンピュータのメモリには限界があるので、数値を 32 の0|1の範囲に限定して表現します。

多倍長整数

対して多倍長整数扱う数に応じて動的にメモリを確保する数値の表現方法です。

理論上は無限の数を扱うことができます。(もちろんコンピュータのメモリの許す限りですが。)

固定長整数と多倍長整数

みんな大好きオーバーフローはこの固定長整数が引き起こします。

たとえば java のbyteは 8bit 固定の整数です。
頭の 1bit を正負の符号に、残りを値の表現に使います。

0000|0000から1ずつ増加を始め、0111|1111から1000|0000になるところでオーバーフローし、
1111|1111から1|0000|0000になるところで 9bit 目が範囲外になり0000|0000として扱われます。
(見やすくするために 4 桁ごとに|を入れています。)

スクリーンショット 2020-03-16 0.36.23.png

対してBigInteger多倍長整数です。
こいつは桁あふれが起きそうになると、動的にメモリを確保するのでオーバーフローしません。

スクリーンショット 2020-03-16 2.43.29.png

(符号や値の保持については実装方法によるので、上図はイメージです。)

固定長整数はメモリ効率や性能に優れ、多倍長整数は精度に優れます。
これらは適材適所です。

浮動小数点

整数と同じく小数においても同様の考え方があります。

浮動小数点とは数値の表現方法の一つで、固定長仮数部指数部を持つ表現方法です。

ざっくり仮数部は値で指数部は桁を表していると考えれば大丈夫。

例えば二進数の0.00000101101 * 2^-8の様に表されます。

ただこれだと10.1 * 2^-7とかでも表現できちゃうので、IEEE754と言う規格で仮数部1.xにすると決まってます。なので1.01 * 2^-6です。
1.01e-6なんて書いたりもします。

コード書いていてたまに出るe入ってるやつはこれだね。怖かったけど克服したぞ。

仮数部指数部によって小数点を打つ位置が変わってくるので浮動小数点と言うのかな。
一方で対になる単語は固定小数点で、例えば整数がこれに含まれます。

java で確認

前置きが長くなりました。ここからはガシガシ java で確認していきます。

type 説明
byte, Byte 8bit 固定長整数
short, Short 16bit 固定長整数
int, Integer 32bit 固定長整数
long, Long 64bit 固定長整数
float, Float 単精度浮動小数点 ( 32bit )
double, Double 倍精度浮動小数点 ( 64bit )
BigInteger 多倍長整数
BigDeciaml 多倍長小数

以下のコードはSystem.out.printlnに相当するものは省略し、その行のコメントがその結果とします。

byte, short, int, long

たくさんあるけど恐れることはありません。

こいつらは全部固定長整数で、違いは表現できる精度しかありません。

Byte.MAX_VALUE;       // 127
Short.MAX_VALUE;      // 32767
Integer.MAX_VALUE;    // 2147483647
Long.MAX_VALUE;       // 9223372036854775807

例えばIntegerの上限値に+1すると、オーバーフローします。

Integer.MAX_VALUE + 1;    // -2147483648

相互変換

また当然ですが、精度の低い方から大きい方へのキャストは問題ありませんが、逆は正しく行えません。

short s = 20000;

(int) s;    // 20000
int i = 40000;

(short) i;    // -25536

ところで int と Integer の違い

もともとの趣旨とは離れるのですが、案外面白いのでせっかくと言うことで。

java のintはプリミティブ型で、Integerはクラス型と言います。

主な違いはすんごいざっくり言うと「intnullが許容されない」のと、「intList<T>とかのTになれない」くらいです。
精度とかについてはintIntegerに違いはありません。これ大事。

また java にはコンパイラがよしなに相互変換してくれる仕組みがあるので、大体の場合はあんまりどちらかを気にしなくても大丈夫です。

相互変換、の前にメモリの話

普段あんまり考えることはないかもしれませんが、スタック領域とヒープ領域について超ざっくり説明します。

例えばこの様なコードを書いた場合。
intIntegerの変数を区別しやすくするため、本記事では変数名の先頭に大文字を使います。)

Integer Ia = new Integer(1);

この場合、メモリはこんな感じになってます。

スクリーンショット 2020-03-16 1.31.34.png

newをするとスタック領域のIaという変数に何かが入ります。
なんとなくIaにはインスタンス自体が入ってる気がしますが、入ってるのは矢印だけです。恐ろしい言い方をするとポインタです。

作られたインスタンスはヒープ領域に入っています。

対してプリミティブ型のintはスタック領域にそのまま確保されます。

Integer Ia = new Integer(1);
Integer Ib = new Integer(1);
int ia = 1;
int ib = 1;

なのでこんなコードを書いた場合の絵は下のようになります。

スクリーンショット 2020-03-16 1.36.04.png

同一性と同値性

「java で比較に==を使うんじゃあねぇ」と怖い人に怒られたことがある人はいっぱいいると思いますが、せっかくなのでなんでなのか見てみましょう。

クラス型における同一性同じインスタンスかを、同値性同じ値かを比較することです。
前者は==で、後者はequalsによって行われます。また同値性は実装に依存します。
(例えば DDD の entity の比較では identity の一致のみで同値とみなす場合もあります。)

プリミティブ型の==はシンプルに値を比較します。

スクリーンショット 2020-03-16 1.40.54.png

なのでIa == Ib宛先の違う矢印なので false です。Ia.equals(Ib)宛先の値が同じなので true です。

例えるなら「A さんも B さんも 500 円玉を持っていて、物理的には違う硬貨だけど価値は同じ」と言った感じです。

auto boxing と auto unboxing

スタック領域とヒープ領域、比較について理解したところで、相互変換についてです。

int -> Integerboxing , 逆を unboxing と言います。
ラッパークラスの箱に入れるイメージかな。

以下のコードが実行できるのは auto boxing | auto unboxing によるものです。

Integer Ia = new Integer(1);
int ia = Ia;                   // unboxing

スクリーンショット 2020-03-16 1.47.07.png

int ib = 1;
Integer Ib = ib;               // boxing

スクリーンショット 2020-03-16 1.47.15.png

内部的にはスタック領域に値を持ってきたり、ヒープ領域にインスタンスを作って参照を得たりしています。
(実際には元の値は消えませんが、イメージしやすいので薄くしています。)

余談 落とし穴

さて、以下のコードはtruefalseどちらになるでしょうか。

int ia = 1;
int ib = 1;
Integer Ia = ia;
Integer Ib = ib;

Ia == Ib;    // true or false ?

auto boxing によってnewされるのでIaIbの矢印は違うはずです。上の絵でもそうなってます。

が、これtrueになります。

どうやら auto boxingInteger#valueOfで、auto unboxingInteger#intValueによって実現される様です。

Integer Ia = Integer.valueOf(ia);
Integer Ib = Integer.valueOf(ib);

で、肝心のInteger#valueOfですが、こんな実装になっています。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

どうやらよく使う-128~127はキャッシュされているみたいですね。なので上のコード例だとnewされないです。

こんなコードだとちゃんとfalseになります、理解は間違ってなかった様で安心だ。

int ia = 1000;
int ib = 1000;
Integer Ia = ia;
Integer Ib = ib;

Ia == Ib;    // false

あ、ちなみに内部では auto unboxingInteger#intValueを使うと言うことは、Ianullの場合に auto unboxing をするとNullPointerExceptionがでますよ。

int と Integer の違いまとめ

  • 精度は同じ
  • 比較はちょっと気をつけろよ
  • 相互変換は便利だけど完全に置き換えてくれるわけではないから気をつけろよ

ってことですね。

本記事においては以後intIntegerfloatFloatは精度の違いがないため、特に断りなくサンプルコードで都合が良い方を用います。

float, double

整数は押さえましたね。次は小数です。

doubleの何がなんだって思ってましたが、勉強すれば明瞭ですね。
floatは 32bit を使って、doubleは 64bit を使って値を表現するということでした。だから倍精度。

コンピュータのメモリが有限である以上無限小数を完全に表現することは不可能なので、誤差が出る前提で扱わなければなりません。

例えば十進数の0.01は二進数だと有限で表現することができません。
有限で表現できない以上どこかで諦めなければいけず、それを繰り返せば誤差が大きくなるのはなんとなく感覚で理解できますね。

で、どんな誤差が出るか、です。試してみましょう。

float f = 0;
for (int i = 0; i < 100; i++) {
    f += 0.01f;
}

double d = 0;
for (int i = 0; i < 100; i++) {
    d += 0.01d;
}

f;    // 0.99999934
d;    // 1.0000000000000007

doubleの方が1.0に近いですね。

相互変換

floatdoubleの変換も、shortintと同様に精度の高い方から低い方へ変換すると壊れます。

f;             // 0.99999934
d;             // 1.0000000000000007

(double) f;    // 0.9999993443489075
(float) d;     // 1.0

doubleからfloatにした場合は欠けてしまっていますね。

またそもそも有限なので、単純に以下の様な値で誤差が出ます。

10d / 3d;       // 3.3333333333333335
1.00000001f;    // 1.0

BigInteger, BigDecimal

お待たせしました、多倍長の奴らです。

こいつらは桁に応じて動的にメモリを確保するので、オーバーフローしないし誤差も出ません。なんかすごい。

さっそく試してみましょう。

BigDecimal

小数BigDecimalから試してみます。初っ端から気前よく巨大な整数を扱ってみましょう。

BigDecimal bd = new BigDecimal(Long.MAX_VALUE);

bd;                           // 9223372036854775807

bd.add(new BigDecimal(1));    // 9223372036854775808

longの上限に加算してもオーバーフローしてません。
もっと思い切りよく足しても全然大丈夫。

bd.add(bd);                   // 18446744073709551614

小数も加算できる。

bd.add(new BigDecimal(0.5));  // 9223372036854775807.5

本命?の小数の誤差はどうでしょうか。

BigDecimal bd = BigDecimal.ZERO;
BigDecimal x = new BigDecimal(0.01);
for (int i = 0; i < 100; i++) {
    bd = bd.add(x);
}

bd;    // 1.00000000000000002081668171172168513294309377670288085937500

double1.0000000000000007より精度が良いですね。(toString出来ているのはすげぇ頑張ってるからです。)

doubleで誤差が出た10d / 3dはどうでしょうか。

BigDecimal bd10 = new BigDecimal(10);
BigDecimal bd3  = new BigDecimal(3);

bd10.divide(bd3);    // ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

terminatingって単語は冒頭のベン図っぽいもので見ましたね、有限小数じゃあないって怒られてます。

誤差のでる値は誤差のあるまま持たせてくれないみたいですね。
切り捨てたり切り上げたりを明示しないとだめみたいです。

bd10.divide(bd3, RoundingMode.FLOOR) // 3

bd10.divide(bd3, RoundingMode.CEILING) // 4

BigInteger

こいつは簡単です。小数の扱えないBigDecimalです。

BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE);

bi;            // 9223372036854775807

bi.add(bi);    // 18446744073709551614

BigIntegerには0.5みたいな小数を渡せる生成メソッドがないので、BigDecimalと比べると「これだけ」です。

もう大丈夫。怖くない。

余談 破壊/非破壊

ところでなんとなく java の感覚だとaddすると破壊する気がしませんか?
List#addとかそうじゃん。

けど、addするたびにメモリを確保し直す可能性があることを理解していると、非破壊で毎度違うインスタンスを作ってるって考えやすいよね。
(実装方法によるので immutable の場合もあるけど mutable の場合もあるらしい。)

まとめ

ながーい記事になったけど、やってみて感じた java における数値表現の要点は3つだけだ!

  • byte, short, int, longの違いは精度だけ、それぞれ限界があるぜ
  • float, doubleも違いは精度だけ、小数は有限のメモリでは表現できないので誤差が前提なんだぜ
  • BigIntegerBigDecimalは(メモリがある限り)限界がない整数小数だぜ

これだけだ!intIntegerの違いは数値表現ってより java のお勉強としてがんばるんだ!

いやーそれにしても勉強になった。普段どれだけ適当にやってきたかを痛感した。

そしてこれを理解したらどうするかと言うと、やっぱドメインロジックとは切り離したいので value object を作って隠蔽するわけだ!
きっちり理解したので普段の業務(ドメイン実装)ではやっぱり使わないわけだ!なんというパラドクス!

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