技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

C#:try-catch-finally句内のreturnの仕様

表題についてjavaでは調査している記事が別にあったのですが、C#だと調査している記事が見つからなかったので、調査してみました。
 
C#でも、try句やcatch句の中でreturnを行った場合、returnする直前にfinally句が実行されるという挙動となります。これはjavaと同じ仕様です。
また、finally句でreturnを記述してreturnを上書きしようとするとビルドエラーになります。javaだと警告止まりのようなので、この点では仕様が異なっています。
 
【調査プログラム】
・Test1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test1
{
    class Test1
    {
        static int Main(string[ ] args)
        {
            try
            {
                Console.WriteLine("try句でreturnします");
                return 1;
            }
            catch
            {
                Console.WriteLine("catch句は通りません");
            }
            finally
            {
                Console.WriteLine("finally句を通ってからreturnします");
            }
            return 0; // ここは通らない
        }
    }
}
 
・Test2.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test2
{
    class Test2
    {
        static int Main(string[ ] args)
        {
            try
            {
                throw new Exception("例外発生");
                Console.WriteLine("try句でreturnする前に例外発生");
                return 1;
            }
            catch
            {
                Console.WriteLine("catch句でreturnします");
                return 2;
            }
            finally
            {
                Console.WriteLine("finally句を通ってからreturnします");
            }
            return 0; // ここは通らない
        }
    }
}
 
・Test3.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test3
{
    class Test3
    {
        static int Main(string[ ] args)
        {
            try
            {
                throw new Exception("例外発生");
                Console.WriteLine("try句でreturnする前に例外発生");
                return 1;
            }
            catch
            {
                Console.WriteLine("catch句でreturnします");
                return 2;
            }
            finally
            {
                Console.WriteLine("finally句でreturnを書くとコンパイルエラー");
                return 3;
            }
            return 0; // ここは通らない
        }
    }
}
 
・Test.bat
@echo off

echo ■Test1
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Test1.cs>nul
Test1.exe
echo 戻り値:%ERRORLEVEL%
echo;

echo ■Test2
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Test2.cs>nul
Test2.exe
echo 戻り値:%ERRORLEVEL%
echo;

echo ■Test3
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Test3.cs
rem Test3.exe
rem echo 戻り値:%ERRORLEVEL%
echo;

pause
 
【調査結果(実行結果)】
■Test1
try句でreturnします
finally句を通ってからreturnします
戻り値:1

■Test2
catch句でreturnします
finally句を通ってからreturnします
戻り値:2

■Test3
Microsoft (R) Visual C# Compiler version 4.7.3062.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, w
hich is no longer the latest version. For compilers that support newer versions of the C# programming language, see http
://go.microsoft.com/fwlink/?LinkID=533240

Test3.cs(27,17): error CS0157: コントロールが finally 句の本体から出られません。

続行するには何かキーを押してください . . .

初めてのWindowsバッチ

Windowsバッチを作ったことが無い方向けに、Windowsバッチの用途や使い方を書いた入門的な記事です。
詳しいコマンドの使い方は記載しませんので、必要に応じて調べてみて下さい。
 
Windowsバッチとは】
WindowsOSに標準で用意されている実行ファイル形式であり、拡張子は.batである。
エクスプローラ上でダブルクリックすることで、記述されたDOSコマンド(スクリプト)を自動実行することができる。
簡単な制御や実行ファイル呼び出しを記述可能で、要件に合わせて一連の処理を取りまとめたい時に使用される。
 
WindowsバッチでHello WorldWindowsバッチの導入)】
WindowsOSであることが前提です。
 
1.エクスプローラを開き、表示 > ファイル名拡張子 のチェックを入れる。
f:id:akira2kun:20200614200559j:plain

2.任意の名前でテキストドキュメントを新規作成した後、ファイルの名前を変更する。拡張子を「.txt」から「.bat」に変更する。
3.2で作成したファイルを 右クリック > 編集 で選択し、下記のように入力して保存する。
@echo off
echo Hello World!
pause
4.3で編集したファイルをダブルクリックする。コンソールが表示され、下記のように表示されればOK。
Hello World!
続行するには何かキーを押してください . . .
 
Windowsバッチでできること】
Windowsバッチは複雑な処理を記述するには不向きですが、下記で挙げるような処理であれば記述可能です。
実行ファイルを部品として組み合わせる分には困ることは少なく、「WindowsOSであれば初期設定無しで動く」「習得が容易で、エンジニアであればメンテナンスできる人も多い」といった特徴から、気軽に導入することができます。
 
