20200627のJavaに関する記事は14件です。

Javaの繰り返し処理

はじめに

正直クラスの書き方全然理解できない(本内容とは無関係)

繰り返し処理

繰り返し処理とは、一定の処理を自動で繰り返し行う処理のことwhile文とfor

kane.java
while(条件){
 繰り返す処理;
}

for(条件){
 繰り返す処理;
}
kane.java
int = 1;                     //初期化

while(i <= 5){ //条件
 System.out.println(i);      //繰り返す処理
 i++;                        //1を追加していく 変数の更新
}

for(int i = 1; i <= 5; i++){ //初期化、条件、更新をまとめて書ける
 System.out.println(i);      //繰り返す処理
}

変数に1を足し忘れると変数は1のまま変わらず、条件が永遠にtrueになるので、繰り返し処理が無限に行われる(無限ループ)

break

繰り返しを終わらせるためには、条件をfalseにする以外に、breakを使って強制的に終了させる方法がある。if文などの条件分岐と組み合わせることで、任意の箇所で繰り返し処理を終わらせることができる。

kane.java
for(int i = 1; i <= 10; i++){ 
 if (i > 5){ //6になると強制終了
  break;
 }
 System.out.println(i);      
}

continue

continueはその周の処理だけをスキップして、次の周を実行できる。continueもif文などと組み合わせて利用するのが一般的。

kane.java
for(int i = 1; i <= 10; i++){ 
 if (i % 3 == 0){ //3の倍数の時飛ばして次の処理を行う
  continue;
 }
 System.out.println(i);      
}
コンソール
1
2
4
5
7
8
10 

3の倍数は飛ばされてる

終わりに

コーヒーの効果に今頃気づく28才。

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

Java 11 から正式導入された HTTP Client API で Yahoo!ショッピング商品検索(v3) の API をコールするサンプルコード

概要

  • Java 11 から正式導入された HTTP Client API (java.net.http パッケージの HttpClient, HttpRequest, HttpResponse クラスなど) を使って、Yahoo!ショッピング商品検索(v3) の API をコールするサンプルプログラムを作る

今回の環境

  • Java 11 (AdoptOpenJDK 11.0.7+10)
  • Jackson Databind 2.11.0
  • Gradle 6.5
  • macOS Catalina

ソースコード

ソースコード一覧

├── build.gradle
└── src
    └── main
        └── java
            └── com
                └── example
                    ├── ErrorResponse.java
                    ├── ItemSearch.java
                    └── ResultSet.java

build.gradle

plugins {
  id 'application'
  id 'java'
}

group 'org.example'
version '0.0.1'
mainClassName = "com.example.ItemSearch"
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {

  // JSON とクラスのマッピングに必要
  // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0'
}

src/main/java/com/example/ItemSearch.java

package com.example;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

/**
 * ショッピング:商品検索(v3) - Yahoo!デベロッパーネットワーク
 * https://developer.yahoo.co.jp/webapi/shopping/shopping/v3/itemsearch.html
 */
public class ItemSearch {

  public static void main(String[] args) {

    try {
      String appid = args[0];
      String query = args[1];
      System.out.println("appid: " + appid); // アプリケーションID
      System.out.println("query: " + query); // 検索キーワード

      // 商品を検索
      ResultSet rs = new ItemSearch().search(appid, query);
      if (rs != null) {
        for (ResultSet.Hit hit : rs.hits) {
          System.out.println("**************************************************");
          System.out.println("商品名: " + hit.name);
          System.out.println("商品説明: " + hit.description);
          System.out.println("キャッチコピー: " + hit.headLine);
          System.out.println("76×76サイズの画像URL: " + hit.image.small);
          System.out.println("146×146サイズの画像URL: " + hit.image.medium);
          System.out.println("商品URL: " + hit.url);
          System.out.println("価格: " + hit.price);
        }
      }
    } catch (Exception e) {
      System.out.println("エラー発生: " + e);
      e.printStackTrace();
    }
  }

  /**
   * 商品を検索する。
   * @param appid アプリケーションID
   * @param query 検索キーワード
   * @return 検索結果
   * @throws Exception エラー発生時
   */
  public ResultSet search(String appid, String query) throws Exception {

    try {
      // API コール用の URL を組み立てる
      String baseurl = "https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch";
      String url = baseurl +
        "?query=" + URLEncoder.encode(query, StandardCharsets.UTF_8) + // 検索キーワード
        "&results=2"; // 検索結果2件まで
      System.out.println("URL: " + url);

      // HTTP リクエスト情報を構築
      HttpRequest req = HttpRequest.newBuilder(new URI(url))
        .GET()
        .setHeader("User-Agent", "Yahoo AppID: " + appid) // アプリケーションID
        .timeout(Duration.ofSeconds(10))
        .build();

      // API をコールして結果を取得
      HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_1_1)
        .followRedirects(HttpClient.Redirect.NORMAL)
        .connectTimeout(Duration.ofSeconds(10))
        .build();
      HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
      String body = res.body();

      // レスポンスのステータスコードを出力
      int statusCode = res.statusCode();
      System.out.println("statusCode: " + statusCode);

      // ステータスコードで成功・失敗を判断
      switch (res.statusCode()) {
        case 200:
          // HTTP レスポンスの JSON を ResultSet クラスにマッピング
          return new ObjectMapper().readValue(body, ResultSet.class);
        case 403:
          // エラー情報を出力
          ErrorResponse errorResponse = new ObjectMapper().readValue(body, ErrorResponse.class);
          System.out.println("エラーメッセージ: " + errorResponse.error.message);
          return null;
        default:
          return null;
      }

    } catch (Exception e) {
      throw e;
    }
  }
}

src/main/java/com/example/ResultSet.java

検索結果のレスポンスを表すクラス。

package com.example;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import java.util.List;

/**
 * 検索結果。
 */
@JsonIgnoreProperties(ignoreUnknown = true) // 不明な JSON プロパティを無視する
public class ResultSet {

  public List<Hit> hits;

  @JsonIgnoreProperties(ignoreUnknown = true) // 不明な JSON プロパティを無視する
  public static class Hit {

    // hits/name string 商品名
    public String name;

    // hits/description string 商品説明
    public String description;

    // hits/headLine string キャッチコピー
    public String headLine;

    // hits/image
    public Image image;

    // hits/url string 商品URL
    public String url;

    // hits/price integer 価格
    public int price;
  }

  public static class Image {

    // hits/image/small string 76×76サイズの画像URL
    public String small;

    // hits/image/medium string 146×146サイズの画像URL
    public String medium;
  }
}

src/main/java/com/example/ErrorResponse.java

エラーレスポンス情報を表すクラス。

import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * エラー情報。
 */
public class ErrorResponse {

  @JsonProperty("Error")
  public Error error;

  public static class Error {

    @JsonProperty("Message")
    public String message;
  }
}

実行例

使用可能なアプリケーションIDと検索キーワード「猫」を指定する

検索結果のレスポンスが ResultSet クラスにマッピングされて情報が出力されている。

