Java silver 勉強中

JavaSilver勉強中。今更感あるけど(笑)

黒本の章末、総仕上げ問題で不正解だったものを実際にコーディングして復習。

①子クラスで継承したメソッドの可視性を厳しくすることはできない

   interface inter {  
        //インターフェースで宣言したメソッドは暗黙的にpublicで修飾される!
        void test();
    }
    
    class impl implements inter {
        //子クラス(実装クラス)側は親クラスで宣言された可視性より厳しい可視性にはできない
        public void test() {
            
        }
        //このメソッド宣言ではコンパイルエラー!
        protected void test() {
            
        }

    }

②起動引数なしのmainメソッド実行 要素数0の配列として使用される。
→argsにアクセスしてもNullPointerExceptionにはならない

③StringBuilderのコンストラク
前提:StringBuilderは内部的で文字列を保持するためのバッファを持つ。
デフォルト(コンストラクタでバッファ数を指定しない)では16文字分のバッファを持つ。

       //sb1はバッファとして16文字分の領域を持つ(デフォルト)
        StringBuilder sb1 = new StringBuilder();
        //sb2はバッファとして30文字分の領域を持つ
        StringBuilder sb2 = new StringBuilder(30);

④LocalDateのインスタンス生成時に不正な日付を与えるとExceptionが発生する

     //2017年1月32日は存在しない日付
        LocalDate date = LocalDate.of(2017, 1, 32);

結果

Exception in thread "main" java.time.DateTimeException: Invalid value for DayOfMonth (valid values 1 - 28/31): 32

⑤継承関係にあるクラス構造でのメソッド実装義務があるのは実現クラス

   interface ParentInterface {
        void test();
    }
    
    interface childInterface extends ParentInterface {
        void childTest();
    }
    //このクラスではインターフェースのメソッドを実装しなくてもコンパイルエラーにはならない   
    abstract class parentClass implements childInterface{
        
    }
    //このクラスはインターフェースのメソッドを実現する必要がある
    class child extends parentClass {
            //コンパイルエラー!!
    }

⑥アクセス修飾子のレベル
public → 全クラスからアクセス可能
デフォルト(指定なし)→同一パッケージ、同一クラス内
protected → 同一パッケージ、同一クラス、サブクラス
private → 同一クラス内のみ

⑦switch文で使用できる型
byte,short,int,char,String,Byte,Short,Integer,Character,列挙型(Enum)

⑧配列の宣言

       int[] array = new int[3];
        int[] array2 = {1,2,3,};
        int[] array3;
        array3 = new int[]{1,2,3,4,};
                        
        int[] array4;
                //コンパイルエラー
        array4 = {1,2,3,};

ArrayListのremoveメソッド

       List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("A");
        //#equalsで一致したオブジェクトで初めに合致したオブジェクトを削除する
        list.remove("A");
        //要素0のAのみが削除対象となる
        System.out.println(list);

結果

[B, C, D, A]

⑩staticイニシャライザ
staticなフィールドに対して何らかの初期化処理を行う場合に使用する。
static変数は使用しないで、例としてstaticイニシャライザで標準出力。

public class Main {
    
    static {
        System.out.println("first");
    }
    
    static {
        System.out.println("second");
    }

    public static void main(String[] args) {
        System.out.println("in main method");
        
    }
}

結果

first
second
in main method

⑪arraycopyメソッド

       
        char[] array = "hello world".toCharArray();
        char[] array2 = new char[array.length];
        //(コピー元、コピー元のどこからコピー開始するか、コピー先配列、コピー先配列のどこからコピー開始するか、第2引数の位置からいくつの要素をコピーするか)
        System.arraycopy(array, 0, array2, 1, 5);
        
        System.out.println(array2);

結果

□hello□□□□□

