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

Java言語で学ぶデザインパターン入門のはじめにから1章までのまとめ

お久しぶりです。白々です。
先日エンジニアの先輩にデザインパターンをご存知でない!?という冷静なツッコミを頂き、その後結城浩さんが書かれた「Java言語で学ぶデザインパターン入門」を渡されたので勉強することにしました。
ただ、本を一読しても覚えられないので覚書として記事にしようと思いました。
完走できるように頑張ります。
また、「Java言語で学ぶデザインパターン入門」には、サンプルプログラムも有りますが、著作権の都合上省かせて頂きます。御了承ください。

そもそもデザインパターンって?

「Java言語で学ぶデザインパターン入門」では、

デザインパターンは、日々書いているプログラムを新しい観点から見直し、再利用しやすく、機能拡張しやすいソフトウェアを作るための有益な技法なのです。

と記載が有りました。

この記事を読んでいる皆さんは、プログラムをたくさん書いていると似たようなコードを書くことは有りませんか?私はあります。なので良く部分的にコピペしながらコードを書いています。
このようにプログラムでよく使われるパターンってたかだか23パターンしか無いよね?と言った4人の人がいて、the Gang of FourやGoFと呼ばれているそうです。
GoFは、23個のパターンに名前をつけてカタログと整理した本が「Design Patterns: Elements of Reusable Object-Oriented Software」だそうです。
該当の本について触れているリンクは以下です。
https://en.wikipedia.org/wiki/Design_Patterns

グダグダ書きましたが、再利用性と機能拡張しやすいプログラムを書くために、プログラムの書き方のパターンを覚えようねってことだと思います。

第1部 デザインパターンに慣れる

第1章 Iterator —1つ1つ数え上げる

for文やwhile文のように、順序をつけて繰り返し処理するプログラムがあります。
このように繰り返し処理していくパターンをIteratorパターンと言うそうです。

なんでこんなパターンが必要なの?

「Java言語で学ぶデザインパターン入門」にも記載が有りましたが、配列を使って処理するようなものは、for文を使えばいいという意見もあると思います。
結論を言えば、順に実行するようなものでも抽象化しておけば、実行対象のデータ構造が変わった場合でも、順に実行するメソッドは変更しないで済むということです。

以下は、私が考えた例です。ただし、IteratorインタフェースとAggregateインタフェースは、「Java言語で学ぶデザインパターン入門」に記載があったものを使用しています。

例えば、大学で学生を管理しているとします。

「次を探すメソッド」と「次のオブジェクトを取得するメソッド」を宣言しているIteratorのインタフェースがあるとします。
また、このIteratorのインタフェースを宣言している、Aggregateインタフェースがあるものとします。

1つの学科には複数人の学生がいて、学生は、名前と学籍番号を持っているとします。
学生の学籍番号と名前を管理する学生クラスがあるとし、学科に所属している学生をList型で管理している学科クラスがあるとします。
学科クラスは、Aggregateインタフェースを実装したものとします。
学科クラスの中には、「List型の変数に入っている学生を取得するメソッド」と、「何人の学生がいるのかを取得できるメソッド」と、「学生を追加するメソッド」と、後述の「Iteratorクラスのインスタンスを返しているメソッド」があるものとします。

学科内の学生を学籍番号順に取得する学科Iteratorクラスがあるとします。
この学科Iteratorクラスは、Iteratorのインタフェースを実装したものとします。
この学科Iteratorクラスには、現在どこまで検索したかを管理する整数型の変数aと、「次の学籍番号を持っている学生がいるかどうかを判断するメソッド」と、「学生を取得し、変数aの値にプラス1するメソッド」があるものとします。
この学科Iteratorクラスが、まだ学生がいるかどうか判断するために、学科クラスが持っている「何人の学生がいるかを取得できるメソッド」の値と学科Iteratorクラスの変数aを比較して、変数aの値の方が小さいときまだ学生がいるものとします。
また、学科Iteratorクラスが、学生を取得するときは、学科クラスの「List型の変数に入っている学生を取得するメソッド」を使用するものとします。

このとき、学科クラスで学生の管理方法をList型からArrayList型に変えたとしても、学科Iteratorクラスの実装には影響が無いため、影響範囲が学科クラス内のみとなります。
このようなときにIteratorのデザインは必要とされています。

「Java言語で学ぶデザインパターン入門」には、
>抽象クラスやインターフェースを使ってプログラミングをする考え方を頭の隅に置いておいてください。

という記載が有ったので、忘れないようにします。

クラス図

例の内容をクラス図で現したものは以下のようになります。
iterator.png

このクラス図は、PlantUMLというもので記載しています。
私が、書いたPlantUMLのコードは以下のGitHubに記載がありますのでReadMeを読んでお使いください。
該当ファイル名は、iterator.txtです。
https://github.com/sirajirasajiki/design_pattern_uml

