20200209のJavaに関する記事は10件です。

Lombokのカスタムアノテーションを作ろうとして諦めたメモ

Lombokのカスタムアノテーションを作ろうとして諦めたメモです。

3行でまとめ:

  1. Lombokのアノテーション実装ではJava内部クラス(コンパイラ関連)を参照する。
  2. Java9 以降、Java内部クラスの参照方法がビルドシステムやIDEで大きく変わり、難易度がめちゃくちゃ高くなった。
  3. アノテーションを集約するメタアノテーション的なのもLombokでは未対応なので、打つ手なし、ギブアップ。

作ろうとした経緯:

  1. @Value 大好きなんだけど、Eclipse上からlombokが作成したgetterの利用箇所を Ctrl + Shift + F で逆引きできない。というかしづらい。
  2. そもそも Immutable なデータ構造体として @ToString @EqualsAndHashCode @AllArgsConstructor をまとめて付けたいがための @Value で、個人的な好みとして getter 要らない。データ構造としてのフィールドは public final でえーやろ、という割り切り。
  3. よし、カスタムアノテーション作ってみるか・・・ な~に、@Value のソースパクって、@Getter 機能呼び出してるところ削ればおしまいじゃろ。

ということで、まずは Lombok でカスタムアノテーション作るイロハをググった:

うん、予想はしてたけど Java のバイトコードのガチ操作でめんどくさそうだし難易度も高い。止めようかな・・・。
いやいや、そもそも作りたいのは @Value - @Getter なのだから、@Value のソースコードはどうなってる?

handleFieldDefaults.generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true);
handleConstructor.generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode);
handleConstructor.generateExtraNoArgsConstructor(typeNode, annotationNode);
handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true, List.<JCAnnotation>nil());
handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode);
handleToString.generateToStringForType(typeNode, annotationNode);

ここまででコピペ自体は行けそうだが、ふと「あれ・・・そういえば com.sun.tools 系使ってるよな・・・。この辺、java9のmodule導入の影響ガチで受けてるとこだし、どうすりゃいいんだ・・・」と途方に暮れる。

とりあえず java8 時代までの com.sun.tools を maven で扱うときの記事をググった。

そうそう、こんな感じでした。では java9 でどうなったかちょっとぐぐってみると・・・

めっちゃ雲行き怪しい。というかすでに阿鼻叫喚。ヤバい。
これに Eclipse 側の対応も・・・と軽くぐぐってみると、若干内容は異なるものの、いずれもJava内部クラスへのアクセスで苦労されてる様子:

これはまずい。maven/pom.xml の調整がまずヤバそうだし、それとEclipse側の組み合わせも複雑さを跳ね上げてる。

そもそも、(今回の調査で初めて知った)サードパーティ製のLombokカスタムアノテーションを集めた https://github.com/peichhorn/lombok-pg も、そしてLombok 本体もビルドは Ant を使ってる様子。Eclipse IDE用にも結構調整入ってる雰囲気。(IDEに組み込むものなのか、IDEで開発するときのためのものかまでは不明だけどどちらにしても結構面倒くさそう)

トドメに、Java9 以降でLombokメンテすることの大変さが忍ばれる記事発見。

うーん、カスタムアノテーションがだめだったら、既存のアノテーションを集約するメタアノテーション的なのは作れないかな?とググってみたが・・・

どうやら難しそうで未対応とのこと。

以上を考えるに、そもそもやりたいのは以下のアノテーションを付けるのを簡単にしたいだけ・・・にも関わらず、実現するためのコストが異常に高い。

@AllArgsConstructor
@EqualsAndHashCode
@ToString
(場合によっては @With )

よって、今回はカスタムアノテーションを作成するのは一旦あきらめ、無難に必要なものだけコピペしてくことにする。

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

Angularチュートリアル + SpringBoot + PostgreSQLをやってみた

Angular + SpringBoot + PostgreSQL

はじめに

フロントエンド: Angular(TypeScript)
バックエンド: SpringBoot(Java)
DB: PostgreSQL

こちらの記事を参考にして、タイトル通りのものを作ってみました。
Angularチュートリアル + Spring Bootやってみた

前提条件

Spring Initializerでプロジェクトを作成していること
Maven Projectで作成し、
DependenciesはpostgreSQL DriverLombokを入れました。

名前は適当に

環境
- Mac OS Catalina 10.15.2
- Intellij Community 2019.3.1
- Java 11
- PostgreSQL 12.1

この記事ではMacでやっていますが、Windowsでも問題なく動きます。(7は確認済みだが、10は知らない)

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.2
BuildVersion:   19C57

% osascript -e 'version of app "IntelliJ IDEA"'   
2019.3.1

% java --version
java 11.0.4 2019-07-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.4+10-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.4+10-LTS, mixed mode)

 % postgres --version
postgres (PostgreSQL) 12.1

% ng --version
Angular CLI: 8.3.4
Node: 11.12.0
OS: darwin x64
Angular: 
... 

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.803.4
@angular-devkit/core         8.3.4
@angular-devkit/schematics   8.3.4
@schematics/angular          8.3.4
@schematics/update           0.803.4
rxjs                         6.4.0

PostgreSQL

すでにPostgreSQLをインストールしている前提で進めます。
今回はスキーマを使ってやってます。

スキーマ作成&テーブル作成

CREATE SCHEMA tutorial
CREATE TABLE tutorial.heroes(
    id INT,
    name VARCHAR(64)
)

モックデータ挿入

INSERT INTO tutorial.heroes VALUES
(1, 'キャプテン・アメリカ'),
(2, 'アイアンマン'),
(3, 'ハルク'),
(4, 'ソー・オーディンソン'),
(5, 'ブラック・ウィドー'),
(6, 'ホークアイ'),
(7, 'ウィジョン'),
(8, 'スカーレット・ウィッチ'),
(9, 'ファルコン'),
(10, 'ウォーマシン'),
(11, 'キャプテン・マーベル');

これでPostgreSQLの準備はできました。

Angular

Angular チュートリアルがすべて終わっている前提で進めます。

サービスクラス

サービスクラスのheroUrlをREST APIのURLに変更します。

hero.service.ts
...
// 略

export class HeroService {
// private heroesUrl = 'api/heroes';  // Web APIのURL
  private heroesUrl = 'http://localhost:8080'; // <= ここを追加

  httpOptions = {
    headers: new HttpHeaders({ "Content-Type": "application/json" })
  };

