- 投稿日:2020-03-27T22:38:46+09:00
【Java】Listの使い方【ArrayList】
Listとは?
複数の要素を含み、順序のつけられたコレクションのこと。
インターフェースのため、実装することで使用可能となる。コレクションとはなんぞや?ってことで調べてます。
コレクション:オブジェクトの集合を扱うための仕組み。以下の種類がある。
List系
- ArrayList 配列を扱う。
- LinkedList 配列を扱う。挿入・削除が高速。
- Vector 配列を扱う。パフォーマンスが悪いため現在ではあまり推奨されない。
Set系
- HashSet 値の重複を許さない順不同の要素集合。
- TreeSet 値の重複を許さないソートされたの要素集合。
Map系
- HashMap キーと値の組からなる要素の集合。
- TreeMap キーと値の組からなる要素の集合。キーでソートされている。
今回はArrayListを使用してみます。
ArrayListの使用例
listtest.javapackage listtest; import java.util.ArrayList; import java.util.List; public class Listtest { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("りんご"); list.add("みかん"); list.add("メロン"); System.out.println(list); } }実行結果.[りんご, みかん, メロン]ArrayListは実装クラスのため、以下のようにインスタンスが作成できます。
List<String> list = new ArrayList<String>();実は以下のものと同様の内容となっています。
listにStringクラスのオブジェクトを詰め込んでるということですね。listtest.javaList<String> list = new ArrayList<String>(); list.add(new String("りんご")); list.add(new String("みかん")); list.add(new String("メロン")); System.out.println(list);ある一つの要素を取り出したい場合はgetメソッドを利用します。引数にはリストの取り出したい要素の番号を入れます。
listtest.javaList<String> list = new ArrayList<String>(); list.add(new String("りんご")); list.add(new String("みかん")); list.add(new String("メロン")); System.out.println(list.get(1));実行結果.みかん要素が取り出せました!
- 投稿日:2020-03-27T21:12:31+09:00
Scope(スコープ)
Scope(スコープ)
インスタンスを保存できる領域。
スコープを経由させることにより、サーブレットクラスとJSPファイルの間でインスタンスを共有させることが可能になる。JavaBeans
スコープに保存する再利用しやすくするルールに基づいたクラス(インスタンス)
直列化
プロパティ
getter/setter
リクエストスコープ
レスポンスが返されるまで利用可能
HttpServletRequestリクエストスコープにインスタンス生成
記述request.setAttribute("属性名", インスタンス);リクエストスコープからインスタンスを取得
記述取得するインスタンスの型 変数名 = (取得するインスタンスの型) request.getAttribute("属性名");セッションスコープ
保存インスタンスの有効期限は開発者が設定
リクエストをまたいでの使用可能セッションスコープの取得
記述HttpSession session = request.getSession();セッションスコープに保存
記述session.setAttribute("属性名", インスタンス);セッションスコープからインスタンスを取得
記述取得するインスタンスの型 変数名 = (取得するインスタンスの型) session.getAttribute("属性名");セッションスコープからインスタンスを削除
記述session.removeAttribute("属性名");セッションスコープを破棄
記述session.invalidate();アプリケーションスコープ
アプリケーション終了まで利用可能
高速アクセスアプリケーションスコープの取得
記述ServletContext application = this.getServletContext();アプリケーションスコープに保存
記述appliction.setAttribute("属性名", インスタンス);アプリケーションスコープからインスタンスを取得
記述取得するインスタンスの型 変数名 = (取得するインスタンスの型) application.getAttribute("属性名");アプリケーションスコープからインスタンスを削除
記述application.removeAttribute("属性名");
- 投稿日:2020-03-27T19:13:05+09:00
MVCモデル
MVCモデル
M...Model(モデル)
Javaクラス
アプリケーションの処理(計算処理等)、データ格納V...View(ビュー)
JPSファイル
画面表示C...Controller(コントローラー)
サーブレットクラス
要求の受け取り、処理実行依頼(Model)、結果表示依頼(View)転送処理
フォワード
処理を他のサーブレットクラス、JPSクラスに移す(内部)
URLがリクエスト時の状態記述RequestDispatcher dispatcher = request.getRequestDispatcher("フォワード先"); dispatcher.forward(request, response);リダイレクト
処理を他のサーブレットクラス、JPSクラスに移す(外部)
URLがリダイレクト先のものに変更記述response.sendReddirect("リダイレクト先のURL");
- 投稿日:2020-03-27T18:30:10+09:00
From(フォーム)
From(フォーム)
webページに入力したデータをサーバーサイドプログラムに送信
入力項目のまとまり記述<form action="送信先" method="リクエストメソッド">...</from>リクエストメソッド
GETリクエスト
新しい情報(Webページ等)を取得POSTリクエスト
フォームに入力した情報を登録テキストボックス
記述<input type="text" name="部品名">パスワード
記述<input type="password" name="部品名">ラジオボタン
記述<input type="radio" name="部品名" value="値">送信ボタン
記述<input type="submit" value="送信">リクエストパラメータ取得方法
APサーバによってHttpServletRequestインスタンスに格納されて送信先のサーブレットクラスまたはJSPファイルに渡される
文字コードの指定
記述request.setCharacterEncoding("送信元HTMLの文字コード");取得
記述String xxx = request.getParameter("リクエストパラメータの名前");
- 投稿日:2020-03-27T15:02:29+09:00
Gradleってなんだろう?
javaのビルド周りに興味をもったので、その時のメモ。
Gradleとは
javaのビルドツールで jarファイル(javaのバイトコードファイルや画像をzipファイル形式で一つにまとめたもの)
へのビルドをしてくれます。
mavenと違い、設定ファイルはgrovyという言語で書きます。また、gradleを毎回インストールすることがないように、gradle wrapper というものを作り、
これを配布することで、gradleをインストールしていない環境でもgradle を使うことが出来ます。Gradle 初期化
簡単なgradleプロジェクトを作ります。
gradle initこれで色々ファイルが作られます。具体的には以下の感じ
├── build.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle依存性解決
gradleはjavaの外部のパッケージを管理して、自身ののプロジェクトでそのパッケージを使えるようにしてくれます。
dependencies { implemention("パッケージ名") }タスク
gradleにおけるタスクとは,公式から引用すると、ビルドを行うための細かい作業とのことです。
組み込みのタスク
このタスクはgradleのプロジェクトが生成された時点でいくつか作られています。
例えば、テスト、ビルド、実行などです。これらは、
gradle tasks
で一覧を見ることが出来ます。
タスクの定義
これは、自分で定義できるので、簡単なタスクを定義してみましょう。
task hello { println("hello") }実行してみよう。
gradle hello #以下のが表示されるはず #hello #:hello UP-TO-DATEこんな感じでタスクを作って、内部でプラグインの設定や特定のタスクのみを束にしておくことが出来ます。
タスクをアクションで構成する
タスクは自分が実行スべきタスクをアクションという単位で認識していて、それを配列として順次実行していきます。
アクションは
doFirst:先頭に追加する
doLast: 末尾に追加するといった形で追加していきます。
task hello{ hello.doFirst { println "hello1" println "hello2" } hello.doLast { println "hello3" println "hello4" } }これを実行すると
gradle hello結果は
:hello hello1 hello2 hello3 hello4 BUILD SUCCESSFUL in 0s 1 actionable task: 1 executedとなる。
こんな形で実行順序を制御して、処理順序をカスタマイズすることができます。
gradleのtips
これまででざっくりとgradleの使い方を触ってきましたが、今度は各々のタスクが早くなる設定や分析ための便利な機能を見ていきたいと思います。
並列化
graleは基本的にタスクを一つずつしか実行していきません。ですが、
並列実行を強制する設定があります。
なお、これは完全に独立しているタスクを並列実行するので、依存関係があるものに関してはこれまでどおり逐次実行されます。gradle.propertiesに以下を追加します。
org.gradle.parallel=trueプロファイリング
gradleにはタスク実行時にどのくらい時間を使ったか、並列実行したかなどをプロファイリングして、視覚化してくれる機能があります。
これはタスクの実行時に--scan
をつければ、実行後にCLIにURLが表示され、そこから確認ができます。ただ、これにはgradleでメールアドレスの登録などが必要なため、情報管理的な部分を考える必要があります。
このプロファイリングは結構便利で、テストの重い部分やサジェストなど、ビルドの効率化に重要な情報を得ることができます。
参考
https://guides.gradle.org/performance/
最後に
gradleは設定ファイルが、独自言語だったりしますが、その言語にさえ慣れてしまえば設定は読みやすいし、
タスクの設定も細かく行える印象です。また、ビルド時のプロファイリングなど、検証と改善の昨日も揃っているところもいいです。
それでは良いgradleライフを
- 投稿日:2020-03-27T14:45:44+09:00
JPS(JavaServer pages)
- 投稿日:2020-03-27T14:33:40+09:00
非staticネストクラスが握っちゃう、エンクロージングオブジェクトの暗黙的参照
この記事は、Javaにおける「非staticネストクラスが、エンクロージングオブジェクトを暗黙的参照として握っちゃうから、気を付けましょう」について述べます。
なぜ、気を付けなければならないのか、と言えば、「メモリーリークを引き起こす可能性を高めてしまうから」です。
そして結論は、「ネストクラスは、
static
修飾子を付けた方がイイ(さすれば、エンクロージングオブジェクトを暗黙的参照として握ることはないから)」です。エンクロージングオブジェクトは短命で、ネストクラスのインスタンスの方が長命である場合は特にです!事のきっかけはAndroidアプリ開発時
(この記事は、Androidアプリ開発に特化したものではありません。が、読み進めていけばJavaに関わることになっていきますので、当初は我慢してお読みください)
ある日のことでございます。AndroidアプリをJavaで作っていた時に、Android Studio1が、こんな警告を出してきました。
This 'AsyncTask' class should be static or leaks might occur
CouponAsyncTaskと名付けたこのクラスは、ネストクラス(インナークラス・内部クラス)として定義しているのですが、「static修飾子を付けなさいよ、さもなければメモリリークを引き起こすかもよ」と吹き出しに書かれています。まあ、怖い。
Android Studioが備えているコードチェック機能「Lint」が教えてくれる詳しい説明を見ますと、
Static Field Leaks
A static field will leak contexts.
Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected.
Similarly, direct field references to activities and fragments from these longer running instances can cause leaks.
ViewModel classes should never point to Views or non-application Contexts.
Issue id: StaticFieldLeakGoogle翻訳に頼んでみます。
静的フィールドリーク
静的フィールドはコンテキストをリークします。
非静的内部クラスには、外部クラスへの暗黙的な参照があります。 その外部クラスがたとえばフラグメントまたはアクティビティである場合、この参照は、長時間実行されるハンドラー/ローダー/タスクがガベージコレクションの取得を妨げるアクティビティへの参照を保持することを意味します。
同様に、これらのより長時間実行されているインスタンスからのアクティビティおよびフラグメントへの直接フィールド参照は、リークを引き起こす可能性があります。
ViewModelクラスは、ビューまたは非アプリケーションコンテキストを指すことはありません。
問題ID:StaticFieldLeak難しい英文を、難しい日本語に翻訳されてもねえ...チンプンカンプンです。
開発者サイトに書いてありました
ちゃんとAndroidアプリ開発者向け公式サイトに「スレッド化されたオブジェクトを使ったコード設計によく見られる欠陥の例」として掲載されていました。あるあるなんでしょうね。
スレッド化によるパフォーマンスの向上[非明示的参照]この公式サイトの日本語ページでは「非明示的参照」と訳されていますが、原文は「Implicit references」と書かれています。
当記事では「暗黙的参照」と五字熟語で語っていきます。ネストクラス
いきなり「非staticネストクラスが握っちゃう、エンクロージングオブジェクトの暗黙的参照」について語ろうにも、このお題目に乗っかっている情報量が多いので、前提知識を一つ一つ説明していきます。
急がば回れ、です。ただ、ちょっともどかしいかもしれませんが。「ネストクラス(nested class)」は、「インナークラス(inner class)」とも呼ばれます。当記事は「ネストクラス」で統一します。
Fooクラスには2つのネストクラスがあるpublic class Foo { class Bar { void methodBar() { System.out.println("methodBar"); } } static class Baz { void methodBaz() { System.out.println("methodBaz"); } } void methodFoo() { System.out.println("methodFoo"); } }この場合、
- BarクラスとBazクラスは、「Fooクラスのネストクラス」と言います。
- Fooのことは、「Barクラスのエンクロージングクラス」と言います。
「エンクロージングクラス(enclosing class)」は、「アウタークラス(outer class)」とも呼ばれます。当記事は「エンクロージングクラス」で統一します。
BarとBazの違いは、
static
修飾子が付いているか否かなので、
- Barクラスは、「Fooクラスの非staticネストクラス」と言います。面倒くさいと思う人は「Fooクラスのネストクラス」で済ませてしまいますが。
- Bazクラスは、「Fooクラスのstaticネストクラス」と言います。
ネストクラスのインスタンスを生成するには
Fooの2つのネストクラスのインスタンスを生成するclass Main { public static void main(String[] args) { Foo foo = new Foo(); Foo.Bar bar = foo.new Bar(); Foo.Baz baz = new Foo.Baz(); } }非staticネストクラスのインスタンスを生成する場合
Barクラスのインスタンスを生成するには、2行を要しました。どうしても1行で済ませたい場合は、以下のコードとなります。
1行でBarクラスのインスタンスを生成Foo.Bar bar = new Foo().new Bar();いずれにせよ、非staticネストクラスのインスタンスを生成する場合は、エンクロージングのインスタンスが必要です。
そして、new
演算子の前に.
が付くのも特徴的です。staticネストクラスのインスタンスを生成する場合
事前にエンクロージングのインスタンスなんて要りません。いきなり
new
できます。特徴的なのは、クラス名が「
Baz
」ではなく、「Foo.Baz
」という名前だということです。クラス名の合間に.
が含まれているのが特徴的です。書籍「Effective Java」に当たる
Effective javaは、第1版(2001年刊行)から「非staticのメンバークラスよりもstaticのメンバークラスを選ぶ」べしとのたまっております。それは2018年に刊行された第3版ですら残っている御成敗式目です。
メモリリークは壊滅的になり得ます。
とか
メモリリークの検出がたいてい難しいです。
とか
余分な参照を保持しメモリと時間を無駄に使います。
など、怖いことが書かれています。なぜなら、
static修飾子を省略すると、個々のインスタンスはエンクロージングオブジェクトへの関係のない参照を持ってしまいます。その参照を保存することは時間とメモリを必要とします。深刻なのは、その参照がなければガベージコレクションの対象になる場合、そのエンクロージングインスタンスが残ってしまう可能性があることです。
と書かれてあります。
エンクロージングのインスタンスはもう不要なのに、天国に召されない
前出のプログラムをちょっと改変してみました。staticネストクラスである
Baz
はちょっと退かせました。public class Main { public static void main(String[] args) { Foo foo = new Foo(); Foo.Bar bar = foo.new Bar(); foo = null; System.gc(); // foo.methodFoo(); // NullPointerException発生 bar.methodBar(); } }エンクロージングクラスの
Foo
のインスタンスにnull
を代入してガベージコレクション(GC)を実行させてから、非staticネストクラスのBar
インスタンスにメソッドを呼び出してみました。実行してみますと、以下の通りです。実行結果methodBar
へえ、ちゃんと動くんだぁ!と私は瞠目してしまいました。
Javaの仕様としては、私がいくらエンクロージングクラスの
Foo
のインスタンスにnull
を代入しても、その非staticネストクラスのBar
インスタンスが動く限り、foo
と名付けたオブジェクトはGCされない(GC対象と見做されず華麗にスルーされた)のです。なぜなら、bar
がfoo
を暗黙的参照として保持しているから です。ファニーな例えとしてイラストにすると、こんなかんじでしょうか。
階段を一歩ずつ登っていってる女性が、
bar
で、元気いっぱいです。一方、成仏したがっているお爺ちゃんが
foo
なんですが、bar
が握ってるのでね、なかなか天国へ召されずにいるのですよ。地縛霊になりかねません。
- 握っている=参照している
- 天国へ召される=GCによりメモリから解放される
- 地縛霊=メモリリーク
で読み替えてください。
では、短絡的にネストクラスにstaticを付けるだけで万事解決!といくのか?
FooとかBarとかBazの題材をやめて、以下のプログラムを見てください。
ビフォアー:ネストクラスが非staticだclass Fizz { private Integer x = 12; private static Integer y = 34; class Buzz { void m() { x = 56; y = 78; System.out.println(x + y); } } } class FizzBuzzMain { public static void main(String[] args) { Fizz.Buzz fb = new Fizz().new Buzz(); fb.m(); } }ビフォアーの実行結果134
(わざとらしく)やるなっつってんのに非staticネストクラスですよ。では、安直にこの
Buzz
と名付けたネストクラスにstatic修飾子を付けてあげましょう。アフターclass Fizz { private Integer x = 12; private static Integer y = 34; static class Buzz { void m() { x = 56; // xがstaticフィールドではないのでコンパイル不可 y = 78; System.out.println(x + y); // xがstaticフィールドではないのでコンパイル不可 } } } class FizzBuzzMain { public static void main(String[] args) { Fizz.Buzz fb = new Fizz.Buzz(); fb.m(); } }途端にコンパイルできなくなりました。
x
がstaticフィールドではないために、ダメだとIDEが怒り出します。なぜなのか。それは、 staticネストクラスは、エンクロージングのstaticメンバーにのみアクセス可能 だからです。
また再びのAndroidアプリ開発の話に戻ります
作りたかったAndroidアプリは、このアニメーションGIFをご覧ください。
起動したとたんに、勝手にカウントアップが始まります。このアニメーションGIFは、3まで至ったらまたSTART!を繰り返しているアニメーションですが、本当はこのアプリはSTART!から始まったら「9」まで数え上げたらお終い(「9」を出力したままキープ)なんです。
そしてそのプログラムは以下の通りです。Androidアプリ開発未経験者の方も我慢しておつきあいください。
画面のクラスpublic class MainActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.text_view); new MyTask().execute(); } class MyTask extends AsyncTask<Void, String, Void> { @Override protected Void doInBackground(Void... voids) { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); publishProgress(String.valueOf(i)); } catch (InterruptedException e) { Log.e("MyTask", e.getMessage(), e); } } return null; } @Override protected void onProgressUpdate(String... values) { textView.setText(values[0]); } } }Androidで「画面のクラス」を作りたければ、
AppCompatActivity
を継承したクラスをコーディングしはじめます。
TextView
というクラスは、画面中央に配置して「START!」か「0」とか「1」とか表示している、文字列を表示する(ことしかできない)ビュー(部品)です。
このTextView
を非同期処理で「0」を「1」に、そして「2」へと1000ミリ秒きざみで変えさせたい。これがこのアプリの要件です。非同期処理をしたければ、
AsyncTask
のサブクラスを定義して、それにその処理をコーディングしておきます。その非同期処理をする
MyTask
にexecute()
メソッドを呼び出すと、MyTask
が処理を始めます。
この画像の赤い囲みがTextView
です。MyTask
がこのTextView
に、"0"
とか"1"
とか"2"
とか文字列を上書きします。これでカウントアップするように見せかけています。それでは、
MyTask
クラスにstatic
を付けてみます。IDEが真っ赤に怒り出しました。なぜならその
textView
と名付けたフィールド、static修飾子を付けてませんからね!MainActivityのインスタンスはもう不要なのに、天国に召されない
前出の階段を元気に登っている女性と地縛霊のお爺ちゃんのイラストを今一度見てください。
Androidでは、不要になった画面は、GCされます。「不要になった画面」とは、ユーザが戻るボタンを押すなどしてその画面を非表示にした(消しちゃう)時のことをいいます。
一般的にAndroid端末はPCと比較してメモリが少ないです2。なのでユーザが非表示にした画面=Activityクラスのインスタンスはランタイムにより速やかにGCされます。ユーザが気軽に戻るボタンを押しちゃう、という所作ひとつで、その画面=Activityインスタンスは不要!んじゃGC!と短命なりにし運命になりがちです。
しかして、バックグラウンドで(否、「バックグラウンド」という用語は正鵠を射てなくて、正確には「ワーカースレッドにおいての非同期処理で」と言った方が適切です)MyTask
は、己の仕事(カウントアップ)を達成しきるまで生きることになります。この、生存期間の比較で【 エンクロージングインスタンス < ネストクラスのインスタンス 】であることを危惧して警告を出してくれたのがAndroid StudioというIDEなのです。
この私が作ったアプリを起動して、カウントアップが
MyTask
の仕業により始まっても、ユーザがもういいやと戻るボタンを押すなどして画面を非表示にする(消す、アプリをやめる)としても、 非staticネストクラスのインスタンスであるMyTask
オブジェクトは、MainActivity
インスタンスの暗黙的参照を保持している ので、とっくに表示されてないんだからとっととGCされちゃえばいいのに、GCされない状態に陥ります。
ユーザが見えてないところで、MyTask
は脈々とカウントアップのお仕事をしているのです。
イラストの、女性であるMyTask
は1段1段階段を登って。でもその右手には、成仏できないお爺ちゃんMainActivity
をしっかり握っちゃってて。ユーザはこのお爺ちゃんが見えてないのに。解決したくとも、にっちもさっちも
私はここでジレンマに我が身が嘖(さいな)まれるのです。
- この
MyTask
は、このMainActivity
でしか使われないクラスだから、ネストクラスにしたい。- しかしstaticネストクラスにせよとIDEが警告を出す。
- ところが
TextTview
型のフィールドはstaticフィールドにはしたくない(すべきではないので)。- となると、この
MyTask
とこのMainActivity
は、別クラスに分けるか。- でもそうなると、
TextTview
の参照の受け渡しが面倒だ(今度はこの受け渡しでヘタこくとTextTview
がメモリリークのタネとなりかねん)。TextTview
を持っているのは(持つべきなのは)MainActivity
だし、そのTextTview
の文字列を変えるのはMyTask
だ。- だったらやっぱり、この
MyTask
は、このMainActivity
のネストクラスにしたい。java.lang.ref.WeakReference<T>
を使うか...。ずっとずっとこれに悩んでいます。どこかでなにかを妥協しなければいけないのかもしれません。
以上です。なんだかこれで終わりにしていいのか、良心の呵嘖(かしゃく)に悩む私に、福沢諭吉先生の御言葉でこの記事を綴じたいと思ひます。
されども不良の子に窘(くる)しめらるるの苦痛は、地獄の呵嘖よりも苦しくして、然(しか)も生前現在の身を以てこの呵嘖に当たらざるを得ず。
福沢諭吉「教育の事」1878(明治11)年
ちょっとユキチよ、「地獄の呵責よりも苦しくて」はシビアすぎて引くわ。
やめた。和辻先生に縋(すが)ることにします。かく道元の説く慈悲の前には、「悪は必ずしも呵嘖すべきものでない」。
和辻哲郎「日本精神史研究」1922(大正11)年
以上です。
- 投稿日:2020-03-27T14:16:08+09:00
サーブレット
サーブレット
サーバーサイドプログラムを作るための技術
サーバレットクラスを作りAPサーバ上で実行する
ブラウザからのリクエストによって実行され、結果をHTMLで出力しAPサーバによってブラウザにレスポンスするdoGet()メソッド
サーブレットクラスがリクエストされると実行されるメソッド
HttpServletRequest
ブラウザから届いたリクエストに関する情報と機能を持つインスタンス
HttpServletResponse
サーバから送り出すレスポンスに関する情報と機能を持つインスタンス
Content-Typeヘッダ
URL
Javahttp://<サーバー名>/<アプリケーション名>/<URLパターン>@WebServlet("/URLパターン")で設定
- 投稿日:2020-03-27T14:16:08+09:00
Servlet(サーブレット)
Servlet(サーブレット)
サーバーサイドプログラムを作るための技術
サーバレットクラスを作りAPサーバ上で実行する
ブラウザからのリクエストによって実行され、結果をHTMLで出力しAPサーバによってブラウザにレスポンスするdoGet()メソッド
サーブレットクラスがリクエストされると実行されるメソッド
HttpServletRequest
ブラウザから届いたリクエストに関する情報と機能を持つインスタンス
HttpServletResponse
サーバから送り出すレスポンスに関する情報と機能を持つインスタンス
Content-Typeヘッダ
URL
Javahttp://<サーバー名>/<アプリケーション名>/<URLパターン>@WebServlet("/URLパターン")で設定
- 投稿日:2020-03-27T12:32:14+09:00
【Java】範囲チェックの実装
範囲チェックの実装、どうしてる?
数値やら文字の範囲チェックって、
obj.compareTo(value)
の組み合わせで書いてあることが多いですよね。
ですがそのcompareToの戻り値のパターンがどうしても覚えられないんです…
覚えてるのは、一致が「0」ということだけ。> 0
と< 0
のどっちが小さい/大きいなのか、毎回実行して確認しています…
でも、compareToメソッドや等号・不等号演算子による「最小値以上かつ最大値以下」の範囲チェックを書かなくて済む方法があったんです。やはりApacheCommonsはぐう優秀
org.apache.commons.lang3.Range
クラス(結構昔からあるのに無知でした)がポンコツSEの救世主でした…これは愛さざるを得ない。
「java 範囲 チェック」でググると、等号・不等号演算子やcompareToによる実装が検索結果の上位にいるので、「commons」とかキーワードをちょっと工夫しないとなかなか辿り着けないんですよね、Rangeクラスには。サンプルコード
int num0 = 0; int num1 = 1; int num2 = 2; // 範囲オブジェクトを作成 Range rng = Range.between(num0, num2); System.out.println("範囲内チェック(" + num1 + ") = " + rng.contains(num1)); System.out.println("範囲内チェック(" + -1 + ") = " + rng.contains(-1)); // Rangeなら範囲チェックだけじゃなくて、いろいろできます System.out.println("範囲の最小値 = " + rng.getMinimum() + "、範囲の最大値 = " + rng.getMaximum()); System.out.println("範囲が引数より後かチェック(" + -1 + ") = " + rng.isAfter(-1)); System.out.println("範囲が引数より後かチェック(" + num0 + ") = " + rng.isAfter(num0));実行結果
範囲内チェック(1) = true 範囲内チェック(-1) = false 範囲の最小値 = 0、範囲の最大値 = 2 範囲が引数より後かチェック(-1) = true 範囲が引数より後かチェック(0) = false上記サンプルの他、isBeforeやcompareTo的な戻り値を返すelementCompareTo等いろんなメソッドが実装されています。
ちなみにbetweenの最小と最大の値の型が不一致の場合はコンパイルエラー、片方でもnullの場合はIllegalArgumentExceptionが発生します。
範囲とした型とcontainsの引数の型が合わない場合、ClassCastExceptionが発生します。ジェネリクスの恩恵
RangeクラスはcompareToからの脱却というだけで最高なんですけど、素晴らしいのはそれだけじゃないんです。
上記の例はプリミティブのint型(Integerへの変換不要)でしたが、例えばStringだってLocalDateだって渡せちゃう!Rangeまじ愛してる。LocalDate ld0 = LocalDate.now().minusDays(1); LocalDate ld1 = LocalDate.now(); LocalDate ld2 = LocalDate.now().plusDays(1); Range rng = Range.between(ld0, ld2); System.out.println(rng.contains(ld0)); // true System.out.println(rng.contains(ld1)); // true System.out.println(rng.contains(ld2)); // true System.out.println(rng.contains(ld2.plusDays(1))); // false System.out.println(rng.isAfter(ld0.minusDays(1))); // true System.out.println(rng.contains(null)); // falseもっと早く知りたかった…最の高じゃん…
Range.betweenには、Comparatorクラスのオブジェクトを渡すこともできるので、自作オブジェクトの比較も可能です。
もうほんと最高of最高。ポンコツ具合がちょっと改善される気がします。compareToの戻り値が覚えられないポンコツSEはそんなに多いわけではないと思うのですが、Rangeクラスを使うほうが見た目もすっきりわかりやすいと思います。contains使うと論理演算子も不要ですし。
なので、これからの範囲チェックの実装にはぜひRangeクラスを使っていただきたいなと思うのです。
- 投稿日:2020-03-27T12:32:14+09:00
【Java】compareToが不要な範囲チェック
範囲チェックの実装、どうしてる?
数値やら文字の範囲チェックって、
obj.compareTo(value)
の組み合わせで書いてあることが多いですよね。
ですがそのcompareToの戻り値のパターンがどうしても覚えられないんです…
覚えてるのは、一致が「0」ということだけ。> 0
と< 0
のどっちが小さい/大きいなのか、毎回実行して確認しています…
でも、compareToメソッドや等号・不等号演算子による「最小値以上かつ最大値以下」の範囲チェックを書かなくて済む方法があったんです。やはりApacheCommonsはぐう優秀
org.apache.commons.lang3.Range
クラス(結構昔からあるのに無知でした)がポンコツSEの救世主でした…これは推せる。
「java 範囲 チェック」でググると、等号・不等号演算子やcompareToによる実装が検索結果の上位にいるので、「commons」とかキーワードをちょっと工夫しないとなかなか辿り着けないんですよね、Rangeクラスには。サンプルコード
int num0 = 0; int num1 = 1; int num2 = 2; // 範囲オブジェクトを作成 Range rng = Range.between(num0, num2); System.out.println("範囲内チェック(" + num1 + ") = " + rng.contains(num1)); System.out.println("範囲内チェック(" + -1 + ") = " + rng.contains(-1)); // Rangeなら範囲チェックだけじゃなくて、いろいろできます System.out.println("範囲の最小値 = " + rng.getMinimum() + "、範囲の最大値 = " + rng.getMaximum()); System.out.println("範囲が引数より後かチェック(" + -1 + ") = " + rng.isAfter(-1)); System.out.println("範囲が引数より後かチェック(" + num0 + ") = " + rng.isAfter(num0)); // 変数に格納しなくてもメソッドチェーンでも可 System.out.println("範囲内チェック(" + num1 + ") = " + Range.between(num0, num2).contains(num1));実行結果
範囲内チェック(1) = true 範囲内チェック(-1) = false 範囲の最小値 = 0、範囲の最大値 = 2 範囲が引数より後かチェック(-1) = true 範囲が引数より後かチェック(0) = false 範囲内チェック(1) = true上記サンプルの他、
isBefore
やcompareTo的な戻り値を返すelementCompareTo
等いろんなメソッドが実装されています。
ちなみにbetweenの最小と最大の値の型が不一致の場合はコンパイルエラー、片方でもnullの場合はIllegalArgumentExceptionが発生します。
また、betweenに渡す最小値と最大値の順番は関係なく、between(最大値, 最小値)
でも、正しく範囲は設定されます。(getMaximum
やgetMinimum
の値は崩れない)
範囲とした型とcontainsの引数の型が合わない場合は、ClassCastExceptionが発生します。ジェネリクスの恩恵
RangeクラスはcompareToからの脱却というだけで最高なんですけど、素晴らしいのはそれだけじゃないんです。
上記の例はプリミティブのint型(Integerへの変換不要)でしたが、例えば文字列型だって日付や時刻型だって渡せちゃう!Rangeまじ愛してる。// 日付型(LocalDate)のサンプル LocalDate ld0 = LocalDate.now().minusDays(1); LocalDate ld1 = LocalDate.now(); LocalDate ld2 = LocalDate.now().plusDays(1); Range rng = Range.between(ld0, ld2); System.out.println(rng.contains(ld0)); // true System.out.println(rng.contains(ld1)); // true System.out.println(rng.contains(ld2)); // true System.out.println(rng.contains(ld2.plusDays(1))); // false System.out.println(rng.isAfter(ld0.minusDays(1))); // true System.out.println(rng.contains(null)); // falseもっと早く知りたかった…最の高じゃん…
betweenには、Comparatorクラスのオブジェクトを渡すこともできるので、自作オブジェクトの比較も可能です。
もうほんと最高of最高。ポンコツ具合がちょっと改善される気がします。compareToの戻り値が覚えられないポンコツSEはそんなに多いわけではないと思うのですが、Rangeクラスを使うほうが見た目もすっきりわかりやすいと思います。
contains
使うと論理演算子も不要ですし。
なので、これからの範囲チェックの実装にはぜひRangeクラスを使っていただきたいなと思うのです。
- 投稿日:2020-03-27T10:36:46+09:00
【Java】「final変数」と「不変オブジェクト」の違い
どーも、ふぎとです。
今回はfinal変数と不変オブジェクトについて
まとめてみました。final変数
final変数とは、final修飾子を指定した変数のこと。
その性質は「変更不可・読み込み専用」と呼ばれる
が、厳密には「再代入不可の変数」。final int i = 3; i = 5; //再代入によるコンパイルエラー上では、基本型(int型)変数iにfinalを指定している。
基本型変数は、変数の値に「値そのもの」が保持される
変数だ(詳細は『基本型変数の代入と参照型変数の代入
の違い』を参照)。そのため、「値そのもの」を入れ替
える再代入はコンパイルエラーになる。つまり、基本型
変数に限って言えば、final変数は「変更不可」である
ように見える。
話をややこしくしているのは、final指定の参照型変数だ。final StringBuilder sb = new StringBuilder("123"); sb = new StringBuilder("456"); //コンパイルエラー final StringBuilder sb2 = new StringBuiler("789"); sb.append("★"); //コンパイルエラーにならない! System.out.println(sb2); //"789★"が出力される上の例では、参照型変数sbとsb2に対して操作をしている。
ここで、final変数の性質を「変更不可」と理解していると、
sbへの操作がエラーになり、sb2への操作がそうならない事
の意味がわからなくなる。
ここで、参照型変数が保持している値を思い出してみると、
これは「オブジェクトへの参照」であった。つまり、sbや
sb2が持っているのは、"123"や"789"という「値そのもの」
ではない。彼ら(?)が持っているのは、あくまでも「"1
23(456)"という状態となったオブジェクトへの参照」なのだ。
この仕組みと、final変数の厳密な定義「再代入不可」とを考
えに入れると、上の例を理解することができる。すなわち、
sbに対する "sb = new StringBuilder("456")"という操作、これ
は「新たな参照の値をsbに再代入する」という操作だ。よっ
て、final変数の性質により、コンパイルエラーがでる。一方、
"sb2.append("★")" の操作、これは「"789"という状態を持つ
オブジェクトの状態を"789★"という状態に変える」という操
作となる。要するに、sb2への「オブジェクトへの参照値」の
再代入は起こっていない。そのためエラーとならないのである。不変オブジェクト
final変数が「再代入不可」の性質を持つ一方で、不変オブジェ
クトは「オブジェクトの状態変更」をも禁じている。つまり、
上の例でいうところの "sb.append("★")" の操作もできないのが
不変オブジェクトなのだ。まとめ
・final変数は「再代入不可」の性質を持つ
・参照型変数をfinal指定すると、「参照先の変更」は
できないが「最初に参照したオブジェクトの状態変
更」はできる
・「最初に参照したオブジェクトの状態変更」もできない
オブジェクトを「不変オブジェクト」と呼ぶ...
では今回はこの辺で。ふぎとでした。(P.S.)記事の内容への指摘などあれば
遠慮なくお知らせくださいm(__)m
- 投稿日:2020-03-27T09:28:11+09:00
Android 11で令和に標準で対応予定
Androidのバージョンはグングン上がります。
今やそろそろAndroid 11がリリースされつつあります。
そのAndroid 11にて、ついに日本の元号「令和」が標準APIで対応するようです。結論
結論を真っ先に述べます。
- Android 11(R)にて、ついに日本の元号「令和」が標準APIで対応するようです。
- すでにAPIレベル24からAndroid標準APIはICUを取り込んでいます。
- でもAPIレベル24~29の間は「令和」には対応していません。「平成」どまりです。
[閑話]コードネームとかAPIレベルとか
ところで、お菓子の名前のコードネームですが、「Q」からやんなくなりました。
KitKatとかLollipopとかMarshmallowとかNougatとかOreoとかPieとか。時折、登録商標も混ざりながらも。
Androidっ子たちは、「Q」から綴りが始まるお菓子が何になるのかワクワクしてたのに、Googleが「お菓子の名前はもうやめた」と言い出して。Android 10は「Q」。そろそろリリースされる11は「R」です。
Androidの「R」で「令和」に対応...まっまさか!その「R」は、「Reiwa」のRか?!なんちゃって。
そして、この10とか11というバージョン番号、そしてコードネームとは別に、開発者にとって重要な番号は「APIレベル」です。
Android 8.0はOreoであり、APIレベルは26です。が、Android 8.1となると、同じくOreoなんですが、APIレベルは27です。ややこしい!そして「R」に割り当てられるAPIレベルの番号は...?それは当記事の最後の方に記しましたので、お楽しみに。(じらしてスミマセン)
IBM社製のICUというライブラリ
http://site.icu-project.org/home
「International Components for Unicode」の略だそうです。とにかく色々と便利なAPIが収録されています。
オープンソースソフトウェアです。ICUのJapaneseCalendarクラス
com.ibm.icu.util.JapaneseCalendar
クラスは、日本の元号に対応しています。
最初の元号「大化」から現代まで。南北朝時代をどう扱っているか、というマニアックなところまで私は検証していませんが。本能寺の変import java.util.Locale; import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.DateFormatSymbols; import com.ibm.icu.text.SimpleDateFormat; import com.ibm.icu.util.GregorianCalendar; import com.ibm.icu.util.JapaneseCalendar; public class Honnoujinohen { public static void main(String[] args) { JapaneseCalendar jpCalendar = new JapaneseCalendar(); DateFormatSymbols dfs = new DateFormatSymbols(jpCalendar, Locale.JAPANESE); DateFormat dateFormat = new SimpleDateFormat("Gyy年MM月dd日", dfs); dateFormat.setCalendar(jpCalendar); // 「本能寺の変」1582年6月2日 int year = 1582; int month = 6; int day = 2; String s = dateFormat.format(new GregorianCalendar(year, month - 1, day).getTime()); System.out.println(s); } }実行結果天正10年06月02日
大河ドラマファンや歴女には堪りませんね。麒麟がくる。「天が正しい」と書く元号だったのか。
ああ、応仁の乱。天保の改革。安政の大獄。元号に思いを馳せるだけで脳内で時代を駆け巡れます。このICUが「令和」に対応したのは、2019年4月17日リリースのバージョン64.2からです。
Android標準API
Android標準APIでは、APIレベル24(Android 7.0 Nougat)からICUを取り込みました。
取り込んだだけあって、パッケージも変わっております。例えば、前出のJapaneseCalendar
クラスは、android.icu.util.JapaneseCalendar
です。そして。ついに。Android 11(R)のAPIレベルで、
public static final int REIWAが追加されます!まさかのint enumパターーーン!!!いやーーーん!!!
しょうがねぇだろ、IBM社製のICUがそもそもint enumパターーーンなんだからーーーん!!!サンプルアプリ
こんなアプリを作ってみました。
「建武の新政」は、鎌倉幕府を倒した後醍醐天皇が、1333年7月17日からスタートさせた新政策ですが、当日はまだ元号が「元弘」だというヒッカケ問題ですね。今度の2学期中間試験に出そうだな。
平成から令和へ。
開発環境は以下の通りです。
Android Studio 3.6.1
Build #AI-192.7142.36.36.6241897, built on February 27, 2020
Runtime version: 1.8.0_212-release-1586-b04 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
GC: ParNew, ConcurrentMarkSweep
Memory: 1237M
Cores: 8
Registry: ide.new.welcome.screen.force=true
Non-Bundled Plugins:Gradleの設定
Android 11(R)のAPIレベルで作らなければなりません。
Gradle設定ファイルに以下のようにします(一部略)。build.gradleandroid { compileSdkVersion 'android-R' buildToolsVersion "29.0.2" defaultConfig { applicationId "com.example.gengou" minSdkVersion 24 } compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 } }この記事執筆時点では、Android 11(R)のAPIレベルに番号が割り当てられていません。まだプレビュー版だからのようです。正式版がリリースされたら、おそらく
30
になると推測されます。
minSdkVersion
は最低でも24
にしなければなりません。なぜなら前述にありますように、ICUがAndroid標準APIに組み込まれたのが、APIレベル24からだからです。Activity
MainActivity.javapackage com.example.gengou; import android.icu.text.DateFormat; import android.icu.text.DateFormatSymbols; import android.icu.text.SimpleDateFormat; import android.icu.util.GregorianCalendar; import android.icu.util.JapaneseCalendar; import android.os.Bundle; import android.widget.EditText; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import java.util.Locale; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EditText year = findViewById(R.id.year); EditText month = findViewById(R.id.month); EditText day = findViewById(R.id.day); TextView gengo = findViewById(R.id.gengou); findViewById(R.id.button).setOnClickListener(v -> { int y = Integer.parseInt(year.getText().toString()); int m = Integer.parseInt(month.getText().toString()); int d = Integer.parseInt(day.getText().toString()); JapaneseCalendar jpCalendar = new JapaneseCalendar(); DateFormatSymbols dfs = new DateFormatSymbols(jpCalendar, Locale.JAPANESE); DateFormat dateFormat = new SimpleDateFormat("Gyy年MM月dd日", dfs); dateFormat.setCalendar(jpCalendar); String s = dateFormat.format(new GregorianCalendar(y, m - 1, d).getTime()); gengo.setText(s); }); } }Android標準APIではなく、IBMのICUで令和に対応する
せっかくAndroid標準APIで令和に対応するってのに!それを使わずに、IBMのICUで令和に対応するって、なんか本末転倒!
と思われるかもしれませんが、以下の理由でそんなコトもしてみたいのです。
- Android標準APIで令和に対応するのは、「R」からだ。それはいい。
- ICUがAndroid標準APIに組み込まれたのは、APIレベル24(Android 7.0「N」)だ。
- であれば、Gradleの設定で
minSdkVersion
は最低でも24でないと、令和うんぬん以前に、android.icu
パッケージが使えない。- だけど、自分の作るアプリは、Androidのバージョン6とか5とか4とかにも対応したい!
ということで、そのやり方です。
Gradleの設定
build.gradledependencies { implementation 'com.ibm.icu:icu4j:64.2' }依存ライブラリとして追記してSyncNowですね。令和対応の
64.2
以上を指定します。プログラム
名前衝突を避けるためにも
import
文はこうなります。パッケージにご注意import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.DateFormatSymbols; import com.ibm.icu.text.SimpleDateFormat; import com.ibm.icu.util.GregorianCalendar; import com.ibm.icu.util.JapaneseCalendar; import java.util.Locale;
Locale
だけは、安定のjava.util
パッケージですね。
これでAndroid 6とか5とか4とかでも、令和対応できます。[おまけ]当たり前ですが。
「光文」という元号は、ICUにはありませんので、ご安心を。
以上です。
- 投稿日:2020-03-27T02:05:30+09:00
Java 8のOptionalをSwiftのOptionalと比較する
概要
本記事ではOptionalのApple公式ドキュメントの概要に掲載されている項目を元にして、Swiftの
Optional
型とJava 8のOptional
型を比較します。
なお本記事に掲載してあるスニペットは多少簡略化されているので、そのままコピペしても動かないものがあります。ご了承ください。Nil結合演算子(Nil-Coalescing Operator)
Swift
中身があるならそれを、なければ指定したデフォルト値を返すように
Optinal
をアンラップします。??
という演算子を使います。複数のOptional
型をオペランドとしてつなぐこともできます。Swiftlet wrappedValue: String? = nil let defaultValue = "default" let value = wrappedValue ?? defaultValue print(value) // "default"Swiftlet wrappedValue: String? = nil let defaultValue = "default" let otherWrappedValue: String? = nil // 右オペランドがOptionalである限り??でつなぐことができる let value = wrappedValue ?? otherWrappedValue ?? defaultValue print(value) // "default"Java 8
Java 8では演算子ではなく
orElse
、orElseGet
というメソッドです。orElse
ではデフォルト値そのものを、orElseGet
ではデフォルト値を提供するSupplier<? extends T>
型のオブジェクトを渡します。しかしいずれのメソッドもラップしている型を返すので、SwiftのようにorElse
で複数のOptional
型をつなぐことはできません。JavaOptional<String> wrappedValue = Optional.empty(); String defaultValue = "default"; String value = wrappedValue.orElse(defaultValue); System.out.println(value); // "default" String otherValue = wrappedValue.orElseGet( () -> defaultValue ); System.out.println(otherValue); // "default"JavaOptional<String> otherWrappedValue = Optional.of("other"); // これはできない // String unwrappedValue = wrappedValue.orElse(otherWrappedValue).orElse(defaultValue);短絡評価
Swift
SwiftのNil結合演算子は短絡評価します。したがって左オペランドが
nil
でない限り右オペランドが評価されることはありません。Swiftfunc hoge() -> String { print("called") return "hoge" } let value = nil ?? hoge() print(value) // "called" // "hoge" // 短絡評価なのでhoge()は呼ばれない let value = "non nil" ?? hoge() print(value) // "non nil"Java 8
Java 8の
orElseGet
も短絡評価ですが、orElse
はしません。デフォルト値そのものを渡しているので当然と言えば当然ですね。Javapublic String hoge() { System.out.println("called"); return "hoge"; } String value = Optional.<String>empty().orElseGet(this::hoge); System.out.println(value); // "called" // "hoge" // 短絡評価なのでhoge()は呼ばれない String value = Optional.of("non null").orElseGet(this::hoge); System.out.println(value); // "non null"Java// orElseは短絡評価しない String value = Optional.of("non null").orElse(hoge()); System.out.println(value); // "called" // "non null"使い分けについてですが、デフォルト値生成にコストがかかる場合などには短絡評価する
orElseGet
を使うと良いでしょう。Javapublic String fetchValue() { // 時間のかかる処理 return result; } // △ - 必ずfetchValue()が呼ばれるので少なくともパフォーマンスに影響 String value = wrappedValue.orElse(fetchValue()); // ◯ - 短絡評価なので中身があれば呼ばれない String value = wrappedValue.orElseGet(this::fetchValue);Java// △ - 中身があると無駄なオブジェクト生成となる Person person = wrappedPerson.orElse(new Person()); // ◯ - 短絡評価なので中身があればオブジェクトは生成されない Person person = wrappedPerson.orElseGet(Person::new);強制アンラップ(Unconditional Unwrapping)
Swift
中身があるかどうかにかかわらず値をアンラップします。演算子
!
を使います。もしnil
であった場合は実行時エラーを引き起こします。Swiftlet number = Int("42")! print(number) // 42Java 8
Java 8では
get
メソッドを使います。もしnull
であった場合は非検査例外であるNoSuchElementException
が投げられます。
※ なお、intFromString
は説明の便宜上Optional<Integer>
型を返していますが、通常整数を包む場合はOptionalInt
型を使う方が良いでしょう。Javapublic Optional<Integer> intFromString(String string) { try { return Optional.of(Integer.parseInt(string)); } catch (NumberFormatException ignored) { return Optional.empty(); } } int number = intFromString("42").get(); System.out.println(number); // 42変換
Swift
値を変換します。
map
というメソッドを使います。中身があれば与えられた(Wrapped) -> U
型のクロージャに従って値を変換し、nil
であればnil
のまま流します。したがってOptional
型を返します。Swiftlet intString: String? = "42" let percentage: String? = intString.map { "\($0)%" } print(String(describing: percentage)) // Optional("42%")クロージャ内で
Optional
型を返したいときは、(Wrapped) -> Optional<U>
型のクロージャを引数にとるflatMap
メソッドを使います。Swiftlet intString: String? = "42" // Int(String)はInt?を返す let integer: Int? = intString.flatMap { Int($0) } print(String(describing: integer)) // Optional(42)Java 8
Java 8でも同様に
map
、flatMap
というメソッドを使います。使い分けも同様です。JavaOptional<String> intString = Optional.of("42"); Optional<String> percentage = intString.map( str -> str + "%" ); System.out.println(percentage); // Optional[42%]JavaOptional<String> intString = Optional.of("42"); Optional<Integer> integer = intString.flatMap(this::intFromString); System.out.println(integer); // Optional[42]オプショナルバインディング(Optional Binding)
Swift
安全なアンラップ手段として馴染み深いことと思います。
if
やguard
といったキーワードと併せることで、中身がある場合とない場合とで処理を分岐することができます。Swiftlet wrappedValue: String? = "value" if let value: String = wrappedValue { print(value) } else { print("no value") } // "value"Swiftlet wrappedValue: String? = "value" guard let value: String = wrappedValue else { print("no value") return } print(value) // "value"Java 8
Java 8では
ifPresent
というメソッドを介して安全にアンラップすることができます。Consumer<? super T>
型のオブジェクトを引数にとり、その中に中身があった場合の処理を記述します。JavaOptional<String> wrappedValue = Optional.of("value"); wrappedValue.ifPresent( str -> { System.out.println(str); }); // "value"しかしながら中身がない場合の処理を記述できるメソッドはありません。したがって両方の処理を記述したい場合は
isPresent
を使って中身の有無を確認する方法をとる必要があります。ただし、中身がある場合明示的に強制アンラップget
を記述しなければいけないためSwiftと比較すると冗長です。JavaOptional<String> wrappedValue = Optional.of("value"); if (wrappedValue.isPresent()) { // 冗長だが明示的に強制アンラップする必要がある String value = wrappedValue.get(); System.out.println(value); } else { System.out.println("no value"); } // "value"JavaOptional<String> wrappedValue = Optional.of("value"); // カード節的に記述する if (!wrappedValue.isPresent()) { System.out.println("no value"); return; } // 冗長だが明示的に強制アンラップする必要がある String value = wrappedValue.get(); System.out.println(value); // "value"JavaOptional<String> wrappedValue = Optional.of("value"); wrappedValue.ifPresent( str -> { System.out.println(str); })// .orElse( () -> { // これはできない // System.out.println("no value"); // }) ;また
ifPresent
に渡す処理のスコープはラムダ式内であるため、その処理内で呼び出し元のメソッドのリターンはできません。したがって早期リターンなどを行いたい場合はisPresent
を使った方法をとる他ありません。Javapublic boolean foo(Optional<String> wrappedValue) { // 値があるなら早期リターンする if (wrappedValue.isPresent) { String str = wrappedValue.get(); // 何かする return true; } // 値がない場合の処理 return result; }Javapublic boolean foo(Optional<String> wrappedValue) { // 値があるなら早期リターンする wrappedValue.ifPresent( str -> { // 何かする // return true; // スコープがこのラムダ式内にあるため、これはできない }); // 値がない場合の処理 return result; }オプショナルチェイニング(Optional Chaining)
Swift
中身があるときのみプロパティやメソッドへのアクセスを行います。後置演算子
?
を使います。Swiftlet wrappedValue: String? = "value" let uppercased: String? = wrappedValue?.uppercased() print(String(describing: uppercased)) // Optional("VALUE")Java 8
Java 8では先に紹介した
map
メソッドが使えるでしょう。メソッド参照を用いればある程度簡潔に書くことができます。JavaOptional<String> wrappedValue = Optional.of("value"); Optional<String> uppercased = wrappedValue.map(String::toUpperCase); System.out.println(uppercased); // Optional[VALUE]まとめ
本記事ではSwiftでの
Optional
型の扱いを元に、Java 8とOptional
型との比較を行いました。特に、オプショナルバインディングについては両者の違いがよくあらわれていました。言語レベルでNullセーフであるSwiftと比べてしまうと扱いづらさが目立ってしまうものの、ラムダ式やメソッド参照のおかげでかなり書きやすくなっているなと感じています。
もし何か間違いなどございましたらコメントにてご指摘ください。オマケ - Java 9のOptional
ここではJava 9で追加された
Optional
のメソッド、or
とifPresentOrElse
を紹介します。Java 8の話は出てきません。完全なオマケです。or
中身がない場合に与えられた
Supplier<? extends Optional<? extends T>>
型のオブジェクトを実行してOptional
型のデフォルト値を得ます。これはちょうどSwiftのNil結合演算子で右オペランドにOptional
型を持ってきた場合と対応しています。
また短絡評価をするため、中身がある限り与えられたSupplier
オブジェクトが実行されることはありません。Swiftlet wrappedValue: String? = nil let defaultValue = "default" let otherWrappedValue: String? = nil let value = wrappedValue ?? otherWrappedValue ?? defaultValue print(value) // "default"Java// available Java 9 or later Optional<String> wrappedValue = Optional.empty(); String defaultValue = "default"; Optional<String> otherWrappedValue = Optional.empty(); // Java 9以降ならデフォルト値にOptional型を指定できる String value = wrappedValue.or( () -> otherWrappedValue ).orElse(defaultValue); System.out.println(value); // "default"ifPresentOrElse
中身がある場合とない場合とで処理を分岐することができます。中身がある場合には
Consumer<? super T>
型のオブジェクトが、ない場合にはRunnable
型のオブジェクトが実行されます。null
だった場合の処理も記述できるので、Java 8と比べるとSwiftのオプショナルバイディングにかなり近づいています。Swiftlet wrappedValue: String? = "value" if let value = wrappedValue { print(value) } else { print("no value") } // "value"Java// available Java 9 or later Optional<String> wrappedValue = Optional.<String>empty(); wrappedValue.ifPresentOrElse( str -> { System.out.println(str); }, () -> { System.out.println("no value"); }); // "no value"
- 投稿日:2020-03-27T02:05:30+09:00
Java 8のOptionalをSwiftと比較する
概要
本記事ではOptionalのApple公式ドキュメントの概要に掲載されている項目を元にして、Swiftの
Optional
型とJava 8のOptional
型を比較します。若干今更感のある内容にはなりますが個人的な備忘録も兼ねて投稿に至りました。
記事の最後にはそれぞれの記法の比較表も掲載しています。あわせてご覧ください。
なお本記事に掲載してあるスニペットは多少簡略化されているので、そのままコピペしても動かないものがあります。ご了承ください。Nil結合演算子(Nil-Coalescing Operator)
Swift
中身があるならそれを、なければ指定したデフォルト値を返すように
Optinal
をアンラップします。??
という演算子を使います。複数のOptional
型をオペランドとしてつなぐこともできます。Swiftlet wrappedValue: String? = nil let defaultValue = "default" let value = wrappedValue ?? defaultValue print(value) // "default"Swiftlet wrappedValue: String? = nil let defaultValue = "default" let otherWrappedValue: String? = nil // 右オペランドがOptionalである限り??でつなぐことができる let value = wrappedValue ?? otherWrappedValue ?? defaultValue print(value) // "default"Java 8
Java 8では演算子ではなく
orElse
、orElseGet
というメソッドです。orElse
ではデフォルト値そのものを、orElseGet
ではデフォルト値を提供するSupplier<? extends T>
型のオブジェクトを渡します。しかしいずれのメソッドもラップしている型を返すので、SwiftのようにorElse
で複数のOptional
型をつなぐことはできません。JavaOptional<String> wrappedValue = Optional.empty(); String defaultValue = "default"; String value = wrappedValue.orElse(defaultValue); System.out.println(value); // "default" String otherValue = wrappedValue.orElseGet( () -> defaultValue ); System.out.println(otherValue); // "default"JavaOptional<String> otherWrappedValue = Optional.of("other"); // これはできない // String unwrappedValue = wrappedValue.orElse(otherWrappedValue).orElse(defaultValue);短絡評価
Swift
SwiftのNil結合演算子は短絡評価します。したがって左オペランドが
nil
でない限り右オペランドが評価されることはありません。Swiftfunc hoge() -> String { print("called") return "hoge" } let value = nil ?? hoge() print(value) // "called" // "hoge" // 短絡評価なのでhoge()は呼ばれない let value = "non nil" ?? hoge() print(value) // "non nil"Java 8
Java 8の
orElseGet
も短絡評価ですが、orElse
はしません。デフォルト値そのものを渡しているので当然と言えば当然ですね。Javapublic String hoge() { System.out.println("called"); return "hoge"; } String value = Optional.<String>empty().orElseGet(this::hoge); System.out.println(value); // "called" // "hoge" // 短絡評価なのでhoge()は呼ばれない String value = Optional.of("non null").orElseGet(this::hoge); System.out.println(value); // "non null"Java// orElseは短絡評価しない String value = Optional.of("non null").orElse(hoge()); System.out.println(value); // "called" // "non null"使い分けについてですが、デフォルト値生成にコストがかかる場合などには短絡評価する
orElseGet
を使うと良いでしょう。Javapublic String fetchValue() { // 時間のかかる処理 return result; } // △ - 必ずfetchValue()が呼ばれるので少なくともパフォーマンスに影響 String value = wrappedValue.orElse(fetchValue()); // ◯ - 短絡評価なので中身があれば呼ばれない String value = wrappedValue.orElseGet(this::fetchValue);Java// △ - 中身があると無駄なオブジェクト生成となる Person person = wrappedPerson.orElse(new Person()); // ◯ - 短絡評価なので中身があればオブジェクトは生成されない Person person = wrappedPerson.orElseGet(Person::new);強制アンラップ(Unconditional Unwrapping)
Swift
中身があるかどうかにかかわらず値をアンラップします。演算子
!
を使います。もしnil
であった場合は実行時エラーを引き起こします。Swiftlet number = Int("42")! print(number) // 42Java 8
Java 8では
get
メソッドを使います。もしnull
であった場合は非検査例外であるNoSuchElementException
が投げられます。
※ なお、intFromString
は説明の便宜上Optional<Integer>
型を返していますが、通常整数を包む場合はOptionalInt
型を使う方が良いでしょう。Javapublic Optional<Integer> intFromString(String string) { try { return Optional.of(Integer.parseInt(string)); } catch (NumberFormatException ignored) { return Optional.empty(); } } int number = intFromString("42").get(); System.out.println(number); // 42変換
Swift
値を変換します。
map
というメソッドを使います。中身があれば与えられた(Wrapped) -> U
型のクロージャに従って値を変換し、nil
であればnil
のまま流します。したがってOptional
型を返します。Swiftlet intString: String? = "42" let percentage: String? = intString.map { "\($0)%" } print(String(describing: percentage)) // Optional("42%")クロージャ内で
Optional
型を返したいときは、(Wrapped) -> Optional<U>
型のクロージャを引数にとるflatMap
メソッドを使います。Swiftlet intString: String? = "42" // Int(String)はInt?を返す let integer: Int? = intString.flatMap { Int($0) } print(String(describing: integer)) // Optional(42)Java 8
Java 8でも同様に
map
、flatMap
というメソッドを使います。使い分けも同様です。JavaOptional<String> intString = Optional.of("42"); Optional<String> percentage = intString.map( str -> str + "%" ); System.out.println(percentage); // Optional[42%]JavaOptional<String> intString = Optional.of("42"); Optional<Integer> integer = intString.flatMap(this::intFromString); System.out.println(integer); // Optional[42]オプショナルバインディング(Optional Binding)
Swift
安全なアンラップ手段として馴染み深いことと思います。
if
やguard
といったキーワードと併せることで、中身がある場合とない場合とで処理を分岐することができます。Swiftlet wrappedValue: String? = "value" if let value: String = wrappedValue { print(value) } else { print("no value") } // "value"Swiftlet wrappedValue: String? = "value" guard let value: String = wrappedValue else { print("no value") return } print(value) // "value"Java 8
Java 8では
ifPresent
というメソッドを介して安全にアンラップすることができます。Consumer<? super T>
型のオブジェクトを引数にとり、その中に中身があった場合の処理を記述します。JavaOptional<String> wrappedValue = Optional.of("value"); wrappedValue.ifPresent( str -> { System.out.println(str); }); // "value"しかしながら中身がない場合の処理を記述できるメソッドはありません。したがって両方の処理を記述したい場合は
isPresent
を使って中身の有無を確認する方法をとる必要があります。ただし、中身がある場合明示的に強制アンラップget
を記述しなければいけないためSwiftと比較すると冗長です。JavaOptional<String> wrappedValue = Optional.of("value"); if (wrappedValue.isPresent()) { // 冗長だが明示的に強制アンラップする必要がある String value = wrappedValue.get(); System.out.println(value); } else { System.out.println("no value"); } // "value"JavaOptional<String> wrappedValue = Optional.of("value"); // カード節的に記述する if (!wrappedValue.isPresent()) { System.out.println("no value"); return; } // 冗長だが明示的に強制アンラップする必要がある String value = wrappedValue.get(); System.out.println(value); // "value"JavaOptional<String> wrappedValue = Optional.of("value"); wrappedValue.ifPresent( str -> { System.out.println(str); })// .orElse( () -> { // これはできない // System.out.println("no value"); // }) ;また
ifPresent
に渡す処理のスコープはラムダ式内であるため、その処理内で呼び出し元のメソッドのリターンはできません。したがって早期リターンなどを行いたい場合はisPresent
を使った方法をとる他ありません。Javapublic boolean foo(Optional<String> wrappedValue) { // 値があるなら早期リターンする if (wrappedValue.isPresent) { String str = wrappedValue.get(); // 何かする return true; } // 値がない場合の処理 return result; }Javapublic boolean foo(Optional<String> wrappedValue) { // 値があるなら早期リターンする wrappedValue.ifPresent( str -> { // 何かする // return true; // スコープがこのラムダ式内にあるため、これはできない }); // 値がない場合の処理 return result; }オプショナルチェイニング(Optional Chaining)
Swift
中身があるときのみプロパティやメソッドへのアクセスを行います。後置演算子
?
を使います。Swiftlet wrappedValue: String? = "value" let uppercased: String? = wrappedValue?.uppercased() print(String(describing: uppercased)) // Optional("VALUE")Swiftclass Hoge { func hoge() { print("hoge") } } let wrappedValue: Hoge? = Hoge() wrappedValue?.hoge() // "hoge"Java 8
Java 8では先に紹介した
map
メソッドが使えます。対象がメソッドであればメソッド参照を用いて簡潔に書くことができます。ただし、対象がOptional
型を返す場合にはflatMap
を使う必要があります。JavaOptional<String> wrappedValue = Optional.of("value"); Optional<String> uppercased = wrappedValue.map(String::toUpperCase); System.out.println(uppercased); // Optional[VALUE]Javaclass User { final String id; Optional<String> mail = Optional.empty(); User(String id) { this.id = id; } } Optional<User> wrappedUser = Optional.of(new User("user1")); Optional<String> mail = wrappedUser.flatMap( user -> user.mail ); System.out.println(mail); // Optional.empty使い分けを含め変換のときと全く同じ記法ですね。Java 8ではラップしている値のフィールドやメソッドにアクセスするためだけの特別な記法やメソッドが用意されているわけではありません。
値を返さないメソッドへのアクセスは先に紹介したifPresent
を使います。Javaclass Hoge { void hoge() { System.out.println("hoge"); } } Optional<Hoge> wrappedValue = Optional.of(new Hoge()); wrappedValue.ifPresent(Hoge::hoge); // "hoge"まとめ
本記事ではSwiftでの
Optional
型の扱いを元に、Java 8とOptional
型の比較を行いました。以下にそれぞれの記法の比較表を掲載します。特にオプショナルバインディングとオプショナルチェイニングについては両者の違いがよくあらわれています。Java 8のオプショナルバインディングでは分岐と同時にアンラップできない分Swiftより少し冗長です。またJava 8にはSwiftの?
演算子のようなラップしている値にアクセスするための専用手段は用意されていません。
Swift Java Nil結合演算子 ??
orElse
※ 非短絡評価
orElseGet
強制アンラップ !
get
変換 map
flatMap
map
flatMap
オプショナルバインディング if let
guard let
ifPresent
もしくはisPresent
で分岐した後にget
で強制アンラップ
オプショナルチェイニング ?
Optional
型を返すならflatMap
それ以外を返すならmap
値を返さないならifPresent
もし何か間違いなどございましたらコメントにてご指摘ください。
オマケ - Java 9のOptional
ここではJava 9で追加された
Optional
のメソッド、or
とifPresentOrElse
を紹介します。Java 8の話は出てきません。完全なオマケです。or
中身がない場合に与えられた
Supplier<? extends Optional<? extends T>>
型のオブジェクトを実行してOptional
型のデフォルト値を得ます。これはちょうどSwiftのNil結合演算子で右オペランドにOptional
型を持ってきた場合と対応しています。
また短絡評価をするため、中身がある限り与えられたSupplier
オブジェクトが実行されることはありません。Swiftlet wrappedValue: String? = nil let defaultValue = "default" let otherWrappedValue: String? = nil let value = wrappedValue ?? otherWrappedValue ?? defaultValue print(value) // "default"Java// available Java 9 or later Optional<String> wrappedValue = Optional.empty(); String defaultValue = "default"; Optional<String> otherWrappedValue = Optional.empty(); // Java 9以降ならデフォルト値にOptional型を指定できる String value = wrappedValue.or( () -> otherWrappedValue ).orElse(defaultValue); System.out.println(value); // "default"ifPresentOrElse
中身がある場合とない場合とで処理を分岐することができます。中身がある場合には
Consumer<? super T>
型のオブジェクトが、ない場合にはRunnable
型のオブジェクトが実行されます。null
だった場合の処理も記述できるので、Java 8と比べるとSwiftのオプショナルバイディングにかなり近づいています。Swiftlet wrappedValue: String? = "value" if let value = wrappedValue { print(value) } else { print("no value") } // "value"Java// available Java 9 or later Optional<String> wrappedValue = Optional.<String>empty(); wrappedValue.ifPresentOrElse( str -> { System.out.println(str); }, () -> { System.out.println("no value"); }); // "no value"