20200912のJavaに関する記事は13件です。

JavaのgetClass()はジェネリクス(or型変数)との相性が悪い

少し直観に反するJavaの言語仕様に気が付いたのでメモしておきます。
検証環境は Windows 版 OpenJDK 11 (build 28) です。

出来ないこと

public static <T> void hoge(@NonNull T value){
    Class<? extends T> type = value.getClass(); // コンパイルできない
    // type を使って作業したい
}

value はTかそのサブクラスのはずなので一見,問題なさそうな気がしますが,このコードはコンパイル出来ません.

javac
GenericGetClass.java:3: エラー: 不適合な型: Class<CAP#1>をClass<? extends T>に変換できません:
        Class<? extends T> type = value.getClass();
                                                ^
  Tが型変数の場合:
    メソッド <T>hoge(T)で宣言されているT extends Object
  CAP#1が新しい型変数の場合:
    ? extends ObjectのキャプチャからのCAP#1 extends Object
エラー1個

何だかよくわからないエラーメッセージ…

何がダメなのか?

いくつか試してみます。

// 当然OK
public static void hoge(CharSequence value){
    Class<? extends CharSequence> type = value.getClass();
}
// これもOK
public static <T extends CharSequence> void fuga(T value){
    Class<? extends CharSequence> type = value.getClass();
}
// これはコンパイルできない
public static <T extends CharSequence> void piyo(T value){
    Class<? extends T> type = value.getClass();
}

代入先の型に型変数 T を使っているとエラーになるようです。ここまで試して、 Object#getClass() の型の扱いが特殊だという話を思い出したので改めて調べました。

getClass() の仕様

API仕様書によると、

実際の結果型はClass<? extends |X|>です。ここで、|X|はgetClassが呼び出される式のstatic型の消去です。

そもそも、 Object クラスでは public final Class<?> getClass() のように定義されているので、

CharSequence cs = ...;
Class<?> type = cs.getClass()

は可能でも、普通のメソッドと同じように扱うと

Class<? extends CharSequence> type = cs.getClass()

のように代入することは出来ないことになります。そこで、言語使用で特別扱いしてこの場合getClass() の型が Class<? extends CharSequence> だとみなすわけですね。具体的には、型の制限がコンパイル時の変数の型(=「static型」)で決まります。

ここで問題になるのが、「static型の消去」になっていること。「消去」は翻訳として微妙な気もしますが、言語仕様§4.6のイレイジャ(erasure)のことでしょう。List<String> に対する Listclass Hoge<T extends CharSequence> と宣言されているときの T に対する CharSequence 、などを示すようです。

今回の例では?

public static <T extends CharSequence> void fuga(T value){
    Class<? extends CharSequence> type = value.getClass(); // OK
}

の場合は、valueT 型で、そのextends指定1CharSequenceなので、Tのイレイジャは CharSequence であり、getClass() の返り値の型は ? extends CharSequence となります。これは T やそのサブクラスとは限らないため、Class<? extends T> には代入できないんですね。

最初の例では、

public static <T> void hoge(@NonNull T value){
    Class<? extends T> type = value.getClass(); // コンパイルできない
}

T のイレイジャは Object なので getClass() の型は ? extends Object になるわけです。

なぜこのような仕様になっているのか

ここからは筆者の推測がかなり含まれるのですが、まずそもそも総称型そのままではなくイレイジャが指定されている理由は、総称型だとこんなことが起こるからでしょう。

ArrayList<String> list = new ArrayList<>();
Class<? extends ArrayList<String>> type = list.getClass(); // 実際にはコンパイルエラー

いやな予感がしますね。

Classクラスのインスタンスは仕組み上詳細な型情報を持てない2ので、Class<ArrayList<String>> 型や Class<ArrayList<Integer>> 型のオブジェクトは、実際には同じClass<ArrayList>のインスタンスになります。Class<ArrayList>のメソッドはArrayList<T>の型変数Tが何に設定されているかを知ることができません。

つまり、Class<ArrayList<String>> という型の変数は危険です。もし上の例がコンパイルできると、

ArrayList<Integer> another = new ArrayList<>();
another.add(123);
ArrayList<String> casted = type.cast(another); // 実行時には単に ArrayList へのキャスト扱いなので通る
// ...
String str = casted.get(0);// ここで実行時エラー?

全体的にraw型を使わずに書いてコンパイルが通っているのに、予期せぬ場所で ClassCastException が起こることになりそうです。

総称型を使えない理由は分かりました。だからといって型変数の場合まで消さなくていいだろうと思ってしまったのですが、型変数Tが呼び出し側で List<String> に割り当てられたりしていれば結局同じです。だから冒頭のようなプログラムも許されないということなのでしょう。

余談

筆者がこの振る舞いに気が付いたのは、

@SuppressWarnings("unchecked")
public static <K extends Enum<K>> EnumMap<K,String> of(@NonNull K key, String value){
    Class<K> type = (Class<K>) key.getClass(); // キャストしないとコンパイルエラー
    EnumMap<K,String> map = new EnumMap<>(type); // 拡張されたEnum定数を渡すとここで実行時エラーになる
    map.put(key, value);
    return map;
}

のようなコードを書いていた時です3。この場合は、enumfinal なので大丈夫な気がしていたのですが、よく考えれば、 enum の定数個別にメソッドとかを定義すると別クラスになるのでアウトでした。

あとがき

初めてQiitaの記事を書いてみましたが、考えて書いていると確かに知識の整理になりますね。至らぬ点があれば是非ご指摘ください。


  1. 英語では leftmost bound ですが何か良い日本語訳はないのでしょうか? 

  2. 各クラスごとにClassオブジェクトは一意に存在するので(このため==で比較できる) 

  3. 実際には EnumMap の独自実装みたいなことをしていました。 

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

<java>Zipファイルを読み込み直接文字列に変換する

概要

Zipファイルを読み込み直接文字列に変換します。

解説

読み込みにはInputStreamクラスのreadAllBytesメソッドを使用しています。
すべてのバイトを一気に読み込んでくれるのでとても便利ですね~。

ただしリファレンスによると、「このメソッドが、すべてのバイトを1つのバイト配列に読み取ると都合が良い簡単なケースで使用するものであることに注意してください。 大量のデータを持つ入力ストリームを読み込むためのものではありません。」という記載がありました。

コード

Unzip.java
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.util.zip.ZipInputStream;

public class Unzip{
    public static void main(String[] args) throws Exception {

        String filePath = "ここでZipファイルのパスを指定";

        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(filePath)));

        //zipファイルの中にファイルがあるだけ繰り返す
        while(zis.getNextEntry() !=null){

            String str = new String(zis.readAllBytes());

            //とりあえず出力しておく
            System.out.println(str);
        }
        zis.close();
    }
}


参考

参考までに、少しずつ読み込むコードも載せておきます。

Sample.java
        String filePath = "ここで読み込むZipファイルのパスを指定します";

        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(filePath)));


        String str = null;
        byte[] tempBuffer = new byte[1024];//一度に読み込むバイト数
        ByteArrayOutputStream streamBuilder = null;
        int bytesRead;

        //zipファイルの中にファイルがあるだけ繰り返す
        while(zis.getNextEntry() !=null){
            while ( (bytesRead = zis.read(tempBuffer)) != -1 ){

                streamBuilder = new ByteArrayOutputStream();
                streamBuilder.write(tempBuffer, 0, bytesRead);

            }
           //Stringに変換する
            str = streamBuilder.toString("UTF-8");
            System.out.println(str);
        }
        zis.close();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

My Study Note (Java)

Ref. site

メソッド参照

-https://www.sejuku.net/blog/22337
image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

■■■関数型interfaceの実装をラムダ式に置き換え

関数型インターフェースとは抽象メソッドを1つだけ持つインターフェースのことです。

■匿名クラスでの記述(従来例)
// インターフェース
interface InterfaceTest{
    // 抽象メソッド
    public String method(String name, int n);
}

public class Main {
    public static void main(String[] args) {
        // 匿名クラスの場合
        InterfaceTest it = new InterfaceTest() {
            // オーバーライド
            public String method(String name, int n) {
                return "Hello " + name + n + "!";
            }
        };
        System.out.println(it.method("Java", 8));
    }
}

Result
Hello Java8!


■ラムダ式での記述
// インターフェース
interface InterfaceTest{
    // 抽象メソッド
    public String method(String name, int n);
}

public class Main {
    public static void main(String[] args) {
        // ラムダ式の場合
        InterfaceTest it = (name, n) -> {
            return "Hello " + name + n + "!";
        };
        System.out.println(it.method("Java", 8));
    }
}

Result
Hello Java8!

■String to LocalDateTime

String dateString = '20201112131415';
String dateString = '20201112';

LocalDateTime dateTime = (dateString.length() > 8)
    ? LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
    :     LocalDate.parse(dateString, DateTimeFormatter.ofPattern("yyyyMMdd")).atTime(LocalTime.MIN);

■LocalDateTime to String

LocalDateTime dateTime = LocalDateTime.of(2020, 11, 12, 13, 14, 15);
String dateString = dateTime.format("yyyy/MM/dd HH:mm:ss");

■LocalDateTime to LocalDate

LocalDateTime dateTime = LocalDateTime.of(2020, 1, 2, 3,4 ,5);
LocalDate date = date.toLocalDate();

■String

        System.out.println("--------------- ■replace ---------------");
        String str = "aaa-bbb-ccc";
        System.out.println(str.replace("-", "")); // aaabbbccc
        System.out.println(str); // aaa-bbb-ccc
    System.out.println("--------------- ■subString ---------------");
    String str = "123456789";
    System.out.println(str.substring(1, 5));// 2345
    System.out.println(str); // 123456789
        System.out.println("--------------- ■getLastChar ---------------");
        String str = "1234567890";
        System.out.println(str.substring(str.length() - 8));// [34567890]
        System.out.println(str); // [1234567890]
    System.out.println("--------------- ■stripLeading ---------------");
    String str = "  123456789  ";
    System.out.println(str.stripLeading() + "End");// [123456789  End]
    System.out.println(str); // [  123456789  ]

■enum

public class enumTest {

    public static void main(String[] args) {
        getKeyValue();
    }

    private static void getKeyValue() {
        String[] msgList = {"e0001", "e0002"};
        for (String id : msgList) {
            System.out.println(MessageEnum.valueOf(id)); // e0001
            System.out.println(MessageEnum.valueOf(id).getMessage()); // メッセージ
        }
    }

    enum MessageEnum {
        e0001("メッセージ"),
        e0002("メッセージ222");

        private final String message;

        public String getMessage(){
            return message;
        }
        MessageEnum(String message) {
            this.message = message;
        }
    }
}
//************************ Result
e0001
メッセージ
e0002
メッセージ222

■LocalDate, LocalDateTimeテスト結果

