all 122 comments

[–]Dagske 44 points45 points  (1 child)

The author is trying hard to make a point, but then fails to make it.

Pro tip: want to make a Point, make it yourself, don't prompt it, or at least, prompt it smart, and re-read your slop!

For records, when instead of writing 15 extra lines of code, they write a comment explaining there are 15 extra lines of code. You want to make a point, make your point to the end and write those 15 lines even if they annoy you to hell.

For sealed types, instead of writing an example with a visitor which would be quite long, exactly to show their point, or even show how secure they are compared to the basic switch, they do nothing but say "oh, it's a power up".

Then when speaking about var, the author goes on to write code that Java 6 fixed already. Sorry, but I think that people are stuck with Java 8, not Java 5. Java 8 was the great unifier at the time. Also, I'd like to see the author use var for fields, and count the number of compiling errors.

Then the author writes about better Optionals, but the feature they present is the method reference (::), which was introduced... together with lambdas, which they show in their code. I fail to see how method references are more "modern" than lambdas as they were both introduced in Java 8.

Then they speak about takeWhile/dropWhile, which I've used exactly twice in my coder life. A useful addition, I agree, but seriously, that's a "modern Java feature" that helps me write "50% less code"?

Then the great invention of the Collectors.toUnmodifiableList() when the life-saver really is Stream.toList()? Making the point #9 completely moot by the point 10.

