疲れたらやすむ

Javaを学ぶ上でハマったところを書いていきます。iPhoneアプリ開発や日常ネタもあるかも。

【Java】いつも悩む正規表現

今回は正規表現についての記事になります。

たまに使う機会があるのですが、毎回1時間ほど試行錯誤してしまうのでメモ的な意味も含めて。
ちゃんと理解しないと、意外と扱いが難しいと感じます。

正規表現とは

説明しなくてもご存知だとは思いますが。

正規表現とは文字列のパターンを表現したものです。

何となく意味は伝わりますかね。
言葉よりも実物をお見せした方が早いかもしれません。

例えば、0〜9の数字1文字のパターンを正規表現で表すと[0-9]となります。
かなり初歩的なやつです。

上記の正規表現では、例えばJavaで言う所の

String s1 = "1"
String s2 = "123"

など文字列の中に含まれている数字1文字ごとにマッチします。
文字列の中の数字は何がありますか?との問いに対する答えみたいな感じですね。
s1は1だけ、s2は1と2と3があります。それぞれ[0-9]の正規表現にマッチします。

ただし。
Javaの場合は正規表現を扱う場面がいくつかあります。
単純に文字列の中にマッチする文字が含まれているか、もしくは文字列全体がマッチするか。
そして、正規表現に一致する文字列だけを置換したい場合など様々。

まずは用途別に見ていきましょう。

文字列に正規表現が含まれているか

PatternとMatcherを用いて判定します。

Main.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {
		String s1 = "1";
		String s2 = "ABC9";
		String s3 = "ABCDE";

		Pattern pattern = Pattern.compile("[0-9]");

		Matcher matcher1 = pattern.matcher(s1);
		Matcher matcher2 = pattern.matcher(s2);
		Matcher matcher3 = pattern.matcher(s3);

		System.out.println(matcher1.find()); // true
		System.out.println(matcher2.find()); // true
		System.out.println(matcher3.find()); // false
	}
}

Patternクラスで正規表現を定義します。
次に、正規表現を定義したPatternクラスのmathcerメソッドの引数に判定を行う文字列を渡しMatcherを生成します。
生成したMatcherのfind()メソッドでは、文字列が正規表現を含んでいればtrueを返します。

文字列が正規表現とすべて一致するか

こちらもPatternとMatcherを用います。

Main.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {
		String s1 = "1";
		String s2 = "ABC9";
		String s3 = "ABCDE";
		String s4 = "123";

		Pattern pattern = Pattern.compile("[0-9]");

		Matcher matcher1 = pattern.matcher(s1);
		Matcher matcher2 = pattern.matcher(s2);
		Matcher matcher3 = pattern.matcher(s3);
		Matcher matcher4 = pattern.matcher(s4);

		System.out.println(matcher1.matches()); // true
		System.out.println(matcher2.matches()); // false
		System.out.println(matcher3.matches()); // false
		System.out.println(matcher4.matches()); // false
	}
}

Matcherクラスのmatchesメソッドで判定します。
[0-9]は0~9までの数字1文字に合致する正規表現なので、123は合致しません。
123に合致させたい場合は、例えば

Pattern pattern = Pattern.compile("[0-9]*");

としてあげると合致します。
ただし012などの0から始まる数字のみの文字列も合致してしまうので使用用途には注意。
この辺はいつも悩んじゃいます。

そして、PatternとMatcherを使わない方法も存在します。
こっちの方が簡単ですね。

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "123";		
		System.out.println(s.matches("[0-9]*")); // true
	}
}

これだけです。

どっちが良いかはお好みですかね。
あとは、業務の場合はコーディング規約とか他のソースでどうやっているかで使い分けると良いと思います。

正規表現に一致した部分を取り出す

文字列の中で正規表現と一致するものを取り出す方法です。

Main.java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {
		String s = "123";

		Pattern pattern = Pattern.compile("[0-9]");
		Matcher matcher = pattern.matcher(s);

		while (matcher.find()) {
			System.out.println(matcher.group());
		}
	}
}

実行結果

1
2
3

findメソッドで一致する部分だけループさせ、groupメソッドで取り出します。

正規表現と一致する箇所を置換する

一致した箇所だけを他の文字に置換させます。

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "123ABC";
		System.out.println(s.replaceAll("[0-9]", "★"));
	}
}

実行結果

★★★ABC

