Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The problems with Java can't be fixed by adding new things, you can't undo decades of ecosystem development, training, and ideology built on top of the idea that inheritance is really good idea and belongs everywhere.

edit: I will say that as a Java developer I am grateful every day for the improvements to the language. Java is a very impressive language and I have a lot of respect for the people working on it.



I rarely see inheritance used in practice in java code bases. Except where I would use a union type or sealed class in other languages anyways. I don't feel what youre describing is a real issue.


You must be really lucky then. In my previous job, inheritance and abstract classes were everywhere. Coupled with dependency injection frameworks that "worked like magic", it was really hard to follow the code inside that big, monolithic app. It made me never want to work with Java ever again.


I've never been a big fan of dependency injection. It solves a problem with unit testing in Java, sure, but the reality is that Java could have done some nifty things to help alleviate that as well.


Agreed. Java injection frameworks are opaque and accomplish what they need to with overly powerful mechanisms because of the nature of the language. You don't see that sort of nonsense in python.


Wtf, so what do you think Django is?


I'm curious to understand what dependency injection is in Django that you're referring to?

I know pytest does DI with fixtures, and trying to figure out what you're pulling into a test can be difficult.


In django you're importing a default cache, a default storage etc, and write your code to the interface. In settings.py you wire it all up. It's basically the same, for testing you would have to mock the import or provide some implementation.


technically that's not dependency injection, but a global service locator.


Yeah I agree on that here.

The user doesn't write DI code for Django Class Based views. E.g. the view doesn't accept a Database upon instantiation.


Nothing to do with the nature of the language, but with the nature of the program.

If you're writing a few line script, you don't need a DI container. Once your program gets large, it becomes extremely messy without one. It's no surprise projects like [1] exist.

[1] https://github.com/ets-labs/python-dependency-injector


That DI library is not pythonic at all.

There's nothing wrong with this at all, say:

    class MyClass:

        def __init__(self, my_dep1=None):
             if my_dep1 is None:
                 self.my_dep1 = get_my_dep1()  # Or potentially raise
             else:
                 self.my_dep1 = my_dep1

             [...]

Then to test:

    def test_my_class(): 
        mocked_dep1 = MagicMock()
        my_class = MyClass(my_dep1=mocked_dep1)
        [...]


And if you want to configure the scope of `my_dep1` (singleton, transient, etc.)? What about nested dependencies? etc.


Why do I care about any of that while writing python? Those seem like artifacts of a Java based DI system.


It's a reality of any non-trivial program, regardless of the language.


No. Some languages don't require you to play cat and mouse games to get around artificial limitations put there by the language designers.

There was a talk at PyCon a while back about that patterns commonly used in Java were non-existent in Python.


If that’s the only thing it’s used for in the project, then you are most likely good with Autowired (in Sping) or something similar.

Any complicated DI solution must have a reason for it being there and I’ve seen too many projects complicating themselves on buzzwords like DI or MSOA without really needing either that much


Sounds like you’re confusing DI with DI containers.


I've seen the same craziness with NodeJS, and very simple Java services with minimal inheritance.


Inheritance used to be extremely common, look at AWT/Swing - however more or less it finished there, e.g. more than 20y back.

There are still lots of folks who love 'protected' and deep hierarchies, of course. There is stuff like spring that uses way too many interfaces with a single implementation, doing something a bit off the regular road ends up implementing tons of the said interfaces anew.

However the 'hate' part is mostly the internet (well esp. Hacker news)warrior topic


Great to hear. I used to program java in 2010 and inheritance was still beeing heavily used back then. But things change, if both the lang and the main community has change to focus more on simplicity its def worth looking at again. Coroutines and pattern matching is really good features


Contrast that, I've seen numerous Java codebases, young and old, and inheritance is very much one of the core ways that people program.

I strongly suspect that in a few cases some Java devs using net new systems and avoiding common frameworks will perhaps be able to avoid lots of inheritance but I find it insane to say that that's common or even easy.


Inheritance is heavily used in Java in 2023, at least in projects I’ve had to look at.


> I rarely see inheritance used in practice in java code bases.

That seems absurd to me and I have a hard time understanding it, honestly.


> I rarely see inheritance used in practice in java code bases.

Without some specific call-out, it can be assumed that this is a very niche viewpoint that has no bearing on modern development.

The vast majority of projects use inheritance, today.

Does it use Spring? extends SpringBootServletInitializer

Meaningful responses using values from a request? extends OncePerRequestFilter

Formatting exception handling responses on web requests? extends ResponseEntityExceptionHandler

Then there's all the interfaces you have to satisfy, because it's all tied into the standard library and popular libraries.


