JDK-8029674 : (reflect) getMethods returns default methods that are not members of the class
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8,9
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2013-12-06
  • Updated: 2017-05-17
  • Resolved: 2014-06-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u20Fixed 9 b19Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8177361 :  
Description
Dan writes:

---

To illustrate, the JDK 7 implementation of getMethods includes the following:
1) The class's/interface's declared (public) methods
2) The getMethods() of the superclass (if this is a class), minus any that have a match in (1)
3) The getMethods() of each superinterface, minus any that have a match in (1) or a _concrete_ match in (2)

So, for example, as expected:
interface I { void m(); }
interface J extends I { void m(); }
interface K extends J {}
K.class.getMethods() = [ J.m ]

But:
interface I { void m(); }
interface J extends I { void m(); }
interface K extends ***I,*** J {}
K.class.getMethods() = [ I.m, J.m ]

This is consistent with a reasonable reading of JLS 7 (though it is vague)*, but conflicts with JLS 8.  We changed the 8 spec because, unlike abstract methods, inheriting two conflicting default methods is a compiler error, and that should be avoided.

[* There was a time when I thought the current behavior could be considered a 7 bug, but having experimented with it more, I no longer think that's the case.  This is a new issue in 8.]

An implementation of getMethods consistent with JLS 8 would include the following (see Lambda Spec, Part H, 9.4.1 and 8.4.8):
1) The class's/interface's declared (public) methods
2) The getMethods() of the superclass (if this is a class), minus any that have a match in (1)
3) The getMethods() of each superinterface, minus any that have a match in (1) or a _concrete_ match in (2) ***or a match from a more-specific class/interface in (2) or (3)***

However, there is a compatibility concern: the behavior of K.class.getMethods in the second example above changes.  How much do we care about this?  The spec change was made with the understanding that this was simply an internal change to an intermediate result, without observable consequences.  Reflection exposes that intermediate result.  But, even so, client code should not be depending on the rather vague notion of how many identical abstract methods are members of a class.

---

That is, the spec changed in 8 but the implementation (which was correct up through 7) has not changed.

The proposed fix for default methods as outlined by Dan:

---

Given the sets of (1) declared methods, (2) superclass member methods, and (3) superinterface member methods, prune as follows:
a) Remove methods from (2) and (3) that have a match in (1)
b) Remove methods from (3) that have a _concrete_ match in (2)
c) Remove  methods declared in _interfaces_ from (2) and (3) that have a more-specific _default_ method in (2) or (3)

The new logic is (c).  We still allow a default method and its overriding abstract method to coexist in the same type, but these kinds of mixtures can already happen in a limited way (see Lambda Spec, 8.4.8.4), so that seems tolerable.

I feel a little more comfortable with this particular ad hoc strategy given the guiding principle that we just want to ensure that methods that don't need to be implemented are excluded.  (Thus, no need to consider all possible hierarchies and make arbitrary choices about correct behavior in each case.)

This still has an impact on legacy clients (an abstract method might be made default, and then only one method turns up instead of two), but it's a narrow enough case that we should probably consider it far less risky.

---

and:

---

How do we feel about the compatibility risk of getMethod returning a different result that i) has the same name and descriptor, and ii) overrides the previously-returned result?

Arguments for why this is okay:
- The interface search order is completely unspecified and implementation-dependent
- Depending on the declaring class/interface of a Method, when you got that method from a search like this, is crazy

Arguments for why this is bad:
- The accessibility of the method depends on its declaring interface (if that interface is package-access), possibly prompting an access error (but javac-generated bridges make this a very rare circumstance)

Unlike getMethods, there is no incompatible change here (i.e., the new results are valid under the old javadoc), just a refinement of what results are considered acceptable.

---



Comments
For release notes: Scope: JDK Synopsis: Default methods affect the result of Class.getMethod and Class.getMethods Description: The javadoc for the Class.getMethod and Class.getMethods refer to the definition of inheritance in the Java Language Specification. Java SE 8 changed these rules in order to support default methods and reduce the number of redundant methods inherited from superinterfaces (see JLS 8, 8.4.8). For example, say a class has two superinterfaces, I and J, each of which declare "int length();". Generally, we consider both methods to be members of the class; but if J also extends I, then as of Java SE 8, the class only inherits one method: J.length(). Class.getMethod and Class.getMethods were not updated with the 8 release to match the new inheritance definition (both may return non-inherited superinterface methods). Typically, the distinction is of no consequence; and for compatibility, it is preferred that the identity and number of returned methods match Java SE 7 as closely as possible. However, when the overriding method ("J.length", above) is a default method, it is important to filter out other overridden methods ("I.length" above). As of 8u20, the implementation has been changed to perform this filtering step when the overrider is a default method. Incompatibility: Behavioral
2014-08-04

OK with deferral.
2013-12-06

Release team: Mark wants Brian to review this first. Mark will send a note to Brian and follow up.
2013-12-06

8-defer justification: Propose to defer this to 8u20. The fix is somewhat complicated and the testing is hard to get right. The impact of _not_ fixing this in 8.0 GA is reduced due to the fact that default methods are a new construct and that the unfixed behavior is in line with what could be expected moving from 7.
2013-12-06

ILW: H (conformance, to many methods returned), M (reflecting over interfaces with default methods), M (rewrite client to manually refine result) -> P2
2013-12-06