1.コンソールへの文字列表示(標準出力)
Hello Worldの例の通り、コンソールへの文字列表示が可能です。
"echo "の後に表示させたい文字列を記述することで、その文字列を表示することができます。
なお、Windowsバッチでは実行されるコマンドもコンソールに表示されるのですが、"@echo off"と記述すれば以降はコマンドのコンソール表示を抑止することができます。
また、Windowsバッチは全てのコマンドの実行が完了したら自動的にウインドウを閉じてしまうので、表示内容を確認したい場合は"pause"と記述して一時的に処理を中断(任意のキー押下で再開)する必要があります。
 
2.外部の実行ファイルの実行
実行ファイルのパスを記述することで、Windowsバッチから外部の実行ファイルを実行することができます。
実行ファイルのパスの後にスペース区切りで文字列を入力すれば、その文字列を引数として与えることもできます。
 
例えば、下記のように記述すれば、WindowsOS標準のプログラム言語「C#」のコンパイラを起動することができます。
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe コンパイルしたいC#ソースファイルのパス
 
C#コンパイル済み実行ファイル(.exe)やjavaコンパイル済み実行ファイル(.jar)があるなら、そのファイルのパスを記述することでその実行ファイルを実行することができます。
下記は、C#の実行ファイル C:\tmp\Program.exe を実行する例です。
C:\tmp\Program.exe
 
また、Windowsバッチよりも強力な機能を持ったWindowsOS標準のスクリプト言語Windows Power Shell」は、OSの設定によってはダブルクリックで実行できないという難点があるのですが、下記のように記述することで設定問わずダブルクリックで実行できるようになります。
Windows Power Shell は環境変数でパスが通っているので、実行ファイルのパスの指定は不要です)
powershell -ExecutionPolicy RemoteSigned -File 実行したいWindows Power Shellファイルのパス
 
ダブルクリックするだけで引数付きで実行ファイルを実行したり、複数の実行ファイルを実行したり、Windows Power Shell のスクリプトを実行したりできるので、単純に外部の実行ファイルの実行したい時にもWindowsバッチのファイルを作る価値があります。
難しい処理はC#Windows Power Shell等で記述し、実行はバッチファイルから行う、というスタイルがWindowsOSの場合は馴染みやすいと思います。
 
3.現在のパス(カレントディレクトリ)の変更
Windowsバッチでは、実行直後はWindowsバッチのファイルが存在するパスを指しています。
例えば、C:\tmp\hoge.bat を実行した場合、実行直後のパスは C:\tmp\ となります。
ファイルのパス指定は現在のパスを起点に相対パスで指定することができ、例えば C:\tmp\ にいる場合は、C:\tmp\Program.exe を
.\Program.exe
と指定して実行することが可能です。
(.\は省略することも可能です)
 
現在のパス(カレントディレクトリ)を変更したい場合は、cdコマンドで変更することができます。
cdコマンドのパス指定は絶対パスでも相対パスでも可能で、例えば C:\tmp\ から C:\hoge\ に移動したい場合は、
cd ..\hoge\
で移動できます。
 
他の環境でもWindowsバッチを動かしたい場合は、相対パスが便利です。
 
4.フォルダの作成
Windowsバッチでは、mkdirコマンドでフォルダを作成することができます。フォルダ名には絶対パス・フルパス両方指定可能です。
mkdir フォルダ名
 
なお、フォルダを削除することもでき、コマンドはrmdirコマンドです。
 
5.ファイルの作成
Windowsバッチでは標準出力の内容をファイルに出力することが可能です。
(出力先は絶対パス・フルパス両方指定可能です)
 
例えば、以下のように記述することで、Hello World! と書かれたテキストが C:\tmp\hoge.txt に出力されます。
echo Hello World!>C:\tmp\hoge.txt
 
">"の部分はリダイレクトと呼ばれており、">"と記述すれば上書きで出力、">>"と記述すれば改行して追記で出力となります。
 
なお、空ファイルを出力するには、下記のように記述すれば良いです(空の文字を入力する、という意味です)。
type nul >C:\tmp\hoge.txt
 
6.変数の利用
Windowsバッチでは変数を利用することが可能です。
 
以下は、変数を利用してHello World!を出力する例です。SETで変数を定義、%%で囲むことで変数の参照(正確には展開)が可能です。
@echo off
SET NAME=World
echo Hello %NAME%!
pause
 
