くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

AndroidStudioで開発をはじめるときにすること~パッケージ構成&共通クラス編~

いろいろやっているとAndroidアプリを開発しはじめるときにいつもやることを忘れるので、その備忘録。その2

環境
  1. Android Studio: 2.3.3
  2. Gradle: 3.3
  3. Android Plugin for Gradle: 2.3.3
  4. Java8

パッケージ構成はこんな感じ

<root_package>/
├── activity              ... Activity
│   └── MainActivity.java
├── di                    ... DI関係。DaggerのComponentとか
├── dialog                ... DialogFragment
├── entity                ... OrmaのModels
├── fragment              ... Fragment
├── repository            ... OrmaのHelper
├── util                  ... Utitly関係
├── widget                ... UI部品
└── AppApplication.java   ... Applicationクラス
一括作成bashコマンド
mkdir -p activity di dialog entity fragment repository util widget

共通クラスはこんな感じ

BaseActivity

import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v7.app.AppCompatActivity;

import butterknife.ButterKnife;

public abstract class BaseActivity extends AppCompatActivity {

    private AppComponent appComponent;

    protected static void start(@NonNull AppCompatActivity activity, @NonNull Intent intent) {
        activity.startActivity(intent);
        // activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left);
    }

    @NonNull
    protected AppComponent getComponent() {
        if (appComponent == null) {
            AppApplication appApplication = (AppApplication) getApplication();
            appComponent = appApplication.getAppComponent();
        }

        return appComponent;
    }

    @LayoutRes
    protected abstract int layoutId();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(layoutId());
        ButterKnife.bind(this);
    }
}

BaseFragment

import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import butterknife.ButterKnife;

public abstract class BaseFragment extends Fragment {

    @LayoutRes
    protected abstract int layoutId();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(layoutId(), container, false);
        ButterKnife.bind(this, view);
        return view;
    }
}

AppApplication

import lombok.Getter;
public class AppApplication extends Application {

    @Getter
    AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        // Dagger
        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
    }
}

Dagger関連

di.AppModule

@Module
public class AppModule {

    private Context context;

    public AppModule(Application app) {
        context = app;
    }

    @Provides
    public Context provideContext() {
        return context;
    }

    @Singleton
    @Provides
    public OrmaDatabase provideOrmaDatabase(Context context) {
        return OrmaDatabase.builder(context)
                .writeOnMainThread(AccessThreadConstraint.WARNING)
                .build();
    }

    @Singleton
    @Provides
    public MyRepository provideMyRepository(OrmaDatabase orma) {
        return new MyRepositoryImpl(orma);
    }
}

di.AppComponent

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {

    void inject(MainActivity activity);
}

Orma関連

MyRepository

@Singleton
public class MyRepositoryImpl implements MyRepository {

    OrmaDatabase orma;

    @Inject
    public PersonRepositoryImpl(OrmaDatabase orma) {
        this.orma = orma;
    }

    @NonNull
    @Override
    public List<PersonEntity> findAll() {
        return orma.selectFromPersonEntity().toList();
    }
}

サンプル

MainActivity

public class MainActivity extends BaseActivity {
    @Inject
    MyRepository myRepo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getComponent().inject(this);
    }
}

AndroidStudioで開発をはじめるときにすること~build.gradle編~

いろいろやっているとAndroidアプリを開発しはじめるときにいつもやることを忘れるので、その備忘録。

環境
  1. Android Studio: 2.3.3
  2. Gradle: 3.3
  3. Android Plugin for Gradle: 2.3.3
  4. Java8
使うライブラリ
  1. Gradle Retrolambda PluginAndroidでJava8のラムダ式がつかえるようになるやつ
  2. LombokJavaのボイラープレートコードをシンプルにしてくれるJavaのライブラリ
  3. Butter KnifeAndroidの View Injection ライブラリ
  4. Android-OrmaAndroid用のO/R Mapper
  5. Dagger … DIフレームワーク
  6. RxAndroidAndroidのリアクティブプログラミングライブラリ
  7. Picasso … 画像読み込みライブラリ