PlantUMLのインストールの時にお世話になったサイトは以下です。
https://qiita.com/kohashi/items/1d2c6e859eeac72ed926
PlantUMLを書くときにお世話になっているサイトは以下です。
https://qiita.com/ogomr/items/0b5c4de7f38fd1482a48

イテレータの種類

順序つけて繰り返し処理するものは、最初から順に処理する以外にも以下のような種類があります。
- 逆順
- 双方向
- 途中から最後まで処理する

まとめ

集合体をひとつづつ数えるパターンをIteratorというということが分かりました。

第1章感想

簡単な処理でも抽象化しておくことで、変更箇所が少なくて済むので楽だなと思いました。
また、クラスを日本語で表現しようと頑張りましたが、割と簡単な概念なのにすごく難しかったので、プログラムって偉大だなと思いました。
後日Pythonでそれっぽく書きます。

最後に

何か間違っているところがあれば、ご指摘いただけると嬉しいです!

更新履歴

2020/2/22 クラス図の項目を追加

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

SpringBoot-JPA-Hibernateのハマりメモ

この記事について

この記事は自分がアプリケーションの開発中にハマった点と、発見できた場合は原因、解決方法をまとめたものです。
この記事で問題としているものの、そもそもの利用法が間違っているケースや、解決方法にもっと良いものがあるかもしれません。

環境

  • Spring Boot 2.2.4
  • Hibernate 5.4.10
  • MySQL 8.0.16

遭遇した問題たち

PK以外をキーにした関連を作るとエラーが出る

データベース上では普通にあり得る話なのですが、Hibernateの実装上問題が発生することがあります。

ユースケース

@Entity
class Parent {
    @Id
    Long id;
    @NatulalId
    Integer key;
    String firstName;
    String lastName;
}

@Entity
class Child {
   @Id
   Long id;
   @ManyToOne
   @JoinColumn(name = "parent_key",referencedColumnName = "key")
   Parent parent;
}

上記のような、Hibernate自身が推すPKをサロゲートキー、@NaturalIdで自然キーを指定するような利用法のとき、JPA仕様の範囲内だとkeyがUniqueを指定されており、idの代わりに外部キーとして機能させたい場合です。
自然キーで関連付けを行うとchild.getParent()などの実行時にキャスト絡みでエラーが発生します。1

原因

公式issue tracker HHH-7668

解決

参照されるエンティティ(上記だとParent)をSirializableにする。
これを鑑みるに、Hibernateで使用するエンティティはすべてSirializableである方が無難かもしれません。

issue trackerをざっと眺めたところ、PKではないUniqueカラムで関連付けするとこれ以外の不具合にも遭遇する可能性がある模様です。

FetchType.LazyのエンティティがJacksonによるシリアライズ時にエラーを出す

原因

Jacksonがシリアライズを行う時にアクセスしているLazy指定のプロパティの実体はHibernateのプロキシであるため

解決

Hibernateのバージョンに対応するJacksonのプラグインを追加する。
初期状態でSpringBootはHibernateとJacksonに依存しているのになぜこれは入っていないんだろう。2
Stack Overflow:No serializer found for... って?
Stack OverFlow:SpringのJacksonでLazy属性を抑制する設定
みんな苦労してます。

Spring JPAエンティティのJacksonシリアライズがLazyもトリガする問題の解決
上記が非常にわかりやすく説明と解決方法を示してくれます。

というわけで念のためにプラグインのGitHubです。内容はあまり親切じゃないです。
ついでに、上記GitHubで紹介されているJacksonのアノテーションによる出力制御解説(無限ループ回避策)

カラム名やテーブル名関連のエラーが出る(追記・修正2020-02-21)

これは仕様の話ですね。バグではありません。

原因

デフォルト命名戦略はJPAアノテーションよりも後に処理される

単に優先順位の問題で、JPA(≒Hibernate)のテーブル名やカラム名を指定するアノテーションが使用されている場合、フィールド名からのカラム名変換が処理されていないため、エラーになります。

仕様の理解が足りていませんでした。
Hibernateでは、2段階の変換を経てDBのカラム名が生成されます。

  1. エンティティ名、フィールド名から論理名を生成する
  2. 論理名を物理名に変換する

現在のSpringのデフォルトでは、

  1. 論理名の生成

    • クラス名、フィールド名を論理名として取得する
    • @Column@Tableで指定されていれば、その文字列を論理名にする
  2. 物理名の生成

    • ピリオドをアンダースコアに置き換える
    • キャメルケースをスネークケースに置き換える

という設定になっています。

@Column@Tableで指定された文字列を論理名にするため、フィールドやクラス名をキャメルケースで指定しているなら、アノテーションで指定する名前もキャメルケースにしなくてはなりません。ここで物理名を(つまりスネークケース)を指定していると、"Table [table name] contains physical column name [column name] referred to by multiple logical column names:"(テーブルの物理カラム名に対応する論理名2個あるんですけど)とエラーが出ます。