オーバーロード時の呼び出されるメソッドの優先順位

   public static void main(String[] args) {
        System.out.println(calc(5, 8));
    }

    //呼び出し側で渡された実引数の個数にマッチする仮引数を受け取るメソッドが優先されて呼び出される
    static int calc(int a, int b){
        System.out.println("2arg");
        return a + b;
    }
    
    static int calc(int... ints){
        System.out.println("varargs!");
        return IntStream.of(ints).sum();
    }

結果

2arg
13

今日はこれくらいで。
javaそこそこできると思っていたけど、まだまだだ。
勉強勉強。

再帰処理

1. フィボナッチ数列

   //再帰を使用した場合
    static int fibonacci(int n) {
        
        if (n < 2) {
            return 1;
        }
        
        return fibonacci(n - 2) + fibonacci(n - 1);
        
    }

    //ループを使用した場合   
    static int fibonacci2(int n) {
        if (n < 2) {
            return 1;
        }
        
        int fibo1 = 1, fibo2 =1, fibonacci = 1;
        for (int i = 3; i <= n; i++) {
            
            fibonacci = fibo1 + fibo2;
            fibo1 = fibo2;
            fibo2 = fibonacci;
        }
        return fibonacci;
    }
    //呼び出し側
    public static void main(String[] args) {
        
        for (int i = 0; i < 10; i++) {
            System.out.println(fibonacci(i));
        }
        System.out.println("------------------------------------");
        
        for (int i = 0; i < 10; i++) {
            System.out.println(fibonacci2(i));
        }
        
    }

2. 階乗

    static int factorial(int n ) {
        if (n < 2) {
            return 1;
        }
        return n * factorial(n - 1);
    }

for文の挙動について

javaのfor文の挙動について。 初歩レベルだけどあまり意識しないで使っていたことが判明。 更新処理って一番最後だったんですね。

   public static void main(String[] args) {
        for (int i = initialize(); condition(i) ; update()) {
            System.out.println("ループの中");
            i++;
        }
    }
    
    static int initialize() {
        System.out.println("初期化");
        return 0;
    }
    
    static void update() {
        System.out.println("更新");
    }
    
    static boolean condition(int i) {
        System.out.println("条件判定");
        return i < 3;
    }

結果

初期化
条件判定
ループの中
更新
条件判定
ループの中
更新
条件判定
ループの中
更新
条件判定

変数初期化後、 条件判定 → ループ内処理 → 更新文実行 のように動くようです。

Javaプログラマが2018年に学ぶべき10のこと

元ネタ:10 things Java Programmers Should Learn in 2018

と、いうわけでよく読むJavarevisitedというブログで気になる記事があったので。

一覧

こんなにあるのか・・・。 ま、ひとつひとつやっていきますかね。

java 正規表現

今更だけどjava正規表現
今回は携帯電話番号の形式になっているかを調査する。

java正規表現を用いた入力値チェックをするときには
String#matchesメソッドを使用
Patternクラス・Matherクラスを使用する
の2通りが考えられるので1つずつ見ていく。

①String#matches

String target = "080-1234-5678";
target.matches("0[7-9]0-[0-9]{4}-[0-9]{4}"); //trueを返す

※String#matchesは「完全一致」のみtrueとなるので注意

String target = "0080-1234-5678";
target.matches("0[7-9]0-[0-9]{4}-[0-9]{4}"); //falseを返す

②PatternとMatcher

String target = "080-1234-5678";
Pattern p = Pattern.compile("0[7-9]0-[0-9]{4}-[0-9]{4}");
Matcher m = p.matcher(target);

m.find(); //trueを返す

Matcherクラスにはfind以外にもいろいろ便利そうなメソッドがある。 目的によって使い分けする感じか。

Matcherいろいろ

①置換
//パターンに一致した文字を置換する
String str = "aabWooabWoobWooab";
//「aが0個以上b」の正規表現
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher(str);
//Matcher#replaceAllを使用して正規表現に合致した個所すべてを置換する
String replaced = m.replaceAll("-");
//-Woo-Woo-Woo-が出力される
System.out.println(replaced);

