Immutable Type: public final fields vs. getter
Asked Answered
E

9

67

I need a small Container-Class for storing some Strings which should be immutable. As String itself is an immutable type, I thought of something like that:

public final class Immu
{
  public final String foo;
  public final String bar;

  public Immu(final String foo, final String bar)
  {
    this.foo = foo;
    this.bar = bar;
  }
}

Many people seem to object using public fields at all and use Getters instead. IMHO this would be just boilerplate in this case, because String itself is immutable.

Other thoughts I may be missing on this one?

Egidius answered 3/8, 2011 at 14:5 Comment(2)
Had the same question, and I like the answer, but wanted to note: that's what I like about Scala, start with a val, then change to a getter, signature stays the same, adding to the list of "java questions that won't be asked in Scala"Bromal
That's what I like about Kotlin: data class Immu(val foo: String, val bar: String), and you're done. No redundancy, no endless discussion about technical choices.Freemason
R
65

I would do what you believe is simplest and clearest. If you have a data value class which is only used by a restricted number of classes. esp a package local class. then I would avoid getter/setters and use package local or public fields.

If you have a class which you expect other modules/developers to use, following a getter/setter model may be a safer approach in the long run.

Radiolarian answered 3/8, 2011 at 14:9 Comment(4)
Thanks Peter, I like your differentiation of use cases.Egidius
Besides, unless this is an API, you can probably use Eclipse or Intellij to refactor the code later if and when a need for getters does arise. Refactor > Encapsulate Field. YAGNI.Totalitarian
I would just like to add that if the class is merely an aggregator of fields and contains no business logic (or logic of any kind) then, regardless of being exposed or not, the getters are unnecessary (but not wrong).Hagan
Good answer. I think the distinguishing feature to look for is whether or not all users of the class are recompiled at the same time the class is. If you need to distribute compiled libraries, then public final instance variables are a no-go. If the class is a part of an application that gets built in full every time, then they probably aren't a problem.Misguidance
M
28

The problem is the uniform access principle. You may later need to modify foo so that it's obtained through a method instead of being fixed, and if you exposed the field instead of a getter, you'll need to break your API.

Mello answered 3/8, 2011 at 14:8 Comment(8)
Also, you would give up the possibility to subclass Immu with some custom get-methods.Wistful
Allowing subclasses of Immu is something I want to avoid, because this could lead to mutable subclasses.Egidius
If you need to modify foo then you create a brand new Immu. Immutability should be used as much as possible. If you have a case where you do a lot of mutation of your POJOs then you have a design problem.Rustic
@NewestStackOverflowUser By modify foo I mean modify the code that produces foo.Mello
@DanielC.Sobral in that case as long as u guarantee that your class is Immutable I would go for public fields with no boilerplate code. My vision is that getters and setters are just wrapping a public field. The outcome is the same. If foo is being produced rather than injected then you also have a change in the behavior of Immu, then Immu is no longer a data holder but contains some logic.Rustic
@NewestStackOverflowUser Your last sentence is breaking encapsulation, but so be it. What follows is: so what? Yes, it's no longer a data holder, so what? Are you going to break the API because the business rules changed? Where's your encapsulation? Code that uses Immu should not care about how it works. For example, if a Config object was initialized, had hard coded values for test, or is reading info over the wire is NOT the client's concern.Mello
@DanielC.Sobral I agree that in that case you should hide the logic behind a wrapper. But yet again you are hiding logic in that case and that config object is no longer a POJO. In the case of a simple POJO data holder I would go with public FINAL fields and in the case where data is provided from somewhere it makes sense to hide it behind a method. In such a case you wont be able to make final fields and thus they should not be public.Rustic
@NewestStackOverflowUser Your argument makes sense only if there's a difference between a final field and a method, and the point of the uniform access principle is making sure there isn't. These problems you are creating are a result of not having the uniform access principle. Fields and methods ARE NOT DIFFERENT. There's no distinction. They are the same thing. That's true at a machine code level, and at Scala level. It's Java pretending they are different.Mello
P
14

This answer is obviated:

Why not

interface Immu { String getA() ; String getB ( ) }

Immu immu ( final String a , final String b )
{
       /* validation of a and b */
       return new Immu ( )
       {
              public String getA ( ) { return a ; }

              public String getB ( ) { return b ; }
       }
}
Phia answered 3/8, 2011 at 15:42 Comment(5)
Interesting solution. What's the advantage of the inner class?Egidius
All Immus from your immu method will be immutable. (Others are free to implement mutable Immus if they like, but they can't touch your Immus.) It also satisfies the people who want getter methods w/out too much boiler-plate code.Phia
Although not quite what I've been looking for this approach might come in handy sometime.Egidius
This requires duplicating the method implementations every time you need an instance. Do not recommend.Misguidance
@Misguidance I think newer ideas like dependency injection and lambda functions obviates this answer. I would no longer recommend it either.Phia
P
10

I found this thread hoping for some actual arguments, but the answers I've seen here didn't help me all that much. After some more research and thinking I think the following has to be considered:

  • public final looks cleanest for immutable types.
  • Mutable types could be altered by accessors even if this is not intended - in concurrent environments this could lead to a lot of headaches.
  • There can be no no-arguments constructor. This is importent if you need factory methods (e.g. for LMAX Disruptor). In a similar way instantiating your objects via reflection becomes more complicated.
  • Getters and setters can have side effects. Using public final clearly tells the programmer that no hidden magic is occuring and the object is inherently dumb :)
  • You can't return a wrapper or a derived class instance to the accessor. Then again, this is something you should know about when the field is assigned its value. In my opinion container classes should not be concerned about what to return to whom.