解決

JPAのアノテーションでカラム名等を指定している場合、フィールドに@Columnでnameを指定しておかなくてはなりません。思ったより手抜きができません。3
仕様とエラーメッセージはきちんと読みましょう。
物理名の生成ですべて小文字にしてしまうため、大文字のカラム名やテーブル名を使用するために使用するのが、

apprication.yaml
spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

という設定(論理名をそのまま物理名にする)になります。
ただし、Springがデフォルトで使用しているSpringImplicitNamingStrategyはHibernateのデフォルトであるImplicitNamingStrategyJpaCompliantImplをほぼそのまま使用しているため、キャメルケースのプロパティ名を使用しているとそのままキャメルケースのカラム名になってしまいます。
stackoverfrow:Hibernateの命名戦略の実例を参考に最も近いものを選んでアノテーションで調整することになるかと思います。
Hibernateの戦略を流用するだけでは足りない場合には、戦略を自分で実装することになります。
参考:
PhysicalNamingStrategyで調整
ImplicitNamingStrategyJpaCompliantImplで調整

関連エンティティとキーの実値とをそれぞれ別プロパティに持ちたい

これは問題ではなくTipsですね。
キーの実値をプロパティに保持しつつ、必要になったときだけ関連をたどりたいという都合のいいエンティティの作り方です。

解決

@JoinColumnで関連エンティティをupdate,insert不可にする

@Entity
class Parent {
    @Id
    Long id;
    @NatulalId
    Integer key;
    String firstName;
    String lastName;
}

@Entity
class Child {
   @Id
   Long id;
   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "parent_key",referencedColumnName = "key",
        insertable = false, updatable = false)
   Parent parent;
   @Column(name="parent_key")
   Integer parentKey;
}

上記のようなクラスにすることでキーを直接保持しつつ親の参照が可能になり、子が親を更新してしまうのも防げます。
参照先を更新する際にはキーになっているフィールドの値を直接変更します(この例だとparentKey)。
マスターデータを参照するオブジェクトのとき、この形がとてもしっくりきます。


  1. 必ず発生するものでもないようで、そのあたりの疑問もたびたび投稿されているようです。 

  2. 記事を書いてる途中で気付きましたがSpring Data REST WebMVCには入ってますね…なんでや 

  3. Hibernate側のカスタマイズで解決できそうな気はします。 

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

Spring Bootについて学んだことのまとめ

はじめに

去年の11月~12月にSpringBootを初めて触った時に学んだことを備忘録として残します。
※あくまで備忘録です

対象読者

Spring Bootをこれから触ろうって方向けです。使ったことのある方には既知の内容になるかと思います。

作ったもの

受け取った入力値をもとに情報を返すだけの簡単な外部公開API。

使用した諸々

  • Java(1.8)
  • Spring Boot(2.1.0)
  • Spring Data JPA
  • Maven(3.5.3)
  • Lombok(1.18.2)

今回実装したアプリの構成

クラス構成.png

各種クラスの概要

クラス名 概要
RestControllerクラス APIのインターフェース部分を実装するクラス。
Formクラス クライアントからのリクエストの内容や返すレスポンスの情報を保持するクラス。
RestControllerAdviceクラス RestController共通の処理を実装するクラス。
Serviceクラス 業務ロジックを実装するクラス。
Repositoryクラス DBアクセスを行うクラス。
Entityクラス DBから取得したデータやDBに投入するデータを保持するクラス。

各種クラスの説明とサンプル

Mainクラス

@SpringBootApplicationを付与する。
※以下の3つのアノテーションを付与した状態と等価。

アノテーション 概要
@EnableAutoConfiguration Spring Bootの自動設定がオンになり、定義した依存関係に従って自動で各種設定を行ってくれる。
@ComponentScan コンポーネントスキャンが実行され、スキャン対象パッケージ内の@Componentが付与されたクラスが自動で読み込まれてDIコンテナで管理される。コンポーネントスキャンしたクラス同士は@Autowiredにより変数にセットすることが出来る。※後述のサンプルコード参照
@Configuration 付与することで、個別にBeanを登録したり、設定クラスを読み込むことが出来る。

@ComponentScan@EnableAutoConfigurationにより各種クラスの読み込み、設定が行われ、アプリケーションが実行される。
※デフォルトだと@ComponentScanが付与されたクラスと同階層のクラス、及びそれより下の階層のパッケージがスキャン対象になるため、@ComponentScanが付与されたクラスより上の階層のクラスは読み込まれない。

SampleApplication.java
@SpringBootApplication
public class SampleApplication {

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

    @Override
    protected SpringApplicationBuilder configure (final SpringApplicationBuilder builder) {
        return builder.sources (SampleApplication .class);
    }
}

