[HN Gopher] Nuances of overloading and overriding in Java
___________________________________________________________________
Nuances of overloading and overriding in Java
Author : whack
Score : 63 points
Date : 2023-05-18 15:48 UTC (7 hours ago)
(HTM) web link (software.rajivprab.com)
(TXT) w3m dump (software.rajivprab.com)
| shagie wrote:
| Varargs methods in Java was one that bit me with an overload in
| the past (my tale of is told in more detail at http://the-
| whiteboard.github.io/java/debugging/warstory/2016... )
|
| The issue was that there was a framework class with the method
| signature of execute(Object ... inParams). The class had
| overloaded the method signature to be execute(String foo, String
| bar). If you called it with two args, it got the overloaded call
| and there were a half dozen calls to this method.
|
| At some point, someone added another argument to this so that it
| was execute(String foo, String bar, String qux) and updated the
| half dozen less one methods to use the third parameter.
|
| However, the two args call in some other code still compiled
| correctly because it now went back to calling execute(Object ...
| inParams). Though, the parameters were completely wrong for that
| call structure and the application suddenly sprouted runtime
| errors.
| int_19h wrote:
| This kind of thing is exactly why in C++, when a derived class
| declares a method, it doesn't just overload any methods with
| the same name inherited from the bases, but _hides_ them. If
| you actually want overloading across the hierarchy, the derived
| class needs to explicitly opt into it by "using" the base
| methods it needs.
| mrkeen wrote:
| I'm disappointed that
|
| > The method that gets invoked depends on the "actual" instance
| type, not the "declared" instance type.
|
| doesn't apply more broadly. If you have a call(fruit) method and
| a call(apple) method, when you supply a fruit, it will resolve to
| call(fruit), even if the fruit instance is actually an apple.
| [deleted]
| ackfoobar wrote:
| Some languages do it.
|
| https://en.wikipedia.org/wiki/Multiple_dispatch
| adelarsq wrote:
| These are one of the things that shiver me in production legacy
| code.
| cutler wrote:
| A good reason, if any were needed, to escape from the Java OOP
| maze once and for all and never return. Honestly, talk about a
| mountain of incidental complexity. People, wake up! It doesn't
| have to be this way. You've been sold Java's absurd contortions
| dressed in the emperor's new clothes as the default by
| universities and big corps for decades. There's a whole other
| world out there where data is .... just ..... data.
| seadan83 wrote:
| Most of these exact same issues would be the same in C++, for
| sure similarly contrived examples could be contrived.
|
| > People, wake up! It doesn't have to be this way. You've been
| sold Java's absurd contortions dressed in the emperor's new
| clothes as the default by universities and big corps for
| decades.
|
| I think this is a very incorrect characterization. Effective
| Java was required reading for me in the very late 90s (decades
| ago). Back then it was even argued favor composition over
| inheritance [1]. AFAIK you'd have to go back to the 70s or 80s
| for inheritance to still have a good reputation (and which at
| the time was certainly better than a GOTO statement). The
| incidental complexity was later revealed to be quite profound,
| and a large chunk of "Effective Java" is dedicated to
| explaining these complexities and why and how to avoid these.
|
| [1] https://blogs.oracle.com/javamagazine/post/java-
| inheritance-...
| brazzy wrote:
| Inheritance is still absolutely a good thing to leverage
| polymorphism - but for that all you need are simple, shallow
| inheritance hierarchies. What has a deservedly gotten a bad
| reputation are ivory tower architectures with deep
| inheritance hierarchies that lead to things like the Abstract
| Factory Pattern.
| paulddraper wrote:
| Abstract factory pattern isn't related to deep inheritance
| hierarchies.
| rootlocus wrote:
| Between duck-typing languages like python and javascript and
| the minefiled that is C++, I think Java and C# fit a pretty
| nice sweet-spot. Java absolutely has "just data", they're
| mostly used for DTOs and database Entities, with tools like
| lombok @Data and `records` being some nice mechanisms of
| implementing them.
|
| Inheritance, when used for implementing is-a relationship, is a
| pretty powerful tool. If you're surprised by overriding, maybe
| instead of complaining about `Parent parent = new Child()`
| examples, try to see a `Writer writer = new PdfWriter()`. Are
| you still surprised that `writer.write(document)` generates a
| pdf?
|
| There are absolutely horrible ways of designing code in Java,
| but that can be said about any language.
| whstl wrote:
| That's a good point. But IME, when people complain about
| inheritance, they're primarily talking about implementation
| inheritance.
|
| Interface inheritance, used for polymorphism like in your
| example, is still considered a good practice and is not
| really criticized as much (an exception is when it is
| "abused" for other reasons, like in codebases that have one
| interface for every class, even without polymorphism).
| UncleMeat wrote:
| Eh, the meme has gotten so strong that I now see people say
| that subtyping in general is bad. It has escaped the
| gravity of concrete criticisms of implementation
| inheritance and morphed into a general "oop bad" thought
| system.
| kaba0 wrote:
| GUI widgets are one area where implementation inheritance
| is the best model.
| lmm wrote:
| > Between duck-typing languages like python and javascript
| and the minefiled that is C++, I think Java and C# fit a
| pretty nice sweet-spot.
|
| Imagine where we would be if an actually good way of doing
| type systems had been invented in the 1970s and there was a
| whole family of languages that followed that approach. Oh
| wait.
|
| > Java absolutely has "just data", they're mostly used for
| DTOs and database Entities, with tools like lombok @Data and
| `records` being some nice mechanisms of implementing them.
|
| The fact that you have to step outside the language with
| @Data rather proves the point. And while Java now has a
| "record" keyword, they're still reference types, with turning
| them into actual values being something that's supposedly
| coming Real Soon Now for 10+ years.
|
| > Inheritance, when used for implementing is-a relationship,
| is a pretty powerful tool. If you're surprised by overriding,
| maybe instead of complaining about `Parent parent = new
| Child()` examples, try to see a `Writer writer = new
| PdfWriter()`. Are you still surprised that
| `writer.write(document)` generates a pdf?
|
| Inheritance is a conflation of 3 useful features (interfaces,
| composition, and delegation) that's a lot less useful than
| the sum of its parts. Better languages separate these out
| properly. (And, credit where it's due, Java made a positive
| step in normalizing the separation of interfaces from the
| other parts of inheritance).
|
| > There are absolutely horrible ways of designing code in
| Java, but that can be said about any language.
|
| That's a cop-out. No language completely eliminates bad code,
| but there are better and worse languages.
| exabrial wrote:
| Most of these rules are pretty straigthtforward. I don't really
| find any surprises in the Java method dispatch system, it just
| sort of works without a lot of thinking about it.
|
| The one area I think leaves some question marks for me is the
| "new" default methods in interfaces, aka "public defender
| methods".
|
| They were introduced awhile back, but they've mostly been avoided
| until recent years. As they become more common, a thorough
| understand of how Java deals with double-diamond needs to be
| understood by Java developers.
| tmtvl wrote:
| Reminds me of CLOS and the versatility of _defgeneric_ and
| _defmethod_.
| martincmartin wrote:
| Many of these things are at the core of the book Java Puzzlers.
|
| http://www.javapuzzlers.com/
| yotamoron wrote:
| I hate inheritance.
| nforgerit wrote:
| Some months ago, I stumbled into an OOP inheritance rabbithole
| which got me thinking the same:
|
| - Isn't the (biological) concept of inheritance built at it's
| core around the idea of "generations"? How does that make any
| sense in OOP?
|
| - Why do Java beginner courses still teach Inheritance like it
| was 1995 with all the bells and whistles and dogs and cats and
| mammals?
|
| - Why do they teach Inheritance and later introduce "favor
| composition to inheritance"? Doesn't this just confuse
| everyone?
|
| - Why does Inheritance have first-class syntax support in an
| OOP language like Java ("extends") whereas Composition usually
| needs to be "engineered" using some more complex patterns, e.g.
| DI?
|
| Most of it is probably for historical reasons, i guess.
| paulddraper wrote:
| > How does that make any sense in OOP?
|
| OOP inheritance forms a tree, and that is the language of
| trees (parent, child, ancestor, descendant, sibling).
|
| > Why do Java beginner courses still teach Inheritance like
| it was 1995
|
| > Why do they teach Inheritance and later introduce "favor
| composition to inheritance"? Doesn't this just confuse
| everyone?
|
| Codecademy's course introduces inheritance in module 9 of 11,
| and calls it "deeper object-oriented" feature:
| https://www.codecademy.com/learn/learn-java
|
| > Why does Inheritance have first-class syntax support in an
| OOP language like Java ("extends") whereas Composition
| usually needs to be "engineered" using some more complex
| patterns, e.g. DI?
|
| Class members absolutely have first class syntax.
|
| DI is composition with IoC container.
| [deleted]
| tored wrote:
| I remember when C++ was touted as a superior language to Java
| because C++ has support for multiple inheritance.
| UncleMeat wrote:
| Java has interfaces with method implementations now. You
| can do multiple inheritance with it too :P
| celaleddin wrote:
| I really liked all these questions, thanks for sharing. I'll
| think about them.
| emodendroket wrote:
| If every class inherits Object then you probably need to
| understand the concept still, even if it's no longer favored
| for organizing your own business logic. Besides, you will
| encounter other people's code that doesn't follow all the
| best practices eventually. And I'm not sure anyone has come
| up with a better way of teaching inheritance in the past few
| decades.
| pie_flavor wrote:
| Because it's an object-oriented language, and so its primary
| paradigm is object-orientation. Various style guides will
| tell you that object-orientation is bad and you should try
| writing in a procedural style instead, but the correct answer
| when you pine for procedural programming is to stop using
| Java.
| psychoslave wrote:
| I don't know for Java currently but for example in Ruby
| composition is simply a matter of putting your code in a
| module rather than a class. Then you can extend any class
| with this module. You can even extend instances. That is
| 3.extends(Some_module).method_from_some_module is perfectly
| valid.
|
| In PHP, surely you can use traits.
| emodendroket wrote:
| Of the many models of "composition" that are possible I
| think Ruby's free-for-all blend of mix-ins and monkey
| patches is the only one that can drive a maintenance
| programmer more insane than a deep hierarchy of inheritance
| of Java classes.
| shagie wrote:
| irb(main):001:0> "bar".foo Traceback (most recent
| call last): 4: from /usr/bin/irb:23:in
| `<main>' 3: from /usr/bin/irb:23:in
| `load' 2: from
| /Library/Ruby/Gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in
| `<top (required)>' 1: from (irb):1
| NoMethodError (undefined method `foo' for "bar":String)
| Did you mean? for irb(main):002:0> class String
| irb(main):003:1> def foo irb(main):004:2>
| "foobar!" irb(main):005:2> end
| irb(main):006:1> end => :foo
| irb(main):007:0> "bar".foo => "foobar!"
| irb(main):008:0>
|
| Yes! Let's modify the core library String class on the
| fly to add new functions to it.
|
| There are things about ruby that truly scare me if my
| goal was to write secure and reasonable code.
| emodendroket wrote:
| Right, that's monkey patching. You run into the real
| problems when you have multiple people wanting to add
| them.
|
| Ruby isn't alone in supporting this functionality (you
| can fiddle with JavaScript prototypes, for instance), but
| I think it is unique how much it is encouraged (at least
| in Rails world). I think extension methods are a much
| better model to achieve something similar.
| shagie wrote:
| There are also some _really_ weird /powerful/voodoo
| things that you can do when that is combined with the
| first class environments.
|
| The "why Scheme didn't do this": First-class environments
| - http://funcall.blogspot.com/2009/09/first-class-
| environments...
|
| why Ruby did (and wasn't a good idea): Ruby Conf 2011
| Keeping Ruby Reasonable https://youtu.be/vbX5BVCKiNs
|
| Consider where you can slip in a block invocation and the
| following code: def mal(&block)
| block.call block.binding.eval('a = 43')
| end a = 42 puts a mal do
| puts 1 end puts a
|
| and that that means that you've got full access to be
| able to inspect and manipulate all of the variables in
| scope at the time of the invocation. While that example
| is rather obvious, it can be done much more subtly too.
|
| The power of ruby to do meta programming and by extension
| some really neat DSLs also provides it with some
| dangerous tools that are otherwise rather difficult to
| track down.
| whstl wrote:
| _" Why does Inheritance have first-class syntax support in an
| OOP language like Java ("extends") whereas Composition
| usually needs to be "engineered" using some more complex
| patterns, e.g. DI?"_
|
| I think the main reason is that DI is seen as an orthogonal
| concept by language designers, so you'd need new two first-
| class features in a language.
|
| If you do composition without injection (e.g.: by having _new
| ChildObject()_ in the constructor), you don 't really require
| that many more lines of code compared to inheritance.
| class Car { private Engine engine;
| public Car() { engine = new Engine();
| } public void drive() {
| engine.start();
| System.out.println("Driving...");
| engine.stop(); } }
|
| Of course it's much less flexible and less testable than
| composition + injection, but not that inflexible when
| compared to inheritance. And first-class support for _only_
| that would make the feature a bit useless without DI...
| lmm wrote:
| Yep. You've got to remember that Java is nearly 30 years old;
| at the time making a distinction between interfaces and
| abstract classes was seen as a radical move. Newer languages
| have better support for more decoupled ways of doing things,
| e.g. Rust's use of typeclasses (which it calls traits for
| some reason) or Kotlin's built-in support for delegation.
| Twisol wrote:
| > Rust's use of typeclasses (which it calls traits for some
| reason)
|
| Scala and (apparently) PHP call them traits too, among many
| others [1] -- Rust wasn't the first here.
|
| [1]
| https://en.wikipedia.org/wiki/Trait_(computer_programming)
| invalidname wrote:
| Are these things HN readers didn't know?
|
| I didn't know you can do operator overloading in Java (with
| Manifold). That would be interesting:
| https://www.youtube.com/watch?v=pwQs-308OdY
| nilptr wrote:
| I would be terrified on introducing that into a project and
| worrying about its long-term support...
| Twisol wrote:
| A team I was on decided against Manifold precisely because we
| wanted to keep up with Java's twice-yearly version updates,
| and we didn't want a bespoke compiler plugin to lock us to an
| older version.
|
| I think Manifold is extremely cool, and it does look like
| they're keeping up with the latest -- but the deep compiler
| magic it relies on really makes me nervous.
| bvanderveen wrote:
| Just say no.
| agumonkey wrote:
| Makes you appreciate the complexity of haskell.
| lern_too_spel wrote:
| None of this is surprising. Just think about how the compiler is
| going to use the object's vtable. There is no magic going on.
| alserio wrote:
| I believe that OP is confused about when the resolution
| happens. Overloading is resolved by the compiler, inheritance
| at runtime
| marginalia_nu wrote:
| Kind of a blurry distinction with a JIT-compiled languague
| tbh.
| alserio wrote:
| why? the JIT works on bytecode and JVM bytecode has no
| concept of overloading. Maybe I should have specified that
| by "compiler" i meant the first compilation step, Java
| source to bytecode.
| hinkley wrote:
| The JVM bytecode is not the JVM.
|
| There's some late binding allowed in the JVM to deal with
| API changes. So the caller can call a method signature
| that doesn't exist in the target (but either does in a
| later version or did in a previous one), and the JVM has
| to resolve it at first invocation, rewriting the bytecode
| in the process.
|
| The compile time resolution ends up being a hint that
| usually works, but doesn't always. And if memory serves a
| number of languages that run on top of the JVM have
| leveraged this fact, including the original third party
| implementation of generics.
| alserio wrote:
| still the lookup is based on the symbol, no? so the JVM
| does not resolve overload even in that case, i believe
| hinkley wrote:
| Read what I said again.
|
| Step 1 is a lookup by symbol. You're ignoring the vast
| majority of the logic.
|
| There are hints about what I'm talking about in this
| conversation about invokespecial:
| https://stackoverflow.com/questions/13764238/why-
| invokespeci...
|
| This trickiness is not called out in the bytecode
| specification, but in the JVM spec. They get a little
| closer to the mark here:
|
| https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6
| .ht...
|
| "Is Java Statically Typed or Dynamically Typed?" is
| basically an interview gotcha question. Most people
| should answer the former, but if you are hiring people to
| create a JVM or a language that translates to the JVM,
| you're looking for a much longer answer that circles
| around gradations of the latter, because the compiler is
| strongly typed but the JVM has weaker type guarantees
| that are sorted out at first invocation, and which look
| like variance (predating most literature on contra and
| covariance, I might add). Prior to invokedynamic people
| used this and other facts to trick the JVM into doing
| things that are illegal in Java. The Hotspot people
| learned a whole lot about how the JVM _actually_
| functions from interactions with these sorts of people.
| aardvark179 wrote:
| Why are you linking to the spec for invoke special (early
| binding) when you seem to be talking about invoke virtual
| (late binding) which is what most method calls are
| compiled to? Also weather a method is interpreted or JIT
| compiled has ni effect on method lookup or any other
| semantics, and trying to be pedantic about compile time
| in that sense is a complete red herring.
| alserio wrote:
| i now get what you mean, thanks
| jchw wrote:
| I think the main confusion is that when you override a method
| in Java, similar to other languages like C++, it considers not
| only the name but actually the entire function signature.
| That's in fact why overloading works to begin with, because of
| the fact that the identity of a method is not just its name but
| in fact it's signature.
___________________________________________________________________
(page generated 2023-05-18 23:01 UTC)