$ gradle run -q --args="your_application_id 猫"
appid: your_application_id
query: 猫
URL: https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch?query=%E7%8C%AB&results=2
statusCode: 200
**************************************************
商品名: 猫 ハーネス 猫用リード 猫用品 ペット用品
商品説明: 脱走対策として<br><br>猫は通院やお引越しなどで慣れない移動をさせられると、パニックを起こして逃げ出そうとしたり、暴れたりすることが多いものです。そんな時、ペットケージに入れておくだけでなく、ハーネスを装着した上でペットケージに入れるという二重の備えをしておくと、とても安心です。また万が一、大災害の被害を受けて避難しなければならなくなった時にも脱走防止に役立つので、防災グッズの中に常備しておく飼い主も増えています。<br><br>お散歩用として<br><br>脱走防止目的だけでなく、猫のお散歩用として使用するのもおすすめです。ハーネスは猫の身体の動きを邪魔しにくいので、のびのびと運動できて良いストレス発散になることでしょう。ただし、猫の性格によっては外に連れ出すことがかえってストレスとなる場合もありますので、お散歩はくれぐれも猫の様子をみつつ、交通状況などにも気を配りながら行ってください。<br><br>カラーバリエーションは、赤・青・黒・ピンクの4色です。<br>ナイロン製<br><br>首回り16〜26センチ<br>胴回り26〜36センチ<br>リードの長さ110cm<br><br>検索キーワード:猫リード・猫ハーネス・猫用リード・ポイント消化・送料無料
キャッチコピー: 猫ハーネス猫リード猫用リード猫首輪
76×76サイズの画像URL: https://item-shopping.c.yimg.jp/i/c/sam-store_0030
146×146サイズの画像URL: https://item-shopping.c.yimg.jp/i/g/sam-store_0030
商品URL: https://store.shopping.yahoo.co.jp/sam-store/0030.html
価格: 599
**************************************************
商品名: 猫おもちゃ 魚ロボット 猫電動おもちゃ 猫自動おもちゃ 猫おもちゃ自動 猫おもちゃ電動 猫おもちゃ魚
商品説明: 【ペット用おもちゃ】 電動魚ロボット 魚ロボット おもちゃ猫 電動おもちゃ猫         <br>【自動水泳機能】 水センサーで自動的にオン/オフします。自動ロボットフィンを使用して設計されています         <br>        水中に置かれるとすぐに動き始める、取り出すと自動的にオフになり、電力を節約します。        <br>【LEDライト付】LEDライトが内蔵されているので暗闇で光ります。光って動く魚に動物の狩猟本能がくすぐられます          <br>【商品使用シーン】 自動猫おもちゃ          <br>【適用ペット】 スコティッシュフォールド メインクーン ラグドール ロシアンブルー ブリティッシュ ショー ヘア         <br>                      サイベリアン キジトラ 野良猫 アメリカンカール スコティッシュ サバトラ ハチワレ 猫ちゃん<br>
キャッチコピー: ネコちゃんの狩猟本能を刺激する電動魚ロボット
76×76サイズの画像URL: https://item-shopping.c.yimg.jp/i/c/himawaridifang-store_robo-fish1
146×146サイズの画像URL: https://item-shopping.c.yimg.jp/i/g/himawaridifang-store_robo-fish1
商品URL: https://store.shopping.yahoo.co.jp/himawaridifang-store/robo-fish1.html
価格: 880

使用不能なアプリケーションIDと検索キーワード「猫」を指定する

エラーレスポンスが ErrorResponse クラスにマッピングされて情報が出力されている。

$ gradle run -q --args="invalid_application_id 猫"
appid: invalid_application_id
query: 猫
URL: https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch?query=%E7%8C%AB&results=2
statusCode: 403
エラーメッセージ: Your Request was Forbidden

参考資料

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

CameraXとOpenCVを使った画像処理Androidアプリのひな形

はじめに

Androidでカメラ画像を入力して、何らかの画像処理を行うためのサンプルプロジェクトです。

以前別の記事( Androidで OpenCV 4を使う方法とカメラライブビューの表示 )で、カメラ読み込みもOpenCVを使った方法を記載しましたが、最新のAndroid SDKだと使えないようでした。

