1

effective java has a statement about the access control: Even if a field is final and refers to an immutable object, by making it public you give up the flexibility to switch to a new internal data representation in which the field does not exist.

I don't understand what it means. Appreciate if someone can explain.

  • If some other code relies on the fact that a final field is, for example, an `ArrayList` and we want to change it to another `List`-implementationn, the other code will break. – Turing85 Apr 22 '22 at 19:12
  • @Turing85 Thanks for your comments. But final field pointing to an immutable obj is something can never be changed right? – pnpjunction Apr 22 '22 at 19:17
  • It ties other code into implementation. Were you to change the class so the field no longer exists, there would be issues. – Kayaman Apr 22 '22 at 19:19
  • We can always change the type in the source code. That is what Josh Bloch is talking about. – Turing85 Apr 22 '22 at 19:19
  • @pnpjunction It can't be changed *while the program is running*. But when your boss comes and says "hey, requirements have changed!", then someone has to go update the code. `final` won't save your variable from the backspace key of a future programmer. – Silvio Mayolo Apr 22 '22 at 19:20

1 Answers1

3

Unlike newer languages like Kotlin and Scala, Java makes a very clear distinction between fields and methods. So if you've got a

public final Foo foo;

Then that's always pointing to an actual field in memory. It can never be a method, and it can never run arbitrary code. The call myInstance.foo is always direct memory access, which is contrary to OOP principles. When I ask an instance for something, it should be the instance's decision how to handle that request, not mine.

In Kotlin, properties can have getters and setters, so even if we have something that looks like a field, it might call a method to access it. This allows us to future-proof our code in advance. We can implement an actual instance variable now,

val foo: Foo = myConcreteInstance()

and then, down the road, when foo needs to go make a database query or something because we're scaling our application up, we can change the implementation

val foo: Foo
  get() = loadFooLazily()

private fun loadFooLazily(): Foo {
  // Some complicated code ...
}

Crucially, this affects nobody downstream. Everybody else downstream still writes myInstance.foo and works with the new code. It's not a breaking change.

We can't do this in Java. myInstance.foo is always a variable, not a method. So Effective Java suggests always accessing instance variables through a method in order to future-proof them. If we write

private final Foo foo;

public Foo getFoo() {
  return foo;
}

Then we can change getFoo in the future without breaking other code. It's a difference in philosophy: In Java, methods are the OOP construct while instance variables are strictly an implementation detail. In most newer languages, "instance variable" has been replaced with a high-level construct called a "property" which is intended to be used in an OOP way.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116