StringのreplaceAllメソッドを使用します。
第1引数に正規表現、第2引数に置換後の文字を指定します。


ちなみにreplaceFirstメソッドで同じことをすると。

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "ABC123";
		System.out.println(s.replaceFirst("[0-9]", "★"));
	}
}

実行結果

ABC★23

正規表現と一番最初に一致した箇所が置換されます。

replaceAllやreplaceFirstは、Matcherのfindメソッドと同等の条件となります。
そのため1文字ごとに正規表現と一致するか判定し、一致している場合は置換する動きをします。
もし、123を★に置換したいのであれば

Main.java

public class Main {
	public static void main(String[] args) {
		String s = "123ABC";
		System.out.println(s.replaceAll("[0-9]+", "★"));
	}
}

実行結果

★ABC

こんな感じの正規表現になります。
+は1回以上の繰り返し、*は0回以上の繰り返しを意味しています。
[0-9]*でも良いのでは?と思っていたのですが、試してみるとAとBの間などにも★が入っちゃいました。
0回以上なので、文字の間も合致しちゃうんですかね。

個人的な正規表現まとめ

今まで苦労した正規表現をいくつかまとめておきます。

1から100までの数値
[1-9]|[1-9][0-9]|100

1から350まで
[1-9]|[1-9][0-9]|[1-2][0-9]{2}|3[0-4][0-9]|350

1から100までの数値
[1-9]|[1-9][0-9]|[1-9][0-9]{2}|1000

数値の場合、型が変わるところで区切って考えるとわかりやすいと思います。
|(バーティカルバー)で区切ることで、「または」の条件として正規表現を記述出来ます。


ちなみに、正規表現を試すとき、1回1回書いてビルドして実行・・・とやっていると時間がかかってしまいます。
そんな時は、Web上で正規表現が完全一致、もしくは部分一致するかを試すことが出来るサイトがあります。

Regex Test Drive | 正規表現オンラインテストサイト

ここで試行錯誤してみるのも良いかもしれません。

エンジニアとして新しい道に挑戦

すっかり梅雨ですね。
でも今年は例年に比べ、降水量は少なめでしょうか。
ずーっと降っている感じではないです。

この度、というか最近。
エンジニアとして次のステップへ挑戦しています。

サーバサイドからフロントエンドへ

私は今までサーバサイドの開発を行なっていました。

サーバサイドとは、DBへの読み書きやファイルの入出力などの処理を担うものです。
画面のボタンをクリックしたりすることで走る処理ですね。
言語で言うとJavaを触っていました。

それに対しフロントエンドとは、画面側の開発を行うことです。
HTMLやCSS、JavaScriptなどを扱います。
実際に目で見える部分であり、私はフロントエンドの方がやっていてやりがいを感じます。


私はITの世界に入ってすぐ、Javaを使ってサーバサイド開発を行いました。
1年半くらいですかね。
画面を触ることはなく、ひたすら中の処理を書いていました。
実際には設計の時間も結構多く、どちらかと言えばプログラムを触っている時間の方が短かったかも。

そして最近、ついに待望のフロント側を経験させてもらえることに。
やったね!!

フロントってどうなの?楽しい?

私は好きです。フロント。

なんでしょう。
今までサーバサイドばかりだったので新鮮に感じるからなのかもしれません。

ただ、個人的にHTMLやCSS、JavaScriptの知識を蓄えられるのはすごく嬉しいです。
Webアプリケーションを作ったり、ブログのデザインをカスタマイズしたりといろいろ役立ちそう。
副業とかするにしてもフロントエンドの経験は活きてきそう。

HTMLは個人的に馴染みがあるので、色んなページのHTMLを見ながら書き進められました。

でもJavaScript。
お前は聞いてないぞ!

実務経験はないですが、研修でJavaScriptは一通り学習しました。
でも苦手だったなー。
そのままのイメージを持ち続けているので、やっぱり苦手意識のままです。

これからJavaScriptの記事も少しずつ書いていこうかな。
何やらやることが多くて手詰まり感はありますけど。
Xcodeでアプリ作成やJava Goldの勉強。
どれをいつやろうかって感じですね。

話が脱線しましたが、楽しいけど難しいっていう先入観はありますね。
結局は何事も慣れが必要なのでひたすら経験を積むしかないですが。