  constructor(
    private http: HttpClient,
    private messageService: MessageService
  ) {}

// 略
...

app.module.ts

モックではなく、実際にAPIを叩いてデータを貰うため、
APIサーバっぽく振る舞ってくれるHttpClientInMemoryWebApiModuleをコメントアウトします。

app.module.ts
@NgModule({
  declarations: [
    AppComponent,
    HeroesComponent,
    HeroDetailComponent,
    MessagesComponent,
    DashboardComponent,
    HeroSearchComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    HttpClientModule
    // HttpClientInMemoryWebApiModule.forRoot(
    //   InMemoryDataService, { dataEncapsulation: false }
    // )
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

ヒーローコンポーネント

heroes.component.ts
add(name: string): void {
    name = name.trim();
    // ↓ 最後の要素に1プラスしたものを新ヒーローのIDとして追加
    let id = this.heroes.slice(-1)[0].id + 1;
    if (!name) { return; }
    // idを追加し、Heroとして引数を渡す
    this.heroService.addHero({ name, id } as Hero)
      .subscribe(hero => {
        this.heroes.push(hero);
      });
  }

Angularの変更はこれで終了です。

バックエンド側 (Java)

Hero class

ヒーローの型定義のため、ヒーロークラスを作成します。
Lombokを使っているのでsetter, getterはいりません。
(IntellijではPluginでlombokを入れる必要があります)

Hero.java
package tutorial.tutorial.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@Data
@NoArgsConstructor
@ToString
public class Hero {
    private Integer id;
    private String name;
}

HeroDAO class

データベースにアクセスするためのDAOクラスを作成します。
CRUD処理を全部作ります。

HeroDAO.java
package tutorial.tutorial.model;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class HeroDAO {

    /**
     * DB接続情報を確立するメソッド
     *
     * @return conn コネクション情報
     */
    public Connection getConnection() throws ClassNotFoundException, SQLException {
        // Connection情報を格納するための変数を用意
        Connection conn = null;
        // 初期化
        Class.forName("org.postgresql.Driver");
        // DB情報を入力
        conn = DriverManager.getConnection("jdbc:postgresql://localhost/postgres?currentSchema=tutorial", "postgres", "postgres");
        // 自動コミットは無効にする
        conn.setAutoCommit(false);
        // コネクション情報を返す
        return conn;
    }

    /**
     * すべてのHeroを取得し返却するメソッド
     *
     * @return heroes Heroの型リスト
     */
    public List<Hero> findAll() throws ClassNotFoundException, SQLException {
        // Connection情報を格納するための変数用意
        Connection conn = null;
        // dtoクラスのインスタンス格納用
        List<Hero> heroes = new ArrayList<>();


        // データベースへの接続
        try {
            conn = getConnection();
            // SQL文を実行するためのオブジェクト生成
            Statement pstmt = conn.createStatement();
            // SELECT文の発行
            String sql = "SELECT * FROM tutorial.heroes";
            // SQL文の実行結果を取得(DBから受け取る値)
            ResultSet rs = pstmt.executeQuery(sql);

            // DBから受け取った値をレコード分だけ繰り返す
            while (rs.next()) {
                // Hero(DTO)クラスのインスタンスを生成
                Hero dto = new Hero();
                // カラムidの値をセット
                dto.setId(rs.getInt("id"));
                // カラムnameの値をセット
                dto.setName(rs.getString("name"));
                // インスタンスをListに格納
                heroes.add(dto);
                // while文で次のレコード処理へ(あれば)
            }
            //エラーキャッチ文
        } catch (SQLException e) {
            e.printStackTrace();
            // 例外の発生有無に関わらず実行する処理
        } finally {
            // もしconnの中身が入っていればdb接続を切る
            if (conn != null) {
                conn.close();
            }
        }
        // DTOクラスのインスタンスのListを返す
        return heroes;
    }


    /**
     * 引数で受け取るidに一致するHeroを取得し、返却するメソッド
     *
     * @param id
     * @return selectedHero
     */
    public Hero findOneHero(int id) throws ClassNotFoundException, SQLException {
        Connection conn = null;
        Hero selectedHero = new Hero();

        // データベースへの接続
        try {
            conn = getConnection();
            // SELECT文の発行
            String sql = "SELECT * FROM tutorial.heroes WHERE id = ?";

            // SQL文を実行するためのオブジェクト生成
            PreparedStatement pstmt = conn.prepareStatement(sql);

            // プレースホルダーで引数で受け取ったidをセットする。
            pstmt.setInt(1, id);

            // SQL文の実行結果を取得(DBから受け取る値)
            ResultSet rs = pstmt.executeQuery();

            // DBから受け取った値をdtoにセットする。
            while (rs.next()) {
                // Hero(DTO)クラスのインスタンスを生成
                Hero dto = new Hero();
                // カラムidの値をセット
                dto.setId(rs.getInt("id"));
                // カラムnameの値をセット
                dto.setName(rs.getString("name"));
                //
                selectedHero = dto;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return selectedHero;
    }

    /**
     * 引数で受け取るidに一致するHeroをUpdateするメソッド
     *
     * @param hero
     */
    public void updateHero(Hero hero) throws ClassNotFoundException, SQLException {
        Connection conn = null;
        try {
            conn = getConnection();
            String sql = "UPDATE tutorial.heroes SET name = ? WHERE id = ?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, hero.getName());
            pstmt.setInt(2, hero.getId());
            pstmt.executeUpdate();
            conn.commit();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }

    /**
     * 引数で受け取るidに一致するHeroを削除するメソッド
     *
     * @param id 消したいheroのid
     */
    public void deleteHero(int id) throws ClassNotFoundException, SQLException {
        Connection conn = null;
        try {
            conn = getConnection();
            String sql = "DELETE FROM tutorial.heroes WHERE id = ?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id);
            pstmt.executeUpdate();
            conn.commit();

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }

    /**
     * 引数で受け取るidとnameで新しいHeroをINSERTするメソッド
     *
     * @param hero
     */
    public void createHero(Hero hero) throws ClassNotFoundException, SQLException {
        Connection conn = null;
        try {
            conn = getConnection();
            String sql = "INSERT INTO tutorial.heroes VALUES(?, ?)";

            // SQL文を実行するためのオブジェクト生成
            PreparedStatement pstmt = conn.prepareStatement(sql);

            // プレースホルダーで引数で受け取ったidをセットする。
            pstmt.setInt(1, hero.getId());
            pstmt.setString(2, hero.getName());

            // SQL文の実行結果を取得(DBから受け取る値)
            pstmt.executeUpdate();
            conn.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }
}

HeroController class

REST APIを作ります。
以下の5つを作成しました。
- すべてのヒーローを返す getHeroes
- idに紐づくヒーローを返却する getHero
- 新しいヒーローを作成する create
- ヒーローを削除する delete
- ヒーローの情報を更新する update

チュートリアルに合わせて受け取るデータをHeroにしています。

HeroContoller.java
package tutorial.tutorial.controller;

import org.springframework.web.bind.annotation.*;
import tutorial.tutorial.model.*;
import java.sql.SQLException;
import java.util.*;

@RestController
public class HeroController {

    /**
     * DAOクラスからすべてのheroを受け取るメソッド
     *
     * @return heroList 
     */
    @GetMapping("/")
    public List<Hero> getHeroes() throws SQLException, ClassNotFoundException {
        HeroDAO dao = new HeroDAO();
        List<Hero> heroes = dao.findAll();
        List<Hero> heroList = new ArrayList<>();
        heroList.addAll(heroes);
        return heroList;
    }

    /**
     * DAOクラスからidに紐付くheroを受け取るメソッド
     *
     * @param id
     * @return hero
     */
    @GetMapping("/{id}")
    public Hero getHero(@PathVariable Integer id) throws SQLException, ClassNotFoundException {
        HeroDAO dao = new HeroDAO();
        Hero hero = dao.findOneHero(id);
        return hero;
    }

    /**
     * DAOクラスで、受け取ったidとnameでINSERTするメソッド
     *
     * @param newHero
     * @return hero
     */
    @PostMapping("/")
    public Hero create(@RequestBody Hero newHero) throws SQLException, ClassNotFoundException {
        HeroDAO dao = new HeroDAO();
        dao.createHero(newHero);
        return newHero;
    }


    /**
     * DAOクラスでidに紐付くheroをDELETEするメソッド
     *
     * @param id
     */
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Integer id) throws SQLException, ClassNotFoundException {
        HeroDAO dao = new HeroDAO();
        dao.deleteHero(id);
    }

    /**
     * DAOクラスでidに紐付くheroをUPDATEするメソッド
     *
     * @param updatedHero
     */
    @PutMapping("/")
    public Hero update(@RequestBody Hero updatedHero) throws SQLException, ClassNotFoundException {
        HeroDAO dao = new HeroDAO();
        dao.updateHero(updatedHero);
        return updatedHero;
    }

}

CORS対応

ajax等は、セキュリティのため、SOPによって、同一オリジンからしかリソースを取得することができません。
今回はAngularとSpringBootでポート番号が違うため、信頼できるオリジン間に限定してSOPを解除するためCORSを有効にしてあげる必要があります。

WebConfig.java
package tutorial.tutorial.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer{
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/*")
                // 許可するport番号を指定(Angular側)
                .allowedOrigins("http://localhost:3000")
                // 許可するメソッド一覧
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 許可するヘッダー
                .allowedHeaders("Origin", "X-Requested-With", "Content-Type", "Accept")
                .allowCredentials(false).maxAge(3600);
    }
}

実行する

TutorialApplicationとAngularを実行してみましょう。
APIが呼ばれて、DBが操作されていることが確認できます。

まとめ

Webアプリがどういう風になっているのか、理解するために、普段使っているIonicと関係が深いAngularをフロントエンド、SpringBootをバックエンドに、そしてデータベースにPostgreSQLを使ったやり取りを作ってみました。

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

Groovy超簡単入門

Groovyの紹介

Groovyは2003年開発されたJavaプラットフォームで動作する動的プログラミング言語であり、直接スクリプトで実施することを特徴としています。

Groovyプロジェクト作成

まずは、簡単なJava Beanを作ります。

public class Cat{

    /**The name of the cat*/
    private String name;

    /**The age of the cat*/
    private int age;

    /**Constuct*/
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**Get the cat name*/
    String getName() {
        return name
    }

    /**Set the cat name*/
    void setName(String name) {
        this.name = name
    }

    /**Get the cat age*/
    int getAge() {
        return age
    }

    /**Get the cat age*/
    void setAge(int age) {
        this.age = age
    }
}

GroovyとJavaの比較

GroovyとJavaを比べると以下の特徴を持っています。

  1. 行末のセミコロンは省略できる。
  2. returnは省略できる。
  3. getter/setterは自動で作られる。
  4. == によるオブジェクトの比較は、自動的に equals による比較となり、null チェックの必要がない。
public class Cat {
    private String name  /**The name of the cat*/
    private int age /**The age of the cat*/
    /**Constuct*/
    public Cat(String name, int age) {
        this.name = name
        this.age = age
    }
}

Cat cat = new Cat("wow", 1);
print cat.name;

NullPointerException チェック

public class Cat {
    private String name  /**The name of the cat*/
    private int age /**The age of the cat*/
    /**Constuct*/
    public Cat(String name, int age) {
        this.name = name
        this.age = age
    }
}

Cat cat = new Cat("wow", 1);
print cat.name;
Cat cat1 = null
print cat == cat1

コンパイル成功!

スクリーンショット 2020-02-09 16.22.21.png

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

OpenJDKについて簡単に整理

はじめに

開発言語のJava。
はいくつか種類があるって知ってました?
簡単に知識整理をしておきたいと思います。

OpenJDKとは

  • Open Java Development Kit
  • Java のフリーかつオープンソースの実装。
  • 2006年、サン・マイクロシステムズが始めた。
  • GPLリンク例外つきの GNU General Public License (GNU GPL) でライセンスされている。
  • OpenJDKはJava SE 7以降の公式リファレンス実装とされている。
  • 参考:https://ja.wikipedia.org/wiki/OpenJDK

その他の用語と合わせてみると。

用語 説明
Java SE Java Platform Standard Edition。仕様の集まり。
OpenJDK Java SE に基づいた開発を行っているオープンソースプロジェクト。ソースコードが管理されている。
JDK Java Development Kit。Oracleが提供するJava SE開発ツールキット。

OpenJDKの種類

OpenJDKはソースコードなので、それを使って各社がビルドしたものが提供されています。

ビルド パーミッシブ Pure 商用サポート LTS
AdoptOpenJDK / IBM / JClarity Yes Optional Yes Yes
Amazon Corretto Yes No No Yes
Azul Zulu(Azul systems) Yes No Yes Yes
BellSoft Liberica JDK Yes No Yes Yes
ojdkbuild Yes Yes No Yes
Oracle Java SE No No Yes Yes
Oracle OpenJDK Yes Yes No No
Red Hat for Windows Yes No Yes Yes
SapMachine Yes No No Yes

引用:https://ja.wikipedia.org/wiki/OpenJDK

パーミッシブ

ライセンスの種類 特徴
コピーレフト型 二次的著作物(derivative work)の頒布条件に同じライセンスが要求されるライセンス。例えばGPLのソースコードを使って開発した二次的著作物はGPLの基で頒布する必要があるといったもの。パーミッシブ型の対極に位置する。OSSの理念を強く持つライセンスと評価されることが多い。代表的なライセンスはGPL、AGPLなど。
準コピーレフト型 コピーレフト型ほどコピーレフト性が強くないライセンス。開発コミュニティに利便性が出るように工夫されている。代表的なライセンスはMPL、LGPL、CDDL、EPLなど。
パーミッシブ型 無保証であること、著作権表示をすること、ライセンス条文を表示すること、などの条件さえ満たせば頒布を許可するライセンス。二次的著作物を同じライセンスにする必要がない。コピーレフト型の対極に位置する。企業における商用利用への転用が容易だと評価されることが多い。代表的なライセンスはBSD、MIT、Apache、MS-PLなど。

引用:https://www.atmarkit.co.jp/ait/articles/1205/21/news101.html

Pure

100% Pure Java。
Java言語で開発されたプログラムが、特定のOSや開発環境に依存した機能を使用せず、標準仕様やAPIだけで構成されていること。

引用:http://e-words.jp/w/100-_Pure_Java.html

LTS

Long Term Support。
長期サポートをしているかどうか。

参考

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

[写真整理][java][windows]選択した写真を2クリックでフォルダに移動する

フォトビューアで参照している写真を★★★のフォルダに振り分けを行います

写真ファイルの振り分けって、地味に手間がかかります。
選んでいるうちに番号を忘れたり、印刷しようとするのがコレジャナイ。となったり、また、要らない写真をみていたり。
必要なファイルだけを見たい!しかも超絶簡単に振り分けたいって自分の都合から、こんなものを作りました。

フォトビューアから、これ(★★★.EXE)を起動すると、今いるフォルダの一つ上に★★★をつくり、★★★に移動します。

例えば

こんなフォルダ構成にて、001.JPGから005.JPGまでのファイルがあったとします。

CANON001/
└ jpg
  ├ 001.jpg
  ├ 002.jpg
  ├ 003.jpg
  ├ 004.jpg
  ├ 005.jpg

この状態で、フォトビューアにて003.jpgを選択中に★★★を起動すると、以下のようにJPGと同列に★★★フォルダが作成され、003.jpgが移動されます。

CANON001/
└ jpg
  ├ 001.jpg
  ├ 002.jpg
  ├ 004.jpg
  ├ 005.jpg
├ ★★★
  ├ 003.jpg

移動したファイルはフォトビューアの特性上、見ているフォルダの写真や画像をリピートしてみるので、移動したファイルは見るフォルダを変えないとみることができません。

つまり、移動後、フォトビューアでは以下のファイルを参照するだけとなります。

└ jpg
  ├ 001.jpg
  ├ 002.jpg
  ├ 004.jpg
  ├ 005.jpg

移動後に★★★のフォルダから画像を見ると、以下のファイルだけが見ることができます。

├ ★★★
  ├ 003.jpg

幾つかの準備(JAVAを実行できる環境、フォトビューアの復活)は必要ですが、2クリック(1.開く→2.★★★)だけで写真の振り分けができるのは非常に楽ですよ。

※私が忘れっぽいだけかも(ピックアップする番号を覚えられない)
※ダウンロードできる圧縮ファイルにはJPG フォルダに戻すのと、★★に移動するのが入ってます。

20.jpg

Exe加工はlaunch4j等にて実施しています。
http://launch4j.sourceforge.net/

フォトビューアを有効にするにはこの辺りを参考に。レジストリを編集するのが手っ取り早いです。
https://popozure.info/20190823/14633

実行ファイルのダウンロード

きっと実行ファイルだけが欲しいかと思いますので
こちらからダウンロードしたいただけば実行ファイルを入手できます。
https://drive.google.com/open?id=1dKcispHr8_D81kaPRgID7oKf27bEMfvS

念のため、ソースも公開して置きます

starSelector.java
import java.io.*;
import java.util.logging.*;
public class starSelector {
  public static final String LOGFILE = "StarSelector.log";
  public static void main(String[] args) {
    try {
      final Logger logger = Logger.getLogger("starSelector");
      try {
        // 出力ファイルを指定する
        FileHandler fh = new FileHandler("starSelector.log", true);
        // 出力フォーマットを指定する
        fh.setFormatter(new java.util.logging.SimpleFormatter());
        logger.addHandler(fh);
      } catch (IOException e) {
        e.printStackTrace();
      }
      File file = new File(args[0]);
      File parentDir = new File(file.getParent());
      //System.out.println("親の親のディレクトリ名 : " + parentDir.getParent());
      String strParentDir = file.getParent();
      String strFileName = file.getPath();
      String strStarDir = "★★★";
      File moveNewFolder = new File(parentDir.getParent() + "\\" + strStarDir);
      // 読み込みファイルチェック
      if (!file.exists() || !file.canRead()) {
        logger.log(Level.WARNING, "指定したファイルがありません");
        return;
      }
      if (file.getParent() == null) {
        logger.log(Level.INFO, "Homeディレクトリのファイルを指定しています。親ディレクトリの指定ができません");
        return;
      }
      //ディレトリチェック
      if (parentDir.getParent() == null) {
        logger.log(Level.INFO, "Homeディレクトリのファイルを指定しています。作成先のフォルダを指定できません");
        return;
      }
      moveNewFolder.mkdir();
      //ファイルの移動実施
      File file2 = new File(parentDir.getParent() + "\\" + strStarDir + "\\" + file.getName());
      try {
        if (file.renameTo(file2)) {} else {
          logger.log(Level.WARNING, "ファイルの移動に失敗しました");
        }
      } catch (SecurityException e) {
        logger.log(Level.WARNING, e.toString());
      } catch (NullPointerException e) {
        logger.log(Level.WARNING, e.toString());
      }
    } catch (ArrayIndexOutOfBoundsException err) {
      System.out.println("使い方 : java starSelector <移動するファイル>");
      return;
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【資格試験】Java SE8 Silver学習方法まとめ

はじめに

最近Java SE8 Silverの資格を取得しました。
このあとJava SE8 Goldも受験予定なので、諸々振り返りと今後の整理をするためメモにまとめます。

あくまで個人的な意見ですが参考になれば幸いです^^
また、間違いや異なった意見を持ってる方いらっしゃったら、コメントいただけると嬉しいです!

※本めもは、企業でしっかり研修などを受けて受験する方というより、個人的に資格取得を目指している初学者の方向けな感じです。(私がソンな感じ)

受験者のバックグラウンド

十数年前に1ヶ月ほど、入社時研修としてJava研修を受けた事があります。
その後は、ほぼプログラミングに関わる事なく10年ほど金融系企業向けのアカウントSEをしておりました。そのため、プログラマーとしては下の下。現在駆け出し中です(笑)

※ちなみに、Androidエンジニアとして転職して2ヶ月半ほど経ちます。現在、Java,kotlin双方で開発を行なっています。

Java Silver SE8 試験について

OracleのJavaSE8認定資格公式ページより概要を抜粋します。

Oracle Certified Java Programmer, Silver SE 8 認定資格は、Javaアプリケーション開発に必要とされる基本的なプログラミング知識を有し、上級者の指導のもとで開発作業を行うことができる開発初心者向け資格です。日常的なプログラミング・スキルだけでなく、さまざまなプロジェクトで発生する状況への対応能力も評価することを目的としています。
出展:Java SE 8 認定資格

記載のとおり、Silverでは「設計された状態」で「中上級者(GOLD保持者と同等程度)の指導のもと」コーディングができるレベルが求められています。
そのため、JavaSE8の「言語仕様(コンパイルも含む)」を正確に理解しているかどうかが重要であり、それらを問う問題が多く出題されています。このレベルは独立して業務遂行するには程遠いレベルである事を認識しておく必要がありそうです。

むしろ、かの有名な「リーダブルコード」で謳っている内容をガン無視した問題コードがたくさん出てくる印象です(笑)

総じて、私なりに考える「Java SE8 Silverを受ける意義」は以下3つかなと。

【Java SE8 Silverを受ける意義】
 ・Java SE8の言語仕様を正しく理解しているかの確認
 ・言語仕様を知っているだけではダメ(設計は大切)だなと気づく機会作り
 ・Goldを受験するための権利獲得

ここからも分かるように、Silverを取得するなら学習開始直後が良いかと思います。
(私もJava学習開始1〜2ヶ月ほどで資格試験の学習をはじめました)

勉強期間

 約1ヶ月程度、実質40〜50時間程度の学習時間を確保しました。

勉強方法

 ここからは自身の学習を”振り返って”こうすれば効率よかったな。
 と、個人的に思った勉強方法をご紹介します。
 (私がこの通り勉強した訳ではありません(笑))

 学習教材

  私は以下2つの教科書を使用して学習を進めていきました。
  言わずもがなな2大巨頭です^^
  書籍のタイトルよりも「紫本」や「黒本」と言う呼び方の方が定着していそうですね。

  オラクル認定資格教科書 Javaプログラマ Silver SE 8(紫本)
   Java SE8 Silverの試験範囲を網羅した「教科書」です。
   各章ごとに言語解説・練習問題、最終章は模擬試験という構成になっています。
   内容としては、広く浅くと言う印象を受けました。

  徹底攻略 Java SE 8 Silver 問題集[1Z0-808]対応(黒本)
   Java SE8 Silverの試験内容に沿った「問題集」です。
   教科書と似たような章立てになっており、最終2章が模擬試験という構成になっています。
   問題に対する解説に重点が置かれ、より深い理解を促してくれる印象でした。

 紫本か黒本か問題(?)

  どちらを買えば良いか?と言う話がたまに上がりますが、
  個人的には「学習教材として一方選ベ」と言われたなら「黒本」一択だと考えています。
 
  私が黒本を進める理由は大きく以下2つです。

 【黒本一択な理由】
  ① 実際の試験問題と、ほぼ同じ問題ばかりが掲載されている
  ② 問題に対する解説が、紫本より(かなり)丁寧
※紫本は「教科書」色が強いので、黒本と比較すると解説はそれほどで、教科書自体をおさらいが必要なイメージでしょうか

 <注意点>
  黒本は”試験対策”に重きをおいた書籍だと思います。
  そのため、知識が網羅的な訳でも、体系化されている訳でもありません。
  黒本だけで学習を進めて資格試験に合格しても、その試験に合格できた”だけ”で肝心の言語仕様への理解が伴っていない可能性大です。

  受験の目的が「資格ホルダー」ではなく、純粋に「Javaの知識・技術向上」なのであれば、
  黒本だけ2周、3周して合格するだけではNGと考えましょう〜。

 学習全体の流れ

 ここまでの説明を踏まえ、私はこんな感じで試験勉強を進めていくのが良いのかなと考えています。

  ⓪前提[Java初学本]

  ①[黒本1章〜9章]
   ↓ (1章ずつ繰り返し)
   ↓  ①-1 [黒本1章分]問題を解く
   ↓  ↓
   ↓  ①-2 答え合わせ/回答確認
   ↓  ↓ <補足学習>
   ↓  ↓  ①-2-1 [紫本(黒本の該当章に対応する内容)]又は、ネットで深堀
   ↓  ↓     +
   ↓  ↓  ①-2-2 実機で挙動の確認
   ↓  ↓
   ↓  ①-3 [黒本1章分]2周目
   ↓
  ②[黒本模擬試験①]
   ↓
  ③[黒本模擬試験②]
   ↓
  ④[紫本模擬試験]
   ↓
  ⑤[黒本をもう一周やってみる]3週目(模擬試験だけもありかも)

 各学習の補足説明

 そんなに中身ないですが、各学習の流れごとの補足説明です。

 ちなみに私は、[紫本]→[黒本]の順番、且つ全章通しで学習を進めました。
 ですが、紫本を何の観点や目的意識も持たずに読むと、読了後頭に何も残っていない自分に愕然とすること請け合いです。何せJava初学者本を読んだ後なら「これくらいは知ってるかな〜」ぐらいの軽い気持ちで流し読みしちゃうので^^
 Silverは重箱の隅をつつくような「言語仕様」を問う傾向がある事を念頭に置いて、粘着質に動きを追って行くくらいでなくちゃです!

 また、より粘着質になるなら1章ごとがお勧めかと(笑)

 ⓪ 前提[Java初学本]

  全く無知識からの受験は「別の言語やってました」じゃない限り結構キツイのではないかと思います。まずは「スッキリわかるJava入門」あたりを眺めてみるのも良いかと^^
 (私はサラッと流し読みした程度ですが)

 ①[黒本1章〜9章]

  最初に説明したとおりです!
  ただ、黒本は体系的、網羅的ではありません。
  問題で問われた挙動の周辺仕様や異なるパターンでの動きを確認しておく必要があります。

  特に解説で「何となく理解したつもり」になっている可能性を考慮し、①-2であげたように紫本やネットで知識を補完しつつ、その理解が合っているかを実機で試しながら進めるのがオススメです。

  ※この段階でJava初学者本で知識を補おうとするのはオススメしません。
   なぜならJava SE8 Silverの知識体系と同期をとった章立てとなっていない場合、効率が上がらずストレスを感じる可能性が高いからです。(もちろん読むのは大切です。こちらは効率の観点からだけの意見です)

 <実機確認に便利なサイト>
 実機確認と言っても、環境準備が面倒とか思っちゃうかもしれません。
 ですが、今はクラウド実行環境を無償で提供してくれる素晴らしいサイトも存在するので、うまく活用してみてください^^

 クラウド実行環境paiza.io(https://paiza.io/ja)

paiza.IOはオンラインですぐにプログラミングが始められる、 オンライン実行環境です。C,C++,Java,Ruby,Python,PHP,Perlなど 主要24言語に対応。ファイルアップ機能、外部apiへの接続や、 スクレイピングなども可能です。

 ※ただ、紹介したクラウド実行環境ではデバッグ機能は無いので、痒いところに手が届く…とまでは行きません^^;

 ②〜⑤ [模擬試験]

  最低1回分、模擬試験は試験前日まで残しておく事をオススメします。
  その理由は簡単で、試験前日に「区切り」をつけやすくするためです。「??」と思われるかもしれませんが、Java SE8 Silverは重箱の隅をつつくような問題が出題されます。
  これは、受験時に知識と合わせて「集中力」や「慎重さ」が求められるという事です。
  (特に私の集中力が弱いからかもしれませんが///)

  そのため、試験前日に自分の到達度がわからず、無理して夜更かしする事の無いよう、少なくとも1回分の模擬試験を残しておきます。そして、前日にその模擬試験で到達度を確認し、合格点(正答率65%)に達しているなら無理せず早めに休むのです!これが意外と大事^^
※集中力切れて、引っ掛け問題などにまんまとハマったとか残念すぎます笑

  ちなみに、残しておいた模擬試験の私の初回正答率は70%ほどでギリギリでした!
  ですが、最後に模擬試験の振り返りをして、翌日受験した結果は以下の通り、無事合格できました。
 image.png

 ※正答率としては、あまり高い方では無いです^^;
  会社で研修などを受けて、受験される方は90%がほとんどだとか…?(知らないですが)

試験当日について

あまり意味ないんですが補足です(・ω・)

 え?写真撮るの?

 試験会場では、受付の後に資格者ライセンスに掲載される写真撮影があります!!!
 私は何も考えず、マスク・すっぴん・超ラフな格好で行ったので、若干後悔しました(笑)

 どこかに出るようなものでは無いと思いますが、何となく気になると言う方は、それを念頭に置いて準備してください。

 問題はどこから確認する?

 私は試験問題を確認する際、頭から読むのではなく、public static void main(String args[]){}から確認するようにしていました。その方が「このコードが一体何をしようとしているか?」が把握しやすく、確認効率が上がります。
 ただし、その為に他のクラス確認を疎かにすると確実に引っ掛けにハマるので注意です^^;

受験後にやりたいこと

 Silver受かったならGoldだ〜!
 と、なりそうですが、折角基礎を覚えたのですから少し横道に逸れてみる事をオススメします。特に受験後に「リーダブルコード」を読み返すと、すごく共感できると思います(笑)

 【受験後に読もうとしている書籍】
  ①リーダブルコード   ← 受験後再読
  ②リファクタリング  ← 受験後再読中(2/9時点)
  ③Java言語で学ぶデザインパターン入門

さいごに

 私も、Javaを勉強し始めたばかりの初心者なので、偉そうなことは言えません(言ってるけど(汗)
 そして、Goldに向け絶賛勉強中(上であげた書籍を再読中)です!

 今年中にGOLDも受けたいと思っているので、受験したらGOLD版もまとめようと思います。

おまけ

 試験勉強時に色々調べた内容を別Qiitaとして整理していこうと考えています。
 よろしければ参考にしていただき、間違いなどあれば教えていただけると嬉しいです!!
 (今後、増やして行く予定…)

 【試験対策用のまとめ】
  ・単項演算子(++,--)の前置や後置めも

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

C#とJavaの書き方の違いをまとめる

前置き

いつもはC#で開発をしていますが、使用するAPIにC#用のインターフェースがまだ整っていなくJavaで開発を始めてみました。
あまりJavaは触ったことがなかったため、C#でいう「xxxx」はJavaでいう「xxxx」というのをまとめてみました。
私がよく使っている予約語、実装方法が中心となっています。そのため全ての違いが網羅できているわけではありません。

C#とJavaの予約語、実装方法の違い

予約語の違い

C#、Javaでの予約語の違い、変わらない点をまとめてみました。
同じ、似ていると思った項目を横並びにして比較してみます。

差異 C# Java 備考
namespace package
using import
なし class class
: extends クラス継承
なし interface interface
なし abstract abstract
virtual なし 継承先で関数をoverride可能にする場合、C#はvirtualを付与、Javaはなし
override @Override C#は定義に含めるが、Javaはアノテーション(C#で言う属性、Attribute)
readonly final 読み取り専用フィールド
const final 定数
sealed final クラス継承禁止、Javaは関数のoverride禁止としても使用する
なし Enum Enuml 列挙型
struct なし 構造体

Javaには「struct」、「virtual」がありませんでした。
「struct」はclassでそれっぽく代用できるため問題なさそうですが、「virtual」は無いと少し困りそうです。
「virtual」については下記のタイトルでページの最後に掘り下げてみます。
・「C#のvirtualとJavaのfinal」

また、Javaの「final」がC#の複数の機能を含有しておりました。
それだけでなくJavaのみの使い方があるため、この違いについては下記タイトルでページの最後に掘り下げてみます。
・「定数の宣言、設定方法の違い」

実装方法の違い

C#での実装方法、Javaでの実装方法、どちらにしかない実装方法をまとめてみました。
こちらも同じ、似ていると思った項目を横並びにして比較してみます。

やりたいこと C# Java
属性自作 Attributeの継承 @interfaceでクラス定義
拡張メソッド public static class なし
interfaceのデフォルト実装 なし
C#8.0 (.NET Core 3.0)で追加
interfaceを継承し、メンバをdefaultで定義
リソースの自動解放 using(Stream stream = new Stream()) try-with-resources
型パラメータの制約 where T: int <T extends Number>
上記例)void Test<T>(T arg) void Test<T>(T arg) where T : int <T extends Number> void Test(T arg)
Null許容型宣言 Nullable<T>,T? Optional<T>
ラムダ式 ()=>{ } ()->{ }
コールバック引数なし Action Runnable
同上 引数1つ Action<T> Consumer<T>
同上 引数2つ Action<T1,T2> BiConsumer<T,U>(引数は2つまで)
同上 返り値あり Function<R> Supplier<T>
API仕様の関数型インターフェースを見る限りなし
同上 引数1つ、返り値あり Function<T,R> Function<T,R>
同上 引数2つ、返り値あり Function<T1,T2,R> BiFunction<T,U,R>(引数は2つまで)

今回挙げた機能については使い勝手は違えどC#、Javaの両方が同等の機能を持っていることがわかりました。

また、コメント欄でご指摘頂いている通り、C#にもinterfaceのデフォルト実装機能がC# 8.0より追加されています。
abstractとinterfaceで迷い後者で実装したけど、継承先で同じ実装を行った経験がある人はきっといるはず。私です
これは是非活用していきたいですね。

また、C#で言う「Action」、「Function」、いわゆるコールバックの使い勝手がかなり違いそうです。
「コールバック」については下記のタイトルでページの最後に掘り下げてみます。
・コールバックの使い方の違い

C#とJavaで似ているようで違う点を掘り下げる

「予約語の違い」、「実装方法の違い」で挙げた3つについて掘り下げていきます。

C#のvirtualとJavaのfinal

C#では「virtual」でoverrideを可能にし(正確には仮想メソッドとして実装)、Javaは「final」でoverrideを禁止します。
つまりJavaの関数に「final」を付けなかった場合、C#の感覚で言うと常に「virtual」での実装になります。
いつの間にかoverrideされてる!?といった状況になりたくなければ必ず「final」を付けたほうがよさそうですね。。

※追記
コメント欄でご指摘頂いているように「final」を付与しているかいないかで呼び出しコストが変わってきます。
シビアなメモリ、処理速度が求められる場合は是非付与していきましょう。

コールバックの使い方の違い

C#とJavaのコールバックの違いはざっくりとこんな感じです。
この違いを把握していればC#とJavaで混乱するとはなさそうです。

  • Javaのコールバックは引数の数に上限があり、C#は最大16個まで取れる。

  • Javaのコールバックで数値型を引数に取れないが、C#は全ての型を引数に取れる。(※1)

※1
今回記事に挙げたJavaのコールバックは参照型のみを引数に取れます。
そのためC#と違い、Javaでは数値型を引数に取ることができません。数値が設定できなくて困惑したのは私です
その代わりに数値型用のコールバックがあり下記の名前で定義されているためこちらを使います。
・「数値型」Consumer
・Obj「数値型」Consumer
・「数値型Function」
・To「数値型」Function
・To「数値型」BiFuction

定数の宣言、設定方法の違い

C#の「const」、「readonly」、Javaの「final」を定数、読み取り専用として使用する場合の違いをまとめました。

定数宣言、代入方法 const readonly final
定数フィールドでの宣言
定数フィールドへ宣言場所以外での代入 × 〇 ※1 ×
ローカル変数での宣言 ×
ローカル変数での宣言場所以外での代入 × ×
引数への付与 × ×
宣言時の型制限 あり ※2 なし なし

上記以外の違いとして「final」の機能はコメントでご指摘頂いている通り、再代入の禁止になります。
C#は基本的に宣言場所以外での代入をすることができないため、これに該当する機能はありません。
強いて言うのであれば、ローカル変数でも宣言できる「readonly」といったところでしょうか。
もちろんこんな機能はないため是非、C#にもほしい機能です。

※1 readonlyは宣言時以外、コンストラクタ内でのみ代入可能
※2 宣言できる型についてはこちらconst キーワード - C# リファレンス _ Microsoft Docs

参考にしたページ

Java(tm) Platform, Standard Edition 8 API仕様
Javaのfinalを大解剖 finalの全てがここにある!!

最後に

C#とJavaは言語のベースが同じだから似ていると聞いたことはありました。調べていく中で予約語に同じものが多かったりと確かに似ていると感じました。
また、実際にコードを書かないと気づけない違い(予約語が前後するなどの小さな違い)があったため、知識をため込むだけでなく、アウトプットのすることの大事さを改めて感じました。
ただ似た書き方をしていながら、異なる動きをする場合があるため、エラーが起きた場合に嵌らないようにしたいですね。

あと、何より情報のアップデート。。指摘して頂いた方々ありがとうございます。

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

安易なメソッド参照は気を付けるべし

導入

こんにちは。けちょんです。
皆さん、メソッド参照使っていますか?
便利だし、可読性も上がりますよね。
そんなメソッド参照で。気を付けるべき仕様があったので紹介します。

よくやる使い方

public class Main {
    public static void main(String[] args) throws Exception {
        Arrays.asList("test1", "test2").stream().forEach(System.out::println);
    }
}
// test1
// test2

staticメソッドをメソッド参照していますね。
他にもこんな使い方ができますね。

public class Main {
    public static void main(String[] args) throws Exception {
        Arrays.asList("test1", "test2").stream().map(String::toUpperCase).forEach(System.out::println);
    }
}
// TEST1
// TEST2

StringクラスのtoUpperCaseを呼び出していますね。
見やすくて便利です。

インスタンスメソッドの呼び出し方

また、上ではstaticメソッドを紹介していますが、インスタンスメソッドも使用できます。

class Customer {
    public String getName() {
        return "which1";
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Arrays.asList(new Customer(), new Customer()).stream().map(Customer::getName).forEach(System.out::println);
    }
}
// which1
// which1

同じ記法で書けるんですね。

つまり

以下は同じCustomer::getNameで書けることになります。

map(e -> Customer.getName(e)) // staticメソッドを呼び出す場合
map(e -> e.getName()) // 格納されたインスタンスのインスタンスメソッドを呼び出す場合

ここで疑問

参照できるメソッドが両方あったらどうなるの?
同じ記法でstaticメソッドとインスタンスメソッドを呼び出せることは紹介しましたが、両方あった場合、どちらが使用されるのでしょうか。

class Customer {
    public String getName() {
        return "which1";
    }

    static public String getName(Customer customer) {
        return "which2";
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Arrays.asList(new Customer(), new Customer()).stream().map(Customer::getName).forEach(System.out::println);
    }
}
  1. staticメソッド
  2. インスタンスメソッド
  3. コンパイルエラー
  4. 実行時エラー






正解は、3のコンパイルエラーです!
このようなエラー文が出ます。
Ambiguous method reference: both getName() and getName(Customer) from the type Customer are eligible

日本語訳:
あいまいなメソッド参照:タイプCustomerのgetName()とgetName(Customer)の両方が適格です

どちらを参照すべきかわからないため、コンパイルエラーを出していますね。

結局何を気を付けるべき?

今回は両方のメソッドが参照できるためコンパイルエラーを吐きました。
ただし、以下のように一目では分かりにくい、参照できないパターンがあります。
1. メソッドが可視性により参照できなかった場合
2. メソッド名が微妙に異なる場合
3. メソッドの引数が異なる場合
などなど
どちらのメソッドが参照されるのか、開発者の想定外の動きをする可能性があります。

まとめ

開発者側で実装した型を用いたオブジェクトを扱う場合、メソッド参照は使用を控えた方が無難

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

現場で「StreamAPI使うな」と言われたあなたへ

寒暖差激しいですね。
暖かい日しか出社したくないですが、人は習慣に飼いならされることで怠惰にならなくて済むので、定時出社という悪しきルーティンを受け容れようかなとか思っています。

「ストリームの乱用はプログラムの理解や保守を難しくする」

Effective Java第3版より
"ストリームの乱用はプログラムの理解や保守を難しくする"

いきなりですがこれが結論です。短くて読みやすいコードは素晴らしいです。しかし、StreamAPIは、短くて難解なコード、長くて難解なコードを生み出すことも容易です。私をはじめ、StreamAPIを理解しきれていない人が実装する際は、プログラムの可読性悪化のリスクを常に認識しなければなりません。
これはすなわち現場のプログラマの問題であり、StreamAPIが悪い訳ではないということです。
StreamAPIが書きたければ、場所を変えましょう。あなたの実装したStreamAPIによる簡潔なコードが、コピペによる濫用によってそこかしこに広がり、問題の種になる前に。

短くて読みやすいStreamAPIによる実装

とはいえ、このまま終わってしまっては、StreamAPIを勉強するモチベーションが削がれたままの一部の方に申し訳ないので、StreamAPIの可読性が高くなるようなコードを紹介します。

ここでは、自社の従業員がどの案件を担当しているか知りたいとしましょう。この情報を、特定の案件リストを使って抽出するにはどうしたらいいでしょうか。
実装方針としては、
1, Recordの獲得
2, Recordからキーとして扱う値の取得
3, キー値とRecordが対応したMapを作成する
となるでしょう。
StreamAPIによる実装は以下のようになります

/** 
*  StreamAPIによる実装。
*  あくまで説明コードであり、コネクション操作、例外ハンドリングなどについて実装はしない。
*  @param salescodeList 案件を特定するコード情報を格納したリスト
*  @return <K, V> = {従業員コード, 担当案件のリスト}
*/
public Map<Integer, List<Record>> getSalesListGroupingByEmployeeCode (List<Integer> salesCodeList) {
    List<Record> recordList = searchBySalesCodeList(salesCodeList);
    return recordList.stream().collect(
        Collectors.groupingBy(recordList::getEmployeeCode));
}

まず、レコードのリストを獲得をしました。
そして、獲得したレコードのリストをStreamオブジェクトにします。これで、ストリームパイプラインにて操作が可能になります。
ここで、ストリームパイプラインでやりたいことについて整理しましょう。
1, ストリームをリストに収集したい
2, 従業員コードをキーにして、グルーピング(マッピング)を行いたい
ですので、以下のような実装になるでしょう

// StreamAPIを使用した部分を抜き出した
recordList.stream(). // ListオブジェクトをStreamオブジェクトに変換する
    collect( //ストリームオブジェクトをリストとして収集することを宣言する
    Collectors.groupingBy( // グルーピングを行う
    recordList::getEmployeeCode) // グルーピングに使用するキー値を引数にわたす
    );

StreamAPIは、「なにをするか」に焦点を当てているため、このようなグループ分けや分割はわかりやすい例になるでしょう。

StreamAPIを使用しない実装

StreamAPIによって実装を行ったところで方向転換です。
StreamAPIを禁止して、さきほどと同じ値を返す場合、おそらく以下のような実装になるでしょう。

/** 
*  StreamAPIを使用しない実装。
*  あくまで説明コードであり、コネクション操作、例外ハンドリングなどについて実装はしない。
*  @param salescodeList 案件を特定するコード情報を格納したリスト
*  @return <K, V> = {従業員コード, 担当案件のリスト}
*/
public Map<Integer, List<SalesRecord>> getSalesListGroupingByEmployeeCode (List<Integer> salesCodeList) {
    // このメソッドの戻り値として使用するHashMap
    Map<Integer, List<SalesRecord> result = new HashMap<>();

    List<SalesRecord> recordList = searchBySalesCodeList(salesCodeList);
    for (List<SalesRecord> record : recordList) {
        int employeeCode = record.getEmployeeCode();
        // ここで、すでにKey-Valueのペアが生成されているかどうかを判別する
        // ペアが生成されている場合、resultにすでにペアが存在するため、salesList取得可能である。
        List<SalesRecord> salesList = result.get(employeeCode);

        // ペアが存在しない場合、担当案件を格納するリストが格納できないため、Listを生成する。
        if (salesList == null) {
            salesList = new ArrayList<>();
        }
        salesList.add(record);
        result.put(employeeCode, salesList);     
    }
    return result;
}

行数は増え、やりたいことがあまり明確ではないですね。

StreamAPIを使用するべきかどうか

ここで示した実装例は、Streamで実装したほうがよい例でした。しかし、Streamではなくfor文で実装したほうがよいものも存在します。ストリームパイプラインでの中間操作があまりに多い場合は、ある程度の分割が必要かもしれません。
結局のところ、「プログラマがどちらを好むか」です。情報隠蔽のように、そうすべきことが明らかな事項ではありません。
もしStreamAPIの熱狂的支持者であり、StreamAPIが含まれたコードのレビュー時に「StreamAPIはやめろ」と言われたら癇癪を起こすようであれば、あなたはそこにいるべきではないかもしれません。そのときは、StreamAPIを積極的に取り入れ、コード品質も高く保つことのできる環境が見つかるまで、現場を転々としましょう。

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

JavaFXのWebViewで絵文字が出ない問題への対処

JavaFXのWebViewに文字列を出そうとした時に大抵の日本語は出るのだが、絵文字&他にも一部の文字(?とか ?とか)が出ない問題が起きた。

Javaの内部ではUTF-16で扱っていて、UTF-8にする時にサロゲートペアで扱われている文字がおかしくなるようだ。

HTMLには数値文字参照という文字のコードポイントを直接指定して文字を出す機能がある。
(書き方は&#x{16進}; または &#{10進};)

例えば HTML上に&#x1F4AF; とかくと?になる。

この機能を使い、以下のコードでHTMLに出力される文字列が数値文字参照になるようにして解決した。

    String toCharacterReference(String str) {
        int len = str.length();
        int[] codePointArray = new int[str.codePointCount(0, len)];

        for (int i = 0, num = 0; i < len; i = str.offsetByCodePoints(i, 1)) {
            codePointArray[num] = str.codePointAt(i);
            num += 1;
        }

        StringBuffer stringBuffer = new StringBuffer();
        for (int value : codePointArray) stringBuffer.append("&#x" + (Integer.toHexString(value)) + ";");
        return stringBuffer.toString();
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む