Shiraji's Blog

アクセス修飾子次第で、メソッド数が増えてしまう問題について

まとめ

privateアクセス修飾子をつけると条件次第で勝手にメソッド余分に生成されるから気をつけてね!

最初に

Jakeさんがこのpull requestを投げていて、察するに$accessorというメソッドが生成されてしまうので、それを生成しないようにしたぜ?ってことだと思う。が、なんでそうなるのかわからない。 他にも、コンストラクタの修飾子を変更しているんだけど、これもなんのためなのかわからない。 そこで、コードを書いてみて、実際にやってみました。

このpull requestでは以下の4つを修正しています。

コンストラクタのprivateの削除

この変更がわかりやすいようにサンプルコードを作成してみました。

1
2
3
4
5
6
7
class PrivateConstructor {
  private abstract class AbstractInnerClass {
    private AbstractInnerClass() {}
  }
  class InnerClass extends AbstractInnerClass {
  }
}
1
2
3
4
5
6
7
class DefaultConstructor {
  private abstract class AbstractInnerClass {
    AbstractInnerClass() {}
  }
  class InnerClass extends AbstractInnerClass {
  }
}

違いは、PrivateConstructor内にある、AbstractInnerClassのコンストラクタにprivateがあるかどうかです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% javap -p PrivateConstructor*.class
Compiled from "PrivateConstructor.java"
class PrivateConstructor$1 {
}
Compiled from "PrivateConstructor.java"
abstract class PrivateConstructor$AbstractInnerClass {
  final PrivateConstructor this$0;
  private PrivateConstructor$AbstractInnerClass(PrivateConstructor);
  PrivateConstructor$AbstractInnerClass(PrivateConstructor, PrivateConstructor$1);
}
Compiled from "PrivateConstructor.java"
class PrivateConstructor$InnerClass extends PrivateConstructor$AbstractInnerClass {
  final PrivateConstructor this$0;
  PrivateConstructor$InnerClass(PrivateConstructor);
}
Compiled from "PrivateConstructor.java"
class PrivateConstructor {
  PrivateConstructor();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% javap -p DefaultConstructor*.class
Compiled from "DefaultConstuctor.java"
abstract class DefaultConstructor$AbstractInnerClass {
  final DefaultConstructor this$0;
  DefaultConstructor$AbstractInnerClass(DefaultConstructor);
}
Compiled from "DefaultConstuctor.java"
class DefaultConstructor$InnerClass extends DefaultConstructor$AbstractInnerClass {
  final DefaultConstructor this$0;
  DefaultConstructor$InnerClass(DefaultConstructor);
}
Compiled from "DefaultConstuctor.java"
class DefaultConstructor {
  DefaultConstructor();
}

Javaファイル内では違いはprivateだけでしたが、classファイル内では、以下のメソッドが出来ています。

1
PrivateConstructor$AbstractInnerClass(PrivateConstructor, PrivateConstructor$1);

これが生成されることで、メソッド数がDefaultConstuctor.javaでは3つなのに、PrivateConstructor.javaでは4つになってしまいます。 なぜこうなるのかというと、PrivateConstructor$InnerClassが継承するために必要なコンストラクタを定義する必要があるためです。

実際こんなクラスを定義してみるとわかります。

1
2
3
4
5
6
7
class PrivateConstructor {
  private abstract class AbstractInnerClass {
    private AbstractInnerClass() {}
  }
  // class InnerClass extends AbstractInnerClass {
  // }
}
1
2
3
4
5
6
7
8
9
10
% javap -p PrivateConstructor*.class
Compiled from "PrivateConstructor.java"
abstract class PrivateConstructor$AbstractInnerClass {
  final PrivateConstructor this$0;
  private PrivateConstructor$AbstractInnerClass(PrivateConstructor);
}
Compiled from "PrivateConstructor.java"
class PrivateConstructor {
  PrivateConstructor();
}

PrivateConstructor$AbstractInnerClassだけしか定義されておらず、privateのコンストラクタにアクセスする必要がないため、PrivateConstructor$AbstractInnerClass(PrivateConstructor, PrivateConstructor$1);は生成されません。

デフォルトコンストラクタの定義

今回の修正で一番おもしろいのがこれです。

1
2
3
4
5
6
class NoDefaultConstructor {
  private abstract class AbstractClass {
  }
  class InnerClass extends AbstractClass {
  }
}

これをコンパイルすると、以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Compiled from "NoDefaultConstructor.java"
class NoDefaultConstructor$1 {
}
Compiled from "NoDefaultConstructor.java"
abstract class NoDefaultConstructor$AbstractClass {
  final NoDefaultConstructor this$0;
  private NoDefaultConstructor$AbstractClass(NoDefaultConstructor);
  NoDefaultConstructor$AbstractClass(NoDefaultConstructor, NoDefaultConstructor$1);
}
Compiled from "NoDefaultConstructor.java"
class NoDefaultConstructor$InnerClass extends NoDefaultConstructor$AbstractClass {
  final NoDefaultConstructor this$0;
  NoDefaultConstructor$InnerClass(NoDefaultConstructor);
}
Compiled from "NoDefaultConstructor.java"
class NoDefaultConstructor {
  NoDefaultConstructor();
}

コンストラクタが生成されています。

1
2
private NoDefaultConstructor$AbstractClass(NoDefaultConstructor);
NoDefaultConstructor$AbstractClass(NoDefaultConstructor, NoDefaultConstructor$1);

Javaではコンストラクタを定義していない場合、デフォルトコンストラクタが生成されます。 しかし、そのデフォルトコンストラクタはclassのアクセス修飾子と同じものが定義されます。

AbstractClassはprivateで定義されているため、privateのデフォルトコンストラクタが定義されます。それがこれ

1
private NoDefaultConstructor$AbstractClass(NoDefaultConstructor);

それで、このコンストラクタにアクセスするためのコンストラクタを定義する必要があるので、

1
NoDefaultConstructor$AbstractClass(NoDefaultConstructor, NoDefaultConstructor$1);

これが生成されます。

メソッドを0で定義していますが、実は4つのメソッドが出来ていました。 少なくともprivateのデフォルトコンストラクタはいらないので、空のデフォルトコンストラクタの変更を入れているわけです。

メンバー変数のprivateの削除

この変更も面白いです。

サンプルコードはこんな感じです。

1
2
3
4
5
6
7
8
9
class DefaultMember {
  boolean flag;

