Javaの合成メソッド

このブログ投稿では、Java合成メソッドの概念について説明します。この投稿では、Java合成メソッドとは何か、Java合成メソッドを作成および識別する方法、およびJava開発におけるJava合成メソッドの影響について要約しています。

Java言語仕様(セクション13.1)には、「デフォルトのコンストラクターとクラス初期化メソッドを除いて、ソースコードに対応するコンストラクトがないコンパイラーによって導入されたコンストラクトは合成としてマークする必要があります」と記載されています。Javaでの合成の意味に関するさらなる手がかりは、Member.isSynthetic()のJavadocドキュメントにあります。そのメソッドのドキュメントには、「このメンバーがコンパイラによって導入された場合にのみtrue」が返されると記載されています。私は「合成」の非常に短い定義が好きです。コンパイラによって導入されたJava構造です。

Javaコンパイラは、プライベート修飾子で指定された属性がそれを囲むクラスによってアクセスされるときに、ネストされたクラスに合成メソッドを作成する必要があります。次のコードサンプルは、この状況を示しています。

DemonstrateSyntheticMethods.java(クラスを囲むと1つのネストされたクラスのプライベート属性が呼び出されます)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

上記のコードは問題なくコンパイルされます。コンパイルされた.classファイルに対してjavapを実行すると、出力は次の画面スナップショットのようになります。

上記の画面スナップショットが示すように、名前の合成メソッドがaccess$100ネストされたクラスNestedClassに作成され、そのプライベート文字列を囲んでいるクラスに提供します。合成メソッドは、囲んでいるクラスがアクセスするNestedClassの単一のプライベート属性に対してのみ追加されることに注意してください。NestedClassのすべてのプライベート属性にアクセスするように囲んでいるクラスを変更すると、追加の合成メソッドが生成されます。次のコード例は、これを実行する方法を示し、それに続く画面スナップショットは、その場合に4つの合成メソッドが生成されることを証明します。

DemonstrateSyntheticMethods.java(クラスを囲むと4つのネストされたクラスのプライベート属性が呼び出されます)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

上記の前の2つのコードスニペットと関連する画像が示すように、Javaコンパイラは必要に応じて合成メソッドを導入します。ネストされたクラスのプライベート属性の1つだけが、それを囲むクラスによってアクセスさaccess$100れた場合、コンパイラによって1つの合成メソッド()のみが作成されました。ネストされたクラスのすべての4つのプライベート属性を囲むクラスによってアクセスされたときしかし、4つの対応する合成方法は、(コンパイラによって生成されたaccess$100access$200access$300、およびaccess$400)。

ネストされたクラスのプライベートデータにアクセスする包含クラスのすべての場合において、そのアクセスが発生することを可能にする合成メソッドが作成されました。ネストされたクラスが、それを囲むクラスが使用できるプライベートデータのアクセサーを提供するとどうなりますか?これは、次の画面のスナップショットに示すように、次のコードリストとその出力に示されています。

プライベートデータ用のネストされたクラスのパブリックアクセサーを備えたDemonstrateSyntheticMethods.java

package dustin.examples; import java.util.Calendar; import java.util.Date; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); out.println("Date: " + nested.getDate()); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; private Date date = new Date(); public Date getDate() { return this.date; } } } 

上記の画面スナップショットは、ネストされたクラスのプライベートDate属性にアクセスするための合成メソッドをコンパイラーが生成する必要がなかったことを示しています。これは、囲んでいるクラスが提供されたgetDate()メソッドを介してその属性にアクセスしたためです。getDate()提供されている場合でも、コンパイラは、アクセサメソッドを介してではなくdatedate属性に直接(プロパティとして)アクセスするように記述された、にアクセスするための合成メソッドを生成します。

最後の画面のスナップショットは、別の観察結果をもたらします。新しく追加されたgetDate()メソッドがその画面のスナップショットに示されているように、などの修飾子publicはjavap出力に含まれています。コンパイラによって作成された合成メソッドの修飾子は表示されないため、それらがパッケージレベル(またはpackage-private)であることがわかります。つまり、コンパイラは、プライベート属性にアクセスするためのパッケージプライベートメソッドを作成しました。

JavaリフレクションAPIは、合成メソッドを決定するための別のアプローチを提供します。次のコードリストは、JavaリフレクションAPIを使用して、上記のネストされたクラスのメソッドに関する詳細を便利に提供するGroovyスクリプト用です。

ReflectOnMethods.groovy