build.gradleを整える

Retrolambdaの導入

  • build.gradle
 buildscript {
     repositories {
         jcenter()
+        mavenCentral()
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:2.3.3'
+        classpath 'me.tatarka:gradle-retrolambda:3.6.1'
     }
 }
 
 allprojects {
     repositories {
         jcenter()
+        mavenCentral()
     }
 } 
  • app/build.gradle
 apply plugin: 'com.android.application'
+apply plugin: 'me.tatarka.retrolambda'
 
 android {
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
 }

Lombokの導入

  • app/build.gradle
 dependencies {
+    provided 'org.projectlombok:lombok:1.16.18'
 }

Butter Knifeの導入

  • app/build.gradle
 dependencies {
+    compile 'com.jakewharton:butterknife:8.7.0'
+    annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0'
 }

RxAndroidの導入

  • app/build.gradle
 dependencies {
+    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
+    compile 'io.reactivex.rxjava2:rxjava:2.1.0'
 }

Ormaの導入

  • app/build.gradle
 dependencies {
+    annotationProcessor 'com.github.gfx.android.orma:orma-processor:4.2.5'
+    compile 'com.github.gfx.android.orma:orma:4.2.5'
 }

Daggerの導入

  • app/build.gradle
 dependencies {
+    compile 'com.google.dagger:dagger:2.11'
+    annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
+    provided 'javax.annotation:jsr250-api:1.0'
 }

