- 投稿日:2020-02-13T18:31:12+09:00
JavaのpathとPackageに関するメモ
①前提
構成ディレクトリ
- stage11
- test10
- BondTest.java
- issues
- Position.java
- Bond.java
現在地
カレントディレクトリはtest10となっている.実行ファイルと呼び出すファイル
実行ファイルはBondTest.java
実行ファイルから呼び出すファイルはPosition.javaとBond.java②package名に関して
package名はソースファイルが入っているフォルダ名1つをつけてあげるBondTest.java
package test10;
Position.java
package issues;
Bond.java
package issues;③クラスパス
packageのルートディレクトリをつけることが肝であった.
今回はBondTest.javaはpackage test10であるからstage11を指定する必要がある.
また呼びだすファイルはpackage issuesであるから一つ上のtest10を指定する必要がある.
クラスパスは以上の2つある.④import
import文はBondTest.javaからPosition.javaとBond.javaを呼びだす際に必要になる.BondTest.javaのpackageがtest10であるから相対パスは,issues.~となる.よってBondTest.java
import issues.Position;
import issues.Bond;⑤javacとjava
いよいよjavacでコンパイルしてjavaで実行するわけである.
javac -cp ..:. BondTest.java
が正解である.
.. は実行ファイルのpackageのルートディレクトリである.
. はPosition.javaとBond.javaのpackage issuesのルートディレクトリ.
ファイル名はBondTest.javaであるのも意外と間違えてtest10.BondTest.javaとかやりたくなるが書かない.次にjavaコマンドで実行する.-cpはそのままであるからほぼ変わらないが,重要な点が一点あった.
javac -cp ..:. test10.BondTest.java
が正解なのである.javaコマンドでは,package名から書くことを忘れがちになるので注意が必要である.最後にまとめると,
ディレクトリが揃っていること
実行ファイルのディレクトリにいること
この前提のもとで,
package名のルートディレクトリをクラスパスに指定すること
package名をjavacではつけずjavaではつけることになることこのあたりが要点になる.ディレクトリ構成が異なる人はうまくいかないことがあるかもしれないので検索して自分の形に合ったディレクトリを見つけることが重要である.
- 投稿日:2020-02-13T16:33:41+09:00
GCSにアップロードされているCSVファイルを扱う
csvファイルを読み込む
blob.getContentでファイルの中身が取得できるが、ファイルサイズが大きい場合は困ってしまう
Bucket bucket = getBucket(bucketName); Storage.BlobListOption option = Storage.BlobListOption.prefix(directory); Page<Blob> blobs = bucket.list(option); Iterator<Blob> iterator = blobs.iterateAll().iterator(); List<String> csvList = new ArrayList<>(); while (iterator.hasNext()) { Blob blob = iterator.next(); byte[] content = blob.getContent(Blob.BlobSourceOption.generationMatch()); csvList.add(new String(content)); } for (String csv : csvList) { // process... }csvファイルを1行ずつ読み込む
getContentの代わりに、以下のようにすれば1行ずつ読みこんで処理できる
ReadChannel readChannel = blob.reader(); BufferedReader br = new BufferedReader(Channels.newReader(readChannel, "UTF-8")); String line; while((line = br.readLine()) != null){ // process... }
- 投稿日:2020-02-13T15:32:23+09:00
OpenCV+AndroidStudioで顔認識アプリ
やりたいこと
OpenCVを使った顔認識アプリの作成
・目標
・とりあえずAndroidStudioの導入、使い方を学ぶ(初心者)
・OpenCVをAndroid上で動かす
・カメラで撮影した画像、ギャラリーに保存されている画像のどちらも使用可能に参考
OpenCVを使って、簡単顔認識Androidアプリを作ってみた
https://qiita.com/Kuroakira/items/094ecb236da89949d702
インテントでカメラを呼び出す方法の補足(主に、Xperia 2.1問題対応)
https://gabu.hatenablog.com/entry/20101125/1290681748使ったもの
Android Studio 3.5.3
OpenCV 4.2.0大まかな流れ
実装についてはほぼ上記の先人たちを参考にしているのでざっと書きます。
カメラ、ギャラリーボタンを押下
↓
カメラ、ギャラリーから画像を取得
↓
取得した画像をbitmap→Mat形式に変換
↓
OpenCVへMat形式の画像を渡す
↓
顔認識情報を取得し、画面へ表示結果
それっぽい座標情報が出てきました。
今後も使えそうなこと
・カメラ
カメラで撮影した画像を高解像度で取得したい場合、いったんURLへ書き出し、URLから画像を取得する。
getImg = (Bitmap) data.getExtras().get("data");ではサムネイル用の低解像度画像しか取得できない。
(画像認識用の画像はgetImg = (Bitmap) data.getExtras().get("data");で取得しているが、人数が増えたり、引きの画だと不都合がでそう)MainActivity.javagetImg = (Bitmap) data.getExtras().get("data");MainActivity.java/**略**/ imageView.setImageURI(mImageUri); /**略**/ /** * 写真撮影用 * 撮影した写真をいったんURLに書き出して、書き出し先のURLをmImageUriに格納 */ protected void takePhoto(){ String filename = System.currentTimeMillis() + ".jpg"; ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.TITLE,filename); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); mImageUri = getContentResolver().insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values ); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); startActivityForResult(intent, RESULT_CAMERA); }今後、実現したいこと
・顔認識部分だけクラス分割
当初、クラス分割しての実装を試みたが、以下の部分がクラス分割した場合の方法がわからず断念。
(別クラスからでは、MainActivity.javaと同様の記述ではのres/rawへアクセスができない?)MainActivity.javaFile cascadeDir = getDir("cscade", Context.MODE_PRIVATE); File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");・ImageViewから画像取得
ImageVIewから画像取得することでカメラ、ギャラリーの場合、それぞれの場合での実装が不要になるためコードを削減できるのではないか・Kotlinでの実装
とりあえず慣れてみるためだったので書きなれたJavaで実装を行ったが今後Android向け開発を行う場合はKotlinを使用したいソースコード
MainActivity.javapackage com.example.myapplication; import androidx.appcompat.app.AppCompatActivity; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Paint; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import org.opencv.android.Utils; import org.opencv.core.Mat; import org.opencv.core.MatOfRect; import org.opencv.core.Rect; import org.opencv.objdetect.CascadeClassifier; public class MainActivity extends AppCompatActivity { private final static int RESULT_CAMERA = 1001; // カメラ用 private final static int REQUEST_GALLERY = 1000; // ギャラリー用 private Uri mImageUri; // 画像のURLを格納するインスタンス変数 private Bitmap getImg = null; // カメラ、またはギャラリーから取得する画像 private ImageView imageView;//イメージビューの宣言文 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = findViewById(R.id.image_view);//先にImageViewをレイアウトビューのIDと紐づけ Button cameraButton = findViewById(R.id.camera_button); cameraButton.setOnClickListener(new View.OnClickListener() {//普通のインナークラスを使っての実装 @Override public void onClick(View v) { takePhoto(); } }); Button galleyButton = findViewById(R.id.gallery_button); galleyButton.setOnClickListener(new View.OnClickListener() {//普通のインナークラスを使っての実装 @Override public void onClick(View v) { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(intent, REQUEST_GALLERY); } }); } //これからImageViewにとった写真を張り付け。 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == RESULT_CAMERA || requestCode == REQUEST_GALLERY) { TextView textView = findViewById(R.id.text1); // テキストビュー(表示するテキストを格納するビュー) String outputText = ""; // 表示するテキスト MatOfRect faceRects; // 顔認識データを格納する // カメラからの画像選択の場合 if (requestCode == RESULT_CAMERA) { // cancelしたケースも含む if (data.getExtras() == null) { Log.d("debug", "cancel ?"); return; } else { imageView.setImageURI(mImageUri); getImg = (Bitmap) data.getExtras().get("data"); } } // ギャラリーからの画像選択の場合 if (requestCode == REQUEST_GALLERY && resultCode == RESULT_OK) { try { InputStream in = getContentResolver().openInputStream(data.getData()); getImg = BitmapFactory.decodeStream(in); in.close(); // 選択した画像を表示する imageView.setImageBitmap(getImg); } catch (Exception e) { // ファイルが無かった時 textView.setText("ファイルが見つかりません"); } } // imageViewに張り付けられた画像(カメラ、ギャラリーから取得)から顔認識 try { faceRects = checkFaceExistence(getImg); outputText = makeOutputText(faceRects);\ // 選択した画像を表示する imageView.setImageBitmap(getImg); } catch (IOException e) { outputText = "顔認識エラー"; e.printStackTrace(); } Toast.makeText(this,outputText,Toast.LENGTH_LONG).show(); textView.setText(outputText); } } /** * 写真撮影用 * 撮影した写真をいったんURLに書き出して、書き出し先のURLをmImageUriに格納 */ protected void takePhoto(){ String filename = System.currentTimeMillis() + ".jpg"; ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.TITLE,filename); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); mImageUri = getContentResolver().insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values ); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); startActivityForResult(intent, RESULT_CAMERA); } /** * 渡された画像を元に顔認識を行う * @param bitmap 顔認識対象の画像 * @return text 顔認識情報 * @throws IOException */ protected MatOfRect checkFaceExistence(Bitmap bitmap) throws IOException{ System.loadLibrary("opencv_java4"); // 送られた画像データ(bitmap)をMat形式に変換 Mat matImg = new Mat(); Utils.bitmapToMat(bitmap, matImg); String text = ""; // 顔認識情報を格納するtext // 顔認識を行うカスケード分類器インスタンスの生成(一度ファイルを書き出してファイルパスを取得) // 一度raw配下に格納されたxmlファイルを取得 InputStream inStream = getResources().openRawResource(R.raw.haarcascade_frontalface_alt); MatOfRect faceRects = new MatOfRect(); // 顔認識データを格納する try { // 出力したxmlファイルのパスをCascadeClassfleの引数に CascadeClassifier faceDetetcor = outputCascadeFile(inStream); // カスケード分類器に画像データを与えて、顔認識 faceDetetcor.detectMultiScale(matImg, faceRects); } catch (IOException e){ Toast.makeText(this,"解析情報ファイルオープンに失敗しました。",Toast.LENGTH_SHORT).show(); } return faceRects; } /** * あらかじめ用意されたopenCV分類器を一度取り込んで、書き出し使用可能にする。 * @param inStream 分類器の元データ * @return faceDetector 分類器データ * @throws IOException */ protected CascadeClassifier outputCascadeFile(InputStream inStream) throws IOException { File cascadeDir = getDir("cscade", Context.MODE_PRIVATE); File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml"); // 取得したxmlファイルを特定ディレクトリに出力 FileOutputStream outputStream = new FileOutputStream(cascadeFile); byte[] buf = new byte[2048]; int rdBytes; while ((rdBytes = inStream.read(buf)) != -1) { try { outputStream.write(buf, 0, rdBytes); } catch (IOException e) { e.printStackTrace(); } } // 出力したxmlファイルのパスをCascadeClassfleの引数に CascadeClassifier faceDetetcor = new CascadeClassifier((cascadeFile.getAbsolutePath())); outputStream.close(); return faceDetetcor; } /** * 出力用テキスト作成 * 顔認識情報を元に座標情報をtextにする * @param faceRects * @return */ protected String makeOutputText(MatOfRect faceRects){ String text = ""; // 顔認識結果をStringでreturn if(faceRects.toArray().length > 0){ for(Rect face: faceRects.toArray()) { text = "顔の縦幅:" + face.height + "\n" + "顔の横幅" + face.width + "\n" + "顔の位置(Y座標)" + face.y + "\n" + "顔の位置(X座標)" + face.x; } } else{ text = "顔が検出されませんでした。"; } return text; }
- 投稿日:2020-02-13T15:23:38+09:00
java バブルソート
ソート
今回からソートについて投稿していこうと思います。
※自分のアウトプット用の記事です。間違いなどがあったら指摘してください。バブルソート
BubbleSort.javapublic class BubbleSort { public static void sort(int[] array) { for(int i=0;i<array.length;i++) { for (int j=0;j<array.length-1;j++) { if(array[j]>array[j+1]) { int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } } public static void main(String args[]) { int[] array = {5,4,3,2,1}; sort(array); for(int i=0;i<array.length;i++) { System.out.print(array[i]); } } }今回はバブルソートを試してみました。次回は選択ソートを投稿しようと思います。
- 投稿日:2020-02-13T14:26:49+09:00
mutable(可変な)オブジェクトとimmutable(不変な)オブジェクト
Java Silver SE11の学習をしていて、「Stringオブジェクトは不変なオブジェクトである」の意味がよくわからなかったので、覚書ついでに。
mutableとimmutableの違い
そもそもmutable(可変)とimmutable(不変)の違いとはなんぞやから。
簡単に言うと、一度セットした値を後から変更できるオブジェクトがmutableオブジェクト。
一度セットした値を後から変更できないオブジェクトがimmutableオブジェクト。そのままですね。immutableオブジェクトの定義方法は下記らしいです。
- すべてのフィールドをprivateで修飾する
- オブジェクト内部の状態を変更可能なメソッド(setterなど)を提供しない
- クラスをfinalで宣言し、メソッドがオーバーライドされないことを保証する(サブクラスから変更させない)
- 内部に可変オブジェクトを保持している場合、そのオブジェクトを外部に提供(getterなど)しない
次項より、immutableなクラス:Hogeを作成することでそれぞれの詳細を確認します。
(その他のクラスも記載しますが、これらはimmutableでないものとします)すべてのフィールドをprivateで修飾する
これはまぁ、わかりやすいですよね。publicで修飾してたら変え放題ですものね。
Test.javapublic class Test { public static void main(String args[]){ Hoge hoge = new Hoge("ダイゴウジ", "ガイ" ,18); } } class Hoge{ private String name; private int age; private Foo foo; Hoge(){} Hoge(String lastName, String firstName, int age){ foo = new Foo(lastName, firstName); this.name = lastName + firstName; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Foo getFoo() { return foo; } public void setFoo(Foo foo) { this.foo = foo; } public void dummy(){} } class Foo{ private String lastName; private String firstName; Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } }クラスHogeのフィールド、name、age、fooはアクセス修飾子がprivateになっています。はい。
オブジェクト内部の状態を変更可能なメソッド(setterなど)を提供しない
これもわかりやすいですね。せっかくprivateで修飾しても、メソッド経由で変更可能だったら何も意味ないです。
Test.javapublic class Test { public static void main(String args[]){ Hoge hoge = new Hoge("ダイゴウジ", "ガイ" ,18); } } class Hoge{ private String name; private int age; private Foo foo; Hoge(){} Hoge(String lastName, String firstName, int age){ foo = new Foo(lastName, firstName); this.name = lastName + firstName; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public Foo getFoo() { return foo; } public void dummy(){} } class Foo{ private String lastName; private String firstName; Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } }メソッド経由での変更ができなくなりました。
クラスをfinalで宣言し、メソッドがオーバーライドされないことを保証する(サブクラスから変更させない)
クラス:Hogeを継承したHogeSubを作ってみます。
Test.javapublic class Test { public static void main(String args[]){ HogeSub hogesub = new HogeSub("ダイゴウジ", "ガイ", 18); System.out.println("lastName:" + hogesub.getFoo().getLastName() + " firstName:" + hogesub.getFoo().getFirstName()); System.out.println("-----------------"); hogesub.setLastName("山田"); hogesub.setFirstName("二郎"); hogesub.dummy(); System.out.println("lastName:" + hogesub.getFoo().getLastName() + " firstName:" + hogesub.getFoo().getFirstName()); } } class Hoge{ private String name; private int age; private Foo foo; Hoge(){} Hoge(String lastName, String firstName, int age){ foo = new Foo(lastName, firstName); this.name = lastName + firstName; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public Foo getFoo() { return foo; } public void dummy(){} } class HogeSub extends Hoge { private String lastName; private String firstName; HogeSub(String lastName, String firstName, int age) { super(lastName, firstName, age); this.lastName = lastName; this.firstName = firstName; } @Override public void dummy() { super.getFoo().setFirstName(this.firstName); super.getFoo().setLastName(this.lastName); } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } } class Foo{ private String lastName; private String firstName; Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } }実行結果は次の通りです。
実行結果lastName:ダイゴウジ firstName:ガイ ----------------- lastName:山田 firstName:二郎HogeSubは、Hogeが持っているdummyメソッドをオーバーライドし、fooのフィールドを変更しています。
こういった実装を回避するため、Hogeをfinalで宣言します。Test.javapublic class Test { public static void main(String args[]){ Hoge hoge = new Hoge("ダイゴウジ", "ガイ", 18); } } final class Hoge{ private String name; private int age; private Foo foo; Hoge(){} Hoge(String lastName, String firstName, int age){ foo = new Foo(lastName, firstName); this.name = lastName + firstName; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public Foo getFoo() { return foo; } public void dummy(){} } class Foo{ private String lastName; private String firstName; Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } }オーバーライドによるフィールドの変更ができなくなりました。
内部に可変オブジェクトを保持している場合、そのオブジェクトを外部に提供(getterなど)しない
前段は、フィールド:fooのgetterを経由し、オブジェクトの値を変更していました。
Hoge.getFoo().setXXXName(”xxx”);
このような操作をされてしまうと、不変とは言えませんね。getterも消します。Test.javapublic class Test { public static void main(String args[]){ Hoge hoge = new Hoge("ダイゴウジ", "ガイ", 18); } } final class Hoge{ private String name; private int age; private Foo foo; Hoge(){} Hoge(String lastName, String firstName, int age){ foo = new Foo(lastName, firstName); this.name = lastName + firstName; this.age = age; } public void dummy(){} } class Foo{ private String lastName; private String firstName; Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } }これで、immutableなクラス:Hogeが完成しました。
完成したHogeは実質何もできませんが、そこはサンプルということでお許しください…。せやかてString型変数の内容変えられるやん
※この項は推測です。誤りがあればご指摘ください。
Test2.javapublic class Test2 { public static void main(String args[]){ String str = "すとりんぐ1"; System.out.println(str); System.out.println("-----------------"); str = "すとりんぐ2"; System.out.println(str); } }実行結果すとりんぐ1 ----------------- すとりんぐ2確かにstrの内容は変わっています。
注意が必要なのは、変数strの値(参照先)が変わっているだけであって、インスタンスの値は変わっていないという点です。デバッガで追ってみます。以下のようにブレークポイントを設定します。
strの参照先が変わっていることが確認できます。
Stringクラスは特殊で、new演算子を使わずとも、代入演算子でインスタンスの生成が可能です。
(str = "すとりんぐ2";
とstr = new String("すとりんぐ2");
はどちらも同じ処理ということ)
つまり、String型変数に代入演算子で文字列を設定するということは、都度インスタンスを生成していることと同義です。
(厳密には、本当に都度都度生成しているとは限りませんが)使い方によっては、JVM上のメモリをどんどん食いつぶすことになりかねません。
こういった問題を解消するために、SringBuilderクラスが作られた…のだと、推測します。最後に
やはりといいますか、(推測交じりですが)アウトプットすることで理解が深まると感じました。
Silver/SE11の取得を目指して引き続き頑張ります。
- 投稿日:2020-02-13T14:09:45+09:00
Java 14新機能まとめ
Java 14もRCフェーズに入りました。
うまくいけば2020/3/17にリリースされます。
https://jdk.java.net/14/JEP
大きめの変更はJEPでまとまっています。
https://openjdk.java.net/projects/jdk/14/今回は、16ものJEPが取り込まれました。影響が大きいものも多いです。
305: Pattern Matching for instanceof (Preview)
343: Packaging Tool (Incubator)
345: NUMA-Aware Memory Allocation for G1
349: JFR Event Streaming
352: Non-Volatile Mapped Byte Buffers
358: Helpful NullPointerExceptions
359: Records (Preview)
361: Switch Expressions (Standard)
362: Deprecate the Solaris and SPARC Ports
363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
364: ZGC on macOS
365: ZGC on Windows
366: Deprecate the ParallelScavenge + SerialOld GC Combination
367: Remove the Pack200 Tools and API
368: Text Blocks (Second Preview)
370: Foreign-Memory Access API (Incubator)分野ごとにまとめていきます。
言語仕様
言語仕様にかかわる変更としては次のようなものがあります。
359: Records (Preview)
305: Pattern Matching for instanceof (Preview)
368: Text Blocks (Second Preview)
361: Switch Expressions (Standard)359: Records (Preview)
データ保持用のクラスとしてrecordがpreview機能として入りました。
Java 15でフィードバックを反映した改善版がpreviewされてJava 16で正式化という流れになりそうです。recordとして定義します。
record Foo(int x, int y) {}他のクラスは継承できません。
record レコード名(状態) { 定義 }これは次のようなクラスになります。
class Foo extends Record { // 状態がprivate finalとして定義 private final int x; private final int y; // 追加のインスタンスフィールドは定義できない // 状態をフィールドに設定するコンストラクタが定義される public Foo(int x, int y) { this.x = x; this.y = y; } // 状態と同じ名前のメソッドが定義される public int x() { return x; } public int y() { return y; } // 状態を反映するhashCodeが定義される public int hashCode() { ... } // 状態を比較するequals が定義される public boolean equals() { ... } // 状態を表示するtoStringが定義される public String toString() { ... } }取得用メソッドは定義されますが、値設定用のメソッドは定義されません。つまり、イミュータブルなオブジェクトとなります。また、get/setではないことからJava Beanとの互換性もありません。
hashCode()
メソッドやequals()
メソッドなどは実際にはinvokeDynamicで実行時に実装コードが生成されます。次のように、状態の検査や正規化にコンストラクタが定義できます。
record Range(int lo, int hi) { public Range { if (lo > hi) throw IllegalArgumentException(); // 設定されなかったフィールドはあとで設定される } }APIの拡張
recordは
java.lang.Record
を継承したクラスになります。次のようにRecordを継承するコードを書こうとすると
records cannot directly extend java.lang.Record
というエラーになります。class Foo extends Record { }staticではないinner classの中でrecordを定義することはできません。
次のようなコードをコンパイルするとrecord declarations not allowed in inner classes
というエラーになります。public class NestedRecord { class Foo { record Bar(int x){} } }次のようなコードはコンパイルできます。
public class NestedRecord { static class Foo { record Bar(int x){} } }
Class
クラスにはレコード関連のメソッドが追加されています。
isRecord
メソッドで型がrecordかどうか判定できます。またgetRecordComponents
でrecordで定義されたコンポーネントを取得することができます。ただし、値の取得はRecordComponent経由では行えないので、Field経由で行うようです。jshell> Foo.class.isRecord() $9 ==> true jshell> Foo.class.getRecordComponents() $10 ==> RecordComponent[2] { int x, int y } jshell> String.class.isRecord() $11 ==> false jshell> String.class.getRecordComponents() $12 ==> null型名としての
record
の制限
record
という名前を型(クラス・インタフェース・レコード型など)につけることは制限されています。
--enable-preview
を付けた状態ではエラーになります。$ jshell --enable-preview | JShellへようこそ -- バージョン14-ea | 概要については、次を入力してください: /help intro jshell> class record{} | エラー: | ここでは'record'は許可されません | リリース13から'record'は制限された型名であり、型の宣言に使用できません | class record{} | ^ jshell> String record="" record ==> "" jshell> record Rec(int record){} jshell> Rec record=new Rec(3) record ==> Rec[record=3]enumと違いキーワードではないので、変数名やフィールド、recordのコンポーネント名にはrecordを使えます。
--enable-preview
を付けない状態では、警告が表示されます。$ jshell | JShellへようこそ -- バージョン14-ea | 概要については、次を入力してください: /help intro jshell> class record{} | 警告: | 'record'は将来のリリースで制限された型名になる可能性があり、型の宣言での使用、または配列の要素タイプとしての使用はできなくなる可能性があります | class record{} | ^ | 次を作成しました: クラス record今後の改善
当初はSealed Typesと同じJEPでしたが分離されました。
クラスの継承を限られたクラスに制限できる機能です。Java 15でpreviewに入ると思われます。
JEP 360: Sealed Types (Preview)Preview 2での改善点はこちらで提案されています。
https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-January/001913.html
java.util.Record
はクラスですが、Valhallaのinline classはinterfaceしか継承できないので、これをinterfaceにしたほうがいいかどうか。- 必須メンバーへの可視性(private recordのメンバがpublicなのはおかしい?)
- 現状ではstaticではないinner classではrecordを入れ子にすることができないけど対応したい
- abstract record
- パターンマッチでのdeconstructionへの対応
- recordコンポーネントへの
@Deprecated
305: Pattern Matching for instanceof (Preview)
パターンマッチングです。
まずはinstanceofを使ったパターンマッチが14にプレビューとして入ります。
http://openjdk.java.net/jeps/305
値 instanceof パターン
で、値をマッチさせることができます。
パターンは、定数か変数定義です。変数定義の場合には、型が一致していた場合にtrueになりその変数に値が割り当てられます。if (x instanceof Integer i) { // can use i here }switchで使えるようになれば便利ですが、これは別のJEPで定義されていて、Java 15に持ち越されています。
JEP draft: Pattern matching for switch (Preview)また、recordを分解する機能(deconstruction)やその入れ子が導入されてPreview 2としてJava 15に入るようです。
http://openjdk.java.net/jeps/8235186妥当なスケジュールとしては
- 14 Pattern Matching for instanceof(Preview)
- 15 Pattern Matching for instanceof(Preview 2)
Pattern matching for a switch (Preview)- 16 Pattern Matching for instanceof(Standard)
Pattern matching for a switch (Preview 2)- 17LTS Pattern matching for a switch(Standard)
となるか
- 14 Pattern Matching for instanceof(Preview)
- 15 Pattern Matching for instanceof(Preview 2)
Pattern matching for a switch (Preview)- 16 Pattern Matching for instanceof(Preview 3)
Pattern matching for a switch (Preview 2)- 17LTS Pattern Matching for instanceof(Standard)
Pattern matching for a switch(Standard)となるかですが、17LTSにはパターンマッチングの機能がひととおり標準機能として入る目途がついてきました。
368: Text Blocks (Second Preview)
改行などを含んだ文字列を定義できます。
"""
で囲みます。
JDK13にPreviewとして入ったものにJDK14では改行のエスケープなど少し仕様変更が入りました。JDK15でstandardになる予定。// same as "You can write\ntwo line string.\n" var str = """ You can write two line string. """;開始の
"""
のあとには文字列を続けれません。また、インデントは"""
や内部の文字列で一番浅いところが基準になります。var str = """ ..You can write ..two line string. """;var str = """ .. You can write .. two line string. """;改行をエスケープすることもできます。
var str = """ You can write \ two line string, \ but this is single. """;これは
"You can write two line string, but this is single."
になります。行末のスペースは削除されます。
そこで、行末にスペースが必要なときは\s
を入れてスペースが必要なことを示します。var str = """ test\s test \s """;これは
"test_\ntest__\n"
になります。(Qiitaでは複数スペースをいれてもスペースひとつになってしまう)文字列への変数の埋め込みはできません。その代わりに
formatted
メソッドがインスタンスメソッドとして用意されて、次のように書けるようになりました。var str = """ こんにちは、%sさん。 今日はいい天気ですね。 """.formatted("きしだ");361: Switch Expressions (Standard)
いままでステートメントであった
switch
を式として使えるようになります。Java 12でプレビューとして導入され、Java 13で仕様変更、そしてJava 14で正式機能として導入されます。
https://openjdk.java.net/jeps/361
switch
はステートメントでしたが、多くのswitchで同一の変数に値を割り当てたりすべてのcaseでreturnしたりといった使いかたがされていたため、効率的に書けるように式としても使えるようになります。つまり、こう。
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; };
yield
で値を返すということもできます。int result = switch (s) { case "Foo": yield 1; case "Bar": yield 2; default: System.out.println("Neither Foo nor Bar, hmmm..."); yield 3; }基本的な形は
yield
で値を返すものです。case LABEL: yield expression;そのシンタックスシュガーとしてラムダっぽく書けるということのようです。
case LABEL -> expression;また、
case
に複数の値を指定できるようにもなります。switch (day) { case MONDAY, FRIDAY, SUNDAY: numLetters = 6; break; ... };このような
case
の拡張は、既存のswitch
ステートメントでも有効です。JVM
JVMの挙動変更としては、
NullPointerException
での詳細メッセージがあります。
358: Helpful NullPointerExceptions358: Helpful NullPointerExceptions
Javaプログラマのみなさんは
NullPointerException
が大好きだと思います。
しかし、メッセージがそっけないという悩みがありました。
Java 14では、NullPointerException
について詳細なメッセージを表示できるようになりました。例えば次のようなコードがあるとします。
public class Sample { static String s; public static void main(String... args) { s.length(); } }Java 14でも普通に実行すると以前のバージョンと同様に特別なメッセージは表示されません。
$ java Sample.java Exception in thread "main" java.lang.NullPointerException at Sample.main(mysample.java:4)
-XX:+ShowCodeDetailsInExceptionMessages
オプションをつけて実行すると次のように何に対して何を呼び出したときにエラーになったかということが表示されます。$ java -XX:+ShowCodeDetailsInExceptionMessages Sample.java Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "Sample.s" is null at Sample.main(mysample.java:4)このメッセージ構築はメッセージ取得時に行われるので、パフォーマンスへの影響はほとんどないようです。
JShellの場合は
-R-XX:+ShowCodeDetailsInExceptionMessages
をつけます。$ jshell -R-XX:+ShowCodeDetailsInExceptionMessages | JShellへようこそ -- バージョン14-ea | 概要については、次を入力してください: /help intro jshell> String[] strs = {null} strs ==> String[1] { null } jshell> strs[0].toUpperCase() | 例外java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because "REPL.$JShell$11.strs[0]" is null | at (#2:1)API
APIに関する変更は次の3つのJEPです。
349: JFR Event Streaming
370: Foreign-Memory Access API (Incubator)
352: Non-Volatile Mapped Byte Buffers
この他にCompactNumberFormat
での複数形対応とStrictMathでの便利メソッドの追加がありました。
また、あとでツールの項で取り上げるPackagingツール関連のAPIがIncubatorとして追加されたのと、Pac200関連のAPIが削除されています。349: JFR Event Streaming
Flight Recorderはメトリクス収集ツールで、障害発生時の解析に役立ちますが、ダンプファイルの解析が必要で、モニタリング用途には不便でした。
JFR Event Streamingでは、イベント登録ができるなど、モニタリングに使いやすい機能が追加されています。JEPのサンプルではイベントの登録の例が載っていますが、これにGCログの出力をつけくわえてみました。
import java.io.IOException; import java.time.Duration; import jdk.jfr.consumer.RecordingStream; public class JFRStreamTest { public static void main(String[] args) throws IOException { try (var rs = new RecordingStream()) { rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1)); rs.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(10)); rs.onEvent("jdk.CPULoad", event -> { System.out.println(event.getFloat("machineTotal")); }); rs.onEvent("jdk.JavaMonitorEnter", event -> { System.out.println(event.getClass("monitorClass")); }); rs.onEvent("jdk.GarbageCollection", System.out::println); rs.start(); } } }
-XX:StartFilghtRecording
をつけて実行すると、Flight Recorderが起動して、定期的にメトリクスが表示されます。$ java -XX:StartFlightRecording JFRStreamTest.java Started recording 1. No limit specified, using maxsize=250MB as default. Use jcmd 79660 JFR.dump name=1 filename=FILEPATH to copy recording data to file. [1.715s][warning][os] Unable to resolve PDH index: (230) [1.716s][warning][os] Please check the registry if this performance object/counter is disabled { classLoader = null name = "jdk/jfr/internal/PlatformRecorder" package = { name = "jdk/jfr/internal" module = { name = "jdk.jfr" version = "14-ea" location = "jrt:/jdk.jfr" classLoader = null } exported = true } modifiers = 49 } jdk.GarbageCollection { startTime = 13:51:48.973 duration = 12.5 ms gcId = 1 name = "G1New" cause = "G1 Evacuation Pause" sumOfPauses = 12.5 ms longestPause = 12.5 ms }JDK Mission Control(JMC)で見ることができます。
JFRを動かしておくと、イベントブラウザにイベントが記録されているのがわかります。
370: Foreign-Memory Access API (Incubator)
ヒープ外のメモリをアクセスする方法としては、ByteBufferを使う方法やUnsafeを使う方法、JNIを使う方法がありますが、それぞれ一長一短があります。
ByteBufferでdirect bufferを使う場合、intで扱える範囲の2GBまでに制限されたり、メモリの解放がGCに依存したりします。
Unsafeの場合は、性能もいいのですが、名前が示すとおり安全ではなく、解放済みのメモリにアクセスすればJVMがクラッシュします。
JNIを使うとCコードを書く必要があり、性能もよくないです。ということで、ヒープ外のメモリを直接扱うAPIが導入されたわけです。
次のようなコードになります。VarHandle intHandle = MemoryHandles.varHandle(int.class); try (MemorySegment segment = MemorySegment.allocateNative(100)) { MemoryAddress base = segment.baseAddress(); for (int i = 0 ; i < 25 ; i++) { intHandle.set(base.offset(i * 4), i); } }352: Non-Volatile Mapped Byte Buffers
ByteBufferを不揮発メモリに対応します。
jdk.nio.mapmode
といモジュールが導入されて、同名のパッケージにExtendedMapMode
クラスが用意され、新しいMapModeとして
READ_ONLY_SYNC
READ_WRITE_SYNC
が追加されます。
var fc = FileChannel.open(path); fc.map(ExtendedMapMode.READ_WRITE_SYNC, 0, 1024);などとする感じです。デバイスが不揮発メモリではない場合はUnsupportedOprationExceptionが投げられます。
また、BufferPoolMXBeanに永続MappedByteBufferの統計情報が
mapped - 'non-volatile memory'
という名前で取れるようになるようです。CompactNumberFormatの複数形対応
JEPになってない変更のひとつ。
ドイツ語とかイタリア語の場合に100万と200万でMillionかMillionenみたいな変形があって、それに対応したということらしい。jshell> import java.text.* jshell> var cnf = CompactNumberFormat.getCompactNumberInstance(Locale.GERMAN, NumberFormat.Style.LONG) cnf ==> java.text.CompactNumberFormat@5088aaba jshell> cnf.format(1_000_000) $3 ==> "1 Million" jshell> cnf.format(2_000_000) $4 ==> "2 Millionen"UnicodeのLanguage Plural Rulesに従って独自のルールを設定することができるコンストラクタも用意されています。
StrictMathへの便利メソッド追加
StrictMathに便利メソッドが追加されています。メソッド参照が使いやすくなることを狙ったんでしょうか。
- incrementExact(int)
- incrementExact(long)
- decrementExact(int)
- decrementExact(long)
- negateExact(int)
- negateExact(long)
インクリメント、デクリメント、符号反転ですが、オーバーフローしたときに
エラーがでます。jshell> StrictMath.incrementExact(3) $5 ==> 4 jshell> StrictMath.incrementExact(Integer.MAX_VALUE) | 例外java.lang.ArithmeticException: integer overflow | at Math.incrementExact (Math.java:968) | at StrictMath.incrementExact (StrictMath.java:849) | at (#4:1) jshell> StrictMath.negateExact(Integer.MAX_VALUE) $6 ==> -2147483647 jshell> StrictMath.negateExact(Integer.MIN_VALUE) | 例外java.lang.ArithmeticException: integer overflow | at Math.negateExact (Math.java:1044) | at StrictMath.negateExact (StrictMath.java:909) | at (#7:1)通常の演算子ではオーバーフローがおきます。
jshell> Integer.MAX_VALUE+1 $8 ==> -2147483648 jshell> Integer.MIN_VALUE $9 ==> -2147483648 jshell> -Integer.MIN_VALUE $10 ==> -2147483648ツール
ツールの変更としては、jpackageの追加が大きいですね。
343: Packaging Tool (Incubator)
367: Remove the Pack200 Tools and API343: Packaging Tool (Incubator)
Javaアプリケーションのインストーラを作るツールが入りました。
関連APIも追加されていますが、Incubatorモジュールになっています。
Windowsではlight.exeとcandle.exeが必要なのでhttps://wixtoolset.org からダウンロードしてPATHに追加する必要があります。試しに次のアプリケーションのインストーラを作ってみます。
import javax.swing.*; public class App { public static void main(String[] args) { var f = new JFrame("My App"); var t = new JTextArea(); f.add(t); var b = new JButton("Hello"); b.addActionListener(al -> t.append("Hello!\n")); f.add("North", b); f.setSize(500, 400); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } }アプリケーションはJarファイルになっている必要があります。
$ javac App.java $ mkdir target $ jar cf target/app.jar App.class $ jpackage --name myapp --input target --main-jar app.jar --main-class AppそうするとWindowsの場合はmyapp.exeという名前で45MBのインストーラが作成されます。
モジュラーJARの場合は自動的に必要なモジュールのみのJavaランタイムがインストールされるようになりますが、ここではモジュール対応しないJarファイルを作ったので、jlinkを使って最低限のJavaランタイムを作ると小さなインストーラが作れるようになります。$ jdeps App.class java.base java.desktop $ jlink --add-modules java.base,java.desktop --output jre $ jpackage --name myapp --input target --main-jar app.jar --main-class App --runtime-image jreそうすると27MBまでインストーラのサイズが削減されました。
インストール後のアプリケーションサイズも124MBから74MBに減ります。
Windowsの場合は--win-menu
をつけてWindowsメニューにアプリが追加されるようにするほうがいいでしょう。367: Remove the Pack200 Tools and API
Pack200はJarファイルを効率的に圧縮するツールでしたが、主なユースケースであるAppletが使われなくなり、役目を終わったということで削除されます。
java.util.jar
配下の関連APIも削除されます。GC
GCに関する変更のJEPは5つありました。
363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
364: ZGC on macOS
365: ZGC on Windows
366: Deprecate the ParallelScavenge + SerialOld GC Combination
345: NUMA-Aware Memory Allocation for G1363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
Concurrent Mark & Sweep GCがソースコードから削除されます。
Java 9のJEP 291でDeprecatedになっていましたが、メンテナンスを引き継ぐような人も現れなかったので削除するということです。
そうすることで、GCのメンテナンスコストが下がり、他のGCの開発が速くなることが期待されています。364: ZGC on macOS / 365: ZGC on Windows
いままではLinuxしかサポートしてなかったZGCがmacOSとWindowsをサポートするようになりました。
当初は、ZGCは本番のLinuxサーバーでだけ使われて、macOSやWindowsは開発用途に使うだけだという想定でしたが、IDEをZGCで動かしたいなどの要望もあったことからmacOSやWindowsもサポートすることになったようです。
WindowsはWindows 10 ver 1803以降に対応しています。
ZGCは仮想アドレスを使って物理メモリを複数のアドレスでアクセスする仕組みを使っていますが、Windowsでは1803からページングファイルメモリがサポートされたということでWindowsでもZGCが動かせるようになったようです。366: Deprecate the ParallelScavenge + SerialOld GC Combination
Young領域のGCをPrallelでOld領域のGCをSerialでという、ほとんど使われてない割にメンテナンスが大変な組み合わせが非推奨になりました。
345: NUMA-Aware Memory Allocation for G1
最近は各コアからのメモリアクセスが均等というわけではないNUMA(Non-Uniform Memory Access)アーキテクチャが広まっています。
パラレルGCではNUMAに対応していましたが、G1は対応していませんでした。
このJEPによってG1もNUMAに対応しています。ただし対応OSはLinuxのみです。JDK
JDK自体に関する変更というのは、リリース形態やビルド方針などに関するものです。
Java 14ではSolarisやSPARCへの対応がDeprecatedになりました。
362: Deprecate the Solaris and SPARC Ports362: Deprecate the Solaris and SPARC Ports
Solaris/SPARCやSolaris/x64、Linux/SPARCへの対応を外していくというものです。
CMSの削除と同様に、もし、いっぱい需要があるということを示せれば、取り下げられます。
また、これはオープンソースとしてのOpenJDKの開発の話で、Oracle JDKなど商用ディストリビューションでの対応継続は、それぞれに続く可能性があります。Solaris/SPARCを導入してるところだとJavaのライセンス代もそれほど問題にならなそうだし。
- 投稿日:2020-02-13T11:43:36+09:00
Java Integerの比較(==)が正しく動作しない
現象
import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); // 「2 10000000 10000000」が入ってくる int n = sc.nextInt(); List<Integer> list = new ArrayList<>(); for (int i = 0; i < n; i++) list.add(sc.nextInt()); for (int i = 0; i < n-1; i++) { if (list.get(i) == list.get(i+1)) { System.out.println("等しい"); return; } } System.out.println("等しくない"); } }
list.get(i)
には10000000、list.get(i+1)
にもおなじ10000000のはずなのに結果が「等しくない」が出力されてしまいました。なぜ??プリミティブ型は
==
、オブジェクト型はequals
Integerはオブジェクト型のため、
==
で比較する場合、同一のインスタンスかどうかを確認することになるため、「等しくない」になってしまうようです。import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); // 「2 10000000 10000000」が入ってくる int n = sc.nextInt(); List<Integer> list = new ArrayList<>(); for (int i = 0; i < n; i++) list.add(sc.nextInt()); for (int i = 0; i < n-1; i++) { if (list.get(i).equals(list.get(i+1))) { System.out.println("等しい"); return; } } System.out.println("等しくない"); } } // 結果 // 等しい結論、数を比較する際は、プリミティブ型は
==
、オブジェクト型はequals
を使いように注意しないとでしたー。
- 投稿日:2020-02-13T02:00:23+09:00
配列 vs ArrayList vs HashMapの拡張for文レース
訂正
比較演算子間違ってました(修正済み)
つかったもの
・Java9
・eclipce動機
何が最適かは置いといて拡張for文で回したらどれが一番早いのかなと。
結果
ForEaerVs.javapublic class ForEaerVs { public static void main(String[] argS) { Integer mm; // 配列 Integer[] arry = new Integer[1000000000]; for (Integer i = 0 ; i < 1000000000; i++) { Integer arryInt = i; arry[i] = arryInt; } long staetTimeArry = System.nanoTime(); for(Integer iA : arry) { mm = iA; } long endTimeArry = System.nanoTime(); System.out.println("配列速度:" + (endTimeArry -staetTimeArry)); // List List<Integer> list = new ArrayList<Integer>(); for (Integer j = 0 ; j < 1000000000; j++) { Integer arryInt = j; list.add(arryInt); } long staetTimeList = System.nanoTime(); for (Integer iL: list) { mm = iL; } long endTimeList = System.nanoTime(); System.out.println("List速度:" + (endTimeList -staetTimeList)); // HushMap HashMap<Integer,Integer> hash = new HashMap<Integer,Integer>(); for (Integer k = 0 ; k < 1000000000; k++) { Integer arryInt = k; hash.put(k, arryInt); } long staetTimeHash = System.nanoTime(); for (Integer iH : hash.values()) { mm = iH; } long endTimeHash = System.nanoTime(); System.out.println("Hash速度:" + (endTimeHash -staetTimeHash)); } }ForEaerVsResult.java配列速度 :4020600 List速度 :11350400 Hash速度 :18974400作成時間もいれた結果は以下
ForEaerVsPlusCreateResult.java配列速度 :35613700 List速度 :98473300 Hash速度 :121742700感想
それぞれ使いどころは違うとはいえ、配列ってこんなに遅いのか。
配列先輩の凄さを崇めよ追加
LinkedListでもやってみた
ForEaerVsResult.javaArray速度 :4001200 List速度 :9521900 Hash速度 :19611200 Linked速度:15211300作成時間もいれた結果は以下
ForEaerVsPlusCreateResult.javaArray速度 :32542100 List速度 :80343600 Hash速度 :101256200 Linked速度:140576800
- 投稿日:2020-02-13T01:00:22+09:00
【WireMock】Javaでスタブ・モックサーバを立ててE2Eテストを行いたい
連携先システムが検証系では使えないけどE2Eテストがしたい
世はマイクロサービス時代なので、HTTPリクエスト・レスポンスのモック・スタブの需要が高まっているみたいですね。
私の参加しているプロジェクトはマイクロサービスなんて関係ないのですが、
表題のとおりの目的で、Javaで動くAPIサーバを探していたところ教えていただきました。WireMock
HTTPベースのAPIのシミュレータ、mockサーバー。
http://wiremock.org/
https://github.com/tomakehurst/wiremockほかのAPIServerとの比較は下記サイトで確認できます
Comparison of API simulation toolsThoughtWorksのTECHNOLOGY RADARに2018年にTRIALで選ばれてました
https://www.thoughtworks.com/radar/tools/wiremock
ちなみにTRIALはちゃんと管理するのであればやる価値はあるという指標らしいです。利用方法
ざっと見た感じ以下の2通りがあります
ソースコード内で利用する
MavenやGradleで導入します。こちらはJUNITと組み合わせて利用している例が多そうです
Download and InstallationStandAloneサーバとして導入する
公式サイトからJarファイルをダウンロードして起動します
Running as a Standalone Process今回はE2Eテストのシミュレータとして動いてほしいので、StandAloneサーバとして利用します。
こちらの形式だと、できることが減りそうですかね?
やりたいことが実現できるか調査していきます。参考サイト