Camera APIが廃止され、Camera2 APIを使うことが推奨されていましたが非常に使いづらいです。より簡単にAndroid上でカメラを触れるCameraXというものが存在しました。今回はこれを使ってみます。
CameraXは使い方が簡単で、チュートリアルも充実しています (https://developer.android.com/training/camerax ) 。ネット上のほとんどのサンプルがKotolin向けだったので、ここではJavaで書いてみようと思います。

今回は、入力画像と前フレームの画像との差分を計算して、変化したところが分かるような簡単な画像処理をサンプルにしてみます。
↓の動画は、上がオリジナルのカメラ画像(CameraXのプレビュー画像)、下が画像処理をした結果になります。

環境

  • Host
    • Windows 10 64-bit
    • Android Studio 4.0
    • Android SDK API Level 30
      • 多少低くても大丈夫なはず
    • Android NDK Version 21.3.6528147
      • NDKは不要かも
    • opencv-4.3.0-android-sdk.zip
  • Target
    • Galaxy S7 (Android 8.0.0)

プロジェクトの用意

プロジェクトを作る

Android StudioのCreate New Projectから、Empty Activityを作ります。

Minimum SDKはAPI23としておきました。CameraXがサポートするAndroid 5.0(API レベル 21)以降であれば何でも大丈夫なはずです。

01.jpg

Manifestの設定

カメラを使用するので、そのための設定をします。
AndroidManifest.xml を開き、以下のように編集します。(「追加」で検索)

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.samplecameraxandopencv">

<!--    ↓↓↓ 追加 ↓↓↓    -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-feature android:name="android.hardware.camera" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<!--    ↑↑↑ 追加 ↑↑↑    -->

    <application
        android:allowBackup="true"
省略
    </application>

</manifest>

OpenCVを取り込む

  • https://github.com/opencv/opencv/releases からopencv-4.3.0-android-sdk.zipをダウンロードして解凍します
    • バージョンは4以降なら何でも大丈夫です。3以前だと少しやり方が変わると思います
  • メニューバー -> File -> New -> Import Module で、OpenCV-android-sdk/sdk の場所を指定します
  • メニューバー -> File -> Project Structure の、Dependenciesを開き、appを選択します。Declared Dependencies 内で+ -> 3 Moduel Dependency をクリックして追加したsdkを選択します。

手順詳細はこちら (https://qiita.com/iwatake2222/items/2642669419fdaa20a8a6#opencvのダウンロード ) を参照。

CameraXを使えるようにする

CameraXのチュートリアルの通りです。(https://codelabs.developers.google.com/codelabs/camerax-getting-started/#1 )

build.gradle(Module: app) を開き、android セクションと dependencies セクションを以下のように編集します。(「追加」で検索)

build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"

    defaultConfig {
        applicationId "com.example.samplecameraxandopencv"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    //    ↓↓↓ 追加 ↓↓↓
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    //    ↑↑↑ 追加 ↑↑↑
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

//    ↓↓↓ 追加 ↓↓↓
    def camerax_version = "1.0.0-beta03"
// CameraX core library using camera2 implementation
    implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
    implementation "androidx.camera:camera-view:1.0.0-alpha10"
//    ↑↑↑ 追加 ↑↑↑
}

編集後、右上にSync Now と表示されるのでクリックします。
ここまで完了したら、一度ビルドと実行が出来ることを確認することをお勧めします。

レイアウトを作る

まず最初にレイアウトを作ります。

res->layout->activity_main.xmlを開き、コードを表示します。
デフォルトで用意されているTextView は消して、LinearLayout でCameraXのプレビュー用のandroidx.camera.view.PreviewView と処理結果表示用のImageView を上下に並べます。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <androidx.camera.view.PreviewView
            android:id="@+id/previewView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"/>
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"/>
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

02.jpg

コード説明

onCreate()

プレビュー表示と結果表示用のviewを取得します。その後、カメラ使用の権限を取得し、カメラ処理開始関数(startCamera )を呼びます。

startCamera()

チュートリアル(https://codelabs.developers.google.com/codelabs/camerax-getting-started/#3 )に倣ってCameraXの設定をしています。

CameraXではUseCaseという概念で、カメラに関係する処理を登録するようです。チュートリアルでは以下の3つを使用していました。

  • Preview
  • ImageCapture
  • ImageAnalysis

ImageCaptureは静止画撮影用ですが、今回は未使用です。
ImageAnalysisは各フレーム画像を解析して、何らかの処理を行うことが出来ます。チュートリアルでは平均輝度を計算するだけでしたが、今回はこの部分にOpenCVを用いた画像処理を実装します。

独自のImageAnalysisを行うためには、ImageAnalysis.Analyzer インターフェイスを持つクラスを実体化して指定します。今回はMyImageAnalyzer というクラスを作りました。

MyImageAnalyzerクラス

public void analyze(@NonNull ImageProxy image) という関数を持つクラスを自分で定義します。この中で処理を行います。
ImageProxy imageにYUV(NV21)形式の画像が入力され、毎フレーム呼ばれます(image.close(); するまで次の処理は呼ばれない模様)。

最初に、入力されるNV21画像をOpenCVのmatに変換します。変換処理は https://stackoverflow.com/questions/30510928/convert-android-camera2-api-yuv-420-888-to-rgb を参考にしました (getMatFromImage() )。

次に、入力画像を適切に回転します。過去の方法でAndroidカメラを使ったことがある方には常識的な処理ですが、どうもCameraXのプレビューだとCameraX側で適切に処理してくれているようです。一方、この関数に入ってくる画像に対してはケアされていないようなので、自分で回転処理をする必要があります。今回はここでもOpenCVを使って回転・反転処理を行いました (fixMatRotation() )。

OpenCVのmat(RGB)になったら、後は好きな処理をするだけです。
今回は前回フレームとの画素値差分を計算して、変化のあったところだけ表示するような処理にしてみました。
ついでに、適当に四角形と文字を出力してみました。

最後に、OpenCVのmatをBitmapに変換し、ImageViewに出力します。ImageViewへの描画はUIスレッドで行っています。

コード全文

MainActivity.java
package com.example.samplecameraxandopencv;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.widget.ImageView;

import com.google.common.util.concurrent.ListenableFuture;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {
    /*** Fixed values ***/
    private static final String TAG = "MyApp";
    private int REQUEST_CODE_FOR_PERMISSIONS = 1234;;
    private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};

    /*** Views ***/
    private PreviewView previewView;
    private ImageView imageView;
    /*** For CameraX ***/
    private Camera camera = null;
    private Preview preview = null;
    private ImageAnalysis imageAnalysis = null;
    private ExecutorService cameraExecutor = Executors.newSingleThreadExecutor();

    static {
        System.loadLibrary("opencv_java4");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        previewView = findViewById(R.id.previewView);
        imageView = findViewById(R.id.imageView);

        if (checkPermissions()) {
            startCamera();
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_FOR_PERMISSIONS);
        }
    }

    private void startCamera() {
        final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
        Context context = this;
        cameraProviderFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {
                    ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                    preview = new Preview.Builder().build();
                    imageAnalysis = new ImageAnalysis.Builder().build();
                    imageAnalysis.setAnalyzer(cameraExecutor, new MyImageAnalyzer());
                    CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();

                    cameraProvider.unbindAll();
                    camera = cameraProvider.bindToLifecycle((LifecycleOwner)context, cameraSelector, preview, imageAnalysis);
                    preview.setSurfaceProvider(previewView.createSurfaceProvider(camera.getCameraInfo()));
                } catch(Exception e) {
                    Log.e(TAG, "[startCamera] Use case binding failed", e);
                }
            }
        }, ContextCompat.getMainExecutor(this));
    }

    private class MyImageAnalyzer implements ImageAnalysis.Analyzer {
        private Mat matPrevious = null;

        @Override
        public void analyze(@NonNull ImageProxy image) {
            /* Create cv::mat(RGB888) from image(NV21) */
            Mat matOrg = getMatFromImage(image);

            /* Fix image rotation (it looks image in PreviewView is automatically fixed by CameraX???) */
            Mat mat = fixMatRotation(matOrg);

            Log.i(TAG, "[analyze] width = " + image.getWidth() + ", height = " + image.getHeight() + "Rotation = " + previewView.getDisplay().getRotation());
            Log.i(TAG, "[analyze] mat width = " + matOrg.cols() + ", mat height = " + matOrg.rows());

            /* Do some image processing */
            Mat matOutput = new Mat(mat.rows(), mat.cols(), mat.type());
            if (matPrevious == null) matPrevious = mat;
            Core.absdiff(mat, matPrevious, matOutput);
            matPrevious = mat;

            /* Draw something for test */
            Imgproc.rectangle(matOutput, new Rect(10, 10, 100, 100), new Scalar(255, 0, 0));
            Imgproc.putText(matOutput, "leftTop", new Point(10, 10), 1, 1, new Scalar(255, 0, 0));

            /* Convert cv::mat to bitmap for drawing */
            Bitmap bitmap = Bitmap.createBitmap(matOutput.cols(), matOutput.rows(),Bitmap.Config.ARGB_8888);
            Utils.matToBitmap(matOutput, bitmap);

            /* Display the result onto ImageView */
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });

            /* Close the image otherwise, this function is not called next time */
            image.close();
        }

        private Mat getMatFromImage(ImageProxy image) {
            /* https://stackoverflow.com/questions/30510928/convert-android-camera2-api-yuv-420-888-to-rgb */
            ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
            ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
            ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
            int ySize = yBuffer.remaining();
            int uSize = uBuffer.remaining();
            int vSize = vBuffer.remaining();
            byte[] nv21 = new byte[ySize + uSize + vSize];
            yBuffer.get(nv21, 0, ySize);
            vBuffer.get(nv21, ySize, vSize);
            uBuffer.get(nv21, ySize + vSize, uSize);
            Mat yuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CvType.CV_8UC1);
            yuv.put(0, 0, nv21);
            Mat mat = new Mat();
            Imgproc.cvtColor(yuv, mat, Imgproc.COLOR_YUV2RGB_NV21, 3);
            return mat;
        }

        private Mat fixMatRotation(Mat matOrg) {
            Mat mat;
            switch (previewView.getDisplay().getRotation()){
                default:
                case Surface.ROTATION_0:
                    mat = new Mat(matOrg.cols(), matOrg.rows(), matOrg.type());
                    Core.transpose(matOrg, mat);
                    Core.flip(mat, mat, 1);
                    break;
                case Surface.ROTATION_90:
                    mat = matOrg;
                    break;
                case Surface.ROTATION_270:
                    mat = matOrg;
                    Core.flip(mat, mat, -1);
                    break;
            }
            return mat;
        }
    }

    private boolean checkPermissions(){
        for(String permission : REQUIRED_PERMISSIONS){
            if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
                return false;
            }
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == REQUEST_CODE_FOR_PERMISSIONS){
            if(checkPermissions()){
                startCamera();
            } else{
                Log.i(TAG, "[onRequestPermissionsResult] Failed to get permissions");
                this.finish();
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで糖質制限の情報をまとめたサイトを作った

はじめに

こんなサイトを作りました。
https://lowcarb-recommend.com/
toppage.PNG

3月中旬くらいにそろそろ手を出すか~とVue.jsのお勉強を始め、ゴールデンウィーク終わりごろにそろそろ何か作りたいなーと思っていた時、ちょうど自粛生活で太り気味で糖質制限ダイエットを始めていたので、それに関する情報をまとめたサイトを作ろうと思い作成を始めました。

どんな機能があるの?

機能としては下記3点です。

  1. 糖質制限食をランキングで閲覧
  2. 糖質制限レシピの閲覧
  3. 糖質制限に関するニュースの閲覧

1.の糖質制限食に関しては楽天市場系APIより情報を取得し、ショップごと、もしくはジャンルごと(パンや麺類など)で検索をかけられるようにしました

2.の糖質制限レシピについては、これも楽天レシピ系APIより情報を取得しています。

3.のニュースに関してはGoogle NewsよりRSSを取得しています。

環境

  • Java
  • Spring Boot
  • Vue.js(Vue Router、Vuex、Vuetify)
  • Swiper.js
  • Heroku
  • Mongo DB
  • Netlify

もともとはVue.jsを使いたかったためフロントエンドは確定として、バックエンドはどうしようかなーと思ってたのですが、全部が慣れていないものだと作るのに時間がかかる→最後まで作り切れなさそうと少しビビっていたので業務で使っているJavaを選びました。

製作期間

  • 2か月弱

バックエンドは、仕事で使っているJavaということもあり、2週間足らずで終わらせることができました。
フロントエンドについては、学び始めたばかりのVue.jsということもあり、1か月半かけて調べながら完成させました。

苦労したところ

下記に、苦労した点と参考にさせていただいたものをまとめます。

ニュース情報をどう集めるか

当初はニュース関連のサービスでよく使われているであろうNews APIを使おうと思ってました。
しかし、9割9分自分の使い方がまずかったと思うのですが、うまく糖質制限に関する情報を取得できず、代替案を考えていたところ、Google NewsよりRSSを取得する方法を見つけたので、それに切り替えました。

Google News RSSを取得する方法は下記を参考にさせていただきました。
https://qiita.com/KMD/items/872d8f4eed5d6ebf5df1
http://mogakana.blogspot.com/2011/05/javarss.html

Swiper.jsの使い方

「TOPページになにか動くの導入してやろ~」と思いいろいろ調べてみると、当ライブラリに出会いました。
トップページに写真の上にキャプションをうまく載せられなかったり、うまくレスポンシブにできなかったりといろいろ思考錯誤しました。

Swiper.jsに関しては下記を参考にさせていただきました。
https://qiita.com/whike_chan/items/c68e094f412b04b1afc2
https://github.surmon.me/vue-awesome-swiper/
https://www.kabanoki.net/4783/

PWA化

開発も終盤にかかってきたところ、せっかくだしPWA化して「ホーム画面に追加」って出るとかっこいいなと思いいろいろ試したがなかなかLighthouseでOKにならない。
コンソールを見てみると下記エラーが出てました。

console
Site cannot be installed: no matching service worker detected. You may need to reload the page, or check that the service worker for the current page also controls the start URL from the manifest

さらに「bad-precaching-response」のようなエラーも出ていました。
Netlifyのデプロイ時に置いている_redirectsファイルのキャッシュを更新しようとする→そんなファイル無いぞと怒られるのが原因みたいだったため、下記サイトを参考にキャッシュ対象から除外しました。

vue.config.jsに下記を書くことで解決しました。

vue.config.js
module.exports = {
  //省略
  pwa: {
    workboxPluginMode: 'GenerateSW',
    workboxOptions: {
      exclude: /_redirects/
    }
  }
}

下記サイトを参考にさせていただきました。
http://t-kuni.sub.jp/2020/06/12/pwa%e3%81%a7%e6%96%b0%e3%83%90%e3%83%bc%e3%82%b8%e3%83%a7%e3%83%b3%e3%82%92%e3%83%aa%e3%83%aa%e3%83%bc%e3%82%b9%e3%81%97%e3%81%a6%e3%82%82%e3%83%90%e3%83%bc%e3%82%b8%e3%83%a7%e3%83%b3%e3%81%8c/#%E7%B5%90%E6%9E%9C

https://michimani.net/post/programming-build-pwa-with-vuejs/

教訓

開発中、大枠は1か月くらいでできた(つもりだった)ため、もうすぐ終わるだろうと思っていたら、結局2か月かかってしまいました。
細かいところで修正したいところがたくさん出てくるんですよね、、開発しながら下記故事成語があったな~と思いだしたので、今後開発するときはこれを頭に入れておいて、「9割終わったと思うけど多分まだ5割なんだよな~」と思っておくと気が楽になるかもしれません、、

百里を行く者は九十を半ばとす
https://imidas.jp/proverb/detail/X-02-C-27-8-0010.html

おわりに

いろいろ書きましたが、ぜひ使ってみてください!
ロカボのススメ

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

自粛生活で太ったので、糖質制限の情報をまとめたサイトを作った

はじめに

こんなサイトを作りました。
https://lowcarb-recommend.com/
toppage.PNG

3月中旬くらいにそろそろ手を出すか~とVue.jsのお勉強を始め、ゴールデンウィーク終わりごろにそろそろ何か作りたいなーと思っていた時、ちょうど自粛生活で太り気味で糖質制限ダイエットを始めていたので、それに関する情報をまとめたサイトを作ろうと思い作成を始めました。

どんな機能があるの?

機能としては下記3点です。

  1. 糖質制限食をランキングで閲覧
  2. 糖質制限レシピの閲覧
  3. 糖質制限に関するニュースの閲覧

1.の糖質制限食に関しては楽天市場系APIより情報を取得し、ショップごと、もしくはジャンルごと(パンや麺類など)で検索をかけられるようにしました

2.の糖質制限レシピについては、これも楽天レシピ系APIより情報を取得しています。

3.のニュースに関してはGoogle NewsよりRSSを取得しています。

環境

  • Java
  • Spring Boot
  • Vue.js(Vue Router、Vuex、Vuetify)
  • Swiper.js
  • Heroku
  • Mongo DB
  • Netlify

もともとはVue.jsを使いたかったためフロントエンドは確定として、バックエンドはどうしようかなーと思ってたのですが、全部が慣れていないものだと作るのに時間がかかる→最後まで作り切れなさそうと少しビビっていたので業務で使っているJavaを選びました。

製作期間

  • 2か月弱

バックエンドは、仕事で使っているJavaということもあり、2週間足らずで終わらせることができました。
フロントエンドについては、学び始めたばかりのVue.jsということもあり、1か月半かけて調べながら完成させました。

苦労したところ

下記に、苦労した点と参考にさせていただいたものをまとめます。

ニュース情報をどう集めるか

当初はニュース関連のサービスでよく使われているであろうNews APIを使おうと思ってました。
しかし、9割9分自分の使い方がまずかったと思うのですが、うまく糖質制限に関する情報を取得できず、代替案を考えていたところ、Google NewsよりRSSを取得する方法を見つけたので、それに切り替えました。

Google News RSSを取得する方法は下記を参考にさせていただきました。
https://qiita.com/KMD/items/872d8f4eed5d6ebf5df1
http://mogakana.blogspot.com/2011/05/javarss.html

Swiper.jsの使い方

「TOPページになにか動くの導入してやろ~」と思いいろいろ調べてみると、当ライブラリに出会いました。
トップページに写真の上にキャプションをうまく載せられなかったり、うまくレスポンシブにできなかったりといろいろ思考錯誤しました。

Swiper.jsに関しては下記を参考にさせていただきました。
https://qiita.com/whike_chan/items/c68e094f412b04b1afc2
https://github.surmon.me/vue-awesome-swiper/
https://www.kabanoki.net/4783/

PWA化

開発も終盤にかかってきたところ、せっかくだしPWA化して「ホーム画面に追加」って出るとかっこいいなと思いいろいろ試したがなかなかLighthouseでOKにならない。
コンソールを見てみると下記エラーが出てました。

console
Site cannot be installed: no matching service worker detected. You may need to reload the page, or check that the service worker for the current page also controls the start URL from the manifest

さらに「bad-precaching-response」のようなエラーも出ていました。
Netlifyのデプロイ時に置いている_redirectsファイルのキャッシュを更新しようとする→そんなファイル無いぞと怒られるのが原因みたいだったため、下記サイトを参考にキャッシュ対象から除外しました。

vue.config.jsに下記を書くことで解決しました。

vue.config.js
module.exports = {
  //省略
  pwa: {
    workboxPluginMode: 'GenerateSW',
    workboxOptions: {
      exclude: /_redirects/
    }
  }
}

下記サイトを参考にさせていただきました。
http://t-kuni.sub.jp/2020/06/12/pwa%e3%81%a7%e6%96%b0%e3%83%90%e3%83%bc%e3%82%b8%e3%83%a7%e3%83%b3%e3%82%92%e3%83%aa%e3%83%aa%e3%83%bc%e3%82%b9%e3%81%97%e3%81%a6%e3%82%82%e3%83%90%e3%83%bc%e3%82%b8%e3%83%a7%e3%83%b3%e3%81%8c/#%E7%B5%90%E6%9E%9C

https://michimani.net/post/programming-build-pwa-with-vuejs/

教訓

開発中、大枠は1か月くらいでできた(つもりだった)ため、もうすぐ終わるだろうと思っていたら、結局2か月かかってしまいました。
細かいところで修正したいところがたくさん出てくるんですよね、、開発しながら下記故事成語があったな~と思いだしたので、今後開発するときはこれを頭に入れておいて、「9割終わったと思うけど多分まだ5割なんだよな~」と思っておくと気が楽になるかもしれません、、

百里を行く者は九十を半ばとす
https://imidas.jp/proverb/detail/X-02-C-27-8-0010.html

おわりに

いろいろ書きましたが、ぜひ使ってみてください!
ロカボのススメ

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

Spring の RestTemplate で Yahoo!ショッピング商品検索(v3) の API をコールするサンプルコード

概要

  • Spring が提供する HTTP クライアント RestTemplate を使って、Yahoo!ショッピング商品検索 (v3)の API をコールするサンプルプログラムを作る

今回の環境

  • Java 11 (AdoptOpenJDK 11.0.7+10)
  • Spring Web 5.2.7
  • Jackson Databind 2.11.0
  • Gradle 6.5
  • macOS Catalina

ソースコード

ソースコード一覧

├── build.gradle
└── src
    └── main
        └── java
            └── com
                └── example
                    ├── ErrorResponse.java
                    ├── ItemSearch.java
                    └── ResultSet.java

build.gradle

plugins {
  id 'application'
  id 'java'
}

group 'org.example'
version '0.0.1'
mainClassName = "com.example.ItemSearch"
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {

  // RestTemplate を使うために必要
  // https://mvnrepository.com/artifact/org.springframework/spring-web
  implementation 'org.springframework:spring-web:5.2.7.RELEASE'

  // JSON とクラスのマッピングに必要
  // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0'
}

src/main/java/com/example/ItemSearch.java

package com.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

/**
 * ショッピング:商品検索(v3) - Yahoo!デベロッパーネットワーク
 * https://developer.yahoo.co.jp/webapi/shopping/shopping/v3/itemsearch.html
 */
public class ItemSearch {

  public static void main(String[] args) {

    try {
      String appid = args[0];
      String query = args[1];
      System.out.println("appid: " + appid); // アプリケーションID
      System.out.println("query: " + query); // 検索キーワード

      // 商品を検索
      ResultSet rs = new ItemSearch().search(appid, query);
      for (ResultSet.Hit hit : rs.hits) {
        System.out.println("**************************************************");
        System.out.println("商品名: " + hit.name);
        System.out.println("商品説明: " + hit.description);
        System.out.println("キャッチコピー: " + hit.headLine);
        System.out.println("76×76サイズの画像URL: " + hit.image.small);
        System.out.println("146×146サイズの画像URL: " + hit.image.medium);
        System.out.println("商品URL: " + hit.url);
        System.out.println("価格: " + hit.price);
      }
    } catch (Exception e) {
      System.out.println("エラー発生: " + e);
    }
  }

  /**
   * 商品を検索する。
   * @param appid アプリケーションID
   * @param query 検索キーワード
   * @return 検索結果
   * @throws Exception エラー発生時
   */
  public ResultSet search(String appid, String query) throws Exception {

    try {
      // API コール用の URL を組み立てる
      String baseurl = "https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch";
      String url = UriComponentsBuilder.fromUri(new URI(baseurl))
        .queryParam("query", query) // 検索キーワード
        .queryParam("results", 2) // 検索結果2件まで
        .build(false)
        .encode()
        .toUriString();
      System.out.println("URL: " + url);

      // HTTP リクエスト情報を構築
      HttpHeaders reqHeaders = new HttpHeaders();
      reqHeaders.set("User-Agent", "Yahoo AppID: " + appid); // アプリケーションID
      RequestEntity req = RequestEntity.get(new URI(url))
        .headers(reqHeaders)
        .build();

      // API をコールして結果を取得
      RestTemplate restTemplate = new RestTemplate();
      ResponseEntity<ResultSet> res = restTemplate.exchange(req, ResultSet.class);
      return res.getBody();

    } catch (RestClientResponseException e) {

      // レスポンスのステータス情報を出力
      int statusCode = e.getRawStatusCode();
      String statusText = e.getStatusText();
      System.out.println("RestClientResponseException.RawStatusCode: " + statusCode);
      System.out.println("RestClientResponseException.StatusText: " + statusText);

      // エラー情報を出力
      String body = e.getResponseBodyAsString();
      ObjectMapper mapper = new ObjectMapper();
      ErrorResponse errorResponse = mapper.readValue(body, ErrorResponse.class);
      System.out.println("エラーメッセージ: " + errorResponse.error.message);

      throw e;

    } catch (Exception e) {
      throw e;
    }
  }
}

src/main/java/com/example/ResultSet.java

検索結果のレスポンスを表すクラス。

package com.example;

import java.util.List;

/**
 * 検索結果。
 */
public class ResultSet {

  public List<Hit> hits;

  public static class Hit {

    // hits/name string 商品名
    public String name;

    // hits/description string 商品説明
    public String description;

    // hits/headLine string キャッチコピー
    public String headLine;

    // hits/image
    public Image image;

    // hits/url string 商品URL
    public String url;

    // hits/price integer 価格
    public int price;
  }

  public static class Image {

    // hits/image/small string 76×76サイズの画像URL
    public String small;

    // hits/image/medium string 146×146サイズの画像URL
    public String medium;
  }
}

src/main/java/com/example/ErrorResponse.java

エラーレスポンス情報を表すクラス。

package com.example;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * エラー情報。
 */
public class ErrorResponse {

  @JsonProperty("Error")
  public Error error;

  public static class Error {

    @JsonProperty("Message")
    public String message;
  }
}

実行例

使用可能なアプリケーションIDと検索キーワード「猫」を指定する

検索結果のレスポンスが ResultSet クラスにマッピングされて情報が出力されている。

$ gradle run -q --args="your_application_id 猫"
appid: your_application_id
query: 猫
URL: https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch?query=%E7%8C%AB&results=2
**************************************************
商品名: 猫おもちゃ 魚ロボット 猫電動おもちゃ 猫自動おもちゃ 猫おもちゃ自動 猫おもちゃ電動 猫おもちゃ魚
商品説明: 【ペット用おもちゃ】 電動魚ロボット 魚ロボット おもちゃ猫 電動おもちゃ猫         <br>【自動水泳機能】 水センサーで自動的にオン/オフします。自動ロボットフィンを使用して設計されています         <br>        水中に置かれるとすぐに動き始める、取り出すと自動的にオフになり、電力を節約します。        <br>【LEDライト付】LEDライトが内蔵されているので暗闇で光ります。光って動く魚に動物の狩猟本能がくすぐられます          <br>【商品使用シーン】 自動猫おもちゃ          <br>【適用ペット】 スコティッシュフォールド メインクーン ラグドール ロシアンブルー ブリティッシュ ショー ヘア         <br>                      サイベリアン キジトラ 野良猫 アメリカンカール スコティッシュ サバトラ ハチワレ 猫ちゃん<br>
キャッチコピー: ネコちゃんの狩猟本能を刺激する電動魚ロボット
76×76サイズの画像URL: https://item-shopping.c.yimg.jp/i/c/himawaridifang-store_robo-fish1
146×146サイズの画像URL: https://item-shopping.c.yimg.jp/i/g/himawaridifang-store_robo-fish1
商品URL: https://store.shopping.yahoo.co.jp/himawaridifang-store/robo-fish1.html
価格: 880
**************************************************
商品名: 猫 ハーネス 猫用リード 猫用品 ペット用品
商品説明: 脱走対策として<br><br>猫は通院やお引越しなどで慣れない移動をさせられると、パニックを起こして逃げ出そうとしたり、暴れたりすることが多いものです。そんな時、ペットケージに入れておくだけでなく、ハーネスを装着した上でペットケージに入れるという二重の備えをしておくと、とても安心です。また万が一、大災害の被害を受けて避難しなければならなくなった時にも脱走防止に役立つので、防災グッズの中に常備しておく飼い主も増えています。<br><br>お散歩用として<br><br>脱走防止目的だけでなく、猫のお散歩用として使用するのもおすすめです。ハーネスは猫の身体の動きを邪魔しにくいので、のびのびと運動できて良いストレス発散になることでしょう。ただし、猫の性格によっては外に連れ出すことがかえってストレスとなる場合もありますので、お散歩はくれぐれも猫の様子をみつつ、交通状況などにも気を配りながら行ってください。<br><br>カラーバリエーションは、赤・青・黒・ピンクの4色です。<br>ナイロン製<br><br>首回り16〜26センチ<br>胴回り26〜36センチ<br>リードの長さ110cm<br><br>検索キーワード:猫リード・猫ハーネス・猫用リード・ポイント消化・送料無料
キャッチコピー: 猫ハーネス猫リード猫用リード猫首輪
76×76サイズの画像URL: https://item-shopping.c.yimg.jp/i/c/sam-store_0030
146×146サイズの画像URL: https://item-shopping.c.yimg.jp/i/g/sam-store_0030
商品URL: https://store.shopping.yahoo.co.jp/sam-store/0030.html
価格: 599

使用不能なアプリケーションIDと検索キーワード「猫」を指定する

エラーレスポンスが ErrorResponse クラスにマッピングされて情報が出力されている。

$ gradle run -q --args="invalid_application_id 猫"
appid: invalid_application_id
query: 猫
URL: https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch?query=%E7%8C%AB&results=2
RestClientResponseException.RawStatusCode: 403
RestClientResponseException.StatusText: Forbidden
エラーメッセージ: Your Request was Forbidden
エラー発生: org.springframework.web.client.HttpClientErrorException$Forbidden: 403 Forbidden: [{
"Error" : {
"Message" : "Your Request was Forbidden"
}
} ... (512 bytes)]

参考資料

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

SpringBoot における各コンポーネントの役割

SpringBoot における各コンポーネントの役割

コンポーネントとは

アーキテクチャが構造、構成を意味する言葉であるとしたら、コンポーネントとは何かしらの部品を現す言葉である。

車というアーキテクチャがあれば、エンジンやAT、タイヤやハンドルなどは車を構成するためのコンポーネントであるといえる。

大まかなコンポーネントの種類

SpringBootの基本構成に基づきアプリケーションのアーキテクチャスタイルをMVCモデルとして構成すると、コンポーネントは下記となる。

コンポーネント 説明         
View   クライアント(PCなど)に送出するレスポンスデータを構成するためのコンポーネント。JSPやThymeleafを内部的に使用する。一般的にはHTML形式のデータを構成する処理を持つ。
Model(Form) 送信されたリクエスト(http通信におけるget送信やpost送信などが代表的)と対応するFormオブジェクトのフィールドにリクエストデータをバインドする。また、リクエストデータをチェックする。(バリデーション)
Controller リクエストをハンドリングするためのコンポーネント。リクエストデータを一次加工したり、下位コンポーネント(Facadeなど)に受け渡し、実行する。
 Helper ViewやControllerで使用する簡易なデータの加工処理を受け持つ。
Facade Controllerに呼び出され、処理要件に対応する。ビジネスロジックの窓口となるコンポーネント。Facadeの1メソッドが1トランザクションとなり、下位コンポーネント(Service)のビジネスロジックを必要な順序で呼び出す。データの保存や参照、削除といった具体的なビジネスロジックは下位コンポーネント(Service)に任せ、内部ではそれらのビジネスロジックを協調させる処理を行う。
Service 保存や、参照、削除といった個別のビジネスロジックを各メソッドに持たせる。データアクセスは本コンポーネントで行う。
Repository 永続化されたデータへのアクセス、コントロールを実行するコンポーネント。実際にデータベースとやり取りするのは下位コンポーネント(Dao)となる。
Dao リレーショナル・データベースへのデータアクセスの実装をするコンポーネント。内部的にMyBatis等を使用する。XMLに記述したSQLを実行する。

登場用語説明

バインドとは何かと何かを割り当てるという意味を持つ。リクエストデータをFormオブジェクトのフィールドに割り当てるなど。

バリデーションとは入力内容や記述内容が要件を満たしているか、妥当性を確認すること。入力した電話番号が0スタートかどうかなど。

ハンドリングとは何かを捕まえるという意味を持つ。formのリクエストデータをControllerが捕まえる。

トランザクションとは処理単位を意味する。りんご(リクエストデータ)を受け取り、皮を剥き(ビジネスロジック)、4つに切り分け(ビジネスロジック)、食べる(ビジネスロジック)。このビジネスロジックのまとまりが一つのトランザクションであるといえる。

データの永続化とは、アプリケーションを落としてもデータが保持され続ける。極めて簡単に言えばデータベースに保存すること。

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

LINEBOTを使ってオーキド博士を作った話

はじめに

LINEのMessagingAPIを使って何か作成したいなと考えていたところ、
友人がポケモンにハマっているという話を聞いて、ポケモンの検索やランダムでポケモンを取得し、種族値合計で他のユーザと対戦するようなものを作成しようと思いました。

作成したBOTは↓のURLから追加できます。ぜひお試しください!
lin.ee/zmHQYl6

環境

  • サーバー(2台構成)
    • APサーバー(Nginx)
    • DBサーバー(PostgreSQL)
  • 作成言語
    • Java(Spring Boot)
    • Messaging API(API)

できること

  • ポケモン検索
    • 図鑑番号検索
    • ポケモン名検索(LIKE検索)
  • ランダム取得
    • ランダム取得(1体)
    • ランダム取得(6体)
  • 種族値合計ランキング
    • 歴代ランキング
    • 月間ランキング
  • すばやさ確認
    • 作成中

完成形

  • ポケモン検索

    • 図鑑番号検索 image.png
    • ポケモン名検索(LIKE検索) image.png
  • ランダム取得(1体)
    image.png

  • ランダム取得(6体) + ランキング
    image.png

素晴らしい機能

FlexMessage という機能が素晴らしかったです。(今回の検索結果の表示等に使っている機能)
レイアウトについてはFLEX MESSAGE SIMULATORで簡単に作成できるので簡単にそれっぽいレイアウトが作成できます。

最後に

Messaging APIをつかったLINEBOTの作成についてはLINEの公式ドキュメントが充実しているので特に詰まってしまうような箇所はありませんでした。
簡単にできるのぜひ皆さんお試しください。

参考にしたサイト

Messaging APIリファレンス

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

【AWS SDK for Java】S3クライアントにリトライポリシーを設定する

ジャストほしい情報が日本語でなかったのでドキュメンテーションする。
AWS SDK for Javaを利用してS3にファイルをアップロードする際に、やや詳細な設定を付加する。

目次

  • リトライ周りのケース概略
  • とりあえず実装してみる
  • 関連クラスの整理
  • その他
  • 参考

リトライ周りのケース概略

AWS SDK for Javaを使って、ローカルにあるS3にファイルをアップロードしたい。
そして、AWS側の都合で通信断となったり諸事情により正常稼働できないケースを見越して、AWSクライアントにリトライポリシーを仕込みたい。
今回は、AWS SDK for Javaに含まれる AmazonS3 オブジェクトのシングルトンを返すユーティリティクラスを用意する。

なお、本記事では自分が特に困った「クライアント生成時のオプション設定」にフォーカスを置く。そのため、ファイルアップロード前後の処理関連の説明は省略する。

とりあえず実装してみる

シンプルなクライアントオプションを詰めたクライアントを構築する。
流れとしては、リトライポリシーインスタンス、クライアント設定インスタンス、そしてクライアントの3つを準備していく。

まずは、ポリシーの基本設定やAWS定義のストラテジーなどを詰めた RetryPolicy インスタンスを返すメソッドを用意。

AmazonS3Utils.java
/**
 * ExponentialBackoffStrategyのコンストラクタにbaseDelayとmaxBackoffTimeの適切な値を入れる。
 * 注意:ここではサンプルとして適当な値を入れてます。
 */
private static RetryPolicy getS3BaseRetryPolicy() {
        return new RetryPolicy(
                new PredefinedRetryPolicies.SDKDefaultRetryCondition(),
                new PredefinedBackoffStrategies.ExponentialBackoffStrategy(0, 0),
                PredefinedRetryPolicies.DEFAULT_MAX_ERROR_RETRY,
                true
        );
    }

次に、↑で定義したRetryPolicyインスタンスを返すメソッドを呼んで、クライアント設定を定義する。

AmazonS3Utils.java
private static final ClientConfiguration clientConfiguration = new ClientConfiguration()
            .withRetryPolicy(getS3BaseRetryPolicy());

そして、他のクラスやメソッドが呼び出すS3クライアントのエントリーポイントとなる AmazonS3 オブジェクトを、クライアント設定を詰めて初期化。

AmazonS3Utils.java
private static final AmazonS3 amazonS3 = AmazonS3ClientBuilder
            .standard()
            .withClientConfiguration(clientConfiguration)
            .withRegion(Regions.DEFAULT_REGION)
            .build();

ここまでの設定をまとめると、

AmazonS3Utils.java
public final class AmazonS3Utils {

    /**
     * リトライポリシーを反映した、クライアント設定を初期化。
     */
    private static final ClientConfiguration clientConfiguration = new ClientConfiguration()
            .withRetryPolicy(getS3BaseRetryPolicy());

    /**
     * AmazonS3インスタンスを初期化。
     */
    private static final AmazonS3 amazonS3 = AmazonS3ClientBuilder
            .standard()
            .withClientConfiguration(clientConfiguration)
            .withRegion(Regions.DEFAULT_REGION).build();

    /**
     * ファイル操作を提供するオブジェクトを返す
     */
    public static AmazonS3 getS3() {
        return amazonS3;
    }

    /**
     * リトライポリシーをインスタンス化。
     */
    private static RetryPolicy getS3BaseRetryPolicy() {
        return new RetryPolicy(
                new PredefinedRetryPolicies.SDKDefaultRetryCondition(),
                new PredefinedBackoffStrategies.ExponentialBackoffStrategy(0, 0),
                PredefinedRetryPolicies.DEFAULT_MAX_ERROR_RETRY,
                true
        );
    }
}

これで準備完了。あとは、getS3メソッドで設定を反映したS3クライアントを呼び出せるようになる。

登場オブジェクトの整理

実装例に出てくる複数のクラスの役割を整理してみたい。
自分の調べ方が悪かったのか、やりたいことをさくっとできる感じではなかった...

AmazonS3(インターフェース)

S3のオブジェクトを操作するメソッドを定義するインターフェース。
具体的な実装はクライアントビルダークラスにより提供されるようだ。
AmazonS3
このオブジェクトはインターフェースであり、具はAmazonS3ClientBuilderなどにより提供されるようだ。

もともと、リトライ周りの設定を追加する前はAmazonS3ClientBuilder.standard().build();で済ませていた。最小限の機能で済むなら、そのようなミニマルなクライアントで十分かと思う。

ClientConfiguration(クラス)

ClientConfigurationは、リトライやプロキシ、ユーザエージェント文字列などAWSクライアントに追加できるオプションの設定クラス。
今回の例では自分は多少カスタマイズを加えたRetryPolicyを渡したが、PredefinedRetryPoliciesにあるstaticメソッドであるgetDefaultRetryPolicy()を呼び出すことで、デフォルト設定をクライアント設定に詰められるようだ。

RetryPolicy(クラス)

RetryPolicyは、 ClientConfiguration と組み合わせて、リトライポリシーを構築するクラス。
コンストラクタに必要なオブジェクトや値を詰めて初期化する。

コンストラクタには、RetryConditionBackoffStrategyなどのインターフェースが引数に指定されているのだが、引数のインターフェースに合うクラスに何を指定すればよいのか、最初悩んだ。いくつかコンストラクタがあるが、そのうちの一つを挙げてみると

RetryPolicy.java
public RetryPolicy(RetryCondition retryCondition,
    BackoffStrategy backoffStrategy,
    int maxErrorRetry,
    boolean honorMaxErrorRetryInClientConfig) {
    this(retryCondition, backoffStrategy, maxErrorRetry, honorMaxErrorRetryInClientConfig, false); 
} 

いろいろ調べていくうちに、どうやらSDKは事前にある程度定義したポリシーを詰められるクラスを用意していたとわかった。

RetryConditionインターフェースには、今回の実装ではAWS SDKのデフォルトに従うことにした。PredefinedRetryPoliciesに、何種類か、使えそうな実装があるのでRetryPolicyのコンストラクタに渡せばよい。
設定にこだわりがないのであれば、デフォルト設定を返してくれるstaticプロパティDEFAULT_RETRY_CONDITIONを指定すればよさそう。

BackoffStrategyインターフェースを拡張した抽象クラスV2CompatibleBackoffStrategyが用意されており、この抽象クラスを拡張したアダプターおよび各種具象クラスを使えばBackoffStrategyインターフェース相当の引数を設定できる。

詳しくいうと、PredefinedBackoffStrategiesには V2CompatibleBackoffStrategyAdapterを拡張した、

  • FullJitterBackoffStrategy
  • EqualJitterBackoffStrategy
  • EqualJitterBackoffStrategy

などが用意されている。RetryPolicyを初期化する際に、コンストラクタになんとかBackoffStrategyを渡すことで細かい設定を施すことが可能である。

その他

本当は公式のドキュメントで、いい感じに説明してくれているサムシングがある気がしている...
自分は探し当てられなかったため、「そんな無理せんでもこれ読んだらええんやけど」ってのがあればご指摘いただければありがたいです。

参考

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

【Java】気付きにくいけど恐ろしく遅くなるコード

目的

レビューをしていてIntegerだったりintだったりが混ざったコードに出会いました。
オートボクシングで変換掛かるから問題ないかなと一瞬思ったけど、性能面はどうなんだろうと気になってしまったので検証してみたいと思います。

検証環境

  • Eclipse Oxygen.3a Release (4.7.3a)
  • Java8

検証用ソースコード

1.参照型とプリミティブ型が混ざったパターン

qiita.java
long begin = 0L;
long end = 0L;

begin = System.currentTimeMillis();

Integer sum = 0;

for(int i = 0; i < 1000000; i++) {
    sum += i;
}

end = System.currentTimeMillis();

System.out.println(end - begin + "ミリ秒");

2.プリミティブ型のみのパターン

qiita.java
long begin = 0L;
long end = 0L;

begin = System.currentTimeMillis();

int sum = 0;

for(int i = 0; i < 1000000; i++) {
    sum += i;
}

end = System.currentTimeMillis();

System.out.println(end - begin + "ミリ秒");

実施手順

  • 検証用ソースコードを使用して1,000,000回加算処理を行う。それぞれ3回計測することで平均時間を算出する。小数第3位を四捨五入する。

実施結果

1.参照型とプリミティブ型が混ざったパターン

  • 1回目:15ミリ秒
  • 2回目:16ミリ秒
  • 3回目:15ミリ秒
  • 平均:15.33ミリ秒

2.プリミティブ型のみのパターン

  • 1回目:5ミリ秒
  • 2回目:4ミリ秒
  • 3回目:4ミリ秒
  • 平均:4.33ミリ秒

考察

参照型とプリミティブ型が混ざったパターンは平均15.33ミリ秒に対して、プリミティブ型のみのパターンでは平均4.33ミリ秒でした。参照型とプリミティブ型が混ざっていてオートボクシングが働いていると約4倍の時間が掛かっていることが分かる。塵も積もれば山となる、データ件数にもよりますが、無意味にオートボクシングしないようにすべきということが分かった。

ではまた(^_^)ノシ

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

項目57 ローカル変数のスコープを最小限にする

前提

java開発歴1ヶ月程度の新人が、Effective Javaを読んで学習した内容をアウトプットするために書いています。自分の解釈を噛み砕いて解説し、初心者が読んでもできるだけ理解しやすい記事を目指しています。

【ざっくり】

ローカル変数のスコープを最小限にすることで、コードの可読性と保守性が向上し、ミスが生まれる可能性を減らすことができる。

【悪い例1】

badPractice1
String hoge = "hoge";
int fuga;
double piyo = 0.1;

doSomething(hoge);

... //何らかの処理

doSomething(fuga); // fugaの型なんだっけ? 初期値なんだっけ?

... //何らかの処理

{
    doSomething(piyo) // piyoの型なんだっけ? 初期値なんだっけ?
}

  • 読み手の注意を逸らしてしまう。
    -> どのような処理を行っているかを理解しようとしているのに、変数の記憶に脳のリソースを使ってしまう。
  • 読み手は、変数が使われる時点で型や初期値を思い出しにくくなる。
  • スコープが広い。
    -> スコープが広ければ広いほど、意図しない場で利用され得るため、悲惨な結果を生む可能性が高くなる。

【こう書きましょう】

goodPractice1
String hoge = "hoge";
doSomething(hoge);

... //何らかの処理

int fuga = 10;
doSomething(fuga);

... //何らかの処理

double piyo = 0.1;
{
    doSomething(piyo)
}

  • ローカル変数が初めて使われたときに宣言する。
    -> 変数の型、初期値の記憶の必要がなくなり、読み手は処理の理解に集中できる。
  • スコープを小さくする。
    -> fuga, piyoは前方へのスコープが狭まっているため、その分間違って利用される可能性が減る。
  • ローカル変数は、基本的に初期値を含むようにする。
    -> 変数を合理的に初期化するための情報が無ければ、初期化すべきタイミングではないので、情報が得られるまで先送りすべき(下記のtry-catchブロックによる例外は除く)。
exception
Hoge hoge = null;
try {
    hoge = hogeHoge();
} catch (HogeException e) {
    e.hoge();
}
hoge.doSomething();

-> このような場合には、合理的な初期化はできない。

【悪い例2】

badPractice2
Iterator<Hoge> i = hoge.iterator();
while(i.hasNext()) {
    doSomething(i.next());
}

Iterator<Hoge> i2 = hoge2.iterator();
while(i.hasNext()) { // バグがある
    doSomething(i2.next());
}
  • ローカル変数のスコープが広いため、バグを生む原因になる。
    -> コード自体に問題はないため正常にコンパイルされ、間違いに気づきにくい。

【こう書きましょう】

goodPractice2
for (Iteratro<Hoge> i = hoge.iterator(); i.hasNext()) {
    doSomething(i.next());
}
// コンパイルエラー - iが見つからない
for (Iteratro<Hoge> i2 = hoge2.iterator(); i.hasNext()) {
    doSomething(i2.next());
}
  • エラーにすぐに気づくことができる。
    -> i, i2のスコープはそれぞれforブロック内に収まっているため、スコープ外の変数を参照しようとすればコンパイルエラーが出ます。
  • 変数名の重複を避ける必要がなくなる。以下に例を示します。
goodPractice3
for (Iteratro<Hoge> i = hoge.iterator(); i.hasNext()) {
    doSomething(i.next());
}

for (Iteratro<Hoge> i = hoge2.iterator(); i.hasNext()) {
    doSomething(i.next());
}

-> とても洗練された書き方になっていてかつ、whileと比べて読みやすい。

【悪い例3】

badPractice4
public int calcTotalAmount(int price) {
    double taxRate = 1.1;
    ... // 税込み価格を計算する処理

    ... // 送料を計算する処理

    int totalAmount = taxIncludedPrice + shippingFee;

    return totalAmount;
}
  • その処理とは無関係なローカル変数がスコープ内に存在している。
    -> taxRateというローカル変数が、送料を計算するという無関係な処理のスコープの中に存在している。

【こう書きましょう】

goodPractice4
public int calcTotalAmount(int price) {
    int taxIncludedPrice = calcTaxIncludedPrice(price);
    int shippingFee = calcShippingFee(price);

    return taxIncludedPrice + shippingFee;
}

public int calcTaxIncludedPrice(int price) {
    double taxRate = 1.1;
    ... // 税込み価格を計算する処理

    return taxIncludedPrice ;
}

public int calcShippingFee(int price) {
    ... // 送料を計算する処理

    return shippingFee;
}

-> スコープ内に不必要なローカル変数を含まないので、バグの生まれる可能性を減らせる。

【まとめ】

  1. ローカル変数が初めて使われたときに宣言する。
  2. ローカル変数宣言時は、基本的に初期値を含むべき。
  3. whileよりもforループを使う(ループ終了後にループ変数が必要ない場合に限る)。
  4. メソッドを小さくして焦点をはっきりさせる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゼロから始めるScala (環境構築 mac)

Scala をはじめよう

Scala とは?

プログラミング言語の1つ。

「オブジェクト指向」と「関数型プログラミング」の両方の性質を併せ持つ (両方できる二刀流)。

Scala の「良さ」

  • オブジェクト指向と関数型プログラミングの両方を表現できる。
  • オブジェクトの「不変性」が意識された設計
  • 高いモジュール性
  • Java と互換性がある (Java のライブラリを使うことができる)
  • 非同期の計算ができる Future ライブラリを標準装備
  • 並列・分散プログラミングのためのライブラリ Akka がある

インストール on Mac

Homebrew を使うのが簡単だと思う。

環境構築は全部こいつでなんとかなる(気がする)。

探す

# scala (本体, しかし前提として Java が動く環境が必要 - 後述)
$ brew search scala
==> Formulae
scala        scala@2.11   scala@2.12   scalaenv     scalapack    scalariform  scalastyle
==> Casks
scala-ide

# sbt (scalaのビルドツール, 色々するのに便利なやつと思えば良い)
$ brew search sbt
==> Formulae
sbt                           sbt@0.13                      sbtenv

(前提として) Java をインストール

やはりHomebrew を使う。

AdoptedOpenJDK をインストールするのが良いっぽい。

AdoptedOpenJDKはJava大好きコミュニティ()により提供されているOpenJDKのバイナリ(だと思っている)。
Oracle経由でのインストールが、アカウント登録とか、自分でフォルダ配置するとか面倒になったので、変にこだわるよりは、これでサクッと入れてしまうのがコスト低いと思う...

# リポジトリの追加
$ brew tap AdoptOpenJDK/openjdk

# search
$ brew search openjdk

# install
$ brew cask install adoptopenjdk11

JAVA_HOMEのPATHを設定しよう

java_home コマンドでインストールされたディレクトリの場所を確認

$ /usr/libexec/java_home -v 11
/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home

$ なんたら/java_home -v 11して出てきた文字列を、環境変数JAVA_HOMEとしてPATHに追加

必要に応じて .bash_profile.bashrc などに記述する。

# .bashrc に書いた。
export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home
PATH=${JAVA_HOME}/bin:${PATH}

設定ファイルの再読み込み

# 以下、どちらか一方でOK
source ~/.bashrc
exec $SHELL -l

一応、チェックする。

$ java -version
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.7+10, mixed mode)

$ javac -version
javac 11.0.7

# コンパイル例
$ javac source.java

# 実行例 (.class を含めないように注意)
$ java Source

scala を入れる

(後述の)sbtだけでも良いが、一応書いておく。

# install
$ brew install scala

以上。

使ってみる

# REPL起動
$ scala

Welcome to Scala 2.13.3 (OpenJDK 64-Bit Server VM, Java 11.0.7).
Type in expressions for evaluation. Or try :help.
scala>
// 出力
scala> println("Hello scalalala")
Hello scalalala

// 演算1
scala> 1 + 2
val res1: Int = 3

// 演算2
scala> 3 * 6
val res2: Int = 18

// 演算3
scala> 8.0 / 2.0
val res3: Double = 4.0

// 終了
scala> :quit
// scala> :q でも OK
ソースファイル

拡張子は.scala

Main.scala
object Main {
  def main(args: Array[String]): Unit = {
    println("Hello scala program")
  }
}
  • コンパイルして実行
$ scalac Main.scala
$ scala Main
  • コンパイルしないで実行
$ scala Main.scala
  • REPLから実行
$ scala

Welcome to Scala 2.13.3 (OpenJDK 64-Bit Server VM, Java 11.0.7).
Type in expressions for evaluation. Or try :help.
scala> :load Main.scala

sbt を入れる

# install
$ brew install sbt

以上。

使ってみる

# REPL起動
$ sbt console
...
[info] Starting scala interpreter...
Welcome to Scala 2.12.10 (OpenJDK 64-Bit Server VM, Java 11.0.7).
Type in expressions for evaluation. Or try :help.

scala>

(あとは同じなので詳細略)

sbt でコンパイル, 実行

下準備として、何かフォルダを作っておくと良いと思います。
(名前はmyfolderとしましたが、なんでも良いです、対応する部分は読み替えてください)

$ mkdir myfolder
$ cd myfolder

myfolder下にソースファイルを書いて置きます。

HelloWorld.scala
object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("Hello, Scala World!")
  }
}