private static void checkDisplay() {
    System.out.println("************************ checkDisplay ******************************");
    System.out.println(LocalDate.of(2020,1,1));
    System.out.println(LocalDate.of(2020,1,1)); // 2020-01-01
    System.out.println(LocalDateTime.of(2020,1,1,1,1,1).toString());//2020-01-01T01:01:01

    /*
    2020-01-01
    2020-01-01
    2020-01-01T01:01:01
     */
}
    public static LocalDate convert(String date, String formatter) {
        return LocalDate.parse(date, DateTimeFormatter.ofPattern(formatter));
    }

    public static LocalDateTime convertLocalDateTime(String date, String formatter) {
        if (date.length() <= 10) {
            LocalDate noTimestamp = convert(date, formatter.replaceAll(" ", "").split("HH", 0)[0]);
            return (noTimestamp != null) ?  noTimestamp.atTime(LocalTime.MIN) : null;
        }

        return LocalDateTime.parse(date, DateTimeFormatter.ofPattern(formatter));
    }
private static String stringToDateWithFormat(String value, String format) {
    // LocalDateTime型に変更
    LocalDateTime dateValue = LocalDateTime.parse((value.length() > 8) ? value : value.concat("000000"), DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
    return dateValue.format(DateTimeFormatter.ofPattern(format));
}
    private static void before() {
        System.out.println("************************ before ******************************");
        LocalDate date1 = LocalDate.of(2020,1,1);
        LocalDate date2 = LocalDate.of(2020,1,1);
        LocalDate date3 = LocalDate.of(2020,1,2);
        System.out.println(date1.isAfter(date2)); // false
        System.out.println(date1.isAfter(date3)); // false
        System.out.println(date3.isAfter(date2)); // true
    }
private static void stringToDate() {
    System.out.println("************************ stringToDate ******************************");
    System.out.println(stringToDateWithFormat("20200102", "yyyy/MM/dd HH:mm")); // 2020/01/02 00:00
    System.out.println(stringToDateWithFormat("20200102", "yyyy/MM/dd"));       // 2020/01/02
    System.out.println(stringToDateWithFormat("20200102", "MM/dd HH:mm"));      // 01/02 00:00
    System.out.println(stringToDateWithFormat("20200102", "HH:mm"));            // 00:00


    System.out.println(stringToDateWithFormat("20200102030405", "yyyy/MM/dd HH:mm")); // 2020/01/02 03:04
    System.out.println(stringToDateWithFormat("20200102030405", "yyyy/MM/dd"));       // 2020/01/02
    System.out.println(stringToDateWithFormat("20200102030405", "MM/dd HH:mm"));      // 01/02 03:04
    System.out.println(stringToDateWithFormat("20200102030405", "HH:mm"));            // 03:04


    // Error because of count > 14
    // System.out.println(stringToDateWithFormat("202001020303059999", "yyyy/MM/dd HH:mm"));
    // System.out.println(stringToDateWithFormat("202001020303059999", "yyyy/MM/dd"));
    // System.out.println(stringToDateWithFormat("202001020303059999", "MM/dd HH:mm"));
    // System.out.println(stringToDateWithFormat("202001020303059999", "HH:mm"));

    /*
    ************************ stringToDate ******************************
    2020/01/02 00:00
    2020/01/02
    01/02 00:00
    00:00

    2020/01/02 03:04
    2020/01/02
    01/02 03:04
    03:04

    Exception in thread "main" java.time.format.DateTimeParseException: Text '202001020303059999' could not be parsed at index 0
        at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
        at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
        at java.base/java.time.LocalDateTime.parse(LocalDateTime.java:492)
        at Date.Date.stringToDateWithFormat(Date.java:33)
        at Date.Date.stringToDate(Date.java:51)
        at Date.Date.main(Date.java:14)

     */
}
    private static void localDateTimeTolocalDate() {
        System.out.println("************************ localDateTimeTolocalDate ******************************");
        LocalDateTime date = LocalDateTime.of(2020, 1, 2, 3,4 ,5);
        System.out.println("LocalDateTime =" + date);                                // LocalDateTime =2020-01-02T03:04:05
        System.out.println("LocalDateTime -> LocalDate =" + date.toLocalDate());     // LocalDateTime -> LocalDate =2020-01-02
    }
private static void localDateToString() {
    System.out.println("************************ localDateToString ******************************");
    LocalDate date = LocalDate.of(2020, 1, 2);
    System.out.println("LocalDate =" + date);
    System.out.println("LocalDate -> String =" + date.toString());
    System.out.println("LocalDate -> String(Without -) =" + date.toString().replace("-", ""));
    /*
        ************************ localDateToString ******************************
        LocalDate =2020-01-02
        LocalDate -> String =2020-01-02
        LocalDate -> String(Without -) =20200102
     */

}
    private static void localDatePlusDay() {
        System.out.println("************************ localDatePlusDay ******************************");
        Short plusValue = 2;
        LocalDate date = LocalDate.of(2020, 1, 2);
        System.out.println("LocalDate =" + date);
        System.out.println("LocalDate -> String =" + date.toString());
        System.out.println("LocalDate -> String(Without -) =" + date.toString().replace("-", ""));
        System.out.println("LocalDate(plusDays:" + plusValue + ") =" + date.plusDays(plusValue));
        /*
            ************************ localDatePlusDay ******************************
            LocalDate =2020-01-02
            LocalDate -> String =2020-01-02
            LocalDate -> String(Without -) =20200102
            LocalDate(plusDays:2) =2020-01-04
         */

    }
private static String localDateTimeToString(LocalDateTime paramDate, String paramFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(paramFormat);
    return paramDate.format(formatter);
}

private static String localDateToStringWithFormat(LocalDate paramDate, String paramFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(paramFormat);
    return paramFormat.contains("HH:mm") ? paramDate.atTime(LocalTime.MIN).format(formatter) : paramDate.format(formatter);
}

private static void test1() {
    System.out.println("************************ test1 ******************************");
    System.out.println("■String to Date:" + convertLocalDateTime("2020/10/11 11:22", "yyyy/MM/dd HH:mm")); // ■String to Date:2020-10-11T11:22
    System.out.println("■String to Date:" + convertLocalDateTime("2020/10/11 11:22", "yyyy/MM/dd HH:mm")); // ■String to Date:2020-10-11T11:22

    System.out.println("■LocalDateTime to String(yyyy/MM/dd):" + localDateTimeToString(LocalDateTime.now(), "yyyy/MM/dd"));                                                                        // ■LocalDateTime to String(yyyy/MM/dd):2020/06/09
    System.out.println("■LocalDateTime to String(yyyy/MM/dd HH:mm):" + localDateTimeToString(LocalDateTime.of(2020,12,12,0,1,2), "yyyy/MM/dd HH:mm"));  // ■LocalDateTime to String(yyyy/MM/dd HH:mm):2020/12/12 00:01
    System.out.println("■LocalDateTime to String(yyyy/MM/dd HH:mm):" + localDateTimeToString(LocalDateTime.of(2020,12,12,23,1,2), "yyyy/MM/dd HH:mm")); // ■LocalDateTime to String(yyyy/MM/dd HH:mm):2020/12/12 23:01
    System.out.println("■LocalDateTime to String(yyyy/MM/dd hh:mm):" + localDateTimeToString(LocalDateTime.of(2020,12,12,0,1,2), "yyyy/MM/dd hh:mm"));  // ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/12/12 12:01
    System.out.println("■LocalDateTime to String(yyyy/MM/dd hh:mm):" + localDateTimeToString(LocalDateTime.of(2020,12,12,1,1,2), "yyyy/MM/dd hh:mm"));  // ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/12/12 01:01
    System.out.println("■LocalDateTime to String(yyyy/MM/dd hh:mm):" + localDateTimeToString(LocalDateTime.of(2020,12,12,14,1,2), "yyyy/MM/dd hh:mm")); // ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/12/12 02:01
    System.out.println("■LocalDateTime to String(yyyy/MM/dd hh:mm):" + localDateTimeToString(LocalDateTime.now(), "yyyy/MM/dd hh:mm"));     // ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/06/09 05:53
    System.out.println("■LocalDateTime to String(yyyy/MM/dd HH24:mm):" + localDateTimeToString(LocalDateTime.now(), "yyyy/MM/dd HH24:mm")); // ■LocalDateTime to String(yyyy/MM/dd HH24:mm):2020/06/09 1724:53
    System.out.println("■LocalDateTime to String(MM/dd):" + localDateTimeToString(LocalDateTime.now(), "MM/dd"));                           // ■LocalDateTime to String(MM/dd):06/09

    System.out.println("■LocalDate to String(yyyy/MM/dd):" + localDateToStringWithFormat(LocalDate.now(), "yyyy/MM/dd"));             // ■LocalDate to String(yyyy/MM/dd):2020/06/09
    System.out.println("■LocalDate to String(MM/dd):" + localDateToStringWithFormat(LocalDate.now(), "MM/dd"));                       // ■LocalDate to String(MM/dd):06/09
    System.out.println("■LocalDate to String(yyyy/MM/dd HH:mm):" + localDateToStringWithFormat(LocalDate.now(), "yyyy/MM/dd HH:mm")); // ■LocalDate to String(yyyy/MM/dd HH:mm):2020/06/09 00:00
    System.out.println("■LocalDate to String(MM/dd HH:mm):" + localDateToStringWithFormat(LocalDate.now(), "MM/dd HH:mm"));           // ■LocalDate to String(MM/dd HH:mm):06/09 00:00

    /* Result:
   ■String to Date:2020-10-11T11:22
   ■LocalDateTime to String(yyyy/MM/dd):2020/03/31
   ■LocalDateTime to String(yyyy/MM/dd HH:mm):2020/12/12 00:01
   ■LocalDateTime to String(yyyy/MM/dd HH:mm):2020/12/12 23:01
   ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/12/12 12:01
   ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/12/12 01:01
   ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/12/12 02:01
   ■LocalDateTime to String(yyyy/MM/dd hh:mm):2020/03/31 03:50
   ■LocalDateTime to String(yyyy/MM/dd HH24:mm):2020/03/31 1524:50
   ■LocalDateTime to String(MM/dd):03/31
   ■LocalDate to String(yyyy/MM/dd):2020/03/31
   ■LocalDate to String(MM/dd):03/31
   ■LocalDate to String(yyyy/MM/dd HH:mm):2020/03/31 00:00
   ■LocalDate to String(MM/dd HH:mm):03/31 00:00
     */
}
■タイムアウトテスト

public class TimeUnit {


    public static void main(String[] args) throws InterruptedException {
        timeUnit();
        calculateTime();
    }

    private static void calculateTime() throws InterruptedException {
        Integer period = 10;
        long now;
        long start = System.currentTimeMillis();

        for (int i = 1; i <= 2; i++) {

            now = System.currentTimeMillis();
            if (now - start > period) {
                System.out.println("タイムアウトしました");
                break;
            }

            executeProcess();
        }
    }

    private static void executeProcess() throws InterruptedException {
        System.out.println("****************** Execution Start ******************");
        java.util.concurrent.TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("****************** Execution End");
    }

    private static void timeUnit() throws InterruptedException {
        System.out.println(java.util.concurrent.TimeUnit.MILLISECONDS + ", value=" + System.currentTimeMillis());
        java.util.concurrent.TimeUnit.MILLISECONDS.sleep(10000);
        System.out.println(java.util.concurrent.TimeUnit.MILLISECONDS + ", value=" + System.currentTimeMillis());
        System.out.println(java.util.concurrent.TimeUnit.MICROSECONDS);
        System.out.println(java.util.concurrent.TimeUnit.NANOSECONDS);
        System.out.println(java.util.concurrent.TimeUnit.DAYS);
        System.out.println(java.util.concurrent.TimeUnit.HOURS);
        System.out.println(java.util.concurrent.TimeUnit.MINUTES);
        System.out.println(java.util.concurrent.TimeUnit.SECONDS);
    }
}
//*********************************** Result ******************************* //
MILLISECONDS, value=1599635237478
MILLISECONDS, value=1599635247541
MICROSECONDS
NANOSECONDS
DAYS
HOURS
MINUTES
SECONDS
****************** Execution Start ******************
****************** Execution End
タイムアウトしました

■Map Search

    private static Map<String, String> MAP = Map.of(
        "001", "01",
        "002", "02",
        "003", "03");

    public static void main(String[] args) {
        search("001"); // 01
        search("002"); // 02
        search("003"); // 03
        search("006"); // null
    }
    private static void search(String value) {
         System.out.println(MAP.get(value));
    }

■Static, Inner Class {}

public class StaticTest {

    static {
        System.out.println("Static.....");
    }

    public static void main(String[] args) {
        staticTest();
        innerTest();
    }

    private static void staticTest() {
        System.out.println("staticTest Method");
    }
    private static void innerTest() {
        innerClassT obj = new innerClassT();
        System.out.println(obj.getTest());
    }

    static class innerClassT {

        {
            test = "{} setting";
            System.out.println("innerClassT Static.....");
        }

        private String test;
        public String getTest() {
            return test;
        }
    }
}

********************* Result:
Static.....
staticTest Method
innerClassT Static.....
{} setting

stream (Object)

import static java.util.Objects.nonNull;
import java.util.ArrayList;
import java.util.List;
public class Stream {

    public static void main(String[] args) {
        MyObject obj1 = new MyObject();
        obj1.str = "1st";
        obj1.intVal = 100;
        MyObject obj2 = new MyObject();
        obj2.str = "2nd";
        obj2.intVal = 200;
        List<MyObject> lst = new ArrayList<MyObject>();
        lst.add(obj1);
        lst.add(obj2);

        streamIsExists(lst);
    }
    private static void streamIsExists(List<MyObject> lst) {
        System.out.println("'xxx' exist in lst list : Result = " + isExist(lst, "xxx"));
        System.out.println("'1st' exist in lst list : Result = " + isExist(lst, "1st"));
        System.out.println("'1st' exist in lst list : Result = " + isExist(lst, "GG"));
    }

    private static boolean isExist(List<MyObject> lst, String searchVal) {
       return lst.stream()
            .filter(MyObject -> nonNull(MyObject.str))
            .filter(MyObject -> searchVal.equals(MyObject.str))
            .findFirst()
            .isPresent();
    }
}

class MyObject {
    String str;
    int intVal;
}

/* ********************* Result
'xxx' exist in lst list : Result = false
'1st' exist in lst list : Result = true
'1st' exist in lst list : Result = false
*/

stream (Object Filter)

import static java.util.Objects.isNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class Stream_ObjFilter {

    public static void main(String[] args) {
        filter();
    }

    private static void filter() {
        System.out.println("'1st' exist in lst list : Result = " + getInnerClassValObj(getList("1st")).isPresent());
        System.out.println("'1st' exist in lst list : Result = " + getInnerClassValObj(getList("2nd")).isPresent());
    }
    private static List<MyObject3> getList(String strVal) {
        MyObject3 obj1 = new MyObject3();
        obj1.intVal = 100;
        MyObject3 obj2 = new MyObject3();
        obj2.classVar = new InnerClass();
        obj2.classVar.str = strVal;
        obj2.intVal = 100;
        List<MyObject3> lst = new ArrayList<MyObject3>();
        lst.add(obj1);
        lst.add(obj2);
        return lst;
    }

    private static Optional<InnerClass> getInnerClassValObj(List<MyObject3> lst) {
        return lst.stream()
            .filter(InnerClass::hasValue)
            .map(MyObject3::getInnerClass)
            .filter(Objects::nonNull)
            .filter(InnerClass::isFirst)
            .findAny();
    }
}
class MyObject3 {
    InnerClass classVar;
    int intVal;
    public InnerClass getInnerClass() {
        return this.classVar;
    }
}
class InnerClass {
    String str;

    public static boolean hasValue(MyObject3 myObject3) {
        return !isNull(myObject3.toString());
    }

    public boolean isFirst() {
        return "1st".equals(this.str);
    }
}
/* ********************* Result
'1st' exist in lst list : Result = true
'1st' exist in lst list : Result = false
*/

instanceof

    private static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        }

        if (obj instanceof CharSequence) {
            return ((CharSequence) obj).length() == 0;
        }
        if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        }
        if (obj instanceof Collection) {
            return ((Collection) obj).isEmpty();
        }
        if (obj instanceof Map) {
            return ((Map) obj).isEmpty();
        }

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

