Javaに限らないと思うけど、再帰処理は途中で抜けられない感じですかね

f:id:ts0818:20190507213316j:plain

「真実からは嘘を 嘘からは真実を 夢中で探してきたけど    今 僕のいる場所が 探してたのと違っても 間違いじゃない きっと答えは一つじゃない~(Mr.Children:『Any』)」

答えは1つじゃないかもしれないけれど、答えは欲しい、はい、どうもボクです。

再帰処理について、途中で抜けられるんだと思っていった情弱な私です。というわけで、Javaのお話。

 

再帰処理って?

『サイコボール!』 を使うサイキッカーとは何の関係もない、ということで、まずは、Wikipediaさんにお尋ねしてみた。

再帰(さいき)は、あるものについて記述する際に、記述しているものそれ自身への参照が、その記述中にあらわれることをいう。定義において、再帰があらわれているものを再帰的定義という。自己相似の記事も参照のこと。

再帰 - Wikipedia

主に英語のrecursionとその派生語の訳にあてられる。他にrecurrenceの訳(回帰#物理学及び再帰性を参照のこと)や、reflexiveの訳として「再帰」が使われることがある。数学的帰納法との原理的な共通性から、recursionの訳として数学では「帰納」を使うことがある。

再帰 - Wikipedia

んで、肝心のプログラミング的には、以下の説明が。 

再帰呼出し(さいきよびだし、recursive call)は、プログラミング技法の一つである。

手続きや関数といった概念をもつプログラミング言語では、ある手続き中で再びその手続き自身を呼び出すことを認める場合が多い。これを再帰呼出しといい、階乗計算やフィボナッチ数列のように、本来再帰的な構造をもつアルゴリズム再帰アルゴリズム)を記述するのに適している。

再帰 - Wikipedia

⇧  プログラミングでいうと、あるメソッドAを実行した時に、そのメソッドAの処理の中で、メソッドAを実行するような感じのコーディングですかね。 

 

qiita.com

⇧  上記サイト様が詳しいです。

 

再帰とは再び帰ってくるということと見つけたり...

まぁ、名前の通りで、再帰の処理が進んでいき、途中で終了処理の条件になった場合、いままで行われた処理が実際に行われると、いや、処理は行われてたんだけど、途中で遷移して止まっちゃってた部分が処理されるってことっすかね。 

自分もよく分かってないんだけども、図にすると分かりやすい感じですかね。

qiita.com

⇧  上記サイト様の図が分かりやすいので、流用させていただきましょう。

f:id:ts0818:20190413131457p:plain

 

⇧  全部、同じメソッドを読んでるんだけども(引数の値は異なる)、メソッドの処理の途中で再帰のため遷移するので、戻り値が帰ってくるのは、再帰の条件にあわなくなった「power(0)」の実行部分で、初めてメソッドのすべての処理が完結するので、戻り値の1が「power(1)」に渡され、「power(1)」も遷移して処理ができていなかった部分(未処理な部分)が実行されて戻り値の1を「power(2)」に渡し、それを受け取った「power(2)」が遷移して処理ができていなかった残りの部分(未処理な部分)を実行し、戻り値の2を「power(3)」に渡し....という同様の処理が「power(5)」までなされると。

つまり、何が言いたいかと言うと、「power(0)」で、何か分かったから処理終了させたいな~、と思ったとしても、再帰のために遷移してしまっていて未処理な部分が残っている状態なので、その未処理な部分が強制的に実施されるまで終わらないという悪夢

 

この原理をよく分かってなかったので、むちゃくちゃ泥沼にハマってしまった...。

再帰って名前で気づけよ!ってツッコミはなしでお願いします(涙目)。

 

というわけで、泥沼にハマった結果

今回、リフレクションの中で再帰処理を実施して、ドツボにはまりましたんで、備忘録として。

Eclipseを起動し、Javaのクラスを作成で。

f:id:ts0818:20190413133742p:plain

以下のクラスを今回は使用しています。

Nerima.java

package dto;

public class Nerima {

  private long pepole = 7350000;

  private String feature = "anime";

  public long getPepole() {
    return pepole;
  }

  public void setPepole(long pepole) {
    this.pepole = pepole;
  }

  public String getFeature() {
    return feature;
  }

  public void setFeature(String feature) {
    this.feature = feature;
  }
}

Tokyo.java

package dto;

public class Tokyo {

  private long people = 13857443;

  private Nerima nerima;

  public long getPeople() {
    return people;
  }

  public void setPeople(long people) {
    this.people = people;
  }

  public Nerima getNerima() {
    return nerima;
  }

  public void setNerima(Nerima nerima) {
    this.nerima = nerima;
  }
}

KantoArea.java

package dto;

public class KantoArea {

  private String name = "関東エリア";

  private Tokyo tokyo;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Tokyo getTokyo() {
    return tokyo;
  }

  public void setTokyo(Tokyo tokyo) {
    this.tokyo = tokyo;
  }
}

TestReflection.java

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import dto.KantoArea;
import dto.Nerima;
import dto.Tokyo;

public class TestReflection {
  
  private static Logger logger = Logger.getLogger(TestReflection.class.getName());

