20200119のJavaに関する記事は5件です。

javaでAtCoder Beginner Contest 152を解く

AtCoder Beginner Contest 152お疲れ様でした!
公式ページ

今回の自分の書いたコードはこちら
結果はA-DまでAC、EがWAでした。

以下簡単に解説します。

問題A

引数のNとMを比較する問題。
同じ場合はYes、違う場合はNoを出力すればOK

問題B

aをb回繰り返した文字列とbをa回繰り返した文字列のどちらが辞書順ではやいかを出力する問題。
辞書順なので、aとbのうち小さいほうが先です。

aとbの小さい方をaとbの大きい方ぶん繰り返した文字列を出力できたらACです。

問題C

説明文はちょっと難しく書いてありますが、結局数列で最小値が何回更新されたかを答えればOKです。
4, 5, 2, 3, 1
上記だと太字になっている数字の数3が正解になります。

問題D

N以下の数字の中で、

  • Aの先頭の数字 = Bの末尾の数字
  • Bの先頭の数字 = Aの末尾の数字

のどちらも満たす数字の組み合わせが何通りあるかを答える問題です。
Nの数字の数的に二重ループしていると間に合わないと思ったので、

  • 最初にループして先頭の数字と末尾の数字のMapをつくる
  • 二回目のループで条件にマッチする数をとってきて足し合わせる

としました。計算量的に間に合わないから方針変えなきゃって気づけたのは個人的にうれしいポイントでした。

問題E

どのような i,j についても AiBi=AjBjが成り立つ。
つまり、最小公倍数を求めてAiで割ってBiを求める問題。

答えはさらにそれを足し合わせて出力しなさいとのこと。

方針を決める部分までは分かり、最近勉強した逆元使えばAiで割れるやん!というところまではよかったが、
大きい数の場合にうまく動かず。原因まで特定もできました。

最小公倍数を計算する際、大きい数になり1000000007で一度割ってしまうと最小公倍数を正しく求められなくなるというものでした。
色々考えた結果解消できず。。

解説にもある最小公倍数を素因数分解した形で保持するというのも考えたのですが、実力不足でコードにできませんでした。
(ちょっとまだどうすればいいかイメージも出来ていないので、平日仕事終わりにまた考えてみます。。)

問題F

問題Eがもうちょっとでできる気がしたので手を付けれていないです。。


レーティングは897→956。最高更新です!

D問題までは25分とサクサクできて、成長も感じれたのですが
E問題の逆元使うところまでは思いつけたのに、あと一歩及ばずでとても悔しいです。。

次こそは学びをきちんと形にしたいです・・・:sunny:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaで実行しているディレクトリの絶対パスを取得する方法

はじめに

Qiita初投稿です。

環境

java1.8.0_221

やりたいこと

C:\Users\Miyayu\IDEA\example\example.jarを実行しているとする。
C:\Users\Miyayu\IDEA\example(String型)を取り出したい。

コード

FileSystems.getDefault().getPath("").toAbsolutePath().toString()

これだけ!かんたんでしょ?

指定したファイルの絶対パスを知りたいときは

.\expdir\hoge.jsonの絶対パスを知りたい

FileSystems.getDefault().getPath("expdir","hoge.json").toAbsolutePath().toString()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】多重forループを一重にする方法

ネタです

はじめに

Javaに限らず,プログラムを書いていると何重ものforループを書かなければならない時が発生します.

例. int型三次元配列の全ての要素を1にしたい.

final int n = 10;
final int[][][] cube = new int[n][n][n];

for (int i = 0; i < n; i++) {
  for (int j = 0; j < n; j++) {
    for (int k = 0; k < n; k++) {
      cube[i][j][k] = 1;
    }
  }
}

しかし,このようなループはネストが深くなったり行数が無駄に増えるため,プログラムの保守性が悪化する原因となります.
そこで,多重forループを一重にする方法を考えます.

forループの構文

問題を解決するために,まずはforループの構文を確認して行きます.
Eclipse JDTによると,for文は以下のような構成になっています

for (
[ ForInit ];
[ Expression ] ;
[ ForUpdate ] )
Statement

また,ForInitとForUpdateは以下のように定義されています.

ForInit:
Expression { , Expression }
ForUpdate:
Expression { , Expression }

ForInitForUpdateは複数の式を指定できるので,ForInitに多重forループで必要となるそれぞれの変数の宣言をForUpdateで変数の更新を書くことができそうです.

やってみた

それでは実際に一重のforループにしていきます.

初期化式

先ほどの例だと,必要な変数はi,j,kの三つなので,初期化式は

int i = 0, j = 0, k = 0

で大丈夫そうです.

条件式

次に(終了)条件式ですが,今回の場合はj,kの値に関係なく,一番外側のループを制御するiについて

i < n

と書くことができます.

更新式