You need to inherit to create anything that is a class. But the focus is on composition. You inherit from useful classes so you can build the solution using composition.

I don’t like modern Java because there’s too much non-Java magic code. Layers of stuff that “helps” but removes me from the language. Entire programs written in config and special text wrapping classes and methods. How it works requires understanding multiple intersecting languages that happen to be strung together in .java files.

Edit: when something is in config it’s not checked at compilation. Every environment from dev to prod can have its own config so when you compile in dev you don’t know what’ll happen in prod. I know: let’s add more tools and layers.


> You need to inherit to create anything that is a class

That's true of the Java compiler, for which the documentation is strewn around the intenet...but an example is here: https://medium.com/javarevisited/compiler-generated-classes-...

The Java syntax doe not require extending Object explicitly.eg This is a valid, useless, class:

    public class App {}
> I don’t like modern Java because there’s too much non-Java magic code

It seems like this is the common path for popular languages. They develop their own library-backed DSL's for the most common use cases, which are often little more than macros (@data @getter, @notnull, etc). I am biased by what I've seen in the last 30 years though.


The OP/GP wasn't really complaining about inheritance, despite the fact that this is what they wrote.

The OP is complaining about the difficulty of the API because it is _exposed_ through inheritance.

The complaint about `SpringBootServletInitializer`, for example, is exactly this. There's nothing wrong with inheritance. In fact, SpringBootServletInitializer is exactly what you want to use inheritance for - because you need to build your app using the servlet api.

There are http servers that aren't servlet api, such as https://vertx.io/docs/vertx-web/java/#_re_cap_on_vert_x_core... , which uses little to no inheritance (since the api surface is smaller).


