やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

(Java)コレクションをプリミティブ型の配列に変換する方法

Java でコレクションをプリミティブ型の配列に変換する際に迷ったのでメモ

開発環境

変換方法

まず、やりたいことは以下の通り。

// 仮にList<Integer> を作る。
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}
// ここをどうにかしたい。
int[] result = target;

List などのコレクションは、Integer などの参照型しかジェネリックに指定できません。
その為、プリミティブ型のint 配列などへ変換することができません。
"java collection to array primitive" などで検索しましたが、先頭に引っかかったStackOverflow では以下のようなコメントが有りました。

stackoverflow.com

Unfortunately, I don't believe there really is a better way of doing this due to the nature of Java's handling of primitive types, boxing, arrays and generics. In particular:

・List.toArray won't work because there's no conversion from Integer to int

・You can't use int as a type argument for generics, so it would have to be an int-specific method (or one which used reflection to do nasty trickery).

I believe there are libraries which have autogenerated versions of this kind of method for all the primitive types (i.e. there's a template which is copied for each type). It's ugly, but that's the way it is I'm afraid :(

Even though the Arrays class came out before generics arrived in Java, it would still have to include all the horrible overloads if it were introduced today (assuming you want to use primitive arrays).

そうかー、良い方法は無いのか…
と、ネガティブな結果となりましたが、それでも2018年だし何かしらベターな方法があるのでは?
と思い調査しました。
その結果、Apache Commons Language3 にプリミティブ型配列への変換機能を見つけました。

Apache Commons Language3 を使う
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
} 

// org.apache.commons.lang3.ArrayUtils#toPrimitive(Integer[]) メソッドを使用して
// List<Integer> → Integer[] → int[] の順にキャストする。
int[] result = org.apache.commons.lang3.ArrayUtils.toPrimitive(target.toArray(new Integer[0]));

コレクションを使用した場合、一度参照型の配列へ変換する必要があります。
あまり美しくはないですが、使用頻度を考慮すればこれでも良い気がします。

ただ、

target.toArray(new Integer[0])

この部分がものすごく書き心地が悪いのでもう少し頑張りたいところ。

そこで、org.apache.commons.lang3.ArrayUtils#toPrimitive(Integer[]) メソッドの実装を確認したところ、以下のようになっていました。

public static int[] toPrimitive(final Integer[] array) {
    if (array == null) {
        return null;
    } else if (array.length == 0) {
        return EMPTY_INT_ARRAY;
    }
    final int[] result = new int[array.length];
    for (int i = 0; i < array.length; i++) {
        result[i] = array[i].intValue();
    }
    return result;
}

上記のStackOverflow の質問者も似たようなコードを書いていました。
なので、こちらを参考にした変換メソッドを作ってみました。

独自の変換メソッドを使う
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

final int[] result = new int[target.size()];
for (int i=0; i<result.length; i++) {
    result[i] = target.get(i);
}

ほんとにそのまま持ってきただけです。
やっぱりfor 文を使用すると途端にわかりにくくなるのは否めません。

とはいえ、上記のどちらもUtils のstatic メソッドにしてしまえば使用者が見ることは無いのでどちらでも良い気がします。
どちらでも良いのですが、個人的事情もあり、今回は実行速度の早い方を採用したいと思います。

どちらが速いか?
  • for Apache Commons Language3
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

long begin = System.currentTimeMillis();

int[] result = org.apache.commons.lang3.ArrayUtils.toPrimitive(target.toArray(new Integer[0]));

long finish = System.currentTimeMillis();
System.out.println((finish - begin) + "ms");
  • for 独自の変換メソッド
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

long begin = System.currentTimeMillis();

final int[] result = new int[target.size()];
for (int i=0; i<result.length; i++) {
    result[i] = target.get(i);
}

long finish = System.currentTimeMillis();
System.out.println((finish - begin) + "ms");

実行結果は以下の通り。

  • for Apache Commons Language3 : 8ms
  • for 独自の変換メソッド : 1ms

厳密にベンチマークテストできているわけではないですが、明らかに差があります。
ということで、速度を求めた結果、以下のようになりました。

public final class ArrayUtils {
    
    public static int[] toArray(List<Integer> target) {
    
        final int[] result = new int[target.size()];
        for (int i=0; i<result.length; i++) {
            result[i] = target.get(i);
        }
        
        return result;
    }
}

ブログ用にエラー処理などは記述していません。

ちなみにC#だとどうなるか

C# だと、コレクションから配列への変換は以下のようになります。
といってもC# は言語仕様上、コレクションの型にプリミティブ型を使用することができるため、Java と全く同じではないですが。

var target = new List<int>();

for (int i = 0; i < 100000; i++)
{
    target.Add(0);
}

target.ToArray();

実行速度は1/10 ミリ秒 ~ 2/10 ミリ秒程度でした。

(Jenkins)Android アプリのビルド・ユニットテスト・Lint を実行する

最近、Android アプリの開発をメインにやってます。 Android でもCI やりたいなーと思って色々調べて何とか最低限のジョブは実行できるようになったので、その内容についてメモ。

開発環境

今回は、Windows Server 2012 R2 上にいるJenkins がSlave 兼開発環境であるWindows10 に対してジョブを実行する構成となっています。

実行するジョブ

今回は以下のジョブをPipeline で実行してみます。

Lint については、以下のページを参照してください。

Lint によるコードの改善 | Android Studio

また、Jenkins でLint のレポートを集計する場合は以下のプラグインを予めインストールする必要があります。

Android Lint Plugin - Jenkins - Jenkins Wiki

Pipeline Script

node('<スレーブ名>') {

    stage('Get Repository') {
        // リポジトリからソースコードを取得する。
        git branch: 'develop', credentialsId: '<認証ID>', url: '<対象のリポジトリ クローンURL>'
    }
    
    stage('Build Debug') {
        // コマンドラインからGradle を実行する。
        // ./gradlew 以降の引数は、Android Studio でビルドした際にGradle が実行するタスクを列挙している。
        bat './gradlew :app:generateDebugSources :app:generateDebugAndroidTestSources :app:mockableAndroidJar :app:prepareDebugUnitTestDependencies build'
    }
    
    stage('Lint Report') {
        // Lint を実行・集計する。
        androidLint canComputeNew: false, defaultEncoding: 'UTF-8', healthy: '', pattern: '**/lint-results*.xml', shouldDetectModules: true, unHealthy: '', unstableTotalAll: '0'
    }

    stage('Unit Test') {
        // ユニットテストを実行
        bat './gradlew clean test'

        // HTML で実行結果がレポートされる。(こっちが見やすい)
        // レポートはデフォルトでは"app/build/reports/tests/testDebugUnitTest/"に出力される。
        publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'app/build/reports/tests/testDebugUnitTest/', reportFiles: 'index.html', reportName: 'JUnit Results'])

        // JUnit 結果をカウントするためにレポート結果を集計する。
        // レポートはデフォルトでは"/app/build/test-results/testDebugUnitTest/"配下にあるXML ファイルが対象となる。
        junit allowEmptyResults: true, testResults: '**/app/build/test-results/testDebugUnitTest/*.xml'
    }

}