最後に更新式について考えていきます.
ループの内側を制御する変数から順番(k -> j -> i)に考えていきます.
まずkについてですが,ループの度にインクリメントし,なおかつkの値がnになるタイミングで0になればよいので

k = (k + 1) % n

と書けます.

次にjについてですが,jの更新のタイミングはkが0になったタイミングであり,それ以外では値は変わらないため

j = k == 0 ? (j + 1) % n : j

となります.

最後にiですが,更新のタイミングはk == 0 && j == 0なので

i = j == 0 && k == 0 ? i + 1 : i

です.

結果

これまでの結果を統合すると以下のように書き換えられます.

final int n = 10;
final int[][][] cube = new int[n][n][n];

for (int i = 0, j = 0, k = 0; i < n; k = (k + 1) % n, j = k == 0 ? (j + 1) % n : j, i = j == 0 && k == 0 ? i + 1 : i) {
  cube[i][j][k] = 1;
}

最初の三重ループのコードに比べるとネストが減り,すっきりした印象を受けたのではないでしょうか?

おわりに

今回は多重forループを一重ループに変換する方法を紹介しました.
ぜひこれを参考にしてコードの可読性を高めてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのインライン展開についてちょっと実験してみる

JavaではJITコンパイラがインライン展開を行っているということは聞いたことがあるのですが、それによってどれだけの恩恵を受けているのか、ちょっと実験してみたくなりました。

そもそもインライン展開とは

詳細はWikipediaを参照してください。
要はメソッドの呼び出しにはオーバーヘッドがあるため、メソッド内の処理をメソッドの呼び出し側に埋め込んでしまえば、メソッド呼び出しのコストを抑えることができるということですね。JITコンパイラが必要に応じてこれを行っています。

実験

今回試してみるコードは以下の通りです。StringBuilderに文字列をappendするだけのメソッドを用意して、繰り返し呼び出してみました。
同じメソッドを何度も呼び出していますので、インライン展開される/されないによってパフォーマンスに差が出るのではないかと仮説を立ててみました。

InlineSample.java
public class InlineSample {

    public static void main(String[] args) {

        long start = System.currentTimeMillis();

        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 10_000_000; i++) {
            appendHoge(builder);
        }
        System.out.println(builder.length());

        long end = System.currentTimeMillis();
        System.out.println("elapsed " + (end - start) + " ms");
    }

    private static void appendHoge(StringBuilder builder) {
        builder.append("hoge");
    }
}

このコードをインライン化を有効/無効の両方のパターンで実行して性能を検証しました。
インライン化はデフォルトで有効になっているため、以下のVMパラメータを追加することによって無効化しました。

-XX:-Inline

なお、Windows10、Java15-eaの環境で試しています。

結果

5回試行して平均をとったところ、以下のような結果になりました。

インライン展開ON: 平均 125ms
インライン展開OFF: 平均 342ms

結構な差ですね。
ただ、JITコンパイルはプログラム実行中に行われますので、プログラムの実行開始時からインライン展開されたいたわけではありません(-XX:+PrintInlining オプションによりインライン展開のログを確認しました)。この点ではあまり公平な比較にはなっていません。
ベンチマークツールのJMHを使ったほうがよかったかなと思いますが、ともあれ、これだけの差がつくとは驚きです。

なお、インライン展開の対象となるメソッドのサイズは

-XX:MaxInlineSize=size

により、指定することができます。デフォルトは35バイトです。
インライン展開を意識したコードを書くことによって、よりJITコンパイラの恩恵を受けることができるかもしれませんね。

参考

https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/java.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macOS での Java のバージョン管理

ちょっと真の本題で書いたことを調べようと思って、いろいろ調べた結果の備忘録です。
でも、真の本題の方は、いまいち解決していないという。。

Java のバージョンについて

Java については、まず、言語そのものと、その動作環境である JVM があり、それらについて、公式の仕様の部分と、公式 (Oracle) やその他さまざまなところが出している実装の部分があります。

Oracle が出している Java の仕様は Java SE (Standard Edition) と呼ばれます。
アプリケーション実装に踏み込んだ仕様である Java EE (Enterprise Edition) (現在は Jakarta EE として移管) や組み込み用途での Java ME (Micro Edition) というのがあった影響で、 Java SE と名付けられていますが、完全に歴史的経緯ですね。

Oracle による Java 実装は、通称 Oracle JDK と呼ばれています。

また、準公式的な位置づけのオープンソース Java 実装として OpenJDK というのもあります。Oracle 買収前の Sun Microsystems が始めたもののようです。

そして、昨今 Java を取り巻く環境が大きく変わってきています。
他のプログラミング言語の進歩に取り残される形になった Java が変化に対応しようとする中での動きなのでしょう。