#!/usr/bin/env groovy import java.lang.reflect.Method import java.lang.reflect.Modifier if (args == null || args.size() < 2) { println "Outer and nested class names must be provided." println "\nUsage #1: reflectOnMethods qualifiedOuterClassName nestedClassName\n" println "\nUsage #2: groovy -cp classpath reflectOnMethods.groovy qualifiedOuterClassName nestedClassName\n" println "\t1. Include outer and nested classes on classpath if necessary" println "\t2. Do NOT include \$ on front of nested class name.\n" System.exit(-1) } def enclosingClassName = args[0] def nestedClassName = args[1] def fullNestedClassName = enclosingClassName + '$' + nestedClassName def enclosingClass = Class.forName(enclosingClassName) Class nestedClass = null enclosingClass.declaredClasses.each { if (!nestedClass && fullNestedClassName.equals(it.name)) { nestedClass = it } } if (nestedClass == null) { println "Unable to find nested class ${fullNestedClassName}" System.exit(-2) } // Use declaredMethods because don't care about inherited methods nestedClass.declaredMethods.each { print "\nMethod '${it.name}' " print "is ${getScopeModifier(it)} scope, " print "${it.synthetic ? 'is synthetic' : 'is NOT synthetic'}, and " println "${it.bridge ? 'is bridge' : 'is NOT bridge'}." } def String getScopeModifier(Method method) { def modifiers = method.modifiers def isPrivate = Modifier.isPrivate(modifiers) def isPublic = Modifier.isPublic(modifiers) def isProtected = Modifier.isProtected(modifiers) String scopeString = "package-private" // default if (isPublic) { scopeString = "public" } else if (isProtected) { scopeString = "protected" } else if (isPrivate) { scopeString = "private" } return scopeString } 

上記のGroovyスクリプトを上記のクラスおよびネストされたクラスに対して実行すると、出力は次の画面のスナップショットに示されているものになります。

前の画像に示されているGroovyスクリプトの結果は、javapがすでに教えてくれたことを確認していますNestedClass。ネストされたクラスには4つの合成メソッドと1つの非合成メソッドが定義されています。このスクリプトは、コンパイラーが生成した合成メソッドがパッケージプライベートスコープであることも示しています。

パッケージプライベートスコープレベルでネストされたクラスに合成メソッドを追加することは、コンパイラが上記の例で行ったことだけではありません。また、ネストされたクラス自体のスコープを、コードのプライベート設定から.classファイルのpackage-privateに変更しました。実際、合成メソッドは、囲んでいるクラスがプライベート属性にアクセスした場合にのみ追加されましたが、コードでプライベートとして指定されている場合でも、コンパイラは常にネストされたクラスをパッケージプライベートにします。幸いなことに、これはコンパイルプロセスの結果として生じるアーティファクトです。つまり、ネストされたクラスまたはその合成メソッドの変更されたスコープレベルに対して、コードをそのままコンパイルすることはできません。ランタイムは物事が危険にさらされる可能性がある場所です。

クラスRogueは、いくつかのNestedClass合成メソッドにアクセスしようとします。次にそのソースコードを示し、続いてこの不正なソースコードをコンパイルしようとしたときに見られるコンパイラエラーを示します。

コンパイル時に合成メソッドにアクセスしようとしているRogue.java

package dustin.examples; import static java.lang.System.out; public class Rogue { public static void main(final String[] arguments) { out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); } } 

上記のコードは、非合成メソッドgetDate()でもコンパイルされず、次のエラーが報告されます。

Buildfile: C:\java\examples\synthetic\build.xml -init: compile: [javac] Compiling 1 source file to C:\java\examples\synthetic\classes [javac] C:\java\examples\synthetic\src\dustin\examples\Rogue.java:9: dustin.examples.DemonstrateSyntheticMethods.NestedClass has private access in dustin.examples.DemonstrateSyntheticMethods [javac] out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); [javac] ^ [javac] 1 error BUILD FAILED C:\java\examples\synthetic\build.xml:29: Compile failed; see the compiler error output for details. Total time: 1 second 

上記のコンパイルエラーメッセージが示すように、ネストされたクラスにはプライベートスコープがあるため、ネストされたクラスの非合成メソッドでさえコンパイル時にアクセスできません。 Charlie Laiは、彼の記事「Java Insecurities:Accounting for Subtleties That Can Compromise Code」で、これらのコンパイラーによって導入された変更がセキュリティの脆弱性である可能性のある状況について説明しています。 Faisal Ferozはさらに進んで、「セキュアJavaコードの書き方」の投稿で、「内部クラスを使用しないでください」と述べています(ネストされたクラスのサブセットとしての内部クラスの詳細については、ネストされたクラス、内部クラス、メンバークラス、およびトップレベルクラスを参照してください)。 。

私たちの多くは、合成メソッドを十分に理解していなくても、Java開発に長い間取り組むことができます。ただし、これらの認識が重要な場合があります。これらに関連するセキュリティの問題に加えて、スタックトレースを読み取るときにそれらが何であるかを認識することも必要です。などのメソッド名access$100access$200access$300access$400access$500access$600、およびaccess$1000スタックトレースには、コンパイラによって生成された合成法を反映しています。

元の投稿は//marxsoftware.blogspot.com/で入手できます

このストーリー「Javaの合成メソッド」は、もともとJavaWorldによって公開されました。