<java>正規表現で住所を番地前後で分割する

概要

javaで正規表現を使って、住所を番地前後で分割しようと思います。

コンソールから住所を入力することで分割された住所が表示されます。
※あくまで簡略的なものであり、全ての住所を完璧に分割できるものではないのでご了承ください。

手順

手順は以下の通りです
1.Patternクラス(java.util.regex.Pattern)で正規表現をコンパイル
2.Matcherクラス(java.util.regex.Matcher)のfindメソッドで入力された文字列が正規表現にマッチするか確認する。
3.マッチした場合文字を分割し、表示する。

正規表現の詳細はこちらのサイトを参考にいたしました。
https://www.sejuku.net/blog/13215

コード

SplitAddress.java
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SplitAddress{

    public static void main(String args[]) throws Exception {
        System.out.println("住所を入力してください");
        Scanner sc = new Scanner(System.in);
        //コンソールに入力された文字を下記メソッドに引数として渡します。
        String[] ad = split(sc.nextLine());

        //出力しておく
        System.out.println(ad[0]+"\n"+ad[1]+"\n"+ad[2]);
        sc.close();
    }

    //住所を分割するメソッド
    static String[] split(String str)throws Exception {
        String[] ad = new String[3];

        //判定するパターンの生成準備
        //半角数字・全角数字が一回以上または漢数字が一回以上(番地の先頭)
        String p1 = "([0-90-9]+|[一二三四五六七八九十百千万]+)+";

        //半角数字・全角数字が一回以上または漢数字が一回以上またはハイフンなどの接続記号が0回以上(番地の中身)
        String p2 = "([0-90-9]+|[一二三四五六七八九十百千万]+|(丁目|丁|番地|番|号|-|‐|ー|−|の))*";

        //半角数字・全角数字が一回以上または漢数字が一回以上または数字/号など番地の終わりに使われる文字が1回(番地の終わり)
        String p3 = "([0-90-9]+|[一二三四五六七八九十百千万]+|(丁目|丁|番地|番|号))";

        //ex)番地が2-6-6なら「2」がp1、「-6-」がp2、「6」がp3に当てはまります。

        //正規表現
        Pattern p = Pattern.compile(p1+p2+p3);
        Matcher m = p.matcher(str);

        //正規表現に該当する文字列があればその前後で住所を分割する
        if(m.find()) {
            ad[0] = str.substring(0,m.start());
            ad[1] = str.substring(m.start(),m.end());
            ad[2] = str.substring(m.end(),str.length());


            //四万十3-6-1みたいな漢数字+数字の時のための処理
            //(3分割された住所の番地部分に、漢数字1回+数字1回という正規表現に該当する文字列があれば分割する境を訂正する)

            //漢数字1回+数字1回
            String p4 = "[一二三四五六七八九十百千万][0-90-9]";

            p = Pattern.compile(p4);
            m = p.matcher(ad[1]);
            if(m.find()) {
                String tmp = ad[1].substring(0,m.start());
                ad[0] = ad[0] + tmp;
                ad[1] = ad[1].substring(m.start(),ad[1].length());
            }

            //3-4-5三井ビルみたいな数字+漢数字の時のための処理
            String p5 = "[0-90-9][一二三四五六七八九十百千万]";
            p = Pattern.compile(p5);
            m = p.matcher(ad[1]);
            if(m.find()) {
                String tmp = ad[1].substring(m.start()+1,ad[1].length());
                ad[2] = tmp + ad[2];
                ad[1] = ad[1].substring(0,m.start());
            }
        }else ad[0] = str;

        return ad;
    }
}

実行結果

緑色の文字が入力した住所、その下3行が分割された住所です。

image.png

試しに使ってみてください!!

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

Effective Java privateのコンストラクタでインスタンス化不可能を強制する

Effective Javaの独自解釈です。
第3版の項目4について、自分なりにコード書いたりして解釈してみました。

結論

staticメンバしか持たないクラスには、実行時例外を出すprivateコンストラクタを用意しよう。

説明

例えば共通の処理だけを集めたユーティリティクラス(以下、Utilクラス)はstaticメソッド、static変数のみを持ち、インスタンス化を前提としていない。

一般にpublicなUtilクラスは、コンストラクタを定義しないとpublicなデフォルトコンストラクタが用意されるため、外部のクラスで無駄にインスタンス化されてしまう懸念が生じる。

明示的にprivateコンストラクタを定義することで、Utilクラス外でのコンストラクタ呼び出しを防ぐことができる。
また、コンストラクタに例外出力処理を入れることで、Utilクラス内でのコンストラクタ呼び出しをエラーとすることができる。

※Utilクラスそもそもいらない論はここでは無視。

コンストラクタ定義なしコード(ダメな例)

Utilクラス

public class SampleUtil {

    public static final String HOGE = "hoge";

    public static String fugaMethod() {
        return "fuga";
    }
}

呼び出し側クラス

public class SampleService {

    public String sampleMethod() {
        // 無駄にインスタンス化
        SampleUtil sampleUtil = new SampleUtil();
        return sampleUtil.fugaMethod();
    }
}

呼び出し側クラスでインスタンスを作っています。fugaMethod()はstaticメソッドなので、インスタンス化するのは無駄です。

privateコンストラクタ定義ありコード(良い例)