ハマった箇所

  1. コマンドラインが実行できない。
    Windows 上で"bat"コマンドを実行できない場合があります。
    その場合は、スレーブの設定から環境変数にパスを設定する必要があります。
    コマンドプロンプトは通常以下の場所にあるので環境変数から"Path"を追加して値を"C:\Windows\System32;"としましょう。
  2. Lint の検査に引っかかるとビルドが停止する。
    Lint はGradle のデフォルト設定では検査結果に異常があればビルド失敗として扱われます。
    その為、1件でも検査エラーがあれば、Jenkins の以降のタスクが実行されません。
    これでは検査エラーが有った場合でもレポートを出力したいときなどに困ります。
    そこで、Android プロジェクトの/app/build.gradle ファイルからビルド失敗時にAbort させないように設定する必要があります。
    変更箇所は以下の通り。
android {
    lintOptions {
        abortOnError false
    }
}

Lint オプションについては以下のページを参照してください。

LintOptions - Android Plugin 3.0.0-dev DSL Reference

(Jenkins)Pipeline を使う

Jenkins のPipeline の使い方と環境設定についてメモ。

Pipeline について

Jenkins のPipeline 機能が追加されてからもう2年近くが経ちますが、今の今までGroovy が分からなくて触ってなかったのですが、最近使って便利だったので使い方と環境設定についてメモします。
Jenkins Pipeline の概要については以下のページが参考になります。

jenkins.io

www.buildinsider.net

開発環境

Pipeline を使う

Pipeline の使い方、Groovy の書き方についての日本語情報は以下のページが非常に参考になりました。

kimulla.hatenablog.com こうやってスクリプトとその結果を画像で並べていると見やすくていいですね~