RestControllerクラス

  • @RestControllerアノテーションを付与する。@ComponentScanの対象。
  • APIのインターフェース部分を実装するクラス。入力値の精査や各種コンポーネントの呼び出しを行い、クライアントにレスポンスを返す。
  • 各メソッドにHTTPメソッドやURLのマッピングを定義し、リクエストと呼び出されるメソッドのマッピングを行う。
  • 受け取ったリクエストや返すレスポンスを扱うためにどのFormクラスを使うかは各メソッドごとに定義する。
SampleController.java
/**
 * サンプルコントローラ。
 */
@RestController
@Validated
public class SampleController {

    @Autowired
    private SampleService service; //使用するServiceクラスを紐づける。

    @PostMapping (path = "/sample", //メソッドにマッピングするパスを指定する。
        consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, //受け取るリクエストのMediaTypeを指定する。
        produces = MediaType.APPLICATION_JSON_UTF8_VALUE) //返すレスポンスのMediaTypeを指定する。
    @CrossOrigin
    public ResponseEntity<SampleForm> sampleMethod (
        @RequestHeader (value = "sample_key") //ヘッダーから受け取る値のキー値を定義する。
        @NotEmpty (message = "sample_key is empty.") //Formを使用せずに精査する場合はここに精査内容を定義する。
        @Valid String sampleKey, //ヘッダーから受け取った値を格納する変数を定義する。
        @RequestBody
        @Valid SampleForm form //ボディから受け取った値を格納するFormクラスと格納先の変数を定義する。
    ) {
        return new ResponseEntity<> (service.sampleMethod (form.getSampleUserId), HttpStatus.OK);
    }
}

Formクラス

  • クライアントからのリクエストの内容や返すレスポンスの情報を保持するクラス。
  • 精査内容等はここに記述する。
  • アノテーションによる精査定義のほか、独自の精査用メソッドを定義することも出来る。
SampleForm.java
@Data
@ToString
public class SampleForm implements Serializable {

    private static final long serialVersionUID = 1L;

    @JsonProperty ("sample_user_id") //JSON上でのキー値を定義する。
    @NotEmpty (message = "sampleUserId is empty") //精査内容を定義する。
    private String sampleUserId;

}

RestControllerAdviceクラス

  • @RestControllerAdviceを付与する。@ComponentScanの対象。
  • RestControllerクラスに共通の処理を実装出来る。(事前処理やエラーハンドリング等)
  • @ExceptionHandlerを付与したメソッドを用意することで、指定した例外が送出された際に自動でエラーハンドリングしてくれる。
SampleExceptionHandler.java
@RestControllerAdvice
public class SampleExceptionHandler {

    @ExceptionHandler (Exception.class) //キャッチする例外を定義する。
    @ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR) //上記例外発生時に返すレスポンスのHTTPステータスコードを定義する。
    protected ErrorForm handleInternalServerError (Exception e) {
        logger.error (e);
        return new ErrorForm ("Internal Server Error");
    }

    @RequiredArgsConstructor
    private class ErrorForm implements Serializable {
        private static final long serialVersionUID = 1L;
        private final String message;
    }
}

Serviceクラス

  • @Serviceを付与する。@ComponentScanの対象。
  • 業務ロジックを実装するクラス。RestControllerクラスから受けとった入力値をもとに、各種計算やDBアクセスを行う。
  • 今回はSpring Data JPAを使ったのでリポジトリクラスを呼び出すことでDBアクセスを行う。
SampleService.java
@Service
@Transactional //例外発生時に自動ロールバックする。
public class SampleService {

    @Autowired
    private SampleUserRepository sampleUserRepository; //使用するRepositoryクラスを紐づける。

    public SampleForm returnSampleForm (String sampleUserId) {
        return convertToSampleForm(sampleUserRepository.findBySampleUserId(sampleUserId));
    }
}

Repositoryクラス

  • @Repositoryを付与する。@ComponentScanの対象。
  • DBアクセスを行うクラスで、Spring Data JPAを使うとinterfaceだけ実装すれば簡単なCRUDなら実行することが出来る。
  • JpaRepositoryを継承させ、アクセスするテーブルを定義する。
  • 命名規約に従ってメソッド名を付けることで、自動的にCRUDのどれなのか、どんな検索条件かを読み取ってくれる。
    例)findByUserId(String userId) とすると、userIdで検索を行うメソッドとなる。
  • クエリを実装したりテーブル結合を伴う検索をしようとするとここまで単純にはいかないが、今回は使用しなかったため割愛。
SampleUserRepository.java
@Repository
public interface SampleUserRepository extends JpaRepository<SampleUser, String> { //アクセス先のテーブルと主キーの型を定義する。
    SampleUser findBySampleUserId (String sampleUserId); //命名規約に従ってメソッド名と引数を定義する。
}