例えば、実行したWindowsバッチのフルパスの取得や、システム時刻の取得で変数を使う場合が多いです。
実行したWindowsバッチのフルパスの取得は
SET hoge=%~dp0
システム時刻(YYYYMMDDHHMMSS)の取得は
SET time0=%time: =0%
SET hoge=%date:~0,4%%date:~5,2%%date:~8,2%%time0:~0,2%%time0:~3,2%%time0:~6,2%
で可能です。
 
7.IF文の制御
WindowsバッチではIF文で簡単な制御を行うことができます。
 
例えば、ファイル・フォルダの存在確認は下記のような記述で実現可能です。
IF EXIST hoge (
  echo OK
) ELSE (
  echo NG
)
 
ここで、hogehoge\ とすることで、フォルダのみの存在確認が可能になります。
hoge という名前のファイルが存在していても偽とすることができます。
また、"IF"の直後に"NOT"を繋げることで、真偽判定を逆にすることができます。
 
文字列比較も可能で、下記のように記述します。
IF %HENSU%==hoge (
  echo OK
) ELSE (
  echo NG
)
 
数値の比較も可能ですが、比較演算子として"=="の代わりに"equ"(等しい)、"neq"(等しくない)、"lss"(左辺が右辺より小さい)、"leq"(左辺が右辺以下)、"gtr"(左辺が右辺より大きい)、"geq"(左辺が右辺以上)を使うことに注意が必要です。
 
8.その他の制御
forループやcall・gotoによるジャンプ、exitでのバッチ終了も可能です。
この記事では例を1つ挙げるだけに留めますが、下記はworkフォルダの中の各々のファイルを、順番に C:\tmp\Program.exe の引数として与えて実行する例です。
for文で順番に従って"%%a"にファイル名を格納、"%%a"を第一引数としてmethodルーチンへ飛ばし、methodルーチンでは受け取った引数を C:\tmp\Program.exe の引数として与えて実行、for文が終了したら(全てのファイルに対して処理が終了したら)exitでバッチファイルを終了、ということをしています。
cd work
for %%a in (*) do (call :method %%a)
exit
:method
C:\tmp\Program.exe %1
goto :eof

マークダウン記法とは

【マークダウン記法とは】
マークダウン記法とは、簡単に言うとマークアップ記法(HTML等)を簡略化したものです。
タグを用いることなく、簡単な記法で文書の整形を行うことができます。
Wordに代表される文書作成ツールやマークアップ記法の文書よりも簡単に書くことができ、テキストファイルの文書よりも見た目を綺麗にすることができるので、近年ではReadMe等に使われることが少なくありません。
 
マークダウン記法は容易に綺麗な文書を作成できることがメリットですが、エディタやビューアを自分で用意しなければならないのが難点です。
そのため、Excel方眼紙が多様されている等、マークダウン記法が広まっていない現場だと使いにくいかもしれません。
(ただし、普通のテキストファイルとして見ても、それなりに見やすい文書にはなります)
 
【マークダウン記法の文書の作成方法】
拡張子は「.md」を用います。
文字コードUTF-8である必要があります。
文書は、シャープやアスタリスクやスペース等の記号を用いて整形します。
(当記事にも簡単なサンプルは載せますが、詳しくはWeb等で調べてみて下さい)
 
マークダウン記法の文書を作成するなら、エディタを用意する必要があります。
私はエディタにVisual Studio Codehttps://azure.microsoft.com/ja-jp/products/visual-studio-code/)を使用しています。
意味を持つ記号がハイライト表示されるだけでなく、Ctrl + Shift + v でプレビュー表示することもできます。
単純にマークダウン記法を書きやすいだけでなくプログラミングのエディタとしても有力で、IDEよりも軽量、かつ様々なプログラム言語(C系の言語、PythonJavaScript等)の構文がハイライト表示されるので、入れておくと色々便利です。
 