これだけだと参考ページの紹介で終わってしまうので、実際に使ってみてハマった箇所の解決方法を幾つか挙げてみます。

  • 環境設定でハマった箇所
    Slave を作成する際、ノード プロパティの環境変数にSlave の端末の環境変数を設定しておく必要があります。
    これをしていないと、例えばWindows batch を実行できなくなります。
    特にキー:PATH の環境変数は必須です。

  • Pipeline Syntax で使用したいPlugin が表示されない
    Pipeline のコード スニペットを使用する際に、"Sample Step" から実行したいJob を選択してPipeline Script を生成することが大半だと思います。
    この時、Sample Step に表示されるJob はPipeline に対応したPlugin のみ列挙されます。
    Plugin はバージョンによってはPipeline に対応していないものも存在するため、一度バージョンアップを試してみましょう。
    私の場合は、"Task Scanner Plugin" でこの問題にハマりました。

軽く使用しただけでもPipeline 機能は非常に便利なので、今後も使用していきたいと思います。

(Android)スクリーンのON/OFFを検出する

Android端末でスクリーンのON/OFFを検出する方法についてメモ。

開発環境

  • 開発環境 : Android Studio 3.0
  • Compile Sdk Version : API26 Android8.0 (O)
  • Min Sdk Version : API23 Android6.0 (Mashmallow)

スクリーンのON/OFFを検出する

通常、スクリーンのON/OFFを検出するにはIntentFilter を設定したBroadcastReceiver を使用します。
今回はサンプルとしてWakeLockBroadcastReceiver というスクリーンON/OFFを検出するためのBroadcastReceiver クラスを作成し、スクリーンの状態が変わるとWakeLockListener というリスナーに通知します。
Intent のAction 文字列は、スクリーンの表示が"Intent.ACTION_SCREEN_ON"、スクリーンの非表示が"Intent.ACTION_SCREEN_OFF"となっています。
以下、コード例です。

<WakeLockBroadcastReceiver.java

package com.example.iyemon.wakelocksample;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public final class WakeLockBroadcastReceiver extends BroadcastReceiver {

    private WakeLockListener listener;

    public WakeLockBroadcastReceiver(WakeLockListener listener) {
        this.listener = listener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(Intent.ACTION_SCREEN_ON)) {
            this.listener.onScreenOn();
        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
            this.listener.onScreenOff();
        }
    }
}

<WakeLockListener.java

package com.example.iyemon.wakelocksample;

public interface WakeLockListener {
    
    void onScreenOn();

    void onScreenOff();
}


<MainActivity.java

package com.example.iyemon.wakelocksample;

import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.format.DateFormat;
import android.widget.TextView;

import java.util.Calendar;


public class MainActivity extends AppCompatActivity implements WakeLockListener {

    private WakeLockBroadcastReceiver wakeLockBroadcastReceiver;
    private TextView loggingTextView;

    private final String NewLine =  System.getProperty("line.separator");

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

        this.loggingTextView = (TextView)findViewById(R.id.loggingTextView);

        wakeLockBroadcastReceiver = new WakeLockBroadcastReceiver(this);
        registerReceiver(wakeLockBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
        registerReceiver(wakeLockBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
    }

    @Override
    public void onScreenOn() {
        String text = loggingTextView.getText().toString();
        loggingTextView.setText(text + NewLine + getNow() + " Screen On.");
    }

    @Override
    public void onScreenOff() {
        String text = loggingTextView.getText().toString();
        loggingTextView.setText(text + NewLine + getNow() + " Screen Off.");
    }

    private String getNow(){
        return DateFormat.format("yyyy/MM/dd HH:mm:ss", Calendar.getInstance()).toString();
    }
}

これを実行すると以下のようになります。
f:id:iyemon018:20171228011742p:plain

ハマったところ

BroadcastReceiver にIntentFilter を設定する場合、manifest ファイルにフィルター名を設定すればいいのですが、ACTION_SCREEN_ON/ACTION_SCREEN_OFF はContext#registerReceiver を使用する必要があります。
その理由については以下のページが参考になります。

qiita.com


今回使用したプロジェクトはこちら↓↓↓↓↓

github.com

(WPF)要素のスクリーンキャプチャーを保存するTriggerActionを作る

WPFで画面上の要素をキャプチャーして画像ファイルとして保存するような要件がワリとありますが、どうせならTriggerActionで作られたプロダクトは無いかなーと思って探してみたところ、すぐには見つからなかったので自作してみました。

以下、今回の開発環境です。

WPFでスクリーンキャプチャーを保存する方法は色々ありますが、今回は以下のページを参考にさせていただきました。

mseeeen.msen.jp

これをTriggerActionで実装してみた結果がこちら

<CaptureAciton.cs>

namespace CaptureTriggerSample.TriggerActions
{
    using System;
    using System.IO;
    using System.Windows;
    using System.Windows.Interactivity;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;

