The one thing keeping me away from Java still is the awful experience around modeling data.
Compared to something like a case class in Scala or even autoproperties in C#, writing classes with a bunch of getter and setter methods just to keep state is really tedious.
Use public fields, add getters and setters if you have to. Serialize your POJOs using Jackson. All modern data frameworks can deal with public fields just as well as getter/setter combinations.
(Unless you are publishing a public API. In that case, you can't easily convert a public field to a private field/getter/setter setup if needed, as the cat is out of the bag and clients have code against the public field.)
Do I wish there were auto properties like C#? Oh yeah, I'd love that. But I do find modeling fairly easy.
I also lament having to manually maintain equals/compare/hashCode/toString implementations. However, with IDE auto-generation of code (using IntelliJ) makes this merely noisy and annoying, and not a time sink or a constant source of dumb errors.
I'm seriously tired of this argument against getters and setters, just generate them with the IDE some also support collapsible regions with a start and end comment, and forget about them.
So considering you can just generate and forget why is it tedious?
Because it's actually useless, having the work been done by someone else or something else means the work is still done, and you still depend on that 3rd-party.
I was strictly speaking about getters and setters, and I do maintain that they are completely stupid and useless. If you're writing your 10 kLOC side-project, then sure, just use them and take comfort in knowing that there's a tool out there that you can just add to the toolset. You're (almost) the only one working on this project, there are no clear steps for how to setup a working environment because you gradually built it along the road.
But when you're on a 10 MLOC projects that's been touched by hundreds of people for decades, most of which just can't program and aren't there anymore to fix the mess they left, with untouched original parts still being the core of your system, with nobody having a clear picture of how the whole thing works because it's been divided in so many parts that interact through a poorly-defined, undocumented interface, then you become extremely wary of adding anything because you don't want to complicate things further, but you still have to make it work, because it actually solves a problem for the customer.
The more you can remove, and the less you need, the happier you will be. Using lombok or having the IDE do it may solve the problem, but there is a much better alternative: remove the problem altogether.
Because it's not about tooling, it's about being explicit vs implicit. Though, there is something to be said about having a nice default that you can easily invoke at the code-level.
The best argument is that the compiler doing it add complexity to the language. One might say adding complexity to the language is justified if it means removing complexity from the programmer's workload, but in many languages that attempt to solve this problem that's not how it works out.
Most solutions to this amount to allowing programmers to intercept field access via getters/setters. This way they can just use public fields and switch to getters/setters when they need to add special behavior without breaking the interface. Pseudocode:
// using methods; no special behavior
class Foo {
private bar
public get_bar() { return bar }
public set_bar(_bar) { bar = _bar }
}
// using methods; special behavior
class Foo {
private bar
public get_bar() { return decode(bar) }
public set_bar(_bar) { bar = encode(_bar) }
}
// using getters/setters; no special behavior
class Foo {
public bar
}
// using methods; special behavior
class Foo {
public get bar { return decode(bar) }
public set bar(_bar) { bar = encode(_bar) }
}
Rather than eliminate complexity from the programmer's workload, this just moves it elsewhere. Instead of tedium, which is something that can be alleviated by simple tooling (copy/paste, editor macros, templated code snippets), you get a constant concern over whether a field access will have unexpected behavior, which is something that can only be alleviated by complex tooling (semantic code analysis).
So ultimately this approach adds complexity to the language/compiler, adds complexity to the tooling, and has no effect on the programmer's workload.
I'm trying to think of a better approach and the best I can come up with right now is a macro system in the language (one that makes it very clear where a macro is being expanded, to avoid the same problem of "constant concern"). For example:
macro #generate_accessors(field) {
let paramname = unique_identifier()
return #[
public #[ identifier_from_string("get_" + field.as_string()) ]() { return #[ field ] }
public #[ identifier_from_string("set_" + field.as_string()) ](#[ paramname ]) { #[ field ] = #[ paramname ] }
]
}
// no special behavior
class Foo {
private bar
#generate_accessors(bar)
}
// special behavior
class Foo {
private bar
public get_bar() { return decode(bar) }
public set_bar(_bar) { bar = encode(_bar) }
}
But I haven't seen any language where this is the canonical approach.
You also have that problem in a language with non-synthesized setters, because there's no guarantee that they don't do anything - and people avoid permitting direct field access, so you'll be going through a setter with unknown behavior.
That being said, if you're throwing off side-effects like mad in setters, short of prohibiting either mutability or side-effects, there's not much the language can do to prevent that.
I really prefer Groovy's approach - default get/set behaviour is provided at compile time, but you can override in code with explicit getter/setter methods. Really seems to be the best of both worlds.
Or just use public instance variables, really. Unless you plan on explicitly specifying how your internal variables are going to be used and accessed, you don't get a free-lunch.
Meijer or Oderski advised to use Kotlin / Ceylon to lift the burden of Java POJO. They have enough syntactic sugar and type niceties to make you happy again.
That's because Java is object-oriented. Data and the functions that operate on that data are not supposed to be separated. A clean object-oriented design has no plain getters and setters.
Except that encapsulation doesn't make a lot of sense when you're just modelling information.
For example, suppose you have an Employee class that contains the employee ID, name, birth day, etc. Employee records don't do anything. They're just data. Hiding that data makes no sense if the point of their existence is just to hold the data.
You don't simply model information in an OO language, everything is an object, you're supposed to model your domain.
I don't reach into the employee class and "get" the employee ID so I can do something with it. The employee class knows what to do with their own ID.
Just like a cashier doesn't reach into a Person's brain for their debit code. The cashier asks them to type it into the keypad, and points them where to do it if it isn't obvious.
Low-level accessors to an "object's" state are almost never an accurate representation of the domain you are modeling -- they're bad design.
If you need them, you've probably missed a piece of the puzzle.
That being said, most programmers don't need to be doing OO at all. The level of guarantee it provides is, for most people, not worth the cost. In those cases, don't use Java.
We will all be having this same argument about functional languages in 10 years. Mutability is not functional, if you're relying on mutability you're probably just doing procedural code wrapped up in functional decorations.
Well, if we're talking Java, not everything is an object. None of the primitive types are, and Java has static methods.
> Just like a cashier doesn't reach into a Person's brain for their debit code. The cashier asks them to type it into the keypad, and points them where to do it if it isn't obvious.
OK, so I picked a bad example. What if you're dealing with records of tree data? Are you suggesting that modelling the domain means that trees should know what to do with the information about their diameter? In what way is that modelling a domain?
In software we're rarely writing simulations of the real world, and even then, it doesn't always make sense to have objects hide information that other agents could just observe about them.
We're usually dealing with information, not actors.
Compared to something like a case class in Scala or even autoproperties in C#, writing classes with a bunch of getter and setter methods just to keep state is really tedious.