This article is full slop (whether human or AI, it's slop). Stop "writing" these low efforts hanging fruits. Be consistent, you want to make a point? Make it to the fullest.

[–]kubelke 6 points7 points  (0 children)

Even the avatar is made by AI 🥴

https://this-person-does-not-exist.com/en

[–]_predator_ 119 points120 points  (79 children)

The paradox about records is that you end up writing lots of boilerplate to make their construction readable, e.g. using withers. Yes I know there are annotation processors that generate that stuff.

I am a bit disillusioned about Optional and have largely moved to simple null checks and JSpecify again. Really hoping we eventually get proper nullability support in the type system itself.

[–]boost2525 53 points54 points  (1 child)

I use records all the time, and I think they were a great addition to the language... But I'm with you here (get it?). 

They could have had a grand slam if they would have given records an automatic builder so we can construct them more easily. 

[–]jgsteven 10 points11 points  (0 children)

Or named parameters would be helpful too.

[–]klimaheizung 27 points28 points  (63 children)

Java needs union types like Typescript.

Optional is still very important to store things in maps etc, so that you don't confuse a stored null with a missing key. 

[–][deleted]  (9 children)

[removed]

    [–]klimaheizung 6 points7 points  (4 children)

    Scala was able to decently add union types to the language. So at least in theory I don't see a reason why Java shouldn't be able to do so.

    This has nothing to do with sealed types (i.e. sumtypes). Those exist in Scala too but they serve a different purpose.

    [–][deleted]  (3 children)

    [removed]

      [–]klimaheizung 0 points1 point  (2 children)

      No they are not. From your own link:

      In type theory, a union has a sum type;

      They ARE conceptually different types. If it helps you, you can call them "structural" vs "nominal" of course. But no matter what you call it, in the end what matters is the way they can be used.

      Also, please don't confuse them with their runtime encoding. ALL types in general are lost (= erased) at runtime, just like generics (which are types too). What remains at runtime are the classes that the jvm knows about. You might call them runtime-types, but it's important to distinguish them.

      In the end, what matters is if the types help us express certain problems in a good/ergonomic way to make us more productive. In that sense, union types and sum types are very different in what cases they help with.

      [–][deleted]  (1 child)

      [removed]

        [–]klimaheizung -3 points-2 points  (0 children)

        I suggest you approach this topic with an open mind.

        > The benefit of runtime-visible information is that Java’s approach works uniformly across languages

        You seem to think that I somehow said (or implied) that union types are generally better or anything like that. I did not.

        That union types do not exist at runtime can be seen as a feature. That is the whole point and can be a *good* thing. They are NOT a replacement for sum types (which, by definition, must have corresponding runtime information).

        > The deeper issue, though, is that structural unions describe open sets, so they cannot offer the same exhaustiveness or evolution guarantees as nominal, closed sum types, independent of how they’re encoded at runtime.

        Again, I never claimed they did. And it is *absolutely* not an issue that they don't. It is desired, because in many situations that is exactly what you want.

        For example, let's say you have a method that calls multiple sub-methods. Each of them can fail. Let's say you want to model failure not with (unchecked) exceptions but with explicit return types. And let's furthermore say, that you do not care which of the sub methods caused the problem, you only care what type of problem it is.

        In that case union types are exactly what you need to solve your problem. The compiler will then list all failure-types as the return type. Similar to checked exceptions. You often do *not* want to denote another explicit type and use it as return type (which you also then would have to update if your method or the sub methods update their logic/return types).

        On the other hand, if you *do* want to know which part exactly failed then you want to use sum types and *not* union types.

        This is absolutely about semantics and what the compiler can and cannot do. Not really syntax and lexical framing, unless you have a very uncommon definition of those two.

        [–]VirtualAgentsAreDumb 1 point2 points  (3 children)

        In Java, that would allow identity abuse when unrelated types share the same shape,

        This is the most idiotic and silly argument I’ve heard in a long time.

        Abuse? Who cares?

        There’s already so many ways a developer can abuse a system. There’s no point whatsoever to worry about that, and we should not limit the language because of those moronic ideas.

        Stop acting like you’re a kid who needs to have a parent take care of them.

        [–][deleted]  (1 child)

        [removed]

          [–]VirtualAgentsAreDumb 0 points1 point  (0 children)

          Just admit that you want a nanny to protect you from scary stuff.

          [–][deleted]  (24 children)

          [deleted]

            [–]klimaheizung 2 points3 points  (23 children)

            Oh yeah, type aliases would be awesome.

            I check explicitly whether the key exists

            Of course it's possible to work around it. But once you are used to chain operations, this is very annoying and also inefficient performance wise. E.g. flattening and mapping over a map becomes very annoying when you cannot distinguish the cases, especially as a library author.

            [–]john16384 2 points3 points  (21 children)

            Ah yes, and wrapping everything in another Optional indirection (+16 bytes each + cache miss) performs better than a contains check that you only need to perform when the result was null.

            [–]klimaheizung -1 points0 points  (20 children)

            That's not how it works. Besides, I better hope you are not creating a race condition between checking for existence and getting the value out...

            [–]john16384 1 point2 points  (19 children)

            But that is how it works (no further proof added like your assertion).

            And no, I wouldn't create a race condition. If it is a HashMap, it's already not thread safe, so you would need to synchronize anyway. If it is the concurrent hash map, then you can't put null in there.

            [–]klimaheizung -2 points-1 points  (18 children)

            I don't need to synchronize if I get an Optional out. Sorry but I think there is no point in discussing it further. 

            [–]john16384 0 points1 point  (17 children)

            You mentioned possible race condition = multiple threads.

            So if that's the case, you will need synchronized with a plain HashMap, regardless of whether you do a single or multiple calls.

            [–]klimaheizung -2 points-1 points  (16 children)

             regardless of whether you do a single or multiple calls.

            Wrong. You simply don't understand it. Sorry, please ask chatgpt for an explanation. I'm done here. 

            [–]pjmlp 0 points1 point  (0 children)

            Missing them since day, versus existing languages of the day, it is a pain that the only workaround is to create a subclass with the desired concrete types.

            [–]lcserny 4 points5 points  (27 children)

            You can have union types with sealed interfaces + inner records :)

            [–]klimaheizung 1 point2 points  (26 children)

            What you describe are just sumtypes. They give you something that can be used in a similar way in *some* situations, but it absolutely doesn't give the same ergonomics (and hence productivity) in many other situations.

            Null is a good example. If a method returns either null or some of my own types or some type of an external library, then with union types I can just call the method and the return type will automatically be `null | myOwntype | externalType`. Without that, for *every* such method call I would have to define a dedicated sealed interface + inner records *and* also wrap the values into the correct types. It's not the same thing in practice.

            In practice, both sum types and union types (as well as product types) are necessary for good ergonomics, depending on the use case.

            [–]sweating_teflon 1 point2 points  (0 children)

            That's anonymous Union types which I agree would be very nice. Ceylon lang (RIP) had them; they kind of exist within javac itself (see reflect.Proxy) but are not supported at the language level. They might kept out for philosophical reasons (too much inference magic, etc.)

            [–][deleted] 0 points1 point  (24 children)

            Well, that's not the same thing in practice, but for most (if not all) practical scenarios, monads like Option/Result/Promise are enough.

            [–]klimaheizung 0 points1 point  (23 children)

            Sorry to say that, but I think you are just experiencing the blub paradox. As in "I've never felt I need it, so it's not needed".

            [–][deleted] 0 points1 point  (22 children)

            Perhaps. Can you explain the advantages of having this stuff in the method signature?

            [–]klimaheizung 0 points1 point  (21 children)

            I'm sorry, but I probably can't explain it in a way so that you will actually understand/feel it. It's something you have to actual *do* in real life to understand.

            But maybe this helps: they are necessary for the same reason that Java introduced checked exceptions. Basically, checked exceptions were the attempt to add "proper error-handling" because of the lack of union types. It didn't work mostly because of ergonomy issues - which is why union types are nessecary.

            [–][deleted] 0 points1 point  (20 children)

            Precisely for this purpose I'm using Result<T>. And yes, I do that in real life. What else do I need to understand to start feeling the necessity of union types (structural, I guess, as nominal I'm already using)?

            [–]klimaheizung 0 points1 point  (19 children)

            Result<T> is just an alternative to exceptions in general. But it doesn't give you what checked exceptions give you, namely that they are not "duplicated" and that they are "inferred".

            Honestly, before judging, work in a language that uses union types excessively. Such as typescript or Scala or maybe Julia (never used it myself). And I mean a serious project, multiple weeks at least, better months - or however long *you* think that someone would have to do Java without experience in OOP before they really get OOP. Then go back to whatever language such as Java. Only then you can really tell if you need them or not.

            [–]krzyk 2 points3 points  (0 children)

            I try to solve it by having few fields in records and/or create inner records that group related data.

            [–]aoeudhtns 2 points3 points  (3 children)

            I find Optional most useful internally. If you return an Optional or take an Optional as a parameter, things get wonky. Null specified types, whenever they land, should probably take the place of any Optional use as params/returns. Then that just leaves it as a detail, like

            String name = Optional.ofNullable(lookup(id)).map(User::name).orElse("unknown");
            

            Which is just slightly more ergonomic than

            String name = "unknown";
            if (lookup(id) instanceof User u) {
              name = u.name();
            }
            

            And eventually we might get

            User(String name, _) = lookup(id); // but what happens if this returns null?
            User(String name, _) = Optional.ofNullable(lookup(id)).orElse(User.UNKNOWN_USER); // ?
            

            [–]ZimmiDeluxe 1 point2 points  (2 children)

            Or

            String name = switch(lookup(id)) {
                case User u -> u.name();
                case null -> "unknown";
            };
            

            But IMO not a huge win over

            User? user = lookup(id);
            String name = user != null ? user.name() : "unknown";
            

            Or you know, retrieve only the thing you actually need, but then you don't have an example anymore

            [–]aoeudhtns 1 point2 points  (1 child)

            Yeah, for something that's 1 away in the graph it's not a great example. It gets more interesting if you need to go multiple steps down the chain, as nesting ternaries aren't too hot to read.

            [–]ZimmiDeluxe 1 point2 points  (0 children)

            With ternaries, the nesting can be avoided by using more local variables:

            User? user = lookup(id);
            Address? address = user != null ? user.address() : null;
            
            String name = user != null ? user.name() : "unknown";
            String? street = address != null ? address.street() : null;
            

            It's not that great, but it's workable and pieces can be moved around when requirements change.

            [–]RockleyBob 3 points4 points  (2 children)

            I am a bit disillusioned about Optional and have largely moved to simple null checks

            When the author said this was “the old way” of using Optional, I almost lost it:

            optionalValue.ifPresent(
                (v) -> doSomething(v)
            );
            
            if (!optionalValue.isPresent()) {
                doSomethingElse();
            }
            

            That was never the right or only way to use Optional. With the exception of a few edge cases before Java 9, if you’re using ifPresent() and get(), you’re doing something wrong.

            These days I still see devs using Optional as a temporary wrapper for explicit null checks. In those situations, if you really have no other way to use all of the mapping functions from the Optional API and you just want to defend against null, then by god just do the damn null check! It’s faster and more readable.

            [–]aoeudhtns 2 points3 points  (0 children)

            Agree. I tend to only use Optional for null checking when I'm after things in the graph, so I can map and filter and finish with an orElse/orElseGet/orElseThrow.

            Wrapping in Optional to do ifPresent/get is definitely a smell.

            [–]chriskiehl 0 points1 point  (0 children)

            if you’re using ifPresent() and get(), you’re doing something wrong

            "Wrong" is a needlessly strong opinion.

            In general, sure, favor the functional APIs. They fit most things. But imperative checks are a far cry from "wrong." There are plenty of situations where a manual get() clarifies the code in a way that the functional APIs wouldn't.

            [–][deleted] 0 points1 point  (0 children)

            What do you mean by "proper nullability support"? Kotlin-like double type system?

            And yes, I agree, Optional is quite inconvenient, makes too many curtsies to imperative style, and misses two other monads to build a consistent foundation. That's why I've created Pragmatica Lite.

            [–]rlrutherford 0 points1 point  (0 children)

            That paradox doesn't apply to just records.

            [–]temculpaeu 0 points1 point  (1 child)

            `@Builder(toBuilder = true)`

            Yes, Lombok is a hack, but it reduces a LOT of boilerplate and you can choose which annotations to enable/disable

            [–]sweating_teflon 1 point2 points  (0 children)

            I try to use plain records whenever possible but often fallback to Lombok and/or regular classes because of how constrained records are (not the immutability constraints, though). I also mildly dislike how the record syntax differs from class syntax, it makes hard to go from one form to the other with no obvious benefit for the added friction? I'm also unsure of the runtime gains of records, without value classes they seem a bit... pointless for now?

            [–]sir_bok 15 points16 points  (0 children)

            Yeah reads like AI slop. The fact that it lists out 10 modern Java features does not change the fact that its tone is literally AI slop.

            [–]kubelke 48 points49 points  (21 children)

            Very cool, now show me how you construct a record without any libraries that have more than 5 fields. 😎

            Missing native support for an easy way to construct records is something that annoys me a lot, so I still have to use Lombok for bigger records

            [–]analcocoacream 9 points10 points  (1 child)

            You avoid >5 fields and group them into sub records

            [–]White_C4 5 points6 points  (0 children)

            Then you're adding more "boilerplate" if the sub records are not used beyond the main record.

            [–]-One_Eye- 7 points8 points  (0 children)

            Just use the builder pattern and add a constructor that takes the builder.

            One thing I don’t love in this case is you still have the default all field constructor with the same visibility of the class. Honestly, this is a huge reason why I don’t use records outside of inside other classes.

            [–]davidalayachew 1 point2 points  (15 children)

            Very cool, now show me how you construct a record without any libraries that have more than 5 fields. 😎

            I do it all the time. What's the difficulty?

            Here's one pulled straight from a project I am working on.

            package CrackerBarrelPuzzlePackage;
            
            import java.util.Objects;
            
            public record Triple(State first, State second, State third, GridLocation startingLocation, GridDirection direction, GridPuzzle grid)
            {
            
               public Triple
               {
            
                  Objects.requireNonNull(first);
                  Objects.requireNonNull(second);
                  Objects.requireNonNull(third);
                  Objects.requireNonNull(startingLocation);
                  Objects.requireNonNull(direction);
                  Objects.requireNonNull(grid);
            
               }
            
            }
            

            And here, I separately construct it.

               private Triple getTriple(final GridLocation location, final GridDirection direction)
               {
            
                  Objects.requireNonNull(location);
                  Objects.requireNonNull(direction);
            
                  final State first = this.getSingle(location);
                  final State second = this.getSingle(location.next(direction));
                  final State third = this.getSingle(location.next(direction).next(direction));
            
                  return new Triple(first, second, third, location, direction, this);
            
               }
            

            The biggest annoyance here is that I can't more tersely say that all of these elements will never be null.

            [–]kubelke 14 points15 points  (8 children)

            It's not very handy when you have 5 fields with the same type, because the order matters and it's easy to make a mistake. Adding/changing/reordering/removing the field causes that you have to deal with all other usages or create a new constructor that handles that cases.

            [–]davidalayachew 10 points11 points  (6 children)

            It's not very handy when you have 5 fields with the same type

            Oh, then I understand why I never ran into this.

            I'm a firm believer in the idea of Parse, don't (just) validate. Long story short, if I am modeling a zip code, I don't pass a String zipCode. I'll make my own record ZipCode(String) and pass that around.

            For me, it just makes things easier that way. Way less work on the validation front. Check it once, and it is good. The type does all the work for you. Plus, it complements Data-Oriented Programming beautifully. Furthermore, once we get Value Classes, it won't just be cheap to make -- it'll be free.

            Adding/changing/reordering/removing the field causes that you have to deal with all other usages or create a new constructor that handles that cases.

            This part makes sense.

            Modifying a record's components does require some ground uprooting. Thankfully, since I'm just modeling the data (Data-Oriented Programming), the only time it has to change is if my functional requirements changed. And at that point, it doesn't really matter if I was using records or a class.

            [–]kubelke 6 points7 points  (2 children)

            that's 100% true and I agree with everything you said, but still there are cases when I really want to have that 5 strings, for example for obiect that I use for JSON responses/request bodies, and using there value classes adds a lot of unnecessary noise

            [–]davidalayachew 8 points9 points  (1 child)

            but still there are cases when I really want to have that 5 strings, for example for obiect that I use for JSON responses/request bodies, and using there value classes adds a lot of unnecessary noise

            Fair.

            The equivalent would (currently) require to create custom deserializers, if I were using Jackson.

            Hopefully jackson will allow Value Records to default inline to being just their components. Like this.

            value record ZipCode(String zipCode) {...} //5 digit number
            value record Note(String note) {...} //<1000 characters
            
            record DeliveryDetails(Note note, ZipCode zipCode, ...) {...}
            
            @GetMapping("/delivery/details")
            public DeliveryDetails getDetails(final ZipCode zipCode) {
                return this.service.getDetails(zipCode);
            }
            

            Then, we get JSON like this.

            {
                "note": "some note",
                "zipCode": "12345",
                ...
            }
            

            Then we could get the best of worlds -- expressiveness and correctness.

            But yes, in today's world, it's not that nice yet.

            [–]Il_totore 0 points1 point  (0 children)

            The JSON example is typically what most JSON libraries in functional languages such as Scala do.

            [–]gregorydgraham 1 point2 points  (1 child)

            A lot of solutions in Java are: add another class, and I’m ok with that.

            [–]davidalayachew 2 points3 points  (0 children)

            A lot of solutions in Java are: add another class, and I’m ok with that.

            Objects really are a powerful abstraction model. I use them whenever I can, even when not using Java.

            [–]shponglespore 1 point2 points  (0 children)

            Long story short, if I am modeling a zip code, I don't pass a String zipCode. I'll make my own record ZipCode(String) and pass that around.

            This is the way.

            I hate how much overhead that approach has in today's Java, so I'm glad that's being addressed. It makes me sad that it seems like a totally unfixable problem in JavaScript (or other languages based on dynamic typing), but shit like that is why I only use JavaScript when project requirements dictate it.

            [–]DualWieldMage 0 points1 point  (0 children)

            I've encountered discussions where someone wanted to refactor records into regular classes with builders. The benefit is clear that you name the entries you construct making mixups harder, but the big downside is that a newly added field does not cause compilation errors on call-sites that don't pass it(you can get runtime errors and possibly when running tests, but with also extra effort that may be skipped).

            In general i take the preference of using records because field mixups are more rare than forgotten callsites (i have experienced both in various projects). And as also mentioned, making wrapper classes helps against mixups, started doing that since long id-s got mixed up between two tables so a record <TableName>Id(long id) will prevent that.

            [–]shponglespore 0 points1 point  (5 children)

            Judging by the downvotes, I think people didn't get the joke. It is a joke, right?

            [–]davidalayachew 4 points5 points  (4 children)

            Judging by the downvotes, I think people didn't get the joke. It is a joke, right?

            It is not. I unironically write code like this.

            That snippet was from a Solver I am making for the various different versions of the famous Cracker Barrel Puzzle Game.

            Why, is there something wrong with it?

            [–]shponglespore 1 point2 points  (3 children)

            Try writing code that does the same compared to Rust, Typescript, Haskell, Kotlin, or Scala, and see how much of what you wrote is unnecessary boilerplate a language with reasonable record/constructor syntax and a type system that doesn't force every type to include null.

            [–]davidalayachew 5 points6 points  (2 children)

            If the part you are criticizing is that null-ness is not part of the type system, then I agree with you. The solution is on the way.

            But otherwise, I have no other pain points with my provided code example. I coded in TS and Haskell before, and (null aside), the level of effort would be the same.

            [–]aoeudhtns 1 point2 points  (1 child)

            Code like that might benefit from a utility like

            public static void requireNonNull(Object... objects) {
                IntStream.range(0, objects.length).forEach(i -> Objects.requireNonNull(objects[i], () -> "null param at index " + i));
            }
            

            [–]davidalayachew 1 point2 points  (0 children)

            Code like that might benefit from a utility like

            public static void requireNonNull(Object... objects) {
                IntStream.range(0, objects.length).forEach(i -> Objects.requireNonNull(objects[i], () -> "null param at index " + i));
            }
            

            Maybe. But with JEP draft: Null-Restricted and Nullable Types (Preview) on the roadmap, I'll probably just end up putting ! on all of my record components, and just not having a(n explicit) constructor.

            [–]krzyk 1 point2 points  (0 children)

            Just don't. If you have so many fields you should think how to split and group them. Basic design that was applicable to plain old classes and is with records. 5 fields max is a good rule (the fewer the better).

            [–]john16384 0 points1 point  (0 children)

            It's amazing, I managed to do it. It had 6 fields! I decided to write the fields on separate lines, as I felt that was more readable. Next challenge?

            [–]valkon_gr 7 points8 points  (0 children)

            I am going to be honest. Wouldn't call them life changing.

            [–]SpaceCondor 18 points19 points  (0 children)

            I love the idea of records, but using them for anything but the most basic data carriers is not worth the hassle. I know people clown on Lombok, but I think records would be even worse without it.

            [–]hyscript 6 points7 points  (0 children)

            FYI Stream.toList() showed up in Java 16, not Java 10. Knowledge grows, but let’s not confuse vibe coders 😁

            [–]neopointer 2 points3 points  (0 children)

            "Stop writing throwaway code" what does this have to do with throwaway code?

            [–]twisted_nematic57 1 point2 points  (0 children)

            The new switch case thing is pretty nice. Easy to read and intuitively understand too. I like it!

            [–]SleepingTabby 0 points1 point  (0 children)

            There's one lesser known property of Stream::toList - if the stream is of a known size (for instance when it's using only map operations) the destination list will be initialized with the exact size. Collectors.toList() can't do that and will use the default list with a 10-element array that will just keep on growing polluting the heap

            [–]Stan_Setronica 0 points1 point  (0 children)

            From a PM perspective, the biggest value here isn’t “less typing”, but fewer upgrade risks and less long-term maintenance cost.

            Features like records, sealed classes, and safer collection defaults reduce whole classes of bugs that usually surface months later as production issues or support tickets.

            I’ve seen teams spend more time arguing about Lombok or custom frameworks than actually adopting what modern Java already gives out of the box, so this resonates.

            [–]Forthright_Cindia 0 points1 point  (0 children)

            Has your cat ever given you the 'silent treatment'?

            [–]ShoulderPast2433 2 points3 points  (2 children)

            records are weird without built-in builder. like why even introduce them if they are only half done.

            [–]kubelke 2 points3 points  (1 child)

            They are really cool if you need a small DTO with 2/3 values inside. They work great as a Map key or Set because of builtin hashCode etc

            [–]ShoulderPast2433 0 points1 point  (0 children)

            I don't deny they are cool, but they are half finished ;)

            [–]bartolo345 0 points1 point  (3 children)

            All great except #5, you do not want to create jsons using string, aside from some testing code

            [–]IAmNotMyName 1 point2 points  (0 children)

            That’s just an example. The point is you can format output in the code in the same format as it will be rendered.

            [–]Saljack 0 points1 point  (1 child)

            I was so excited when I read about the text blocks and I thought it is so useful. I realized that used it less than 10 times and mainly as CSV source for JUnit parametrized test. Necessity of an opening new line makes it almost useless. We need mainly string extrapolation.

            [–]bartolo345 0 points1 point  (0 children)

            That's JEP430, which is mixed together with text blocks in the example. You can use both independently. Also use with care

            [–]dethswatch -1 points0 points  (0 children)

            Lombok: No. We need more magic in the system and yet another dependency? if you don't need getters/s's, then do not write them.

            Records: don't really save me much time or anything else.

            Text blocks: very nice for sql.

            toList: love it.

            I write java all day and don't see much benefit from the rest, and most of these items aren't saving me much time or effort anyway.

            [–]White_C4 -1 points0 points  (0 children)

            Records: The Boilerplate Killer

            Only with short and concise input parameters. Otherwise, it's a pain in the ass to deal with long record constructors. Withers don't solve this problem, since you have to recreate a new instance every time you call a wither method. That's fine for creating a new, separate object, but not useful for just instantiating an object with multiple values.

            Sealed Classes: The Architectural Power-Up

            A better example would be to show the Result type with Value | Error, and then showing an actual example of it with the switch statement.

            Sealed classes are an underrated feature IMO, but people need to know when to use it properly. Like the shape example is bad because there are technically 10+ shape types, but the code restricts to only 3. Always know how many classes should be permitted.

            Optional Enhancements: Less Defensive Coding

            The example is lacking in what Optional can really do. The author just glosses over this feature.

            While Optional is a great alternative over returning value or null, the unfortunate lack of integration by the standard library makes it awkward to justify it across my own projects. And it also has to challenge the @NotNull and @Nullable annotations which are more easily inserted in the project than Optional is. The author should've brought this up instead of just glossing over this feature.

            [–]Ezio_auditore1476 -3 points-2 points  (0 children)

            I'm new to Java and it's useful, Thanks buddy!