また、ビューアも別途用意した方が便利かもしれません。
例えば、FireFoxMarkdown Viewer(https://addons.mozilla.org/ja/firefox/addon/markdown-viewer-chrome/)をインストールすれば、FireFoxでマークダウン記法の文書を整形された状態で読むことができます。
インストール自体もワンクリックで済むので、非常に導入しやすいです。
 
【サンプル】
・test.md
# はじめに
このファイルはmdファイルのサンプルです。 
シャープ+スペースで見出しになります。 
スペースを2つ繋げると改行になります。

# usageもどき
シングルクォーテーション3つで囲われた箇所はコード表記になります。

```
hoge.exe [-b]
```

* -bオプション 
バッククォート+スペースで箇条書きになります。

Visual Studio Codeのプレビュー表示
f:id:akira2kun:20200607231816j:plain

FireFoxMarkdown Viewerでの表示
f:id:akira2kun:20200607232031j:plain

Eclipse + Maven で Apache POI を用いてExcelファイルを出力する環境の構築(HelloWorldまで)

Apache POI を用いることで、javaExcelファイルを出力するプログラムを作成することができるようになります。
今回は、環境設定を行い、HelloWorldを試す所まで記事にしました。
ライブラリを落としてきたりパスの設定をするのに手間がかかるので、今回は Eclipse + Maven で設定を行っています。
 
Mavenプロジェクトの作成】
1.Eclipseを立ち上げ、ファイル > 新規 > Mavenプロジェクト を選択。
f:id:akira2kun:20200606164825j:plain

2.「シンプルなプロジェクトの作成」「デフォルト・ワークスペース・ロケーションの使用」にチェックを入れて次に進む。
f:id:akira2kun:20200606164301j:plain

3.グループIdに任意の名前(プロジェクトを一意に識別する名前)、アーティファクトIdにプロジェクト名を入力し、完了を押下する。
f:id:akira2kun:20200606164328j:plain

4.作成されたプロジェクトについて、JREシステム・ライブラリー > プロパティー を選択。
f:id:akira2kun:20200606164355j:plain

5.適切な実行環境を設定(今回は「JavaSE-1.8」を選択)
f:id:akira2kun:20200606164422j:plain

 
【ライブラリのインストール・設定】
1.作成されたプロジェクトに存在するpom.xmlを開き、下記のように入力する。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>hoge</groupId>
  <artifactId>ProjectName</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>4.1.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>4.1.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml-schemas</artifactId>
      <version>4.1.2</version>
    </dependency>
  </dependencies>
</project>
 
※dependenciesタグを自分で追記する。
dependencyタグ内の記述については、自分で調べる場合はMVNrepositoryで調べる。ライブラリ名で検索すると、dependencyタグの記述する内容が記載されているページにたどり着く。
 
2.pom.xmlを保存すると、dependencyタグに記載されたライブラリが自動的にインストール・設定される。
 
Excelファイルを出力】
1.src/main/java 直下で右クリックし、新規 > クラス を選択。その後、クラス名(今回は「POITestMain」)を入力し、クラスを生成する。
2.下記のようにソースを記述する。
import java.io.FileOutputStream;

import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class POITestMain {

    public static void main(String[ ] args) {

        FileOutputStream fos = null;
        XSSFWorkbook workbook = null;

        try {

            // ワークブック→シート→行→セルの生成
            workbook = new XSSFWorkbook();
            XSSFSheet sheet = workbook.createSheet();
            XSSFRow row = sheet.createRow(0);
            XSSFCell cell = row.createCell(0);

            // セルの書式の生成
            XSSFCellStyle cellStyle = workbook.createCellStyle();
            XSSFFont font = workbook.createFont();
            font.setFontName("MS ゴシック");
            cellStyle.setFont(font);
            cell.setCellStyle(cellStyle);

            // セルに書き込み
            cell.setCellValue("Hello World!");

            // ファイル書き込み
            fos = new FileOutputStream("C:\\tmp\\test.xlsx");
            workbook.write(fos);

        } catch(Exception e) {
            e.printStackTrace();
        } finally {
             try {
                 if (fos != null) {
                     fos.close();
                 }
                 if (workbook != null) {
                     workbook.close();
                 }
             } catch(Exception e) {
                 e.printStackTrace();
             }
        }
    }
}
 
3.クラスを実行すると、ソース内で記述したパスに下記のような内容のファイルが生成される。
f:id:akira2kun:20200606164538j:plain

※実行時にjava.lang.NoClassDefFoundErrorが出る可能性があります(私の環境では出ました)。その場合は、Eclipseを一度落とし、C:\Users\ユーザ名\.m2 にある repository フォルダを削除し、もう一度Eclipseを立ち上げ、プロジェクトで右クリック > 実行 > Maven Clean を実施し、pom.xmlをもう一度保存し直したりしてみて下さい。良く起こる事象のようで、「Maven NoClassDefFoundError」でWebで検索すると色々情報が出てきます。

C#:await・asyncの簡単なサンプルコード

C#のawait・asyncは非同期処理のために用意された文法なのですが、Webで調べてみても難しく書かれていることが多く、そもそも何のための処理なのか理解するのが難しい感があります。
await・asyncを用いて関数を呼び出しても、その関数の処理が終わるまで待つという動きをするので、同期処理と何が違うのかいまいちわかりにくいというのもあると思います。
以上のような背景があるので、簡単なサンプルコードを書いてみることとしました。
 
await・asyncは、GUIアプリのために用意された文法と考えて良いです。
await・asyncを用いない場合、処理中はGUI操作ができなくなるのですが、await・asyncを用いて呼び出した関数を処理している間はGUI操作が可能になります。
GUI操作と並行で処理ができる、という意味で、await・asyncは非同期処理であると言えます。
 
以下、Windows Formのサンプルコードです。
3秒後にボタン押下時のシステム時刻をラベルに表示するというサンプルコードであり、await・asyncを用いたボタンとそうではないボタンを用意して挙動の違いを確認します。
 
【サンプルコード】
・From1.cs(デザイン)
f:id:akira2kun:20200531215528j:plain
 
・Form1.cs(ビジネスロジック
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AsyncAwaitTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // Asyncボタン
        private async void button1_Click(object sender, EventArgs e)
        {
            String date = await AsyncProc();
            label1.Text = date;
        }

        private async Task<String> AsyncProc()
        {
            DateTime dt = DateTime.Now;
            await Task.Delay(3000);
            return dt.ToString();
        }

        // NoAsyncボタン
        private void button2_Click(object sender, EventArgs e)
        {
            String date = NoAsyncProc();
            label2.Text = date;
        }

        private String NoAsyncProc()
        {
            DateTime dt = DateTime.Now;
            Thread.Sleep(3000);
            return dt.ToString();
        }
    }
}
 
