- 投稿日:2020-02-06T22:36:03+09:00
Okhttp3でpfx証明書を使う
追記履歴
2020/02/08 参考リンク追加
はじめに
クライアント証明書を使ってHTTPS通信対応をすることになった時に書いたコードを会社を辞めてソースコードが見れなくなった時用にメモ代わりとして残しておく
何故Okhttpにこだわるかというと、大昔に苦しめられたもんだから大嫌いになったんですよねHttp(s)URLConnectionが
一部書き方が雑な部分や現役バリバリな人には釈迦に説法のような部分もあるかもですがご容赦をいt・・・ボク用のメモだから別にいっか!事前準備
1.main配下(main、resと同じ階層)に「assets」フォルダを作成
2.assetsフォルダ内にpfx証明書を格納(今回は便宜上「hogehage.pfx」とするヨ)
3.「AndroidManifest.xml」にインターネット通信のパーミッションを追加(↓こんな感じ)
追加場所はmanifest間なら問題ないが、activityの間には書くな<manifest ・・・ ... //インターネット接続用のパーミッション <uses-permission android:name="android.permission.INTERNET" /> </manifest>4.「build.gradle(app配下の方)」の「dependencies」部分に以下を追加
バージョンは2020/02/05のもの
最新かどうかは https://square.github.io/okhttp/ の「Releases」部分を確認してbuild.gradle・・・ dependencies { ・・・ implementation("com.squareup.okhttp3:okhttp:4.3.1")1と2はHTTPS通信用、3と4はOkhttp用の記述
もうここまでで書くの面倒くさくなってきた・・・FE風花雪〇やりたいコード
メインスレッドではなくAsynctask・・・俗にいう非同期処理で書いてる
前まではメインスレッド(MainActivityとかとか)で書いてたが・・・あれ?なんで変えたんだっけ?AsyncHttps.javapackage com.example.test; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.support.annotation.NonNull; import android.util.Log; import java.io.IOException; import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Arrays; import java.util.concurrent.TimeUnit; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; //非同期処理ってやつ //HTTPS通信を行うためのとこ //呼び出し元では画像をバイト配列に変換して、このactに渡してた public class AsyncHttps extends AsyncTask<byte[], Void, String> { private Context mContext; public AsyncHttps(Context context) {mContext = context;} public static String res=null; final Handler handler = new Handler(); //URLsListの配列番号用 public static int url_id = 0; //通信結果格納変数 public static String result = null; // 非同期処理 @Override protected String doInBackground(byte[]... params) { //url一覧を別のactに配列で書き、送信元で配列番号を送ってやればいいことに気が付いた String urlSt = URLsList.URL[url_id]; byte[] word = params[0]; // return result; } //http通信用 public void http_post(byte[] bytes) { //JSONを使用して送信するための設定らしい //MediaTypeは送る物に合わせて変更が必要(画像なら"image/jpg"みたいな感じ) MediaType mediaType= MediaType.parse("application/json; charset=utf-8"); //Request Body作成。送信する物 RequestBody requestBody = RequestBody.create(mediaType,bytes); //リクエストを作成、郵便物の梱包みたいなもんでしょ(テキトー) Request request = new Request.Builder() //送り先 .url(URLsList.URL[url_id]) .post(requestBody) //送る中身 .build(); //これらをビルドアップする KeyManagerFactory keyManagerFactory; //証明書作成したときに設定したパスワード final char[] PASSWORD = "***ここにパスワード***".toCharArray(); InputStream inputStream; TrustManagerFactory trustManagerFactory; SSLSocketFactory sslSocketFactory; X509TrustManager trustManager; try { //クライアント証明書ファイルの指定(assetsフォルダに配置しとけ) inputStream = mContext.getResources().getAssets().open("hogehage.pfx"); //参考にしたとこの拡張子はp12だったからそっちも行けんじゃない? KeyStore keyStore = KeyStore.getInstance("PKCS12"); //ここから下は何も考えずコピペでOK keyStore.load(inputStream,PASSWORD); trustManagerFactory = TrustManagerFactory.getInstance (TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); } trustManager = (X509TrustManager)trustManagers[0]; keyManagerFactory = KeyManagerFactory.getInstance("X509"); keyManagerFactory.init(keyStore,PASSWORD); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagerFactory.getKeyManagers(),null,null); sslSocketFactory = sslContext.getSocketFactory(); //コピペここまで final OkHttpClient client = new OkHttpClient.Builder() //.connectTimeout(10, TimeUnit.SECONDS) //タイムアウト設定3銃士 //.readTimeout(10, TimeUnit.SECONDS) //何を設定しているかは知らん //.writeTimeout(10, TimeUnit.SECONDS) //あとで見といて .sslSocketFactory(sslSocketFactory,trustManager) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, IOException e) { //例外発生時の処理は別処理にすると安定するらしい。 failMessage(); e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { result = String.valueOf(response); //何か返ってくる場合はここで何かしたりしなかったり client.connectionPool().evictAll(); } }); }catch (IOException e)//こっから下は何か爆発した時Errorタブのログに出る 特に触るとこはない {Log.e("エラー内容:", String.valueOf(e));} catch (NoSuchAlgorithmException e) {Log.e("エラー内容:", String.valueOf(e));} catch (CertificateException e) {Log.e("エラー内容:", String.valueOf(e));} catch (UnrecoverableKeyException e) {Log.e("エラー内容:", String.valueOf(e));} catch (KeyStoreException e) {Log.e("エラー内容:", String.valueOf(e));} catch (KeyManagementException e) {Log.e("エラー内容:", String.valueOf(e));} } //例外発生時(何かしらエラった時)の処理 private void failMessage() {Log.d("通信結果:","ダメでした…");} }参考
Okhttp公式:
https://square.github.io/okhttp/
Android アプリ内でのクライアント証明書認証:
https://qiita.com/c_ume/items/d082ffd20b3316aab805
Now that SSLSocketFactory is deprecated on Android, what would be the best way to handle Client Certificate Authentication?
https://stackoverflow.com/questions/31002159/now-that-sslsocketfactory-is-deprecated-on-android-what-would-be-the-best-way-t
- 投稿日:2020-02-06T21:38:53+09:00
[Android] RecyclerView を用いた横スクロールする ListView の実装
何を作ったか
Android でおなじみの ListView ですが、水平方向へのスクロールは出来ません。そこで、RecyclerView を継承して横方向へスクロールする ListView のようなウェジェットを作ります。
RecyclerView とは?
複数の View を良しなに表示するウェジェットです。かなり自由度が高いので、大抵のものはこれでできます。詳細に関しては、【Android】RecyclerViewの基本的な実装 が詳しいです。
完成品
HorizontalListView.javaimport android.content.Context; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.List; public class HorizontalListView extends RecyclerView{ public interface OnItemClickListener{ void onItemClick(View view, int position); } public HorizontalListView(Context context){ super(context); initialize(context); } public HorizontalListView(Context context, AttributeSet set){ super(context, set); initialize(context); } public HorizontalListView(Context context, AttributeSet set, int defaultAttr){ super(context, set, defaultAttr); initialize(context); } private void initialize(Context context){ LinearLayoutManager manager = new LinearLayoutManager(context); manager.setOrientation(LinearLayoutManager.HORIZONTAL); setLayoutManager(manager); } private OnItemClickListener mListener; public void setOnItemClickListener(OnItemClickListener listener){ mListener = listener; Adapter adapter = getAdapter(); if ( adapter instanceof ArrayAdapter ){ ArrayAdapter a = (ArrayAdapter)adapter; a.mListener = this.mListener; } } @Override public void setAdapter(RecyclerView.Adapter adapter){ super.setAdapter(adapter); if ( adapter instanceof ArrayAdapter ){ ArrayAdapter a = (ArrayAdapter)adapter; a.mListener = this.mListener; } } @Override protected void onDetachedFromWindow(){ super.onDetachedFromWindow(); setOnItemClickListener(null); setAdapter(null); setLayoutManager(null); } private static class SimpleViewHolder extends ViewHolder{ private SimpleViewHolder(View view){ super(view); } } public static abstract class ArrayAdapter<E> extends RecyclerView.Adapter<ViewHolder> { public ArrayAdapter(List<E> list){ mDataList = new ArrayList<>(list.size()); mViews = new ArrayList<>(list.size()); mDataList.addAll(list); } private List<E> mDataList; private OnItemClickListener mListener; private List<View> mViews; /** * リスト要素となるViewをインスタンス化する * @param parent * @return null not acceptable */ public abstract View getView(ViewGroup parent); /** * リストに表示するデータをViewへ反映する * @param view 反映先のView * @param data 反映させるデータ{@link #getItem(int)}でも取得可能 * @param position リスト上での位置 */ public abstract void onBindView(View view, E data, int position); @Override public final ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){ return new SimpleViewHolder(getView(parent)); } @Override public final void onBindViewHolder(final ViewHolder holder, int position){ final View view = holder.itemView; E data = getItem(position); onBindView(view, data, position); mViews.add(holder.itemView); holder.itemView.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v){ if ( mListener != null ){ mListener.onItemClick(view, holder.getAdapterPosition()); } } }); } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView){ super.onDetachedFromRecyclerView(recyclerView); for ( View view : mViews ) view.setOnClickListener(null); mViews = null; mListener = null; mDataList = null; } @Override public final int getItemCount(){ return mDataList.size(); } public E getItem(int position){ return mDataList.get(position); } } }実装の説明
横スクロールの実現
横方向を指定した LinearLayoutManager を RecyclerView に設定するだけでOK。
private void initialize(Context context){ LinearLayoutManager manager = new LinearLayoutManager(context); manager.setOrientation(LinearLayoutManager.HORIZONTAL); setLayoutManager(manager); }アダプターの用意
RecyclerView も ListView と同様に、データと表示する View を管理するアダプターが必要となります。そこで、RecyclerView.Adapter を継承して HorizontalListView.ArrayAdapter を用意。使用時に実装すべきは以下の2つ。
public abstract View getView(ViewGroup parent); public abstract void onBindView(View view, E data, int position);getView(ViewGroup) で表示に使う View をインフレートして、onBindView(View,E,int) でデータ E の内容を View へ反映させます。
ListView を使う時、android.widget.ArrayAdapter を継承したアダプターでレイアウトをカスタマイズするのと同じ感じで操作できるように、同様なAPIを持たせました。コールバックの用意
ListView と同様にリストの要素がクリックされたら通知するようなコールバックが欲しいですね。作りましょう。
public interface OnItemClickListener{ void onItemClick(View view, int position); }クリックされた View と表示位置を通知します。実装としては、先ほど作った HorizontalListView.ArrayAdapter が getView(ViewGroup) で取得して表示する View に OnClickListener をセットして、受け取ったコールバックを中継します。ただし、注意として RecyclerView#setAdapter() で設定するアダプターが HorizontalListView.ArrayAdapter でないと機能しません。
- 投稿日:2020-02-06T18:48:08+09:00
Ruby、PHP、Java、JSでの書き方の違い
はじめに
「Rubyでプログラミングの基礎を学んだから、次はjsやってみようかな!」
↓
「書き方ごちゃごちゃになって、何が何だかよくわかんねー!!」こんな人もいるのでは?
僕自身も以前、詰め込みすぎでパンパンになりましたので、書き方の違いをわかりやすくまとめてみました。
一つ一つ覚える必要はありませんので、この記事を、ストックしていただければと思います。
オブジェクト指向の理解も多少深まると思いますので、最後までご覧ください!文字出力
基本となる文字の出力。言語によって処理のされ方が違うので難しいところですが、基本なのでしっかり押さえましょう。Javaはやっぱり記述が長いですねw
#Ruby puts "こんにちは"#PHP echo "こんにちは";//Java System.out.println("こんにちは");//JavaScript console.log("こんにちは");変数代入
プログラミングの超基本、変数代入です。
#Ruby name = "Tom"#PHP $name = "Tom";//Java(String、intなどの型を指定する) String name = "Tom";//JavaScript let name = "Tom";変数展開、文字列+変数
これもよく使いますね。全部似てますが、違いがあります。※書き方はあくまで一例です
#Ruby "私の名前は#{name}です。"#PHP "私の名前は{$name}です。"//Java "私の名前は"+ name + "です。"//JavaScript(''ではなく``(バッククオーテーション)で囲む) `私の名前は${name}です。`if文
処理の代表と言えばif文でしょう。elsifのところの違いに注意です。
#Ruby if age >= 20 #処理 elsif age >= 10 #処理 else #処理 end#PHP if (age>= 20){ #処理 }elseif (age >= 10){ #処理 }else{ #処理 }//Java & JavaScript if (age>= 20){ //処理 }else if (age >= 10){ //処理 }else{ //処理 }配列と取り出し方
配列の取り出しもよく使いますね、1つづつ取り出す方法も言語によっていろんなのがあります。
#Ruby names = ["Tom","Kenta","John"] names[0]#PHP $names = array("Tom","Kenta","John"); $names[0];//Java String names[] = {"Tom","Kenta","John"}; names[0];//JavaScript const names = ["Tom","Kenta","John"]; names[0];ハッシュ、連想配列、オブジェクトと取り出し方
言語によって呼び方が違うので、検索する際には注意してください。
#Ruby(ハッシュ) user = {name: "Tom",age: 20} user[:name]#PHP(連想配列) $user = array("name" => "Tom","age" => 20) $user["name"]//JavaScript(オブジェクト) const user = {name: "Tom",age: 20}; user.name通常のメソッド、関数
ここからが重要なところです。今回は2つの値を合計するaddメソッド(関数)を作りました。
戻り値も重要なので、しっかり押さえましょう。#Ruby def add(a, b) return a + b end sum = add(5, 2)#PHP function add($a, $b){ return $a + $b; } $sum = add(5, 2);//Java public static int add(int a, int b){ //staticの後のintは戻り値の型を指定、戻り値がないメソッドの場合はvoidを使用 return a + b; } int sum = add(5, 2);//JavaScript const add = function(a, b){ return a + b; }; let sum = add(5,2); //または const add = (a, b) => { return a + b; }; let sum = add(5,2); //または function add (a, b){ return a + b; } let sum = add(5,2);jsはバージョンによって推奨されている記述が違います。
クラスとインスタンス作成
さあ、待ちに待ったオブジェクト指向の始まりです!
Menuクラスを作って、そのクラスをもとに、インスタンスを作成し変数menu1に代入しています。#Ruby class Menu ##処理 end menu1 = Menu.new#PHP class Menu{ ##処理 } $menu1 = new Menu();//Java class Menu{ //処理 } Menu menu1 = new Menu();//JavaScript class Menu{ //処理 } const menu1 = new Menu();インスタンス変数、インスタンスフィールド、プロパティの定義
メニューには名前や値段などの"情報"が含まれます。それをクラス内で事前に宣言しておきます。
#Ruby attr_accessor :name attr_accessor :price#PHP private $name; private $price;//Java private String name; private int price;//JavaScript //事前定義は不要?初期メソッドでのインスタンス変数、プロパティへの代入
さあ、さらに訳わからないところにやってきました!
初期メソッドとはnewされた時(一番最初)に呼ばれるメソッドになります。
今回はnewの引数に指定された値が初期メソッド内で、インスタンスの情報として変数に入ります。#Ruby def initialize(name, price) self.name = name self.price = price end menu1 = Menu.new("ハンバーガー",300)#PHP public function __construct($name,$price){ this->name = $name; this->price = $price; } $menu1 = new Menu("ハンバーガー",300);//Java //クラス内でクラスと同名のメソッドを定義する Menu(String name, int price){ this.name = name; this.price = price; } Menu menu1 = new Menu("ハンバーガー",300)//JavaScript constructor (name, price){ this.name = name; this.price = price; } const menu1 = new Menu("ハンバーガー",300)インスタンスメソッドと呼び出し
さて最後になります。インスタンスは"情報"の他に"処理"を持っています。
今回はその処理の定義方法と呼び出し方です。
変数menu1にはMenuクラスから作られたインスタンスが代入されているので、
出力は"こちらのハンバーガーは300円です"となります。#Ruby def show puts "こちらの#{self.name}は#{self.price}円です" end menu1.show#PHP public function show(){ echo "こちらの{$this->name}は{$this->price}円です"; } $menu1->show();//Java public void show(){ System.out.println("こちらの"+this.name+"は"+this.price+"円です"); } menu1.show();//JavaScript show(){ console.log(`こちらの${this.name}は${this.price}円です`); } menu1.show();終わりに
最後までご覧いただきありがとうございました。
抜けや間違いがあるかもしれませんが大目に見てください!!この世の中では浅く広く学ぶのはあまり良くないとされていますが、知識を深めるために別言語に挑戦してみるのもいいと私は思います。
- 投稿日:2020-02-06T17:59:08+09:00
JavaでTwilio使ってみるよ!(SMS編 その1)
前回、pom.xmlにTwilio SDKの依存を追記したところから。
必要なもの
Twilioに登録したときの、下記の情報が必要です。
- ACCOUNT SID
- AUTH TOKEN
- callerId(購入した電話番号)
電話番号ですが、SMSを送る場合は日本の050番号じゃダメみたいです。
アメリカの番号にしましょう。実装してみる
Twilioの公式ヘルプを参考に実装してみます。(ほぼそのままですが)
https://jp.twilio.com/docs/sms/api/message-resourceACCOUNT_SID、AUTH_TOKEN、電話番号は適宜変えてください。
なお、電話番号は国際電話表記じゃないとだめです。
例えば、「090-0012-3456」に送る場合は、「+819000123456」となります。
(日本の国番号+81に、市外局番の先頭の0を除いた形)SMSController.javapackage jp.co.pmtech.iwata.twilio.controller; import com.twilio.Twilio; import com.twilio.rest.api.v2010.account.Message; import com.twilio.type.PhoneNumber; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SMSController { /** ACCOUNT SID */ private static final String ACCOUNT_SID = "*******"; /** AUTH TOKEN */ private static final String AUTH_TOKEN = "*******"; @PostMapping("/sms") public void sms() { // twilioの初期化 Twilio.init(ACCOUNT_SID, AUTH_TOKEN); // 送信するメッセージ String body = "こんにちは!\n" + "テストメッセージです。"; // SMSを送る Message message = Message.creator( new PhoneNumber("+8190********"), // 送信先の電話番号 new PhoneNumber("+1**********"), // callerID(購入した番号) body).create(); System.out.println(message.getSid()); } }動かしてみる
SpringBootを起動して、http://localhost:8080/sms を叩いてみましょう。
あ、@PostMapping
にしちゃったので、Talend API Testerから叩きますか。おー!届きました!
ね、チョロいでしょ?※ トライアル版の場合、メッセージの先頭に「Sent from a Twilio Trial account.」という文字列が追加されるようです。
絵文字って送れるの?
? ←この絵文字を送ってみたいと思います。
Unicodeで送ればいいのかな?やってみる
https://0g0.org/unicode/1F923/
文字コードはu+1F923らしいのでやってみます。// 送信するメッセージ String body = "こんにちは!\n" + "\u1F923" + "\u1F923" + "\u1F923" +"\n" + "テストメッセージです。";あれ?なんか変な文字になった。
絵文字のUnicode表記方法
絵文字は拡張文字なので4バイトです。
JavaのUnicode表記は2バイトしか書けないので、サロゲートペアで2バイトづつ表現しなきゃいけないみたい。https://www.fileformat.info/info/unicode/char/1f923/index.htm
ここに表記方法のってました。
Encodings C/C++/Java source code "\uD83E\uDD23" // 送信するメッセージ String body = "こんにちは!" + "\uD83E\uDD23" + "\uD83E\uDD23" + "\uD83E\uDD23" +"\n" + "テストメッセージです。";やったね???
次回予告
送信失敗とかわかるのかな?
- 投稿日:2020-02-06T17:53:41+09:00
JavaでTwilio使ってみるよ!(導入編)
これから何回かにわけて、Twilio使ってみた的な記事を書こうと思います。
RubyやPHPでの記事はあるようですが、Javaがあまり無かったので書いてみようと思いました。Twilioについて
簡単に言うと、音声通話・SMS・FAXなどの機能を持つクラウドサービスです。
自分のアプリケーションからSMSを送ったり、電話をかけたりすることができます。
日本では、KDDIウェブコミュニケーションズが代理店になっているようです。https://www.twilio.com/ja/
https://cloudapi.kddi-web.com/availability/Twilioの登録
Twilioのアカウント登録と、電話番号の購入が必要です。
登録は他の記事でも書かれているので割愛します。なお、トライアル登録もできるようです。
無料の電話番号は1つだけなどの制限事項はあるようですが、恐らく今回やることはできるはず。目的
Twilio使って個人的にハマったところもあったので、同じようなところで困っている人の役に立てたらいいなーくらいの感じです。
予定しているのは
- SMS
- 音声通話
- FAX
の3本です。
ちなみにSMSはチョロいです。
音声通話とFAXはちょーっとだけめんどくさいところがあります。環境、その他前提
本記事では、以下を前提にします。
- Twilioアカウント登録済み(電話番号も購入済み)
- Java 1.8.0_181
- SpringBoot 2.1.8.RELEASE
- Twilio SDK 7.42.0
個別に違うライブラリ等使う場合は、その都度書きます。
(バージョン違ってもそれほど問題ないはず)開発環境を作ろう
mavenで、SpringBootのテンプレ的なものができている前提です。
pom.xml
pom.xmlに、Twilio SDKを追加します。
pom.xml<!-- Twilio SDK --> <dependency> <groupId>com.twilio.sdk</groupId> <artifactId>twilio</artifactId> <version>7.42.0</version> </dependency>次回予告
- 投稿日:2020-02-06T17:08:11+09:00
【java】クラスとはOS、インスタンスとは仮想コンピュータだ
0. オブジェクト指向という「新しい概念」
プログラミングの初心者で、今までC言語を使ったコーディングの経験しかない人にJavaを教えるのは、思いのほか難しい。(情報系の大学を想定。1年次にC、2年次にJavaをやる場合が多いそう。)
「クラスとは何か」と聞かれたら「オブジェクト指向で使う」と説明するが、
「ではオブジェクト指向とは何か」と聞かれたらどうだろう。「世の中にはいろんなモノゴトがある。そしてモノゴトには状態と振る舞いがある。
だからいろんなモノゴトに対して、状態と振る舞いを集めてクラスの中にコーディングしていく。これがオブジェクト指向だ」
と答えて、相手は納得するかもしれない。
しかし、この後に続く「カプセル化」、「継承」の説明を聞いて、相手は混乱するかもしれない。
いきなり「状態と振る舞いを集めてモノゴトを表現する」と教わったかと思えば、次に「状態を振る舞いに閉じ込める」、「具体的なモノゴトは抽象的なモノゴトをケイショウする」と教わるなど、覚えることが多い。「理論より実践」な人にとっては『苦痛』だろう。
こうして、オブジェクト指向によるコーディングに苦手意識を持ち、プログラミング界隈から離れていく人が、後を絶たないようである。『苦痛』の正体は、「オブジェクト指向とは、つまり何か」が一言で説明されないことにある。
憶測だが、人は新しい概念を、既存の概念になぞらえて考え、誤差の部分は実践により覚える。
天空の城ラピュタで、空に浮かぶ伝説の島ラピュタの発見のために、主人公パズーとシータが政府専用機ゴリアテを尾行するくだりで、一度ゴリアテを見失いそうになる。
そのとき、協力者ドーラは「見張り台」と呼ばれる凧を使って追跡の続行を試みる。
このときパズーは次のよう指示される。(凧の)操縦は体で覚えるんだ!
命懸けの無茶ぶりを、難なくこなすパズーの勇敢さが引き立つシーンではあるが、
ドーラも何も考えずにパズーに指示したのではない。ゴリアテ尾行の前、半ば脅されて政府に連行されたシータを救出すべく、ドーラはパズーを乗せてシータの居場所へと小型船を出した。この際、事故の衝撃でドーラは気を失い、小型船は危うく墜落するところであったが、パズーが操縦を急遽代行したことで、九死に一生を得たのだ。
凧の操縦(新しい概念)の指示は、極度に緊迫した状況で小型船を妥当に操縦(既存の概念)したパズーの実績を見込んでのことだろう。オブジェクト指向も、同じことではないだろうか。
皆が知っているような何かでオブジェクト指向をたとえ、違いの部分は実践をもとに各々が学習していけばそれでよい。少なくともイメージをつかめずに、手を動かすことすらできないよりはよっぽど。
本記事ではこのようなアプローチでJavaによるプログラミング、およびオブジェクト指向を解説していく。最後に補足すると、「プログラムとは何か」、「コンピュータ(計算機でもよい)とは何か」を定義に立ち返って考えれば、
クラスが「コンピュータを構成できるプログラム」であること、インスタンスがコンピュータであることは論理的に証明可能である。1. Javaとは「『仮想コンピュータ』を作りまくる言語」だ
クラスを一言で説明するなら「コンピュータを構成できるプログラム」である。
このことは論理的に証明できる。同じように、インスタンスが「仮想コンピュータ」であることも証明可能だ。
1-1. クラスが「コンピュータを構成できるプログラム」であることの証明
まず、ベーム・ヤコピーニの定理[1]にあるプログラムの3要素
「順次」、「反復」、「分岐」を、クラス内のメソッドですべて行える。
ということはもちろん、メソッドもプログラムといってよいのであるが、
残念なことにメソッドは「記憶領域」を持たない。
(蛇足:メソッド内でのみ有効な「ローカル変数」は、メソッド終了時に寿命が尽きるので、「記憶」したとは言えない)
そのためチューリングマシン[2]における「ヘッダ」を作ることができないため、
メソッドはプログラムにはなれても、コンピュータにはなれないのだ。
その点、クラスであれば「フィールド」(java以外の言語なら「プロパティ」などと呼ぶ)を持っていて、これを記憶領域として使うことができるため、
クラスはコンピュータを構築することが可能なのだ。(証明終わり)
別の言い方をするならば、クラスとはコンピュータの設計図だ。1-2. インスタンスが「仮想コンピュータ」であることの証明
クラスそのものは、インスタンスという「実体」を作るための型である。
クラスという設計図をもとにインスタンスが生成されるのであるから、
インスタンスは、「クラスという設計図をもとに作った何か」である。
1-1節で証明したことをここに適用すると、
インスタンスとは、
「『コンピュータの設計図』という設計図をもとに作った何か」
である。それは(設計図通りに作れば)コンピュータである。(証明終わり)2. OSと仮想コンピュータを作って遊ぼう
第1章のことより、クラスとは「コンピュータを構成できるプログラム」であり。インスタンスとは「コンピュータ」のことであることが分かった。
「コンピュータを構成できるプログラム」とは、まさにOSである。
不思議かもしれないが、javaでプログラミングするたびに、プログラマはOSを作っているし、new
するたびに仮想コンピュータを作っているのである。例えば、次のコードを考えよう。
「Mindows」OSと「computer」コンピュータpublic class Mindows { private byte[] ram; public byte getRam(int address){return this.ram[address];} public void setRam(int address, byte value){this.ram[address]=value;} public Mindows(int ram_size){this.ram = new byte[ram_size];} } class Main{ public static void main(String...args) { Mindows computer = new Mindows(64);//64byteのRAMを持つコンピュータの作成 int i=0; for(char each_char : "hello world".toCharArray()) computer.setRam(i++, (byte)each_char); //0~10番地に「hello world」を書き込む for(int j=0; j<11; j++) System.out.print((char)computer.getRam(j)); //0~10番地を(charとして)表示 //→コンソールに「hello world」と表示される。 }}このコードではMindows OSを定義し、このOSの入った仮想コンピュータ
computer
を作って、
RAMを操作している。computer
(インスタンス)を使ってできることを定義しているのはOS(クラス)であるが、OSが直接何かするわけではなく、実際に動作する実体はcomputer
(インスタンス)の方である。この他にも、
final定数フィールドでROMを作ったり、
finalメソッドでBIOSを作ったり、
ファイル入出力の機能を使ってストレージを持たせたりすることなどが考えられる。3. javaの「実際」に当てはめて考える
3-1. ArrayListクラス
ArrayList<T>
型インスタンスは記憶領域に順番付けてT
型オブジェクトを格納しておくことのできる仮想コンピュータである。3-2. Listインタフェース
List<T>
インタフェースは、記憶領域にT
型オブジェクトを記憶しておくプログラムの「宣言」に過ぎない。記憶領域の使い方を「おおよそ」決めるものといってよいだろう。
ArrayList<T>
やLinkedList<T>
は、このインタフェースを「実装」している。
この事実は、ArrayList<T>
やLinkedList<T>
が「記憶領域を、T
型オブジェクトの格納に使う」と宣言していることを意味する。
インタフェースを実装した有言...クラスは、インタフェースに定義された空っぽのメソッドをオーバロードし、具体的に定義しなくてはならない。...実行インターフェースによる宣言は、家電を想像すれば理解しやすい。炊飯器や洗濯機(や、いまの時代では、掃除機も)は、どれもコンピュータといってよいだろう。
炊飯器はコメや水の重さから、炊き上がりの米の硬さや残り時間などを計算してくれる。洗濯機も、自動で判断する場面がある。掃除機は、ルンバをみれば分かるだろう。
今から作るクラス(プログラム)が炊飯器なのか洗濯機なのかはたまた掃除機なのかをはっきり言っておくことは重要だ。また、場合によっては「特定のインタフェースを実装したクラスにだけ、特別な使い方を認める」といったことも出来る。(例えば例外クラスなどがそれである。)3-3. カプセル化
「コンピュータ内部の回路をお客様にお見せしたくない」―それがカプセル化だ。
第2章に示したコードをもう一度見てみよう。
computer
のRAMに書き込むとき、わざわざsetRam(アドレス, 書き込む値)
というメソッドを呼んでいる。
そんなことをしなくても、ram[アドレス]=書き込む値
ではダメなのだろうか。
もちろん、原理的にはそれで問題ないが、実際には、やりにくくなっている。
「Mindows」開発者は、メモリへの書き込み機能としてsetRam(アドレス, 書き込む値)
を作ってはいるが、メモリそのものを剥き出しにはしていないのだ。実際のコンピュータが、メモリを鉄の内側に閉じ込めているように、javaのコード上でprivate
というアクセス修飾子を付けている。
このようにすることで、記憶領域をユーザに勝手にいじられないようにして、誤作動の可能性を低減させている。4. OSとコンピュータが自由に作れれば、オブジェクト指向プログラミングが可能
残念ながらOSをクラスとみることは必ずしもできない。
クラスの持たない概念を持つOSが存在する(というか、ほとんどだ)からだ。
(例: マルチタスクOS、仮想メモリなど。またクロックやCPUを考えなければならない場合は、これをクラスで(実用的に)表現するのは困難であろう)
そこで、クラスが表現できるOSを「クラスOS」、インスタンスが表現できるコンピュータを「インスタンスコンピュータ」あるいは「クラスOSのコンピュータ」と呼ぶことにしよう。逆に、クラスの持つ概念(フィールド、メソッドのみとする)のどれかを持たないOSは存在しない。(クラスがOSとみなせることの証明から、明らかである)
OSを自由に設計し、そのOSを入れた仮想コンピュータを生成・利用できる環境では、オブジェクト指向による開発が少なくとも部分的に可能である。(「半オブジェクト指向定理」とでも呼ぼう)
これは、すべてのOSがクラスの概念をすべて持っているため、当然のことである。
エラー発生時には、どのコンピュータ(OS)に不具合が生じたかを確認すればよいためエラーの特定が容易であるし、また仕様変更にも柔軟に対応する。
但し、アクセス制限の仕組みがなければカプセル化は利用できず、コピーの仕組みがなければ継承は不可能だ。n.参考
[1]http://s150001.skr.u-ryukyu.ac.jp/lectures/index.php?ProgrammingAbility
[2]http://e-words.jp/w/%E3%83%81%E3%83%A5%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%9E%E3%82%B7%E3%83%B3.html
- 投稿日:2020-02-06T16:43:39+09:00
人生ゲーム(イベント)
public class Event {
private int ahead; private int back; private String jobChange; private int sum; int getAhead() { return ahead; } void setAhead(int a) { ahead = a; System.out.println(ahead + "マス進む"); } int getBack() { return back; } void setBack(int b) { back = b; System.out.println(back + "マス戻る"); } String getJobChange() { return jobChange; } void setJobChange(String jc) { jobChange = jc; System.out.println("あなたの職業は" + jc + "になりました。"); } int getSum() { return sum; } void setSum() { sum += ahead; } //1マス目 public static void first() { System.out.println("道で財布を落として見知らぬ人が交番に届けた!"); System.out.println("お礼として1,000円支払う"); //-1,000円 } //2マス目 public static void second() { System.out.println("友達に飲みに誘われた!"); System.out.println("会費として3,000円支払った"); //-3,000円 } //3マス目 public static void third() { System.out.println("いらないゲームソフトを売った"); System.out.println("3,000円もらう"); //+3,000円 } //4マス目 public static void forth() { System.out.println("運転中に事故を起こしてしまった..."); System.out.println("慰謝料として全財産失う"); //全財産失う } //5マス目 public static void fifth() { System.out.println("気になる人とデートに!"); System.out.println("一夜を共にする"); //何もなし } //6マス目 public static void sixth() { System.out.println("友達の喫茶店の手伝いをする"); System.out.println("アルバイト代として5,000円もらう"); //+5,000円 } //7マス目 public static void seventh() { System.out.println("たまたま買った馬券が大当たり!"); System.out.println("全財産の倍のお金をもらい、3マス進む"); //財産*2、3マス進む tenth(); } //8マス目 public static void eighth() { System.out.println("親戚の子が遊びに来た"); System.out.println("おこずかいとして2,000円渡す"); //-2,000円 } //9マス目 public static void nineth() { System.out.println("べろべろに酔っ払い店で卓ゲロ!"); System.out.println("迷惑料10,000円を支払い、8マス戻る"); //-10,000円、8マス戻る first(); } //10マス目 public static void tenth() { System.out.println("就職!お祝いとして10,000円もらう"); System.out.println(""); }}
- 投稿日:2020-02-06T12:31:51+09:00
HttpServletRequest のクラス・ファイルが見つかりません
- 投稿日:2020-02-06T01:54:42+09:00
Spring Java
- Spring Tools 4 for Eclipseを入手し以下で解凍
java -jar spring-tool-suite-4-4.5.1.RELEASE-e4.14.0-win32.win32.x86_64.self-extracting.jar
- 必要によってSpringToolSuite4.iniに-vm追加
-vm C:\Program Files\Java\jdk1.8.0_211\bin\javaw.exe
- springプロジェクト作成
File → New → Spring Starter Project → Next →
web機能とホットデプロイを使うため以下をチェック
1. Developer Tools→Spring Boot DevTools
2. Web→Spring Web
→ Finishpom.xml<dependencies> ・・・ <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- ホットデプロイ --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> ・・・ </dependencies>IndexController.javapackage com.example.demo; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class IndexController { private static final Logger log = LoggerFactory.getLogger( IndexController.class ); @RequestMapping("/") private String index() { return "index"; } @RequestMapping(value="/", method=RequestMethod.GET) private Map<String, Object> index( @RequestParam("id") Integer id) { Map<String, Object> map = new HashMap<>(); map.put("id", id); return map; } @RequestMapping(value="/items/{id}", method=RequestMethod.GET) private Item get(@PathVariable int id) { return new Item(id); } @RequestMapping(value="/items", method=RequestMethod.POST) private void post(@RequestBody String body) { log.info("post, " + body); } @RequestMapping(value="/items/{id}", method=RequestMethod.PUT) private void put(@PathVariable int id, @RequestBody String body) { log.info("put, " + id + ", " + body); } @RequestMapping(value="/items/{id}", method=RequestMethod.DELETE) private void delete(@PathVariable("id") int id) { log.info("delete, " + id); } public static class Item { private int id; Item (int id) { this.setId(id); } public int getId() { return id; } public void setId(int id) { this.id = id; } } }
- 投稿日:2020-02-06T01:11:47+09:00
HttpUrlConnectionの謎いスタックトレース
概要
HttpUrlConnectionの
getInputStream
メソッド呼び出し時にIOException
が発生した場合、意味がわからない例外スタックトレースが返却されることがあります。TL;DR
HttpUrlConnection#getInputStream
でIOException
が発生する場合、過去にそのインスタンスで発生したIOException
がネストされます。検証環境
- macOS 10.15.2
- AdoptOpenJDK (HotSpot) 1.8.0_242-b08
事象
以下のようなコードがあるとします。
- 400 Bad Requestを決め打ちで返却する簡易HTTPサーバを用意する
- HttpUrLConnectionを使って、簡易HTTPサーバにリクエストを送信する
- HttpUrLConnection#getInputStreamを呼び出しレスポンスの読み取りを行う
コード
import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.URL; public class HttpURLConnectionTest { public static void main(String[] args) throws IOException { // HTTP Server HttpServer server = HttpServer.create(new InetSocketAddress(8087), 0); server.createContext("/", exchange -> { Headers headers = exchange.getResponseHeaders(); headers.add("Content-type", "text/plain"); String body = "error!"; byte[] bytes = body.getBytes(); exchange.sendResponseHeaders(400, bytes.length); try (OutputStream out = exchange.getResponseBody()) { out.write(bytes); } }); server.start(); System.out.println("start server."); // HTTP Client HttpURLConnection conn = null; try { URL url = new URL("http://localhost:8087/"); conn = (HttpURLConnection) url.openConnection(); conn.connect(); int responseCode = conn.getResponseCode(); System.out.println("responseCode = " + responseCode); // 400 try (InputStream in = conn.getInputStream()) { // ここで例外発生 throw new AssertionError("ここにはこないはず"); } } catch (IOException e) { e.printStackTrace(); // よくわからないスタックトレースが表示される } finally { if (conn != null) conn.disconnect(); server.stop(0); } } }実行すると、以下のようなスタックトレースが表示されます。
スタックトレース
コンソールには以下のように表示されます。
start server. responseCode = 400 java.io.IOException: Server returned HTTP response code: 400 for URL: http://localhost:8087/ at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1950) at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1945) at java.security.AccessController.doPrivileged(Native Method) at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1944) at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1514) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498) at HttpURLConnectionTest.main(HttpURLConnectionTest.java:38) Caused by: java.io.IOException: Server returned HTTP response code: 400 for URL: http://localhost:8087/ at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1900) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498) at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480) at HttpURLConnectionTest.main(HttpURLConnectionTest.java:35) Process finished with exit code 0考察
前提
HttpURLConnectionは、レスポンスのステータスコードが400以上の時に
getInputStream
が呼び出された場合、IOException
またはFileNotFoundException
をスローします。if (respCode >= 400) { if (respCode == 404 || respCode == 410) { throw new FileNotFoundException(url.toString()); } else { throw new java.io.IOException("Server returned HTTP" + " response code: " + respCode + " for URL: " + url.toString()); } }この場合、
getInputStream
ではなくgetErrorStream
メソッドを使用しなければ、レスポンスボディが読めません。にわかに信じ難いAPIですが、実際そのように動作します。
スタックトレースの考察
本題であるスタックトレースを見てみますと、ネストされた例外が存在しています。
最初の例外を見ると、
getInputStream
の呼び出しでIOException
が発生しており、上記前提事項と整合性が取れるスタックトレースになっています。問題はネストした例外のほうです。
ネストした例外を見ると、
getInputStream
ではなく、その前に呼び出したgetResponseCode
の呼び出しで例外が発生しているようにみえます。しかし、実際はgetResponseCode
は例外をスローせず、ステータスコード400を戻り値として返却しています。
getInputStream
より2行前に実行したメソッド呼び出しのスタックトレースが、その後getInputStream
にネストされており、通常のJavaプログラミングではお目にかかれないようなスタックトレースになっています。なぜこのようなスタックトレースが発生するのでしょうか?
getResponseCode呼び出し時の動作
getResponseCode
は内部でgetInputStream
を呼び出す(ステータスコードを読み取るため)- ステータスコード400以上のとき、
getInputStream
はIOException
をスローする
- その際、発生した例外をインスタンスフィールドに保存しておく
getResponseCode
は発生したIOException
はスローせず、ステータスを戻り値として返却するその後のgetInputStream呼び出し時の動作
getInputStream
でIOException
発生時、以前に発生した例外がある場合は、今回の例外にネストさせるソースコード上はこのように記載されています。
/* Remembered Exception, we will throw it again if somebody calls getInputStream after disconnect */ private Exception rememberedException = null;「
disconnect
したあとにgetInputStream
が呼ばれた場合、例外を再度スローするために記憶しておく」とあります。以前に例外が発生した場合、新しい例外に以前の例外をネストさせます(
Throwable#initCause
を使用する)。private synchronized InputStream getInputStream0() throws IOException { // 中略 if (rememberedException != null) { if (rememberedException instanceof RuntimeException) throw new RuntimeException(rememberedException); else { throw getChainedException((IOException)rememberedException); } }try { final Object[] args = { rememberedException.getMessage() }; IOException chainedException = //... // 中略 // ここで以前の例外をネストさせる chainedException.initCause(rememberedException); return chainedException; } catch (Exception ignored) { return rememberedException; } }このため、以前
getResponseCode
が起動されたときに内部で発生していたIOException
が保存され、その後にgetInputStream
を起動した際にネストした例外として現れる、という仕組みです。API使用者からすると
getResponseCode
は成功したように見えますので、その後のgetInputStream
でネストした例外に、前回正常終了したgetResponseCode
のスタックトレースが含まれるのは意味不明に思われます。私は
getInputStream
とgetErrorStream
を使いわける必要があることを知らなかったため、
このスタックトレースを見た時、全く意味がわかりませんでした。まとめ
HttpUrlConnection#getInputStream
でIOException
が発生する場合、過去にそのインスタンスで発生したIOException
がネストされます。今回の調査を通して、HttpUrlConnectionには以下のような問題点があると考えました。
- ステータスコードによって、
getInputStream
とgetErrorStream
を使いわけを強いる不自然なAPI設計がなされている- API使用者に露呈していない例外を、別の例外にネストさせるような不自然な挙動をする
- APIドキュメントに、そのような振る舞いが明記されていない(実装依存)
- 上記の問題点が改善される気配がない
1のAPI設計については、いろいろな人が疑義を呈しています。
- http://quesera2.hatenablog.jp/entry/2014/12/08/001700
- https://qiita.com/KeithYokoma/items/4b72096d386e919379e8
またJava11, 13でも同じ動作となるところを見ると、互換性維持のため挙動を変えれないのだと推察されます。それはそれで仕方ないのですが、せめてAPIドキュメントだけでも改善してほしいと思います。
お世辞にも使いやすいAPIとは言えないので、どうしても標準APIを使う必要がある等、特別な理由がない限りは別のライブラリを使用するのがよさそうです。