Utilクラス

public class SampleUtil {

    // privateかつ実行時例外を出力するコンストラクタを定義
    private SampleUtil() {
        throw new IllegalStateException("Util instance construction is not allowed.");
    }

    public static final String HOGE = "hoge";

    public static String fugaMethod() {
        return "fuga";
    }

}

呼び出し側クラス

public class SampleService {

    public String sampleMethod() {
        return SampleUtil.fugaMethod();
    }
}

コンストラクタがprivateなので、別のクラスではそもそもインスタンス化できません。

また、仮にUtilクラス内でコンストラクタが呼ばれたとしても、例外が出力されるためインスタンスが作られることはありません。コンストラクタを呼ぶようなコードが書かれている場合、それはプログラミングエラーなので、実行時例外を出すようにしましょう。

※コンパイルは通るので、プログラミングエラー自体を防ぐことはできません。

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

privateのコンストラクタでインスタンス化不可能を強制する

Effective Javaの独自解釈です。
第3版の項目4について、自分なりにコード書いたりして解釈してみました。

結論

staticメンバしか持たないクラスには、実行時例外を出すprivateコンストラクタを用意しよう。

説明

例えば共通の処理だけを集めたユーティリティクラス(以下、Utilクラス)はstaticメソッド、static変数のみを持ち、インスタンス化を前提としていない。

一般にpublicなUtilクラスは、コンストラクタを定義しないとpublicなデフォルトコンストラクタが用意されるため、外部のクラスで無駄にインスタンス化されてしまう懸念が生じる。

明示的にprivateコンストラクタを定義することで、Utilクラス外でのコンストラクタ呼び出しを防ぐことができる。
また、コンストラクタに例外出力処理を入れることで、Utilクラス内でのコンストラクタ呼び出しをエラーとすることができる。

※Utilクラスそもそもいらない論はここでは無視。

コンストラクタ定義なしコード(ダメな例)

Utilクラス

public class SampleUtil {

    public static final String HOGE = "hoge";

    public static String fugaMethod() {
        return "fuga";
    }
}

呼び出し側クラス

public class SampleService {

    public String sampleMethod() {
        // 無駄にインスタンス化
        SampleUtil sampleUtil = new SampleUtil();
        return sampleUtil.fugaMethod();
    }
}

呼び出し側クラスでインスタンスを作っています。fugaMethod()はstaticメソッドなので、インスタンス化するのは無駄です。

privateコンストラクタ定義ありコード(良い例)

Utilクラス

public class SampleUtil {

    // privateかつ実行時例外を出力するコンストラクタを定義
    private SampleUtil() {
        throw new IllegalStateException("Util instance construction is not allowed.");
    }

    public static final String HOGE = "hoge";

    public static String fugaMethod() {
        return "fuga";
    }

}

呼び出し側クラス

public class SampleService {

    public String sampleMethod() {
        return SampleUtil.fugaMethod();
    }
}

コンストラクタがprivateなので、別のクラスではそもそもインスタンス化できません。

また、仮にUtilクラス内でコンストラクタが呼ばれたとしても、例外が出力されるためインスタンスが作られることはありません。コンストラクタを呼ぶようなコードが書かれている場合、それはプログラミングエラーなので、実行時例外を出すようにしましょう。

※コンパイルは通るので、プログラミングエラー自体を防ぐことはできません。

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

【Java】LocalDateTimeクラスで日付を取得する

まずはimportが必要

import java.time.LocalDateTime;

でパッケージの利用を宣言する必要があります。
Eclipseを使用している場合は宣言する前にLocalDateTimeのインスタンス化をすれば自動的にimport文が挿入されるようです。超便利ですね。

現在の日付を取得してみよう

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; // 形式を指定する場合はこのパッケージ宣言が必要

public class Sample_01 {

    public static void main(String[] args) {
        LocalDateTime d = LocalDateTime.now(); // 現在時刻のインスタンスを取得
//      LocalDateTime d = LocalDateTime.of(2016,  1, 1, 10. 10, 10); // 特定の日付のインスタンスを取得
//      LocalDateTime d = LocalDateTime.parse("2016-02-02T10:10:10"); // isoが指定する書式で特定の日付のインスタンスを取得
//      
        System.out.println("d: " + d);
        System.out.println("getyear(): " + d.getYear()); // 西暦を取得
        System.out.println("getMonth(): " + d.getMonth()); // 西暦を取得
        System.out.println("getMonth().getValue(): " + d.getMonth().getValue()); // 西暦を取得
        System.out.println("plusMonths(2).minusDays(3): " + d.plusMonths(2).minusDays(3)); // 西暦を取得
        LocalDateTime nd = d.plusMonths(2).minusDays(3); // 日付の計算
        System.out.println("nd: " + nd); // 変数に入れて出力
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd/");
        System.out.println("DateTimeFormatter.ofPattaern: " + d.format(dtf));
    }

}

実行結果
d: 2020-09-12T14:42:11.438
getyear(): 2020
getMonth(): SEPTEMBER
getMonth().getValue(): 9
plusMonths(2).minusDays(3): 2020-11-09T14:42:11.438
nd: 2020-11-09T14:42:11.438
DateTimeFormatter.ofPattaern: 2020/09/12/

ポイント

  • println(d)だとISOの形式で日付が出力できる
  • LocalDateTime nd = d.plusMonth(2)などで日付をずらしたものを別の変数に代入することも可能
  • DateTimeFormatterを使う場合はimport java.time.format.DateTimeFormatter;が必要
  • getMonth()は英語の月名
  • getMonth().getValue()で月の月の数字
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaの診断ツール Arthas(アーサス)を使ってみた

Arthas(アーサス)

Alibaba ArthasはJavaアプリケーションの監視、プロファイリングなどができるツールです。監視対象のアプリケーションの設定変更や再起動なしに利用できるのが特徴です。
Arthasではできることがたくさんあって、これまで一部の機能だけ使っていたのですが、改めて調べてみると様々なことができることが分かったので紹介がてらに触ってみたいと思います。

インストールと実行

Arthasのインストールと実行は以下のコマンドを実行するだけです(Linuxの場合)。
Arthas自体はjavaアプリケーションですので、他の環境でも動かすことができます。

$ curl -O https://arthas.aliyun.com/arthas-boot.jar
$ java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.4.0
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 631 org.logstash.Logstash
  [2]: 3834 camel-springboot-rest-test-0.0.1-SNAPSHOT.jar

下の2行で起動中のjavaアプリケーションが表示されるので、監視対象のプロセスを入力します。

2 ★今回は"2"を入力
[INFO] arthas home: /root/.arthas/lib/3.4.1/arthas
[INFO] Try to attach process 3834
[INFO] Attach process 3834 success.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.                           
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'                          
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.                          
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |                         
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'                          


wiki      https://arthas.aliyun.com/doc                                         
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html                   
version   3.4.1                                                                 
pid       3834                                                                  
time      2020-09-12 01:17:02    

ダッシュボード

dashboardコマンドで、javaアプリケーションのスレッド一覧、ヒープの使用状況等が表示できるダッシュボードを実行できます。
終了するには"q"を押します。

[arthas@3834]$ dashboard

image.png

スレッド

threadコマンドでスレッドの一覧を表示できます。

image.png

先ほどの一覧から[スレッドID]を用いて、特定のスレッドの情報を表示できます。

[arthas@3834]$ thread 156
"Camel (camel-1) thread #1 - ThroughputLogger" Id=156 TIMED_WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74abfe92
    at sun.misc.Unsafe.park(Native Method)
    -  waiting on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@74abfe92
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

スタックトレースを表示する

メソッドのスタックトレースを表示します。

stack [クラス] [メソッド]

※クラスとメソッドには*[アスタリスク]も使用できます。
該当のメソッドが実行されるたびに以下のようにスタックトレースが表示されます。

[arthas@4939]$ stack mkyz08.example.HelloRestController hello
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 29 ms, listenerId: 5
ts=2020-09-12 01:47:58;thread_name=http-nio-8080-exec-8;id=18;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@710f4dc7
    @mkyz08.example.HelloRestController.hello()
        at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:215)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:142)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)


表示回数を制限する場合は以下のように実行します。

stack [クラス] [メソッド] -n [実行回数]

メソッドの実行をモニタリング

メソッドの戻り値、例外、パラメーターなどの情報をモニタリングします。
watchコマンドで実行できます。

watch [クラス] [メソッド] [他のオプション]

"{params,returnObj}"で実行後の戻り値、パラメーターを表示します。
"-x 2"は表示の深さです。

[arthas@4939]$ watch mkyz08.example.HelloRestController hello "{params,returnObj}" -x 2 
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 35 ms, listenerId: 19
ts=2020-09-12 05:24:36; [cost=1.155725ms] result=@ArrayList[
    @Object[][
        @String[hoge],
    ],
    @String[Hello World],
]

"{params,returnObj} -b"でメソッド実行前の戻り値、パラメーターを表示します。
戻り値は指定していますが、実行前なので必ずnullになります。

[arthas@4939]$ watch mkyz08.example.HelloRestController hello "{params,returnObj}" -x 2 -b
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 36 ms, listenerId: 20
ts=2020-09-12 05:25:06; [cost=0.03669ms] result=@ArrayList[
    @Object[][
        @String[hoge],
    ],
    null,
]

ログレベルの変更

loggerコマンドでロガーの情報を表示します。

[arthas@4939]$ logger
 name                  ROOT                                                                                                      
 class                 ch.qos.logback.classic.Logger                                                                             
 classLoader           org.springframework.boot.loader.LaunchedURLClassLoader@685f4c2e                                           
 classLoaderHash       685f4c2e                                                                                                  
 level                 INFO                                                                                                      
 effectiveLevel        INFO                                                                                                      
 additivity            true                                                                                                      
 codeSource            jar:file:/root/camel-springboot-rest-test-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/logback-classic-1.2.3.jar!/    
 appenders             name            CONSOLE                                                                                   
                       class           ch.qos.logback.core.ConsoleAppender                                                       
                       classLoader     org.springframework.boot.loader.LaunchedURLClassLoader@685f4c2e                           
                       classLoaderHash 685f4c2e                                                                                  
                       target          System.out   

以下のコマンドでログレベルを変更することができます。

$ logger --name ROOT --level DEBUG
Update logger level success.

Spring-Bootの場合は"-c"でクラスローダーを指定する必要がありました。

$ logger --name ROOT --level DEBUG -c 685f4c2e
Update logger level success.

MBeanの情報を表示する

mbeanコマンドでMBeanの情報を表示することができます。

[arthas@4939]$ mbean org.apache.camel:context=MyCamelRestApp,type=routes,* ExchangesCompleted
 OBJECT_NAME         org.apache.camel:context=MyCamelRestApp,type=routes,name="hello world route"                                
--------------------------------------------------------------------------------------------------                               
 NAME                VALUE                                                                                                       