フロント側は値の受け渡しとか頻繁にあって大変だと感じます。
HTMLからJavaScriptへ渡したり、JavaからHTMLへ渡したり。
どこがどの値を紐付いてて・・・というのを考えるとちょっと頭が痛くなります。

今後はどうなりたいか

エンジニアとしては、画面を作るフロントエンドと内部の処理を作るサーバサイド。
そしてサーバの構築を行なったりするインフラもあります。

私の目標は、全て1人で出来る様になること。

そんなに簡単な話ではないと思いますが、そうなりたいと思っています。
単純に色んな世界を知りたいのもありますし、1人でWebアプリケーションやスマホ向けアプリ開発をやりたいからです。

ただ、まずはフロント+サーバの処理を書ければ良いかな。
細かいことは抜きにして、画面と画面の項目に紐付く内部処理を書く。
それが出来れば半人前くらいにはなれそう。


私はここ数年で仕事とか技術に対する考え方が結構変わりました。
以前は仕事なんてお金をもらうためにやっているので、そこそこ頑張っていれば良いと思っていました。
もちろん自主的に勉強なんてせず、すべて業務時間の中で調べたりしていました。

でも今は将来の自分のために必死で業務に取り組んでいます。
今経験を積んでいることは、必ず自分の力になり活かせる時が来るからです。

だから家で業務でつまづいたことを予習したり復習するのは厭わないです。
むしろ詰まって良かった!くらいの気持ち。
わからないことを1つずつ覚えていけるのは非常にありがたいことです。

これからも目標に向かって頑張ろうと思います。

【Java】その条件分岐は必要?その変数は必要?

職場でコーディングをしていてふと思いました。

この変数って定義する必要があるのかなあ・・・。
定義しなくても書けるし、かと言って定義した方が何回か使いまわせる。

今回はそんな必要か必要じゃないかの判断に迷う場合について考えてみます。

必要かどうかの判断に迷う状態とは?

まず私が判断に迷う状態。

例えばこんな時です。

ソース

String[] array = new String[] { "A", "B," };

if (array.length > 0) {
	for (String s : array) {
		// 処理
	}
}

for文に入る前のif文ですね。
配列のサイズが0よりも大きければループを行う条件になっています。
一見すると理にかなっているような気もしますが、実際のところこの条件が無くてもちゃんと動きます。


変数の場合はこんな時に迷っちゃいます。

ソース

Student student = new Student("Taro", 1);

String name = student.getName();

if (null != name) {
	String str = name;
}

Studentクラスは内容の内容は省きますが、よくあるBeanのクラスです。
String型のnameとint型のidをプライベートなフィールドに持ち、ゲッターセッターが備わったやつ。
そこから、nameを一度変数に代入し、以後はその変数を条件の判定し使ったり処理に使ったりします。

ゲッター程度なら、毎回ゲッター呼んでもいいような気がしちゃいます。

私個人の考え

極論で言うと、プログラムは動けば良いのです。
プログラムが正常に動いている以上それは正解。

ただし、メンテナンスのコストとか他の人が目にする場合は可読性を少しでも高める必要があります。
基本的に会社で開発を行う場合はほとんど可読性も求められるでしょう。

以上のことから、結局は正解は時と場合によるし、これが正しいという決まりはないと思います。

でも強いて言えば、自分だけが目にするソースだとしても可読性が高いに越したことはありません。
ですので、そう言った意味では常に読みやすいコードを記述したいですよね。

基本的には、ソースは短い方がコンパクトで読みやすいと言えると思います。
しかし無理やりステップ数を減らすよりは、冗長に感じても書いておいた方が良いif文や変数もあるでしょう。
そのあたりは経験とかコーディング規約を神様にして書いていくしかないです。

それを踏まえて、先ほどの判断に迷うソースをもう一度見直して記述してみます。

判断に迷ったらこんな感じで落ち着いた

まずは最初のif文の例。

配列に対しサイズを見てからfor文で回すのは、私は不要だと思います。
サイズが0より大きければ回す場合はですけど。

ソース

String[] array = new String[] { "A", "B," };

for (String s : array) {
	// 処理
}

この状態で意味は伝わるんじゃないかなと思います。
for文の仕組み上、サイズがもし0だったとしても例外は投げませんし、1回もループせずに次の処理に移ります。


そして変数の場合。
これは結構判断が難しいですね。