【実行結果】
・Asyncボタンを押してから1秒以内にNoAsyncボタンを押下
→Asyncボタンの処理中もNoAsyncボタンを押下可能であるため、2つのボタンに対応するラベルの表示上、時刻の差異は1秒以内です。
f:id:akira2kun:20200531215605j:plain

・NoAsyncボタンを押してから1秒以内にAsyncボタンを押下
→NoAsyncボタンの処理中はAsyncボタンを押下不可能であるため、2つのボタンに対応するラベルの表示上、時刻の差異は3秒以上となります。

f:id:akira2kun:20200531215635j:plain

java:walkFileTreeメソッドの使い方

walkFileTreeメソッドとは、ディレクトリ構造を再帰的に走査するメソッドです。
Java7で追加されたFilesクラスが提供するメソッドの一つであり、比較的新しいメソッドです。
(このメソッドの提供により、ディレクトリ構造については自力でCompositeパターンを組む必要がなくなりました)
 
walkFileTreeメソッドの引数は2つあり、1つ目は起点となるパス、2つ目はFileVisitorインターフェースです。
FileVisitorインターフェースにはディレクトリ構造を走査した際の処理が定義されており、FileVisitorインターフェースを利用者が実装することでディレクトリ走査時の具体的な処理内容を記述可能になります。
 
以下、サンプルコードです。
詳しい使い方はWebを調べると出てきますので、必要に応じて調べてみると良いでしょう。
 
【サンプルコード】
・FileVisitorTest.java
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;

// FileVisitorはインタフェースであり、メソッドは定義のみがされている。
// ディレクトリやファイルに訪れた際の処理の中身は自分で実装する。
public class FileVisitorTest implements FileVisitor<Path> {
    private ArrayList<String> dirHierarchy = new ArrayList<>();
    private int currentDepth = -1;

    // 各々のディレクトリの中を走査し始めた時の処理
    @Override
    public FileVisitResult preVisitDirectory
      (Path path, BasicFileAttributes bfa) throws IOException {
        String dirName = path.getFileName().toString();
        currentDepth++;
        if (dirHierarchy.size() == currentDepth) {
            dirHierarchy.add(dirName);
        } else {
            dirHierarchy.set(currentDepth,dirName);
        }

        if (currentDepth > 0) {
            System.out.print
              ("ディレクトリ「" +
               dirHierarchy.get(currentDepth - 1) +
               "」の中身:");
        }
        System.out.println("ディレクトリ名「" + dirName + "」");

        return FileVisitResult.CONTINUE; // 走査継続
    }

    // 走査でファイルを発見した時の処理
    @Override
    public FileVisitResult visitFile
      (Path path, BasicFileAttributes bfa) throws IOException {
        String fileName = path.getFileName().toString();

        System.out.print
          ("ディレクトリ「" +
           dirHierarchy.get(currentDepth) +
           "」の中身:");
        System.out.println("ファイル名「" + fileName + "」");

        return FileVisitResult.CONTINUE; // 走査継続
    }

