- 投稿日:2019-12-18T23:35:47+09:00
Hello Java Lambda
簡単な要件を満たすコードを書くにあたって、Lambda を使わないケースと使うケースを比較してみます。
以下のような数字のリストを用意し、100以上の分だけ2倍したものを合算する処理を考えてみます。
仕様:100以上の分だけ2倍したものを合算するnumber-listfinal List<Integer> numbers = Arrays.asList(50, 100, 10, 400, 120, 30, 220);Lambda を使用しないケース
たとえば、リスト内の値を一つずつチェックし、もし100以上であればその数値を2倍し、total に加算するという流れが考えられます。
これ位の計算であれば、シンプルで分かりやすく大きな問題はないと思われます。
ただ、仕様(100以上の分だけ2倍したものを合算する)に対して、それを実現する一つの方法として ”数字を一つずつチェックする” というエンジニアのアイデアがそこに挟まれるため、やや誤解を与えやすいコードになる可能性があります。without-lambda.javaint total = 0; for (Integer number : numbers) { if (number >= 100) { total += number * 2; } } System.out.println("Total is " + total);Lambda を使用するケース
トータルのコード量としては上記とさほど変わっていません。
ただ、コードから見える処理の流れの読み方が変わってきます。
”リストから100以上の数値をフィルターで取り出し、2倍し、それらを合計する”となります。
ループで一つずつチェックするようなコードは書いていません。
まるで仕様(100以上の分だけ2倍したものを合算する)をそのまま記述しているような形で表現ができています。
全てのケースでそれが当てはまるわけではないと思いますが、要求仕様に対してより誤解の少ないコードがこの例では書けていると思います。with-lambda.javaint total = 0; total = numbers.stream() .filter(num -> num >= 100) .map(num -> num * 2) .reduce(0, (base, value) -> base + value); System.out.println("Total is " + total);
- 投稿日:2019-12-18T23:26:23+09:00
jib-maven-pluginを使ってSpringBootアプリをイメージ化してDockerで起動
はじめに
この記事は、Java Advent Calendar 2019 - Qiita 23日目の記事です。
皆さん、コンテナ使ってますか??
Googleが公開しているjib-maven-pluginを使用すれば、Javaで作成されたアプリを簡単にDockerイメージにすることができます。
今回はSpringBoot・Mavenを使用したSpringBootアプリケーションのイメージビルド及びコンテナ立ち上げまでやってみます(本当はAWSデプロイまでやりたかったけど時間がないので断念)環境
- Java8
- SpringBoot2.1.9.RELEASE
- Maven3.6.3
- jib-maven-plugin1.8.0
- Docker19.03.5
SpringBootアプリケーションを構築する
SpringBoot・Mavenで構築されたプロジェクトを作成します。
Springから提供されているSpring Initializrを使用すれば簡単にアプリケーションを構築することができます。
使用方法はこちら(後日使用方法をアップします)サンプルControllerの追加
Webアプリケーションを今回は作成するので、サンプルのController及びhtmlを追加します。
IndexController
@Controller public class IndexController { @GetMapping("/") public ModelAndView get(ModelAndView mav) { mav.setViewName("index"); return mav; } }index.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>JibSampleApp</title> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <!-- Optional theme --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> <!-- Latest compiled and minified JavaScript --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> </head> <body> <div class="container"> <div class="row"> <div class="jumbotron"> <h1>JibSampleApp</h1> <p>this app is JibSampleApp</p> </div> <div class="panel panel-success"> <div class="panel-heading">blank</div> <div class="panel-body"></div> </div> </div> </div> </body> </html>プラグインを設定
上記で作成したSpringBootアプリケーションのpomに以下のプラグインを追加します。
pom.xml
<plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>1.8.0</version> <configuration> <to> <image>jibsampleimage</image> </to> </configuration> </plugin>build実行
Dockerが起動している状態で、
mvn compile jib:dockerBuild
を実行することで、SpringBootアプリケーションのイメージbuildが実行されます。
Windowsなどで管理者権限を剥奪されている場合、うまく動作しないことがあるので、その場合は一時的に管理者権限を付与する必要があります。
buildがうまくいくと、Dockerのイメージ一覧の中に作成したイメージがあることが確認できます。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE jibsampleimage latest bcb3c6749044 49 years ago 161MBDockerコンテナ起動
上記でbuildしたイメージをもとにDockerコンテナを作成し、実行します。
以下のコマンドを入力することで、ローカルの8080ポートとDockerの8080をマッピングし、ブラウザからアクセスし確認することができます。
docker run -p 8080:8080 -it jibsampleimage
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.9.RELEASE) 2019/12/18 14:11:53.177 [main] INFO c.a.j.s.JibSampleApp Starting JibSampleApp on be8d974734e4 with PID 1 (/app/classes started by root in /) 2019/12/18 14:11:53.189 [main] INFO c.a.j.s.JibSampleApp No active profile set, falling back to default profiles: default 2019/12/18 14:11:56.123 [main] WARN o.m.s.m.ClassPathMapperScanner No MyBatis mapper was found in '[com.atu496.jib.sample]' package. Please check your configuration. 2019/12/18 14:11:58.065 [main] INFO o.s.b.w.e.t.TomcatWebServer Tomcat initialized with port(s): 8080 (http) 2019/12/18 14:11:58.166 [main] INFO o.a.c.h.Http11NioProtocol Initializing ProtocolHandler ["http-nio-8080"] 2019/12/18 14:11:58.213 [main] INFO o.a.c.c.StandardService Starting service [Tomcat] 2019/12/18 14:11:58.217 [main] INFO o.a.c.c.StandardEngine Starting Servlet engine: [Apache Tomcat/9.0.26] 2019/12/18 14:11:58.535 [main] INFO o.a.c.c.C.[.[.[/] Initializing Spring embedded WebApplicationContext 2019/12/18 14:11:58.537 [main] INFO o.s.w.c.ContextLoader Root WebApplicationContext: initialization completed in 5194 ms 2019/12/18 14:11:59.277 [main] INFO o.s.s.c.ThreadPoolTaskExecutor Initializing ExecutorService 'applicationTaskExecutor' 2019/12/18 14:11:59.532 [main] INFO o.s.b.a.w.s.WelcomePageHandlerMapping Adding welcome page template: index 2019/12/18 14:12:00.865 [main] INFO o.a.c.h.Http11NioProtocol Starting ProtocolHandler ["http-nio-8080"] 2019/12/18 14:12:01.033 [main] INFO o.s.b.w.e.t.TomcatWebServer Tomcat started on port(s): 8080 (http) with context path '' 2019/12/18 14:12:01.050 [main] INFO c.a.j.s.JibSampleApp Started JibSampleApp in 9.135 seconds (JVM running for 10.809)起動できていることが確認できると思います。
終わりに
SpringBootアプリケーションの簡単なイメージの作成及びコンテナ起動することができました。
ただ、これはまだローカルでの話なので、時間ができ次第AWS・GCP・Azureのコンテナ実行環境へのデプロイ方法をまとめる予定です(本来はそっちがメインだった。。。)
今回作成したソースコードはGithub上にアップしています。
- 投稿日:2019-12-18T23:15:55+09:00
java 実践編 その1
今日はjavaの簡単な扱いかたについて
書きます。なお先日の投稿であるJDKについて終わっている前提で書いて行きます。
基本手順
少しjavaはrubyと比べて手間がいるコードなので
手順を書いて行きます。1,.javaファイルを作成してjacaコードを書く
2,上記のファイルをjavacコマンドでclassファイルを作成する
3,クラスファイルを実行する。これが簡単な流れです。
基本コードを書いてもコンパイルしてクラスファイルを作らないと
javaは使えないのでjavacコマンドでクラスファイルに変換作成するわけです。基本書式
まず基本的な書式を書いて行きます。
他の言語と比べ前準備のコードがかなり長いです。基本書式.javaclass ファイル名(クラス名){ public static void main(String[] args){ (処理内容) } }例はこちら
HelloJava.javaclass HelloJava { public static void main(String args[]){ System.out.println("Hello!"); } }これでHello!と出力するコマンドがかけました。
クラスファイルの作成
次に実行ファイルに当たるクラスファイルを作成します。
ターミナルjavac javaファイル名.java例として
ターミナルjavac HelloJava.javaこれでHelloJava.classが作成されました。
ファイルの実行
次にクラスファイルを実行します。
例で使われているHelloJava.classを実行します。ターミナルjava クラスファイル名ターミナルjava HelloJavaこれでHello!と出力されました。
名前の注意点
名前は同じにするなど注意点があります。
ファイル名とクラス名を同じ名前にする
ファイル名とクラス名は同じにしなければいけないというJavaの決まりごとがあリます。
大文字、小文字を意識する
また、Javaでは大文字、小文字を認識するため、
「classsample」と「ClassSample」は違うものとして扱われます。
- 投稿日:2019-12-18T21:23:41+09:00
ReactNative AndroidのBrige解説
機能
Native Modules
出来ること
・関数の作成
・イベントの通知
・コールバックNative UI Components
出来ること
・Viewの作成
・Viewのイベントの通知Native Modules
ダイアログを表示するModuleを例にします。
[機能]
1, Androidのダイアログを表示する。(関数の作成)
2, ダイアログの"OK"ボタンがクリックされたらコンソールに"true"が表示される。(イベントの通知)
3, "DATE"ボタンがクリックされたら現在時刻が表示される。(コールバック)DialogModule.javapublic class DialogModule extends ReactContextBaseJavaModule { // コンストラクター public DialogModule(@NonNull ReactApplicationContext reactContext) { super(reactContext); } // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "DialogModule"; } // RNで使用出来るクラス定数を定義出来る public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; } // ダイアログを表示する関数 @ReactMethod public void showDialog(String message, String type) { if (type.equals("SINGLE")) { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } else { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("OK", (dialog, which) -> { sendEvent(); }) .setNegativeButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } } // 現在時刻を表示してコールバックする関数 @ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); } // ボタンがクリックされたことを通知するイベント private void sendEvent() { WritableMap params = Arguments.createMap(); params.putBoolean("click", true); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params); } }解説
getName
RNから呼び出す際の文字列を"DialogModule"のように記述する必要があります。
@Override public String getName() { return "DialogModule"; }
getConstants
RNから使用できるMolueのクラス定数を設定できます。
※実装は必須ではありません。
constants.put(定数名, 値);
public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; }
@ReactMethod
RNから使用できるメソッドを設定できます。
コールバックを実行する場合は.invoke()
で実行します。@ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); }
イベント(リスナー)
RNに登録できるイベントを設定できます。// コールバックの値を設定 WritableMap params = Arguments.createMap(); params.putBoolean("click", true); // イベント名とコールバックの値を設定 getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params);Native UI Components
動画playerを表示するViewを例にします。
[機能]
1, 動画playerを表示する。(Viewの作成)
2, urlをRN側からpropsで受け取り動画を再生する。
3, 再生終了時にログを表示する。(Viewのイベントの通知)VideoViewManager.javapublic class VideoViewManager extends SimpleViewManager<VideoView> { private Context context; // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "VideoView"; } // 使用するViewのインスタンを返すコンストラクターのようなもの @Override protected VideoView createViewInstance(ThemedReactContext reactContext) { this.context = reactContext; return new VideoView(reactContext); } // Propsで受け取った値で処理をする関数 @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) @ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { Uri uri = Uri.parse(urlPath); videoView.setMediaController(new MediaController(context)); videoView.setVideoURI(uri); // 再生準備出来次第再生する videoView.setOnPreparedListener(mp -> { videoView.start(); }); // 再生終了時にpropsの『onFinish』にコールバックする。(通知) videoView.setOnCompletionListener(mp -> { ReactContext reactContext = (ReactContext)context; WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); }); videoView.getDuration(); } @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); } }解説
getName
RNから呼び出す際の文字列を"VideoView"のように記述する必要があります。
@Override public String getName() { return "VideoView"; }
@ReactProp
ViewのPropsを受け取るセッターメソッド
メソッド名自体はなんでもいいです
@ReactProp(name="props名")
public void setProp(Viewの型 view, 型 Propsの値)
@ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { // ... }
createViewInstance
Viewのインスタンスを返す関数
※必須です@Override protected VideoView createViewInstance(ThemedReactContext reactContext) { return new VideoView(reactContext); }
イベントの通知
Propsのコールバックを設定できます。イベントの登録
イベントを登録するメソッドは2つあります。
・getExportedCustomDirectEventTypeConstants
・getExportedCustomBubblingEventTypeConstants
getExportedCustomDirectEventTypeConstants
1つのイベントで1つのpropsに通知する。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("registrationName", "プロップス名")).build();
getExportedCustomBubblingEventTypeConstants
1つのイベントで2つのpropsに通知することができる。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "プロップス名1","captured", "プロップス名2"))).build();
// イベントを登録する関数1 @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } // イベントを登録する関数2 @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); }通知したい箇所で
receiveEvent
を呼び出す
receiveEvent("viewのID", "イベント名", コールバックの値)
// ... ReactContext reactContext = (ReactContext)context; // イベントを通知する処理1 "onDirectEvent"イベント WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); // イベントを通知する処理2 "onBubblingEvent"イベント WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); // ...RN側では以下のようにイベントが通知される
<VideoView onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} // onDirectEvent onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent />ModuleとUI Componentの登録
作成したModuleとUI Componentを以下のように登録します
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( // UI Componentが増えるたびに追加していく new VideoViewManager() ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext return Collections.<ViewManager>singletonList( // Moduleが増えるたびに追加していく new DialogModule(reactContext) ); } }作成したpackageをMainApplication.java内の
getPackages
に登録しますMainApplication.java@Override protected List<ReactPackage> getPackages() { List<ReactPackage> packages = new PackageList(this).getPackages(); // packageが増えるたびに追加していく packages.add(new ExamplePackage()); return packages; }RN側
Native Modules
Dialog.jsximport React from 'react'; import { NativeModules } from 'react-native'; // getName関数の返り値("DialogModule")を指定 DialogModule = NativeModules.DialogModule; const Dialog = () => { const [date, setDate] = React.useState(""); React.useEffect(() => { const eventEmitter = new NativeEventEmitter(DialogModule); eventEmitter.addListener('onClick', (event) => { console.log(event.change) // "true" }); }, []) return ( <> <TouchableOpacity onPress={() => DialogModule.showDialog('SINGLE BUTTON', DialogModule.SINGLE_BUTTON)}> <Text>SINGLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.showDialog('DOUBLE BUTTON', DialogModule.DOUBLE_BUTTON)}> <Text>DOUBLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.getCurrentTime(time => setDate(time))}> <Text>DATE</Text> </TouchableOpacity> <Text>CURRENT DATE:</Text> <Text>[ {date} ]</Text> </> ) } export default DialogNative UI Components
VideoView.jsximport React from 'react'; import { requireNativeComponent } from 'react-native'; VideoView = requireNativeComponent('VideoView'); const VideoView = () => { return ( <> <VideoView style={{ width: '100%', height: '100%' }} url="https://www.radiantmediaplayer.com/media/bbb-360p.mp4" onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} /> </> ) } export default VideoViewおまけ
UI Componentに実装してある関数をModuleから呼びたい場合
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { private ExampleViewManager instance; @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( instance ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext instance = new ExampleViewManager(); return Collections.<ViewManager>singletonList( // モジュールにViewManagerのインスタンスを渡す // あとはモジュール内ので定義した関数からインスタンスのメソッドを呼べば良い new ExampleModule(reactContext, instance) ); } }最後に
Androidのブリッジ部分については英語の記事ばかりかつ動かないサンプルコードが多く苦労しました。
記事書く時間が少なかったので、抜けてるところや間違った箇所があるかもしれないので記事の内容は参考程度にしてください。続きまして、React Native Advent Calendar 20日目の記事は @duka さんの「AB test とかの話」です
- 投稿日:2019-12-18T21:23:41+09:00
ReactNative AndroidのBridge解説
機能
Native Modules
出来ること
・関数の作成
・イベントの通知
・コールバックNative UI Components
出来ること
・Viewの作成
・Viewのイベントの通知Native Modules
ダイアログを表示するModuleを例にします。
[機能]
1, Androidのダイアログを表示する。(関数の作成)
2, ダイアログの"OK"ボタンがクリックされたらコンソールに"true"が表示される。(イベントの通知)
3, "DATE"ボタンがクリックされたら現在時刻が表示される。(コールバック)DialogModule.javapublic class DialogModule extends ReactContextBaseJavaModule { // コンストラクター public DialogModule(@NonNull ReactApplicationContext reactContext) { super(reactContext); } // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "DialogModule"; } // RNで使用出来るクラス定数を定義出来る public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; } // ダイアログを表示する関数 @ReactMethod public void showDialog(String message, String type) { if (type.equals("SINGLE")) { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } else { new AlertDialog.Builder(getCurrentActivity()) .setTitle("DialogModule") .setMessage(message) .setPositiveButton("OK", (dialog, which) -> { sendEvent(); }) .setNegativeButton("CLOSE", (dialog, which) -> { dialog.dismiss(); }) .show(); } } // 現在時刻を表示してコールバックする関数 @ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); } // ボタンがクリックされたことを通知するイベント private void sendEvent() { WritableMap params = Arguments.createMap(); params.putBoolean("click", true); getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params); } }解説
getName
RNから呼び出す際の文字列を"DialogModule"のように記述する必要があります。
@Override public String getName() { return "DialogModule"; }
getConstants
RNから使用できるMolueのクラス定数を設定できます。
※実装は必須ではありません。
constants.put(定数名, 値);
public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SINGLE_BUTTON", "SINGLE"); constants.put("DOUBLE_BUTTON", "DOUBLE"); return constants; }
@ReactMethod
RNから使用できるメソッドを設定できます。
コールバックを実行する場合は.invoke()
で実行します。@ReactMethod private void getCurrentTime(Callback callback) { Calendar calendar = Calendar.getInstance(); callback.invoke(calendar.getTime().toString()); }
イベント(リスナー)
RNに登録できるイベントを設定できます。// コールバックの値を設定 WritableMap params = Arguments.createMap(); params.putBoolean("click", true); // イベント名とコールバックの値を設定 getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onClick", params);Native UI Components
動画playerを表示するViewを例にします。
[機能]
1, 動画playerを表示する。(Viewの作成)
2, urlをRN側からpropsで受け取り動画を再生する。
3, 再生終了時にログを表示する。(Viewのイベントの通知)VideoViewManager.javapublic class VideoViewManager extends SimpleViewManager<VideoView> { private Context context; // このモジュールを呼び出すためのタグのようなもの @Override public String getName() { return "VideoView"; } // 使用するViewのインスタンを返すコンストラクターのようなもの @Override protected VideoView createViewInstance(ThemedReactContext reactContext) { this.context = reactContext; return new VideoView(reactContext); } // Propsで受け取った値で処理をする関数 @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) @ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { Uri uri = Uri.parse(urlPath); videoView.setMediaController(new MediaController(context)); videoView.setVideoURI(uri); // 再生準備出来次第再生する videoView.setOnPreparedListener(mp -> { videoView.start(); }); // 再生終了時にpropsの『onFinish』にコールバックする。(通知) videoView.setOnCompletionListener(mp -> { ReactContext reactContext = (ReactContext)context; WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); }); videoView.getDuration(); } @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); } }解説
getName
RNから呼び出す際の文字列を"VideoView"のように記述する必要があります。
@Override public String getName() { return "VideoView"; }
@ReactProp
ViewのPropsを受け取るセッターメソッド
メソッド名自体はなんでもいいです
@ReactProp(name="props名")
public void setProp(Viewの型 view, 型 Propsの値)
@ReactProp(name="url") public void setVideoPath(VideoView videoView, String urlPath) { // ... }
createViewInstance
Viewのインスタンスを返す関数
※必須です@Override protected VideoView createViewInstance(ThemedReactContext reactContext) { return new VideoView(reactContext); }
イベントの通知
Propsのコールバックを設定できます。イベントの登録
イベントを登録するメソッドは2つあります。
・getExportedCustomDirectEventTypeConstants
・getExportedCustomBubblingEventTypeConstants
getExportedCustomDirectEventTypeConstants
1つのイベントで1つのpropsに通知する。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("registrationName", "プロップス名")).build();
getExportedCustomBubblingEventTypeConstants
1つのイベントで2つのpropsに通知することができる。
登録方法
MapBuilder.builder().put("イベント名", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "プロップス名1","captured", "プロップス名2"))).build();
// イベントを登録する関数1 @Override public Map<String, Object> getExportedCustomDirectEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent")) .build(); } // イベントを登録する関数2 @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames", MapBuilder.of( "bubbled", "onBubble", "captured", "onCapture"))) .build(); }通知したい箇所で
receiveEvent
を呼び出す
receiveEvent("viewのID", "イベント名", コールバックの値)
// ... ReactContext reactContext = (ReactContext)context; // イベントを通知する処理1 "onDirectEvent"イベント WritableMap event = Arguments.createMap(); event.putString("message", "onDirectEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event); // イベントを通知する処理2 "onBubblingEvent"イベント WritableMap event2 = Arguments.createMap(); event2.putString("message", "onBubblingEvent"); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2); // ...RN側では以下のようにイベントが通知される
<VideoView onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} // onDirectEvent onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent />ModuleとUI Componentの登録
作成したModuleとUI Componentを以下のように登録します
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( // UI Componentが増えるたびに追加していく new VideoViewManager() ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext return Collections.<ViewManager>singletonList( // Moduleが増えるたびに追加していく new DialogModule(reactContext) ); } }作成したpackageをMainApplication.java内の
getPackages
に登録しますMainApplication.java@Override protected List<ReactPackage> getPackages() { List<ReactPackage> packages = new PackageList(this).getPackages(); // packageが増えるたびに追加していく packages.add(new ExamplePackage()); return packages; }RN側
Native Modules
Dialog.jsximport React from 'react'; import { NativeModules } from 'react-native'; // getName関数の返り値("DialogModule")を指定 DialogModule = NativeModules.DialogModule; const Dialog = () => { const [date, setDate] = React.useState(""); React.useEffect(() => { const eventEmitter = new NativeEventEmitter(DialogModule); eventEmitter.addListener('onClick', (event) => { console.log(event.change) // "true" }); }, []) return ( <> <TouchableOpacity onPress={() => DialogModule.showDialog('SINGLE BUTTON', DialogModule.SINGLE_BUTTON)}> <Text>SINGLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.showDialog('DOUBLE BUTTON', DialogModule.DOUBLE_BUTTON)}> <Text>DOUBLE BUTTON Dialog</Text> </TouchableOpacity> <TouchableOpacity onPress={() => DialogModule.getCurrentTime(time => setDate(time))}> <Text>DATE</Text> </TouchableOpacity> <Text>CURRENT DATE:</Text> <Text>[ {date} ]</Text> </> ) } export default DialogNative UI Components
VideoView.jsximport React from 'react'; import { requireNativeComponent } from 'react-native'; VideoView = requireNativeComponent('VideoView'); const VideoView = () => { return ( <> <VideoView style={{ width: '100%', height: '100%' }} url="https://www.radiantmediaplayer.com/media/bbb-360p.mp4" onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} /> </> ) } export default VideoViewおまけ
UI Componentに実装してある関数をModuleから呼びたい場合
ExamplePackage.javapublic class ExamplePackage implements ReactPackage { private ExampleViewManager instance; @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.<ViewManager>singletonList( instance ); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext instance = new ExampleViewManager(); return Collections.<ViewManager>singletonList( // モジュールにViewManagerのインスタンスを渡す // あとはモジュール内ので定義した関数からインスタンスのメソッドを呼べば良い new ExampleModule(reactContext, instance) ); } }最後に
Androidのブリッジ部分については英語の記事ばかりかつ動かないサンプルコードが多く苦労しました。
記事書く時間が少なかったので、抜けてるところや間違った箇所があるかもしれないので記事の内容は参考程度にしてください。続きまして、React Native Advent Calendar 20日目の記事は @duka さんの「AB test とかの話」です
- 投稿日:2019-12-18T20:30:22+09:00
ラムダ式をシリアライズして異なるJVM上で実行してみる
Java Advent Calendar 2019 の19日目です。
Java8から導入されたラムダ式はシリアライズが可能で、先達の記事の通り様々な実装パターンがあります。
シリアライズ可能ということはデータストアにロジックを永続化、異なるJVMへの移送・実行が可能ということです。
一方で、JVMに閉じたリソースはどうしてもシリアライズできません。例えばjava.sql.Connection
やjava.io.BufferedWriter
といったI/Oに関するオブジェクトです。ロジックを移送し、かつ移送先で任意のI/Oを注入できれば、ちょっとしたサーバレスアーキテクチャをつくれて面白いんじゃないのか?
本記事はそんな動機から調べてやってみた記録です。FaaSのJavaへの対応状況
本題の前に、各クラウドサービスのFaaSがJavaにどれくらい対応しているのかを確認しておきましょう。
2018年7月現在で、AWS Lambda、IBM Cloud Functions、Azure FunctionsがJavaをサポートしています。
各Cloud Functions で使える言語比較(2018年7月) - QiitaAmazon Lambdaについては、InputStream、OutputStreamをロジックに注入できます。
個別データストアへのコネクションオブジェクトは、ロジック内で都度作成するしかなさそうです。
なお今月発表された情報で、RDS ProxyというRDSのコネクションプーリングの仕組みがプレビュー段階ですが利用可能となったので、JDBCについてはこの手間は近いうちに解消されるでしょう。
[速報]これでLambdaのコネプー問題も解決?!LambdaからRDS Proxyを利用できるようになりました(まだプレビュー) #reinventやってみた
試しに作るアプリは、ざっと次のような処理を行うものです。
- クライアントはラムダ式をシリアライズし、ラムダ式の引数とともにHTTP-POSTする
- ラムダ式はBase64文字列に変換する
- ラムダ式と引数は一つのJSONに格納する。これをリクエストボディとする
- サーバは受信したJSONをデシリアライズし、ラムダ式を実行、結果をクライアントに返す
ソフトウェア構成
- 実行環境
- AdoptOpenJDK 11.0.4
- クライアントの依存モジュール
- spring-boot-starter-web:2.2.0.RELEASE
- サーバの依存モジュール
- spring-boot-starter-web:2.2.0.RELEASE
- spring-boot-starter-jdbc:2.2.0.RELEASE
- HikariCP:3.4.1
- h2database:1.4.199
準備
シリアライズ可能なFunctionインタフェイス
SerializedFunction
を作ります。SerializedFunction.javapublic interface SerializedFunction<T, R> extends Function<T, R>, Serializable { }SerializedFunctionと、その引数を保持する
RequestPayload
を作ります。RequestPayload.javapublic class RequestPayload { private String base64EncodedFunction; @JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY) private Object parameter; public String getBase64EncodedFunction() { return this.base64EncodedFunction; } public Object getParameter() { return this.parameter; } private RequestPayload() { } @JsonCreator public RequestPayload(final @JsonProperty("base64EncodedFunction") String base64EncodedFunction, @JsonProperty("parameter") final Object parameter) { this.base64EncodedFunction = base64EncodedFunction; this.parameter = parameter; } }リソース注入が不要なラムダを実行
まずは、サーバサイドのリソースを特に必要とせず、ラムダ式と引数だけで実行可能なパターンを実行してみます。
クライアントサイド
クライアントサイドで
sayHelloFunction
をシリアライズして、HTTP-POSTします。LambdaRequestClient.javaSerializedFunction<String, String> sayHelloFunction = name -> "Hello, ".concat(name); var payload = new RequestPayload(makeBase64EncodedLambda(sayHelloFunction), "lambda"); var requestBody = new ObjectMapper().writeValueAsString(payload); var response = restTemplate.postForObject("http://localhost:8080/api/launch", requestBody, String.class); LOG.info("response : {}", response);/** * 指定されたラムダ式をBase64文字列に変換します。 * * @param lambda ラムダ式 * @return Base64文字列 * @throws IOException 入出力例外 */ private String makeBase64EncodedLambda(final Object lambda) throws IOException { return Base64.getEncoder().encodeToString(serialize(lambda)); } /** * オブジェクトをシリアライズします。 * * @param obj オブジェクト * @return シリアライズ後のバイト配列 * @throws IOException 入出力例外 */ private byte[] serialize(final Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); try (baos; oos) { oos.writeObject(obj); } return baos.toByteArray(); }サーバサイド
LambdaController.java@RestController @RequestMapping("/api") public class LambdaController { @Autowired private LambdaLauncher launcher; @PostMapping(path = "/launchlambda") public Object launch(@RequestBody final String jsonBody) { try { return this.launcher.launch(jsonBody); } catch (IOException | ReflectiveOperationException e) { throw new HttpMessageNotReadableException("invalid request body"); } } }LambdaLauncher.javapublic Object launch(final String jsonRequest) throws ReflectiveOperationException, IOException { var request = new ObjectMapper().readValue(jsonRequest, RequestPayload.class); var function = deserialize(request.getFunction()); return function.apply(request.getParameter()); }private SerializedFunction<Object, Object> deserialize(final String base64EncodedFunction) throws IOException, ClassNotFoundException { var binary = Base64.getDecoder().decode(base64EncodedFunction); try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(binary))) { return (SerializedFunction<Object, Object>) ois.readObject(); } }実行
クライアントからリクエストすると、ちゃんとラムダ式の実行結果が返ってきました。
ログ[INFO ] response : Hello, lambda [main]リソース注入するラムダを実行
次は、サーバサイドのリソースを注入するパターンです。
Spring Data JDBCのAutoConfigurationを利用して、H2DBへのDataSourceがあらかじめBeanに定義されるように構成します。クライアントサイド
LambdaRequestClient.javaSerializedFunction<DataSource, String> getDatabaseVersionFunction = (@Autowired final DataSource ds) -> { String result = "unknown"; try { try (var conn = ds.getConnection(); var stmt = conn.prepareStatement("select VALUE FROM INFORMATION_SCHEMA.SETTINGS where name = 'info.VERSION'"); var rs = stmt.executeQuery()) { if (rs.next()) { result = rs.getString("VALUE"); } } } catch (SQLException e) { LOG.error("exception while executing query", e); } return "H2 Database version is ".concat(result); }; var payload = new RequestPayload(makeBase64EncodedLambda(getDatabaseVersionFunction), null); // DataSourceはシリアライズ不可能なので、引数はnullとしている var requestBody = new ObjectMapper().writeValueAsString(payload); var response = restTemplate.postForObject("http://localhost:8080/api/launchlambda", requestBody, String.class); LOG.info("response : {}", response);H2DBにクエリを実行して結果を取得するラムダ式
getDatabaseVersionFunction
とHTTPリクエストです。
SQLの実行結果を反映したレスポンスが返れば十分なので、H2DBには特にテーブルを作成していません。
ラムダ式内でDataSource
を利用するため、引数に定義して@AutoWired
を付けています。
アノテーションは実際何でも構わないのですが、型が合致するBeanがサーバサイドで注入されることを明示したいので@AutoWired
を使いました。
ただ、Springはさすがにラムダ式の引数にはDIしてくれないので、自力でDIするようにゴリゴリ書きます。サーバサイド
長いですが一連の処理を貼ります。
LambdaLauncher.java// ApplicationContextAwareをimplementする public Object launch(final String jsonRequest) throws ReflectiveOperationException, IOException { var request = new ObjectMapper().readValue(jsonRequest, RequestPayload.class); var function = deserialize(request.getBase64EncodedFunction()); var params = getLambdaParams(function); Object actualParameter = request.getParameter(); for (var p : params) { var bean = resolveBean(p); if (bean != null) { actualParameter = bean; break; } } return function.apply(actualParameter); } /** * 指定されたラムダ式をデシリアライズします。 * * @param base64EncodedFunction Base64文字列でエンコードした{@link SerializedFunction} * @return SerializedFunction * @throws IOException 入出力例外 * @throws ClassNotFoundException クラスが見つからない場合 */ @SuppressWarnings("unchecked") private SerializedFunction<Object, Object> deserialize(final String base64EncodedFunction) throws IOException, ClassNotFoundException { var binary = Base64.getDecoder().decode(base64EncodedFunction); try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(binary))) { return (SerializedFunction<Object, Object>) ois.readObject(); } } /** * 指定されたラムダ式のパラメータを取得します。 * * @param function ラムダ式 * @return Parameter[] * @throws ReflectiveOperationException リフレクション例外 */ private Parameter[] getLambdaParams(final SerializedFunction<?, ?> function) throws ReflectiveOperationException { var serializedLambda = makeSerializedLambda(function); Class<?> capturingClass = Class.forName(serializedLambda.getCapturingClass().replace('/', '.')); return Arrays.stream(capturingClass.getDeclaredMethods()) .filter(m -> m.getName().equals(serializedLambda.getImplMethodName())) .filter(m -> !m.isBridge()) // ignore bridge .findFirst() .orElseThrow(() -> new NoSuchMethodException("No such method " + serializedLambda.getImplMethodName())) .getParameters(); } /** * 指定されたラムダ式の{@link SerializedLambda}を返します。 * * @param lambda ラムダ式 * @return SerializedLambda * @throws ReflectiveOperationException リフレクション例外 */ private SerializedLambda makeSerializedLambda(final Object lambda) throws ReflectiveOperationException { var writeReplaceMethod = lambda.getClass().getDeclaredMethod("writeReplace"); writeReplaceMethod.setAccessible(true); return (SerializedLambda) writeReplaceMethod.invoke(lambda); } /** * アノテーション付きパラメータに対応するBeanをApplicationContextから取得します。 * * @param param ラムダ式の引数オブジェクト */ @SuppressWarnings("unchecked") private <T> T resolveBean(final Parameter param) { String beanName = null; boolean autoWired = false; for (var an : param.getDeclaredAnnotations()) { if (an.annotationType() == Qualifier.class) { beanName = ((Qualifier) an).value(); } else if (an.annotationType() == Autowired.class) { autoWired = true; } } if (!autoWired) { return null; } if (beanName == null) { if (param.getType() == ApplicationContext.class) { return (T) this.ctx; } else if (param.getType() != Object.class) { return (T) this.ctx.getBean(param.getType()); } } else { return (T) this.ctx.getBean(beanName, param.getType()); } return null; }実行
ログ[INFO ] response : H2 Database version is 1.4.199 (2019-03-13) [main]期待通りの実行結果が返ってきました。
ところが、、
これまではEclipseで実行してきました。その後AdoptOpenJDK11でビルドしたモジュールで実行してみると、ラムダ内部の
DataSource#getConnection()
を実行する行でNPEが発生しました。なぜ?[ERROR] Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause [http-nio-8080-exec-2] java.lang.NullPointerException: null at ksky8864.LambdaRequestClient.lambda$main$2eb00f57$1(LambdaRequestClient.java:26) at ksky8864.LambdaLauncher.launch(LambdaLauncher.java:42) at ksky8864.LambdaController.launch(LambdaController.java:22) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) # 以下略あちこち原因を調べ、バイトコードの比較までやりました。
javap -l -v -p
でクライアントのclassファイルを逆アセンブルしてみると、
え、、なんか情報消えてるやん。
画像で赤く囲った
RuntimeVisibleParameterAnnotation
は、文字通りパラメータに付けられたアノテーションの情報を示す箇所ですが、AdoptOpenJDKでコンパイルした場合すっぽり無くなってしまいました。
NPEの原因は、ここが欠けて@AutoWired
の付いたパラメータを見つけられず、DataSourceの注入ができなかったのですね。これまであまり意識してこなかったのですが、Eclipseのコンパイラはコンパイルエラーを起こした状態でも、途中まで気を利かせてclassファイルを生成してくれます。
AntでEclipseのECJ(Eclipse Compiler for Java)を使う - knjnameのブログJavaの言語仕様的にアノテーション情報を出すのが正しいか、それともECJの忖度が働いたのか…
さらに調べる
バージョン11のJava言語仕様によれば、アノテーションの保持スコープを決める
@Retention
のところで次のような記述があります。An annotation on the declaration of a local variable, or on the declaration of a formal parameter of a lambda expression, is never retained in the binary representation. In contrast, an annotation on the type of a local variable, or on the type of a formal parameter of a lambda expression, is retained in the binary representation if the annotation type specifies a suitable retention policy.
The Java® Language Specification - Chapter 9. Interfacesラムダ式の仮引数の宣言へのアノテーション(An annotation ... on the declaration of a formal parameter of a lambda expression)は、バイナリ表現(バイトコード)では保持されない(is never retained in the binary representation)とあります。後半の"the type of a formal parameter"は、仮引数の型パラメータに対するアノテーションを指す(でいいのかな?)ので、だめなのか。。
一方で、StackOverflowでこんな記述も見つけました。
(2) In response to the comment from
@assylias
You can also try (@MyTypeAnnotation("Hello ") String s) -> System.out.println(s) although I have not managed to access the annotation value...Yes, this is actually possible according to the Java 8 specification. But it is not currently possible to receive the type annotations of the formal parameters of lambda expressions through the Java reflection API, which is most likely related to this JDK bug: Type Annotations Cleanup.
java - Annotating the functional interface of a Lambda Expression - Stack Overflow仮引数へのアノテーションはJava8の仕様上は可能だが、JDKのバグとの関係で今のところはリフレクションでアノテーション情報を取ることができない、という趣旨ですね。
やっぱりできるのか?どっちなんだい。おわりに
結局のところ、アノテーションスキャンは断念して、リフレクションを駆使してラムダ式のパラメータとBeanの型を照合、パラメータに注入するといったロジックに変更して、この問題を回避しました。
しかし、言語仕様とコンパイラの溝をのぞいてしまったようで、夜も寝られません。なにか情報を持っている方はご一報くださると幸いです。
- 投稿日:2019-12-18T18:08:20+09:00
PythonとJAVAの実行速度を比較する
はじめに
一般にPythonは遅いと言われているが、実際に他言語と比べてどれだけ遅いのかを調べてみた。
測定方法
PythonとJAVAの2言語間で、円周率を近似するプログラムを走らせる。
それぞれの差から実行速度が何倍異なるかを調べた。結論
JAVAのほうが、Pythonより約60倍速いと思われる。
ただし、円周率の近似の結果値をうまく出力できなかったため、本記事だけではJAVAのほうが速いと主張できない。やったこと
近似式
ライプニッツの公式から円周率を近似した。
π = 4{(1-1/3)+(1/5-1/7)+...(1/[4n+1] - 1/[4n+3] ) }で求まるらしい。
実行時間の測定方法
JAVA、Pythonともに、
(計算の実行終了時刻 - 計算の実行開始時刻)
を用いて実行時間を求めた。
Pythonのコード
import time num = 100000000 # 計算の繰り返し回数 pi = 0 # 円周率の近似値 if __name__ == '__main__': start = time.time() pi = 0 for i in range(num): pi += (1 / (i * 4 + 1) - 1 / (i * 4 + 3)) print(pi4) # 3.141592153589902 elapsed_time = time.time() - start print("elapsed_time:% .2f" % elapsed_time + "[sec]")JAVAのコード
public class SpeedTest{ public static void main(String[] args){ long startTime = System.currentTimeMillis(); //以下に計測する処理を記述 int num = 100000000; // 計算の繰り返し回数 float pi = 0; // 円周率の近似値を4で割ったもの float pi4 = 0; // 円周率の近似値 for(int i = 0;i<num; i++){ pi += (1 / (i * 4 + 1) - 1 / (i * 4 + 3)); } pi4 = pi * 4; //以上、計測する処理内容 long endTime = System.currentTimeMillis(); System.out.println(pi4); System.out.println("time:" + (endTime - startTime)+"[msec]"); } }結果
Python、JAVAともに1億回の処理をそれぞれ2回行ったところ、
Pythonでは33.88 sec, 31.55 sec
平均32.72 secが得られた。JAVAでは528 msec, 568 msec
平均548 msecが得られた。
--> 平均0.548 sec以上のことから、PythonとJAVAでは59.71倍の実行速度差が存在する。
一方で、円周率の近似値の出力では、
Pythonでは3.1415926445762157が得られ、
JAVAでは4.0が得られた。π = 3.1415926535...
であるため、
Pythonでは8桁の精度で求められたのに対して、
JAVAでは精度としては1桁も合っていない。これはfloat型などでは計算結果が勝手に丸まってしまうことに由来するようだ。
JAVAで計算が正しく行われたかどうかが明らかでないため、この結果からJAVAのほうが速い!と言い張るのは難しい。環境
Python... Python3.6.8, PyCharm 2019.1.2
JAVA... JAVA10.0.2, Eclips Photon Release (4.8.0)参考URL
【Python】処理にかかる時間を計測して表示
https://qiita.com/fantm21/items/3dc7fbf4e935311488bc【Java入門】処理時間をナノ秒・マイクロ秒で計測する方法
https://www.sejuku.net/blog/44904
- 投稿日:2019-12-18T16:15:13+09:00
凝集度・結合度・循環的複雑度
概要
メンテナンスしやすい/テストしやすいコードを書くための知識として以下を紹介してます。
- 凝集度
- 結合度
- 循環的複雑度
ちなみに、言語はJavaで書いてます(他の言語は読み替えて下さい)
あと結合度の部分はもしかしたら間違えた理解してるかも。その場合ご指摘ください。
(以外と本やサイトによって書かれてることがバラバラなので…(´・ω・`))それぞれの説明
凝集度
モジュール強度とも言われる。あるコードがどれだけそのクラスの責任分担に集中しているかを示す尺度。
ひとつの役割のためだけに存在(全てのメソッド群がその役割のためだけに存在)しているクラスは凝集度が高い。
逆に、各メソッドに共通性がなく、様々な役割を担っているクラスは凝集度が低い。凝集度が低いと何が悪いか
- そのクラスが何をするものなのか理解するのが難しくなる
- 保守やテストが難しくなる
- 再利用しづらくなる
凝集度が低いクラスはメンバ変数を多く持つ傾向がある
凝集度が低いと、メンバ変数が多くなる傾向がある。メンバ変数はクラスの状態そのものであるため、メンバ変数が多いとそのインスタンスが取りうる状態のパターンが増え、複雑度が増して保守性が著しく低下する。またテストもしづらくなり、パターンも増える。
凝集度の高いクラスの例: Stringクラス
「文字列を扱う」というためだけにこのクラスは存在しており、他の役割を担っていない。
Stringクラス具体的な例
Price(価格)というクラスを考えてみる。そのクラスは、価格のデータを扱うメソッドのみを持つ。
public class Price { private final int value; public Price(int value) { this.value = value; } public Price() { this(0); } /** 価格を足す */ public Price add(Price other) { return new Price(this.value + other.value); } /** 価格をかける */ public Price multiply(int amount) { return new Price(this.value * amount); } /** 割引する */ public Price discount(double percentage) { return new Price(this.value * percentage); } /** 価格をフォーマットして返却 */ public String format() { return NumberFormat.getCurrencyInstance().format(this.value); } public void print() { System.out.println(this.format()); } } // 使う側 Price price = new Price(1000); price.print(); // ¥1,000 System.out.println(price.format()); // ¥1,000 System.out.println(price.add(new Price(500)).format()); // ¥1,500 System.out.println(price.discount(0.9).format()); // ¥900 // 補足: Priceはイミュータブルになっており、最初設定した値が変更されないため // 最後のdiscountを呼び出した際にも value = 1000 で計算が行われる凝集度を高くするには
- 単一責務の原則を意識
- クラス内に関係のないメソッドを作らない
- メンバ変数を参照していないメソッドは外だしする
- privateメソッドは外だしできないか検討する
- メンバ変数は極力少なくする
- クラスやメソッド内のステップ数が大きくなってる場合は分割を検討する
結合度
簡単に言うと「使う側と使われる側のクラスやメソッドが、どれだけ内容を知っているか(どれだけ依存しているか)」。
結合度が高いほど、保守性が落ちテストも非常にしづらくなる。
結合度の種類
以下の順に結合度は高くなる。
メッセージ結合
引数のないメソッドの呼び出ししかしておらず、返り値もない。理想の形。
public class Main { private final Price price; public Main(Price price) { this.price = price; } public void output() { this.price.print(); } } Price price = new Price(1000); Main main = new Main(price); main.output(); // ¥1,000 と表示される上記は、
Main
クラスとPrice
クラスがデータを共有しておらず、print()
の実装内容にMain#output()
が依存していない。
Price
クラスはMain
クラスのことは全く知らないし、Main
クラスもPrice#print()
が中で何をしているのか気にしていない。たとえばprint()
メソッドの実装を変更してファイルに文字列を出力するようにしたとしても、実際には何もしなかったとしても、Main#output
は関知しない。このような結合は メッセージパッシングとも呼ばれ、このようなオブジェクト間のやりとりを メッセージング という。相手オブジェクト側に作業を完全に委任しており、ちょうど誰かに「これやっといて」とだけ伝えるようなやりとりになるため、メッセージングという名前がついた(んだと思う)。
※補足:上記の場合は報告も不要な感じの実装になってるけど「作業完了したら教えて」というようなコールバックの実装もメッセージ結合を維持したまますることができる。
データ結合
単純なデータの受け渡しだけを行う。
// データ結合の例 public class NameFormatter { public String format(String firstName , String familyName) { return new StringBuilder().add(familyName).add(" ").add(firstName).toString(); } } public class Main { private final String firstName; private final String familyName; public Main(String firstName, String familyName) { this.firstName = firstName; this.familyName = familyName; } public void printName() { NameFormatter formatter = new NameFormatter(); System.out.println(formatter.format(this.firstName, this.familyName)); } } Main main = new Main("一貴", "小田"); main.printName(); // 小田 一貴上記の
Main#printName()
内のNameFormatter
との結合はデータ結合となる。
データ結合では 「必要な情報」 のみ共有 する。なお、ここでいう「情報」とは以下が該当する。
- メソッド(型が該当する)
- 実際のデータ(値が該当する)
データ結合は、同じ入力に対して常に同じ出力になる。また、
NameFormatter
へは必要な情報しか渡していないため、printName()
ではNameFormatter#format
から最終的に返却される値を使うだけでよく、内部でどのような実装がされているのかは関知しなくていい。一般的に、データ結合は引数として渡すデータをイミュータブルまたはプリミティブ型 あるいはインタフェースを渡す場合の結合を指す(と思う)。ただ、参照型のオブジェクトを渡すケースでもデータ結合に該当する場合もある(呼び出し先のメソッドが必要な情報しか公開されていない、等)。
スタンプ結合
データの構造体(つまり、複数のメンバ変数を持つインスタンス)を引数に渡す。
public class Item { // コンストラクタとgetterとsetterはそれぞれあるという体で private String name; private String group; } public class CashRegister { public String format(Item item, Price price) { return item.getName() + ":" + price.format(); } } CashRegister regi = new CashRegister(); System.out.println(regi.format(new Item("スマブラ", "ゲーム"), new Price(6800))); //スマブラ:¥6,800スタンプ結合の場合、その構造体の中で、呼び出し先が使わないデータが参照できる(つまり、呼び出し先は不要な情報も知っている)。上記の場合は
group
が使われていないが、やろうと思えば、CashRegister#format
内でいずれも参照することもできる。単に参照だけならそれほど問題にはならないが、場合によっては呼び出し先のメソッド内でデータの値を書き換えられる可能性もある。
なのでスタンプ結合の場合に呼び出し元が正しく使おうと思った時、呼び出し先のメソッド内でどのデータを扱っているのか、ある程度知っている(関知している)必要がでるケースがある。上記は、CashRegisterを以下のようにすれば呼び出し元とはデータ結合になる。
ただし、呼び出し元とItem、また呼び出し元とPriceはスタンプ結合になっているpublic class CashRegister { public String format(String item, int price) { return item + ":" + price; } } CashRegister regi = new CashRegister(); Item item = new Item("スマブラ", "ゲーム"); Price price = new Price(6800); System.out.println(regi.format(item.getName(), price.format())); //スマブラ:¥6,800このように、クラスを利用するかぎりスタンプ結合は簡単にはなくせない。いずれもインタフェースを使うことで、データ結合またはメッセージ結合にまで結合度を下げることはできるけど、長くなるから割愛。
※個人的には、中規模のプロジェクトの場合スタンプ結合までは普通に許容するという方がオブジェクト指向原理主義に走るより現実的にいいと思う。
制御結合
与えられた引数の内容によって戻り値が変わるようなケース。
public class Item { // コンストラクタとgetterとsetterはそれぞれあるという体で private String name; private Price unitPrice; } public class SaleService { public Price fixPrice(Item item, int amount, boolean discount) { Price newPrice = item.getUnitPrice().multiply(amount); if (discount == false) { return newPrice; } return newPrice.discount(0.90); } } Item item = new Item("スマブラ", new Price(6800)); SaleService service = new SaleService(); System.out.prinln(service.fixPrice(item, 2, false)); // ¥13,600 System.out.prinln(service.fixPrice(item, 2, true)); // ¥12,240この制御結合は、とりうるパターンによって同じような値を引数に入れてもパターンの数だけ返却値が異なる。つまりはとりうるパターンが引数に依存する。
この制御結合は、ポリモーフィズムを用いることでスタンプ結合以下にまで結合度を下げることができる。
(ただ、個人的には制御結合も3〜4パターン程度なら許容してもいいと思ってる)外部結合
外部結合は、1つのグローバルなリソースを複数のモジュールが参照している状態を指す。
ここから先は、この結合を気軽に許容するとテストが非常にしづらくなる。なお、グローバルなリソースとはたとえば以下のものを指す。
- グローバルなstatic変数
- Singletonに保持された編集可能なデータ
- ファイル
- データベースのリソース
- 別サーバーからの返却値(Web APIなど)
これらは、他のインスタンスなどで変更した値が、他の参照するクラス全体に影響させる。
またそれらリソースの構造が変更されると、呼び出している全てのクラスを修正する必要がでてくる。テスタブルなコードを書くには、この外部結合以降の結合をしているコードをいかに少なくするかが重要となる(と思ってる)。
共通結合
グローバルなリソースの複数のデータをいろんなクラスが参照しあっている状態。
外部結合よりよりカオスなことになる。内容結合
他のクラスの公開していないメソッドや変数名まで知っており、直接参照 / 呼び出ししてるような結合。内容結合では、依存してるクラスの内部実装(公開されていない実装)が変わると直に影響を受けるため、依存先の変数名を変えただけでも影響を受ける。
※ リフレクションを利用した場合にはそのままクラッシュにつながる危険性がある。結合度を低くするには
以下を心がけることで、結合度は低く抑えることができる。
- データを極力直接参照しない
- setter / getter は極力使わない(特にsetter)
- インタフェースの作成を心がける
- 特に外部との連携をするクラスは外だし&インタフェース化する
- 依存性逆転の原則
- Dependency Injection を利用する
- Guiceなど、色々なDIライブラリがある
- ファクトリメソッドと組み合わせるとより効果的
- デメテルの法則を意識
循環的複雑度(サイクロマティック複雑度)
簡単にいうと、「if文/for文/while文/switch文/try文/catch文」などの数。それらが何もないと1で、分岐などの数があるほど1ずつ増していく。
例えば以下のメソッドの循環的複雑度は4となる。public void hogehoge(int test) { if (test > 5 ) { // doSomething1 } else if (test < 1) { // doSomething2 } for (int i = 0; i < 5; i++) { // doSomething3 } }循環的参照度が高いと
当然ながら、テストのパターンが増える。
また、可読性も落ち、メンテナンスがしづらくなる。循環的参照度を下げる方法
- メソッドを分割する
- ネストを浅くする(早期リターンを心がける)
- DRY原則を守る
- ポリモーフィズムで処理を分ける
- StreamAPIなどを活用する
まとめ
- 凝集度は高い方が良い
- 結合度は低い方が良い
- 循環的複雑度は低い方が良い
- 投稿日:2019-12-18T16:09:30+09:00
【2019年12月版】Liferay7.2のGraphQLにOAuth2トークン付きでアクセスしてみた結果
Liferay 7.2 で GraphQL の API が公開されたので叩いてみる
Liferay 7.21 から GraphQLが公開された模様です。
GraphQLのお手軽クライアントとしては Chrome プラグインの Altair が有名のようなのでこれを使ってAPI叩いてみました。
このままでは Mutation系のAPIはもちろん、query系のAPIの一部も権限がないと怒られてしまいます。ログインしてない=認可されてないので当然です!
そこでLiferayでのOAuthを使ってみたいのですが、Altair にはPostmanのように簡単にOAuth2.0トークンを取ってこれる機能がありません・・・ここは Postman を召喚いたします。"ポストマーン!!"
さて、ここから Postman での OAuth 2.0 トークンの取得方法をご紹介いたします。
1. Liferay の設定
まず、KeyCloakの場合と同様に、ポータル側に"クライアント"の登録をいたします。
以下のマニュアルを参考にしつつ Postman クライアントを登録してみましょう。Liferayの管理者でログインした後、コンパネから"OAuth2 管理"ページを開きます。
"OAuth2 管理"ページ右上の"+"ボタンをクリックしてクライアントを新規追加します。
アプリケーション名は適当でOKです。コールバックURIはとりあえずLiferayのトップページとしておきます。
"クライアントのプロフィール"はWebアプリケーション
としてください。そのまま"保存"ボタンクリックでOKです。登録完了後のページから"クライアントID"をコピペしておきましょう。
同様に、クライアントシークレット欄の"編集"ボタンをクリックしてダイアログを表示し、クライアント・シークレットをコピペしておきましょう。"キャンセル"ボタンをクリックしてダイアログを閉じます。続いて許可する権限の設定を行います。上部の"範囲"タブを開きます。
許可するパーミッションをそれぞれクリックして選択し、"保存"をクリックいたします。
ここで重要なのが、ここで設定したパーミッションの
Liferay.Headless.Admin.Workflow.everything
などのキーワードが後の Postman でトークンをリクエストする際のscopes
に当たるという点と、ココで設定したものと完全一致でないと怒られてしまう という点です。
ここでチェックを付けたスコープのキーワードは逐一、メモっておきましょう。2. Postman でのトークン取得
以下の Liferay のマニュアルを参考に、上記のクライアント設定とアクセスポイントを Postman に設定します。
ちょっとわかりにくいですが、Postman での OAuthアクセストークン新規取得ダイアログのスクショです。
- Token Name → これは適当でOKです。
- Grant Type →
Authoization Code
を選択- Callback URL → Liferayで入力した"Callback URI"のアドレスを入力
- Auth URL →
http://<Liferayのホスト>/o/oauth2/authorize
のアドレスを入力- Access Token URL →
http://<Liferayのホスト>/o/oauth2/token
のアドレスを入力- Client ID → Liferay で登録した"クライアント ID" を入力
- Client Secret → Liferay で登録した"クライアント・シークレット"を入力
- Scope → Liferay の "範囲"タブで設定した
権限のキーワード
を空白区切りですべて列挙以上で、"Request Token" をクリックしてください!
ログイン画面が表示されると思います。ここでLiferayに存在するユーザーでログインをお願いします。
ログインが成功すると、Liferayでの権限確認の画面が表示されます。FacebookやTwitterのOAuthログインでも同様ですね。("ステージング機能"が有効になっているので余計な表示が出ていますが気にしないでください。。。)
ここで"許可する"をクリックすると・・・
はい、Postman のトークン管理ダイアログに追加されました!
3. GraphQL の問い合わせ
それでは早速、GraphQL の API にアクセスしてみましょう。GraphQL の入力は Altairの方が楽ちん なので、Altair の方でクエリを作成します。
ちょっと見にくいですがブログ記事を1件取得するQueryを書いてみました。Altairでアクセスすると
Access denied
となっています。HTTP ステータス200
でAccess denied
の例外が発生しているのは気になるところですね。。。さて、このクエリを Postman から実行してみます。
はい、バッチリ・・・あ、ん~?!?
Altairと同じで、HTTP ステータス200
でAccess denied
の例外が発生してしまいました。
OAuthのトークンが効いてないのか・・・?というか Altair でも HTTP 200 なのでログインなしでもこのAPIは呼べている、ということになりますね。。。
その後の処理にトークンで取得した権限が渡ってないような気がします。というか・・・どういうこと?
Liferay 7.2 での GraphQL API は奥が深い、ということでした。。。
(いや、ダメだろう。。。)2019/12/19 追記:ちゃんと?できました。
Liferay の設定で "サービスアクセスポリシー" の設定を行うとGraphQLでのアクセスが出来ました。以下に手順を追記します。
コントロールパネルの"サービス・アクセス・ポリシー"を開きます。
代理ログイン
の行のメニューから"編集"を選択します。
"代理ログイン" の "デフォルト"を"いいえ"→"はい"に変更します。"保存" をクリックし、再度、Postman からトークンを取得してGraphQLを投げてみます。
"テストです。"が列挙されたブログ記事が取得できました!
今回は"代理ログイン"という強力な権限を使ってますが本番サイトではどこまで権限つければよいのだろうか・・・?試してみたいと思います!
どちらにしても HTTP ステータス 200 はアカンやろ・・・
余談: Postman とか Altair のスクショについて。(Windows 10)
記事に載せているスクショは大抵、Webの画面なので Chrome のスクショプラグイン "Awesome Screenshot" だけでOKなのですが、Postman は単独のアプリ、Altair は Chrome のプラグイン、ということで、"Awesome Screenshot" じゃスクショ撮れないのです。
Windows アプリのスクショはこれまで
Alt + PrtScr
(ほぼAlt + Fn + PrtScr
の3つ)で最前面のアプリをスクショ撮ってペイントアプリなどに貼り付けて名前を付けてPNGで保存などしてましたが、これがとんでもなくめんどくさいので一発でファイル保存までしてくれるショートカットキーを調べてみました。デスクトップ全体のスクショは、
Windows + PrintScreen
(ほぼWin + Fn + PrtScr
)キーアクティブなアプリのスクショは、
Windows + Alt + PrintScreen
(ほぼWin + Alt + Fn + PrtScr
)キーで、
C:\Users\ユーザー名\Videos\Captures
、エクスプローラーでいうところのビデオ>キャプチャ
フォルダに直接、スクショが PNG形式で保存されます!で、PNG形式の画像であれば、"Awesome Screenshot" で加工ができますので、スクショ撮って切り抜いて、のような作業がとても楽ちんになりました。
モジュールやソースの入手は SourceForge の Liferay Portal もしくは Github の Liferay Portal が手っ取り早いです。 ↩
- 投稿日:2019-12-18T15:49:51+09:00
Javaユーザーがよく見るイベントサイトは? connpassとDoorkeeperで収集したデータをRevealで可視化してみた。
はじめに
JavaユーザはーTECH系イベントに参加するとき、どんなイベントサイトを参照しているのか?
有名なTECHイベントサイトの"connpass"と"Doorkeeper"のJavaコミュニティの数値取得し、Revealでダッシュボードを作ってみました。
(2019年12月の情報に基づいた記事です)対象サイトを選定
有名なIT系イベントサイトをリストアップ
まずは、私も利用経験のあるイベントサイトをリストアップ。
- connpass
- Doorkeeper
- ATND
- TECH PLAY
最終的に2つ(connpass と Doorkeeper)に絞り込み
それぞれのサイトでJavaの情報を収集し、
そこそこのボリュームが得られたconnpassとDoorkeeperを対象にデータを収集することにしました。
connpass
カテゴリ「Java」から結果を取得。9コミュニティがヒットしたのでこれらを分析対象に決定。
https://connpass.com/category/Java/Doorkeeper
トピック「Java」から結果を取得。49コミュニティもヒットしたのでこれらを分析対象に決定。
https://www.doorkeeper.jp/topics/javaATND
https://atnd.org/events/search?q%5Battendee_gteq%5D=1&q%5Btitle_or_description_or_place_or_address_cont%5D=Java
-> 2019/12/18時点でイベント数が3件という結果だったので今回は除外。TECH PLAY
-> TECH PLAYの、横断検索を行う性質から、connpassとDoorkeeperも対象になるため、今回は除外しました。データの収集対象
connpass
connpass はカテゴリ「Java」のグループに所属しているメンバー数の合計を取得。
Doorkeeper
Doorkeeper はトピック「Java」のコミュニティ所属しているメンバー数の合計を取得(※)
※ Doorkeeperはトピックのフォロワー数を知ることも出来ますが、connpassとなるべく近い集計手順を実施するために、コミュニティに所属しているメンバー数の合計としています。
収集結果
泥臭く収集した結果は、下記のExcelにまとめています。(疲れた...)
https://github.com/furugen/Reveal-Samples/raw/master/1218-RevealApp/CommunityData-Doorkeeper-connpass.xlsxシンプルに3カラムを設けました。
Reveal でダッシュボード作成
出来たもの
ダッシュボードの作り方
別途動画とって公開します!(近日公開予定)
まとめ
Javaユーザーがテックイベントを探す場合は、Doorkeeperは必須!
Doorkeeperが圧倒的にJavaユーザ多し! その差、約8倍以上でした。
メンバー数上位コミュニティで1位の日本Javaユーザーグループ、2位の日本Springユーザ会がJavaコミュニティを引っ張っている結果でしょう。
※余談ですが昔所属していたJava Kueche(沖縄)が上位コミュニティにランクインしているのが嬉しい。最近テックイベントはconnpassで探すことが多かったのすが、久々にJavaイベントを探そうとした際に、「あれ?Javaのイベントが少ない…?」と感じたのが調査のきっかけでした。
イベントサイトによっては、それぞれ特色があるので利用する方も気を付けないといけませんね。本記事はJavaでしたが、.NET系(たぶんconnpassが強いはず)や、他の言語も機会があれば可視化していこうと思います。
次回もまたRevealの記事を執筆予定ですので、興味があればまたご覧ください!
- 投稿日:2019-12-18T12:51:35+09:00
Find the Smallest Common Number
Find the Smallest Common Number
昇順でソートされた3つの整数配列から、3つの配列すべてに共通する最小の数を見つける必要があります。
説明
昇順で並べ替えられた3つの整数配列が与えられ、3つの配列すべてに共通する最小の数を見つけるアルゴリズムを実装する。
以下の3つの配列を見てみましょう。解は6 で、すべての配列に共通する最小数です。
ヒント
- 昇順配列を活用する。
- 3つのポインターを使用する。
Solution
Runtime Complexity O(n)
Memory Complexity O(1)
解説
配列が昇順で並べ替えられるという事実を利用する方法を考えてみましょう。
3つの反復子(イテレータ)を同時に使用して、各配列を走査します。
各配列の最小値を持つ0番目のインデックスから配列を走査することから始められます。3つのイテレータが指す配列インデックスの値が等しい場合、
すべての配列に存在する最小値(配列が昇順で並べ替えられているため)になるため、その値を返す。それ以外の場合は、3つのポイントのうち最小の値を指すイテレータを確認し
次のインデックスを指すようにそのイテレータをインクリメントしていきます。3つのイテレータのいずれかが配列の最後に到達したときに共通の番号が見つからなかった場合、Nullを返します。
実装
Test
Output
- 投稿日:2019-12-18T09:14:24+09:00
アルゴリズム Search Rotated Array
Search Rotated Array
任意の数だけ右へ回転されたソート済み配列と、指定された数(key)が渡されて検索します。
説明
任意の数だけ回転されたソート済み配列で、指定された数(key)を検索します。 Keyが存在しない場合は-1を返します。 配列に重複が含まれていないと仮定します。
以下は、回転前の元の配列です。
この配列で6回、回転を実行すると、次のように変わります。
条件
- 線形検索O(n)は受け入れられない解決策です。
- 修正したBinary Searchを考える。
Solution
Runtime Complexity O(logn)
Binary Search を利用している。
Memory Complexity O(logn).
一回のループごとに、渡されたArrayを半分に切って、片方のみに検索をかけている。
解説
解決策は、基本的にBinary Search ですが、いくつかの修正が加えられています。 例の配列をよく見ると、配列の少なくとも半分が常にソートされていることがわかります。 この特徴を利用します。 探している数値nが配列のソートされた半分の中にある場合は、Binary Search で見つけることができます。 それ以外の場合は、ソートされた半分を破棄し、ソートされていない半分を調べ続けます。 各ステップで配列を半分に分割しているため、これによりRuntime Complexity はO(logn)となります。
コードにおいて、少し読みにくい条件分けの部分があります。四つの条件で
1. 半分に切ったstart ~ mid の範囲のSubArrayがソートされている、かつ、探しているKeyがその範囲に存在するとき。
2. 半分に切ったmid ~ end の範囲のSubArrayがソートされている、かつ、探しているKeyがその範囲に存在するとき。
3. 半分に切ったstart ~ mid の範囲のSubArrayがソートされてなく、かつ、探しているKeyがその範囲に存在するとき。
4. 半分に切ったmid ~ end の範囲のSubArrayがソートされてなく、かつ、探しているKeyがその範囲に存在するとき。
と言う様に条件分けしています。実装
テスト
アウトプット
- 投稿日:2019-12-18T06:33:13+09:00
インポート宣言/パッケージ宣言
個人的な勉強のためにまとめました
インポート宣言/パッケージ宣言
パッケージの目的
パッケージの目的は3つ
①名前空間を提供し、名前の衝突を避ける
②アクセス修飾子と組み合わせてアクセス制御機能を提供する
③クラス分類を提供する①は、もし、クラス名が重複してしまうとコンパイラやJVMは、どちらのクラスを利用すればよいか判断できないので、このような事態を防ぐために、コンパイラやJVMは、クラスを「パッケージ名.クラス名」の完全修飾クラス名で扱うから
※慣習としてドメイン名を逆にしたものがパッケージ名に使われる②は、クラスを複数のパッケージに分けると、パッケージ単位でアクセス制御ができるようになる
例)publicなSampleクラスの宣言
Sample.javapackage jp.co.xxx; public calss Sample{ //any code }例)Testクラスの宣言
Test.javapackage jp.co.xxx; calss Test{ //any code }公開・非公開に分けることで、設計者が意図しないクラスが不用意に使われることを防ぐ
③は、パッケージは、ディレクトリ構造とマッピングされるので、多数のクラスを分類整理することができ、ソフトウェアの管理が容易になる
パッケージのインポート
・インポート宣言することで、パッケージ名を省略してクラス名だけで記述することができる
・java.langパッケージは基本的なクラスがまとめられたパッケージであり、頻繁に利用するためインポート宣言を省略することができる
・アスタリスク「*」を使ってそのパッケージに属するクラスすべて利用することがインポートできる例)java.utilパッケージに属する全クラスのインポート宣言
import java.util.*;
・ただし、指定したパッケージに属するクラスに限定されるので、サブパッケージに属するクラスがインポートされることはない
staticインポート
・本来、staticなフィールドやメソッドは、クラス名.フィールド名やクラス名.メソッド名の書式で、どのクラスに定義されているものか明示しなければいけない
これをフィールド名やメソッド名だけで省略表記できるようにするための宣言がstaticインポート
・staticインポートは、import staticに続けて、省略表記したいフィールドやメソッドの完全修飾クラス名を記述する例)staticインポート宣言
import static jp.co.xxx.Sample.num
←Sampleクラスのstaticなフィールドをインポート
import static jp.co.xxx.Sample.print
←Sampleクラスのstaticなメソッドをインポート・staticメソッドをインポート宣言するときは、メソッドだけを記述し、カッコ「()」や引数などは記述しない
・オーバーロードされた同名のメソッドが複数あった場合はオーバーロードのルールが適用されるので、メソッドの数だけインポート宣言したり、引数を指定したりする必要はない
・インポートしたクラスに、インポートされたメソッドやフィールドと同名のものがあった場合、そのインポートは無視される
- 投稿日:2019-12-18T01:28:35+09:00
JavaでExcelデータの取り込み2
前回の続きで今回もApachePOIの内容です。
※前回
https://qiita.com/Mk-4000/items/908266355307bcaf6993セルの値の取得について
前回のセルの値の取得部分について別メソッドで記載していた部分についてです。
セルの値は単純にそのまま持ってこれるようではないようです。
前回のメソッドをそのまま抜粋。SamplePOI.javaprivate static String getCellStringValue(Cell cell) { String retStr; CellType cellType = cell.getCellType(); switch (cellType) { case STRING: retStr = cell.getStringCellValue(); break; case NUMERIC: retStr = String.valueOf(cell.getNumericCellValue()); break; case BOOLEAN: retStr = String.valueOf(cell.getBooleanCellValue()); break; case FORMULA: retStr = String.valueOf(cell.getCellFormula()); break; case ERROR: retStr = String.valueOf(cell.getErrorCellValue()); break; default: retStr = ""; break; } return retStr; } }ここでは初めに取得対象のセルのタイプ(Javaで言う変数の型?状態?)を判断しています。
タイプによって取得に使うメソッドが変わってくるようです。セルタイプについて
getCellTypeメソッドでセルのタイプを取得できる。
具体的には以下の7つがある。
・CellType._NONE
不明セル。よくわからないけどめったに出てこないセルかと。・CellType.BLANK
空白セル。・CellType.BOOLEAN
ブーリアン。TRUEかFALSEや真偽を問うセル(「=A1=A2」のような)が対象。・CellType.ERROR
エラー。多分計算ミスとは数式でエラーが出ているセル。
※「#N/A」や「#DIV/0!」などのセル・CellType.FORMULA
数式。「=A1+B1」みたいに計算されて表示上は計算後の値で表示されるセル。
getCellFormulaメソッドで取得すると数式そのものが取得できるため、String変換するとそのまま「=A1+B1」が取得できます。・CellType.NUMERIC
数値。数字が入っていたり、日付が入っているセル。・CellType.STRING
文字列。対象セルの中身がテキストとなっているセル。
Excel上で数字を文字列として扱っていた場合は数字もこっちに来る。(はず)確認
SamplePOI2.javapublic class SamplePOI2 { /* * 本処理 */ public static void main(String[] args) { //ここに取り込みたいExcelファイルのフルパスを入れる String ExcelPath = ""; //Excel用オブジェクト Workbook wb; Sheet sh; Row row; Cell cell; //取得データを保持するリスト List<String> columnA_List = new ArrayList<>(); try (InputStream is = new FileInputStream(ExcelPath)) { //対象のExcelファイルをJavaに取り込み wb = WorkbookFactory.create(is); //対象ファイルの1枚目のシートを指定 sh = wb.getSheetAt(0); //シート内の最大行を取得 int rowMaxA = sh.getLastRowNum(); //最大行分のループを回してA列のセルをString型として取得 for (int i = 0; i <= rowMaxA; i++) { row = sh.getRow(i); if (row == null) { continue; } cell = row.getCell(0); String cellValue = getCellTypes(cell); columnA_List.add(cellValue); } //コンソールに出力 for (String outStr : columnA_List) { System.out.println(outStr); } } catch (Exception e) { e.printStackTrace(); } } /* * セルの状態を判別してString型で返す。 */ private static String checkCellType(Cell cell) { String retStr=""; CellType cellType = cell.getCellType(); switch (cellType) { case _NONE: retStr ="_NONE"; break; case BLANK: retStr ="BLANK"; break; case BOOLEAN: retStr ="BOOLEAN"; break; case ERROR: retStr ="ERROR"; break; case FORMULA: retStr ="FORMULA"; break; case NUMERIC: retStr ="NUMERIC"; break; case STRING: retStr ="STRING"; break; } return retStr; }cellType.resultBLANK BOOLEAN ERROR FORMULA NUMERIC STRINGとなりました。
取得について
セルタイプがわかったらセルタイプごとの値の取得について。
STRING
getStringCellValue()
もしくは
cell.getRichStringCellValue().getString()
で取得。違いはよくわからない。NUMERIC
int型にしたいなら
getNumericCellValue()
日にちなどDate型で取得したいなら
getDateCellValue()FORMULA
getCellFormula()
式がString型で取得できるERROR
getErrorCellValue()
エラーコードとして取得する。(byte)BOOLEAN
boolean型でそのまま使えます。
getBooleanCellValue()こんな感じです。
- 投稿日:2019-12-18T01:11:39+09:00
java インストール編
今日はjavaの開発キットである
JDKのインストールについて書きます。JDKとは
Java Development Kitの略です。
javaはOracleによって提供されています。
よりビジネスに使われた方がoracle社としても都合がいいので
他の言語にとって変わられないよう
リリースから随分時がたった今でも頻繁に更新がされています。JDKのインストール確認
前提としてJDKのインストールがされてるか
verはどうなっているか確認します。ターミナルjava -version
JDKのインストール
こちらのリンクよりJDKの提供元であるOracle社のHPに行きます。
そして画像中央のjavaのアイコンをクリックします。
リンク先の下部にダウンロード一覧があります。
ライセンス契約に同意するにクリックして
自分のPCにあったファイルをダウンロードします。
※わかりやすくするため日本語変換しています。あとはダウンロードしたのを開いて、流れにそってインストールすれば完了です。
確認で前述したJDKのインストール確認を行って問題確認できれば完了です。