简体   繁体   中英

Immutable Type: public final fields vs. getter

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?

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.

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.

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 ; }
       }
}

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 (eg 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.

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.]

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

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.

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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM