Lombok is a useful crutch if you're writing lots of Java code on a day to day basis. However, given how easy it is to use Kotlin alongside Java I would question whether Lombok is the right solution to the problem.
Kotlin has data classes which auto generate sensible 'toString' and 'hashCode' methods which is a massive time saver.
Really, if you are considering introducing Kotlin to a Java project I'd recommend you just take the plunge. It's pretty much just a minor change to your pom file (if you're using Maven). Kotlin has Java interop as a primary feature and is flawless from my experience.
Unless you're using arrays. They went with "predictable" over "sensible" here.
> is flawless from my experience
As a heavy Kotlin user, I'd say it's far from flawless (not sure if you mean interop or the entirety of the language, but applies to both). I'm often creating and starring YouTrack issues. But yes, it's still a highly recommended alternative to Java.
Assuming it's for the JVM, you should definitely choose Kotlin for your next project. Most of the flaws I find are for advanced usage (e.g. interop, coroutines, etc) or tooling (e.g. gradle issues, dokka) or just general advanced language use. I don't keep a running list of issues (I made one back in the day [0] but not all of it is still applicable), but nothing would keep me away from the language. It made a lot of right decisions.
Jetbrains has a very good 4 weeks free course on Coursera https://www.coursera.org/learn/kotlin-for-java-developers.
We’ve beginning to introduce Kotlin in our code based, and also begin to use Kotlin Dsl on our new Gradle projects; for the moment, the experience is excellent.
Interop with Java is really good and the support from IntelliJ is also fine.
Kotlin is great. But go and try to persuade your company to bless another language for production.
It may be easy when your engineering org is 5 persons. It's much harder, genuinely harder, when it's 250 engineers, and an hour of downtime costs hundreds of thousands. Adoption of another language, even clearly superior and interoperable, becomes a large and costly undertaking.
So "just use Kotlin" is not always possible, and I'm thankful to Lombok's authors for their work.
I am not parent commenter, but your comment really struct the chord here.
I failed to convince 2 people (my mgr and team-lead) for our relatively-green-field project 2 years ago to use Kotlin... Thing is - same people killed an idea to use Lombok as well. So it is an uphill crawl no matter how "big" is the change - it is how comfortable people are with what you are proposing, and where they (and you) are in a command (aka responsibility-and-headache-if-something-new-goes-wrong) chain.
Scala support in IntelliJ is ok, but still not great. There are some Akka constructs IntelliJ underlines in red for years and JetBrains doesn't fix it. I've never seen something like that with Kotlin.
The hard truth is that Scala was not designed with tool support in mind and it probably never will be. For example hierarchical implicit conversions, while powerful, are terrible for tooling.
> Scala was not designed with tool support in mind and it probably never will be
You should pay attention to what's going on in the ecosystem. Dotty comes with LSP support. The Scala Center's main focus is tooling. JetBrains has been pretty committed and cooperating as of late. And several projects are building serious alternatives to SBT.
Intellij still can't figure out how to handle implicits. At this point i have an intuition about the errors that idea reports as errors but actually aren't'.
Tend to agree. But if you work in a team, there will be someone who just loves implicits and overloading and you won't be able to change his mind. Strong code style policies help, but they are really rare in real processes.
Your team can make a decision as a group to not allow defining operators in the team's codebase. As you mentioned you can then enforce that with scalastyle (set FieldNamesChecker and MethodNamesChecker regexes to ^[a-z][A-Za-z0-9_]*$). Is that not enough to handle the one person who loves overloading?
That time when my boss started using "implicit" on everything. We were even using a new "Functional Relational Mapping" framework from Lightbend/Typesafe called Slick which was a mess.
Kotlin has not flawless integration and some attention is required when writing libraries for Java consumption.
Additionally the JVM wasn't enough for JetBrains and code written for multi-platform has slightly different semantics (see Kotlin/Native and immutable data).
Kotlin is a good alternative on Android due to a frozen Java 8.
Against Java 12 not so much.
It is very hard to displace platform languages when others just feel like guests.
I would argue that Kotlin brings massive benefits over Java 12:
* Excellent null safety enforced by the type system
* Data classes reduce boilerplate and bug sources
* Extension methods
* Declaration site variance removes a lot of the ? extends T
* Everything being an expression (if, switch/when) is powerful
* Smart casts are useful, especially when combined with subtypes and when (switch)
* Removal of checked exceptions
* Inline functions, a good way to work around runtime type erasure
* Properties, no more getX or setX
None of these are the killer reason to go over to Kotlin or another JVM language, but all combined they make building software much more efficient. Java could get all of these, but they've tried to increase feature delivery speed multiple times by adjusting release schedule and failed so far.
I give it 5 years time for Kotlin to be having beers with Scala, Clojure, Groovy, Beanshell, Ceylon, XTend in terms of global JVM developer market share (I do not count Android as part of JVM market).
Specially since JetBrains decided that they not only want to go against Java, they are also positioning Kotlin agoainst JavaScript, TypeScript, WebAssembly languages, C, C++, Rust, .... Ergo choosing multiple fronts to focus on.
Which already has the consequence that Kotlin/Native code does not fully follow the same semantics.
Also regardless of what people in HN think, Eclipse still rules the enterprise and the Kotlin plugin is a bit meh playing catchup with latest features. Most IT departments won't sign off InteliJ licenses just because some devs think Kotlin is cool.
It is not language feature bullet points that make an eco-system, specially for those that don't own platforms.
Of those three (Kotlin, Scala, Groovy), Kotlin is by far the most pragmatic. Scala is nice, more powerful, but has a high cost when it comes to learning curve, complexity, and the library ecosystem. I'm not sure of the state of Groovy at this point after it got transferred to the apache foundation. Kotlin on the other hand strikes a nice balance in many areas.
Kotlin got a major push on Android, thanks to a stagnant Java 8 support.
With ChromeOS and Fuchsia on the game as well, it remains to be seen who will win Google's internal OS wars.
So far Android seems to be the one taking them all, though.
Outside Android I don't really see a major benefit switching whole teams away from Java, Eclipse/Netbeans and rewriting/wrapping libraries to be idiomatic Kotlin and such.
How do you define pragmatic? Apache Groovy is still under active development. We've actually started using it more for green-field development where I work.
Not the parent, but I prefer Groovy over other JVM languages because:
- supports both dynamic and static typing - it works just as well for bash level scripting as it does for full application development. For example, you can use it exactly like an interpreted language (no compilation needed).
- seamless Java integration. I know many JVM languages say that, but Groovy actually makes it one of its core features. It has a lower impedance mismatch than any other JVM language I've tried (and I try them all).
- partly because of the dynamic typing, it is able to support the most concise, powerful options in its syntax of any of the JVM languages. So you get more done with fewer, simpler lines of code
- because of the static typing and low impedance mismatch with Java, it can achieve very high performance. I was not able to get Scala to perform as well, for example, because of type conversions needed to invoke underlying Java library calls (unless you abandon idiomatic Scala).
The seamless integration is amazing. For a lot of existing Java code, it's as simple as renaming the file to .groovy. In addition to your list, some of our most used features are
- The amazing SQL library
- HTML builders (there are XML builders too)
- Literal collections (lists and maps)
> For a lot of existing Java code, it's as simple as renaming the file to .groovy
This causes the program to run in dynamically-typed mode, much sloooower, so why do it? You'd have to add @Static annotations everywhere to get anything approaching Java-like speeds. One of the Apache Groovy project managers has long since wanted to make Groovy statically compiled by default, but that's never going to happen because they're running an unofficial go-as-slow-as-possible in the official upcoming upgrade to Groovy 3.
I agree, it's as easy to tap into your build tool to build kotlin/groovy/scala alongside with java, as it is to tap into buildtool to process the annotations.
Annotation-based programming might seem cool now, but wait a few years.
Magic always seems cool when you add it to a project - hey, look - I don't have to do <tedious thing> anymore.
Then time goes on, and you (or your successor) opens up the project to track down a bug. Only there's no logical flow - things just seem to happen as if by magic.
Lombok strikes me as magic in these ways.
I actually had to go to the project home page to figure out what problem it solves:
The examples refer to a specific kind of boilerplate, namely the kind that comes from treating classes a dumb bags of properties. Getters directly returning private fields. toString methods, etc.
An alternative approach to Lombok would be to think about how the project ended up with so many dumb data classes. How could the project be refactored to eliminate them, for example?
Doing so would avoid the need for magic and produce a code base in which effect followed cause.
I liked to refer to the genre as MOP -- Magic Oriented Programming. Every ecosystem I've seen go down that route ends up bailing out. A good example of this would be Apple's Cocoa Bindings [1] on macOS that were killed with fire when the iOS APIs were defined, and KVO [2] which is flat out rejected at every company I've ever worked.
On the other hand, when built into the language, this kind of explicit but automatic code generation can be awesome (as in Rust's #[derive()] traits [3]) but I think the difference there is the compiler/language vendor agree to support it indefinitely.
> An alternative approach to Lombok would be to think about how the project ended up with so many dumb data classes.
Mapping entities to/from data transfer objects is a well established technique, as using dumb POJOs to drive serialzation/deserialization.
If you throw CQRS into the mix you can get a POJO/endpoint ratio that is greater than 1:1.
Do you want to write bug-prone boilerplate code for all those POJOs you are forced to write? Or do you prefer to simply add lombok to the project and add a single class annotation?
It's disingenuous to blindly criticize the extensive use of POJOs and DTOs as if it was code smell. In some applications it's quite the opposite. There is a ver good reason why lombok is very popular, as are other projects designed to complement it. In .NET Core land, where POCOs are used extensively, AutoMapper is also a must-have just because the need to handle POCOs without having to write all the boilerplate code is very real and pervasive.
Why bother with getters and setters for a POJO though if it’s all in a project you control? You can always make a property private later if you really need to add behavior to the fetcher.
Yes, but as a programmer when I'd start working on a new project, I would call it a Rails project, I've never heard of a Lombok project.
Sure, I've seen Spring/J2EE projects that used Lombok... some 10 years ago, but the idea seemed to have died down, maybe it's my own bubble.
Automagic is ok for a specific type of problem (Rails for webaps?), Lombok however reminds me a bit of the promises of AOP which failed to gain traction outside frameworks.
There are other highly opinionated frameworks/stacks that have mixed adoption in the java world -- jhipster seemed to gain some traction, but people seem to just use bare spring boot nowadays(which is quite opinionated)
I don't have absolutely anything against small projects, I'd love to work more on smaller projects, but 18000 lines of generated code is NOTHING, try to convince a medium team to use Lombok, you'll very soon end up with people loving or hating it, removing it for debugging purposes, creating their own generators and very soon it's a real mess.
But all that is just my experience, I'm not trying to dismiss Lombok or any approach, if it works for you, great!
You've must have heard about Spring projects. I'm yet to come across a Spring project that doesn't use lombok.
> Sure, I've seen Spring/J2EE projects that used Lombok... some 10 years ago, but the idea seemed to have died down, maybe it's my own bubble.
Perhaps it's my bubble as well but I'm still seeing Spring projects in production today that feature lombok extensively.
Not all organizations are willing to burn through cash to jump to the flavor of the month every couple of months. If you deployed a system and the system works, you need to discover a very good reason to throw a revolution and start from scratch with a new tech stack.
I recall my limited time with Ruby and Rails being the really frustrating, seconded probably only by Tornado (either misleading error messages or no error messages whatsoever, requiring me to get dirty with the debugger for the most minor of things).
RubyMine had no idea about more than half of the Ruby code being written in the project.
I couldn't grasp the concept of symbols in Ruby reliably enough. E.g. methods used either symbols or strings - I couldn't discern a pattern for which to be used when.
This was the magic part of it - things seemed to work seamlessly but when they wouldn't, I had no idea where should I even begin to look.
Also every file had to be annotated for the strings to be treated as immutable. No ways to set it globally, project-wide.
It felt like a mess.
On the plus side, there were gems.
I grew to really hate annotation-based magic when I worked on a project that used Jackson for JSON serialization. Not only does it circumvent the type system entirely, it also makes errors incredibly hard to track down by virtue of the fact that there are next to no static constraints on how annotations are used. Something as simple as adding a serializable field to an interface or object often ended up requiring a ridiculous amount of trial and error to get right. I think annotations are valuable if used judiciously, but unfettered annotation-based metaprogramming only ever leads to pain in my experience.
Java is not the most powerful language, yet it has huge adoption; the main reason is the number of quite OK and very powerful libraries.
Sure, it's very easy to add Jackson, but people severly underestimate the problem of JSON <-> POJO mapping(multiple ways of doing it and different programmers wanting to do it in different ways). So when you start it's very easy to just add some Jackson annotations and you think it's going to be smooth sailing from now on; add some complexity down the road and you just use some stackoverflow recipes to do the thing I want (without any in-depth Jackson knowledge) and it still is ok-ish; add some more complexity down the road and then you feel the frustration of using Jackson.
Add in some web annotations, security annotations, ORM annotations, logging, test annotations and so on and it's the same story -- you are in fact doing something very complex and when you depart from the happy path of using the functionality via annotations, it gets quite frustrating.
I really don't know anything "better"(time wise) to accomplish the same thing though.
> Annotation-based programming might seem cool now, but wait a few years.
I even have this experience on my own projects and with much shorter span than a year.
When I go back to a code base I worked just 3-4 month ago -- I have very hard time following Dagger injections.
Braking one of the Dagger providers (I use dagger 2) creates 10s and 10s of compilation errors.
So I have been slowly removing dagger from my code bases.
As I just cannot follow it, I do not know it well enough either.
I stopped using Lombok for same reason, although I found Lombok is much more intuitive.
I thought about going to Kotlin, but it was just too much of cognitive overload (in this particular instance both for backend and android UI).
Instead, I am finding the use of rxjava and friends a lot more intuitive and helpful for the type of things I need help with (interconnecting components on different threads, with different lifecycle and data structure semantics, with lots of asynchronous behavior options).
And the same rx-<> library is available to many other languages, therefore, the skill I am building up is sort of 'cross-language'.
Thats fairly funny, since I find Rx to be the very complicated thing if used extensively in a project and hard to reason about memory usage and other things when trying to dig into problems.
On top of that it creates huge ass stack traces that makes finding your code difficult unless your debugger can filter out specific packages:
> Then time goes on, and you (or your successor) opens up the project to track down a bug. Only there's no logical flow - things just seem to happen as if by magic.
This.
I use a different but similar analogy borrowing from DnD.
- Java and especially annotations need wisdom (WIS)
- E.g., Clojure needs intelligence (INT)
This holds even for syntax where homoiconicity requires just so much less "experience".
+1, I would rather have the cruft than have magic annotations. These dumb data classes also tend to proliferate when you are using other annotation heavy magic libraries like hibernate/spring. If you are having a dumb data class that is not a class that is meant to be serialized on disk (protobuf/avro etc.), then you are likely doing something wrong. See if you can avoid it.
Annotation based magic has always felt like a hack to me. Surely there is a way to solve these problems within the constraints of the language. It adds a layer of ambiguity to what code any class actually contains at run time and that is never ideal.
Spring boot with Lombok has been my go to framework for a few years now. Annotation-based programming seems to get a lot of negative attention here on HN, and it definitely is not perfect but there is just no other framework that (in my opinion) comes close in terms of productivity and workflow.
Currently I'm building my SaaS + Selfhosted software licensing product with this Spring Boot + Lombok + Postgresql combo and Annotations are helping me out for :
* @Bean, @Autowired, @Profile,... for configuring dependency injection with multiple profiles (saas vs selfhosted)
* @Entity, @Table, @Repository, @Enumerated, @Embedded for mapping my classes to Database tables
* @Api, @ApiOperation, @ApiParam,... for generating Swagger documentation for my endpoints
* @GetMapping, @PostMapping, @PreAuthorize for configuring and authorizing my endpoints
* Lombok @Data, @Value, @Builder for tedious boilerplate generation
* @NotNull, @NotBlank, @Email, @PhoneNumber, @Pattern(regex), @Length(max=12),... for validation of form input, api params and database models.
* @JsonProperty, @JsonSerialize, @JsonIgnore for configuring json deserializing for my DTO's
* @Value("${my.config.key}") for decoupling configuration from my code and injecting values at runtime
I probably forgot a few more use cases. All of these are pretty straightforward, are defined alongside the code they are modifying or interacting with and would lead me to a lot of lost time and a lot more code if I were to implement them myself, or configure them by hand "in real java code".
While it is great while you are trying to get your product to market quickly, I would highly recommend getting rid of all these annotations as soon as you can. Preferably before your team grows to 5+ people or complexity grows beyond around 100k lines of code. It's hard to explain this to people from non-Java background, but the time writing non-annotation code will save you in debugging and maintainability is well worth it beyond that point.
I can see that the dependency injection annotations can be complex for people not experienced with the whole Spring/IoC/DI setup. I have some rules and general guidelines I use for keeping them in check which I can explain in about 10minutes (and have done to team members in a few companies)
Honestly, I prefer to debug with annotations and a debugger over a manually coded configuration class with hundreds of lines of wiring components together.
And for removing the other annotations: Why should I decouple apidocs from my code? They are litterally annotating the source code so it makes sense to have them close.
Stuff like the validators and JSON properties and configuration also makes sense to have them close to the fields that they are about instead of in a separate JsonSerializer class or whatever.
I use the elements of programming from SICP[1] as a litmus test for this kind of frameworks.
The main issue with these annotation hacks is that they fail in the area of “means of combination” and “means of abstraction”.
For example if you have:
@X
@Y
class Something{}
You cannot tell if X & Y can be combined (or how it’s combined), or you cannot easily create an abstraction Z with X and Y (you may be able to do that but it requires framework support and usually is painfully impractical)
It seems to be a small issue compared to the added value, but to me is not.
As the code base grows the maintenance and debugging issues grow too... in the end probably is better to use Kotlin or Scala.
I agree, but it's helpful that the things Lombok abstracts away are very idiomatic and straightforward. I would never want to use it to take on anything more complicated.
My main issue with Java is all the annotation based programming. Some of these are nice and can make the easy case super easy but if you need go even slightly off the easy path you seem to quickly loose all the time gained on the easy path.
> My main issue with Java is all the annotation based programming.
That seems like an easy problem to solve. Don't use annotations.
Snark aside, it can be a bit of a tricky balancing act. When there are more annotations than actual code, that's a code smell in my book and something needs to change. Separating concerns usually does the trick. I'm heavy on code generation, so anything that's boring enough to benefit from oodles of annotations will probably end up getting generated based on a 2 line config file or something.
That's a fair point. IMO it's easy to mix a whole array of concerns with annotations, much more so than what you'd likely do in 'normal' code. Some annotations are really compiler instructions ("don't print these warnings"), some influece the build process, some are AOP entry points, some add runtime behaviour etc, some are security related etc. I guess I like things to be simple.
Yep. I love data classes for the simplicity and utility but pretty soon there are more annotations than java code. In Java I prefer Immutables over Lombok but once you add Jackson annotations, customize the Immutables generated code (@Value.Immutable, @Value.Parameter, etc), add some javax.validation (@NotNull, @NotEmpty) it gets very noisy very quickly. And yet it is still better than writing mutable bean classes by hand IMHO.
Switching to / mixing in Kotlin is worth it just for the data classes at this point (and you get some type-level nullability and `val` instead of `final var` to boot).
I'd also prefer immutables.org over lombok, because it cleverly gets by without changing the way sourcecode is turned into bytecode. All it ever does is generate additional class files that add the boring stuff. If lombok says "we can have all this awesome stuff if we break the rules a bit", immutables.org answers "we can have almost as much of we don't".
I would be nice for any language that supported annotations(TypeScript, C#, python, etc) if we could somehow annotate the same code in different files while maintaining a single source of property truth.. A combination of interfaces and class annotations for combining them could work..
I don't know if this is what you mean, but Aspect Oriented Programming was a hot topic for a very short period in the early 2000's, especially in the enterprise Java community. Ultimately it didn't seem to find many use cases and the buzz died down pretty quick.
It's not exactly a new thing though, it's always been possible in a variety of ways, from build steps to pre-processor macros. Annotations mostly just do what macros or code generators have always done.
Reading this just reminds me of all the terrible things about Java. The lack of reasonable default string representations, comparison and hashing methods, etc. are all glaring mistakes in the language design that have wasted huge amounts of time for millions of programmers. It's as though Java programmers are so deep in the grips of Stockholm Syndrome that ordinary, sensible behaviour seems amazing and "cool".
This is similar to the situation with design patterns. When design patterns came along, Java folks talked about them as though they were a wonderful new invention. But they only exist because Java is so clumsy that it needed crutches to do things that had been easy and natural in other languages for years; someone merely came along and gave the crutches names.
Which design patterns are you thinking of and which languages had them? The original gang of four Design Patterns book is filled with C++ examples, most likely because almost nobody was using Java in 1995 outside of a few people at Sun.
More generally comparison and hashing methods seem like small potatoes compared to locking primitives, portability, and a large, well-documented class library that have made Java ideal for a very large fraction of business applications. There are many things to criticize in Java but the points you list don't seem to be especially important compared, say, to the relatively steep learning curve/complexity vs languages like modern Javascript.
I'm not saying Java doesn't have other strengths. It certainly does! It also has stupid weaknesses.
Many of the popular design patterns are simply lambda. If you have first-class functions, they are trivial. But Java doesn't have first-class functions; instead you have to write a giant thing that looks like a class, and because that feels like a lot of work, it seems to deserve a fancy name like Observer or Strategy. But it's all fabricated work; the only reason you have to do it is that the language has this stupid weakness.
It's as though you were working in a language that didn't have lists. And one day you realize, hey, it's useful to be able to manipulate variable-length collections of things. So you look at a problem and say "Aha, this is exactly the place that we should use a Collection design pattern" and you go write the entire implementation of lists from scratch and feel satisfied that you did a good day's work. You tell all the programmers you know that there is this great design pattern called a "Collection" that they should all start using. You write books about it. You give workshops. You get really good at writing list implementations, because every time you need one you implement a new list from scratch.
When you tell a Python programmer this, she looks at you like you have lost your mind, writes a function that takes a list, solves the problem in 5 minutes, and gets on with her day.
C++ circa 1995 was a lot more like Java with manual memory management, really. Templates were a rare sight, STL was not yet a thing, and it was still considered an impure OO language rather than multi-paradigm.
These were also closer to Java libraries of late 90s in design. Even ATL mostly used templates to optimize (using the curiously recurring template pattern, usually).
Qt was also born in that era, and look at how strikingly similar its standard library is to Java's in many cases, and how unlike STL and Boost.
> When design patterns came along, Java folks talked about them as though they
were a wonderful new invention. But they only exist because Java is so
clumsy that it needed crutches to do things that had been easy and natural
in other languages for years; someone merely came along and gave the
crutches names.
Design patterns are a useful way of thinking about any sort of design, not
even just in programming. Novels and paintings have them, too, so does your
favourite language. There will always emerge from experience particular ways
of using your medium, and having names for those patterns can actually be
pretty useful for thinking about them and structuring them into a good design
(like how SICP talks about knowing a spirit's name gives you power over it).
You can't avoid using design patterns; someone just might not have come
along and named them yet, or you might not be aware of them.
The GoF book itself actually discusses this in the first chapter a bit, here's
a relevant part:
Point of view affects one's interpretation of what is and isn't a pattern. One
person's pattern can be another person's primitive building block. For this
book we have concentrated on patterns at a certain level of abstraction. [...]
Our patterns assume Smalltalk/C++-level language features, and that choice
determines what can and cannot be implemented easily. If we assumed procedural
languages, we might have included design patterns called "Inheritance,"
"Encapsulation," and "Polymorphism." Similarly, some of our patterns are
supported directly by the less common object-oriented languages. CLOS has
multi-methods, for example, which lessen the need for a pattern such as
Visitor.
Not to mention languages like golang that decided not to learn from previous mistakes (like the billion dollar mistake by including null in the language)
You either like strongly typed object oriented langs or you don't. If you do, then a bit of verbosity is really nothing. If there are actual lang improvements beyond saving me a few characters, I'll take them.
> You either like strongly typed object oriented langs or you don't.
Sorry, but this argument doesn't pass muster anymore, when there are abundant examples of strongly typed object oriented langs which don't exhibit these flaws. C# avoids getter and setter boilerplate with `get` and `set`. Kotlin and Swift, unlike Java, actually are strongly typed, because objects aren't implicitly nullable. And the many conveniences afforded by modern language features add up to improved productivity, provided the programmer using the language doesn't rigidly refuse to learn to use them.
It's about having non-nullables. It's about having value types. It's about controlling mutability. It's about the evils of (especially mutable) statics and life-before main. It's about having closures (at least recent Javas have them!)
None of this goes against strong typing or object orientation. Java just is... not so great. Of course, everything about this is hindsight: Java was also a revolution: generics (slightly flawed and bolted afterwards, but anyways), garbage collection, memory safety, JIT in an usable package.
Java has shown it's age and some people are aware of it.
I don't think that any of Java's warts are related to liking strongly-typed object oriented languages. I think the standard library is Java's biggest problem (read through the code that's been around since the 1.0 days, it's AMAZING in the way that a car crash is amazing). The community has done a good job building a better standard library (back when I did Java it was Guava, maybe that's not the state of the art anymore), and that helped a lot.
All in all, I am not a fan of OO anymore. I like the computer science principles behind it, but practical programs never use them. They always get Liskov Substitution backwards. People always use subclasses for cases like "give me the superclass, but with these conditions" which is the exact opposite of what you're "supposed" to do. Problem is making your subclass less restrictive is largely useless, what people really want is copy-paste without having to maintain two copies of the code. So there is some mismatch to what programmers and computer scientists call classes, and the result is that you get a mess. I'm not sure that's Java's fault or its largest problem, but it isn't helping.
The difference is more in a "batteries included" sense - Python values developer productivity and gives you what you need, similarly Haskell which is on the opposite end of a Dynamic vs Static spectrum, while Java was traditionally more oriented towards stability and predictability and partly attached to the old-school cool of potato programming.
I am sorry to disagree but if you think that the iterator or visitor pattern or the state pattern or abstract factories are only bound to Java than you have a very low skills/understanding of those patterns and you really don't understand their importance and/or applicability.
I think it's a cultural thing. Other languages don't name things that seem obvious, like iterators or filters. Consider something like filepath.Walk in go; you give it a function that gets called for every file and directory in the root of your choosing. They don't call it a visitor, though, they call it a "walk function". It's just as clear, it's just as much of a design pattern, but they don't make a big deal about it.
Meanwhile, I'm sure in Java that you have to implement some class that is the visitor, which means you have to create a new file, and probably install some VisitorUtils for making file visitors easier to implement, ... that is where it's gone off the rails. (But I am perfectly willing to believe that the exact same API exists somewhere in Java too, and most people browsing directory trees don't have 13 classes to print the names of the files in a directory. But at the same time, I feel like there are pressures pushing you in that direction. That's where the complaints come from.)
> Consider something like filepath.Walk in go; you give it a function that gets called for every file and directory in the root of your choosing. They don't call it a visitor, though, they call it a "walk function".
The ironic thing is that golang doesn't have an even better construct for this kind of pattern, namely pattern matching.
The majority of design patterns only exist because of the limitations of single dispatch. Java is far from the only single dispatch object system, but his criticism is still valid.
>When design patterns came along, Java folks talked about them as though they were a wonderful new invention.
Your history is wrong. Design patterns became popular with the release of the GoF book, which was written using C++ and was published in 1994 -- before Java was even released.
The remark about design patterns is not accurate. Design patterns did not originate with Java and aren't specific to that language. The classic book "Design Patterns: Elements of Reusable Object-Oriented Software", by the 'Gang of Four', was written in and about C++. It was published in 1994 and predates Java.
"Design Patterns", both as a book and a concept, is about the recurring patterns found in object-oriented software; it is about the problems that lead to those patterns, and the solutions to them. Although the book itself is rather dated, the patterns described in it remain relevant to most modern languages. Some of those patterns include: Builder, Singleton, Proxy, Decorator, Iterator, Visitor, Prototype (c.f. JavaScript prototypes), Observer, Chain of Responsibility, and more.
Many languages employ these patterns in the design of their standard library. E.g. the Iterator pattern shows up in a large number of language libraries (Python, Ruby, JavaScript, Swift, Rust).
If you search for "Design Patterns in X", for programming language X, you can find quite a lot of literature about how to employ them in any language. For example, see "Python 3 Patterns, Recipes and Idioms" [1], which describes how to employ many design patterns in Python, or "Design Patterns in Ruby" [2]. A lot of modern literature for many programming languages relies on these patterns either implicitly or explicitly.
Most of these patterns do not exist because of language weaknesses, although I would agree that this is true for some of them, such as the Visitor Pattern. Use of the Visitor Pattern is often better replaced with pattern matching in programming languages that have it. However, this isn't true for all of the patterns (e.g. Chain of Responsibility, Facade, Proxy); and even the Visitor Pattern remains relevant to many languages, especially dynamic languages that do not support pattern matching.
Many of these structural concepts were not new, in the sense that they did not originate with the book; but the GoF took the time to assign names, characterize them, and describe the trade-offs involved in using them.
"about the recurring patterns found in object-oriented software"
... in statically typed languages of that time (like C++ and to some extent Java). At least some patterns solve problems that are not (that kind of) a problem in, e.g., dynamically typed languages.
> This is similar to the situation with design patterns. When design patterns came along, Java folks talked about them as though they were a wonderful new invention.
They are/were a bit over-hyped and sometimes overused inside the Java community, that's true.
> But they only exist because Java is so clumsy that it needed crutches to do things that had been easy and natural in other languages for years;
Sure, some design patterns work around language deficiencies or are that commonly used to justify including them into the language. Java sadly did not do this enough in the past, that's right. But if you look through the list of design patters only a minority fall into this category.
> someone merely came along and gave the crutches names.
That's basically the whole point of design patterns. It's assigning a name to things already widely used to improve communication between developers and to provide learners a point to look up a concept.
You have to look at it from a time when C++ was the mainstream for large scale projects. Java is certainly a god-sent gift to software salaryman all over the world.
I'm interested in the downsides of using Lombok, since the article seems to only focus on its positives and makes it seem like I should download it and start using it right now.
Is there anyone here with Lombok experience that wants to share any issues they've run into while using it? All I can think of right now is the fact that the source code isn't compatible with Java.
> Project Lombok hooks itself into the compilation process as an annotation processor. But Lombok is not your normal annotation processor... The trick is that Lombok modifies the AST. It turns out that changes made to the AST in the Annotation Processing phase will be visible to the Analyse and Generate phase. Thus, changing the AST will change the generated class file.
That said, we never encountered any Lombok-related problems when running services in the cloud or locally. And the Lombok plugin for IntelliJ is good enough in that the auto-complete will "see" the Lombok-modified version of the file. For example, using the @Value annotation creates an immutable value type, which among other things a) makes every field private and final, and b) generates a getter method for each now-private field. With the Lombok plugin, IntelliJ auto-complete will a) not auto-complete the composed fields which are now private, and b) auto-complete the generated getter methods.
I highly recommend looking past the voodoo bytecode manipulation and using Lombok. The @Value annotation alone is worth the price of admission and made me a more productive programmer.
Oops! My mistake – you are absolutely correct. Lombok was just so pervasively used in our services, I assumed it came included as part of the Dropwizard toolbox.
Yeah, I use it at a financial services client. A previous architect chose it because it was a pet project of his (he contributed to Delombok).
The premise is fine...I have no problem with it. But the default generation of @EqualsAndHashcode literally pulls in the WORLD to generate the output.
The real world scenario we had was this. Lots of POJOs were created, many were simply but a non-trivial number were NOT. Those POJOs could have dozens and dozens of fields. And if you have a key abstraction with say, 86 fields, things get interesting.
Suppose you don't use @EqualsAndHashCode on one of these POJOS with lots of fields, ALL 86 fields are included in the default equals and hashCode methods. They didn't realize this, or didn't care, and as a result, had some serious performance issues because trying to run hashCode on insert to a map when you're hashing 86 fields together might actually take some time inserting 100,000 records... ugh
So in short, it's OK and useful, but you have to understand the side effects of everything to know if it's the right thing for you.
SIDE NOTE: A POJO with 86 fields can be common in financial services when you are representing various kinds of financial trades where gazillions of things are tracked on them...interest rates of note, ratings, security characteristics, etc. That in and of itself isn't necessarily poor design, although these choices predated me at this company.
I have a /really/ hard time believing you wouldn't factor out a couple of POJO's from those properties that belong together and include them to compose your classes. Sounds more like sloppy data modeling.
I dislike Lombok. One reason I dislike it is that it makes IDE navigation, even with a required Lombok plugin, more difficult. A common task in a Java IDE is finding usages. It's harder with Lombok. Generally it's just more friction than plain Java code.
I also don't like that the generated code is not checked in or visible. It makes code review harder. In theory you can have magic change to all Lombok classes just by upgrading the library.
Also it doesn't pass the cost/benefit test for me. Adding Lombok adds complexity to your code, build system, and IDE. What do you get? Slightly shorter classes? Less characters? Most of this code can be generated by the IDE.
I'll give Lombok one win. It will keep equals and hashcode up to date if you add properties. That's a pretty common error.
As others have said, Kotlin is the best alternative. But even without Kotlin I skip Lombok happily.
If you check in generated code, you have to prevent anyone from editing it (so it doesn't become something you have to start testing and reviewing) and include some kind of summary so nobody wastes time looking at it.
The downside is that you'll need to install it into your IDE. And again when you upgrade it. I guess the version needs to loosely match the one your projects use too. That might not sound like a lot but some colleagues hate that overhead.
I love it though, think I've been using it for at least 8 years now in just about every Java project.
> The downside is that you'll need to install it into your IDE. And again when you upgrade it. I guess the version needs to loosely match the one your projects use too.
If you work on a lot of different projects with different versions of Lombok, do you have to have multiple versions of the IDE plugin installed / is that even possible?
The @Builder annotation has some odd behavior if you want to add default values (it just hardcodes them and you cannot override them!) or extra builder methods.
Actually, anything involving default values seems to be very brittle and difficult to work with -- especially when deserializing objects from JSON.
The @Wither annotation results in methods that do not always make a copy, and sometimes use == instead of .equals when comparing class members. If you call one of the Wither methods and assume you have a copy of the original object, and then you modify that "copy", you might have just created a very subtle bug.
If you are using IntelliJ you can use the Refactor > Delombok menu option to show you the code that Lombok generates. I've been told that does not actually invoke the same code that the annotation processor invokes at compile time, so the results of Delombok might be misleading.
Yeah, but what if I want to build an object with @Value on it, which makes all variables final, and I want to specify a default value to be used only if the user does not provide one to the builder? That's not possible.
A builder is just a fancy alternative to a constructor. It is possible to set the value of a final field in a constructor, and it is also possible to have another constructor in the same object that sets a default value for that field. I expect the same flexibility from a builder.
For example:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public Person() {
this("Bob");
}
}
Now you can get immutable Person objects:
Person p1 = new Person();
Person p2 = new Person("some other name");
But if you do this with Lombok:
@Value
@Builder
public class Person {
@Builder.Default("Bob")
private String name;
}
You cannot do this:
Person p = Person.builder().name("Some other name").build();
The name is stuck at the Builder.Default value. In that sense it is not a default, rather it is the only possible value. A default is supposed to be something that can be overridden.
I could use @Data instead of @Value but then the resulting Person objects would be mutable which I don't want.
One of the issues is that it's tied quite heavily to the Java compiler, so you may need to wait until Lombok supports a new Java version before you can use it.
Also as it is compiler magic, it can be a bit confusing to developers who haven't used it before.
I've heard it makes Kotlin integration a pain also if you are ever planning on that. If your Kotlin code depends on Java lombok classes, the compiler fails. Most people probably don't have this concern, but IMHO a mixed Kotlin-Java project is better than any of the current 'data class' libraries available for Java (Lombok, Immutables, Joda-Beans, AutoValue) ... ymmv, and I'm eager to see what comes of the "data classes in java proposal".
You get a more complex (and slower) build. Poorer editor support. It may be hard to upgrade Java version (ie. going from 8 to 11). More string for developers to hang in. I wouldn't use it.
The only downside I've come across was test coverage tools like jacoco can have problems with the auto-generated code (complaining about your tests not covering enough branches of an auto-generated hashCode() implementation, for example). However it seems new-ish versions of jacoco seem to have built support for it.
Just recently removed Lombok from one of our projects.
1. To use it you have to use plugin for each IDE. If project not frequently used, spending time for each developer here doesn't make any sense.
2. Another reason was, person who used Lombok abused all OOP principles. Project became a book of anti-patterns.
It’s ok to let languages die; we’re just barely scraping out of the infancy of programming. Languages like Java and C++ which have carried us a long way need to evolve into new and better languages that incorporate lessons learned and the next wave of research.
We also know from Google’s paper on software practices that software naturally gets rewritten over time, at a cadence that makes it acceptable to switch languages. So there is really no reason not to have a plan for your business to migrate languages periodically.
Unfortunately the industry seems to be stuck in a rut, and we’re forced to retrofit ancient compilers like horse drawn buggies with plate armor and machine guns on them.
I see the opposite. I see an industry all too willing to throw away years of hard earned knowledge, experience and purpose built tools in order to chase the shiny tools the popular kids are using.
New tooling often follows a commom cycle of being light weight because they ditched the stuff that looked unecessary. Then slowly a whole ecosystem of plugins and libraries springs up to rebuild the missing tools. Like the bottom bracket tool in a bicycle tool box. I have never used one, but when I need one no other tool would work.
I do truly believe that there is mastery in advanced, long lived tooling and efficiency may not lie with a new language but in truly understanding a comprehensive set of ordinary tools.
Languages also tend to be just the building blocks for the real tools, which are the frameworks and libraries built with them. So I hesitate to throw away my hard earned knowledge only to re-learn another artisan MVC framework written in a prettier language.
I concede that the parade must go on though, and ultimately I have to pay the bills. If precompiled serverside React is where the money is then... so be it.
> an industry all too willing to throw away years of hard earned knowledge, experience and purpose built tools in order to chase the shiny tools the popular kids are using.
Why do you think people in the industry are doing this? There must be multiple factors behind this.
My take on it is that it boils down to the profession being so young. Since it's so young we have a lot of young developers ( < 10 years experience). Programming is an inherently complex task [0]. Couple that with Not-Invented-Here and favoritism of barely working solutions (meaning by businesses: [1]) and as a result you get extremely fluctuating stacks. Every piece of code written by others was never written with care [2] - that is the assumption at least because more often that not it's true. Everyone in their own right thinks that only their code works well and has some special properties but in the end you face the exact same problems: problems of design rather than implementation.
[1] https://www.cs.utexas.edu/users/EWD/transcriptions/EWD10xx/E...: the business community, which, having been sold to the idea that computers would make life easier, is mentally unprepared to accept that they only solve the easier problems at the price of creating much harder ones
On the other hand I am impressed at how open industry has been to new languages since 2010. At the risk of dating myself slightly in the 2000 - 2006 era proposing a completely new language to solve a business problem was a much more radical idea. This was the era where you could get a lot of street cred and tech envy from hackers by just picking a non mainstream language (Examples - Viaweb using scheme, Naughty dog using lisp, Jane Street using Ocaml, ITA software using lisp). On an optimistic note I think there has never been a better time to ship new tech stacks, users are open to change and companies are fairly open to trying new ideas out.
Java the language is cool. What the enterprise has done with it is not cool. Writing apps with layer upon layer upon layer upon layer of abstraction is ... self defeating. Then they'll holler, we need to rewrite it! All in the name of finding that one true architecture that can handle any business CR. Blech.
How do you feel about Spring? I'm a fan of Java itself as well, but find myself conflicted whenever I use Spring/Boot. It's very nice when it works. But when the magic formula isn't recited correctly....you get frustrated and wind up visiting HN to forget about how unpleasant it is.
Spring is obsessed with using anything but actual Java code to express business logic in Java programs.
XML bean configuration files. Annotations. Implicit code generation. Property and YAML files. Looking for files and resources in "default" locations. Executing code at runtime as the result of adding a dependency, without it ever being explicitly called anywhere.
The result is, you can never really predict what the hell your program is going to do when its running in production. Certainly not by carefully reading and inspecting the Java code you write.
My experience doing Web development in Java started with joining a team that was ramping up a project on WebSphere 5.1, alongside a migration from BEA WebLogic.
I went through a couple of WebSphere, JBoss and TomcatEE versions up to JEE 6, was the technical lead for an in-house JSF framework built on top of RichFaces, and on very last JEE project used PrimeFaces.
All the employers that I worked for, mostly used JEE stacks for their Java projects. Spring was the exception, rather than the norm.
Additionally, even if a bit slowly, many of the Spring improvements ended up in JEE and Spring also supports the JSR related to JEE anyway.
Nowadays I live in the .NET world, and the Java teams next door are happily delivering JEE based solutions.
So if I was suddenly asked to architect a Java based solution, naturally it would be JEE based.
>What the enterprise has done with it is not cool. Writing apps with layer upon layer upon layer upon layer of abstraction is ... self defeating. Then they'll holler, we need to rewrite it! All in the name of finding that one true architecture that can handle any business CR. Blech.
Those decisions were less to do with personal freedom or program simplicity, and more to do with the realities of large scale enterprise development. You end up having to adopt patterns that make individual programmers replaceable, and allow for a standardized pool of candidates with experience in $X framework. The reality is that most companies building Java software just need to be able to hire an average Java programmer to show up every day and manage their huge tangle of business logic.
Java made some fundamental design decisions at its inception which look like fairly bad ones from today's perspective. It also introduced flawed versions of features like generics later. It also has some pretty poorly designed core libraries. What about it do you consider cool? I find it hard to think of anything I'd want to take from Java if designing a new language today. Even its bad design decisions have mostly been done better by other later languages like C#.
how common was it for languages to ship with robust IO, collections, and threading libraries? It might be that a lot of the good ideas in Java have become so commonplace you don't recognize them as "from Java".
(Of course, there is very little new under the sun so I'm not claiming Java was the first to do anything in particular)
Java not only adopted a bad model of concurrency / parallelism, it enshrined it in the language in the form of language level synchronization / locking. True, it wasn't as widely appreciated at the time that it was a bad model but there were better alternatives out there even back then. The focus on a well defined memory model in the presence of parallelism is one thing Java can take some credit for but it is to some degree a consequence of the sub-optimal threading/locking model they adopted at the language level.
The original collections library in Java was poor due to lack of generics. Again, there were better models out there and some of them were even in fairly wide use at the time (C++). Java is only recently catching up to better models for collections with streams that many other languages have been doing in some form for a long time.
I don't have much of an opinion on Java IO or how it compared to contemporary alternatives but most languages have pretty robust support for basic IO in their standard libraries. What about Java do you think was particularly good for the time?
One thing that I think Java deserves some credit for popularizing is reflection, although the Java model of dynamic reflection isn't what I'd pick if designing a language from scratch today.
Some languages do at least one thing particularly well, setting the standard in that regard. Some introduce or popularize new concepts that slowly see adoption elsewhere. Several make you think about programming in a different way. Some just have an overall elegance, unity, consistency or simplicity of design that make the whole greater than the sum of the parts.
Languages that check at least one of those boxes for me include C, C++, Lisp, F#, Haskell, Lua, Python, Elm, APL, SQL, ISPC and Rust (I don't claim to be an expert in all or even most of those languages). Other languages that seem interesting / different but I know almost nothing about include Erlang, Clojure, Smalltalk, Prolog, Forth, Coq, Idris and Kotlin. By contrast I've not really seen anything in Java that checks any of those boxes and isn't done better elsewhere (admittedly sometimes by languages that came later like C#).
Java popularised GC. GC wasn't taken seriously as an option for high performance code before Java, and it altered the mindset of the majority of programmers globally.
Java's biggest mistake IMO is checked exceptions.
Java is more notable for the things it left out than what it added. It was designed for success and achieved more than most of the languages you list.
I don't love Java. However there are many cases in which it's still the best option.
I'd argue that GC still isn't taken seriously as an option for high performance code by people who actually write high performance code and Java is not the language of choice when performance is a primary goal. The lack of value types hurts it there even compared to other similar GC languages like C#.
Tell that to the high-frequency-trader crowd, they almost use java exclusivly, because even 1ms equals a few thousands to them, correctness is apparently worth it for them.
My understanding is that C++ is commonly used in that domain due to the performance requirements, though I don't know how common Java is relatively. C++ is used enough that there is a performance focused Study Group SG14 on the standards committee that explicitly includes trading as a use case: https://groups.google.com/a/isocpp.org/forum/m/#!forum/sg14
I'm from the games programming world where C++ still dominates for performance reasons. Where GC is widely used with C# in Unity, Unity are building their own C# compiler without GC for performance critical code (the Burst compiler) and GC is a major cause of performance issues in existing Unity games.
C++ and another non GC language (Fortran) are also the most used languages for HPC ( High Performance Computing) in scientific computing.
C and C++ also dominate in performance sensitive and resource constrained embedded programming.
I don't really know what you mean. I took issue with the claim that Java demonstrated that GC was suitable for high performance code and pointed out that even though GC is more common today than it was when Java came out that it is still avoided in applications that prioritize performance. Maybe those are 'niche' but some of them are rather large niches and there are several of them.
I personally don't find GC very useful nor do I think it's a particularly good idea and it's great to see new languages like Rust taking new approaches to memory management but I recognize that it's now the default in the majority of applications that don't prioritize performance or deal with severe resource constraints and that many programmers find it valuable. There are actually several languages I like that rely on a GC, Java is just not one of them.
Why? It adds more time to the build, it requires plugins to work effectively, and it confuses the hell out of people when they expect to see Java. If you really want a better syntax, use Scala or Kotlin (I'm a huge Scala convert).
In my professional bubble Lombok is completely gone. I'll go as far and say that modern Java does not use Lombok. Smaller, focused libraries like Immutables or AutoValue, solve the problem of boilerplate for data classes.
Lombok tries to do too much across many concerns, in a fairly opaque way, and makes the code and tooling around it more magic than it needs to be.
Skip Lombok. Modern Java is better off without it...
1073741824 is maximum (signed) integer size divided by 2. This is trying to optimize for the initial size of the set so that it doesn't have to allocate too many elements, since iirc Java's hashsets are backed by a hashmap implementation which has to be sized appropriately.
I understand what it’s doing. I’m curious why it’s necessary to do it explicitly. Wouldn’t the optimal sizing be the default behavior when passing in the collection that will populate the set?
I helped in the development of a microservice as a third party developer for a big lodgings reservation company.
The infrastructure team of the main company forced us to include Lombok and use it and the code was a mess. Too much automagically generated code that was hard to debug.
My company managed to deliver the microservice with enough quality, but I really hated the experience with Lombok, vanilla Java with the libraries needed for the task is more than enough (more control over your code).
Lombok is awesome. There's still some painful stuff in Java but Lombok gets rid of most of it. I agree with other comments about using Kotlin instead, but I've had a ton of resistance to switching languages since all our devs know java.
Since Lombok is "just a library" I've managed to sneak it in almost every project I've worked on. Deftly used, I swear it cuts the LoC in a project by 30%
I think java made some wrong decision, with bigggest one being very pragmatics generics-addition, resulting in trouble down the road like List<int> not being possible. But I don't think checked exceptions are one of them ;)
I don't like too much flexibility in a language that has strong types. If you undermine the type-system, why not code in python?
But some additions of Lombok seem worthwile.
The problem with checked exceptions is that they constrain implementations by what possible failure states they could have. I.E. the implementations of an interface with a method in it that declares no exceptions will either never call anything that could throw a checked exception, it will internally handle its checked exceptions, or it will wrap them in an unchecked exception. In practice, the first is a straitjacket, the second leads to ignoring errors you can't propagate, and the third is boilerplate that makes it harder to actually catch the exceptions when you need them.
What would've made them good would be a static analysis of some sort that could divine all possible exceptions thrown by a method call and its dependents based on its code, but in Java a class can always override another class and do something entirely different so that's not possible.
Try reading this aloud: "The problem with return-types is that they constrain implementations by what possible success states they could have."
Checked exceptions are (for the most part) the mirror image of checked returns! Both describe and circumscribe your expectations for what comes back from the method, the only difference is one is happier than the other.
They follow the same kinds of type-rules, and the real problem is entirely on the human end: It's harder to imagine failure scenarios, features are prioritized over error-handling, and people don't like to do it if they can avoid it somehow.
> What would've made them good would be a static analysis of some sort that could divine all possible exceptions thrown by a method call and its dependents based on its code
Again, ask yourself "why don't we already do this but with return types?" Probably because it's horribly impractical in most cases and and outright impossible in others. For example, when it comes to interfaces, abstract-methods, or overridden methods... All of those involves concrete implementations that haven't been made yet.
Heck, they might not get created until months or years after you ship your .class files. Then some random guy you've never met says: "Oh, hey, I can fix my problem by creating my own implementation based on their interface..."
So unless your compiler can literally see into the future, you've got to set down your type-expectations first.
> but in Java a class can always override another class
No, even if all classes were marked "final", you'd have the same problem when it comes to using interfaces.
The problem with checked exceptions is that any method can throw runtime exceptions, whether it declares checked exceptions or not. So the presence or absence of "throws" does not really change your approach to error handling. An exception might happen either way, so you need to exercise the same level of care either way.
Furthermore, checked exceptions force you to bubble up implementation details all the way up to the interfaces. For example, if any implementation of an interface might throw IOException, you need to put that in the method signature in the interface. You can be clever and wrap it in a custom exception that exposes the same level of abstraction as the interface, but that's not a whole lot better than just using RuntimeException, and it causes people to try to cast the exception cause.
Your second paragraph has a direct-equivalence to complaining about return-types and advocating that everybody should just specify "Object" to make things easier.
In both cases it's about constraining and describing the results from a method. The only difference is that most developers are so much more accustomed to thinking and coding along the happy-path, where they will willingly do all the exact same architectural tasks.
Seriously, read these three variations aloud and see how they sound:
>> return-types force you to bubble up implementation details all the way up to the interfaces
>> if any implementation of an interface might return Foo, you need to put that in the method signature in the interface
>> You can be clever and wrap it in a custom return-type that exposes the same level of abstraction as the interface, but that's not a whole lot better than just using Object
I'm willing to bet you can think of a slightly-incredulous answer to each of those, right? Perhaps something along the lines of "no they don't", "yes that's normal" and "WTF of course you should."
Declaring only some of the things that might go wrong, which is the case with checked exceptions, does not solve the problem you described and only makes it more difficult to write code.
If every kind of exception was checked, then every method in Java, including constructors, would need to declare that it throws RuntimeException and IOException and NullPointerException and NoClassDefFoundError and InterruptedException because those things could happen in any method.
So we settle for declaring the type a function is expected to return, with the understanding that if something exceptional occurs a magical thing called an Exception will be thrown and it's not handled the same way as a return value. This lets us get our jobs done. It's engineering, not science.
When you solve the halting problem we can talk about formalizing every possible failure state as a return value. Until then we will get on with our lives.
> If every kind of exception was checked [...] When you solve the halting problem
Hold it: I said nothing remotely like that. You have begun ranting against an absolutist straw-man of your own creation.
What I DID say is that:
1. Type-checked exceptions are just as valuable as type-checked returns.
2. The "problems" you listed with checked-exceptions are baseless: Each of them is identical to an architectural decision or task you already regularly complete for happy-path return-types.
3. The fundamental driver of complaints is that people don't like to spend time on the unhappy-path, and aren't as rewarded for it either.
> I don't think checked exceptions are one of them
While they may not have been the wrong decision, empirical evidence continually shows they add a syntactical burden many devs just work around (e.g. just wrap in a RuntimeException anyways). It's understandable why most modern languages don't consider predictable exceptions as part of the function signatures (though many discourage exceptions altogether in all but the most extreme cases).
This is similar to saying that since error codes are ignored by most developers, functions shouldn't bother returning them at all.
Error conditions captured in the type system are a vital part of making code robust. The fact that some developers don't know how to properly handle these error cases doesn't change anything to the soundness of checked exceptions.
> This is similar to saying that since error codes are ignored by most developers, functions shouldn't bother returning them at all.
It's not like saying they shouldn't return them (unchecked exceptions exist), it's like saying they shouldn't be forced to check the error code or declare that they will return that error code themselves. Checked exceptions aren't sound when in practice the same ones are reused to mean many things. And we shouldn't pretend deciding whether an exception should or shouldn't be checked is an objective choice applied the same way across the ecosystem.
Checked exceptions are the compiler forcing the developer to consider error cases.
What's not to like about that?
Contrast with all the other approaches (runtime exceptions, return codes, Either/Result, etc...) where the developer can happily ignore errors, the code will compile, and then crash at runtime.
I'd be happier with checked exceptions if Java assumed that
list.stream().map(doStuff)
might throw whatever doStuff might throw, but instead map is defined so that doStuff can never throw any checked exception, which requires smuggling out every real-world failure mode.
I've had both good and bad experiences with checked exceptions - it can really depend on the team!
One of the parts of Lombok that really works well for me is that its notations are completely optional and fit many different levels of granularity as you see fit. Don't want sneaky exceptions? Don't use them!
I mostly end up using it to make my Java code feel like C# though...
Checked exceptions have often been presented as a mistake on the basis of the ergonomics and workflow problems they cause, not because they are unhelpful when implemented. The problem is that people don't want to break their flow to deal with them, so they don't deal with them by catching/throwing exceptions improperly.
EDIT: There's also the fact that Java includes unchecked exceptions, so you can already subvert that part of the type system.
I don't really write much Java these days, but I've use at least a few libraries which use checked exceptions in a way that really sapped the fun out of the experience. (And personally I feel it is important for writing code to be fun, because little else of working with computers is.)
I've never understood the arguments against checked exceptions.
"Forces API clients to catch exceptions you can't handle."
My observation is that obfuscation frameworks throw a bunch of checked exceptions which are unrecoverable. Which says more about the frameworks than the language.
Checked exceptions don't work right with streams/functional programming. They prevent abstractions that couldn't possibly be aware of custom checked exceptions. That's why Scala doesn't have them.
I think you definitely need to approach a 100k line project differently to smaller code (for example declare types more often, static type at least your interfaces and methods, etc) - but it is completely possible to write Groovy in a style that scales to that size and is still maintainable. After all, it can look almost exactly like Java.
On the other hand, it's impossible to scale Java down to the point where it would be sensible choice for what would otherwise be a bash script.
I think Gradle is mostly implemented in Java. I wouldn't think its speed is limited by the language of the DSL. Although I would guess startup time is longer as it definitely does have to load up groovy in the classpath and initialize all its classes.
IMO just move to Scala: the change in thinking from mutable, stateful OO to immutable, pure FP (w/ some side effects mindfully mixed-in) is indeed steep. However, accepting this paradigm shift yields unbelievable dividends in terms of quickly writing correct programs that solve complex problems.
Maybe you just nailed it on the head for me what I've been trying to state for a while in regards to FP. Expressing complex things like algorithms in functional programming can be beautiful and also incredible in how it adjusts your thinking. Fact of the matter is, I don't solve many complex problems like this. It makes doing tutorials and books fun but when i need to get work i stick to my usual guns
"Again"? That seems to pre-suppose a counterfactual.
Counterfactuals can be cool, but you have to explore them, not pre-suppose them. Pre-supposing them is definitely not cool, which calls into question the ability of our interlocutor to recognize what is and (more pointedly) what is not.
Something like this would be handy for the .NET framework.
Off the top of my head; PostSharp (although not free) would get you much of this. Also might be achieveable via Roslyn? And there is another .NET AOP framework whose name eludes me at the moment... EDIT: Fody.
I spent considerable time playing with Java and various usability extensions, but if I could go back I would have ditched Java much sooner. ML based languages really are an order of magnitude productivity boost.
"ML based languages really are an order of magnitude productivity boost."
Is this still true when you consider available libraries, performance, concurrency, build and dependency tools, development environments, and deployment options?
I'd say it depends on which ML derivative you're talking about.
I've found F# to be pretty good in all of the areas you mentioned. Although not enough to literally provide an order or magnitude boost like the post you were replying to suggested.
I think people use "order of magnitude" a little too freely. Developing with Java (and to a lesser extent, C#) feels slower than with F#, but not 10 times slower.
I was not exaggerating, although you would not see the same improvements for all applications. The big win for me is do notation (or computation expressions). I hate to think how I would even write complex async code without it. If I was only writing something linear then Java wouldnt be so bad.
Have you ever noticed how these "cool again" posts always seem to come from companies with billions of dollars that struggle to make their sites work under relatively low load?
Five times in six their grocery search - literally the core of their product - 502s on me. In the remaining 1 of 6, if I just keep repeating, I get random subsets of the data I should actually be getting. It's amazing to me that I haven't seen a fail whale yet.
I refuse to believe that Instacart has a large practical server load.
These are not the people we should be listening to.
Even cooler is using a JVM language that builds many of these features in (my favorite is Groovy). Java will always be weighed down by the (necessary) baggage of backwards compatibility. I think companies still confining themselves to strictly using Java and nothing else are doing themselves a disservice when there are JVM languages that offer almost perfect compatibility at the bytecode level.
I've used lombok in the past but only in projects where somebody insisted on using it before I joined. It can be nice having it around if you are stuck in a project where you have to use Java and it's just not negotiable to use something else and you have to use frameworks that expect a lot of model classes, DTO's and so on that comply with the java beans convention.
This is a convention that emerged in the late nineties where people used a naming convention for setters and getters to expose properties. Lots of frameworks (hibernate, jackson, gson, etc.) rely on discovering such properties via reflection. Unfortunately this results in a lot of boiler plate and code that does nothing interesting. Worse, a lot of these frameworks expect mutable classes, which should rightfully make you feel dirty every time you write one.
Lombok helps with that and you can sort of fake having quasi immutable classes. There's a price to pay unfortunately: it depends on generating code at compile time. This makes it necessary to use plugins for your favorite IDE to not get confused about all the non existent methods you are calling to access these getters, setters, builders and all the other boiler plate Lombok generates. Unfortunately these plugins make intellij more likely to get confused, which is something that can only be fixed with lengthy rebuilds, cache clearing, and the occasional restart. I've wasted no small amounts of time in trying to convince Intellij to get rid of phantom compile errors.
For this reason, I'd never introduce it in any project I'm in charge off. It's just not worth the pain and I'd rather solve the problem of not having to have stupid code like that to begin with by using something that does not require it. IMHO code generation is a bit of an anti pattern. It's kludge for having flawed languages and broken frameworks. There usually is a better solution than generating a lot of source code.
These days, Kotlin is a vastly superior way (and I say that with close to 25 years of Java experience) of avoiding that type of code and modern frameworks like spring boot work fine with it. Also worth pointing out that Java is slowly absorbing new features. Type inference is there now and they are working on data classes and a few other features. It might eventually catch up with less than half of what Kotlin does today in a decade or so. But why wait? IMHO Java is no longer a defensible choice on most new projects.
Converting Java to Kotlin with the built in conversion tool in intellij is not flawless but it gets you quite far and you can go class by class. I actually have one project where I've been gradually moving to Kotlin for over a year now. Every time I touch some old code, the first thing I do is convert it to Kotlin. Only takes a few minutes once you get the hang of it and it's a great way to explore the language. Mostly you spend this cleaning up the things it got wrong regarding nullability, funky generics, or just using a bit more idiomatic constructs (e.g. using sequences instead of streams), etc .
To quote @Retra in this thread: "I feel it is important for writing code to be fun". IMO, programming is fun when you are solving "business" problems, not writing boilerplate or handling a language's myriad edge cases.
I kind of agree, but I also find it curiously self-indulgent that as a profession software engineers feel entitled to discard technologies and methodologies because they are "not fun". Can you imagine civil engineers or electrical engineers saying that? They might enjoy working with certain technologies but would consider it highly unprofessional to demand that their projects be "fun" over other considerations.
NB: Groovy might be the closest thing to "LombokScript"
It is practically impossible to teach good programming to students that have had a prior exposure to JAVA: as potential programmers they are mentally mutilated beyond hope of regeneration.
One of the major features of a language is its syntax-- and here, it seems as if the author, instead of simply using another language, is "fixing" Java's verbosity by installing a compiler pass that converts "shorthand" Java into valid Java code. Why wouldn't the author just use another language?
Kotlin has data classes which auto generate sensible 'toString' and 'hashCode' methods which is a massive time saver.
Really, if you are considering introducing Kotlin to a Java project I'd recommend you just take the plunge. It's pretty much just a minor change to your pom file (if you're using Maven). Kotlin has Java interop as a primary feature and is flawless from my experience.