//Matcher#replaceFirstを使用して正規表現に合致した最初の個所を置換する
//-WooabWoobWooabが出力される(最初だけが「-」で置換される)
String replaced = m.replaceFirst("-")
②Matherの文字列表現
//#toString()呼び出すだけなんだが
System.out.println(m.toString());

結果

java.util.regex.Matcher[pattern=a*b region=0,17 lastmatch=aab]

なにこれわからん・・と一瞬思ったが割と単純。
要は最後に使用した時の結果を出している。 最後に使ったのがString replaced = m.replaceFirst("-")だったので、
pattern=a*b → こわは言わずもがなターゲットとする正規表現
region=0,17 → マッチさせた文字列の対象領域(aabWooabWoobWooabの始点0と終点17)
lastmatch=aab → 最後にマッチした個所(findFirstを最後に使用したのでaabがlastmatchになる)

今日はここまで!

現場で役立つシステム設計の原則

これ読んでる。

オブジェクト指向におけるよりよい設計について書いてある。 データモデル主導の設計ではなく、ドメインモデルを使用した設計によるメリットなど。

まだ途中だが、私にとっては実に勉強になる。というか面白い。 現在私が携わっているシステムは「データモデル」を元にした設計になっており、いろいろな箇所でコードの重複などが目立つ。 どうしたものかと考えていたところでこの本を手に取り、そもそも「データモデル」になっている時点で修正のしづらい・重複の出やすいものになっていることが判明。(泣)
今更どうすることもできないが、せめてドメインモデルの知識を身に着けようと読み進めることにする。

さて、そもそもデータモデルとドメインモデルって何が違うの?ってところで簡単にプログラムを書いてみる。 「人」を表すクラスを使用する場合に・・・
①データモデルの場合

   public Person() {
    }
    
    //よくあるセッター・ゲッター
    String name;
    int age;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

ドメインモデルの場合

   String firstName;
    String familyName;
    int age;
    
    public Person2(String firstName, String familyName, int age) {
        this.firstName = firstName;
        this.familyName = familyName;
        this.age = age;
    }
    
    boolean isAdult() {
        return this.age > 19;
    }
    
    String getFullName() {
        return this.familyName + this.firstName;
    }

一目瞭然。ドメインモデルの場合はオブジェクト自身に処理を記述している。 で、これの何が嬉しいかって話になるんだが、まさに「プログラムの修正が必要になったとき」に大きな違いになってくる(らしい)。

データモデルの場合は「成人である」かの判定ロジックはこのように各機能ごとに書く必要がある。
(条件記述が必要になる)

    if(person.getAge() > 19) {
    ///なんかする
    }

ドメインモデルはこう。判定自体はオブジェクト内部で行っている。

    if(person.isAdult()) {
    ///なんかする
    }

で、何が問題になるかというと、例えば将来「成人年齢を18歳に引き下げ」みたいなことになった場合に・・ データモデルの場合は

if(person.getAge() > 19)

↑ここをすべて17に修正しないといけない。 Personを呼び出すことのできる機能すべて。

一方で、ドメインモデルの場合は、Personクラスのメソッドの個所のみを修正すればいい。

    //PersonクラスのisAdultメソッドの境界値のみを修正する
    boolean isAdult() {
        return this.age > 17;
    }

    //呼び出し側には何の修正もいらない
    if (person.isAdult()) {
    }

つまり、修正部分がドメインモデル(Person)内にとどまるということ。
データモデルでよくある(かもしれない)「あっちは直したけどこっちは直してませんでした」みたいなことも減る。

さらに言うと「Person」クラスそのものが業務アプリケーションにおける用語や処理対象としてそのまま 扱えるってことも大きなメリット。
例えば上記は成人であるか否かを表していただけだったが、「人」という役割を考えたときに何ができるかなどを そのままオブジェクトに持たせることができる。

//被選挙権
public boolean hasEligibilityForElection() {
    //選挙によってまちまち違うがこれにしとく
    return this.age > 24;
}

上記の例はドメインモデルにおけるメリットのほんの一部分にかもしれないけど、わたしにとってはすごく ためになった。 いや、たぶんオジェクト指向について詳しい人とか、ドメインモデル設計をしている人にしたら普通のことかもしれないけど、 私としては目からうろこだったわけです。

こういう設計をやってみたい。とはいえ今のシステムについていきなりドメインモデルに変えたいなんて無理だしなぁ。 というかStrutsとか使っている時点で難しいのかな。 こっそりDTOやらEntityやらにメソッドを書いてみたりしてみようか。(アカンか)

ただ正直ドメインモデル設計ができる気もしないんだけどな(笑)。難しそうだ。

何はともあれまだこの本も第4章の途中までしか読んでないし、まずは最後までしっかり読みますかね。 まだまだ面白いことを学べそう。

Streamを使ったgroup化

まずは実際のソースコード
今回は(ちょっと古いが)「このマンガがすごい」をオブジェクトにしてみた。

       ComicRank cr1 = new ComicRank(2015, "こえのかたち", Genre.Men, 1);
        ComicRank cr2 = new ComicRank(2015, "魔法使いの嫁", Genre.Men, 2);
        ComicRank cr3 = new ComicRank(2015, "子供はわかってあげない", Genre.Men, 3);
        ComicRank cr4 = new ComicRank(2015, "ちーちゃんはちょっと足りない", Genre.Women, 1);
        ComicRank cr5 = new ComicRank(2014, "暗殺教室", Genre.Men, 1);
        ComicRank cr6 = new ComicRank(2014, "坂本ですが?", Genre.Men, 2);
        ComicRank cr7 = new ComicRank(2014, "亜人", Genre.Men, 3);
        ComicRank cr8 = new ComicRank(2014, "さよならソルシエ", Genre.Women, 1);
        ComicRank cr9 = new ComicRank(2014, "ときめきトゥナイト", Genre.Women, 2);

        List<ComicRank> list = new ArrayList<>();
        list.addAll(Arrays.asList(cr1, cr2, cr3, cr4, cr5, cr6, cr7, cr8, cr9));
        //リストを年ごとに名前でグループ化する
        Map<Integer, List<String>> yearsBookNameList = 
                list.stream()
                .collect(
                        Collectors.groupingBy(ComicRank::getYear, Collectors.mapping(ComicRank::getBookName, Collectors.toList())));
        
        System.out.println(yearsBookNameList);

結果

{2014=[暗殺教室, 坂本ですが?, 亜人, さよならソルシエ, ときめきトゥナイト], 2015=[こえのかたち, 魔法使いの嫁, 子供はわかってあげない, ちーちゃんはちょっと足りない]}

肝になるのはここ。 Collectors.groupingBy(ComicRank::getYear, Collectors.mapping(ComicRank::getBookName, Collectors.toList())));

何をしているのかというと、 groupingBy(arg1, arg2)というメソッドに、第一引数にはグループ化の基準となる値(この場合年)を渡し、
第2引数に、mapping()メソッドの結果となるCollectorを渡している。 んで、mappingには(マッピング関数、各要素が蓄積される型に変換するCollector)という形で使う。 今回はmapping(本の名前、リスト化)という具合。

では次に、各年の1位を{年:タイトル}(ジャンルは男のみ)で絞ってみる。

       //各年の、男編1位のコミック名を出す
        Comparator<ComicRank> byRank = Comparator.comparing(ComicRank::getRank);
        Map<Integer, Optional<ComicRank>> noOneMap =
        list.stream()
        .filter(o -> o.getGenre() == Genre.Men)
        .collect(groupingBy(ComicRank::getYear, reducing(BinaryOperator.minBy(byRank))));
        
        System.out.println(noOneMap);

結果

{2014=Optional[2014年1位:男編は[暗殺教室]], 2015=Optional[2015年1位:男編は[こえのかたち]]}