Entityクラス

  • @Entityを付与する。
  • DBから取得したデータやDBに投入するデータを保持する。
  • 今回はテーブルと1対1に紐づかせた。
SampleUser.java
@Entity
@Table (name = "sample_user") //紐づけるテーブル名を定義する。
@Data
public class SampleUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /** サンプルユーザID */
    @Id //主キーの項目に付与する。
    @GeneratedValue (strategy = GenerationType.IDENTITY) //自動採番させる場合は採番方法を定義する。
    private String sampleUserId;

    /** サンプルユーザ名*/
    private String sampleUserName;
}

参考サイト

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

Jenkins Pipelineのpipeline {...}の"pipeline"と"{...}"は何か(Groovy初心者向け、他言語経験者向け)

背景

お仕事でJenkins Pipelineを書くことになりました。早速サンプルをコピーし、Hello Worldが表示されてうれしい一方、こんな疑問が。

この、pipeline{...}とは何なのか?どういう文法規則なのか?

Jenkinsfile
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'echo "Hello World"'
            }
        }
    }
}

Jenkinsfile自体は単なるデータ構造(JSONやYAMLの仲間)に見えます。ということは何か規則があるはずです。しかしそれが書かれているページが見つからない。
職場のつよつよエンジニアに聞いたところ、JenkinsfileはGroovyのプログラムということが分かりました。

その後自分で調べたことと、他の言語との対比を書いておきます。

"pipeline"と"{...}"は何かの答え

pipelineメソッド名
{...}クロージャ

pipeline {...}は、pipelineメソッドに、{...}というクロージャを引数として渡す、という構文です。

pipelineはメソッドなので、Jenkinsfileの実行時に

def pipeline(Closure cl) {
    // 前処理
    // クロージャを呼び出す
    cl();
    // 後処理
}

のように定義されたメソッドが(どこかから)読み込まれています。(上記コードは正確には少し違います。補足を参照してください)

もっと詳しく

groovyのメソッド呼び出し

上記のpipelineの呼び出しを冗長に書くと、

pipeline({ ->
    // 処理
})

です。
groovyのクロージャは{x, y -> x + y}のように、{引数 -> 処理}のように書きます。引数がない場合は、引数 ->の部分は省略可能です1。引数を省略すると、

pipeline({
    // 処理
})

の形になります。
また、メソッド呼び出しのカッコは、引数がある場合は省略可能なので、

pipeline {
    // 処理
}

になります。Jenkinsfileと同じ形になりました。

クロージャ

クロージャとは、メソッドの呼び出し側で動いているような振る舞いをする無名関数です。
こちらの説明が分かりやすいです。
クロージャ - Apache Groovyチュートリアル

他の言語で書いてみると

Groovyのクロージャを他の言語と対比します。(ここに書ききれない言語につきましてはコメントで補足お願いします)

Ruby

Groovyの

pipeline {
    // 処理
}

という記法は、Rubyの

pipeline {
  # 処理
}

という記法と対応します。見た目は同じですね。GroovyはRubyに強い影響を受けていることがわかります。
Rubyのブロックを、Groovyではクロージャと言います。ブロックとクロージャの違いは、こちらのページが分かりやすいです。
Rubyist のための他言語探訪 【第 5 回】 Groovy

JavaScript

Groovyの

pipeline {
    // 処理
}

という記法は、JavaScriptの

pipeline(() => {
    // 処理
})

という記法に対応します。JavaScriptではメソッド呼び出しのカッコは省略できず、引数がない場合も引数を囲むカッコを省略できないですが、Groovyでは省略が可能です。

Java

Groovyの

pipeline {
    // 処理
}

という記法は、Javaの

pipeline(() -> {
    // 処理
})

に対応します。JavaScriptと同様Javaでもメソッド呼び出しのカッコと、引数がない場合は引数を囲むカッコを省略できません。Groovyではこの2つは省略可能です。

Python

Groovyの

pipeline {
    // 処理
}

という記法は、Pythonで対応する記法がないのですが、あえて書くとしたら

pipeline(lambda: 処理)

です。Pythonの場合、lambda式は複数行にまたがることができませんが、Groovyでは可能です。

補足

上で、pipelineメソッドは

def pipeline(Closure cl) {
    // 前処理
    // クロージャを呼び出す
    cl();
    // 後処理
}

のようになっていると書きましたが、正確にいうと少し違います。クロージャ内で使用できる関数を制限するため、delegate(移譲)を行なっています。pipelineメソッドのソースが見つからなかったのですが、より実情に沿ったコードは以下です。

def pipeline(@DelegatesTo(PipelineSpec) Closure cl) {
    def pipelineSpec = new PipelineSpec()
    def code = cl.rehydrate(pipelineSpec, this, this)
    code.resolveStrategy = Closure.DELEGATE_ONLY
    code()
}

