- 投稿日:2020-07-02T21:47:03+09:00
「実装」javaのProcessクラス注意事項
バージョン
java 8
API
先ずJava API DocsからProcessクラスを一緒に見ましょう
注意事項
- API Docs中で下記部分はご注意ください、BUGを発生する可能性があります。
ネイティブなプラットフォームには標準入出力ストリームに使うバッファのサイズが限られるものもあるので、サブプロセスの入力ストリームの書き込みあるいはストリーム出力の読込みが失敗した場合、サブプロセスはブロックされるか、デッドロック状態になる可能性があります。
bad.javaProcess p = null; p = Runtime.getRuntime().exec("cmd /c dir"); p.waitFor(); // 標準出力ストリームに出力量が多い場合は、プロセスはブロックされる
- API Docs中で下記部分を考えると、Processオブジェクトを使う後で、destroy()メソッドを呼出したほうがいいです。
Processオブジェクトへの参照がなくなった場合でも、サブプロセスは終了されず、非同期的に実行を続けます。
bad.javaProcess p = null; p = Runtime.getRuntime().exec("cmd /c dir"); // 終了しませんサンプルコード
Test.javaimport java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.SequenceInputStream; public class Test { public static void main(String[] args) throws IOException, InterruptedException { Process p = null; p = Runtime.getRuntime().exec("cmd /c dir"); // SequenceInputStreamは、ほかの入力ストリームを論理的に連結したものを表します。 SequenceInputStream sis = new SequenceInputStream(p.getInputStream(), p.getErrorStream()); BufferedReader br = new BufferedReader(new InputStreamReader(sis)); // サブプロセスの出力ストリームを文字列に作成 String line = null; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line + "\n"); } br.close(); p.waitFor(); p.destroy(); // データを出力 System.out.print(sb); } }
- 投稿日:2020-07-02T21:35:50+09:00
[Android]自動スクロールで手を動かすことなく実機確認を行う
一覧など、無限スクロールが存在する機能を使用するときに、実機にかかる負担をAndroidStudioのMemory Profilerで監視をし
重箱の隅をつつく品質をチェックしているのだが、手で動かすのがだるい。なので、無限スクロール自動化をしてみた。
SampleCode
簡単にRecyclerViewのSampleコードを実装したので、こちらを使用する。
https://github.com/yamachita0109/RecyclerView実際に自動scrollしてみた
下記コマンドをコマンドラインに貼り付ければOK。
もっと細かい値を指定したい場合は、公式ドキュメントを参考にすること。$ while [ true ]; do adb shell input swipe 521 1540 492 432 done;デモ
まとめ
・RecyclerViewをスクラッチで実装したのは初めて、もっと美しく実装して、実サービスに耐えうる形にしたいなぁ。
・adbコマンドでなんでもことが発覚。
実機操作を記憶して、スクリプト化することもできるそうな。
ちょっと試してみようかな。・他にもっと便利なadbコマンドの使い方がある方は是非教えてください!!
- 投稿日:2020-07-02T21:08:30+09:00
6行でJavaをセグフォらせる
初投稿です。お見苦しいところもあると思いますが、ご了承ください。
pythonから各言語をセグフォらせるのが流行ってきたので...
pythonを三行でセグフォらせる
pythonを2行でセグフォらせる
C言語で16文字でセグフォらせる
Pythonを33文字でセグフォらせる
Rustを5行でセグフォらせる
C 言語で 5 文字でセグフォらせる実際のコード
無理に6行にした結果
a.javaimport java.lang.reflect.*; import sun.misc.Unsafe; class A {public static void main(String[] a) throws Exception {Constructor<Unsafe> b=Unsafe.class.getDeclaredConstructor(); b.setAccessible(true); b.newInstance().putLong(0, 0);} }無理やり過ぎで可読性が皆無です
コードを整理するとa.javaimport java.lang.reflect.*; import sun.misc.Unsafe; class A { public static void main(String[] a) throws Exception{ Constructor<Unsafe> b=Unsafe.class.getDeclaredConstructor(); b.setAccessible(true); b.newInstance().putLong(0, 0); } }あまり変わりませんね...
実行結果
Linux(Ubuntu)の場合
Ubuntu# # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00007f8b7c08ba84, pid=1986, tid=1987 # # JRE version: OpenJDK Runtime Environment (14.0.1+7) (build 14.0.1+7-Ubuntu-1ubuntu1) # Java VM: OpenJDK 64-Bit Server VM (14.0.1+7-Ubuntu-1ubuntu1, mixed mode, sharing, tiered, compressed oops, g1 gc, linux-amd64) # Problematic frame: # V [libjvm.so+0xe99a84] # # No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again # # An error report file with more information is saved as: # /home/user/hs_err_pid1986.log # # If you would like to submit a bug report, please visit: # Unknown # Aborted(Ubuntu+OpenJDK Runtime Environment 14.0.1)
WSL2で実行したため実際の結果とは異なる場合があるかもしれません。
Windowsの場合
Windows# # A fatal error has been detected by the Java Runtime Environment: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffd4fa119b7, pid=18520, tid=8224 # # JRE version: Java(TM) SE Runtime Environment (14.0.1+7) (build 14.0.1+7) # Java VM: Java HotSpot(TM) 64-Bit Server VM (14.0.1+7, mixed mode, sharing, tiered, compressed oops, g1 gc, windows-amd64) # Problematic frame: # V [jvm.dll+0x7219b7] # # No core dump will be written. Minidumps are not enabled by default on client versions of Windows # # An error report file with more information is saved as: # C:\Users\User\aa\hs_err_pid18520.log # # If you would like to submit a bug report, please visit: # https://bugreport.java.com/bugreport/crash.jsp #(Windows + Java(TM) SE Runtime Environment 14.0.1)
禍々しいエラー文と共にclassファイルと同じディレクトリにログファイルが生成されます。
Windowsのlogファイルの40行目:
has_err_pid{prosess_id}.log40:#siginfo: EXCEPTION_ACCESS_VIOLATION (0xc0000005), writing address 0x0000000000000000Ubuntuのlogファイルの44行目:
has_err_pid{posess_id}.log49 siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000000コードにも書いたようにメモリの
0番地
に書き込もうとしてアクセス違反を起こしていますね。なんだよ!
Segmentation Fault
書いてないやんけ!詐欺やん!って思ってる人もいると思いますが、UNIXライクのオペレーティングシステム上では、不正なメモリにアクセスをするプロセスは
SIGSEGV
シグナルを受け取る。Microsoft Windows上では、不正なメモリにアクセスするプロセスはSTATUS_ACCESS_VIOLATION
例外を受け取るこの競技(?)のレギュレーションが決まってないのですがこれはセグフォとして扱っていいですよね?
超大雑把な解説
sun.misc.Unsafe
でJavaでもメモリをアクセスする能力を手に入れれます。じゃあ
Unsafe.getUnsafe().putLong(0, 0)
でもっと短くできんじゃないの?って思うかもしれませんが、javaの
unsafe
は名前の通りものすごく安全ではないクラスです。
finalの値を変更したりメモリを確保したり、アクセスしたりやりたい放題できます(ある程度制限があるらしいが)
そのため、コンストラクタがprivateでさらにgetunsafe()
はgetclassloder()
がnullの場合しかインスタンスを生成できません。その抜け道として、リフレクションAPIで無理やりインスタンス化、さらに
setAccessible(true)
を使って普通はアクセスできないメソッドにアクセスできるようにします。最後に
putLong(address, x);
でaddress
にメモリ番地、x
に適当な値を入れることでjavaでもセグフォを引き寄せることができました!!やったね!参考にしたサイト等
sun.misc.Unsafe の魔力
何かの時にスッと使える力技 - Reflection 編
セグメンテーション違反-wikipedia編集リクエストなど気軽に送りつけてください。
拙い文章ですが最後まで閲覧いただきありがとうございました!
- 投稿日:2020-07-02T14:18:53+09:00
稼ぎたいなら食わず嫌いしない。上に立つ人こそ色々と試そう。
今時のリーダーは、率先して色々と試し、失敗し、部下を成功の最短ルートで動かすものだ。
これはIT業界に限ったことではない。フリーランスで年収を上げるのも、専門技術の高さより、幅広い技術の方が年収を上げやすい。
また、副業で稼ぐにも、幅広い技術がいい。私は旅行代理店のシステム開発で副業していたことがあるが、その時は、PHPとRubyだった。
フリーランスのメインの仕事では、Javaだった。副業、クラウドソーシングでは、PHPばっかである。
ただ、フリーランスの仕事では、PHPはかなり減った。PHPの将来性を検索すれば、残るという意見も無くなるという意見もあるが、
それは、副業で考えるか、フリーランスの仕事で考えるか、で変わってくる。結局、自分で試して、自分で考えるしかない。
この試すという手間を検索して、人の意見を聞いて終わりにしないということである。Ruby が出たばかりのころ、Perl と Python が消えると騒がれた。
私は全部試して、業務でも使用した。
Ruby の急成長は予想できなかったが、Perl が消える予想は当たった。
当時、Perl の将来性を検索したら、使用している企業が多いから、消えることはないという意見が多かった。私が消えると言っているのは、稼げないという意味である。
Rust は良い言語だが、PHP はダメだ。 みたいなのは、思い込みである。
PHPの方が楽だし、稼げる。
言いたいことは、状況によるから決めつけるなということ。
そして、自分で試して、情報に流されないということ。今時、開発言語の選定はかなり難しい。
検索して判断せずに、是非、試してから判断して欲しい。
- 投稿日:2020-07-02T14:14:16+09:00
Spring Boot で Azure Cosmos DBにアクセスする
Spring Bootを利用して、Azure CosmosDB(MongoDBコア)にアクセスしたので、その記録です。
コード書くのが怠かったので、https://spring.pleiades.io/guides/gs/accessing-data-mongodb/ を参考にしました。
まずは、普通にサンプルコードをgit cloneします。
$ git clone https://github.com/spring-guides/gs-accessing-data-mongodb.git
チュートリアル通りにローカルにMongoDBを立てて、
$ sudo apt install mongodb $ mkdir -p data/db $ mongod --dbpath=./data/dbGradleで実行します。
$ gradle bootRun Welcome to Gradle 6.5.1! Here are the highlights of this release: - Experimental file-system watching - Improved version ordering - New samples For more details see https://docs.gradle.org/6.5.1/release-notes.html Starting a Gradle Daemon (subsequent builds will be faster) > Task :bootRun . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.0.RELEASE) 2020-07-02 03:12:00.912 INFO 32643 --- [ main] c.e.a.AccessingDataMongodbApplication : Starting AccessingDataMongodbApplication on DESKTOP-N9T4CN3 with PID 32643 (XXX/gs-accessing-data-mongodb/complete/build/classes/java/main started by XXX in XXX/gs-accessing-data-mongodb/complete) 2020-07-02 03:12:00.917 INFO 32643 --- [ main] c.e.a.AccessingDataMongodbApplication : No active profile set, falling back to default profiles: default 2020-07-02 03:12:01.274 INFO 32643 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode. 2020-07-02 03:12:01.315 INFO 32643 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 36ms. Found 1 repository interfaces. 2020-07-02 03:12:01.589 INFO 32643 --- [ main] org.mongodb.driver.cluster : Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500} 2020-07-02 03:12:01.674 INFO 32643 --- [localhost:27017] org.mongodb.driver.connection : Opened connection [connectionId{localValue:1, serverValue:1}] to localhost:27017 2020-07-02 03:12:01.678 INFO 32643 --- [localhost:27017] org.mongodb.driver.cluster : Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 8]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=2881100} 2020-07-02 03:12:01.745 WARN 32643 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 03:12:01.745 WARN 32643 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 03:12:01.771 WARN 32643 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 03:12:01.771 WARN 32643 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 03:12:02.134 INFO 32643 --- [ main] c.e.a.AccessingDataMongodbApplication : Started AccessingDataMongodbApplication in 1.552 seconds (JVM running for 1.853) 2020-07-02 03:12:02.157 INFO 32643 --- [ main] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:2}] to localhost:27017 Customers found with findAll(): ------------------------------- Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith'] Customer[id=5efcd1f2b15c223a7dc2a9cf, firstName='Bob', lastName='Smith'] Customer found with findByFirstName('Alice'): -------------------------------- Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith'] Customers found with findByLastName('Smith'): -------------------------------- Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith'] Customer[id=5efcd1f2b15c223a7dc2a9cf, firstName='Bob', lastName='Smith'] 2020-07-02 03:12:02.270 INFO 32643 --- [extShutdownHook] org.mongodb.driver.connection : Closed connection [connectionId{localValue:2, serverValue:2}] to localhost:27017 because the pool has been closed. BUILD SUCCESSFUL in 53s 2 actionable tasks: 2 executedmongoコマンドで中を覗いてみます。
$ mongo MongoDB shell version v3.6.8 connecting to: mongodb://127.0.0.1:27017 Implicit session: session { "id" : UUID("2648d101-f236-4f56-bc3d-7250d32ef9a2") } MongoDB server version: 3.6.8 Welcome to the MongoDB shell. For interactive help, type "help". For more comprehensive documentation, see http://docs.mongodb.org/ Questions? Try the support group http://groups.google.com/group/mongodb-user Server has startup warnings: 2020-07-02T02:54:56.366+0900 I STORAGE [initandlisten] 2020-07-02T02:54:56.366+0900 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine 2020-07-02T02:54:56.366+0900 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database. 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted. 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** WARNING: This server is bound to localhost. 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** Remote systems will be unable to connect to this server. 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** Start the server with --bind_ip <address> to specify which IP 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** addresses it should serve responses from, or with --bind_ip_all to 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** bind to all interfaces. If this behavior is desired, start the 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] ** server with --bind_ip 127.0.0.1 to disable this warning. 2020-07-02T02:54:57.070+0900 I CONTROL [initandlisten] 2020-07-02T02:54:57.071+0900 I CONTROL [initandlisten] 2020-07-02T02:54:57.071+0900 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. 2020-07-02T02:54:57.071+0900 I CONTROL [initandlisten] ** We suggest setting it to 'never' 2020-07-02T02:54:57.071+0900 I CONTROL [initandlisten] > db.customer.find() { "_id" : ObjectId("5efcd1f2b15c223a7dc2a9ce"), "firstName" : "Alice", "lastName" : "Smith", "_class" : "com.example.accessingdatamongodb.Customer" } { "_id" : ObjectId("5efcd1f2b15c223a7dc2a9cf"), "firstName" : "Bob", "lastName" : "Smith", "_class" : "com.example.accessingdatamongodb.Customer" } >入ってますね。
この時点で、チュートリアルでは接続先のMongoDBへのアクセスは行っていません。実装されているデータアクセスに関するものも、以下の2つのみです。
Customer.javapackage com.example.accessingdatamongodb; import org.springframework.data.annotation.Id; public class Customer { @Id public String id; public String firstName; public String lastName; public Customer() {} public Customer(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Override public String toString() { return String.format( "Customer[id=%s, firstName='%s', lastName='%s']", id, firstName, lastName); } }CustomerRepository.javapackage com.example.accessingdatamongodb; import java.util.List; import org.springframework.data.mongodb.repository.MongoRepository; public interface CustomerRepository extends MongoRepository<Customer, String> { public Customer findByFirstName(String firstName); public List<Customer> findByLastName(String lastName); }高レベルAPIであるRepositoryを使用しているので、実装は非常に簡単です。実際にはSpring Data MongoDBによってCustomerRepojitoryインターフェースを実装するクラスが自動生成されて、saveやfindなどのメソッドも利用できるようになっています。便利ですね。(低レベルAPIであるTemplateMongoを使用すると、もっと複雑なこともできます。)
今回は、MongoDBコアで作成したCosmosDBにアクセスするのが主目的なので、高レベルAPIを利用したこのサンプルを利用します。
Azure側に、CosmosDBを作成します。こちらは https://docs.microsoft.com/ja-jp/azure/cosmos-db/create-mongodb-java#create-a-database-account を参考にしてサクサクとPortalから作成します。コレクションの作成は不要です。
作成されたら、Portalから接続先のURIを確認します。今回はJavaですので、"クイックスタート"の"Java"タブに表示されているプライマリ接続文字列をコピーします。
サンプルコードに、main/resources/application.propertisを追加し、"spring.data.mongodb.uri"を追加して、先ほどコピーした値を貼り付け、Mongo DB Driverが3.6以降の場合は末尾に"&retrywrites=false"を追加します。Cosmos DBが現時点でRetryable Writesに対応していないためです。
application.propertiesspring.data.mongodb.uri=<your_connection_string>&retrywrites=false定義したURLを使用するため、Configurationを作成します。
CustomerRepogitoryConfig.javapackage com.example.accessingdatamongodb; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CustomerRepositoryConfig { @Value("${spring.data.mongodb.uri:mongodb://localhost:27017}") public String connectionString; public @Bean MongoClient mongoClient() { return MongoClients.create(this.connectionString); } }Gradleで実行します。
$ gradle bootRun -Pargs=--debug > Task :bootRun . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.0.RELEASE) 2020-07-02 04:54:56.451 INFO 3822 --- [ main] c.e.a.AccessingDataMongodbApplication : Starting AccessingDataMongodbApplication on DESKTOP-N9T4CN3 with PID 3822 (XXX/gs-accessing-data-mongodb/complete/build/classes/java/main started by XXX in XXX/gs-accessing-data-mongodb/complete) 2020-07-02 04:54:56.456 INFO 3822 --- [ main] c.e.a.AccessingDataMongodbApplication : No active profile set, falling back to default profiles: default 2020-07-02 04:54:57.370 INFO 3822 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode. 2020-07-02 04:54:57.497 INFO 3822 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 118ms. Found 1 repository interfaces. 2020-07-02 04:54:58.011 INFO 3822 --- [ main] org.mongodb.driver.cluster : Cluster created with settings {hosts=[XXX:YYY], mode=MULTIPLE, requiredClusterType=REPLICA_SET, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500, requiredReplicaSetName='globaldb'} 2020-07-02 04:54:58.011 INFO 3822 --- [ main] org.mongodb.driver.cluster : Adding discovered server XXX:YYY to client view of cluster 2020-07-02 04:54:58.244 WARN 3822 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 04:54:58.245 WARN 3822 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 04:54:58.316 WARN 3822 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 04:54:58.317 WARN 3822 --- [ main] o.s.data.convert.CustomConversions : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation. 2020-07-02 04:54:58.912 INFO 3822 --- [ main] c.e.a.AccessingDataMongodbApplication : Started AccessingDataMongodbApplication in 3.129 seconds (JVM running for 3.84) 2020-07-02 04:54:58.947 INFO 3822 --- [ main] org.mongodb.driver.cluster : No server chosen by com.mongodb.client.internal.MongoClientDelegate$1@210386e0 from cluster description ClusterDescription{type=REPLICA_SET, connectionMode=MULTIPLE, serverDescriptions=[ServerDescription{address=XXX:YYY, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out 2020-07-02 04:54:59.082 INFO 3822 --- [azure.com:10255] org.mongodb.driver.connection : Opened connection [connectionId{localValue:1, serverValue:1162392059}] to XXX:YYY 2020-07-02 04:54:59.097 INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster : Monitor thread successfully connected to server with description ServerDescription{address=XXX:YYY, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 0]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=12402600, setName='globaldb', canonicalAddress=XXX:YYY, hosts=[XXX:YYY], passives=[], arbiters=[], primary='XXX:YYY', tagSet=TagSet{[Tag{name='region', value='Japan East'}]}, electionId=null, setVersion=1, lastWriteDate=null, lastUpdateTimeNanos=134393982328436} 2020-07-02 04:54:59.100 INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster : Adding discovered server XXX:YYY to client view of cluster 2020-07-02 04:54:59.103 INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster : Server XXX:YYY is no longer a member of the replica set. Removing from client view of cluster. 2020-07-02 04:54:59.105 INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster : Canonical address XXX:YYY does not match server address. Removing XXX:YYY from client view of cluster 2020-07-02 04:54:59.338 INFO 3822 --- [azure.com:10255] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:689958399}] to XXX:YYY 2020-07-02 04:54:59.348 INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster : Monitor thread successfully connected to server with description ServerDescription{address=XXX:YYY, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 0]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=8755400, setName='globaldb', canonicalAddress=XXX:YYY, hosts=[XXX:YYY], passives=[], arbiters=[], primary='XXX:YYY', tagSet=TagSet{[Tag{name='region', value='Japan East'}]}, electionId=null, setVersion=1, lastWriteDate=null, lastUpdateTimeNanos=134394233832236} 2020-07-02 04:54:59.349 INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster : Setting max set version to 1 from replica set primary XXX:YYY 2020-07-02 04:54:59.349 INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster : Discovered replica set primary XXX:YYY 2020-07-02 04:54:59.603 INFO 3822 --- [ main] org.mongodb.driver.connection : Opened connection [connectionId{localValue:3, serverValue:1139365834}] to XXX:YYY Customers found with findAll(): ------------------------------- Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith'] Customer[id=5efcea15cac85c79009dba56, firstName='Bob', lastName='Smith'] Customer found with findByFirstName('Alice'): -------------------------------- Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith'] Customers found with findByLastName('Smith'): -------------------------------- Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith'] Customer[id=5efcea15cac85c79009dba56, firstName='Bob', lastName='Smith'] 2020-07-02 04:55:01.231 INFO 3822 --- [extShutdownHook] org.mongodb.driver.connection : Closed connection [connectionId{localValue:3, serverValue:1139365834}] to XXX:YYY because the pool has been closed. BUILD SUCCESSFUL in 8s 3 actionable tasks: 2 executed, 1 up-to-date成功しました。Azureポータルのデータエクスプローラーでcustomerコレクションが追加されていることを確認しましょう。
データが作成されていることが確認できますね。
Cosmos DBは最近Free Tierが提供されたので、手軽に機能を試すことができます。
是非、触ってみてください。
- 投稿日:2020-07-02T14:01:25+09:00
Apache JMeter で大きいファイルをダウンロードする
やりたいこと
JMeter で大容量のファイルをダウンロードするような負荷テストをしたい。
(Azure CDN を使ったファイルダウンロードの負荷テストを行いたいという要望があり調査した内容です)準備すべきこと
以下の条件を整えることが重要
- 64bit 版の Java をインストールする
- JVM の実行時オプションでヒープサイズを拡張する
- ヒープサイズを十分に拡張可能な実メモリを搭載する
- 64bit 版の JVM を使う
- CUI モードで実行する
それぞれやること
64bit 版の Java をインストールする
Java の公式サイトから、64bit 版の Java のインストーラーをダウンロードしてインストール。
手順は下記のサイトのドキュメントを参考にして、マニュアルインストールです。https://java.com/ja/download/faq/java_win64bit.xml
JVM の実行時オプションでヒープサイズを拡張する & 64bit 版の JVM を使う
JMeter インストール先のディレクトリにある jmeter.bat をテキストエディタで開いて、以下の部分を適宜変更。
下記の設定では、ヒープサイズを 16GB に拡張したうえ、64bit JVM を利用する設定。beforeset HEAP=-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256mafterset HEAP=-Xms1g -Xmx16g -XX:MaxMetaspaceSize=256m -d64ヒープサイズを十分に拡張可能な実メモリを搭載する
ここは、物理マシンなら財力任せですし、仮想マシンならヒープサイズよりも大きいメモリを搭載したものを選ぶのが良いでしょう。
例えば、Azure の VM だと E シリーズなどが搭載メモリ量多めで使いやすいサイズになるかな…と思います。
https://docs.microsoft.com/ja-jp/azure/virtual-machines/ev3-esv3-seriesCUI モードで実行する
JMeter の起動時に、GUI で起動すると下記のメッセージが出るはずです。
Don't use GUI mode for load testing !, only for Test creation and Test debugging. For load testing, use CLI Mode (was NON GUI): jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]GUI で jmx ファイルを生成した後、改めてコンソールから JMeter を起動しましょう。
まとめ
上記の条件を整えれば、でかいファイルをダウンロードする類の負荷テストも JMeter で実行しやすくなると思います。(とはいえ、ヒープに確保できるメモリの量次第なので、ダメだったらごめんなさい。)
条件が整っていないときに出るエラーたち(検索用)
JVM のヒープサイズが足りない
java.lang.OutOfMemoryError: Java heap space64bit 版の Java がセットアップされていない
Error: This Java instance does not support a 64-bit JVM. Please install the desired version.
- 投稿日:2020-07-02T12:11:54+09:00
【Java】Spring Boot 2.3.1×Thymeleafでエコーアプリケーション作ったった
はじめに
こんにちは!
macでSTSの環境構築を行って、Spring BootでHelloWorldしていた者です。
今回は簡単なエコーアプリケーションを作成してみました。画面遷移図っぽくすると、以下のようになります。
↓「送信」押下 ↑「お返事bot TOP」押下
前回、前々回同様、Spring徹底入門を参考に進めました。
本にはSpring MVCでのエコーアプリケーションの作り方と、SpringBootでのHelloWorldのコードは載っているのですが、Spring Bootでのエコーアプリケーションの方法はなかったので、なんとか混ぜ合わせながら作りました。本当はバリデートとかテストコードとかもチャカチャカ実装するつもりだったのですが…Viewで使っているThymeleafに馴染みがなく(今までJSPを使っていましたが、非推奨らしい…!)、概念と書き方に慣れるのにちょっと時間がかかりました!ので、バリデートとか一切ない、ただのピュアなエコーアプリケーションです!
Thymeleaf?テンプレートエンジン?何それ?な人が読むとちょっと得するかも!な内容となっています。
使用環境とバージョン
- macOS Catalina
- jdk14.0.1
- JUnit5
- Maven 3.6.3_1
- STS 4.6.1
- Spring Boot 2.3.1
- spring-boot-starter-thymeleaf-2.3.1
Spring Initializrでプロジェクトの雛形を作る
前回同様Spring Initializrでプロジェクトの雛形を作成します。
使い方自体は前回と変わらないので、相違点だけ紹介します。(左上から)
Spring Bootのバージョン
前回は2.3.0があったのですが、今回なくなっていたので2.3.1を使用しました1。Project Metadata
前回はデフォルトのdemoのままで行いましたが、同名ファイルだと混ざるのでformに変更しました2。Dependencies【重要!】
前回は何も指定しませんでしたが、今回はThymeleafを使用するため、ADD DEPENDENCIESからThymeleafを選択しました。(もちろん後からpom.xmlを書き換えればなんとかなりますが…)ファイル構成
前回はメインメソッドとコントローラーを同じクラス
DemoApplication.java
にしていましたが、今回はメインメソッドはEchoApplication.java
に、コントローラーはEchoController.java
に分けて作成しました。ただ、2つのHTMLそれぞれにコントローラーを作るほどでもなかったので、コントローラー1つで2つのページを担当しています。
そのほか、EchoFormクラスは画面に入力された値(今回はお返事botなので入力する値は名前)を受け取るクラスです。index.html
がお返事bot TOPページ、echo.html
は名前「もょもと」が送信されたあとで「Hello もょもと」とお返事していたページです3。①コントローラーの作成
EchoApplication.java
はテンプレートそのままですので、割愛します。
EchoController.java
は以下のようになりました。EchoController.javapackage com.example.form; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping public class EchoController { @ModelAttribute public EchoForm setUpEchoForm() { EchoForm form = new EchoForm(); return form; } @RequestMapping public String index(Model model) { return "index"; } @RequestMapping(value = "echo", method = RequestMethod.POST) public String echo(EchoForm form, Model model) { model.addAttribute("name", form.getName()); return "echo"; } }(早くもformというパッケージ名を後悔し始めている、echoだったな)
setUpEchoForm
メソッドについている@ModelAttribute
は、そのメソッドの戻り値を自動でModelに追加するためのアノテーションです。Modelの属性名はクラス名(EchoForm)の先頭を小文字にしたものになります。この場合はechoForm
です4。
@RequestMapping
アノテーションは、URLリクエストのマッピングをしています。
自分のざっくりした理解では、@RequestMapping
単体だと、ディレクトリパスなしのURL直下にアクセスした時の挙動と思っています。valueを指定するとルートページからの相対パスが指定でき、methodは見た通り、受け付けるHTTPリクエストの種類を指定できます。現在の実装だと、以下の挙動になります。
- indexメソッドは
http://{サーバー名}
というURLへのリクエストで起動し、”index”というview名をreturnします。- echoメソッドは
http://{サーバー名}/echo
というURLへのリクエストで起動し、”echo”というview名をreturnします。 POSTしか受け付けていないため、サーバー起動後直にhttp://{サーバー名}/echo
でGETリクエストをすると、以下のエラーが発生します。②フォームクラスの作成
コントローラーで既に出てきましたが、テキストフィールドの値を受け取るためのフォームオブジェクトとして、EchoFormクラスの実装も必要です。getter/setterでnameプロパティをやり取りするだけのシンプルなクラスを実装しました。
EchoForm.javapackage com.example.form; import java.io.Serializable; public class EchoForm implements Serializable { private static final long serialVersionUID = 1L; private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } }③Viewの作成…っていうかThymeleafってなんなんだ?
よ〜しロジックはできたぞ〜!あとはガワだ〜!JSPでしょ?知ってる知ってる!
…というように、View=JSPだとばかり思っていた人間です。
なので『Spring徹底入門』で以下記述を見たときに、控えめに言って絶望しました。JSPの利用はさまざまな制約があるため推奨されていないため、本書でも扱いません。(p.602)
おやおや?JSPがないと「「Hello もょもと」ページに遷移」のように、入力値によって生成するHTML自体を変えるような実装はできないのではないか??と思いました。実際に検索すると、Spring BootではJSPの使用はやはり非推奨となっているようです。
If possible, JSPs should be avoided. There are several known limitations when using them with embedded servlet containers.
え…どないすればええんや…?汎用なHTMLを作って…都度DOM操作する気か…??(よく分かってない)
ご安心ください。ちゃんと『Spring徹底入門』12章に書いてありました。
Thymeleafは、Webアプリケーションと親和性の高いテンプレートエンジンです。テンプレートエンジンとは、雛形となるドキュメント(テンプレート)に対し、可変データを埋め込むことで動的にドキュメントを生成する仕組みです。この仕組みは、MVCフレームワークのModelとViewを分割する考え方と親和性が高く、しばしばMVCフレームワークのViewとして利用されます。(p.554)
なるほどなるほど。テンプレートエンジンってそういうものなのですね。よく分からぬままDependenciesに入れてました。だめな人ですね?♂️
仕組みとしては、先ほど想像していた形に近く、HTMLをDOMに変換してから処理を行うそうです。以下が
index.html
の実装です。index.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8" /> <link th:href="@{/style.css}" rel="stylesheet" type="text/css"> <title>Echo Application</title> </head> <body> <h1>お返事bot TOP</h1> <form th:object="${echoForm}" th:action="@{/echo}" method="post"> <label for="name">名前を入れてください:</label> <input th:field="*{name}"> <input type="submit"> </form> </body> </html>頭にth:とついている属性が、Thymeleaf固有の書き方になっています。
フォームクラスとModelの紐付けはコントローラーに付与した@ModelAttribute
アノテーションが行っていますが、ThymeleafとModelの紐付けは、以下の部分で行っています。
th:object="${echoForm}"
先述の通り
@ModelAttribute
アノテーションは、Modelの属性名をクラス名(EchoForm)の先頭を小文字にしたものにしているため、echoFormという名称で正しいです。Thymeleaf記法の変数式に入れた${echoForm}
をobjectとして設定することで、入力値の受け渡しができるようになります。
th:action
はHTMLのaction属性と似たような働きです。ただし、パスの書き方がThymeleaf独自のものとなっています。なお、th:action
やth:href
の前に通常のaction属性やhref属性を設定しておくと、アプリの一部として動かす場合だけでなく、普通のHTMLとしても動くようです。ThymeleafのテンプレートはHTML5準拠のため、デザイナーとエンジニア間で共有できるのも強みですね。「もょもと」を返すecho.htmlは以下のような実装となっています。
ここの変数名は、コントローラーのechoメソッド内の第一引数から来ているため、nameになっています。echo.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8" /> <link th:href="@{/style.css}" rel="stylesheet" type="text/css"> <title>Echo Application</title> </head> <body> <p th:text="|Hello ${name}|"></p> <p><a th:href="@{/}">お返事bot TOP</a></p> </body> </html>これで、もょもと完成です!!いええええい!!ぺぺぺ!!
おわりに
Springのプロジェクトを再度作ったので、前回より結構分かるようになっていた気がします…!
コントローラーとメインメソッドを分けたこともあって、どのアノテーションがどこに必要なのか、とかも分かりやすくなったのが良かったなと思います!規模が大きくなってきたら、index.htmlとecho.htmlもコントローラー分けてあげなきゃな…と思うなどしました。Thymeleafは初見だったので、結構いろいろ調べました!最終的に『Spring徹底入門』が最強だったので、Springに手を出したい人は買って損はないです…!辞書的に使えます!
今回もお読みいただき、ありがとうございました!
調べたらSpring Boot 2.3.0が2020年5月15日、2.3.1が2020年6月12日にリリースされているのですね…1ヶ月スパン…早い…。前回はなかった2.4.0のスナップショットも出ていて、移り変わりの速さにびっくりしました。 ↩
のちにクラス名プレフィクスをEchoに変えたりしているので、特に参考にしなくて大丈夫です。 ↩
HTMLベターッはさすがにやめたくなったので、CSSはこちらのサイトの「ゆっくりゆっくり色変化」を参考にさせていただきました。「サイトの引き立て役はコレ! おしゃれすぎる背景をコピペで実装 【 HTML/CSS 】」https://deshinon.com/2019/03/06/background-oshare-kopipe/ ↩
こちらのサイトを参考にさせていただいています。「2.3. はじめてのSpring MVCアプリケーション」https://macchinetta.github.io/server-guideline-thymeleaf/current/ja/Overview/FirstApplication.html ↩
- 投稿日:2020-07-02T11:59:33+09:00
SpringBoot入門ガイドやってみた【Building a RESTful Web Service編】
目的
Spring Quickstart Guideを取り組み終えた方、SpringBootを学び始めた方、復習をしたい方に向けて、
公式が人気ガイドだからやってみて!と勧めてくれている、Building a RESTful Web Serviceを実際に取り組み学んだことを共有します。
開発環境OS: macOS Mojave バージョン10.14.6 テキストエディタ: Visual Studio Code(以下VSCode) Java: 11.0.21.SpringBoot projectを始めよう!
まずは、spring initializrにアクセスします。
web
と入力して、Spring Web
を選択します。Artifact, Nameは、restserviceに変更しました。
Javaのversionは11なので、11を選択します。
GENERATE
ボタンをクリックします。Zipファイルがダウンロードされるので、
そのZipファイルを展開してください。
準備完了です。
2.コードを追加しよう!
先ほどのフォルダをVSCodeで開きます。
拡張機能のJava Extension Packのインストールの推奨します。と言われるのでインストールしておきましょう。Greeting.javaを作成しよう!
src/main/java/com/example/restservice/
にGreeting.javaファイルを作成します。Greeting.javaファイル内にコードを追加していきます。
Greeting.java完成形package com.example.restservice; public class Greeting { private final long id; private final String content; public Greeting(long id, String content) { this.id = id; this.content = content; } public long getId() { return id; } public String getContent() { return content; } }Greeting.javaファイルに追加したコードを深掘りしていきます。
①定数の宣言
private final long id; private final String content;long型のid、string型のcontentという定数をそれぞれ宣言しています。
アクセス修飾子はprivate
、そして、final修飾子を用いている
ので、同一クラス内からしかアクセス出来ない定数となります。
定数なので、値の再代入は不可能となります。②コンストラクタの定義
public Greeting(long id, String content) { this.id = id; this.content = content; }
インスタンス化された時に同時に行っておきたい処理
を記述しておきます。
今回は、インスタンス定数であるid、contentに初期値を代入しています。③ゲッターメソッドの定義
public long getId() { return id; } public String getContent() { return content; }id、contentの値を呼び出すためのメソッドです。
Greeting.javaはこれで完成です!
GreetingController.javaを作成しよう!
src/main/java/com/example/restservice/
にGreetingController.javaファイルを作成します。GreetingController.javaファイル内にコードを追加していきます。
GreetingController.java完成形package com.example.restservice; import java.util.concurrent.atomic.AtomicLong; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class GreetingController { private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @GetMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { return new Greeting(counter.incrementAndGet(), String.format(template, name)); } }GreetingController.javaファイルに追加したコードを深掘りしていきます。
①@RestController
@RestController public class GreetingController { }このアノテーションを記述する事により、SpringBootはControllerとして認識してくれます。Viewには遷移せずにメソッドの戻り値がそのままレスポンスのコンテンツとなります。
後述のgreetingメソッドはGreetingオブジェクトを戻り値にしていますが、SpringBootがJSONに自動変換してくれているのでJSONが画面に表示される事になります。
②定数の宣言
private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong();string型のtemplate、long型のcounterという定数をそれぞれ宣言しています。
アクセス修飾子はprivate
、そして、final修飾子を用いている
ので、同一クラス内からしかアクセス出来ない定数となります。
定数なので、値の再代入は不可能となります。templateの方は、static修飾子を用いているので、クラスに対して1つしか存在しない定数となります。
そして%s
は、String.formatというメソッドを使用する際に、第一引数にきめたれた書式を指定しなければいけないため、記載されています。counterの方は、AtomicLongクラスを用いています。
なぜこのクラスを用いているのかは後ほど記述します。③greetingメソッドの定義
@GetMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { return new Greeting(counter.incrementAndGet(), String.format(template, name)); }
@GetMapping("/greeting")
は、URLで〇〇/greeting
とアクセスされた時(GETリクエストがあった時)、greetingメソッドの処理が行われるためのアノテーションです。
http://localhost8080/greeting とアクセスがあった時にgreetingメソッドが呼ばれるという事です。greetingメソッドの引数の
@RequestParam
は、URLのクエリパラメータを受け取る事が出来ます。
String nameにどのような値を格納するのかを指定しており、http://localhost8080/greeting とアクセスがあった場合は、
nameにはWorldが格納され
、
http://localhost8080/greeting?name=tanaka とアクセスがあった場合は、nameにはtanakaが格納されます
。最後にreturnの部分を深掘りします。
return new Greeting(counter.incrementAndGet(), String.format(template, name));http://localhost8080/greeting とアクセスされる毎にGreetingクラスをインスタンス化したJavaオブジェクトを返しています。
new Greetingのコンストラクタの第一引数にcounter.incrementAndGet()
、第二引数にString.format(template, name)
を渡しています。Greetingクラスのインスタンス化、counterのインクリメント、書式を指定して文字列を返す、と複数処理を同時に行っています。
インクリメントは
counter++;
と記述も出来ますが、複数の処理が実行された場合に正しく処理されない場合があるため、上述のAtomicLongクラスでcounterを定義して、同クラスのメソッドである
incrementAndGet()
を使用し、現在の値(counter)をインクリメントして、インクリメントした値を返しています。アトミックは不可分操作と言われており、加算が終わるまで他の処理の割り込みをさせないためにAtomicLongクラスを用いてcounterを定義した、と解釈しました。
3.実行してみよう!
アプリケーション実行の準備が出来たので確認しましょう。
ターミナルで以下のコマンドを入力してEnterしてください。
ターミナル$ ./mvnw spring-boot:run2秒ぐらい待った後、http://localhost:8080/greeting にアクセスすると、
次に、http://localhost:8080/greeting?name=tanaka でアクセスすると、
次に、http://localhost:8080/greeting?name=suzuki でアクセスすると、
アクセスする毎にidが増えていき、
/greeting?name=任意の文字列
でアクセスすると入力した値が表示されていますね!参考サイト
SpringBoot入門ガイド
OracleのAtomicLong
アトミックであるとはどういうことか
マルチスレッド
Java並行処理の基本
- 投稿日:2020-07-02T11:28:35+09:00
【Android 9.0 Pie】ActivityやFragment以外でのstrings.xmlの呼び出し方の例
初めに
定数の管理に便利なstrings.xmlですが、ActivityやFragmentを継承していないクラスで呼び出す際の方法をメモしておきます。
実装方法
私はApplicationクラスを継承したクラスにコンテキストを生成してしまい、定数取得用の関数を作成して呼び出し側のコードを少しでも削れるようにしてみました。
Model.javapublic class Model extends Application { private static Context context; @Override public void onCreate() { super.onCreate(); context = this; } /** * 定数取得関数 * strings.xmlに定義した定数をどこからでも取得できる * @param resId R.string.resId * @return String 定数 */ public static String getConst(int resId) { return context.getResources().getString(resId); } }strings.xml<string name="chat_list_delete_button_label">DELETE</string>呼び出し側は以下のような記述で呼び出せます
Model.getConst(R.string.chat_list_delete_button_label);以上です。
どなたかの参考になれたら幸いです。
- 投稿日:2020-07-02T06:47:04+09:00
【最初理解できんかった】配列でソートしたいけど、配列が全部文字列!?
こんな感じで出力するつもりが、
/* [出力結果イメージ] 1,犬 2,猫 (省略) 9,鳥 10,サル 11,馬 (省略) 20,獅子 */
なぜかこうなる…
/* 1,犬 10,サル 11,馬 12,カエル 13,みみず 14,ミミズク 15,フクロウ 16,ライオン 17,パンダ 18,ラマ 19,鹿 2,猫 20,獅子 3,魚 4,貝 5,アシカ 6,シャチ 7,かもしか 8,虫 9,鳥 */
これが解決したんで載せておきます。
もとのコード(失敗例)
//importはしている前提 //データ一覧 private static final String[] dataList = { "8,~~", "10,~~", "11,~~", "12,~~", "20,~~", "1,~~", "18,~~", "13,~~", "5,~~", "3,~~", "19,~~", "17,~~", "7,~~", "16,~~", "6,~~", "15,~~", "2,~~", "4,~~", "14,~~", "9,~~" }; public static void main(String[] args) { Arrays.sort(dataList); //ソートしています。 for(String arr : dataList){ System.out.println(arr); } } }これすると
1
11
12
って先頭だけでソートしてしまいます…?しかも
❶”int型”,"Strnig型"と区切られてないのでソートしにくい!
❷そもそも文字列だから配列ごと比較するしかないの?でもどうやってと悩みまくりでした…。
改善後のコード
//importはしている前提 //データ一覧 private static final String[] dataList = { "8,~~", "10,~~", "11,~~", "12,~~", "20,~~", "1,~~", "18,~~", "13,~~", "5,~~", "3,~~", "19,~~", "17,~~", "7,~~", "16,~~", "6,~~", "15,~~", "2,~~", "4,~~", "14,~~", "9,~~" }; public static void main(String[] args) { //ソートしています。 Arrays.sort(dataList, (s1, s2) -> { String[] s1Parts = s1.split(','); int n1 = Integer.parseInt(s1Parts[0]); String[] s2Parts = s2.split(','); int n2 = Integer.parseInt(s2Parts[0]); return Integer.compare(n1, n2); }); for(String arr : dataList){ System.out.println(arr); } } }ただなんもわかってないので、自分用に解説します。
なぜこうなるのか?
まずArrays.sortのドキュメント。具体的にはシグニチャを確認しましょう。
また、ドキュメント見るときはGoogle翻訳もうまく活用しましょうね。シグニチャとは…
シグニチャはメソッド名と引数の数、引数の型、引数の順番が含まれています。※引数の数、引数の型、引数の順番をまとめて引数のリストともいいます。
public static <T> void sort(T[] a, Comparator<? super T> c)1つ目の引数は
T[] a
であり、String[] dataList
がそれにあたります。2つ目の引数は
Comparator<? super T> c
。Comparator
は interface で、「比較器」の意味です。1つ目の引数により、T
がStringに固定
されています。? super T
は、T
のスーパークラスです。全体に見れば、この引数は「String
あるいはそのスーパークラスを比較できる関数」の意味です。sort
はこの関数を使って配列をソートします。また
Comparator
はこのようなものです:
公式ドキュメントより java.util.Comparator.compare@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); }前言により、ここの
T
も「String
あるいはそのスーパークラス」になります。Java 8 以降、
Comparator
みたいな@FunctionalInterface
を求める引数にラムダ式(lambda expression)を入れられます。ラムダ式はこの部分です:(s1, s2) -> { String[] s1Parts = s1.split(','); int n1 = Integer.parseInt(s1Parts[0]); String[] s2Parts = s2.split(','); int n2 = Integer.parseInt(s2Parts[0]); return Integer.compare(n1, n2); }これが実は
compare
を定義しているのです。
(s1, s2)
は引数リストで、s1もs2もString
です。これはComparator
よりJava
もう知っていますから、略しても大丈夫です。
->
はただの提示符
で、「この後はcompare
の内容です」と提示しています。
{}
の中はcompare
の内容です。compare
の要求は、
s1がs2と同じ
の場合、0を返す;
s1がs2より大きいの場合
、正の整数(ここでは1)を返す;
s1がs2より小さいの場合
、負の整数(ここでは-1)を返すことです。この要求をInteger.compareが満たせますので、ここで使います。
オブジェクトの配列をソートする場合、オブジェクトがComparableを実装したら、Comparatorの必要がなくなりますが、ここではStringのComparableが要求の順番と違いますから、新たにComparatorを作って渡すそうです!
そして、Integerが実はjava.util.Integerというクラスで、intに関して様々な関数を含めています。なぜなら、intがプリミティブ型であり、メソッドをつけられなくて、Integerに付けることになりました。だから、Integer.compareが「Integerを返す」ことがなく、「Integerを比較する」の意味です。たまたまcompareがintを返すべきことがあるだけで、最後のreturnは
「Integerを比較する」の意味
で比較した結果を返しているという認識
です。並べ方について(ソートの名前)
1 10 11 12 2 20 21という並び方は「辞書式順序」 (lexicographical order) と呼ばれています。なぜなら、Stringをソートする場合はほぼアルファベット順序でソートするので
a ab abc ac b ba bbみたいなので、そのまま数字に伸びてしまいました。一方
1 2 3 5 11 13 22というのは「自然順」 (natural order) と呼ばれています。バーション数字をソートするのにもよく使われてますので、「バーションソート」 (version sort) とも呼ばれています。
非常に勉強になりました…。
なぜ?の部分が結構自分なりに解釈出来て感動しております
?
- 投稿日:2020-07-02T00:45:28+09:00
SDKMAN でインストールした java のバージョンをディレクトリ移動時に切り替える
.sdkmanrc を使うと、direnv のようにディレクトリ移動時に SDKMAN でインストールした java, scala などのバージョンを切り替えることができる。
比較的最近追加されたので、利用するには SDKMAN を最新(執筆時点では 5.8.3)にアップデートしておく。.sdkmanrc を生成
バージョンを指定したいディレクトリで
sdk env init
を実行する。$ cd sdkenv-test/ $ sdk env init .sdkmanrc created..sdkmanrc が生成され、次のようになっている。
$ cat .sdkmanrc # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below java=8.0.202.j9-adpt設定
生成された .sdkmanrc に使用したいバージョンを指定する。
# Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below java=13.0.2.j9-adpt~/.sdkman/etc/config で
sdkman_auto_env=true
に設定する。
sdkman_auto_env=true
とすることで direnv のようにディレクトリに移動したときに自動的に切り替わる。シェルをリスタートする。
$ exec $SHELL -l実行例
$ cd sdkenv-test/ Using java version 13.0.2.j9-adpt in this shell.sdkman_auto_env=false の場合は手動で切り替える必要がある
sdkman_auto_env=false
に設定されていると、.sdkmanrc のあるディレクトリに移動しても自動では切り替わらない。
sdk env
を実行して切り替えないといけない。$ cd sdkenv-test/ $ java --version openjdk 14.0.1 2020-04-14 OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7) Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.20.0, JRE 14 Linux amd64-64-Bit Compressed References 20200416_44 (JIT enabled, AOT enabled) OpenJ9 - 05fa2d361 OMR - d4365f371 JCL - 5757187cae based on jdk-14.0.1+7) $ sdk env Using java version 13.0.2.j9-adpt in this shell.
- 投稿日:2020-07-02T00:16:43+09:00
Javaの配列
はじめに
順次進行、繰り返し、条件分岐でだいたい作れるって言われても全然作れんわッッ!!(本投稿に関係はない)
配列
変数は1つの値しか入れられないが、配列は複数の値をまとめて入れれる。
1列のものを一次元配列、2列以上は多次元配列という。
ひとつひとつを要素といい、その数を要素数というkane.javaデータ型[] 配列変数名; 配列変数名 = new データ型[要素数]; String[] banana; banana = new string[3]; banana[0] = "Cavendish" banana[1] = "Lakatan" banana[2] = "GrosMichel"上記は宣言と代入を別に記述
下記は宣言と代入を同時にkane.javaデータ型[] 配列変数名 = {要素1,要素2,...}; String[] banana = {"Cavendish", "Lakatan", "GrosMichel"}[]はデータ型の後くっつけて書く(int[],String[]のように)
要素の上書きは、配列変数名[インデックス番号] = "新しい要素"
各要素は、配列名[インデックス番号]で取得できる。
kane.javaString[] banana = {"Cavendish", "Lakatan", "GrosMichel"}; banana[1] = "LadyFinger" //要素の上書き System.out.println(banana[1]); //実行結果 LadyFinger多次元配列
エクセルの表計算ソフト的な
banana 0 1 0 Cavendish Lakatan 1 GrosMichel LadyFinger kane.javaデータ型[][] 配列変数名; 配列変数名 = new データ型[要素数][要素数]; String[][] banana; banana = new string[2][2]; banana[0][0] = "Cavendish" banana[0][1] = "Lakatan" banana[1][0] = "GrosMichel" banana[1][1] = "LadyFinger" System.out.println(banana[1][0]); //実行結果 GrosMichelこれも省略できる
kane.javaString[][] banana = {{"Cavendish", "Lakatan"},{"GrosMichel","LadyFinger"}}; System.out.println(banana[0][0]); //実行結果 Cavendish終わりに
多次元配列って便利なのはわかるが自分の頭が一次元だからパンクする