myfolder下にsbtの設定ファイルを置きます

build.sbt
scalaVersion := "2.12.10"

scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")

sbtを起動

[info] ...
sbt:myfolder>

runコマンドで実行

sbt:myfolder> run
[info] Compiling 1 Scala source to ...
[info] Running HelloWorld
Hello, Scala World!
[success] Total time: 1 s, completed 2015/02/09 15:44:44`

runコマンドでは、mainメソッドを持つオブジェクトを探して実行してくれるらしい。

というか、なんか日付がおかしい...??

参考

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

ChromeDriver 通知バー消す方法 (Java)

Selenium (ChromeDriver) での「自動テストソフトウェアによって制御されています]という通知バーを削除する方法

無題.png

この表示邪魔ですよねー。

通知バーを消すと、デベロッパーモードを無効にしますか?とポップアップが出てしまい、
なかなか方法が見つかりませんでしたが、合わせ技で通知バーを消すことができたので記事に起こしておきます。

import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("excludeSwitches", new String[] { "enable-automation" });
options.setExperimentalOption("useAutomationExtension", false);
WebDriver driver = new WebDriver(options);

これを丸ごとコードに記述することで通知バーが見事に消えます。

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

[Java]メソッド

メソッド定義

public static void メソッド名(){処理;}

public static void hello() {
  System.out.println("hello world");
}

オーバーロード

引数の型、個数を変えることで可能
同じメソッド名で引数が違う

public static void hello() {
  System.out.println("hello world");
}

public static void hello(String name) {
  System.out.println("こんにちは" + name + "さん");
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む