本文中では分かりやすさを優先するため、クロージャをそのまま呼び出す書き方にしています。

参考資料

あとがき

Jenkinsfileを作成している最中、「コピペで動いてはいる、ただ、なんで動いているのか分からない」という状態でしたが、Groovyのプログラムということを理解し、実態がつかめました。
Jenkins Pipelineの記事がまだまだ少ないので、分かったことを今後も書けたらと思います。


  1. 正確に言うと、{-> 処理}{ 処理 }では挙動が変わります。 

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

[Java] Javaは + で文字列連結しても大丈夫と言われていたので確かめた

試したこと

こんなファイルを作り

PlainString.java
import java.time.LocalDateTime;

class PlainString {
    String getString() {
        // え!文字列 + で連結しちゃうの!?
        return "abc" + LocalDateTime.now();
    }
}

コンパイルし

javac PlainString.java

結果

逆コンパイルしてみました。

javap -c PlainString
Compiled from "PlainString.java"
class PlainString {
  PlainString();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  java.lang.String getString();
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
       7: ldc           #4                  // String abc
       9: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      12: invokestatic  #6                  // Method java/time/LocalDateTime.now:()Ljava/time/LocalDateTime;
      15: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      18: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      21: areturn
}

しっかり StringBuilder に置き換えられていました。

結論

イマドキのJavaは、とくに気を使わずに + 使って文字列連結して大丈夫そうです。
2020/2/22追記 @cfm-art さんからコメントいただきました。ありがとうございます。
単純に連結するだけであれば + で大丈夫。
ループのなかで + を使うと、その度にStringBuilderが作られるのでダメ。

環境

  • Ubuntu 18.04.4 LTS
  • OpenJDK 8u242-b08-0ubuntu3~18.04

【2020/2/22追記】 JDK13でも文字列連結を試した

JDK11 以降は StringBuilder 使われていないと教えていただいたので、試してみました。

JDK13における結果

javacしてからjavapしました。

javap -c PlainString                                                                                                      Compiled from "PlainString.java"
class PlainString {
  PlainString();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  java.lang.String getString();
    Code:
       0: invokestatic  #7                  // Method java/time/LocalDateTime.now:()Ljava/time/LocalDateTime;
       3: invokedynamic #13,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/time/LocalDateTime;)Ljava/lang/String;
       8: areturn
}

StringBuilderが消えて、makeConcatWithConstantsというものに変わっていました。

JDK13における結論

文字列連結にどこでも + 使って大丈夫そう。

環境

openjdk version "13.0.2" 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 13.0.2+8)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 13.0.2+8, mixed mode, sharing)

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

【binarysearch.io】Codewars と併用すると良いかもしれないオンラインプログラミング学習サイト

Twitter でのつぶやきで知ったオンラインのプログラミング学習サイトです。

  • Java, Ruby, Python, C++, JavaScript に対応しています (2020/02/20現在)。
  • チャットしながら複数人で解答を作っていくという醍醐味があるみたいですが、今回は一人で(他人のチャットに邪魔されずに)学習する方法をお教えします。
  • Codewars よりも問題の質が安定しているという印象を受けました。(公式が作成した問題のみ)
  • 競技プログラミングではないので、プログラミング学習のドリルとして使う、プログラミングのレベルアップに使う等の用途に向いていると思います。

1. https://binarysearch.io/ にアクセスし「Get Started」をクリックしてサインアップを行います。

image.png

2. 「Full Name」「Username」「Email」「Password」を入力して「Sign up for Binary Search」をクリックします。

image.png

3. 2で登録した「Email」に登録確認用メールが送信された旨が表示されるので、メールアプリを開いて確認用のリンクをクリックします。

image.png

image.png

4. メールアドレスの確認が完了したら「Start」ボタンをクリックします。

image.png

5. https://binarysearch.io/ のログイン後の画面が表示されます。問題を解くには「Create room」ボタンを押します。

image.png

6. Difficulty で問題の難易度を選びます。外国人の方からチャットで話かかけられたりしないように Capacity を「1 player」にしておく(または Private にしておく)と良いかもしれません。

image.png

7. room を作成したら「Start Room」ボタンを押して問題を開始します。(今回は Easy で作成しました)

image.png

8. 「引数として与えられた配列の長さが1かどうかを返す」よう関数を完成させなさい、という問題です(注記:こんな簡単すぎる問題ばかりではありません)。解答言語としてJavaScriptを 選択しました。

image.png

9. 解答を作成したら「Run▶」ボタンをクリックします。「Correct!」と表示されたら「Submit code」ボタンをクリックします。

image.png

image.png

10. 問題によっては、「Solution」というタブに解法・解説が提示されることがあります。(この問題は簡単すぎなので解説がないんだと思います)

image.png