If you're mid development and no guideline is stopping you and the project is isolated or you have control over all involved projects I'd suggest using public final for immutable types. If you decide you need getters later on, Eclipse offers Refactor -> Encapsulate Field... which automatically creates these and adjusts all references to the field.

Prudi answered 29/5, 2015 at 18:44 Comment(1)
"Using public final clearly tells the programmer that no hidden magic is occurring and the object is inherently dumb" I agree. In addition it also tells the programmer that the value won't ever be changed. This is useful to the reader when when grokking an undocumented API.Fedora
M
7

I use the public-final-field (anti?)pattern on home projects for classes which are basically an immutable data structure with a constructor, along with absolute basics like equals(), hashCode(), toString(), etc. if required. (I'm avoiding the word "struct" because of the various different language interpretations of it.)

I wouldn't bring this approach to someone else's codebase (work, public project, etc) because it would likely be inconsistent with other code, and principles like When In Rome or Least Surprise take priority.

That said, with regard to Daniel C. Sobral's and aioobe's answers, my attitude is that if the class design becomes a problem because of unforeseen developments, it's the work of 30 seconds in an IDE to privatise the fields and add accessors, and no more than 5 or 10 minutes to fix broken references unless there are hundreds of them. Anything that fails as a result gets the unit test it should have had in the first place.:-)

[Edit: Effective Java is quite firmly against the idea, while noting that it's "less harmful" on immutable fields.]

Matisse answered 18/4, 2014 at 22:2 Comment(0)
A
4

Forget about encapsulation, immutability, optimization and all other big words. If you are trying to write good java code, I would recommend you just use getter simply because it is java friendly, and most importantly it saves ton of time googling why.

For example, you probably would not expect using streams when you write the code, but later you found

listOfImmus.stream().map(immu -> imm.foo).collect(Collectors.toSet()); // with field
listOfImmus.stream().map(Immu::getFoo).collect(Collectors.toSet());    // with getter

Supplier<String> s = () -> immu.foo;  // with field
Supplier<String> s = immu::foo; // with getter

// final fields are hard to mock if not impossible. 
Mockito.when(immuMock.getFoo()).thenReturn("what ever");

//one day, your code is used in a java Beans which requires setter getter..
¯\_(ツ)_/¯

This list can be long or short or may be none of them makes any sense to your use case. But you have to spend time convincing yourself (or your code reviewers) why you can or should rebel against java orthodoxy.

It is better to just write the getter/setter and spent the time for something more useful: like complaining java

Astroid answered 12/9, 2019 at 15:30 Comment(0)
S
4

Since Java 16, you can use records.

public record Immu(String foo, String bar) {}

All of a record's attributes are automatically final and it automatically has methods like equals(…) and toString() and the constructor.

The getters of the attributes have the same name as the attributes, in this case, they are foo() and bar().

The methods can be overridden, more information is in the documentation.

Sanjak answered 11/10, 2021 at 10:51 Comment(0)
A
-1

It is not very clear if someone is going to use your code through an API. You are also missing an opportunity to validate the input, if you are going to require some later.

Academic answered 3/8, 2011 at 14:18 Comment(1)
Input validation should not be a problem here. As I said, this class is supposed to be immutable, so the only input that goes in is through the constructor. And there I can do validation if I ever need one.Egidius
M
-2

Using public final may be fine for such small job, but it cannot be adapted as a standard practice,

Consider the situation below.

Public class Portfolio {
   public final String[] stocks;
}

Of course, being immutable, this object is initialized vis constructor, and then accessed directly. Do I have to tell you the problem in it? It’s evident!

Consider your client writing the code like below -

Portfolio portfolio = PortfolioManager.get(“Anand”);
Portfolio.stocks[0] = “FB”;
portfolio.calculate();

Is this doable? Your client libraries are able to manipulate the state of your objects, or rather able to hack within your runtime representation. This is a huge security risk, and of course tools like SONAR catch it upfront. But its manageable only if you are using getter-setters.

If you are using getters, you can very well write

   Public class Portfolio {
      private final String[] stocks;
      public String[] getStocks() {
          return Arrays.coptOf(this.stocks);
      }
   }

This prevents you from potential security threat.

Looking at the above example, using public final is strongly discouraged if you are using arrays. In such case, it cannot become a standard. A person like me, will refrain from using a code practice that cannot become a uniform standard across all data types. What about you?

Montagna answered 14/12, 2019 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.