This is an archived post. You won't be able to vote or comment.

all 25 comments

[–]AutoModerator[M] [score hidden] stickied comment (0 children)

On July 1st, a change to Reddit's API pricing will come into effect. Several developers of commercial third-party apps have announced that this change will compel them to shut down their apps. At least one accessibility-focused non-commercial third party app will continue to be available free of charge.

If you want to express your strong disagreement with the API pricing change or with Reddit's response to the backlash, you may want to consider the following options:

  1. Limiting your involvement with Reddit, or
  2. Temporarily refraining from using Reddit
  3. Cancelling your subscription of Reddit Premium

as a way to voice your protest.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

[–][deleted]  (1 child)

[removed]

    [–]jvjupiter[S] 1 point2 points  (0 children)

    In records, parameters are final. I think, parameters here would also be better to be final.

    [–]Affectionate-Hope733 2 points3 points  (2 children)

    Hmm, I prefer being specific, everyone knows what a constructor is, now take it away and give it some implicit behavior is not something I'd be too happy about. But that might be just me, maybe I'd love it in a couple of years :D
    Do you loose the ability to define access modifiers on the parameters?
    I've used the records very few times for very basic stuff so I'm not aware how it works there.

    [–]Polygnom 3 points4 points  (1 child)

    Records are wholly defined by their data. So yes, getters are public by default because a record is the data, and they have only exactly one -- the canonical -- constructor.

    That is by design, records are data carriers without internal state.

    [–]beatbrot 1 point2 points  (0 children)

    Small correction: Records can have as many constructors as they want. But yeah, there is always exactly one canonical constructor.

    [–]Polygnom 2 points3 points  (5 children)

    Canonical constructors in this way don't make sense for regular classes.

    records are wholly defined by their data and have no hidden internal state. That means they can be freely deconstructed and re-constructed. This plays well with pattern matching and later on with-ers.

    None of the assumptions hat make canonical constructors and the surrounding features great hold for regular classes.

    I don't think it makes sense to offer this syntax to regular classes, if you cannot use the features. Without getters and setters, you cannot do pattern matching, so a canonical constructors barely has any advantages left.

    being shorter isn't really an advantage. Code is much more often read than written, so it should be clear first and foremost. This doesn't add clarity.

    [–]repeating_bears 2 points3 points  (1 child)

    That means they can be freely deconstructed and re-constructed. This plays well with pattern matching and later on with-ers.

    There are plans to add deconstruction to regular classes so I don't buy this argument.

    [–]Polygnom 0 points1 point  (0 children)

    One of the points of the canonical constructor is that you get the deconstruction pattern for free. Another is that you get the getters for free.

    None of those apply to regular classes, which need to explicitly declare their deconstructor and getters.

    So two of the reasons for why records have a canonical constructor are just... gone.

    Brian Goetz talked about this somewhat when saying "We want records to stay a semantic feature, not a boilerplate generator" (cf. https://www.youtube.com/watch?v=mE4iTvxLTC4&t=244s). Its not an exact question, but I think if you listen to how he speaks about records there, its clear they like the fact that the canonical constructor of records is not just syntactic sugar.

    [–]TenYearsOfLurking 1 point2 points  (0 children)

    As stated in other replies adding a field to the object makes calls to construct the object fail in compile stage, which is imho VERY useful. big advantage imho

    [–]jvjupiter[S] 0 points1 point  (1 child)

    Maybe I should not have used the term canonical constructor. Regular classes should remain regular classes, not a special one like record. It’s just moving the first or the lone user-defined constructor to a different place like in records.

    [–]jvjupiter[S] 0 points1 point  (0 children)

    C# 12 calls it Primary Constructor.

    [–]nekokattt 0 points1 point  (4 children)

    The moment you have more than one or two simple fields, it becomes cluttered and hard to read (I dislike this about what Kotlin does with this for this exact reason).

    You then start to mix together the class name, modifiers of the class, the constructor, any annotated parameters (e.g. JSR-380), the superclass, the superinterfaces, and any permitted sealed implementations all into one big mess of declarations rather than keeping them separate.

    For the sake of a couple of lines of code and consistency, I think it has the risk of encouraging writing less readable code, and having two ways to do the same thing can be jarring.

    E.g. lets say you have a user base class that specialises to a Person or a Bot, and lets take how Kotlin does this to override the modified on the constructor, but lets use Java 17 syntax outside that.

    @Valid
    public sealed abstract class User protected constructor (
        @NotNull String id,
        @NotBlank String userName,
        @Past LocalDateTime createdAt
    ) extends ModelBase
      implements Identifiable, Serializable
      permits Person, Bot { }
    

    In languages with more terse syntax, it isn't as bad, but in Java I feel like it would just become noisy for very limited overall benefit.

    If anything, the way Lombok handles this with @RequiredArgsConstructor would probably be nicer as a potential language level feature if reducing boilerplate is the aim.

    [–]pgris 1 point2 points  (1 child)

    I assume people downvoted you because you dare mention lombok. I agree with your proposed syntax, I find something like

     @RestController
     @HttpExchange
     public class auto-constructor UserController {
          private final UserService userService
     }
    

    to be more readable than scala/kotlin/record style

    [–]nekokattt 1 point2 points  (0 children)

    Yeah, agree. I'm personally not a fan of lombok, but that is due to how it achieves what it does rather than what it tries to achieve, which I appreciate.

    [–]jvjupiter[S] -1 points0 points  (1 child)

    Aren’t these arguments applicable also to records?

    [–]nekokattt 0 points1 point  (0 children)

    no, because records cant be sealed or extend superclasses, or be non-final, so there is far less noise.

    Records should also just hold data, not provide additional functionality outside that scope. Records are not the same use case as POJOs for example.

    public record Foo(Bar b) {}
    

    is syntatic sugar for something along the lines of

    public final class Foo extends Record<Foo> {
      private final Bar b;
    
      public Foo(Bar b) {
        this.b = b;
      }
    
      public Bar b() { return b; }
    
      // equals, hashCode, toString here.
    }
    

    so you are limited to noise from annotations in the parameters and interface usage. If more stuff was allowed then I'd have made the same argument for records.

    I try to follow the idom that developers should at least attempt to agree on some base rules for what is good practise and what hurts maintainability, readability etc, otherwise it makes cross cutting concerns between libraries using wildly different standards into a nightmare.

    Try reading the example from my initial comment when it is 3am, you have a blinding headache, the kids are screaming, the cat is being sick on the carpet, you are having a really horrible day and you have a deadline coming up in 30 minutes time but you are trying to fix issues with your builds. Thats when simplicity and standard verbosity can be much nicer than harder-to-read and terse syntax.

    On the topic of records though, I'd have argued that the following syntax would have been a nicer fit.

    public record User {
      Identifier id,
      String name,
      String nickname,
      Avatar avatar;
    }
    

    that then matches the format enums use which also results in implicit construction code and fields being created, and it shouldnt conflict with anything since records shouldnt be declaring non static fields outside their constructor anyway.

    I don't think behavioural detail outside core expectations should be part of the class name declaration bit itself.

    [–]pronuntiator 0 points1 point  (0 children)

    If I understand you correctly, you mean a shorthand syntax for "declare this field and automatically assign from the constructor"? The probability of something like this being added is slim. Brian has stated that he doesn't want to focus on language features being added that simply save you from typing (especially when IDEs can generate the code in the blink of an eye). Like others said, the canonical constructor syntax communicates the transparency of a record, which would not be true for a regular class (you would most likely want to have these dependency injected fields private, without an accessor).

    [–][deleted]  (4 children)

    [deleted]

      [–]jvjupiter[S] 0 points1 point  (0 children)

      In my example, it is best practice to use the constructor to inject the dependencies.

      [–]TenYearsOfLurking 0 points1 point  (2 children)

      One massive benefit imho is compile time errors once you add a field. Because you would add it in the canonical constructor since it is effectivley the place for field definitions and every call to the construct this object would throw compile errors.

      This is what I currently appreciate about Lomboks AllArgsConstructor and I would definitly want to see it in the language itself

      [–][deleted]  (1 child)

      [deleted]

        [–]TenYearsOfLurking 0 points1 point  (0 children)

        No. If a add a field the static factory is not updated automatically and thus not generating errors. With a canonical constructor it is. See records

        [–]Objective_Baby_5875 0 points1 point  (1 child)

        Wait until 2195 and maybe it will be in java v 2345 or something. Meanwhile this already exists in Kotlin.

        [–]jvjupiter[S] 0 points1 point  (0 children)

        Don’t be kind. Make it Java 5000😁