11. 別の問題を解くには「Start Room」ボタンを再度押して次の問題を開始します。部屋を抜けるには画面右上の×ボタンをクリックします。

image.png

最後に

公式の問題ばかりなので(?)問題の質が安定している(均一)なのが良いですね。気になったのは同じレベルで学習を続けていると同じ問題が出てくることがあることですが、そのまま解いちゃえば良いので大きな問題ではないと思います。

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

【binarysearch.io】Codewars と併用すると良いかもしれないオンラインプログラミング学習サイト (Master algorithms together)

Twitter でのつぶやきで知ったオンラインのプログラミング学習サイトです。

  • Java, Ruby, Python, C++, JavaScript に対応しています (2020/02/20現在)。
  • チャットしながら複数人で解答を作っていくという醍醐味があるみたいですが、今回は一人で(他人のチャットに邪魔されずに)学習する方法をお教えします。
  • Codewars よりも問題の質が安定しているという印象を受けました。(公式が作成した問題のみ)
  • 競技プログラミングではないので、プログラミング学習のドリルとして使う、プログラミングのレベルアップに使う等の用途に向いていると思います。

1. https://binarysearch.io/ にアクセスし「Get Started」をクリックしてサインアップを行います。

image.png

2. 「Full Name」「Username」「Email」「Password」を入力して「Sign up for Binary Search」をクリックします。

image.png

3. 2で登録した「Email」に登録確認用メールが送信された旨が表示されるので、メールアプリを開いて確認用のリンクをクリックします。

image.png

image.png

4. メールアドレスの確認が完了したら「Start」ボタンをクリックします。

image.png

5. https://binarysearch.io/ のログイン後の画面が表示されます。問題を解くには「Create room」ボタンを押します。

image.png

6. Difficulty で問題の難易度を選びます。外国人の方からチャットで話かかけられたりしないように Capacity を「1 player」にしておく(または Open to public を Private にしておく)と良いかもしれません。

image.png

7. room を作成したら「Start Room」ボタンを押して問題を開始します。(今回は Easy で作成しました)

image.png

8. 「引数として与えられた配列の長さが1かどうかを返す」よう関数を完成させなさい、という問題です(注記:こんな簡単すぎる問題ばかりではありません)。解答言語としてJavaScriptを 選択しました。

image.png

9. 解答を作成したら「Run▶」ボタンをクリックします。「Correct!」と表示されたら「Submit code」ボタンをクリックします。

image.png

image.png

10. 問題によっては、「Solution」というタブに解法・解説が提示されることがあります。(この問題は簡単すぎなので解説がないんだと思います)

image.png

11. 別の問題を解くには「Start Room」ボタンを再度押して次の問題を開始します。部屋を抜けるには画面右上の×ボタンをクリックしてから「Leave」を選択します。

image.png

最後に

公式の問題ばかりなので(?)問題の質が安定している(均一)なのが良いですね。気になったのは同じレベルで学習を続けていると同じ問題が出てくることがあることですが、そのまま解いちゃえば良いので大きな問題ではないと思います。

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

COTOHA API の構文解析 を Java で100回呼んでみた

CotohaNlpService クラスの作成

投稿1 COTOHA API の構文解析 を Java で利用してみる

投稿2 COTOHA API の構文解析 を Java でパースする

をベースに、COTOHA APIを簡単に利用できるように、以下のクラスを作成しました。
(後日、Maven Repository にて公開予定です)

CotohaNlpService.java
https://github.com/oyahiroki/nlp4j/blob/master/nlp4j/nlp4j-cotoha/src/main/java/nlp4j/cotoha/CotohaNlpService.java

このクラスを利用すると以下のような感じで、簡単に形態素解析結果を得ることができます。
(API呼び出しに必要な変数は環境変数としてセットしておきます)
別途、JSONを整形して改行区切りでテキストファイルに保存するクラスを作成しましたので、呼び出し回数の節約も可能になりました。

CotohaNlpService service = new CotohaNlpService();
DefaultNlpServiceResponse response = service.nlpV1Parse("今日は学校に走って行きました。");
// レスポンスのJSON が出力される。キーワードクラスとして返すメソッドも用意している。
System.err.println(response.getOriginalResponseBody()); 

データの用意

国土交通省「自動車のリコール・不具合情報」からデータをダウンロードして、100件分を利用しました。
手でHTMLをコピペするのは当然面倒ですので、ごにょごにょ しました。^_^;

国土交通省「自動車のリコール・不具合情報」
http://carinf.mlit.go.jp/jidosha/carinf/opn/index.html

100回呼んでみる

100回呼んでみた結果は以下のとおりです。

横軸が試行回数で、縦軸が処理時間です。
クライアント側でのJSONパースの処理を含んでいますが、数msと考えてよいでしょう。
最初に2000 msくらいかかっていますが、これはTokenの取得も含んでいます。
だいたいが 200-300 msに収まっているのですが、時々突発的に遅くなるようです。
DEV環境向けのは遅いという可能性も高いと思いますし、もちろんネットワーク環境にもよると思います。