まず、短いスパンで、細かい機能修正を加えたバージョンアップが行われるようになりました。
2017-09 リリースの Java 9 以降、現在のところ、半年に1回、メジャーバージョンが更新されています。

そして Java 11 以降ですが、 Oracle のライセンス形態が変わり、公式版のいわゆる Oracle JDK 自体は利用が有償化されるようになりました。
それに変わって、今まで Oracle JDK に含まれていた機能で OpenJDK に入っていなかった部分も OpenJDK に移管され、オープンソース化されました。
OpenJDK 自体は無償で利用できますし、この流れを受けて、 OpenJDK を独自にビルドしたディストリビューションも各社・コミュニティが公開するようになりました。

つまり、今まで Java といえば、だいたい Java 6-8 あたりの Oracle JDK だけ使っていればよかったのが、各ベンダ別のさまざまなバージョンの "Java" が普及するようになっています。

References

macOS での Java のバージョン管理

本題です。主には SDKMAN! を使った方法と /usr/libexec/java_home を使った方法があります。

SDKMAN! を使った方法

SDKMAN! は、 JVM 系のバージョン管理ツールで、 Java だけではなくて、 JVM 系の言語 (Groovy, Scala など) やミドルウェアのバージョン管理までやってくれます。
もともとは GVM (Groovy enVironment Manager) だったらしい。

各バージョンの検索・インストールから、各 scope でのバージョン切り替えまでをやってくれます。詳細はここでは書きません。

内部的に JAVA_HOME を切り替えているようです。

/usr/libexec/java_home を使った方法

こちらの方法では、インストーラや Homebrew を使ってインストールした Java のバージョンの切り替えを行えます。

例えば Homebrew で以下のように AdoptOpenJDK 最新版をインストールします。
macOS Catalina では、以下のように --no-quarantine option が必要です。

$ brew cask install adoptopenjdk --no-quarantine

インストールした Java の一覧は、以下のコマンドで確認できます。 /Library/Java/JavaVirtualMachines/ 配下にあるものだけが表示されるようです。
ちなみに、このディレクトリはシステムベースのもので、 SDKMAN! はユーザベースでモジュールを管理することから、 SDKMAN! でインストールした Java はこのディレクトリには配置されず、したがってこのコマンドの結果には出てきません。
SDKMAN! のドキュメントにも、 /usr/libexec/java_home を使った方法との互換性はないと書かれています。

$ /usr/libexec/java_home -V

特定のバージョンの Java のパスを得たい場合は、小文字の -v オプションを利用する。

$ /usr/libexec/java_home -v 13.0

オプション無しで実行すれば、デフォルト (たぶん最新バージョン) の Java のパスが取得されます。

$ /usr/libexec/java_home

特定のバージョンに JAVA_HOME を切り替える場合は、以下のようにします。

$ export JAVA_HOME=`/usr/libexec/java_home -v 13.0`

一時的にデフォルト以外の Java バージョンを使って、実行することも可能です。

$ /usr/libexec/java_home -v 13.0 --exec java -version

お察しのとおりですが、 /usr/libexec/java_home は Java のバージョンを見るだけで、そのビルドを発行しているベンダまでは見てくれないので、複数ベンダの Java を入れた場合はうまく対処してくれないみたいです。
/Library/Java/JavaVirtualMachines/<JAVA>/Contents/Info.plist に書いてある JVMVersion のバージョンで比較をしているようですね。

ベンダも含めて管理したい場合は、 jEnv を使うのが良さそうです。

References

Homebrew で Java 1.8+ is required to install this formula みたいなエラーが出る場合

真の本題です。

SDKMAN! で入れた Java しか無い状態で、 Homebrew で bundletool をインストールしようとしたところ、以下のエラーが発生しました。

$ brew install bundletool
bundletool: Java 1.8+ is required to install this formula.
Install AdoptOpenJDK with Homebrew Cask:
  brew cask install adoptopenjdk
Error: An unsatisfied requirement failed this build.

bundletool の formula を見たところ、 depends_on :java => "1.8+" の指定があり、これを満たせていないようです。
SDKMAN! で入れた場合にも JAVA_HOME は入っているし、Homebrew のソースコード をちらっと見るには、 JAVA_HOME に適切な Java が入っていれば問題なさそうなんですが...
あまりじっくり見ていないので、何がダメなのかちょっと良くわかりませんでした。誰か分かる人教えてください><

とりあえず言われるがままに、 Homebrew で AdoptOpenJDK をインストールして、特に JAVA_HOME の切り替えなどはせずに再度 brew install bundletool を実行すると、うまくインストールできました。

$ brew cask install adoptopenjdk --no-quarantine

Homebrew でのインストール時に必要なだけで、後は SDKMAN! でインストールした Java でも動作しそうなので、すぐにアンインストールしても問題なさそうです。

$ brew cask uninstall adoptopenjdk

References

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む