  class InnerClass {
    void foo() {
      flag = true;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
Compiled from "DefaultMember.java"
class DefaultMember$InnerClass {
  final DefaultMember this$0;
  DefaultMember$InnerClass(DefaultMember);
  void foo();
}
Compiled from "DefaultMember.java"
class DefaultMember {
  boolean flag;
  DefaultMember();
}
1
2
3
4
5
6
7
8
9
class PrivateMember {
  private boolean flag;

  class InnerClass {
    void foo() {
      flag = true;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
Compiled from "PrivateMember.java"
class PrivateMember$InnerClass {
  final PrivateMember this$0;
  PrivateMember$InnerClass(PrivateMember);
  void foo();
}
Compiled from "PrivateMember.java"
class PrivateMember {
  private boolean flag;
  PrivateMember();
  static boolean access$002(PrivateMember, boolean);
}

こんなのが出来ています。

1
static boolean access$002(PrivateMember, boolean);

これは、Innerクラスからflagにアクセスするためのメソッドです。 privateアクセス修飾子のため、アクセス出来ないので、accessというprefix付きのメソッドが生成されflagにアクセス出来るようにしています。

メソッドのprivateの削除

パターンパターン!ですけどメソッドのアクセス修飾子も同じです。

サンプルコードはこれ。

1
2
3
4
5
6
7
8
9
10
11
12
class PrivateMethod {
  static {
    Object obj = new Object() {
      void bar() {
        PrivateMethod method = new PrivateMethod();
        method.foo();
      }
    };
  }

  private void foo() {}
}
1
2
3
4
5
6
7
Compiled from "PrivateMethod.java"
class PrivateMethod {
  PrivateMethod();
  private void foo();
  static void access$000(PrivateMethod);
  static {};
}

これでprivateを外すとこうなります。

1
2
3
4
5
6
7
8
9
10
11
12
class PrivateMethod {
  static {
    Object obj = new Object() {
      void bar() {
        PrivateMethod method = new PrivateMethod();
        method.foo();
      }
    };
  }

  void foo() {}
}
1
2
3
4
5
6
Compiled from "PrivateMethod.java"
class PrivateMethod {
  PrivateMethod();
  void foo();
  static {};
}

static void access$000(PrivateMethod);が減りました。 これもアクセスするために生成されたメソッドです。

終わりに

Javaのアプリケーションではそこまで影響度はないですが、Androidはメソッド65K問題があるので、メソッド数には注意が必要です。特に利用しているライブラリではメソッド数を減らそうという狙いがあったのだと思います。

Jakeさんは他にもこの自動生成されるメソッドの検出方法をコメントしています。

Androiderは盲目的にprivateのアクセス修飾子にすればいいという考えは辞めたほうがいいようです。

ちなみにJakeさんは一つどうしようもない例も出してくれてます。

これはメソッドを減らすことがどうやっても出来ないそうです。