bug-report-can-t-call-static-methods-on-a-java-class-instance
Attila Szegedi
szegedia at gmail.com
Thu Feb 11 14:36:12 UTC 2016
On Feb 11, 2016, at 1:04 PM, Christoph Sauer <christoph.sauer at nuveon.de> wrote:
>
> Hi Attila,
>
> thanks for your response.
>
> You wrote "Have you considered that for a large established system, you
> might not want to change the underlying JavaScript engine?"
>
> For now we will continue with Rhino. Since we are building our framework
> using Rhino since 2006 we don't use the build in JDK mechanism to
> instantiate it anyway. But from what I read in the press you advertise
> Nashorn as the successor of Rhino, offering better performance and next
> ECMA Script support.
Nashorn is the successor to the JDK-bundled Rhino (OpenJDK or Oracle don’t have any say in what’s the successor to Mozilla Rhino…). Nashorn was undertaken for multitude of reasons: better performance, ECMAScript 5.1 compliance (now with ongoing ES6 additions), but also dogfooding invokedynamic, and very importantly (although not often emphasized): security.
> I don't care to much about performance right now
> because all the heavy lifting is done in Java API Objects that are then
> offered to the framework users as script vars. They make up the actual
> API for our framework (which contain many static methods). Nevertheless
> a better performance and the ECMA Script future support would buy me in.
> What is a showstopper right now is the separation of static/non static
> methods.
>
> You wrote "hopefully smaller change would be to change the methods in
> your utility classes from being static to being instance methods"
>
> These static methods are not only used in scripting, but of course also
> reused within the java framework. You usually use static methods because
> you don't need any context to call them except the method params you
> pass them. If I would make my static methods to instance methods, this
> would mean that in all methods that call that method they not only would
> need the params but also constructor params for that object. Since they
> are usually not available you could come to the conclusion to use an
> empty constructor just for the sake of simplicity. Lets look at an
> example. Here's how it currently works
>
> TypeUtil tutil = new TypeUtil(RuntimeObject);
> tutil.fmtDate(obj); //uses a property set in RuntimeObject to get the
> format;
> TypeUtil.fmtNumber(obj); //simply uses the standard Locale, does not
> require runtime information.
>
> TypeUtil is passed as "jtype" hook. JS developers use both
> jtype.fmtDate() and jtype.fmtNumber(). They don't care about the
> difference and they don't need to.
So… “jtype” is an instance of TypeUtil, right?
You could do something to bridge this in Nashorn, BTW. Nashorn has a non-standard (meaning, not in ECMAScript) Object.bindProperties() function. Usage is: Object.bindProperties(dst, src). What it does, it adds all properties from “src” object as bound properties to the “dst” object and returns it. The fact they’re bound properties means that even invoking dst.fn(x) will make “fn” see “src” as its “this”.
So you could then do something like
var new_jtype = {}
new_jtype = Object.bindProperties(new_jtype, jtype)
new_jtype = Object.bindProperties(new_jtype, Java.type(“com.foo.TypeUtil”))
jtype = new_jtype
or rolled into one:
jtype = Object.bindProperties(Object.bindProperties({}, jtype), Java.type(“com.foo.TypeUtil”))
You’ll then have both the instance and the static methods exposed through a single object. Of course, I understand that this only works if you’re in control of what’s exposed. If some of these utilities return other utilities, then you obviously can’t go this route…
> Now for Nashorn, since I might not have that "RuntimeObject" i could
> make fmtNumber() to an instance method. For Nashnorn this would work
> perfectly fine. However unfortunately this fmtNumber() is reused in many
> places within the Java Framework code where RuntimeObject is not
> available. So the developers would be tempted to add an "Empty" contructor.
>
> new TypeUtil().fmtNumber(obj)
>
> This works fine. But what if another developer sees the empty
> constructor and says "hey, perfect, i use the simpler Constructor" like
> this:
>
> new TypeUtil().fmtDate(obj); //exception! Nullpointer since
> RuntimeObject is not there.
>
> You and me would not do such a thing. But your design decision would
> finally lead to programming like this because programmers are lazy and
> prefer the less cognitive load approach. They won't read this discussion
> here.
>
> From my experience I never EVER had a problem with a Nullpointer
> deference by using a static method on an object. However it is a
> frequent source of problem in production code if in an edge case a
> method gets called on an object that is in an invalid state because of
> missing constructor parameters.
>
> So your design decision will lead to something that is much worse than
> the null pointer deference: The effect I just described I would call the
> "Nashorn Effect": Empty constructors leading to null pointers the way I
> just described on the Java side.
The real “Nashorn effect” here is the fact it exposes a deficiency in API design as the TypeUtil class clearly has some issues of separation of concerns between operations that depend on some contextual state (RuntimeObject) and those that don’t. That’s one of the things I allude to when I refer to maintainability of code. It can bite you in Java, not just JavaScript.
Firstly, let’s state that you are bitten here by the fact that since Java language allows invocation of static methods on instances, javac won’t allow you to have a static method with the same signature as an instance method (as then it can’t distinguish among them since it allows the ambiguity), so you can’t choose the easier route of keeping both a static and a non-static method with the same signature. Funnily enough, this distinction doesn’t exist on the JVM level, that is, it’s legal to have a class file with both “static void f(Object x)” and “void f(Object x)”. May I add that the invocation of static methods on instances is also strongly discouraged practice, and practically all compilers and IDEs today will issue a warning if you do it.
Basically, you have at least two sets of concerns here that are entangled in a single class: one set of concerns that can operate without a RuntimeObject and one that needs a RuntimeObject. Frankly, this smells like it would need to be two classes. I mean, suddenly you have the need to allow partially-constructed (= “not fully configured") instances in your system? Hmm…
That’s not “Nashorn effect”, that’s a deficiency in API design. You could call it “Nashorn effect” when it makes you aware of it :-) I posit this would’ve come to bite you sooner or later regardless of Nashorn.
> If I would have to make a hypothesis
> for a research paper comparing programming errors with the Nashnorn
> Engine in place I would start like this
>
> |= Programming Errors |= Likeliness |= Severity
> |Null Pointer Deference|x |x
> |Nashorn Effect |x*3 |x
>
> You make it hard to switch existing projects from Rhino to Nashorn
> because of your conviction that null pointer deference is a bad thing.
That’s not the reason; I don’t even see how null pointer dereferencing is related to separating the static namespace of a class. Independently of that, I do proudly uphold the conviction that null dereferencing is a Bad Thing, though.
> Have you based this design decision that might have such severe
> consequences on profound research on how severe it really is in
> practice? Why did Java itself not make it deprecated in the first place?
Beats me. If it were up to me, this would’ve never worked. Weirdly enough this was always a pure language and compiler construct and never existed on bytecode level. You can’t invoke a static method with the INVOKEVIRTUAL bytecode instruction nor an instance method with INVOKESTATIC. I won’t go off to experiment with this, but I wager changing a method from instance to static or other way round is an incompatible class change that would make an attempt to load another class using it without recompiling the user class throw a NoSuchMethodError.
>
> You wrote "I made them in the direction of helping people write more
> maintainable and clear code in the long run".
>
> I think that you might have prevented a sometimes problematic habit
> (null pointer deference), but you increased the likeliness of an even
> worse habit: The Nashorn Effect. I also think you contradict yourself
> saying "so a theoretical JavaScript programmer should have no a priori
> expectations with regard to how platform integration features work".
> Good intention! Not allowing static methods on java will however have
> exactly this effect. Look at this presumably future code comment that
> would pop up if I would present to namespaces for nonstatic/static on
> the same Class to js users
>
> jtype.fmtDate(obj) ;
> jtypeS.fmtNumber(obj); //JS Developer: why the heck do I need jtypeS,
> whats the S standing for?
Maybe you could use more descriptive names so they don’t have to wonder about “S”? :-)
Speaking of descriptive namespace naming, I can take your argument a step further; your “JS Developer” could also then ask “why the heck do I need jtype for, what’s it standing for”? By providing various utility objects, you’re already grouping functions into namespaces represented by those objects. If splitting namespaces (and thus introducing new ones) is bad, why not eliminate them all and just give them a bunch of global functions? I’m not advocating this, mind you (although it might make sense for smallish APIs), just pointing out the logical end conclusion of your argument.
Anyway, Object.bindProperties() lets you knead several otherwise separate concerns into a unified facade, if that’s what you need.
>
> You wrote "in JDK 9 Nashorn can be extended with additional linkers"
>
> That's my light at the end of the tunnel. Thanks for that hint. This
> sounds like I would get what I want while still being able to reuse my
> Java API without the "Nashorn Effect" threat on the Java side. If this
> will come anyway: wouldn't it be a low hanging fruit for you to provide
> us with a "sloopy switch" in the meanwhile :-)
Well, it’s certainly possible. The bar for what’s possible is fairly low in JavaScript, isn’t it? The language is just so extremely dynamic that you can bend it pretty much any way.
I’m not sure if it’s desirable, though. I’ll keep thinking about it.
>
> You wrote "As for 'creature'"
>
> That's what I thought. I am not a native speaker so thanks for the
> clarification. In German even saying "Das Nashorn ist eine Kreatur"
> would sound derogatory to that animal. So sorry for that question :)
No worries, I figured out as much.
>
> The only thing that is left for me is to check if I can make this in
> Nashorn, because this is the SINGLE MOST programming error in our
> framework with rhino:
>
> if (jtpye.fmtDate(obj) == "11.02.2016") //arg! java strings are not js
> string -> a priori expectations needed in Rhino - like you said - there
> you have it!
Heh. Nashorn internally sometimes doesn’t use strings for efficiency reasons, but we convert them into java.lang.String at runtime boundaries where they could be observed.
>
> Thanks for your work and the possibilities you provide us with a good js
> engine for the Java platform. I am really thankful for that. I am
> looking forward using Nashorn (hopefully) in the future. I am out for
> now however.
Thanks for taking the time to lay out your use case and for the civil discussion. It is appreciated.
Attila.
>
> Christoph
>
> Am 04.02.2016 um 18:26 schrieb Attila Szegedi:
>> Hi Cristoph,
>>
>> I presume you’re referring to this post:
>> <http://mail.openjdk.java.net/pipermail/nashorn-dev/2013-October/002204.html>.
>>
>> I still stand by the design decisions we made when mapping the Java
>> class/object system to JavaScript in Nashorn. I understand the
>> differing opinions (yours and Tal’s both), and don’t consider them
>> wrong, just having different requirement priorities.
>>
>> Have you considered that for a large established system, you might not
>> want to change the underlying JavaScript engine? Rhino is yet again in
>> active development, so a hopefully easier way to modernize your system
>> would be to switch from JDK-embedded JavaScript runtime to Rhino as an
>> external library. Ideally you’d just need to touch your script engine
>> factory instantiation code.
>>
>> Even if you decide to switch to Nashorn, a hopefully smaller change
>> would be to change the methods in your utility classes from being
>> static to being instance methods. That would even keep the change
>> cross-compatible with Rhino. (Of course, this doesn’t help you if you
>> have some external dependencies where you can’t change the methods
>> from static to instance…)
>>
>> In terms of future advice, there’s one other approach you might take,
>> although unfortunately it won’t be available for JDK 8, just for JDK
>> 9. Namely, in JDK 9 Nashorn can be extended with additional linkers
>> (see e.g. Sundar’s recent post that uses extra linkers to bridge JS to
>> Python https://blogs.oracle.com/sundararajan/entry/nashorn_javascript_access_to_python).
>> It would be possible to write an additional Dynalink linker that
>> recognizes missing members on POJOs and attempts to look up static
>> methods instead. I know this doesn't help you now, but thought it
>> might be worth mentioning it for completeness so it might help someone
>> who digs this message out of an archive a year from now.
>>
>> As for “creature” – yes, you were misinterpreting it :-). “A different
>> kind of creature” here could be synonymous with “a different cup of
>> tea”, or “another case”. I use the word as synonymous with “being”
>> (“something created”) without any intended negative overtones. I built
>> fairly complex systems on top of Rhino in the past myself, and for a
>> while both contributed to Rhino and served as the project’s
>> administrator. I certainly have neither the moral ground nor the
>> desire to diss Rhino or its users.
>>
>> Attila.
>>
>>> On Feb 4, 2016, at 7:18 AM, Christoph Sauer
>>> <christoph.sauer at nuveon.de <mailto:christoph.sauer at nuveon.de> <mailto:christoph.sauer at nuveon.de <mailto:christoph.sauer at nuveon.de>>> wrote:
>>>
>>> Dear Attila,
>>>
>>> i was just evaluating a switch of our huge codebase written in
>>> Javascript using the Rhino interpreter to Nashorn. We have utility
>>> classes written in Java that are are passed as objects to help users
>>> write less code in JS, because that is why we use JS
>>> in the first place. Those utility objects also contain frequently used
>>> static methods. Your design decisions will make it impossible for us to
>>> switch.
>>>
>>> Not only will we have to rewrite our code, we also will have to make
>>> separate calls to "handles" the user already has. This is against my
>>> design decision to pursue the ideal of "literate programming". The ideal
>>> to make code more readable is broken here. I agree with Tal Liron
>>> that if a programmer wants to use Java, they would. We use Javascript
>>> because of the shorthand and the goal towards cleaner code
>>> without loosing the power of using Java where need be.
>>>
>>> I felt offended to be called a "Creature". I hope I misinterpret you
>>> sentence here .
>>> Two years have passed since this post. Have you changed your mind since?
>>> Will there be at least a "strict" switch like suggested by tal?
>>>
>>> Thanks
>>> Christoph
>>
>
>
> --
> Christoph Sauer
> Geschäftsführer
> CEO
>
> nuveon GmbH
> Felderstraße 20
> 91801 Markt Berolzheim
>
> mobil : +49 (0) 170 / 272 76 76
> büro: +49 (0) 9146 / 95 990 01
> fax: +49 (0) 9146 / 94 220 45
More information about the nashorn-dev
mailing list