自分用の変更①: buildTypeにdebugを追加

  • app/build.gradle
 android {
     buildTypes {
+        debug {
+            applicationIdSuffix '.debug'
+            versionNameSuffix 'a'
+        }
         release {
             minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }

自分用の変更②: apkのファイル名を変更

  • app/build.gradle
 android {
+    android.applicationVariants.all { variant ->
+        if (variant.buildType.name == "release") {
+            variant.outputs.each { output ->
+                def newName = "${defaultConfig.applicationId}_ver${defaultConfig.versionName}_${variant.buildType.name}.apk"
+                output.outputFile = new File(output.outputFile.parent, newName)
+            }
+        }
+    }
 }

自分用の変更③: Support Libraryのバージョンを変数化

  • app/build.gradle
+def support_lib_ver = '25.3.1'
 dependencies {
-    compile 'com.android.support:appcompat-v7:25.3.1'
-    compile 'com.android.support:design:25.3.1'
+    compile "com.android.support:appcompat-v7:${support_lib_ver}"
+    compile "com.android.support:design:${support_lib_ver}"
 }

Unityでよく使うスクリプト集(C#版)【随時更新】

Unityでよく調べなおすので、個人的なチートシート

Unityでブラウザリンクを開く

string url = "http://wannabe-jellyfish.hatenablog.com/";
Application.OpenURL(url);

参考にしたサイト様

Django/PythonでCSVファイルをアップロード(.csv)したり、ダウンロード(.csv)したり、ダウンロード(.zip)したりする方法

Django/Pytonを使ってCSVファイルをあれこれするために、いろいろ調べたので、その際の備忘録。

環境はDjango1.10.1とPython2.7

CSVファイルの扱いは、基本的にimport csvのパッケージ

okadateさんのQiita記事にあるとおり、PythonでのCSVファイルの操作は以下な感じ。

import csv

# 読み込み
with open('some.csv', 'r') as f:
  reader = csv.reader(f)
  header = next(reader)  # ヘッダーを読み飛ばしたい時
  for row in reader:
    print row          # 1行づつ取得できる

# 書き込み
with open('some.csv', 'w') as f:
  writer = csv.writer(f, lineterminator='\n') # 改行コード(\n)を指定しておく
  writer.writerow(list)     # list(1次元配列)の場合
  writer.writerows(array2d) # 2次元配列も書き込める

Django上でこれらを行うときは、リクエストとレスポンスも絡んでくる。

アップロードされたCSVファイルを読み込んで処理する

アップロードされたCSVファイルを読み込む場合は、open()request.FILES['file']になる感じ。

import csv
import traceback
from django.shortcuts import render

def upload_file(request):
  try:
    csv_file = request.FILES['file']

    reader = csv.reader(req_file)
    header = next(reader)

    for csv_row in reader:
      print csv_row 

    csv_file .close()
  except:
    traceback.format_exc()

  return render(request, "index.html")

CSVファイルをダウンロードする

基本的には、csv.writerを使って、`responseに書き込んでいく感じ。
ただ、日本語の場合は、ファイル名とファイルの中身の文字コードに注意!!

  • ファイル名が日本語の場合、UTF8でURLエンコードする
  • ファイルの中身が日本語の場合、環境に合わせる。(例はWindows用としてSJISに変換)
import csv
import urllib
from django.http.response import HttpResponse

def download_csv(request):
  filename = urllib.quote((u'CSVファイル.csv').encode("utf8"))

  response = HttpResponse(content_type='text/csv')  
  response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}'.format(filename)

  writer = csv.writer(response)

  writer.writerow(map(lambda n:toSJIS(n), [u'概要', u'詳細']))
  writer.writerow(map(lambda n:toSJIS(n), [u'ああああ', u'いいい']))
  writer.writerow(map(lambda n:toSJIS(n), [u'かかかか', u'ききき']))

  return response

def toSJIS(s=""):
  u'{}'.format(s).encode("sjis")

複数のCSVファイルをzipにまとめてダウンロードする

こちらも基本はcsv.writerを使ってzip_fileに書き込み、zip_fileの内容をresponseに設定する感じ。
注意点はこちらも文字コード周り。。。

  • ZIPファイル名が日本語の場合、UTF8でURLエンコードする
  • CSVファイル名が日本語の場合、環境に合わせる。URLエンコードは不要(例はWindows用としてSJISに変換)
  • CSVファイルの中身が日本語の場合、環境に合わせる。(例はWindows用としてSJISに変換)
import csv
import urllib
import zipfile
from django.http.response import HttpResponse

def download_csv_zip(request):
  #### 書き込むzipファイルの準備
  memory_file = BytesIO()
  zip_file = zipfile.ZipFile(memory_file, 'w')

  #### CSVファイルの書き込み。1ファイル目
  csv_file1 = BytesIO()
  filename1 = (u'CSVファイル1.csv').encode("sjis")
  writer1 = csv.writer(csv_file1)

  writer1.writerow(map(lambda n:toSJIS(n), [u'概要', u'詳細']))
  writer1.writerow(map(lambda n:toSJIS(n), [u'ああああ', u'いいい']))

  ## CSVファイルの内容をzip_fileに書き込む。
  zip_file.writestr(filename1, csv_file1.getvalue())
  csv_file1.close()

  #### CSVファイルの書き込み。2ファイル目
  csv_file2 = BytesIO()
  filename2 = (u'CSVファイル2.csv').encode("sjis")
  writer2 = csv.writer(csv_file2)

  writer2.writerow(map(lambda n:toSJIS(n), [u'概要', u'詳細']))
  writer2.writerow(map(lambda n:toSJIS(n), [u'ああああ', u'いいい']))

  ## CSVファイルの内容をzip_fileに書き込む。
  zip_file.writestr(filename2, csv_file2.getvalue())
  csv_file2.close()
  

  #### zipファイルの内容をreponseに設定
  zip_file.close()
  zip_filename = urllib.quote((u'CSVファイル(複数).zip').encode("utf8"))
  response = HttpResponse(memory_file.getvalue(), content_type='application/zip')
  response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}'.format(urllib.quote(zip_file_name))

  return response

def toSJIS(s=""):
  u'{}'.format(s).encode("sjis")

以上!!

Python、さくっと書くのはいいんだけど、静的型付け言語が好きなので、結構困る。。。

参考にしたサイト様

Django x jQuery x Ajaxで非同期にファイルをアップロードしてサーバ側で処理をする

前に記事で書いた処理のファイルバージョン。

wannabe-jellyfish.hatenablog.com

見た目的にはこんな感じ。

f:id:wannabe-jellyfish:20170625085507p:plain

HTML側

<!-- フォーム部分 -->
<form id="form" class="form" action="upload_file" method="post" enctype="multipart/form-data">
  {% csrf_token %}
  <div class="input-group">
    <input type="text" class="form-control" readonly="">
    <label class="input-group-btn">
      <span class="btn btn-success">
          Choose File<input type="file" name="file" style="display:none">
      </span>
    </label>
  </div>
</form>

<!-- Ajaxで返ってきた結果を挿入する部分 -->
<div id="result-table" class="row result-table"></div>

<!-- 読み込み中のローディング画像 -->
<div id="loading-div" class="row" style="display:none">
  <img src="{% static 'svg/loading.svg' %}" class="center-block">
</div>

ローディング画像はLOADING.IOで作成
ファイルのフォームは、ynkさんのQiitaを参考に良い感じに。

javascript

//formのinput[type="file"]が変更時の処理
$(document).on('change', ':file', function() {
  // input[type="file"]のファイル名をinput[type="text"]に表示する処理
  var input = $(this);
  numFiles = input.get(0).files ? input.get(0).files.length : 1;
  label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
  input.parent().parent().prev(':text').val(label);

  //Ajaxはここから
  $.ajax({
      url: $("#form").attr("action"),
      type: 'POST',
      data: new FormData($("#form").get(0)),
      processData: false,
      contentType: false,
      beforeSend: function(xhr, settings) {
         //リクエスト送信前の処理
         // CSRFTokenを設定したり、前の結果を削除したり、ローディングを表示したり
         xhr.setRequestHeader("X-CSRFToken", $("input[name='csrfmiddlewaretoken']").val());
         $('#result-table').empty();
         $("#loading-div").show();
      },
  }).done(function(data, textStatus, jqXHR){
      // 成功したら、結果を追加する
      $tbody.append($(data));
  }).fail(function(jqXHR, textStatus, errorThrown){
      // 失敗したら、コンソールにログを吐く
      console.log(jqXHR + "\n" + textStatus + "\n" + errorThrown);
  }).always(function(data, textStatus, jqXHR){
      // 成功しても、失敗しても、レスポンスが返ってきたら、ローディングを非表示にする
      $("#loading-div").hide();
  });
});

ここの記事曰く、

ここでポイントになるのが、
processData と contentType を false にすることです。

processData は data に指定した値を文字列に変換するかどうか設定する項目です。
初期値は true となっており、このままですとURLエンコードされた値が送信されます。
ファイルの送信時は変換不要ですから、false にしておきます。

Django/Python

@require_POST
def upload_file(request):
    try:
        req_file = request.FILES['file']
        # お好みの処理をして、
        params = {
          # お好みのパラメタを用意して、
        }
        # お好みのテンプレートを使って、HTMLをレスポンスとして返す
        response = render(request, 'ajax_result.html', params);
        return HttpResponse(response)
    except:
        # 例外が発生したら、404を返す
        traceback.format_exc()
        return Http404("message")

以上!!

参考にしたサイト様

SpringBootをはじめたあとにやるはじめの一歩(Heroku使う編)

前の記事の続き。ひさびさにSpringBootでアプリを作ろうと思ったけど、だいぶ記憶が。。。
思い出してやったことの備忘録φ(..)メモメモ

wannabe-jellyfish.hatenablog.com

前の記事で以下まで終わっているイメージ。ただ、言語はKotlinになりました。

  1. Spring Initializrでひな形プロジェクトを作成
  2. giboで.gitignoreを作成

「パッケージ構成の設定」のディレクトリ構成を作るスクリプト

GUIでぽちぽちディレクトリ構成を作るのがめんどくさいので、スクリプト化。
propertiesもgistにあげて、wgetを取得するようにした。

#!/bin/bash

ROOT='.'
SRC_ROOT="${ROOT}/src/main/kotlin/com/example"
RES_ROOT="${ROOT}/src/main/resources"

### create base directories
mkdir ${SRC_ROOT}/app
mkdir ${SRC_ROOT}/domain
mkdir ${SRC_ROOT}/domain/entitiy
mkdir ${SRC_ROOT}/domain/repository
mkdir ${SRC_ROOT}/domain/service
mkdir ${SRC_ROOT}/domain/utils

### download resource via Gist

# 本番環境用の設定&共通設定
wget https://gist.github.com/memory-lovers/c5e291c4120d0e8f70b7b112e56982f4/raw/48b48bcae1cd513c9aebd01b71d7e07cff2620f3/application.properties -O ${RES_ROOT}/application.properties

# ローカル環境用のDB設定。H2を使う感じ。
wget https://gist.github.com/memory-lovers/2dc35d8b09b004f24199dc21e8b0cf85/raw/c827450e7ee77b3f04352507bf23b4c5775a9b2a/application-local.properties -O ${RES_ROOT}/application-local.properties

一部、仮当てなので以下の設定が必要。

  1. application.propertiesのログ設定をcom.exampleから自分のルートパッケージに変更
  2. .propertiesにあるDBの設定を追記

Heroku用の設定を追加

とりあえず、公開するよう場として、Herokuを使うので、それ用の設定。HerokuのDBを使ったりとか。
これもGist&スクリプト化。

#!/bin/bash

ROOT='.'
SRC_ROOT="${ROOT}/src/main/kotlin/com/example"
RES_ROOT="${ROOT}/src/main/resources"

# 本番環境用のHerokuのDB設定
wget https://gist.github.com/memory-lovers/191f79c32d50eca012df96e58f0ab746/raw/a739d16e7b1d748414f68a1c8b2c67da58b439c4/application-heroku.properties -O ${RES_ROOT}/application-heroku.properties

# 試験環境用のHerokuのDB設定
wget https://gist.github.com/memory-lovers/db31a6f7ffc3ec47eba5382aeccd0db6/raw/0de61ad65dba07eca22edf76fa448ce97c93edc3/application-heroku_dev.properties -O ${RES_ROOT}/application-heroku_dev.properties

# HerokuのDBを使うようのConfig
wget https://gist.github.com/memory-lovers/352aa0c33070c2acd842fe9aa596e1b7/raw/f46909b7f9a5e80630aa59ac764ceb1a142cd6bd/HerokuConfig -O ${SRC_ROOT}/HerokuConfig.java

# HerokuのProfile。デフォルトでは、試験環境用の設定を使う形
wget https://gist.github.com/memory-lovers/7ac33e32e8d2afc9bd13af34f8b8022b/raw/f625f7b76597fd6bdb195c43bf04fb74ae8ed6ea/Procfile -O ${ROOT}/Procfile

こっちも仮当てなので、以下の設定が必要。

  1. .propertiesにあるDBの設定を追記

以上!!

Javaのリフレクションを使ってBeanをPrettyPrintするライブラリをつくってみた(PP4j)

Javaで開発してるときに、大きめなObjectの中身を確認したいなぁーと思い、
きれいに整形してくれるプリティプリントするライブラリを探してみたけど、
なかなかいいのがなかったので、自分で作った時の備忘録

作ったライブラリはこちら。

github.com

SetとかMapとかArarryとかは対応してないけど、いずれ。。MavenCentralにも公開できてないけど、いずれ。。。

表示のされ方はこんな感じ。

こんなBeanに対して、

@Setter
@Getter
public class Sample {
    private String str;
    private List<String> strList;
    private List<Sample> children;
}

こんなインスタンスを作って、

Sample obj = new Sample();
obj.setStr("aaa");
obj.setStrList(Arrays.asList(new String[] {
    "AAA", "BBB", "CCC"
}));

Sample child1 = new Sample();
child1.setStr("child1");
child1.setChildren(Collections.emptyList());

Sample child2 = new Sample();
child2.setStr("child2");

obj.setChildren(Arrays.asList(new Sample[] {
    child1, child2
}));

こんな感じに呼び出すと、

System.out.println(PP4j.pp(obj));

こんな感じにインデントしてくれます。

Sample {
  str = aaa
  strList = [AAA, BBB, CCC]
  children = [
    Sample {
      str = child1
      strList = <null>
      children = []
    }
    Sample {
      str = child2
      strList = <null>
      children = <null>
    }
    Sample {
      str = <null>
      strList = <null>
      children = <null>
    }
  ]
}

使ったリフレクションまとめ

Class<?>系

// java.lang.Classの取得
Class<?> cls = obj.getClass();

// ClassのFQCNの取得
String fqcn = cls.getName();

// Class名の取得
String className = cls.getSimpleName();

// Classに定義されているFieldを取得
Field[] fields = cls.getDeclaredFields();

// cls.getFields()もあるが、こっちはpublicなものしかとれない

Field系

// Fieldに対してアクセス可能に設定。これでprivateフィールドの値も取れる
field.setAccessible(true);

// フィールド名を取得
String fieldName = field.getName();

// オブジェクトのインスタンスから実際のFieldのインスタンスを取得
Object field = field.get(obj);

// FieldのTypeを取得。ListなどGenericsの場合の判定に利用。
Type type = field.getType()

// FieldがList<String>だった場合に、Stringの部分を取得する
if (type == List.class) {
  // Listの場合、総称型を取得
  Type gType = field.getGenericType();

  if (gType instanceof ParameterizedType) {
    // 総称型だった場合、実際の型を取得
    Type[] pType = ((ParameterizedType) gType).getActualTypeArguments();
    // やっとここで、Stringである、pType[0]を取得できる。。。
  }
}

はまったところ。。。

クラス自体が総称型だった場合、実際の型の取得は、要素を確認しないとわからない。

なぜかクラス自体(Class<?>)は、実際の型パラメタの情報を持っていない。。。
なので、実際なんの型なのかは、要素を入る必要がある。

こんな感じだが、要素がnullだったりすると、もはやわからない。。

List<Sample> objList = new ArrayList<>();
objList.add(new Sample());


Object elm = objList.get(0);
Class cls = elm.getClass();

if (cls == String.class) {

}
Filedでも総称の型パラメタを調べるのがめんどう。。。

上記に書いているが、めんどくさい。。
Type自体の型階層がこんな感じ。

java.lang.reflect.Type                  ... インターフェース
  - java.lang.reflect.GenericArrayType  ... サブインタ―フェース
  - java.lang.reflect.ParameterizedType ... サブインタ―フェース
  - java.lang.reflect.TypeVariable<D>   ... サブインタ―フェース
  - java.lang.reflect.WildcardType      ... サブインタ―フェース
  - java.lang.Class<T>                  ... 実装されたクラス

何故作ったか。Commons-LangとかCommons-BeanUtilsとかあるけど。。。

調べてみると、Commonsライブラリにあるらしい。
すこし触ってみると、ObjectのToStringをオーバライドしないといけなく、めんどくさい。。

表示も1行にまとまっていたり、中途半端に改行されたりとなんだかなぁと。。
もう好きに表示できるように作ればいいのかと思いたつ。

Apache Commons Langでの設定

全部のBeanのToStringをオーバライドする
 public String toString() {
   return ToStringBuilder.reflectionToString(this);
 }
表示はこんな感じで1行になる。。。
sample.Sample@7ab2bfe1[str=aaa,strList=[AAA, BBB, CCC],children=[sample.Sample@6438a396[str=child1,strList=<null>,children=[]], sample.Sample@e2144e4[str=child2,strList=<null>,children=<null>]]]

Apache Commons BeanUtilsでの設定

こっちも全部のBeanのToStringを..(ry
public String toString() {
  return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
}
改行はしてくれたけど、インデント。。。
sample.Sample@7ab2bfe1[
  str=aaa
  strList=[AAA, BBB, CCC]
  children=[sample.Sample@6438a396[
  str=child1
  strList=<null>
  children=[]
], sample.Sample@e2144e4[
  str=child2
  strList=<null>
  children=<null>
]]
]

参考にしたサイト様