[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)