--------------------------------------------------------------------------------------------------                               
 ExchangesCompleted  78                                                                                                          

ヒープダンプを取得する

heapdumpコマンドでヒープダンプを取得することができます。

[arthas@4939]$ heapdump /tmp/dump.hprof
Dumping heap to /tmp/dump.hprof ...
Heap dump file created

"--live"を指定するとヒープダンプ取得前にFullGCが実行されます。

[arthas@4939]$ heapdump --live /tmp/dump_live.hprof
Dumping heap to /tmp/dump_live.hprof ...
Heap dump file created

async-profiler

async-profilerを使って アプリケーションにプロファイラをかけてみます。

[arthas@4939]$ profiler start
Started [cpu] profiling

# サンプルデータの取得件数を確認する
[arthas@4939]$ profiler getSamples
2

# プロファイラーのステータスを確認する
[arthas@4939]$ profiler status
[perf] profiling is running for 19 seconds

[arthas@4939]$ profiler stop
OK
profiler output file: /root/arthas-output/20200912-022458.svg

"profiler stop"でプロファイラーを停止させると、SVGファイルが出力されます。HTMLで出力することもできます。
取得されたFlame Graphは以下のとおり。

image.png

メソッドの呼び出しをトレースする

traceコマンドでメソッドの呼び出しをトレースすることができます。
トレースには各メソッドの実行時間も表示されるので、どのメソッドが遅いのかを分析するのに使用できます。