    public class CaptureAction : TriggerAction<FrameworkElement>
    {
        public static readonly DependencyProperty FileNameProperty =
                DependencyProperty.Register("FileName"
                                            , typeof(string)
                                            , typeof(CaptureAction)
                                            , new FrameworkPropertyMetadata(null));

        public string FileName
        {
            get => (string)GetValue(FileNameProperty);
            set => SetValue(FileNameProperty, value);
        }

        protected override void Invoke(object parameter)
        {
            Rect bounds = VisualTreeHelper.GetDescendantBounds(AssociatedObject);
            DrawingVisual drawingVisual = new DrawingVisual();
            using (DrawingContext drawingContext = drawingVisual.RenderOpen())
            {
                VisualBrush visualBrush = new VisualBrush(AssociatedObject);
                drawingContext.DrawRectangle(visualBrush, null, bounds);
            }

            RenderTargetBitmap renderTargetBitmap =
                    new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, 96, 96, PixelFormats.Pbgra32);
            renderTargetBitmap.Render(drawingVisual);
            renderTargetBitmap.Freeze();

            PngBitmapEncoder png = new PngBitmapEncoder();
            BitmapFrame bitmapFrame = BitmapFrame.Create(renderTargetBitmap);
            bitmapFrame.Freeze();
            png.Frames.Add(bitmapFrame);
            using (FileStream fileStream = File.Create(FileName))
            {
                png.Save(fileStream);
            }

            MessageBox.Show($"キャプチャーを保存しました{Environment.NewLine}"
                            + $"[ファイル名]{Environment.NewLine}"
                            + $" {FileName}"
                            , "キャプチャー"
                            , MessageBoxButton.OK
                            , MessageBoxImage.Information);
        }
    }
}

※ブログ用に可読性を考慮してエラー処理などはしていません。

FileName プロパティに出力するファイル名を指定できるようにしています。

これを使うにはこのようにします。

<MainWindow.xaml

<Window
    x:Class="CaptureTriggerSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:CaptureTriggerSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:triggerActions="clr-namespace:CaptureTriggerSample.TriggerActions"
    Title="MainWindow"
    Width="525"
    Height="350"
    mc:Ignorable="d">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click" SourceObject="{Binding ElementName=CaptureButton}">
            <triggerActions:CaptureAction FileName="Resources\Capture.png" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button
            x:Name="CaptureButton"
            Grid.Row="0"
            Margin="8"
            Padding="16,8"
            HorizontalAlignment="Left"
            Content="Capture" />
        <Grid Grid.Row="1" Margin="8">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <TextBox x:Name="TextBox" />
            <TextBlock
                Grid.Row="1"
                Margin="0,8"
                Text="{Binding Text, ElementName=TextBox, Mode=OneWay}" />
        </Grid>
    </Grid>
</Window>

これを実行すると以下のような画面が表示されます。
f:id:iyemon018:20171215142227p:plain

CaptureボタンをクリックするとWindow要素のキャプチャーが実行されます。
(実行フォルダに"Resources"フォルダを作成する必要があります。)
f:id:iyemon018:20171215142328p:plain

実際にキャプチャーされた画像ファイルはこちら。
f:id:iyemon018:20171215142417p:plain

このように簡単に指定した領域をキャプチャーすることが可能です。
TriggerActionにしたことにより、例えばKeyTrigger を使ってCtrl + Cキー入力時にキャプチャーを保存したりすることも可能です。

ただし、1点注意することがあります。
レアケースだとは思いますが、以下の様にキャプチャー対象の要素の外側にも子要素が配置されている場合、子要素を含む領域がキャプチャーされてしまいます。
以下はVisual StudioXAMLデザイナープレビューです。
右上にEllipseを配置していますが、Grid の領域外に配置しています。
f:id:iyemon018:20171215143722p:plain

これをキャプチャーすると以下の様になります。
f:id:iyemon018:20171215144027p:plain

原因はここ

Rect bounds = VisualTreeHelper.GetDescendantBounds(AssociatedObject);

VisualTreeHelper.GetDescendantBounds は指定したビジュアル要素の全ての子要素の和集合から領域を算出します。
つまり、AssociatedObject の子要素が配置された領域全てがその対象となります。(ただし原点はあくまでAssociatedObject を基準とする。)

これを回避するには、AssociatedObject もしくはその子要素にClipToBounds="True" を設定します。
こうすることで要素の範囲外にあるものはキャプチャーされなくなります。

画面領域外にコントロールを配置することってあんまりないと思うのですが、移動アニメーション中などに予期せずキャプチャー範囲が期待したもので無くなることはあり得るので注意が必要です。


今回使用したサンプルはこちら↓↓↓↓
github.com