今回の場合はゲッターで代入して使い回しているので、ぎりぎり不要かなと思います。

ソース

Student student = new Student("Taro", 1);

if (null != student.getName()) {
	String str = student.getName();
}

クラスの変数自体はstudentとして定義していますし、それを使ってゲッターを呼ぶのであれば毎回呼んでも良さそう。
でも、その後も4回、5回と使うなら変数に保持するのもアリかな・・・
2回や3回使う程度ならゲッターで済ましてしまうと思います。

変数に保持しない理由としては、ゲッターはメンバ変数をただ引っ張ってくるだけの処理だからです。
パッと見ても「ああ、これはクラスの中のフィールドを取り出しているだけなんだろうな。」と予測が付きますし、メソッドの処理もメンバ変数のreturnだけ。
それなら何回か使用されていても別に良いんじゃないかなという考えです。

ただし、処理が複雑なメソッドの戻り値の場合は変数に代入しておくことを薦めます。
理由はずばり、その方がわかりやすいと思うからです。

処理が複雑なメソッドを何回も呼び出すと、そのメソッドの処理を追う機会が多くなります。
呼び出す度に処理を追っていては、ソースの全貌を把握するのも時間がかかってしまいます。
多分処理的にも、同じ戻り値を扱うなら変数に格納して使った方が処理速度は早いはず。

迷わないためにもブレないルールを決める

開発には時間が付き物。
設計に時間をかけてしまうと、開発工程の工数が削られてしまう場合もあります。
そんな時にちょっとした迷いで時間を割くのはなるべく避けたいですよね。

なので、自分の中のコーディング規約をある程度決めておくと良いと思います。

その日その日で思いのままにコーディングをしてしまうと、どうしても仕上がったソースに統一感が無かったり。

ちょっと趣旨はずれますが、コメントを記述する「//」のあとに半角スペースを入れたり入れなかったりするのを統一するだけでもかなり綺麗に見えますよ。
ちなみに私は半角スペース入れる派です。

それはそうと、途中にも書きましたがプログラムなんて動いていればOKという考え方もあります。
あまり深くは考えず、自分なりの綺麗を目指しても良いのではないでしょうか。

どんどんコーディングをしていくうちに自分の定型が見つかると思います。
そしていろんな人のソースを見る。これ大事。
そこから得られるものってかなりあります。

お手本をする人を探して真似しちゃうのも全然アリ。
私は結構人のをパクっちゃうタイプなので、職場ではソースを眺めている時間が長かったりします。
そこから自分のルールを作り、この時はこう書く!と決めておくと良いと思います。

住民税の期別に第1期がない?

技術ネタではないですがちょっと気になったこと。

個人で住民税を納付されている方は6月末が第1期の納期です。
いよいよこの時期がやってきましたね。

見たくもない課税課からの封筒が、先日届いてしまいました。
中身は納税通知書と支払い用紙。

しかし、良くも悪くも私の手元に届いた納税通知書には第1期がありませんでした。

届いた納税通知書に第1期の記載がない

f:id:chibiCat:20190626003138j:plain

ご覧の通り、届いた納税通知書には第1期の記載がありません。
そして納税通知書と一緒に、第2期から第4期までの3回分の支払い用紙が同封されていました。

まさか第1期は払わなくても良い?なんて思いが頭をよぎります。

とは言え、仮に第1期に支払なくとも、その分を第2期以降に割り振られるのかな?とも考えました。
そもそもまだ払わなくて良いとは決まってもいない。

気になってしょうがなかったので課税課の方に聞いてみました。

第1期の支払いはどうするのか?

結論から言いますと、第1期の支払いはありません

ただし、です。
本来納税するはずのお金が闇に葬られるわけではありません。
第1期で支払う分は第2期から第4期までにほぼ均等に割り振られており、結果として支払うこととなります。
ですよねーって感じでした。

でもどうしてこんなことになったのでしょうか。

第1期がない理由

その原因は退職時期でした。

住民税を給料から天引きにしている状態で退職してしまうと、残った分を個人で納めなくてはなりません。
その際に、支払う金額の計算が第1期に間に合いそうにない場合は第1期が消えるみたいです。
私の場合は5月末に離職して第1期に間に合いませんでした。

時期が時期なだけに、6月末期日の第1期の分がない!?と少し焦ってしまいました。
6月以降であれば、既に第1期の支払い期日が過ぎているので第2期からなのはわかるのですが。