  public static void main(String[] args) {
    
    // 
    Nerima nerima = new Nerima();
    nerima.setFeature(null);
    Tokyo tokyo = new Tokyo();
    tokyo.setNerima(nerima);
    KantoArea kantoArea = new KantoArea();
    kantoArea.setTokyo(tokyo);
    
    Map<Integer,Boolean> checkList = new HashMap<>();
    int counter = 0;
    checkList = checkNull(KantoArea.class, kantoArea, checkList, counter);
    
    if(checkList.isEmpty()) {
      System.out.println("必須チェックOK");
    } else {
      for(Map.Entry<Integer, Boolean> map: checkList.entrySet()) {
        if(map.getValue()) {
          System.out.println("NULLな項目が存在します。");
        }         
      }
    }
  }

  private static Map<Integer, Boolean> checkNull(Class<?> clazz, Object object, Map<Integer, Boolean> checkList, int counter) {

    boolean flg = false;
    while (clazz != null) {

      try {

        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
          if(flg){
            //System.out.println(flg);
            break;
          }
          field.setAccessible(true);
          Method[] methods = clazz.getDeclaredMethods();

          for (Method method : methods) {
            
            // getterを含むメソッドの絞り込み(getBytesメソッドとかも取れちゃうけど...)
            if (method.getName().contains("get")) {
              Object obj = method.invoke(object);
              if (obj == null) {
                flg = true;
                checkList.put(counter, flg);
                logger.info(object.getClass().getName()+ " クラスの " + method.getName() + "() メソッドの戻り値がNULLです。");
                
                // getterで取得された値のクラスが、自作クラスの場合
              } else if ((obj.getClass().getName()).toLowerCase()
                  .contains(field.getName().toLowerCase())) {                
                // 再帰処理
                checkNull(obj.getClass(), obj, checkList, counter+1);
              }
            // getterで値が取得できないということは、nullな項目があったということ
            }
            if (flg) {
              System.out.println(counter + "回目の再帰処理でNULLな項目が見つかりました。");
              break;
            }
          }
        }

      } catch (Exception e) {
        throw new IllegalStateException(e);
      } finally {
        clazz = null;
      }
      
    }
    return checkList; // 処理が最後まで行われたら、false というかMapの中身は空
  }
}

そしたらば、実行。

f:id:ts0818:20190413133839p:plain

f:id:ts0818:20190413133919p:plain

期待した結果が戻ってきました。

試しに、NULLを設定してた部分をコメントアウトして、NULLな項目を無くしてみます。

TestReflection.java

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import dto.KantoArea;
import dto.Nerima;
import dto.Tokyo;

public class TestReflection {
  
  private static Logger logger = Logger.getLogger(TestReflection.class.getName());

  public static void main(String[] args) {
    
    // 
    Nerima nerima = new Nerima();
    //nerima.setFeature(null);
    Tokyo tokyo = new Tokyo();
    tokyo.setNerima(nerima);
    KantoArea kantoArea = new KantoArea();
    kantoArea.setTokyo(tokyo);
    
    Map<Integer,Boolean> checkList = new HashMap<>();
    int counter = 0;
    checkList = checkNull(KantoArea.class, kantoArea, checkList, counter);
    
    if(checkList.isEmpty()) {
      System.out.println("必須チェックOK");
    } else {
      for(Map.Entry<Integer, Boolean> map: checkList.entrySet()) {
        if(map.getValue()) {
          System.out.println("NULLな項目が存在します。");
        }         
      }
    }
  }

  private static Map<Integer, Boolean> checkNull(Class<?> clazz, Object object, Map<Integer, Boolean> checkList, int counter) {

    boolean flg = false;
    while (clazz != null) {

      try {

        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
          if(flg){
            //System.out.println(flg);
            break;
          }
          field.setAccessible(true);
          Method[] methods = clazz.getDeclaredMethods();

          for (Method method : methods) {
            
            // getterを含むメソッドの絞り込み(getBytesメソッドとかも取れちゃうけど...)
            if (method.getName().contains("get")) {
              Object obj = method.invoke(object);
              if (obj == null) {
                flg = true;
                checkList.put(counter, flg);
                logger.info(object.getClass().getName()+ " クラスの " + method.getName() + "() メソッドの戻り値がNULLです。");
                
                // getterで取得された値のクラスが、自作クラスの場合
              } else if ((obj.getClass().getName()).toLowerCase()
                  .contains(field.getName().toLowerCase())) {                
                // 再帰処理
                checkNull(obj.getClass(), obj, checkList, counter+1);
              }
            // getterで値が取得できないということは、nullな項目があったということ
            }
            if (flg) {
              System.out.println(counter + "回目の再帰処理でNULLな項目が見つかりました。");
              break;
            }
          }
        }

      } catch (Exception e) {
        throw new IllegalStateException(e);
      } finally {
        clazz = null;
      }
      
    }
    return checkList; // 処理が最後まで行われたら、false というかMapの中身は空
  }
}

f:id:ts0818:20190413134228p:plain

NULLな項目は無いので、OKそうです。

全部の項目が必須っていう前提になってしまいますが...まぁ、ログでどのgetterの戻り値がNULLになってるか分かるから、その項目が必須かどうか調べればOKということで...うまい方法が思いつかんです...。

う~ん、リフレクションと再帰処理の絡みが宜しくないのか...まだまだプログラミングってものがよく分からんですね。

今回はこのへんで。