    // 各々のディレクトリの中を走査し終えた時の処理
    @Override
    public FileVisitResult postVisitDirectory
      (Path path, IOException exc) throws IOException {
        currentDepth--;

        return FileVisitResult.CONTINUE; // 走査継続
    }

    // 走査でファイルの異常を発見した時の処理
    // (ファイルの属性を読み取れない、無限ループ発見、等)
    @Override
    public FileVisitResult visitFileFailed
      (Path path, IOException ex) throws IOException {
        System.out.println(ex.getMessage());

        return FileVisitResult.TERMINATE; // 走査中断
    }

}
 
・WalkFileTreeTestMain.java
import java.io.IOException;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class WalkFileTreeTestMain {
    public static void main(String[ ] args) throws IOException {
        Path startPath = Paths.get("C:\\tmp");
        FileVisitor<Path> fv = new FileVisitorTest();
        // ディレクトリを再帰的に走査するメソッド。
        // 走査時の処理はFileVisitorインターフェースに記述。
        Files.walkFileTree(startPath, fv);
    }
}
 
【テスト用ディレクトリ構造】
C:\tmp>dir /s
 ドライブ C のボリューム ラベルは です
 ボリューム シリアル番号は です

 C:\tmp のディレクト

2020/05/28  22:20    <DIR>          .
2020/05/28  22:20    <DIR>          ..
2020/05/28  22:20    <DIR>          1
2020/05/28  22:20    <DIR>          2
2020/05/28  22:20                 0 3.txt
               1 個のファイル                   0 バイト

 C:\tmp\1 のディレクト

2020/05/28  22:20    <DIR>          .
2020/05/28  22:20    <DIR>          ..
2020/05/28  22:20    <DIR>          11
2020/05/28  22:20    <DIR>          12
2020/05/28  22:20                 0 13.txt
               1 個のファイル                   0 バイト

 C:\tmp\1\11 のディレクト

2020/05/28  22:20    <DIR>          .
2020/05/28  22:20    <DIR>          ..
2020/05/28  22:20                 0 111.txt
2020/05/28  22:20                 0 112.txt
               2 個のファイル                   0 バイト

 C:\tmp\1\12 のディレクト

2020/05/28  22:20    <DIR>          .
2020/05/28  22:20    <DIR>          ..
               0 個のファイル                   0 バイト

 C:\tmp\2 のディレクト

2020/05/28  22:20    <DIR>          .
2020/05/28  22:20    <DIR>          ..
2020/05/28  22:20                 0 21.txt
               1 個のファイル                   0 バイト

     ファイルの総数:
               5 個のファイル       バイト
              14 個のディレクトリ   バイトの空き領域

C:\tmp>
 
【実行結果】
ディレクトリ名「tmp」
ディレクトリ「tmp」の中身:ディレクトリ名「1」
ディレクトリ「1」の中身:ディレクトリ名「11」
ディレクトリ「11」の中身:ファイル名「111.txt」
ディレクトリ「11」の中身:ファイル名「112.txt」
ディレクトリ「1」の中身:ディレクトリ名「12」
ディレクトリ「1」の中身:ファイル名「13.txt」
ディレクトリ「tmp」の中身:ディレクトリ名「2」
ディレクトリ「2」の中身:ファイル名「21.txt」
ディレクトリ「tmp」の中身:ファイル名「3.txt」

シーケンス図を書いてマルチスレッドプログラムを整理する

マルチスレッドプログラムを開発していると、どの処理がいつ動くのか把握し辛くなることがあります。
そこでシーケンス図を書くと、ややこしいスレッド間の関連をわかりやすく整理することができます。
 
シーケンス図とは、UMLの一種であり、クラスやオブジェクト間のやりとりを時間軸に沿って表現する図です。
マルチスレッドを取り扱う解説書や設計書には良く出てくる図なのですが、保守開発等で自分がコーディングする時にも頭の整理のために役立てることができます。
 
以下では、javaでのスレッド制御(joinとsynchronized)で取り上げたサンプルコードを、試しにシーケンス図でまとめてみます。
このように、シーケンス図を書くことで、各スレッドの連関をわかりやすく整理することができます。
 
・そのまま実行した場合
f:id:akira2kun:20200526230946j:plain

・①のコメントアウトを外した場合(join)
f:id:akira2kun:20200526231004j:plain

・②のコメントアウトを外した場合(synchronized)
f:id:akira2kun:20200526231017j:plain