java - Why can this generic method with a bound return any type? -
why following code compile? method ielement.getx(string)
returns instance of type ielement
or of subclasses thereof. code in main
class invokes getx(string)
method. compiler allows store return value variable of type integer
(which not in hierarchy of ielement
).
public interface ielement extends charsequence { <t extends ielement> t getx(string value); } public class main { public void example(ielement element) { integer x = element.getx("x"); } }
shouldn't return type still instance of ielement
- after type erasure?
the bytecode of getx(string)
method is:
public abstract <t extends ielement> t getx(java.lang.string); flags: acc_public, acc_abstract signature: #7 // <t::lielement;>(ljava/lang/string;)tt;
edit: replaced string
consistently integer
.
this legitimate type inference*.
we can reduce following example (ideone):
interface foo { <f extends foo> f bar(); public static void main(string[] args) { foo foo = null; string baz = foo.bar(); } }
the compiler allowed infer (nonsensical, really) intersection type string & foo
because foo
interface. example in question, integer & ielement
inferred.
it's nonsensical because conversion impossible. can't such cast ourselves:
// won't compile because integer final integer x = (integer & ielement) element;
type inference works with:
- a set of inference variables each of method's type parameters.
- a set of bounds must conformed to.
- sometimes constraints, reduced bounds.
at end of algorithm, each variable resolved intersection type based on bound set, , if they're valid, invocation compiles.
the process begins in 8.1.3:
when inference begins, bound set typically generated list of type parameter declarations
p1, ..., pp
, associated inference variablesα1, ..., αp
. such bound set constructed follows. each l (1 ≤ l ≤ p):
[…]
otherwise, each type
t
delimited&
in typebound, boundαl <: t[p1:=α1, ..., pp:=αp]
appears in set […].
so, means first compiler starts bound of f <: foo
(which means f
subtype of foo
).
moving 18.5.2, return target type gets considered:
if invocation poly expression, […] let
r
return type ofm
, lett
invocation's target type, , then:
[…]
otherwise, constraint formula
‹r θ → t›
reduced , incorporated [the bound set].
the constraint formula ‹r θ → t›
gets reduced bound of r θ <: t
, have f <: string
.
later on these resolved according 18.4:
[…] candidate instantiation
ti
defined eachαi
:
- otherwise,
αi
has proper upper boundsu1, ..., uk
,ti = glb(u1, ..., uk)
.the bounds
α1 = t1, ..., αn = tn
incorporated current bound set.
recall our set of bounds f <: foo, f <: string
. glb(string, foo)
defined string & foo
. apparently legitimate type glb, requires that:
it compile-time error if, 2 classes (not interfaces)
vi
,vj
,vi
not subclass ofvj
or vice versa.
finally:
if resolution succeeds instantiations
t1, ..., tp
inference variablesα1, ..., αp
, letθ'
substitution[p1:=t1, ..., pp:=tp]
. then:
- if unchecked conversion not necessary method applicable, invocation type of
m
obtained applyingθ'
type ofm
.
the method therefore invoked string & foo
type of f
. can of course assign string
, impossibly converting foo
string
.
the fact string
/integer
final classes apparently not considered.
* note: type erasure is/was unrelated issue.
also, while compiles on java 7 well, think it's reasonable needn't worry specification there. java 7's type inference less sophisticated version of java 8's. compiles similar reasons.
as addendum, while strange, never cause problem not present. it's useful write generic method return type solely inferred return target, because null
can returned such method without casting.
suppose example have map analog stores subtypes of particular interface:
interface fooimplmap { void put(string key, foo value); <f extends foo> f get(string key); } class bar implements foo {} class biz implements foo {}
it's valid make error such following:
fooimplmap m = ...; m.put("b", new bar()); biz b = m.get("b"); // casting bar biz
so fact can also integer = m.get("b");
not new possibility error. if programming code this, potentially unsound begin with.
generally, type parameter should solely inferred target type if there no reason bound it, e.g. collections.emptylist()
, optional.empty()
:
private static final optional<?> empty = new optional<>(); public static<t> optional<t> empty() { @suppresswarnings("unchecked") optional<t> t = (optional<t>) empty; return t; }
this a-ok because optional.empty()
can neither produce nor consume t
.
Comments
Post a Comment