image.png

次に、文字列の長さと処理時間のグラフです。
見たところ、文字列の長さと処理時間に相関はなさそうです。(極端に長い文字などは変わる可能性が高いと思います)

image.png

以下、生データです。

length,time
61,2001
73,337
54,310
79,349
58,274
51,269
41,660
21,263
38,283
74,295
52,4472
70,1138
68,3074
31,243
39,251
15,219
11,258
14,259
62,293
66,276
27,272
18,220
63,278
62,428
68,284
50,288
43,250
45,264
70,273
58,250
157,593
88,280
66,264
26,272
38,1514
8,237
42,256
53,1472
42,2668
35,230
32,235
36,241
116,325
17,254
102,309
59,268
21,220
43,278
64,249
32,246
31,247
27,252
70,3698
61,340
51,233
23,225
20,226
60,310
50,1685
72,281
37,270
45,253
13,224
54,243
64,302
52,1876
90,3251
30,9501
73,2323
70,3689
70,1304
61,303
67,262
17,3032
128,302
63,272
33,238
32,257
106,3906
57,261
103,299
82,270
71,268
158,803
41,255
36,284
62,304
36,234
38,1778
19,1478
90,345
22,239
62,310
72,2555
66,256
25,927
33,242
39,283
24,237
42,247

以上

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

COTOHA API の構文解析 を Java で100回呼んでパフォーマンス測定しました。

CotohaNlpService クラスの作成

投稿1 COTOHA API の構文解析 を Java で利用してみる

投稿2 COTOHA API の構文解析 を Java でパースする

をベースに、COTOHA APIを簡単に利用できるように、以下のクラスを作成しました。
(後日、Maven Repository にて公開予定です)

CotohaNlpService.java
https://github.com/oyahiroki/nlp4j/blob/master/nlp4j/nlp4j-cotoha/src/main/java/nlp4j/cotoha/CotohaNlpService.java

このクラスを利用すると以下のような感じで、簡単に形態素解析結果を得ることができます。
(API呼び出しに必要な変数は環境変数としてセットしておきます)
別途、JSONを整形して改行区切りでテキストファイルに保存するクラスを作成しましたので、呼び出し回数の節約も可能になりました。

CotohaNlpService service = new CotohaNlpService();
DefaultNlpServiceResponse response = service.nlpV1Parse("今日は学校に走って行きました。");
// レスポンスのJSON が出力される。キーワードクラスとして返すメソッドも用意している。
System.err.println(response.getOriginalResponseBody()); 

データの用意

国土交通省「自動車のリコール・不具合情報」からデータをダウンロードして、100件分を利用しました。
手でHTMLをコピペするのは当然面倒ですので、ごにょごにょ しました。^_^;

国土交通省「自動車のリコール・不具合情報」
http://carinf.mlit.go.jp/jidosha/carinf/opn/index.html

100回呼んでみる

100回呼んでみた結果は以下のとおりです。

横軸が試行回数で、縦軸が処理時間です。
クライアント側でのJSONパースの処理を含んでいますが、数msと考えてよいでしょう。
最初に2000 msくらいかかっていますが、これはTokenの取得も含んでいます。
だいたいが 200-300 msに収まっているのですが、時々突発的に遅くなるようです。
DEV環境向けのは遅いという可能性も高いと思いますし、もちろんネットワーク環境にもよると思います。

image.png

次に、文字列の長さと処理時間のグラフです。
見たところ、文字列の長さと処理時間に相関はなさそうです。(極端に長い文字などは変わる可能性が高いと思います)

image.png

以下、生データです。

length,time
61,2001
73,337
54,310
79,349
58,274
51,269
41,660
21,263
38,283
74,295
52,4472
70,1138
68,3074
31,243
39,251
15,219
11,258
14,259
62,293
66,276
27,272
18,220
63,278
62,428
68,284
50,288
43,250
45,264
70,273
58,250
157,593
88,280
66,264
26,272
38,1514
8,237
42,256
53,1472
42,2668
35,230
32,235
36,241
116,325
17,254
102,309
59,268
21,220
43,278
64,249
32,246
31,247
27,252
70,3698
61,340
51,233
23,225
20,226
60,310
50,1685
72,281
37,270
45,253
13,224
54,243
64,302
52,1876
90,3251
30,9501
73,2323
70,3689
70,1304
61,303
67,262
17,3032
128,302
63,272
33,238
32,257
106,3906
57,261
103,299
82,270
71,268
158,803
41,255
36,284
62,304
36,234
38,1778
19,1478
90,345
22,239
62,310
72,2555
66,256
25,927
33,242
39,283
24,237
42,247

以上

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