An object with public final fields that get loaded from public constructor parameters effectively portrays itself as being a simple data holder. While such data holders aren't particularly "OOP-ish", they are useful for allowing a single field, variable, parameter, or return value to encapsulate multiple values. If the purpose of a type is to serve as a simple means of gluing a few values together, such a data holder is often the best representation in a framework without real value types.
Consider the question of what you would like to have happen if some method Foo
wants to give a caller a Point3d
which encapsulates "X=5, Y=23, Z=57", and it happens to have a reference to a Point3d
where X=5, Y=23, and Z=57. If the thing Foo has is known to be a simple immutable data holder, then Foo should simply give the caller a reference to it. If, however, it might be something else (e.g. it might contain additional information beyond X, Y, and Z), then Foo should create a new simple data holder containing "X=5, Y=23, Z=57" and give the caller a reference to that.
Having Point3d
be sealed and expose its contents as public final fields will imply that methods like Foo may assume it's a simple immutable data holder and may safely share references to instances of it. If code exists that make such assumptions, it may be difficult or impossible to change Point3d
to be anything other than a simple immutable data holder without breaking such code. On the other hand, code which assumes Point3d
is a simple immutable data holder can be much simpler and more efficient than code which has to deal with the possibility of it being something else.