- 投稿日:2019-05-29T23:22:19+09:00
様々なプログラミング言語でクラス (あるいはクラスもどき) を書く
概要
- 様々なプログラミング言語でクラス (あるいはクラスもどき) を書く
Java
MyCounter.java
public class MyCounter { public static MyCounter globalCounter = null; private String name = "No Name"; private int count; public MyCounter(int initValue) { count = initValue; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int addCount(int number) { count += number; return count; } public void print() { System.out.println(format()); } private String format() { return name + ": " + count; } public static void createGlobalCounter(String name, int initValue) { globalCounter = new MyCounter(initValue); globalCounter.setName(name); } }MyMain.java
public class MyMain { public static void main(String[] args) { MyCounter counter = new MyCounter(0); counter.print(); counter.setName("John Doe"); String currentName = counter.getName(); System.out.println("Current Name=" + currentName); int currentValue = counter.addCount(10); System.out.println("Current Value=" + currentValue); counter.print(); MyCounter.createGlobalCounter("CafeBabe! Write once, run anywhere", 256); MyCounter.globalCounter.print(); } }実行結果
No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 CafeBabe! Write once, run anywhere: 256JavaScript
my_counter.js
class MyCounter { constructor(initValue) { this._name = 'No Name'; this.count = initValue; } get name() { return this._name; } set name(name) { this._name = name; } addCount(number) { this.count += number; return this.count; } print() { console.log(this._format()); } _format() { return this._name + ': ' + this.count; } static createGlobalCounter(name, init_value) { MyCounter.global_counter = new MyCounter(init_value); MyCounter.global_counter.name = name; } } module.exports = MyCounter;my_main.js
const MyCounter = require('./my_counter'); counter = new MyCounter(0); counter.print(); counter.name = 'John Doe'; const currentName = counter.name; console.log('Current Name=' + currentName); const currentValue = counter.addCount(10); console.log('Current Value=' + currentValue); counter.print(); MyCounter.createGlobalCounter('JavaScript was born from Netscape Navigator 2.0', 256); MyCounter.global_counter.print();実行結果
$ node my_main.js No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 JavaScript was born from Netscape Navigator 2.0: 256Python
my_counter.py
class MyCounter: global_counter = None def __init__(self, init_value): self.__name = "No Name" self.__count = init_value @property def name(self): return self.__name @name.setter def name(self, name): self.__name = name def add_count(self, number): self.__count += number return self.__count def print(self): print(self.__format()) def __format(self): return "{0}: {1}".format(self.__name, self.__count) @classmethod def create_global_counter(cls, name, init_value): cls.global_counter = MyCounter(init_value) cls.global_counter.name = namemy_main.py
from my_counter import MyCounter counter = MyCounter(0) counter.print() counter.name = "John Doe" current_name = counter.name print("Current Name={}".format(current_name)) current_value = counter.add_count(10) print("Current Value={}".format(current_value)) counter.print() MyCounter.create_global_counter("Batteries included. Beautiful is better than ugly.", 256) MyCounter.global_counter.print()実行結果
$ python my_main.py No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 Batteries included. Beautiful is better than ugly.: 256Ruby
my_counter.rb
class MyCounter attr_accessor :name @@global_counter = nil def initialize(init_value) @name = 'No Name' @count = init_value end def add_count(number) @count += number end def print puts format end private def format "#{@name}: #{@count}" end def self.create_global_counter(name, init_value) @@global_counter = self.new(init_value) @@global_counter.name = name end def self.global_counter @@global_counter end endmy_main.rb
require './my_counter' counter = MyCounter.new(0) counter.print counter.name = 'John Doe' current_name = counter.name puts "Current Name=#{current_name}" current_value = counter.add_count(10) puts "Current Value=#{current_value}" counter.print MyCounter.create_global_counter('Everything is an object in Ruby', 256) MyCounter.global_counter.print$ ruby my_main.rb No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 Everything is an object in Ruby: 256
- 投稿日:2019-05-29T22:46:22+09:00
Jenkinsってなんなんだ
記事の立ち位置
仕事で必要になったけど、何者かわからんので試してみよう
備忘録的なやつ。Jenkinsとは?
- 継続的インテグレーション(CI)のためのOSSツール。
- コンパイル・ビルド・デプロイを自動的に行う。
- 様々な方法で起動することが出来る。(イベント・時間・URLリクエストetc...)
- プラグインが豊富で、機能拡張が容易である。
参考資料
日本人が生み出したソフト「Jenkins」の勢い 日経ビジネス(シリコンバレーNext)
導入方法(MAC)
- AdoptOpenJDKが導入されていることを確認する
- brewからJenkinsをインストールする
$ brew install jenkins以上。
実行方法
ターミナルから下記コマンドを実行
$ jenkinsここでアクセス用のパスワードが発行されるので確認すること。
localhost:8080
にアクセスする
SetupWizardが呼び出されているので、Install Pluginなんとか
を押下する
画面左側のボタンかな?色々とプラグインがインストールされるので、完了するまで待機。
あとは画面の指示に従って名前とかパスワードを設定。これでJenkinsの導入は完了。
機能
TBC
- 投稿日:2019-05-29T18:01:06+09:00
AndroidStudio モジュール内のC++を参照する(Java / kotlin)
Androidでライブラリー作成(AAR形式)はモジュールを作成することで実現します。
ただ、 モジュール内のC++ソース参照はめんどくさいので手順を残します。■Setp1:プロジェクト作成
「File」「New」「New Project」でプロジェクトを作成します。
C++を使用するので。「Native C++」を選択し「Next」をおします。
今回はNameは「Test02」で
Save Locationは開発するディレクトリを指定
Languageは今回はJavaで(Kotlinでも大丈夫です)設定したら「Next」を
あとは、そのまま「Finish」を
こんな画面になればプロジェクト作成できています。■Setp2;モジュール作成
Setp1で作ったプロジェクトにモジュールを追加します。
「File」「New」「New Modile」を選択してください。
今回は「Android Library」を選択し、「Next」を
Application/Library nameは今回は「test02module」にしています。
「Finish」を押せば
こんな感じになり、「test02module」がプロジェクトに追加されたことがわかります。■Setp3:モジュールにC++のソースを追加。
まず、C++のソースを置くディレクトリを作成します。
[test02]
+---[test02modile]
+---[src]
+---[main]
+---[cpp] (このディレクトリを作成)
そこに「CMakeList.txt」「subTest.cpp」「subTest.h」 を作成します。CMakeList.txt# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. native-module # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). subTest.cpp) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} ) set_target_properties( native-module PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR} ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-module # Links the target library to the log library # included in the NDK. ${log-lib})subTest.cpp#include "subTest.h" subTest::subTest() { } void subTest::Hoge(int in_no) { int a = in_no; }subTest.h#pragma once class subTest{ public: subTest(); public: void Hoge(int in_no); };この三ファイルを追加したらbuild.gradle(Module:test02module)を編集します。
build.gradleandroid { : : externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" version "3.10.2" } } }を追加します。
これでいったんSyncさせると
ファイルがModuleに追加されたことがわかります。■Setp4:アプリのCからモジュールのCを呼び出す。
アプリ側のbuild.gradle(Module:app)を編集します。
android { : : sourceSets { main { jniLibs.srcDir '../test02module/src/lib' jni.srcDirs = [] } } }これで、C++のライブラリsoファイルを参照できるようにします。
アプリ側の「CMakeKists.txt」を編集します。
CMakeKists.txt# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). native-lib.cpp) include_directories(../../../../test02module/src/main/cpp) add_library( native-module SHARED IMPORTED ) set_target_properties( native-module PROPERTIES IMPORTED_LOCATION ../../../../../test02module/src/lib/${ANDROID_ABI}/libnative-module.so) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-lib native-module # Links the target library to the log library # included in the NDK. ${log-lib})これで、参照可能になったので。 アプリのCのソースをnative-lib.cpp
native-lib.cpp#include <jni.h> #include <string> #include "subTest.h" extern "C" JNIEXPORT jstring JNICALL Java_l_toox_test02_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { subTest ss; ss.Hoge(32); std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }で参照できます。
■Step5:コンパイルエラー
そのままコンパイルするとエラーになるときがあります。
それはsoファイルが作成されてないかもしれないので
「test02modile」を選択したあとに「Build」を選択すると「Make Moduke 'test02module'」があらわれるので実行してから、アプリを実行すると問題ないです。
- 投稿日:2019-05-29T16:27:52+09:00
AndroidStudioでJavaからCをCからJavaを
AndroidStudioでNDKを使っているとJavaからCを呼ぶのは簡単ですが、時々CからJavaを呼び出したくなるので覚書
(AndroidStudio 3.4.1を使用しています)■Setp1:プロジェクト作成
AndroidStudioの「File」「New」「New Projekut」でプロジェクトを作成します。
今回はJavaからCを呼びたいので「Native C++」を選択し「Next」で次の画面に
Nameは適当に今回は「test01」
Save locationで今回作成するプロジェクトディレクトリを指定
Languageは「Java」で作成をします、設定ができたら「Next」で次の画面に
ここはただ単に「Finish」を選択します。
(この画面ではC++11とか、C++14を使う設定ができますが、後からでも設定できるので無視)
こんな画面がでたら、とりあえず、プロジェクト作成できました。■Setp2:JavaからC
プロジェクト作成した時点で、JavaからCを呼び出すソースになっています。
実行すれば「MainActivity」のなかから「stringFromJNI()」をよんでいるので、native-lib.cppの「Java_l_toox_test01_MainActivity_stringFromJNI」が呼ばれています。
(細かい話は、別途勉強しましょう)■Step3:CからJava
[MainActivity.Java]にCから呼ばれる関数「testFunc」を追加します。
MainActivity.javapackage l.toox.test01; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); public void testFunc(){ } }次にCからJavaを読みだす部分。[Native-lib.cpp]を以下のように修正します。
native-lib.cpp#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL tugini JNIEnv *env, jobject in_thiz) { jobject my_class = env->NewGlobalRef(in_thiz); jclass clazz = env->GetObjectClass(my_class); jmethodID mobj = env->GetMethodID( clazz, "testFunc", "()V" ); env->CallVoidMethod( my_class, mobj ); env->DeleteLocalRef( clazz ); std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }(引数が jobject in_thiz となっている事を注意してください。)
- 投稿日:2019-05-29T15:29:13+09:00
1年に1回くらいAndroidでNFC(FeliCa)をいじる人間のメモ(2019年初夏)
FeliCaの仕事はずいぶんやってないので備忘メモ。
本当はKotlinで書きたいけど、過去のソースとか参考にしたいので一旦Javaで書きます。やりたいこと
- IDmを取得したい
- とりあえずJavaを利用(後でKotlinで書き直す)
- 開発環境はAndroid Studio(3.4をMacで利用)
仕様
ネットに多存在するサンプルの多くはNfcAdapter.enableForegroundDispatch()を利用してアプリがフォアグラウンドにある間ずっと読みっぱなしで、認識したらIntentde処理するものが多いようですが、この仕様だと読むタイミングや機能のOn/OffのコントロールしにくいのでNfcAdapter.enableReaderMode()を利用してみます。
完全にイベントドリブンで読取りってできないのかな?要はonClickで読取りみたいなことしたいのですが、スマートなやり方がわかりません。誰か教えて。
仕様の概要
アプリの動きは下記のような感じ
- READER MODE ONボタン(btn01)で読取りスタート
- 読み取ったらTextView(txt01)に表示
- READER MODE OFFボタン(btn02)で読取り中止
下記のような動き
注意事項
Reader/Write機能をOnに
Reader/Writer機能を利用するアプリを開発する場合は、Android(9.0の場合)の設定で[設定]->[接続機器]->[接続の設定]->NFC[NFC/おサイフケータイ設定]->[Reader/Write,P2P]機能をOnにしておく必要があります。
既存アプリをアンインストール(可能なら)
他のNFC機能を利用するアプリ、特にバックグラウンドで待ってIntentを発生させるようなアプリ(例えば、おサイフケータイアプリ)は開発に影響があるので不要なら削除しておいたほうがいいでしょう。
一方、一般向けアプリの場合は利用者が他のNFCアプリをインストールしていることを前提に仕様を考えておく必要があるでしょう。
実装
無駄が多いですが、各種主要コード全体を貼り付けておきます。
AndroidManifest.xml
NFC利用のパーミッションを追加。
AndroidManifest.xml<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.bluecode.buttontest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> + <uses-permission android:name="android.permission.NFC" /> </manifest>
activity_main.xml
あまり参考になりませんが、とりあえず。
画面は自分で適当にレイアウトした方が早いかも。activity_main.xml<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/txt01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Read ID ..." app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.308" /> <Button android:id="@+id/btn01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" Reader Mode On" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txt01" app:layout_constraintVertical_bias="0.107" /> <Button android:id="@+id/btn02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Reader Mode Off" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btn01" app:layout_constraintVertical_bias="0.116" /> </android.support.constraint.ConstraintLayout>MainActivity
できるだけ要点だけ短く書くために端折ってます。
IDm取得するところまでなら、NfcAdapter作って、Tagを取得すれば、Tag.getId()という感じでIDmを取得できる。
Javaには標準でbyte列をStringにする関数が無いのでカスタム関数で用意してますが、そっちのほうが長いくらい。MainActivity.javapackage jp.bluecode.buttontest; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.os.Handler; import android.os.Looper; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.util.Formatter; import java.util.Locale; public class MainActivity extends AppCompatActivity { //Viewで使う変数を初期化(別にここじゃなくてもいいけど) TextView txt01; Button btn01; Button btn02; //NfcAdapterを初期化 NfcAdapter nfcAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //UIのマーツをマッピング txt01 = findViewById(R.id.txt01); btn01 = findViewById(R.id.btn01); btn02 = findViewById(R.id.btn02); //nfcAdapter初期化 nfcAdapter = NfcAdapter.getDefaultAdapter(this); //Reader Mode Offボタンのenabledをfalseに(トグルにするため) btn02.setEnabled(false); btn01.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //トグル機能 btn01.setEnabled(false); btn02.setEnabled(true); //Redermode On nfcAdapter.enableReaderMode(MainActivity.this,new MyReaderCallback(),NfcAdapter.FLAG_READER_NFC_F,null); } }); btn02.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { //トグル機能 btn01.setEnabled(true); btn02.setEnabled(false); //Readermode Off nfcAdapter.disableReaderMode(MainActivity.this); //表示初期化 txt01.setText("Read ID ..."); } }); } //Callback Class private class MyReaderCallback implements NfcAdapter.ReaderCallback{ @Override public void onTagDiscovered(Tag tag){ Log.d("Hoge","Tag discoverd."); //get idm byte[] idm = tag.getId(); final String idmString = bytesToHexString(idm); //idm取るだけじゃなくてread,writeしたい場合はtag利用してごにょごにょする //親スレッドのUIを更新するためごにょごにょ final Handler mainHandler = new Handler(Looper.getMainLooper()); mainHandler.post(new Runnable() { @Override public void run() { txt01.setText(idmString); } }); } } //bytes列を16進数文字列に変換(めんどい) public static String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); Formatter formatter = new Formatter(sb); for (byte b : bytes) { formatter.format("%02x", b); } return sb.toString().toUpperCase(Locale.getDefault()); } }簡単ですが以上です。
- 投稿日:2019-05-29T07:37:18+09:00
リファクタリングのメモ
ここは?
とりあえずやっつけで書いたコードを、後で見返しても(例え3ヶ月くらい時間が経っていたとしても)再度理解しやすくするために使えそうな技を綴っています。
またエラーや不具合の原因になりうるコードについても、気をつける点と共にまとめています。
GUI編はこちら。断り書き
ここで紹介している内容はあくまでも一例です。
また基本的な言葉そのものの意味などの説明は省略しています。
ケースバイケースで使用すべき技法は変わるものだと思いますので、何でもかんでも厳密にすべて正しい or すべて違うと判断すべきわけではありません。
その技法の利用を強制しているなんていう話でもありません。
人それぞれですので、使えそうなものを拾っていただき、柔軟に見ていただければ幸いです。型変換
""+とvalueOfメソッド
""+とvalueOfメソッドint num = 0; String str = String.valueOf(num); // 処理内容重視 String str = num + ""; // 字数重視これは人によって好みがはっきり分かれるのではないでしょうか。
そこまで厳密にこだわるようなことでもないのでしょうけれど、気になるので挙げました。個人的には
"" +(+ "")
が便利ではある反面、これがソースコードのあちこちで散見されるとなるとどこか歯がゆい思いを抱きそうと思ったんです。
だから(面倒くさくても)valueOfメソッドを使うようにしています。
「valueOfメソッドだって冗長じゃねえか」と言われればそれまでですが、私は「処理内容重視」派なので。Hoge hoge = (Hoge) piyo
総称型を利用した自動変換HogeCalculator hoge = (HogeCalculator) piyo; // 明示的キャスト HogeCalculator hoge = Cast.from(piyo); // 自動キャスト @SuppressWarnings("unchecked") public static <T> T from(Object obj) { return (T) obj; } // Castクラスの中にあるfromメソッド括弧書きの型変換は、行が長かったりクラス名が長かったりすると見づらくなります。
メソッドに総称型を適用し上のコードのような定義を行うことで、代入先の型を判断してキャストしてくれます。どこでも使えるように、私は自作したCastクラスの中にfromメソッドを作り、そこに実装しています。
注意点としては、コンパイルすると型引数の部分(上のコードで言えば
T
)がObjectに置き換わることに配慮しなければならないことです。
上のコードでは、メソッドを定義した直後「型の安全性: Object から T への未検査キャスト」とEclipseから警告されます。これは、「そのまま型引数を使ってキャストしようとしてるけど、型引数の部分がObjectに置き換わるよ?キャストに失敗する可能性があるけどそれでも大丈夫?」と、その注意点を絡めてEclipseが教えてくれているわけです1。
したがって、100%キャスト出来ることが保証されるわけではありません。
キャストしたいオブジェクトの型と代入先の型がキャストできる関係にない状態であれば、ClassCastExceptionを吐きます。
ただその警告がいつまでもされ続けるのは、それはそれで煩わしい。
アノテーション@SuppressWarnings("unchecked")
をつければ、その警告は抑止されます。真偽で代入
真偽で代入String str = ""; // if-else文の場合 if (str.isEmpty()) { str = "yes"; } else { str = "no"; } // メソッドの場合 str = setString(str); private static String setString(String str) { if (str.isEmpty()) return "yes"; else return "no"; // if (str.isEmpty()) return "yes"; return "no"; }ある変数に条件によって異なる値やオブジェクトを代入したいときには、条件演算子が使えます。
条件演算子を使って、if-else文やメソッドによる表現をせずに下のようなコードに書き換えることができます。
ただ条件演算子を無条件で「悪」と一律に嫌うような人は、はじめから参考にしない方が良いでしょう。条件演算子String str = ""; str = str.isEmpty() ? "yes" : "no";条件演算子は
(条件式)? (真式):(偽式)
という構造をなしています。
条件式に分岐処理を行わせるブール値を返すオブジェクトをぶち込み、条件が真である場合に返したい値かオブジェクトを真式に、偽である場合に返したい値かオブジェクトを偽式にぶち込んで使用します。
条件演算子そのものが真か偽かによって値かオブジェクトを返しますので、上のコードのように、変数に直接演算した結果を代入することができるのです。コードが読みづらいという方は、英語に置き換えてイメージしてみてください。
例えばstr.isEmpty() ? "yes" : "no";
は「If str is empty then return "yes", else return "no".」と置き換えると言葉で理解しすくなります(英語には自信がないですが、すみません)。条件演算子の条件式にはブール値を返すオブジェクト、真式、偽式には値やオブジェクトが何でも入りますので、例えばこういう表現もできます。
意味のない使い方、可読性を損ねる使い方str = (str.isEmpty() == false) ? "yes" : "no"; boolean b = str.isEmpty() ? true : false; str = a.isEmpty() ? (c.isEmpty() ? (str.equals("nest") ? c : d) : d.isEmpty() ? "yes" : (b ? c : "no")) : "わけわからん";ただ、このような意味のない使い方や可読性を損ねる使い方は、やめましょう。
1行目は条件式に比較演算子を用いた関係式が入っています。
条件式にはそもそもブール値を返すオブジェクトが入りますので、その返された値を「true」または「false」を用いて判断させるのは無意味です(true == true
やtrue == false
などと記述することと一緒)。
2行目はtrue ? true : false
などと記述することと一緒です(そうしたいならBoolean b = str.isEmpty();
とすべき)。
3行目以降のコードは真式や偽式に条件演算子がネストされていますが、これを読みたいと思えますか?
ある程度の複雑な処理を記述するなら条件演算子だけでは限界がありますので、簡単な処理における利用に留めておきましょう。if-else文
あまり意味のないif-elseや、else-ifの乱用は排除していきましょう。
else-ifの乱用
else-ifの乱用String result; if (str.equals("aaa")) { result = "111"; } else if (str.equals("bbb")) { result = "222"; } else if (str.equals("ccc")) { result = "333"; } else if (str.equals("ddd")) { result = "444"; } else if (str.equals("eee")) { result = "555"; } else { result = "none"; }このぐらいならまだいいかもしれませんが、else-ifが50も100も連なった日には、想像を絶します。
そんな糞コードは後々見返したくなるわけがありません。ただ値(オブジェクト)が同値かどうかを調べるなら、Mapが使えます。
MapによるリファクタリングMap<String, String> map = new HashMap<>() {{ put("aaa", "111"); put("bbb", "222"); put("ccc", "333"); put("ddd", "444"); put("eee", "555"); }}; String result = map.getOrDefault(str, "ぬるぽやないで"); // String value = map.get(str); // String result = Optional.ofNullable(value).orElse("ぬるぽやないで");こうすれば値の関連付けがわかりやすくなるだけではなく、後で関連(キーと値)を追加・削除したい場合には
put()
の数を増減させばいいだけなので、else-ifブロックをいちいち選択してコピペする手間が省けて楽です。else句もなくなりますので、オブジェクト指向エクササイズにもなります。
そんなエクササイズ、すっかり忘れてた。ただMapは存在しないキーを参照しようとするとnullを返しますので、後々の処理でぬるぽにならないようにnull処理をする必要があります。
上のコードでは、getOrDefaultメソッドによってnull処理をしています。
getOrDefaultメソッドは、存在しないキーを参照した際にnullではなく第二引数で指定した値を返してくれます。
よって、キーが存在しない場合は変数resultに文字列「ぬるぽやないで」が代入されます。
Mapの場合はこのメソッド一つで処理できますが、Map以外でリファクタリングする際に似たようなメソッドがないという場合は、無難にOptionalや例外のスローなどでnull処理をしておけばいいと思います。なお、上のコードでnull処理が何も成されていない状態で例えばコンソール出力すると、コンソールには変数resultがnullであることを示す「null」がそのまま表示されます。
少し複雑な条件分岐
続いて、次のようなケースを見てみましょう。
ネストされた条件分岐String result; if (str == null) { result = "ぬるはいけまへん"; } else if (str.equals("aaa")) { result = "111"; } else if (str.startsWith("bbb") && str.length() > 5) { result = "ながい文字だねぇ"; } else if (str.endsWith("だで")) { if (str.length() == 2) { result = "二文字やないかい"; } else if (str.length() > 10) { result = "長すぎるだで"; } else { result = "だでだで"; } } else if (str.contains("4")) { result = "444"; } else { result = "のーん"; }条件分岐のネストに加え、分岐条件が
equals()
だけではなくなりました。
これだと先程のようなString型をキーとするmapを使うやり方では修正することができません。ここまで複雑ならば、メソッドや関数型インターフェースを利用して、純粋にif-returnを羅列していくしかないと思います。
if-returnとUnaryOperatorによるリファクタリングUnaryOperator<String> dadeValidator = s -> { if (s.length() == 2) return "二文字やないかい"; if (s.length() > 10) return "長すぎるだで"; return "だでだで"; }; UnaryOperator<String> mainValidator = s -> { if (s.equals("aaa")) return "111"; if (s.startsWith("bbb") && s.length() > 5) return "ながい文字だねぇ"; if (s.endsWith("だで")) return dadeValidator.apply(s); if (s.contains("4")) return "444"; return "のーん"; }; str = Optional.ofNullable(str).orElse("ぬるはいけまへん"); String result = mainValidator.apply(str);こうなりました。
英文の「if~(then) return ~」に近いコードとなり、ネストも無くなって非常に読みやすくなったと思います。
ネスト部分の処理は、別のOperatorを呼び出して行うことで可能です。それぞれのvalidatorに値を渡すのが面倒くさい!という方は、下のようなコードでも大丈夫です。
if-returnとSupplierによるリファクタリング// str = ...(コンパイルエラーになるので、再代入できない。) Supplier<String> dadeValidator = () -> { if (str.length() == 2) return "二文字やないかい"; if (str.length() > 10) return "長すぎるだで"; return "だでだで"; }; Supplier<String> mainValidator = () -> { if (str.equals("aaa")) return "111"; if (str.startsWith("bbb") && str.length() > 5) return "ながい文字だねぇ"; if (str.endsWith("だで")) return dadeValidator.get(); if (str.contains("4")) return "444"; return "のーん"; }; String result = mainValidator.get();こうすれば、validatorに値を渡す必要なく、ローカル変数strをそのままvalidatorの中(ラムダ式)から参照することができます。
注意点としては、ラムダ式から参照する変数は、その変数の値を変える(再代入する)ことができません。
上のコードであれば「str = ...」のような書き方をするとコンパイルエラーになりますので、最初から変数がnull以外で初期化されていることが前提となります。
逆に考えれば、変数がnullのまま参照することになると「このロケーションでは必ずnullです」とEclipceが警告してくれるので、うっかり屋の私としてはありがたいものです。関数型インターフェースは、使い方さえ覚えれば本当に便利で楽です。
メソッドでなければならない理由がない、または複数のメソッドで使い回したり引数を多くとったりするようなメソッドを設ける必要がなければ、関数型インターフェースで十分のように思います。
メソッドのローカル変数バージョンみたいなイメージです。
ただ関数型インターフェースだけでは実現できないものは、もちろん他の技を使わないといけません。なお、条件分岐に「(変数)== null」や「(変数)!= null」を無闇に盛り込むのはやめましょう。
盛り込むくらいなら、Optionalや例外のスローなどを活用し、事前に処理しておきましょう。
ちなみに「if-returnとUnaryOperatorによるリファクタリング」のコードでは、mainValidatorにnullのまま値を渡す状態だと、最初のif文のequalメソッドでぬるぽになります。今回はif文を用いて条件分岐を行いましたが、switchで条件分岐を行う際は、式にとる値がnullだとその時点でぬるぽになります。
switchを使用する場合も、事前にnull処理を行っておいたほうがいいと思います。
なお、switchの式にとれる値は整数型(byte、char、short、int)、列挙型(JDK1.5以降)、文字列(JDK1.7以降)のいずれかとなりますので、それ以外の場合はそもそも使えません。求めるな、命じよ。
今度は、似たような記述が随所に並ぶコードです。
求めてばかりのコードString apple = "りんご"; String mikan = "みかん"; String banana = "バナナ"; String grape = "ぶどう"; String dorian = "ドリアン"; Map<String, Integer> map = new HashMap<>() {{ put("りんご", 5); put("みかん", 8); put("バナナ", 10); put("ぶどう", 4); put("ドリアン", 7); }}; Supplier<String> validator = () -> { if (str.equals("安藤さん")) return apple + map.get(apple) + "個もってこいやぁ!"; if (str.equals("伊藤くん")) return mikan + map.get(mikan) + "個もってこいやぁ!"; if (str.equals("佐藤さん")) return banana + map.get(banana) + "本もってこいやぁ!"; if (str.equals("鈴木くん")) return grape + map.get(grape) + "房もってこいやぁ!"; if (str.equals("田中さん")) return dorian + map.get(dorian) + "個もってこいやぁ!"; return "誰やねん"; }; String message = validator.get(); System.out.println(message);生徒の名前を聞いて、その生徒から好きな果物と食べたい量(単位付き)をメッセージとして出力してもらうという想定です。
例えば変数strが「佐藤さん」であれば、返ってくるメッセージは「バナナ10本もってこいやぁ!」となります。
生徒の好みの果物や食べたい量は後々変わるかもしれませんし、生徒や果物の種類も増えるかもしれません。このコードも5人や10人くらいならいいかもしれませんが、クラス全員(30~40人)や学校全員などと言われたら、想像を絶します(まずそんな状況そうそうないとは思いますが)。
生徒の好みの果物や食べたい量が変われば、その果物とその果物の単位(個、本など)を直接変えなければならず、しかもこの場合はmapで好みの果物と食べたい量が関連付けされているため、別々に修正することができずとても修正しやすいとはいえません。
また果物の種類が増えるたびに、その果物の名前を表す変数とmapの要素を増やさなければならないのも大変でしょう。好みの果物が変わろうが、果物の種類が増えようが、このままでは生徒らは自発的に何かをしてくれそうにはありません。
皆いつまでも子供じゃないんだから、生徒らから求めるのではなく、生徒らに命じましょう。
(ここでいう「求める」とは、この例で言えば「if(生徒名)return ~」のような条件文をただ一方的に加筆修正することで生徒から情報を求めるという意味です。今のままでは実質的に条件文を加筆修正しない限り、例えば生徒が増えていたとしても「誰やねん」と突っ込まれてしまいます。)それらを区別して命じるためには、クラスの概念が必要になってきます。
クラス抽出
ここでまとめたメモに沿って、クラスにすべき概念、そのクラスに持たせるべき属性を抽出していきます。
後々頻繁な加筆修正が想定され、かつ複数の要素をとりうる集合的概念は、まずクラスの候補になるでしょう。
今回の例で言えば「生徒」「果物」「果物の単位」などです。
「生徒」は{安藤さん、伊藤くん、...}と、複数の生徒を要素としてとりうる集合体になりえます。
「果物」{りんご、みかん、...}や「果物の単位」{個、本、...}にも同じことが言えます。
そして、クラスに持たせておきたい情報(データ)を属性とすべきと思います。
例えば「生徒」には、「生徒の名前」「好きな果物」「食べたい量」を属性に持たせましょう。
「もってこいやぁ!」というような「生徒が発するセリフ」もクラスにしようかと思いましたが、今回はセリフは一つだけの想定なので、「生徒」の属性とします。
他のクラスで頻繁に使いまわしたり、複数のセリフを想定したり、「セリフ」という概念に対してデータや振る舞いを個別に持たせる必要性が生じたりするならば、クラスにすべきだと思います。そもそもなぜクラスを考える必要があるのか?
クラスに属性や振る舞い(メソッド)を持たせ、そのクラスのオブジェクトに対しデータを記憶させたり、何らかの操作を命令させたりすることで、データの管理や操作をしやすくするためです。
こちらがデータを求めてそのデータに対して処理を施し何らかの結果を得るだけで十分な規模ならば、わざわざクラスを考える必要はありません。
通常の条件分岐やメソッド、関数型インターフェースなどの活用だけで十分です。ただ何から何まで全部自分で条件分岐を駆使して求めていくとするならば、その求める情報が大きくなればなるほど、煩雑になり管理しづらくなるということがないように自主的に制約をかけていく必要性が増大します。
そのためにありとあらゆる変数や配列、コレクションなどを単一メソッドないし単一クラス内に乱立するとなると、それらのスコープや宣言順など(制約)を嫌というほど意識しなければならず、加筆修正する度に長文コードを行ったり来たりという最悪な結末が待っています。そして、なぜ「求める」ではなく「命じる」でなければならないのか?
例えば現実社会でも、何から何まで全部自分で仕事をする(情報を整理したり求めたりする)のは嫌ですよね?
役割や役職ごとに仕事を分担し、全体の仕事の効率化を図るのが普通です(ブラック企業を除く)。
クラスを考えるというのも全く同じことだと思います。
何から何まで全部自分で逐一計算して求めるのではなく、クラスごとにあらかじめ「持たせるべき情報やすべき処理」(仕事)を分散させておき、システム全体の効率化を図るということが「求めるではなく命じる」べき理由ではないでしょうか。リファクタリング1回目
前述のクラス抽出にそって、クラスを設計してみました。
生徒クラスclass Student { private static final String LINE = "もってこいやぁ!"; // セリフ private String name; // 生徒の名前 private Fruit fruit; // 好きな果物(果物クラスのオブジェクト) private int quantity; // 食べたい量 Student(String name, Fruit fruit, int quantity) { this.name = name; this.fruit = fruit; this.quantity = quantity; } // 好きな果物を食べたい量(単位付き)だけ持ってくるよう要求するメッセージを出力 String require() { return name + ":" + fruit.name() + quantity + fruit.measureWord() + LINE; } void setFavoritefruit(Fruit fruit) { this.fruit = fruit; } void setQuantity(int quantity) { this.quantity = quantity; } }その生徒から好きな果物を食べたい量(単位付き)だけ持ってくるよう要求するメッセージを出力させるためにrequireメソッドを設けています。
いつ好みの果物や食べたい量が変わっても良いように、セッターも設けてあります。「セリフ」って英語で何ていうんだろう?と思ってWeblioの例文見てたら「line」がよく使われているようなので、変数名はlineとしました。個人的にはなんだかしっくり来ませんが、向こうの文化がよくわからん人間にとってはしょうがない。
なお、後々再代入(修正)する必要がなく、複数のオブジェクト(同一クラス)で共通する値を使い回す変数は、ぜひstatic finalにしておきましょう。
あと、最初から変数やコンストラクタ、メソッドなどのアクセス修飾子を(クラスも含め)何でもかんでもpublicのままにしておくのもなるべくやめましょう。
自分が想定しないタイミングでそれらの要素が参照され、不必要に値を変えられたときに原因が特定できずにいると苦労しますし、そもそも最初からカプセル化に背いています。
自クラスだけで完結するなら(他の誰からも参照されたくないなら)private、自パッケージ内で使うなら修飾子なし(パッケージプライベート)、サブクラスまでならprotected、それでも賄えない広い範囲で初めてpublicを使うようにしましょう。
そんなんどうでもいいならpublicのままで良いと思いますがね~。困るのは自分だし。また、ここで「好きな果物」のオブジェクトを示すためのインスタンス変数fruitを定義する上で、例えば「果物の名前」だけを記録させておきたいからといってString型なんかにしてはいけません。
そんなことをしたら何のために後述する果物クラスを作ったのかわからなくなってしまいます。
いまいちピンとこない方は、その生徒に好きな果物の名前とその単位をメッセージとして出力させるために、果物クラスのオブジェクトに対してどんな操作を命じられればよいか(どんなメソッドが設けてあればよいか)を考えれば、意味がわかってくると思います。
果物クラスのオブジェクトがもつ「果物の名前」と「果物の単位」を呼び出すことが必要なのですから、それらを生徒が呼び出すために果物クラスのオブジェクトを持たせておかなければならないのです。果物クラスclass Fruit { private String name; // 果物の名前 private MeasureWord measureWord; // 果物の単位(単位クラスのオブジェクト) Fruit(String name, MeasureWord measureWord) { this.name = name; this.measureWord = measureWord; } String name() { return name; } String measureWord() { return measureWord.name(); } }measureWordメソッドで単位クラスのオブジェクトから呼び出した名前をそのまま返すようにします。
これにより、生徒クラスのオブジェクトから、その生徒の好きな果物の単位を呼び出すことが出来るようになります。単位クラスclass MeasureWord { private String name; // 単位の名前 MeasureWord(String name) { this.name = name; } String name() { return name; } }単位名しか持たせるものがないくらいなら、わざわざクラスにする必要なかったかも。
まあ存在意義なんてこじつけでも何でもいくらでも後付けできるので、気にしない。そしてクラスの使用例です。
クラスの使用例MeasureWord ko = new MeasureWord("個"); MeasureWord hon = new MeasureWord("本"); MeasureWord husa = new MeasureWord("房"); Fruit apple = new Fruit("りんご", ko); Fruit mikan = new Fruit("みかん", ko); Fruit banana = new Fruit("バナナ", hon); Fruit grape = new Fruit("ぶどう", husa); Fruit dorian = new Fruit("ドリアン", ko); Student ando = new Student("安藤さん", apple, 5); Student ito = new Student("伊藤くん", mikan, 8); Student sato = new Student("佐藤さん", banana, 10); Student suzuki = new Student("鈴木くん", grape, 4); Student tanaka = new Student("田中さん", dorian, 7); String message = ito.require(); System.out.println(message);こちらは想定する果物とその単位、そして生徒の情報(オブジェクト)を予め変数にぶち込んでおき、メッセージを出力させたい生徒にrequireさせるだけ。
上のコードでは、コンソールに「伊藤くん:みかん8個もってこいやぁ!」と出力されます。後で好みの果物や食べたい量が変わっても大丈夫。
その果物が変数になかったとしても大丈夫。
例えば鈴木くんの好みの果物が「いちご」になって「食べたい量」が100にでもなったら、「いちご」を追加して次のように生徒に命令させれば良いだけです。いちご100個食おうぜFruit strawberry = new Fruit("いちご", ko); suzuki.setFavoritefruit(strawberry); suzuki.setQuantity(100); // チェーンにすることも可能。 // suzuki.setFavoritefruit(strawberry).setQuantity(100); // こんな感じに、メソッドが自分のオブジェクトを返すようにすれば良いだけ。 // Student setFavoritefruit(Fruit fruit) { this.fruit = fruit; return this; } // Student setQuantity(int quantity) { this.quantity = quantity; return this; }求めることなんかせずに、命じるだけでいいのです。
命じる内容、欲しい内容はメソッドで定義すればよいのです。クラスやメソッドを定義しなければならなかったり、定義するためにコードを多く記述しなければならなかったりと最初は大変かもしれませんが、慣れてくればifやswitchで条件分岐した何百、何千行というような神コードを記述する必要は一切なくなります。
ただ、上のようなコードもまだまだ再考の余地があります。
ただの変数の宣言の羅列ですので、想定する生徒の数や果物の種類が増えれば増えるほど、更に生徒以外に例えば先生らも想定したり、性別も考慮したりしようとも思えば、このままでは簡単に神コードに近づいていけます。
同じ場所に複数の概念の要素が多く混在しているままというのも良くないですから、別々の場所に分けて参照出来るように修正していきます。リファクタリング2回目(列挙型)
多くの値を一括で管理しておきたいような場合には、列挙型(enum)が使えます。
列挙型とはクラスのようなもの(というかクラス)で、内部に列挙子というオブジェクトを管理しておくことが出来るようになっています。とりあえず例です。
生徒列挙体enum StudentConstants { ANDO(new Student("安藤さん", FruitConstants.APPLE, 5)), ITO(new Student("伊藤くん", FruitConstants.MIKAN, 8)), ENDO(new Student("遠藤さん", FruitConstants.STRAWBERRY, 10)), SATO(new Student("佐藤さん", FruitConstants.BANANA, 10)), SUZUKI(new Student("鈴木くん", FruitConstants.GRAPE, 4)), TANAKA(new Student("田中さん", FruitConstants.DORIAN, 7)), TESHIGAWARA(new Student("勅使河原くん", FruitConstants.DORIAN, 35)),; private Student student; private StudentConstants(Student student) { this.student = student; } Student get() { return student; } }大文字の名前で置かれている各要素が列挙子です。
各列挙子は、クラスのオブジェクトを引数付きコンストラクタを用いて初期化するのと同じように、各列挙子の引数に初期化するためのオブジェクトを指定することで、その列挙子を初期化しています(言い回しがわかりずらいかもしれませんが、すみません)。
もちろん引数を持たせずに列挙子をおくことも可能です。
例えば、列挙子ANDOであれば、new Student("安藤さん", FruitConstants.APPLE, 5)
をStudentConstantsコンストラクタの引数に渡し、インスタンス変数studentにそのインスタンスをぶち込むというような流れです。
列挙型に宣言されているインスタンス変数studentは各列挙子が持つことができますので、それぞれの列挙子が別々の生徒クラスのオブジェクトを管理することが出来る仕組みです。
その管理している生徒クラスのオブジェクトは、コンストラクタの下にあるgetメソッドで取得できるようにしてあります。また次に示すコードのように、生徒クラスのコンストラクタの第二引数に果物列挙体の列挙子をそのまま渡して、コンストラクタで列挙子から果物クラスのオブジェクトを取得するように修正しました。
生徒クラスのコンストラクタ(修正)Student(String name, FruitConstants fruit, int quantity) { this.name = name; this.fruit = fruit.get(); this.quantity = quantity; }これにより、生徒クラスのコンストラクタの第二引数は果物列挙体の列挙子しか受け付けないことになり、今までのように自由に果物クラスの変数を宣言して、そのオブジェクトを生徒クラスのコンストラクタに渡すことはできなくなります。
一見煩わしくしてどうすると思われるかもしれませんが、列挙型を使うことでオブジェクトの使い方やそのオブジェクトを管理する場所を制限するための約束事を決めてもらえるので、使い方さえ慣れれば強い味方です。なお
FruitConstants.~
(列挙名)がやたらと目立ってうざいという場合は、inport static
を用いることで列挙名を省略することが可能です。import static k73i55no5.samples.FruitConstants.*; //最後はアスタリスクとすること。 enum StudentConstants { ANDO(new Student("安藤さん", APPLE, 5)), ITO(new Student("伊藤くん", MIKAN, 8)), ENDO(new Student("遠藤さん", STRAWBERRY, 10)), // 以下略果物クラスのオブジェクトたちも、列挙型を使うとこうなります。
果物列挙体enum FruitConstants { APPLE(new Fruit("りんご", MeasureWord.KO)), MIKAN(new Fruit("みかん", MeasureWord.KO)), BANANA(new Fruit("バナナ", MeasureWord.HON)), GRAPE(new Fruit("ぶどう", MeasureWord.HUSA)), DORIAN(new Fruit("ドリアン", MeasureWord.KO)), STRAWBERRY(new Fruit("いちご", MeasureWord.KO)),; private Fruit fruit; private FruitConstants(Fruit fruit) { this.fruit = fruit; } Fruit get() { return fruit; } }果物クラスのオブジェクトが列挙子に包まれているようなイメージです。
単位クラスのオブジェクトたちについては、数も多くないので、列挙型にはせずにとりあえずクラスの中にstatic finalとしてぶち込んでおきました。
使うときはその変数を参照するようにします。
まず無いとは思いますが、単位が膨大に増えるような想定であれば、各要素を列挙型として管理したほうが良いでしょう。単位クラス(修正)class MeasureWord { static final MeasureWord KO = new MeasureWord("個"); static final MeasureWord HON = new MeasureWord("本"); static final MeasureWord HUSA = new MeasureWord( "房"); private String name; private MeasureWord(String name) { this.name = name; } String name() { return name; } }列挙子の使用例はこんなかんじ。
列挙子の使用例Student student = StudentConstants.TANAKA.get(); String message = student.require(); System.out.println(message); // ワンライナーがお好きな人向け // System.out.println(StudentConstants.TANAKA.get().require());全体のコードはこちら。
https://github.com/k73i55no5/Samples/blob/c68a49f/Refactorers/student190520/0.1/src/Main.java
複数概念が混在する変数の宣言の羅列とは、これでおさらばです。ちなみにこうすると、列挙子として管理されている全生徒の要求が、列挙子が宣言されている順に出力されます。
Stream.of(StudentConstants.class.getEnumConstants()) .map(student -> student.require()) .forEach(System.out::println); /* 出力: * 安藤さん:りんご5個もってこいやぁ! * 伊藤くん:みかん8個もってこいやぁ! * 遠藤さん:いちご10個もってこいやぁ! * 佐藤さん:バナナ10本もってこいやぁ! * 鈴木くん:ぶどう4房もってこいやぁ! * 田中さん:ドリアン7個もってこいやぁ! * 勅使河原くん:ドリアン35個もってこいやぁ! */おまけ
誰かさん「getメソッドださくね?」
しょうがねぇな、これでいいんだろ。
import static k73i55no5.samples.StudentConstants.*; String message = TANAKA.require(); System.out.println(message); enum StudentConstants { // 中略 ... String require() { return student.require(); } }
でもこれってどうなんだろう、見かけ上は「主語.動詞」の名前付けになるからいいかもしれないけど、主語にあたるオブジェクトの実体は列挙子であって生徒クラスのオブジェクトではないからなぁ。う~ん。
まあ、こまけぇこたぁ気にしない。ラムダ式
意図がわからない配列の使い方String[] str = {""}; IntStream.range(0, list.size()).forEach(i -> str[0] += list.get(i));ラムダ式から参照する変数に再代入できないからといってわざわざこのような使い方をするのは、賛否両論あるかもしれませんが、個人的には好きではないです。
配列本来の使用用途からずれてるような気がするし、ここまでしてラムダ式内部から何らかのオブジェクトを参照したいなら、素直にfor文使うとかクラスに属性として包んでそれにブチ込むとかするほうがまだ良いと思う。
テスト的に値の変化を見たくてさっと記述するときには使うことがあるかもしれませんが、半永久的にコードとして残すことはしません。素直にこれで良くないすか?String str = ""; for (int i = 0; i < list.size(); i++) str += list.get(i); // それかこうとか String str = IntStream.range(0, list.size()) .mapToObj(i -> list.get(i)) .collect(Collectors.joining());似たようなクラス
執筆中
抽象クラス使いましょうという話の予定似たようなメソッド
執筆中
インターフェース使いましょうという話の予定もくじに戻る
https://qiita.com/k73i55no5/items/9e0825cee4cc1cb078a6
オブジェクトがとりうるすべての型を想定しなければならないことから引数にとる型もObjectで、型引数が置き換わるObjectと被り紛らわしいかもしれませんが、例えば引数にとる型がStringであったら「型の安全性: String から T への未検査キャスト」と、同じようにStringを用いたキャストが失敗するかもしれない可能性があることを警告してくれます。 ↩
- 投稿日:2019-05-29T01:52:30+09:00
【Java】1対NのListをMapに変換
はじめに
例えば、DBから親子の2テーブルをjoinして抽出した結果をキー毎に処理をしたいとき。
Listのまま処理してもいいけどキーごとにグルーピングした方が見た目きれいですよね?(個人的には可読性が上がるので好きです。性能的に問題が出た場合は仕方ないですが。)
今回はいったん横並びのListで一括取得した後、Map(キー:親テーブル、値:子テーブル)に変換してみます。前提
ER
部署マスタ
- 部署id
- 部署名
社員マスタ
- 社員id
- 社員名
- 部署id(FK)
これを内部結合で取得するとこうなりますね。 ↓
select_all.sqlselect * from 部署マスタ inner join 社員マスタ using 部署id ;取得結果のイメージ(List<結果>) ↓
部署 社員 部署1 社員1 部署1 社員2 部署2 社員3 部署2 社員4 部署2 : これを部署をキー、社員を値とした『Map<部署, List<社員>>』に変換することが今回の目的です。
実装
環境
- Java8
- Spring boot
- Lombok
作ったもの
- DemoService:メイン処理
- DbMapper:実際はMybatisなどのORマッパー。
- DbMapperImpl:テスト用に実装したDbMapper。
- Result:select_all.sqlの結果を格納する
- Department:部署テーブル
- Employee:社員テーブル
Serviceクラス
一応、Java7バージョンのlist2Map7メソッドと、Java8(StreamAPI)のlist2Map8メソッド2つ作成しました。
どちらも結果は同じはず。
やっぱりStreamAPIでの実装はコード量も少なく見やすい。
ビジネスロジックと関係ない実装はあまりしたくないですね。
頑張ればlist2Map8ももっと見やすくなるかも?でもServiceクラス以外に可読性の低いコードが増えそうですね。DemoService.javapackage com.example.demo; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class DemoService { @Autowired DbMapper mapper; public void execute(String[] args) { log.info("### START ###"); // select from DB List<Result> list = mapper.select(); // 抽出した中身 list.forEach(System.out::println); // List ⇒ LinkedHashMap Map<Department, List<Employee>> map = list2Map8(list); System.out.println(); // 変換結果を出力 for (Map.Entry<Department, List<Employee>> entry : map.entrySet()) { System.out.println("Key:" + entry.getKey()); entry.getValue().forEach(e -> System.out.println(" Value:" + e)); } log.info("### END ###"); } /** * List⇒Map変換(java7Ver). * * @param list * @return */ private Map<Department, List<Employee>> list2Map7(List<Result> list) { if (list.isEmpty()) { return Collections.emptyMap(); } Map<Department, List<Employee>> map = new LinkedHashMap<>(); // 前回のキー Department prevDep = list.get(0).getDepartment(); List<Employee> empList = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { if (!list.get(i).getDepartment().equals(prevDep)) { // キーが変わったらMapに詰めてリスト初期化 map.put(prevDep, empList); empList = new ArrayList<>(); } // 値リストに詰める empList.add(list.get(i).getEmployee()); // 前回のキーを更新 prevDep = list.get(i).getDepartment(); if (i == list.size() - 1) { // 最後は必ず追加 map.put(prevDep, empList); } } return map; } /** * List⇒Map変換(java8Ver). * * @param list * @return */ private Map<Department, List<Employee>> list2Map8(List<Result> list) { // いったんList(値)⇒Map(キー、キー&値)に変換 Map<Department, List<Result>> tmp1 = list.stream() .collect(Collectors.groupingBy(Result::getDepartment, LinkedHashMap::new, Collectors.toList())); Map<Department, List<Employee>> ret = new LinkedHashMap<>(); // Map(キー、キー&値)⇒Map(キー、値)に変換 for (Map.Entry<Department, List<Result>> tmp2 : tmp1.entrySet()) { List<Employee> value = tmp2.getValue().stream().map(Result::getEmployee).collect(Collectors.toList()); ret.put(tmp2.getKey(), value); } return ret; } }取得順を保持するためLinkedHashMapを使用しています。
HashMapでは保持されません。DbMapperインタフェース
実際はMybatisとかORマッパーを使う感じ。
DbMapper.javapackage com.example.demo; import java.util.List; import org.springframework.stereotype.Component; @Component public interface DbMapper { List<Result> select(); }DbMapperImplクラス
デバッグ用データを返す。
DbMapperImpl.javapackage com.example.demo; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Component; @Component public class DbMapperImpl implements DbMapper { @Override public List<Result> select() { List<Result> list = new ArrayList<>(); list.add(getResult(1, 101)); list.add(getResult(1, 102)); list.add(getResult(2, 203)); list.add(getResult(3, 304)); list.add(getResult(3, 305)); list.add(getResult(3, 306)); return list; } private Result getResult(int did, int eid) { Department department = new Department(); department.setDepartmentId(did); department.setDepartmentName("システム" + did + "課"); Employee employee = new Employee(); employee.setEmployeeId(eid); employee.setName("山田 " + eid + "郎"); employee.setDepartmentId(department.getDepartmentId()); Result result = new Result(department, employee); return result; } }Resultクラス
DBからのSELECT結果がまず格納されるオブジェクト。
Result.javapackage com.example.demo; import lombok.Data; @Data public class Result { private Department department; private Employee employee; public Result() { } public Result(Department department, Employee employee) { this.department = department; this.employee = employee; } }Departmentクラス
部署マスタ。
Lombokの@Dataでhashcodeとequalsをオーバーライドしています。社員マスタも同様。
状況によりますが実際にこれがテーブルの1レコードを表す場合、オーバーライドするフィールドはPKだけでいいですね。その場合は@Dataは使用せずに自前で作成。LinkedHashMap(HashMap)使うのでオーバーライドは必須。Department.javapackage com.example.demo; import lombok.Data; @Data public class Department { private Integer departmentId; private String departmentName; }Employeeクラス
社員マスタ。
Employee.javapackage com.example.demo; import lombok.Data; @Data public class Employee { private Integer employeeId; private String name; private Integer departmentId; }実行結果
ちゃんとキー(部署)ごとのマップに変換されていますね。
実行結果Result(department=Department(departmentId=1, departmentName=システム1課), employee=Employee(employeeId=101, name=山田 101郎, departmentId=1)) Result(department=Department(departmentId=1, departmentName=システム1課), employee=Employee(employeeId=102, name=山田 102郎, departmentId=1)) Result(department=Department(departmentId=2, departmentName=システム2課), employee=Employee(employeeId=203, name=山田 203郎, departmentId=2)) Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=304, name=山田 304郎, departmentId=3)) Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=305, name=山田 305郎, departmentId=3)) Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=306, name=山田 306郎, departmentId=3)) Key:Department(departmentId=1, departmentName=システム1課) Value:Employee(employeeId=101, name=山田 101郎, departmentId=1) Value:Employee(employeeId=102, name=山田 102郎, departmentId=1) Key:Department(departmentId=2, departmentName=システム2課) Value:Employee(employeeId=203, name=山田 203郎, departmentId=2) Key:Department(departmentId=3, departmentName=システム3課) Value:Employee(employeeId=304, name=山田 304郎, departmentId=3) Value:Employee(employeeId=305, name=山田 305郎, departmentId=3) Value:Employee(employeeId=306, name=山田 306郎, departmentId=3)あとがき
Lombok便利ですよね。
- 投稿日:2019-05-29T00:06:49+09:00
Javaの勉強 ScannerとかMapとか使ってみる
職業訓練校に通い始めてHTML&CSS,JavaをProgateなどで学習していたらQiitaの活用がすっかり、、、
と、いうわけで
Javaの勉強のために購入した「スッキリわかるJava入門 第2版」の練習問題を本来の回答と違う方法で記述してみました
練習2-3
以下のプログラムを作成してください。①画面に「ようこそ占いの館へ」と表示します。
②画面に「あなたの名前を入力してください」と表示します。
③キーボードから1行の文字入力を受け付け、String型の変数nameに格納します。
④画面に「あなたの年齢を入力してください」と表示します。
⑤キーボードから1行の文字入力を受け付け、String型の変数ageStringに格納します
⑥変数ageStringの内容をint型に変換し、int型の変数ageに代入します。
⑦0から3までの乱数を生成し、int型の変数fortuneに代入します。
⑧fortuneの数値をインクリメント演算子で1増やし、1から4の乱数にします。
⑨画面に「占いの結果が出ました!」と表示します
⑩画面に「(年齢)歳の(名前)さん、あなたの運記番号は(乱数)です」と表示します。
その際に(年齢)には変数ageを、(名前)には変数nameを、そして(乱数)に⑧で作った数字を表示させます。
画面に「1:大吉 2:中吉 3:吉 4:凶」と表示します。import java.util.*; public class practice { public static void main(String[] args) { Scanner stdInput = new Scanner(System.in); Random random = new Random(); Map<Integer, String> map = new HashMap<Integer, String>(); map.put(0, "大吉"); map.put(1, "中吉"); map.put(2, "吉"); map.put(3, "凶"); System.out.println("ようこそ占いの館へ!"); System.out.print("あなたの名前を入力してください: "); String name = stdInput.nextLine(); System.out.print("あなたの年齢を入力してください: "); String ageString = stdInput.nextLine(); int age = Integer.parseInt(ageString); int fortune = random.nextInt(4); System.out.println("\n占いの結果が出ました!"); System.out.println(age + "歳の" + name + "さん、あなたの運勢は" + map.get(fortune) + "です"); } }実行結果です
結果ようこそ占いの館へ! あなたの名前を入力してください: はたでめお あなたの年齢を入力してください: 40 占いの結果が出ました! 40歳のはたでめおさん、あなたの運勢は中吉です使用した本
まとめ
色々こうしろと言われてたのですが無視してやった結果がこれです。。。
Pythonの辞書にあたるものを使いたかったのですが、調べて最初に出てきたのがMapだったのでそれを使用しました
javaはデータ型を色んなところで宣言するのがなかなか煩わしいです。。。
分かり易くていいんですがお気づきの点がありましたら遠慮なくご指摘お願いいたします。