Home > other >  getDeclaredMethods() return inherited methods if superclass is default
getDeclaredMethods() return inherited methods if superclass is default

Time:05-24

I have two classes

// BaseClass.java
class BaseClass<T> {
 
   public String getTest(){
       return "one";
   }
 
   public String getTest2(T t){
       return "two";
   }
   public String getTest3(T t){
       return "three";
   }
}
 
// OverrideClass.java
public class OverrideClass extends BaseClass<Test>{
}
 

I tried to run the following code

// Test.java
public class Test {
   public static void main(String[] args) {
       Class<OverrideClass> overrideClass = OverrideClass.class;
       Method[] declaredMethods = overrideClass.getDeclaredMethods();
       System.out.println(Arrays.toString(declaredMethods));
   }
}

and I think it should output

[]

but in fact the output is

[public java.lang.String OverrideClass.getTest()]

Through the bytecode, I thought this a bridge method, but I don't know why it generates, and if I make BaseClass public it will disappear.

  // access flags 0x1041
  public synthetic bridge getTest()Ljava/lang/String;
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKESPECIAL BaseClass.getTest ()Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this LOverrideClass; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}

My question is:

  1. Why getTest() generate a bridge method if BaseClass is default?
  2. Why getTest2() and getTest3() did not generate their bridge method? this seems to be related to generics.

Thanks for any help!

CodePudding user response:

I have analyzed the issue and here is the results. I have simplified a bit the example from the question.

This answer handles the first question of OP

Why getTest() generate a bridge method if BaseClass is default?

For second question regarding the inconsistency appearing with generics you can read Denis answer

Example 1

class BaseClass {

    public String getTest(){
        return "one";
    }

    public String getTest2(){
        return "two";
    }
    public String getTest3(){
        return "three";
    }
}


public class OverrideClass extends BaseClass{}


public class Application {

public static void main(String[] args) throws Exception {
    Class<OverrideClass> overrideClass1 = OverrideClass.class;
    Method[] declaredMethods1 = overrideClass1.getDeclaredMethods();
    System.out.println(Arrays.toString(declaredMethods1));
   }
}

The execution of this either with JDK 8 or with JDK 17 has always the same result

[public java.lang.String OverrideClass.getTest(), public java.lang.String OverrideClass.getTest2(), public java.lang.String OverrideClass.getTest3()]

Example 2

Just modify the above example into

public class BaseClass {

    public String getTest(){
        return "one";
    }

    public String getTest2(){
        return "two";
    }
    public String getTest3(){
        return "three";
    }
}

Note that the change is in the access modifier on Base class which now is public!

The execution of this produces the expected behavior of []

This however is not a bug of JDK. It is intended to be this way.

Explanation

The reason that in the example1 the getDeclaredMethods() has returned the same methods of the parent class is not because those methods are printed as inherited. It is because those are bridge methods that actually belong to that child class (OverrideClass).

This functionality has been added long ago and the explanation as you can see here from developers of oracle was

The proposal is to add bridge methods in these very rare cases to fix a problem in reflection with no other forseen fix or workaround. Specifically, we would generate a bridge method when a public method is inherited from a nonpublic class into a public class.

And as you can also see here, the most recent comment from oracle developers was

The bridge methods are added in a case like this where a public class public methods from a non-public superclass to allow for the possibility of reflective access of the subclasses methods JDK-6342411).

Closing this issue as not a bug.

So this is happening only in non public parent classes because in this case the public methods that are inherited need to be added as bridge methods in that children class.

In the example 2 where bridge methods do not exist, if you try to print out the disassembled code using javap -c OverrideClass you will see the following

public class OverrideClass extends BaseClass {
      public OverrideClass();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method BaseClass."<init>":()V
           4: return
    }

In the example 1 with bridge methods existing, if you try to print out the disassembled code using javap -c OverrideClass you will see the following

public class OverrideClass extends BaseClass {
  public OverrideClass();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method BaseClass."<init>":()V
       4: return

  public java.lang.String getTest3();
    Code:
       0: aload_0
       1: invokespecial #7                  // Method BaseClass.getTest3:()Ljava/lang/String;
       4: areturn

  public java.lang.String getTest2();
    Code:
       0: aload_0
       1: invokespecial #11                 // Method BaseClass.getTest2:()Ljava/lang/String;
       4: areturn

  public java.lang.String getTest();
    Code:
       0: aload_0
       1: invokespecial #14                 // Method BaseClass.getTest:()Ljava/lang/String;
       4: areturn
}

CodePudding user response:

  1. Why getTest() generate a bridge method if BaseClass is default?

    See Panagiotis Bougioukos' answer.

  2. Why getTest2() and getTest3() did not generate their bridge method? this seems to be related to generics.

    This is a bug, from description of JDK-8216196. No visibility bridges are created for generic methods:

    javac is normally adding visibility bridges for methods if they are public but declared by a package-private class:

    This is not the case if a method has a generic type:

The bug is fixed by JDK-8203488. Remove error generation from TransTypes, backported to 11.0.1

Example, run results

package org.example;

class BaseClass<T> {

    public String getTest() {
        return "one";
    }

    public String getTest2(T t) {
        return "two";
    }

    public String getTest3(T t) {
        return "three";
    }
}
package org.example;

public class OverrideClass extends BaseClass<Void> {
}
package org.example.other; // Note other package

import org.example.OverrideClass;

import java.lang.reflect.Method;
import java.util.Arrays;

public class OverrideClass2 extends OverrideClass {
    public static void main(String[] args) throws Exception {
        Arrays.stream(OverrideClass.class.getDeclaredMethods())
                .forEach(System.out::println);

        invokeGetTestReflectively();
        System.out.println("invoked 'getTest' reflectively");
        invokeGetTest2Reflectively();
        System.out.println("invoked 'getTest2' reflectively");
    }

    private static void invokeGetTestReflectively() throws Exception {
        Method method = OverrideClass2.class.getMethod("getTest");
        method.invoke(new OverrideClass2());
    }

    private static void invokeGetTest2Reflectively() throws Exception {
        Method method = OverrideClass2.class.getMethod("getTest2", Object.class);
        method.invoke(new OverrideClass2(), new Object[]{null});
    }
}

Run results

Java 8, openjdk version "1.8.0_332"
public java.lang.String org.example.OverrideClass.getTest()
invoked 'getTest' reflectively
Exception in thread "main" java.lang.IllegalAccessException: Class org.example.other.OverrideClass2 can not access a member of class org.example.BaseClass with modifiers "public"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Method.invoke(Method.java:491)
    at org.example.other.OverrideClass2.invokeGetTest2Reflectively(OverrideClass2.java:26)
    at org.example.other.OverrideClass2.main(OverrideClass2.java:15)
  • The reflective access is allowed but there is a bug in javac
Java 17, openjdk version "17.0.3" 2022-04-19 LTS
public java.lang.String org.example.OverrideClass.getTest()
public java.lang.String org.example.OverrideClass.getTest2(java.lang.Object)
public java.lang.String org.example.OverrideClass.getTest3(java.lang.Object)
invoked 'getTest' reflectively
invoked 'getTest2' reflectively
  • Reflective access allowed, no bug in javac
  • Related