[arthas@4939]$ trace mkyz08.example.HelloRestController hello
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 72 ms, listenerId: 7
`---ts=2020-09-12 02:11:16;thread_name=http-nio-8080-exec-3;id=13;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@710f4dc7
    `---[1.01167ms] mkyz08.example.HelloRestController:hello()
        +---[0.130848ms] org.apache.camel.CamelContext:getEndpoint() #26
        `---[0.649678ms] org.apache.camel.ProducerTemplate:sendBody() #27

実行回数を制限する場合は以下のように実行します。

trace [クラス] [メソッド] -n [実行回数]

指定した時間(例では100ms)実行されたメソッドだけ抽出したい場合は以下のように実行します。

trace [クラス] [メソッド] '#cost>100'

traceコマンドだけではないですが、grepコマンドも使用できます。
例えば、パッケージでフィルタリングするような使い方ができます。

[arthas@5716]$ trace mkyz08.example.HelloRestController hello | grep mkyz08
Press Q or Ctrl+C to abort.
    `---[1.459843ms] mkyz08.example.HelloRestController:hello()
        `---[0.066367ms] mkyz08.example.HelloRestController:add() #29

逆コンパイル

jadコマンドでJVMで実行されているバイトコードをソースコードに逆コンパイルすることができます。
trace, stackコマンドで表示されたメソッドのコードを確認したい場合に利用できます。
他にも修正した内容がサーバに適用されているか確認する目的で使用できそうです。

[arthas@4939]$ jad mkyz08.example.HelloRestController

ClassLoader:                                                                                                                     
+-org.springframework.boot.loader.LaunchedURLClassLoader@685f4c2e                                                                
  +-sun.misc.Launcher$AppClassLoader@70dea4e                                                                                     
    +-sun.misc.Launcher$ExtClassLoader@7c6908d7                                                                                  

Location:                                                                                                                        
file:/root/camel-springboot-rest-test-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/                                                     

/*
 * Decompiled with CFR.
 * 
 * Could not load the following classes:
 *  org.apache.camel.CamelContext
 *  org.apache.camel.Endpoint
 *  org.apache.camel.ProducerTemplate
 *  org.springframework.web.bind.annotation.RequestMapping
 *  org.springframework.web.bind.annotation.RequestMethod
 *  org.springframework.web.bind.annotation.RestController
 */
package mkyz08.example;

import javax.annotation.Resource;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.ProducerTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value={"/spring"})
public class HelloRestController {
    @Resource
    private ProducerTemplate producer = null;
    @Resource
    private CamelContext context;

    @RequestMapping(method={RequestMethod.GET}, value={"/hello"}, produces={"text/plain"})
    public String hello(String msg) {
        Endpoint end = this.context.getEndpoint("seda:hello_world");
        this.producer.sendBody(end, (Object)msg);
        return "Hello World";
    }
}

Affect(row-cnt:1) cost in 505 ms.

クラスを入れ替える

外部のクラスファイルを読み込んで、JVM内のクラスと入れ替えます。

制約事項
・引数、メソッド名、戻り値の変更はできません。
・クラスのフィールドとメソッドを変更、追加、削除はできません。

[arthas@5716]$ redefine /root/HelloRestController.class 
redefine success, size: 1, classes:
mkyz08.example.HelloRestController

メソッドの呼び出しを監視する

monitorコマンドでメソッドの実行回数、平均実行時間等を監視します。

monitor [クラス] [メソッド]

実行結果の例は以下のとおり。"-c"オプションで表示間隔(秒)を指定します。

[arthas@5716]$ monitor -c 5 mkyz08.example.HelloRestController hello
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 35 ms, listenerId: 3
 timestamp           class                         method                       total     success   fail      avg-rt(m  fail-rat 
                                                                                                              s)        e        
---------------------------------------------------------------------------------------------------------------------------------
 2020-09-12 06:40:2  mkyz08.example.HelloRestCont  hello                        1         1         0         0.40      0.00%    
 4                   roller                                                                                                      

 timestamp           class                         method                       total     success   fail      avg-rt(m  fail-rat 
                                                                                                              s)        e        
---------------------------------------------------------------------------------------------------------------------------------
 2020-09-12 06:40:2  mkyz08.example.HelloRestCont  hello                        9         9         0         0.97      0.00%    
 9                   roller                                                                                                      

 timestamp           class                         method                       total     success   fail      avg-rt(m  fail-rat 
                                                                                                              s)        e        
---------------------------------------------------------------------------------------------------------------------------------
 2020-09-12 06:40:3  mkyz08.example.HelloRestCont  hello                        5         5         0         0.26      0.00%    
 4                   roller       

VMオプションの表示

[arthas@4939]$ vmoption
 KEY                             VALUE                            ORIGIN                          WRITEABLE                      
---------------------------------------------------------------------------------------------------------------------------------
 HeapDumpBeforeFullGC            false                            DEFAULT                         true                           
 HeapDumpAfterFullGC             false                            DEFAULT                         true                           
 HeapDumpOnOutOfMemoryError      false                            DEFAULT                         true                           
~略~                       

# 値を変更する場合

[arthas@4939]$ vmoption PrintGC true
Successfully updated the vm option.
 NAME     BEFORE-VALUE  AFTER-VALUE                                                                                              
------------------------------------                                                                                             
 PrintGC  false         true   

その他の基本コマンド

help - ヘルプを表示
cls - 画面をクリア
session - 現在のセッション情報を表示
reset - resets enhanced classes. All enhanced classes will be reset to their original states. When Arthas server closes, all these enhanced classes will be reset too
version - Arthasのバージョンを表示
history - コマンドの実行履歴を表示
quit - Arthasのクライアントを終了
stop - Arthasサーバーを停止
keymap - Arthasのキーボードショートカットを表示

おわりに

今まで色々なツールを使って実行していたことが、Arthasがあればほとんどやれてしまうようになりました。
また、今回紹介したコマンド以外にもたくさんのコマンドがあるので公式サイトで調べてみてください。Webコンソールも面白そうでした。

参考

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

美大生のためのプログラミング入門:変数

※ Qiita では、本文部分のみの印刷に苦労します。そのため、同じ内容を以下のページにも掲載しています。プリンツアウトしたり PDF 化したい人は、こちらのページを利用して下しさい:

http://gurakura.sakura.ne.jp/hellomondrian/variable/

一覧はこちら:
http://gurakura.sakura.ne.jp/series/美大生のためのプログラミング入門/

Qiita 版の総合目次:
https://qiita.com/iigura/items/37180d127da93d0b8abb


Vertical Composition with Blue and White
Vertical Composition with Blue and White by Piet Mondrian, 1936.
https://www.wikiart.org/en/piet-mondrian/vertical-composition-with-blue-and-white-1936

はじめに

 この章では、変数について学びます。変数はプログラミングにおける最初の関門(しかも難関)ではなかろうかと私は思っています。プログラミングを学び始めたばかりの人には難易度の高い「変数」ですが、皆さんに意地悪をするために、このような機能が用意されている訳ではありません。

 「変数」という概念を使わなければ、実現できないことが存在したり、もしくは変数を使った方が便利、もしくは理解しやすい場合があるため、変数という概念・機能が存在するわけです。

とはいうものの、少々難解な概念ではありますので、その必要性や便利になる様子を軸にこの章では説明していきます。

面倒な状況を体験する

 変数の必要性や「ありがたさ」を実感するためには、変数を使わないと面倒な状況を体験するのが良いと思います。ここでは以下に示す図のような絵を、プログラムにより描くことを通じて、あえて「面倒な状況」を体験してみましょう。

vertical0.png

この図形を描くプログラムは以下の通りです:

// vertical comp-position ver.0
background(250,245,230);
size(500,500);

noStroke();
fill(0,80,255); // blue
rect(450,0,50,100);

strokeWeight(10);
stroke(0,0,0);
line(450,0,450,500);   // vertical
line(450,100,500,100); // horizontal

 上のプログラムで描かれた作品は、青色の四角形の位置が少々右すぎるように感じます(あくまでも私の感覚ですが)。もう少し、四角形の配置場所を少し左側に移動させてみましょう。
もちろん、単に移動させるだけではなく、幅も増やさないといけません。それも増やすようにしてみます。

例えば、rect 関数の第 1 引数を 450 から 300 に減らし、第 3 引数を 50 から 120 に増やして様子を見てみましょう。プログラムは次のようになります。

// vertical comp-position ver.1
background(250,245,230);
size(500,500);

noStroke();
fill(0,80,255);        // blue
rect(350,0,120,100);   // <-- change

strokeWeight(10);
stroke(0,0,0);
line(450,0,450,500);   // vertical
line(450,100,500,100); // horizontal

このプログラムを実行すると、次のような作品が得られます:

vertical1.png

...残念ながら、絵が壊れてしまいました。良く考えてみれば、キャンバスのサイズは size 関数にて横幅 500 ピクセルと指定しています。なので、右端いっぱいまでの四角形を配置するのであれば、四角形の幅は四角形の左上の座標の x 値と関係するはずです。左上の座標の x 値が 350 であるならば、四角形 の横幅は 150(= 500 − 350) としなければなりません。

 また、縦線を描画している line(450,0,450,500); の引数も修正が必要です。今回、位置を修正した青色の四角形の左上の座標と重なるように描画するためには、この line 関 数の第 1 引数と第 3 引数の値を 450 から 350 に変更する必要があります。

同様に、水平線の左端の座標についても修正が必要です。具体的には、line(450,100,500,100); と書いていたものを、第 1 引数を 450 から 350 に 変更し、line(350,100,500,100); と書き換える必要があります。

 このように、修正を施したプログラムリストとその実行結果を以下に示します:

// vertical comp-position ver.2
background(250,245,230);
size(500,500);

noStroke();
fill(0,80,255); // blue
rect(350,0,500-350,100);

strokeWeight(10);
stroke(0,0,0);
line(350,0,350,500);   // vertical
line(350,100,500,100); // horizontal

vertical2.png

 なかなか良い感じです。これら関係する場所の数値を全て修正することで、壊れた絵がなんとか直りました。一安心しつつ、もういとど生成された絵を眺めてみると、あと少し、もうちょっとだけ縦線の位置を右にずらしてみたくなりました。

また、もう一度、これまでのようなプログラムの修正を行わなければなりません。しかも、修正すべき場所を全て修正し、かつ、忘れることなく直していかないといけません。何度もプログラム修正して確認、 という作業を延々と繰り返すこととなります。これは非常に面倒です。

これは、なんとかならないものなのでしょうか?(...という気分になれば、このセクションは上手く機能した、ということになります)。

プログラムの構造

 縦線の位置または青い四角形の左上の位置を、気軽に変更するため、先に示したプログラムの構造をみてみます。このプログラムは、縦線の位置および四角形の左上の頂点を x=350 の場所に変更したものでした。この 350 という数値に着目してプログラムリストを再度眺めてみると、次のようなことが分かります。

 最初に 350 という数値が出てくるのは、7 行目の rect 関数の引数です:

rect( 350 ,0,500- 350 ,0);
//    ^^^         ^^^

ここでは、分かりやすいように x 座標の値 350 に関する部分の下にコメントで印を付けておきました。

同様に x 座標の値 350 に関連する部分を以下に示します:

line( 350 ,0, 350 ,500); // 11 行目
//    ^^^     ^^^

line( 350 ,100,500,100); // 12 行目
//    ^^^

 x 座標の値 350 に関する場所を全て調べてみると、計 5 ヶ所あります。つまり縦線の位置を、例えば 350 から 335 に変更する場合、これら 5 ヶ所 全ての数値を変更しなければ、絵が壊れてしまうこととなります。

言い換えると、こんな簡単な絵を生成するプログラムであっても、描画の位置を少し変更するためには 5 ヶ所の変更が必要であることを意味します。もちろん、もっと複雑な絵を生成するプログラムであれば、修正すべき場所は増える可能性が高くなります。

 単に、描画位置を変更するだけなので、これらの変更は自動的に更新されるようにできないものなのでしょうか? いちいち手動で関連する場所をひとつひとつ修正していくのは面倒です。また、手作業で都度修正していくのでは、修正し忘れも発生しそうで心配です。

 …とまあ、このような気持ちになったのであれば、あなたには良いプログラムを書ける素質があります(多分)。少々の面倒を受け入れることにより、より大きな面倒を回避するという考えはプログラム開発においては結構重要な考えだと私は思っています。

 少し話が逸れました。話を元に戻します。そもそも、縦線の位置を変更するたびに、何故、プログラム中の 5 ヶ所を変更しなければならないのでしょうか? それは、これまでに示したプログラムリストが「具体的すぎる」からです。

具体的の反対は抽象的であり、具体的な情報を抽象的にするためには、情報量を減らさなければなりません 。

 情報量を減らすとは、どういう意味なのでしょうか? ここでは、まず情報を増やしてみることについて考え、その後、反対に情報を減らす、ということについて考えてみます。

一般的に、情報を増やすとより具体的になります。例えば、犬という情報に「秋田犬」とか「柴犬」という情報を付加すると、より具体的な犬の犬種について述べることとなります。一方、「犬」という概念から、犬という具体的な情報を減らすと、例えば「哺乳類」とか「脊椎動物」とか「動物」、「生物」という、より抽象的な概念となります。

 プログラムに戻って、もし、上に挙げた 5 ヶ所の部分を、例えば以下のように記述できたらどうでしょうか?

rect( 縦線の x 座標の値 ,0,   500-縦線の x 座標の値 ,0  ); //  7 行目
line( 縦線の x 座標の値 ,0,   縦線の x 座標の値 ,    500); // 11 行目
line( 縦線の x 座標の値 ,100, 500,                 100); // 12 行目

もし、このような書き方をしていれば、任意の場所に縦線を描く場合でも修正は要らなさそうです。つまり、もし、次のようなプログラムが許されるのであれば、どのような場所に縦線を描画しようとも対応できそうです:

// pseudo vertical comp-position ver.3
background(250,245,230);
size(500,500);
noStroke();
fill(0,80,255); // blue
rect(縦線の x 座標の値,0, 500-縦線の x 座標の値,0);
strokeWeight(10); stroke(0,0,0);
// draw a vertical line
line(縦線の x 座標の値,0, 縦線のx 座標の値,500);
// draw a horizontal line
line(縦線の x 座標の値,100, 500,100);

 実は、上のプログラムはこのままでは動作しません。なぜ動作しないのでしょうか? これはコンピュータの側に立って、実際の処理について考えてみると分かります。例えば、7 行目に「rect(縦線の x 座標の値,0, 500-縦線の x 座標の 値,100);」というプログラム片があります。rect は四角形を描く関数でしたが、さて、キャンバスのどこに四角形を描けば良いのでしょうか? 「縦線の x 座標の値」とありますが、「具体的に」どの場所に描けばよいのでしょう?

 Processing に限らず、プログラムの実行に関しては、具体的な値というものが求められます。上のプログラムにおける rect 関数においては、「縦線の x 座標の値」とされている部分の「具体的な値」が分からないので、このプログラムは動かない、という結論になります。

 「だから 350 という値を書いていたのに」という気持ちは十分に理解できます。しかし、それでは rect や line 関数の引数として、情報が具体的すぎるのです。関数の引数に具体的な値を書くのではなく、例えば、「縦線の x 座標の値は 350 とする」という記述が、プログラム中になされていたらどうでしょうか? この方法であれば、rect や line 関数の引数として、具体的な値を使わず、かつ、直線や長方形を具体的に画面のどの場所に描けばよいか、ということも明らかとなります。

プログラムで書くとすると、こんな感じになることでしょう:

// pseudo vertical comp-position ver.3
background(250,245,230);
size(500,500);

noStroke();
fill(0,80,255); // blue縦線の

x 座標の値は 350 とする// <--- 具体的な値の情報! 
rect縦線の ( x 座標の値,0, 縦線の500- x 座標の値,100); 
strokeWeight(10);
stroke(0,0,0);

// draw a vertical line
line縦線の ( x 座標の値,0, 縦線のx 座標の値,500);

// draw a horizontal line
line縦線の ( x 座標の値,100, 500,100);

 このプログラムは 8 行目にて、具体的な x 座標の値が与えられています。なので、理屈としては動作するハズです。実際、プログラムの流れやプログラムに必要な情報については十分なのですが、Processing では我々が日常使用している言葉(自然言語)は理解されませんので、「縦線の x 座標の値は 350 とする」と書かれていても処理できません。

これをどのように Processing が理解できる形で記述するのかについて、次のセクションで説明します。

縦線の位置変更に対応するプログラム

 上のプログラムリストで問題だったのは、日本語で書かれている「縦線の x 座標 の値は 350 とする」という部分でした。そもそも Processing のエディタ (プログラムを入力する部分)に日本語を入力しても文字化けしてしまうので、日本語を使わずにこの情報を記述しなければなりません。

 「縦線の x 座標の値」を例えば px とする場合、「縦線の x 座標の値は 350 とする」という文章は「px は 350 とする」と書き換えることができます。なぜ px なのか?と疑問を持った方もいるかもしれません。結論から言ってしまえば、私が適当に決めたから、です。アルファベットまたはアンダーバーと呼ばれる記号で始まる文字列であれば何でも良いのですが、position x の意味ぐらいとして px と名付けました。

 「px は 350 とする」という処理は px=350 と書きます。数学の場合、px = 350 という記述は px が 350 と「等しい」という意味でもありますが、Processing においては等号(’=’ イコール)の記号は「代入」を意味します。

a=1 ならば a を 1 とし(代入し)、以後 a と書かれているところは 1 と書かれているのと同じとなります。

なので、

px=350;
rect(px,0, 500-px,100);

というプログラムは、

rect(350,0, 500-350,100);

と同じ動作となります。

 では、プログラムでも px=350; と書けば良いかというと、もう少し記述が必要です。単に px=350 とか書かれても「そもそも px って何よ?」という状態になります。

まあ、そうはならない言語もあるのですが、このシリーズで扱っている Java という言語では「そもそも px って何?」という状況になってしまいます。

px は値 — ここでは整数 — を表している記号であることを示す必要があります。そのためには int px; という記述をします。int という単語は整数を意味する integer から来ています。

 int px; と書けば、px という表記は整数を表すものだとコンピュータに伝えることができます。このように、px とは何者だ? という情報をコンピュータに与えることを「宣言」と言います。コンピュータに一度伝えてしまえば(宣言してしまえば)、あとは px に値を割り当てることができるよう になりますので、px=350; などと書けるようになります。

実際のコードで示すと、

int px;
px=350;

といった感じです。

もちろん、350 という値に限らず 349 という値でも構いません。

int px;
px=349;

宣言と当時に値を代入する(割り当てる)ことも可能です。その場合は、

int px=350;

などと書きます。

 ここまでの知識で、px という表現を用いて抽象的な描画プログラムを記述できるようになりました。px を用いて、プログラムを次のように修正すれば、実際に動作するプログラムとなります:

// vertical comp-position ver.4
background(250,245,230);
size(500,500);

noStroke();
fill(0,80,255); // blue

int px=350; // <--- 具体的な値の情報!

rect(px,0, 500-px,100);

strokeWeight(10);
stroke(0,0,0);
line(px,  0, px, 500); // draw a vertical line
line(px,100, 500,100); // draw a horizontal line

 このプログラムは縦線の描画位置変更に対応したプログラムです。なので、8 行目の px の値を ー 例えばもっと極端に ー 100 へと変更してみましょう:

// vertical comp-position ver.5
background(250,245,230);
size(500,500);

noStroke();
fill(0,80,255); // blue

int px=100; // <--- 350 から100 に変更

rect(px,0, 500-px,100); 11

strokeWeight(10);
stroke(0,0,0);
line(px,  0, px, 500); // draw a vertical line
line(px,100, 500,100); // draw a horizontal line

vertical6.png

変数とは何か

 上のプログラムリストを使うと、あまり手間をかけずに ー つまり px に代入する値を変更するのみで ー 生成される作品を(ある程度ですが)自由に変更できるようになりました。px へ代入する値を変化させても、プログラムの 10 行目以降は修正する必要がありません。

 これは一体どういうことなのでしょうか。10 行目以降のプログラムは、ある程度の抽象化が実施されており、例えば、縦線は x の座標値が px である縦線を描くよう抽象的に line(px,0, px,500) とプログラムされています。

抽象的な処理として書かれているので、px に 350 や 100 などの整数値が代入されていても、破綻することなく処理が行われます。

 さて、抽象的に書かれたプログラムにとっては、px というのは一体なんなのでしょうか? あるときは 350 という値(整数値)であったり、またあるときは 100 という値になったりします。

 10 行目以降のプログラムにとっては、px は変化する数値、つまり変化する数に他なりません。この変化する数 px のことを「変数」と呼びます。

int px; などとした宣言は「変数 px を宣言する」等と呼ばれたりします(int px=300; というプログラム片でも同様です)。

変数は複数あっても良い

 ここまで px という変数について説明してきました。抽象的に取り扱う量・値が複数ある場合には、複数の変数を使うこととなります。

一般的なプログラミング言語では、複数の変数が使用可能です。もちろん Processing でもそうです。

 以下、横幅の位置を表す変数を追加し、より自由に絵作りができるようなプログラムを示して、この章を終わることとします。

 横線の y 座標については、py という変数を用いることとします。これは縦線の x 座標についての変数が px だったので、それに類する名前ということで py としただけです。横線の y 座標は、四角形の高さ(height)でもありますので、h という変数名でも良いかもしれません。

もちろんこれ以外の、例えば zzz といった変数名でも動作します。しかし、変数名はコンピュータのためではなく、プログラムリストを読む我々人間のためにあるものです。そのため、できるだけ実体を示す名前をべきです(プログラムリストが読みやすくなります)。

 変数 px と px を用いて、実際に縦線と横線の位置を抽象化したプログラムリストを以下に示します:

// a study for vertical comp-position blue and white
background(250,245,230);
size(500,500);

noStroke();
fill(0,80,255); // blue

// position cofiguration
int px=380;
int py=300;

rect(px,0, 500-px,py); // draw a rectangle.

strokeWeight(10);
stroke(0,0,0);
line(px, 0, px, 500); // draw a vertical line.
line(px,py, 500, py); // draw a horizontal line.

そして、このプログラムで生成される画像は、
以下のようになります:

vertical7.png

コラム:変数名は大事(ヒトにとって)

 他人のプログラムを読む(解読する・理解する)のは非常に大変です。そのため、できるだけ分かりやすい名前を変数に付けるなど、他者に対して優しいプログラムになるようにしましょう。あなたのその優しさは、必ずあなたを救うことでしょう。

 プログラミング界隈には「3 ヶ月前の自分は他人」という言葉があります。プログラムを作っていると、過去の自作コードを利用する・参照する場合があります。そのとき、たとえ自作のプログラムであっても、3 ヶ月も経ってしまうと作った時の記憶は薄れ、もはや他人が作ったプログラムのように感じてしまう、自作のコードとはいえそれを読み解くためには他人のコードと同じけのコストがかかる ー という意味の言葉です。

 なので、できるだけ変数の機能や実体を表す、分かりやすい変数名をつけるようにしましょう。「情けは人のためならず」はプログラミングの世界にも存在します。

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

Java学習方法~1番大変な最初の時期を乗り切る

一番挫折しやすい最初の時期を乗り切るためのJavaの学習方法

今年の2月から本格的にプログラミング学習をはじめまして、
3月からJavaの学習をはじめました。

5月から業務系サービス開発のエンジニアとしてインターンをしていますが、
インターンまでは独学で勉強をしていました。

一番大変だった最初の時期を乗り切るために、おすすめだと思うJavaの勉強法を三段階で紹介します。

①progate

軽くJavaの雰囲気掴む程度でいいと思います。
1~2周で十分です。
やっていて分からないことだらけだと思いますが、気にせず進めていけば問題ありません。

②スッキリわかるJava入門

基礎・オブジェクト指向をもう少し学びたい時期にとても参考になった本です。
イラスト多めで分かりやすく、おすすめです。
基礎は大切です。Javaの基礎、オブジェクト指向の基礎を学びます。

実際に手を動かしながらの方がいいですが、全部は書かなくてもいいかなと思います。
文法の写経はやはり楽しくないです。
見返すこともできるので、一度に頑張りすぎなくてもいいです。

僕はこの時に実践編もやりました。
しかし、そこに書いてあったことは、使わない間に忘れてしまいましたw
一気に知識を詰め込むの大変なので、もっと後でいい気がします。

※この動画を見たところ、Java以外の学習でも勧められていたので、良書だと思います。
https://youtu.be/E2XB3cJqcOU

③なぜ、あなたはJavaでオブジェクト指向ができないのか

オブジェクト指向についての本で、実際にじゃんけんやトランプのゲームを作りながら学んでいきます。
実際にプログラミングで何かを作るという事をイメージできます。

本の中では、「考えて」といわれますが、分からなかったら進めてしまってOKです。
特に、この本の中のババ抜きは最後まで作りきってほしいです。

僕も実際やってみて、初めはさっぱり分かりませんでした。
頑張ってコードをよみ、実際に動かしていきました。
そしたら何となくイメージできるようになりました。

まとめ

ここまで出来たら、それなりにコードは読めて、調べながら簡単なプログラムを作ることができると思います。

実際にインターンを初めて見ても業務で使用しているコードはそれなりに読めますので、
他のコードを読み参考にしながらであれば、改修をしていく業務ならJavaに関しては、あまり困りません。
単純な改修以外ではわからないことだらけですが、、。
調べながらなんとか頑張ってます

Javaでデータベースを使う方法やWebアプリを作る事などは、ここまでで触れていませんが、
これらの細かい部分はフレームワークを使えばガラッと変わってしまうと思っているので、
フレームワークや、それに関するデータベースの使い方を次は学べばいいと思います。

おまけ

「Java言語で学ぶデザインパターン入門」という本は、サンプルプログラムが短く、実際に動かすことができますので、もう少しプログラミングで動くイメージが欲しいという人に、おすすめです。
それ以外なら、もう少し後でもいいかなと思ってます。
デザインパターンを学んで、オブジェクト指向をもっと使えるようにしたいと思った時にこの本に戻ってくるといいかもしれませんね

リンク

(アフェリエイトってやつじゃないです。)

spring bootおすすめサイト紹介

スッキリわかるJava入門

なぜ、あなたはJavaでオブジェクト指向開発ができないのか

Java言語で学ぶデザインパターン入門

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

[ev3×Java] インテリジェントブロックボタン

この記事はJavaでev3を操作してみたい人のための記事です。
今回はインテリジェントブロックボタンを使っていろいろな操作をしていきたいと思います。

目次

0 . 用意するもの
1 . Buttonクラスを使ったプログラム
2 . Keyインターフェースを使ったプログラム

0.用意するもの

◯ ev3(タンク)
◯ パソコン(VSCode)
◯ bluetooth
◯ microSD
API Documentation(これをみながら進めていくのがオススメです。)

1.Buttonクラスを使ったプログラム

1-0 . 特定のボタンを押したら~するプログラム

◯上のボタンを押したら音がなり、右のボタンを押したらループを終了するプログラムです。

button00.java
import lejos.hardware.Button;
import lejos.hardware.Sound;

public class Button00
{
    public static void main(String[] args)
    {
        while(true) {
            //もし右のボタンが押されたら
            if(Button.getButtons() == 8) {
                //ループの処理を終了する
                break;
            //もし上のボタンが押されたら
            }else if(Button.getButtons() == 1) {
                //音を鳴らす
                Sound.beep();
            }
        }
    }
}

Point : Buttonクラス

Point : ボタンの種類
ボタンは数値と紐づけられています。

・上のボタン ID_UP : 1
・下のボタン ID_DOWN : 4
・右のボタン ID_RIGHT : 8
・左のボタン ID_LEFT : 16
・中央のボタン ID_ENTER : 2
・エスケープボタン ID_ESCAPE : 32

詳細:constant-values

1-1 . どれかのボタンを押したら~するプログラム

◯どれかのボタンが押されたらモーターが回転する。ただし、押されたボタンがエスケープボタンだった場合はプログラムを終了する

button01.java
import lejos.hardware.motor.EV3LargeRegulatedMotor;
import lejos.hardware.port.MotorPort;
import lejos.robotics.RegulatedMotor;
import lejos.hardware.Button;

public class Button01
{
    public static final RegulatedMotor l_a = new EV3LargeRegulatedMotor(MotorPort.A);

    public static void main(String[] args)
    {
        while(true) {
            //もしどれかのボタンが押されたら
            Button.waitForAnyPress();
            l_a.forward();

            //もしボタンが押されて、それがエスケープボタンだったら
            if (Button.waitForAnyPress() == Button.ID_ESCAPE) {
                //プログラムを終了する
                System.exit(0);
            }
        }
    }
}

1-2 . バンプのプログラム

◯ボタンを押して離したら音を鳴らすプログラムです。

button02.java
import lejos.hardware.Button;
import lejos.hardware.Sound;

public class Button02
{
    public static void main(String[] args)
    {
            //もしどれかのボタンが押されたら
            Button.waitForAnyPress();
            //もし何かしらの動作が行われたら
            Button.waitForAnyEvent();
            Sound.beep();
    }
}

1-3 . ボタンの長押しに関するプログラム

◯右ボタンを3秒以上長押しした場合はプログラムを終了し、3秒以内に右ボタンを離した場合は音を鳴らすプログラムです。

button03.java
import lejos.hardware.Button;
import lejos.hardware.Sound;

public class Button03
{
    public static void main(String[] args)
    {
        while(true) {
            //もし右のボタンを押したなら
            if(Button.waitForAnyPress() == Button.ID_RIGHT) {
                //もし3秒以内にボタンを離したなら
                if(Button.waitForAnyEvent(3000) != 0) {
                    Sound.buzz();
                //もし3秒以上ボタンを長押ししたら
                }else {
                    System.exit(0);
                }
            }
        }
    }
}

2.Keyインターフェースを使ったプログラム

2-0 . 特定のボタンを押したら~するプログラム

◯それぞれのボタンを押した時に異なる操作をするプログラムです。

import lejos.hardware.Button;
import lejos.hardware.Sound;

public class Key00
{
    public static void main(String[] args)
    {
        while(true) {
            //上のボタンを押したら
            if(Button.UP.isDown()) {
                Sound.beep();
            //右のボタンを押したら
            }else if(Button.RIGHT.isDown()) {
                Sound.buzz();
            //エスケープボタンを押したら
            }else if(Button.ESCAPE.isDown()) {
                System.exit(0);
            }
        }
    }
}

◯Point:Keyインターフェース


2-1 . switch-case文プログラム

◯KeyListenerインターフェースの中にあるメソッドをクラスをつくって実装し、イベント時に呼び出せるようにします。

具体的には、上のボタンを押している間だけ正回転し、下のボタンを押している間だけ負回転するようにします。そしてエスケープキーを押したらプログラムを終了します。

import lejos.hardware.Button;
import lejos.hardware.Key;
import lejos.hardware.motor.EV3LargeRegulatedMotor;
import lejos.hardware.port.MotorPort;
import lejos.robotics.RegulatedMotor;
import lejos.hardware.KeyListener;

public class Key01 {

    private static RegulatedMotor l_a = new EV3LargeRegulatedMotor(MotorPort.A);
    private static RegulatedMotor l_b = new EV3LargeRegulatedMotor(MotorPort.B);

    public static void main(String[] args) {
        //イベントがおきた時にメソッドを呼び出せるようにする
        Button.UP.addKeyListener(new ButtonEvent(l_a,l_b));
        Button.DOWN.addKeyListener(new ButtonEvent(l_a,l_b));
        Button.ESCAPE.addKeyListener(new ButtonEvent(l_a,l_b));
        while (true) {}
    }
}


class ButtonEvent implements KeyListener{ //イベント処理を実装するためにクラスをつくる
    //変数を宣言する
    private RegulatedMotor lmotor_a;
    private RegulatedMotor lmotor_b;

    //コンストラクタを設定する
    public ButtonEvent(RegulatedMotor Motor_A,RegulatedMotor Motor_B) {
        lmotor_a = Motor_A;
        lmotor_b = Motor_B;
    }

    //メソッドをカスタマイズしていく
    @Override
    public void keyPressed(Key k) { //ボタンを押した時に呼び出されるメソッド
        switch (k.getId()){
        case Button.ID_UP:
             lmotor_a.forward();
             lmotor_b.forward();
             break;
        case Button.ID_DOWN:
             lmotor_a.backward();
             lmotor_b.backward();
             break;
        case Button.ID_ESCAPE:
             lmotor_a.stop(true);
             lmotor_b.stop();
             System.exit(0);
        }
    }

    //メソッドをカスタマイズしていく
    @Override
    public void keyReleased(Key k) { //ボタンを離した時に呼び出されるメソッド
        switch (k.getId()){
        case Button.ID_UP:
             lmotor_a.stop(true);
             lmotor_b.stop();
             break;
        case Button.ID_DOWN:
             lmotor_a.stop(true);
             lmotor_b.stop();
             break;
        }
    }
}

◯Point:switch-case文
参考資料:switch-case文の使い方総まとめ

最後に

読んで頂きありがとうございました!!
次回はタッチセンサについて書いていきたいと思います!

より良い記事にしていきたいので
 ◯こうした方がわかりやすい
 ◯ここがわかりにくい
 ◯ここが間違っている
 ◯ここをもっと説明して欲しい
などの御意見、御指摘のほどよろしくお願い致します。

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

【Java】 特定の数値を除いた乱数を取得する方法

9/13 更新

以前書いたコードを改善しました。改善点としては
- ListではなくSetを使用し、リトライ回数を減らしたこと
- 除外したい数値をString型ではなくInterger型にしたこと

main.java
import java.util.Random;
import java.util.HashSet;

public class Main {

    public static void main(String[] args) {
        Random rand = new Random();
        int i;
        // 除外したい数値を格納する
        Set exSet = new HashSet();
        exList.add(2);
        exList.add(5);
        exList.add(8);

        do {
            i = Interger.valueOf(rand.nextInt(10)+1); // 1~10までの乱数を生成し文字列に変換
        } while (exSet.contains(i)); //除外リストのいずれかの数値と一致なら繰り返す
        System.out.println(i);
    }
}

こちらが改善前のコード

main.java
import java.util.ArrayList;
import java.util.Random;

public class Main {

    public static void main(String[] args) {
        Random rand = new Random();
        String i;
        // 除外したい数値を格納する
        List<String> exList = new ArrayList<String>();
        exList.add("2");
        exList.add("5");
        exList.add("8");

        do {
            i = String.valueOf(rand.nextInt(10)+1); // 1~10までの乱数を生成し文字列に変換
        } while (exList.contains(i)); //除外リストのいずれかの数値と一致なら繰り返す
        System.out.println(i);
    }
}

海外でのQAサイトで見つけた特定の数値を除いた乱数を除外する方法も見つけたが
参考までに載せておきます。

public int getRandomWithExclusion(Random rnd, int start, int end, int... exclude) {
    int random = start + rnd.nextInt(end - start + 1 - exclude.length);
    for (int ex : exclude) {
        if (random < ex) {
            break;
        }
        random++;
    }
    return random;
}
main.java
int[] ex = { 2, 5, 6 };
val = getRandomWithExclusion(rnd, 1, 10, ex)
// 5,6,7の乱数を発生させた場合、7の値を取得するため、7のみ発生頻度が高くなってしまう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

続:OOPのたとえ話が(略

熱いぜ

以前にこんな記事を書いたのだが、久しぶりにOOPネタでトレンドが
炎上して 熱く燃えているではないか。そこで、あれからまた少し考え
を整理した結果として、自分の考える抽象クラスとインタフェースは
こんな感じ!という考え方を表明してみたい。あくまでも自分の考えは
こうだ、という表明である。思考はJava的で。
※コードはビルドしてないし概念提示用のためかなり適当です。

インタフェースとは

JavaのRunnableインタフェースがわかりやすいが、実装したい機能を表す
ハンドル(車のハンドルやバスケットの取手やドアノブのイメージ)だと
考えている。Runnableインタフェースで言うならば

  • スレッドとして実装したいメソッドを表現するインタフェース
  • スレッドの実装は要求ごとに異なるので好きに実装すればよい

という非常に便利な機能を提供してくれている。実装されているクラスが
何者かは知らなくても、インタフェースの機能は保証されると考えられる
ので、実装者はインタフェースを自由に実装できるし、使用者はメソッド
の機能にのみ着目して使える。

現実にこんな機能を実装することはあり得ないが、意味を表現する手段と
して車のハンドル、というインタフェースを考えてみる。

CarHandle.java
public interface CarHandle
{
    int turnRight(int angle); //実行後の角度を返す
    int turnLeft(int angle);
    void hold();
    void release(); //ハンドルから手を放し自然動作に任せる
}

CarHandleインタフェースは車のハンドル、という機能を表明している。
なので、実装先のConcreteオブジェクトがどんな車でもかまわない。
軽自動車、セダン、ワンボックスなど、オブジェクトの特性に合わせて
実装者がインタフェースの動作を実装すればよい。

また、実装先のConcreteオブジェクトが、CarHandleインタフェースを
実装したハンドルオブジェクトを受け取る、という形にするのもいい。
どんな特性のハンドルかは知らなくてもよいが、インタフェースが表明
する機能は実装されている。

CarHandleインタフェースを実装したメーカー純正ハンドルオブジェクト
とか、俺はカスタムするぜ!モモステオブジェクト!みたいな楽しいこと
ができてしまう。

では抽象クラス(abstract)って何だ?

これは一言で表現するなら、半完成品のキットパーツだと考えている。
例えばだ。私と同年代(と言えばピンときてしまう人はバラさないように)
の人は、自作PCに熱中した人もいるだろう。自分でケース、電源、マザー、
CPU、HDD、メモリモジュール、GPUをパーツ単位で買ってきて、自分好みの
性能やコストパフォーマンスのPCを組み立てて動かすのは、それだけでも
エキサイティングな遊びだった。

そして時は流れ、BTOメーカーが乱立する。Web上で注文内容を購入者の
好みにカスタマイズしたPCを完成品の形で届けてくれる。だがショップに
よってカスタムできる内容はまちまちで、理想スペックにはならないので
そのショップは選ばない、なんてこともあった。

さて(長い前置きだったが)半完成品でPCを売ってくれるBTOショップが
ついに現れた(と仮定しよう、実際にはそんなものはない)。購入者から
見て重要度の低いパーツ(ケースとかマザーとかHDDとか)はそこそこの
スペックを選択でき、重要なコアパーツ(CPU、GPU、メモリモジュール)
はダミーが乗っているPCを売ってくれるショップだ。当然キットパーツの
状態では動作しない(インスタンス化できない)。

だから購入者(実装者)がコアパーツを買ってきて(キットパーツクラス
を継承して)CPUはCore i9、GPUはGeForce RTX、メモリは8GBのDDR4を
4枚載せて32GBで、みたいなPCを作ることができる。

AbstractPC.java
public abstract class AbstractPC
{
    public int case()  //ケースはBTOで選ぶので実装済み
    {
        //ケースの処理だと思いねぇ
    }
    public int powerUnit() //電源はBTOで選ぶので実装済み
    {
        //電源の処理だと思いねぇ
    }
    public int motherBoard() //マザーはBTOで選ぶので実装済み
    {
        //マザーの処理だと思いねぇ
    }
    abstract int cpu(int model_no); //CPUは自分で買ってくる
    abstract int gpu(int model_no); //GPUは自分で買ってくる
    abstract int memory(int model_no, int nums); //RAMは自分で買ってくる
}

こいつを継承してCPU、GPU、メモリモジュールを買ってくる(実装する)
ことで、超コスパPCでもハイエンドPCでも作れてしまう。厳密には高性能
PCを作る場合は電源容量とかケースの排熱効率とか細かい話はあるのだが、
とりあえず考えないことにする。

Abstractクラスの利点・便利さという意味では、すでに固定化しており、
実装しても仕様変更の影響を受けにくいフレームワークの部分だけを実装
しておき、変化に対応したい処理は適応領域に合わせてカスタマイズした
実装をしたい、といった要求に応えられることだと思う。

正直なところ、インタフェースの便利さに比べると抽象クラスは使い所が
難しく、利用頻度は低いという印象を持っている。それでも部分実装済み
でコア部分だけを最適な実装にできる便利さには価値がある。

ところで・・・

もののついでにいろいろ検索しちゃったりしたのだが、典型的な進化論に
はまっちゃってる人とか割とゴロゴロしていて絶望を禁じ得ない。いやね、
進化論で説明したくなる気持ちはわかるよ。でもさ、犬とか猫みたいな、
何もサービスを提供してくれないオブジェクトを実装しても継承しても、
何が便利なのかさ~っぱりわからんのよ。作る意味が感じられない。

はっきり言うと「伝わらない」のだ。インタフェースだの抽象クラスだの
少々入り組んだ概念を説明するのに、プログラミングする自分が得られる
利益が何なのか伝わってこない。それではいかんと思うのだ。

だから、第三者に対してレクチャーする意図で説明するのなら、相手の立場で
「そうか、こんな便利なことができるんだ」という感動を生み出さなければ、
レクチャーとしては日本じゃあ二番目だ。感動は記憶に深く刻まれるのだ。

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