少しレアなケースだと思いますが、そんな理由でした。

【Java】TreeSetで独自クラスを扱う

今回はTreeSetに的を絞って解説していきます。

Setとは?ListやMapとの違い

Setとはコレクションの1つで、重複した値を持たない特徴があります。

Main.java

import java.util.HashSet;
import java.util.Set;

public class Main {
	public static void main(String[] args) {
		Set<String> set = new HashSet<String>();
		set.add("apple");
		set.add("orange");
		set.add("apple");

		for(String s : set) {
			System.out.println(s);
		}
	}
}

このように同じ値をaddした場合しても

実行結果

orange
apple

重複している値は追加されていません。

Listはaddすれば問答無用で追加するのに対し、Setはaddしても重複する値があれば要素に追加されません。

Mapと比較してみると、重複する値を持たないと言う部分ではキーと似ているかもしれません。
ですが、Mapはキーに対して値を持っているため、どちらかと言えばListの方が似ているかも。

Setの種類について

Setの代表的なものとして、HashSetとTreeSetが存在します。

基本的な理解としては、
「HashSetは格納された要素をソートしないがnullを格納できる」
「TreeSetは格納された要素をソートするがnullは格納できない」
で良いかと思います。

他にもLinkedHashSetと言うものがありますが、全く使ったことがないのでよくわかっていません。
処理速度とかの話なんですかね。

TreeSetの使い方

TreeSetは格納された要素をソートするため、ソート順を決める必要があります。

IntegerやStringなどの基本的な型の場合はそのまま使用可能。
その理由は、IntegerやStringのクラス定義でComparable<T>インターフェースを実装し、compareToメソッドでソート方法を宣言しているからです。

Integerの場合は、数値が小さい順でソートされます。

Main.java

import java.util.Set;
import java.util.TreeSet;

public class Main {
	public static void main(String[] args) {
		Set<Integer> set = new TreeSet<Integer>();
		set.add(3);
		set.add(1);
		set.add(2);

		for (Integer i : set) {
			System.out.println(i);
		}
	}
}

実行結果

1
2
3

Stringの場合はアルファベット順。

Main.java

import java.util.Set;
import java.util.TreeSet;

public class Main {
	public static void main(String[] args) {
		Set<String> set = new TreeSet<String>();
		set.add("B");
		set.add("A");
		set.add("C");

		for (String s : set) {
			System.out.println(s);
		}
	}
}

実行結果

A
B
C

独自クラスでTreeSetを扱う場合

まずソート順を指定しなかった場合どうなるか見てみます。

とりあえず独自クラスを用意します。

Student.java

public class Student {
	int id;
	String name;

	public Student(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return this.id;
	}

	public String getName() {
		return this.name;
	}
}

そしてそのままTreeSetに格納してみます。

Main.java

import java.util.Set;
import java.util.TreeSet;

public class Main {
	public static void main(String[] args) {
		Set<Student> set = new TreeSet<Student>();
		set.add(new Student(2, "Jiro"));
		set.add(new Student(1, "Taro"));
		set.add(new Student(3, "Saburo"));

		for (Student s : set) {
			System.out.println(s.getId() + " : " + s.getName());
		}
	}
}

実行結果

Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable

はい。
ClassCastExceptionが発生します。
これは、TreeSetで要素を並び替える際にComparable<T>型にキャストを行う処理があるためです。
つまりキャストを行うには、独自クラスにComparable<T>を実装する必要があります。

Student.java

public class Student implements Comparable<Student> {
	int id;
	String name;

	public Student(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return this.id;
	}

	public String getName() {
		return this.name;
	}

	@Override
	public int compareTo(Student o) {
		return this.id - o.id;
	}
}

これで実行してみます。
実行結果

1 : Taro
2 : Jiro
3 : Saburo

ちゃんとソートされていますね。

ちなみに、独自クラス側でComparable<T>を実装しないパターンもあります。
その場合は、使用する側でソート順を指定します。

Set<Student> set = new TreeSet<Student>(Comparator.comparing(Student::getId));

これでStudentにComparable<T>が実装されていなくても、idの昇順で問題なく動作します。
関数型インターフェースやメソッド参照が出てきて少しややこしい部分はありますが。

このあたりはJava Goldでも出題される範囲なのでぜひ押さえておきたいところです。