Having a single level of inheritance (especially if it's an interface) is not a problem at all. Having deep inheritance trees can be a code smell.


While you have to extend some things in Spring, yes, the whole point of the DI in spring is so that you can often compose instead of extend.


If you type everything with interfaces in your codebase, you are much less tied to inheritance. In fact, everyone could be written to be composed.

However Java doesn’t support type union so you can get into some ugly and verbose situations but the JVM doesn’t really check type so this is more a compile-time issue and could be fixed in a future Java language revision.


>However Java doesn’t support type union so you can get into some ugly and verbose situations

Isn't the "sealed interface" described in the OP blog post a type union? Or you mean anonymous unions?


Definitely referring to anonymous unions too. Without them, there’s still friction sometimes which makes union types unnatural.

I haven’t written Java in a while and I can’t remember if you could sometimes fake a type union using a generic type on a method, but if you can, it’s definitely super ugly and would raise eyebrows during any code review.


Anonymous unions are a relatively rare feature; Rust and Haskell don't have them, for instance.


Sorry, what? Is not java toghether with Ruby maybe the most hardcore OOP enthusiasts that uses inheritance for "most stuff".

Has the java culture move so far the last decade ?


Effective Java, written by Joshua Bloch, and first published in 2001, had a very influential section called Favor composition over inheritance:

https://books.google.co.nz/books?id=Ra9QAAAAMAAJ&q=inheritan...

Why do you think DI is so prevalent in Java codebases? It's a great way of simplifying composition.


>had a very influential section called Favor composition over inheritance

The GoF book (Design Patterns) had the same, somewhere early, like in the Introduction, in 1994:

https://en.m.wikipedia.org/wiki/Design_Patterns


I’ve been writing OOP code and reading about it for a decade or two, and inheritance was identified as problematic pretty quickly. The problem today, imo, is more underuse now.


>inheritance was identified as problematic pretty quickly

Exactly what I referred to here:

https://news.ycombinator.com/item?id=37539443


Yeah, it’s a bit like how the school version of lisp makes people think real lisp code bases are all lists and recursion.


Funnily enough, it actually started from C++. And, Java is so large that it is simply meaningless to talk about a unified style -- sure, some Java EE behemoth will continue to run on a complicated Application Server for decades still, churning on its workload, but so will someone write a Micronaut microservice, or something for robotics, and these will all have distinct styles.

With that said, even the Java EE/Spring complicated word has been moving towards a less inheritance-based future.


Even in Ruby, IME, deep inheritance is fairly uncommon.

Wide inheritance through mixins is common, though.


HtmlElement. AST. Socket. Stream. Window. Closable. Runnable.


Your Point?

Long:

Please make your Point Understandable.


Examples of classes where inheritance is indispensable. I cannot imagine what kind of programming you are doing where that pattern never comes up.


There is nothing wrong with inheritance. But sure, there is usually something wrong with deep inheritance or using it, where better solutions exist.


No, there's a lot wrong with inheritance. It leads to all sorts of issues and while theoretically one can keep the tree 1 level deep, in practice it's too tempting to expand the hierarchy.

This is one of Java's big issues. The other is reference equality as a default, pretty horrible. Records help but records are also limited in when they can be used.


"in practice it's too tempting to expand the hierarchy."

Not anymore, if you got stuck, by doing that once too often. But 2-3 levels can be allright as well. It depends, what you are doing amd with whom.


> No, there's a lot wrong with inheritance.

No, there is a lot wrong with bad code. No need to blame the language or any programming concept.

Note that I say that for all programming languages, not just Java.


That's a difference between a dangerously unsafe tool and a good tool. By unsafe I mean providing enough of footguns to shoot yourself in the foot. Java has a community of people that indulge in teaching about inheritance as the first thing after classes in their "OOP" lessons. This ingrains the habit in beginners.


> By unsafe I mean providing enough of footguns to shoot yourself in the foot.

Just to play on the comparison: would you say that a gun is a "dangerously unsafe tool"? I would tend to think that guns have been very well optimized over time, and I don't know of a gun design that prevents me from shooting myself in the foot. Actually that would be a limitation of the gun.

But we all agree that people who use guns need to learn how to use them properly. Why doesn't this apply to programming languages? How did we as an industry end up in a place where it's considered the norm that developers don't really know what they are doing and need some kind of child safety in order to not hurt themselves?


Yes, guns are dangerously unsafe, they're designed to cause harm. It's hard to reconcile that goal with the need for safety. That said, guns do have safeties - guarded trigger, safety switch, drop safety, etc. [1].

The general principle is to design in as much safety as possible without compromising the intended purpose of the tool too much. The degree to which this is possible varies. Inherently complex and/or dangerous tools like guns, cars and aeroplanes require significant training before safe operation is possible. Most tools however can be made perfectly safe to use for anyone without any special training. Like plugs and outlets [2].

Programming languages are no different. The history of PL design is defined by the ever increasing restrictions placed on what languages let programmers do in the pursuit of safety & correctness.

But really, we humans, especially programmers, have no clue what we're doing and need all the help we can get.

[1]: https://en.wikipedia.org/wiki/Safety_(firearms)

[2]: https://youtu.be/139Q61ty4C0?t=42, https://youtu.be/139Q61ty4C0?t=105, https://youtu.be/139Q61ty4C0?t=205


It seems you conflate the tool and the education about the tool?

Why is Java itself bad, because some people (maybe) teach it wrong?

Also inheritance as a first lesson with OOP is not bad either, if the follow up is sound. But as far as I know, the concept composition > inheritance was already taught 15 years ago.


If we look at other modern programming languages, some don't even have inheritance. Lets take Rust for example. The language from the get go avoids the trap of deep inheritance hierarchies, by ... not having classes and inheritance! Instead it has structs and traits you can implement for the structs. Behavior separated from structure. They learned from the mistakes or design flaws of the past. Sure, Rust is not perfect, but this aspect about it I really appreciate. I am sure though, that someone somewhere will implement something resembling inheritance and create footguns anew.

Lets compare with Java. Java has forced everyone for decades to shoehorn everything into classes (sometimes an enum, sometimes an abstract class, whatever). Last time I checked it was still impossible to simply open a file, put a function (!) inside and be done. No, Java forces you to wrap that into a class or similar construct, as if a function in itself was not enough and not self-sufficient. There is a whole mindset behind this, that seems to come from an ideological "everything must be a class, because then I can instantiate and then I haz objects and can call methods". Other languages don't need classes to have objects.

Decades fast-forward. Java learns, that lambda expressions are a nice idea. Java will offer structs. Java learns, that lightweight processes are very neat to have. And despite all that, the old footguns still remain and could only be undone at significant cost, because of backward compatibility. This is where programming language design sins really rear their head. PHP suffers from the same problem. Horrible standard library, but cannot be fixed, unless you break backward compatibility.

On one hand you can state, that it is all on the programming, who is "holding it wrong" or needs more education. On the other hand, the programmer can choose a better designed tool, that doesn't cut their fingers every time they try to use it. Just because it is possible to do a good job with Java, that does not mean, that Java is a good tool for the job. It means you can only let the most experienced people work with the tool, instead of what we have now, every Billy knowing Java, writing classes and getting a kick out of inheriting from a super class.

Good teaching will avoid weighing things one should avoid disproportional. There is no good reason to teach inheritance early on. It should be a thing taught on the side, something one quickly glances at and says: "Yeah, that also exists, but lets not get into that much, as we will not need it much ..."

But in Java you probably can't avoid it for long, because you will want to make use of some library sooner or later and library authors might force you to make some class inheriting from their library's class, define a behavior and pass that in. But often that is not enough ... No no no, you need to pass in a factory for classes, that implement an interface, and their methods will implement the actual thing. I have seen this recently for logic of checking, whether a password is valid/acceptable. Why the heck do I need to implement a factory for that, when the actual task is simplest logic, checking whether the password has all required kinds of characters in it?

Usually this is completely overblown, because you want to pass in some behavior, that could be expressed as a simple function. I shouldn't need to create some brimborium. All I should be required to do should be to implement the logic in a function and pass that as an argument to the library.

The existing ecosystem forces its "OOP" on you. If you are not willing to throw out decades of ecosystem, which actually is the main advantage of the JVM, then you will need to deal with the cruft that has been created before.


Ok, you don't have to convince me that Java has many problems, as I intentionally avoided Java for some of those reasons for the last 15 years. But thanks for reminding me of some of them ..

But the concept of inheritance I like. And I use it succesful allmost daily. And like I said, I am aware of how you can use it in the wrong way. And my code surely is not perfect either, but the flaws I have, do not come from inheritance. Those parts are actually very clear, solid and stable. And still flexible.

And Rust is trendy I know. I have never used so far, so it was news to me, that they don't even have inheritance, but to convince me, that Rust has the superior concept, I would like to see it to be used as widely as inheritance languages first.


[..] a programming language designer should be responsible for the mistakes that are made by the programmers using the language.

It's very easy to persuade the customers of your language that everything that goes wrong is their fault and not yours. I rejected that...

- Tony Hoare


I feel like it often goes in the other extreme: "if a 2 years old cannot use it, it's too hard".

I am fine with requiring that professionals know their tools, even if I'm well aware that this is not the norm.


You don't like code reuse?


Love it, and love using plain ol' functions for it


Inheritance is not necessary for code reuse.

All you really need for that is named procedures. A way to combine modules and different files certainly helps too.

Type hierarchies can even hinder code reuse, because you can’t just pick the stuff you actually need.


That question seems blatantly disingenuous.


Composition > inheritance.


Please ELI5 what is wrong with inheritance and/or how Java have it wrong. Do we need to go back to a new object oriented undegraduate course? Genuinely asking.


"Prefer composition over inheritance" is actually a well-known mantra and appears in, for example, the Design Patterns book from 1994, which is basically the OOP bible. And this is within the OOP bubble, you won't even find inheritance outside of it.

https://en.wikipedia.org/wiki/Design_Patterns

Don't take undergraduate OOP courses seriously.


> a well-known mantra

which isn't an explanation, but just the same as the OOP mantra that was taught in undergraduate courses.


One reason is the unstable base class phenomenon. A some changes in the base class require you to constantly change all your derived classes.


"What OOP users claim" -> https://i.imgur.com/KrZVDsP.png


Inheritance is fine. It helps to avoid code duplication for logic that requires encapsulated data (private, protected fields).

The problem is what if you have a class that needs to derive behavior that's in two (or more) classes? Multi-inheritance is terrible because it becomes a nightmare of which class overrides which.

If there's a shallow level of inheritance (1-2 levels deep), then there's nothing wrong with it. Things like composition for most use cases has been common advice since forever.

What happened with java was that there was a massive movement for enterprise code that way over-complicated everything based on ideas that didn't pan out. There were all these auxiliary patterns, and ideas that people had to learn to be onboarded onto projects, and so many people poorly understood them that it led to even more spaghetti code, too many people that developed using dogma instead of common sense.


With compositions A uses B but B can never use A. With inheritance Child can use the Parent, but Parent will also call the Child (virtual methods) which in turn can call the Parent again etc.., so the code can become difficult to follow. It can become very complicated with multiple inheritance and multiple levels.


Such code would be difficult to follow regardless of whether you use inheritance or not. Sometimes, two pieces of code developed independently just need to interact very closely with each other.

Remember that OOP and inheritance to some extent came out of the need to develop GUI systems. Inheritance is still heavily used in GUI toolkits because it's a good fit for that problem space. You have graphs of objects that need to be treated at different levels of abstraction, and controls often need to customize (override) or implement some behavior that shouldn't itself be a part of the public API.

Attempts to get rid of inheritance and OOP in UIs end up looking like Compose or React. I found very quickly when working with these that pure composition just wasn't sufficient and these approaches have their own issues; problems that OOP trivially solves become difficult to impossible to solve cleanly without it.


I've certainly done that in the past, but not since I changed jobs ... 7 years ago. Probably not for several years before then.


Inheritance provides "is a" relationships between classes. At a time when people would spend months designing their software upfront, building big diagrams of classes, etc, this was not so bad. You'd have a very clean system design that maps directly to your class design.

The problem is when things change. A simple example - you build a classification of all life and it is built upon the idea that everything "is a" plant or animal. And then one day it turns out there are fungi. This is not a fun situation to deal with and inheritance makes it a lot harder because the "is a" relationship is driving a ton of your logic.

IDK I don't want to get to into it beyond that, many people have written quite a lot on the topic.


> you build a classification of all life and it is built upon the idea that everything "is a" plant or animal

With all due respect, this and similar examples are just plain wrong, and I really can't take anyone seriously when that example is used. The point of programming, and its abstractions is to help you complete a task, and make the implementation maintainable and easy to reason about. I think grabbing the "is a" part is fundamentally bad -- there is no point in creating a taxonomy in and of itself, this is no database for that data. Inheritance sometimes is the correct abstraction and while it is definitely overused, when it's correctly applied there isn't really another abstraction that would fit better. E.g. see a graphics library's Node class as the stereotypical correct application.


I think your post can be broken down into two main points.

1. That my point is bad for some reason

2. That inheritance is sometimes a great tool

We agree on (2). Inheritance is pretty amazing, even if I think that it's ultimately a terrible feature to build so ingrained into a language and to expand in power to such a degree.

As for (1), I don't really get your point. Abstractions help you complete a task - ok. Abstractions are to help you reason about stuff - ok. Something about "is a" being bad? None of that really explains why my example demonstrates the problems you run into when you try to build a classification of values using inheritance. But I also said that I wasn't going to really try to explain much, it's been written about plenty.


> This is not a fun situation to deal with and inheritance makes it a lot harder because the "is a" relationship is driving a ton of your logic.

Judicious OOP design allows others to change behavior based upon needs that perhaps the original coder never thought of.

I would not call most Java OOP design judicious. The control in Java is owned by the library writers and the language devs -- not the people using it.


What is a language with judicious oop design? Asking to know


I tend to be fond of Python OOP styles.


This problem is partly a type system limitation however.

In a more flexible type system with union types and other magical features, your example problem would be less of an issue.

However Java has an extremely limited type system so there is no middle ground between composition and inheritance. Once you choose one way, there is no “middle step” to migrate over.


Union types are very rare (scala, typescript are the notable ones I can think of that implement it), most other languages only have sum types (which includes java as well, see sealed interface/classes), where you have to create each unique set of types you want to use separately, wrapping each option into a marker type basically.


In cases of refactoring, inheritance seriously sucks.

But it is quite advantageous when, as you say, your model is well thought out and stable.


If you want to see how codebases work without inheritance in practice, I suggest checking out Go, which features polymorphism through interfaces, but not full-fledged inheritance


Prefer composition over inheritance has been a common mantra for at least a decade, if not more. Maybe you should undergo the last decade of development and training.

There will always be shitty devs, and since java is one of the biggest languages, it definitely has more than some ultra niche academic language no one uses. I don't think it is the fault of the language though, or if that somehow were a reason to choose a different language. Should a decent BMW driver sell their car, because plenty assholes buy that car also?


Just because everyone has been saying "composition over inheritance" doesn't mean that that's how things get done. Jump into any Java codebase and you're 99% likely to see inheritance used as one of the primary abstraction mechanisms.

> Should a decent BMW driver sell their car, because plenty assholes buy that car also?

A better analogy would be "Should you drive on the street where all of the shitty drivers do donuts and street races?"


> Prefer composition over inheritance has been a common mantra for at least a decade, if not more. Maybe you should undergo the last decade of development and training.

What are some other well known mantras? The null reference is a billion dollar mistake? Minimise mutability?

Maybe the language designers and library writers could catch up too.


Records and value/primitive classes are immutable, not to mention https://openjdk.org/jeps/401


Inheritance is a useful feature, using bad abstractions is a user error.

Nulls are an error in that no language feature solves them (though third-party tooling does), so far at least.


Yeah, I think this is the part that gets me. To be fair, I think well designed Java is flexible in exactly the right ways for an enterprise development. The trouble is that enterprises don't typically pay well enough to get people who are really good and the popular ways of building Java applications are not great.


> ideology built on top of the idea that inheritance is really good idea and belongs everywhere.

Those are two different ideas.


I used the word "and" to indicate that, yes.


I think a comma would make it less ambiguous.

Starting that single though with "The" indicates to me that you don't think those are two different ideas.

Compare:

    the idea that inheritance is really good idea and belongs everywhere.
With:

    the idea that inheritance is really good idea. and belongs everywhere.


I think that would actually be an erroneous comma?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: