JavaBeans were created by Sun Microsystems in the 1990s to address a couple of shortcomings of the Java language. They wanted a way to make fields either read-only or read-and-write. They also wanted to make Java code better readable by design tools, such as NetBeans. JavaBeans offered a solution to both these problems: Setters and getters would control wether a property was writable and the simplistic interface would allow tools to be able to work with them easily. But using JavaBeans introduced a terrible pattern into software design that unfortunately survives up to this date.
Because JavaBeans are inherently mutable and because they are initialized to an incorrect state which is gradually corrected by setter calls, they should not be used in modern software design. Instead, we need to use patterns which use immutable objects, even in languages such as Java, to make the code easier to reason about and behave more reliably and run faster in multi-threaded environments.
Why are JavaBeans a bad pattern to use?
Mutable objects increase complexity
Over time it became increasingly clear that programming with immutable objects reduced errors in software systems. While in the 90s C++ and Java tried to control the visibility of private state to reduce complexity, in the following years making objects immutable was considered one of the best ways to reduce complexity. Because JavaBeans are mutable, they go against what is considered good software design today.
Mutable objects create a concurrency nightmare
Multi-threaded coding is very likely one of the hardest type of programming there is. I often think that one of the reasons why the Java language was kept so extremely simple by Sun , was that it has introduced threads into the mainstream programming world, and by doing so it unleashed a myriad of hard to solve problems. So Sun tried to compensate by keeping everything else very simple. It turns out that if we make our objects immutable, they become much easier to deal with even in multi-threaded environments. So JavaBeans fail this test as well.
Mutable objects require synchronization which is very slow
It wasn’t just the complexity of multi-threading but also the fact that imperative multi-threaded code required mutex locks called synchronization. Synchronization uses memory barriers in the processor which make them very computationally expensive. Even worse, code that uses locks can dead-lock. Imagine a deadlock in a trading system or air traffic control system, or software that your surgeon uses when he operates on you. Well, yes, that is why there’s a trend away from blocking code. This trend gave birth to Akka for instance. So this is yet another reason to avoid JavaBeans
How to fix these problems using immutables
The design complexity issue
Think of a complex object graph such as what you would have in the UI of a business application. Lets assume you are building a book keeping application and you created 3 screens to look at the chart of accounts from various points of view. One view might just edit and show the debits and credits for an account, another might show the PNL (Profit and loss report), yet a third one might summarize your expected tax liability, and so on. You might create a model representing this data. So you will apply your credits and debits to the model and all views will share this model to display their data.Following one of the classic design books written in the 90s, you might create an API that will take mutable JavaBeans to update the model. So when one view would edit an account to add a transaction, it would call model.addTransaction(TransactionBean t), which would add the transaction to the list of transactions. Then you would send an event to all views telling them to redisplay themselves because the model changed.
So far so good right? No not really! Anybody can change anything at any time which makes each component a moving target.
It will take some time and a certain application size before the problem start showing up
Then months pass by and you hire new programmers and the old one are leaving, while your app has grown to 30 screens. There are many views, reports, and different kinds of wizards to let you edit the accounts more easily. You start having all kinds of strange problems and you don’t know why. For example, a screen that shows summaries doesn’t seem to reflect the most recent changes but when you look at the detail and the code that computes the code they seem to be correct. So how can this be? Well, a transaction was edited in another component and a new programmer forgot to send you an event to let you know this fact. Because it was a mutable JavaBean, that you hold a reference to, you will see these changes, even though your component was not notified that there was a change. But of course your totals will be wrong and any other internal processing your component did based on that bean will be stale, and so wrong.
Let’s look at each of the mutable pieces that cause the problems
See full source code.
So imagine you created a Transaction bean like this. It’s every bit as verbose as you would expect any Java bean to be:
class Transaction{
int amount = 0;
Date date = new Date();
String desc = "";
public Transaction(int amount, Date date, String desc) {
[..]
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
[..Other setters and getters..]
}
Then you created an Account object. Notice how easy it is to corrupt its state because transactions are mutable and anyone with a reference to them, can change the state of Account, with Account knowing nothing about it:
class Account{
private Map<Integer, Transaction> transactions = new HashMap<>();
public void addOrUpdateTransaction(Transaction t) {
this.transactions.put(t.id, t);
}
public Collection<Transaction> getTransactions() {
return transactions.values();
}
[..other getters and setters..]
}
Then various screens holding references to accounts:
interface AccountChangedListener {
void accountEdited(Account a);
}
static class AccountEditor {
private Account a;
private List<AccountChangedListener> listeners;
public AccountEditor(Account a, List<AccountChangedListener> listeners){}
public void addTransaction(Transaction t){
a.addOrUpdateTransaction(t);
listeners.forEach(l -> l.accountEdited(a));
}
public void updateTransaction(Transaction t){
a.addOrUpdateTransaction(t);
listeners.forEach(l -> l.accountEdited(a));
}
}
static class PNLReport implements AccountChangedListener{
private List<Account> accounts;
private int totalFunds = 0;
public PNLReport(List<Account> accounts){
this.accounts = accounts;
computeTotals();
}
public void computeTotals(){}
public void print(){}
@Override
public void accountEdited(Account a) {
computeTotals();
}
}
static class BalanceSheet implements AccountChangedListener{
[..Same kind of code as PNLreport..]
}
Its very easy to corrupt the application state as shown below, just by calling a setter on any transaction, :
//First you initialize everything
Account a = new Account();
List<Account> chartOfAccounts = Arrays.asList(new Account[]{a});
PNLReport pnl = new PNLReport(chartOfAccounts);
BalanceSheet balances = new BalanceSheet(chartOfAccounts);
AccountEditor editor = new AccountEditor(a, Arrays.asList(pnl, balances));
editor.addTransaction(new Transaction(1, 100, new Date(), "First Transaction"));
pnl.print();
//Now you edit some transactions. This would already break things if done from another thread
Transaction t2 = new Transaction(2, 100, new Date(), "Second Transaction");
editor.addTransaction(t2);
editor.updateTransaction(new Transaction(1, 50, new Date(), "Modified First Transaction"));
//This worked because listeners were called
pnl.print();
//This will break things even from the same thread because listeners were not called so totals didn't get recalculated
t2.setAmount(1000);
pnl.print();
//This really won't be reliable because mutability makes it thread-unsafe
new Thread(new Runnable() {
@Override
public void run() {
pnl.print();
}
}).start();
The point is that the application state was modified underneath your component and now your component’s state is incorrect.
Convert everything to immutables and see the problems go away
Now, imagine changing your update mechanism to using some application level event listener, that components can use to exchange events. They don’t hold references to each other. They just all hold a reference to this event listener and talk to each other through events exclusively. These events, unlike before, will not contain JavaBeans. Instead they will contain immutable event objects only. So now when one component receives an event, that event will never change afterwards. Components can base their internal processing on the received event data, and set their internal state accordingly, such as computing our PNL figures. Because we are talking through events exclusively containing immutables, it will be impossible for another component to change your component’s state by accident.
Our new immutable objects will look like this
Look at the immutables version of this code.
JavaBeans will be turned into immutable events so there won’t be need for any methods at all. Each field is an immutable type:
class Transaction{
final public int id;
final public int amount;
final public long date;
final public String desc;
public Transaction(int id, int amount, long date, String desc) {
[..]
}
[..No getters and setters needed..]
We must do the same kind of change to all the other objects and pieces. Once convert everything to immutables, besides simplifying the design, we also are making everything thread safe. Here’s how the account object changes. See how we create an efficient copy instead of mutating Account, when we want to change its state. This is a common technique in functional coding:
class Account{
final public int accountId;
final public ImmutableList<Transaction> transactions;
public Account(int accountId){
this.accountId = accountId;
this.transactions = ImmutableList.of();
}
public Account(int accountId, List<Transaction> tList){}
public Account withTransaction(Transaction t){
return new Account(accountId, ImmutableList.<Transaction>builder().addAll(transactions).add(t).build());
}
public Account withTransactionReplaced(Transaction t){
return new Account(accountId,
ImmutableList.<Transaction>builder()
.addAll(transactions.stream().filter(x -> x.id != t.id).collect(Collectors.toList()))
.add(t)
.build());
}
[..]
}
The account editor would use volatile object references pointing to immutable account objects. This is automatically thread safe.
class AccountEditor{
volatile private Account a;
final private ImmutableList<AccountChangedListener> listeners;
public AccountEditor(Account a, List<AccountChangedListener> listeners){}
public void addTransaction(Transaction t){
a = a.withTransaction(t);
listeners.forEach(l -> l.accountEdited(a));
}
public void updateTransaction(Transaction t){
a = a.withTransactionReplaced(t);
listeners.forEach(l -> l.accountEdited(a));
}
}
Now the PNL Report will get a little more complicated because if want to make it automatically thread safe as well, we need a separate state object, so that we can swap the state change out in one go, and using a volatile reference this means we won’t have to synchronize either. This is another functional programming trick:
class PNLReport implements AccountChangedListener{
static class PNLState{
public ImmutableMap<Integer, Account> accounts;
public Integer total;
public PNLState(ImmutableMap<Integer, Account> accounts) {
this.accounts = ImmutableMap.copyOf(accounts);
this.total = computeTotals(accounts);
}
private int computeTotals(ImmutableMap<Integer, Account> accounts){
[..]
}
}
volatile private PNLState pnlState;
public PNLReport(Map<Integer, Account> accounts){
this.pnlState = new PNLState(ImmutableMap.copyOf(accounts));
}
@Override
public void accountEdited(Account a) {
Map<Integer, Account> m = new HashMap(pnlState.accounts);
m.put(a.accountId, a);
this.pnlState = new PNLState(ImmutableMap.copyOf(m));
}
[..]
}
How did this change the design?
Since other components can’t change your state, when working on a component, at any given time you can safely just consider the state of that component. You don’t always have to keep the entire application in mind because you have eliminated surprise state changes. This is the design complexity reduction that we gain by using immutables instead mutable JavaBeans. It’s much easier and cleaner to this way!
What this means for concurrency
Java’s original approach to concurrency was using threads. So how would one thread know that an object was modified by another thread. We were supposed to use synchronization to achieve this which made the code very complex. Like in the previous example where one component would not know that another modified a bean, here one thread would not know without explicit synchronization, that a bean was modified thread. The idea about synchronization is that before starting to read or write an object’s state, by the means of synchronization we ask the JVM for a memory barrier, which under the hood guarantees that an object will be in a consistent state showing whatever happened before, even if the changes were carried out by another thread. For example if another thread modified it, all those low level caches in the CPU that the thread was using internally to carry out our instructions, will be flushed so that our thread would see the changes in a consistent manner.
Well this sounds good until you start having to synchronize everything. Much of your code will be about synchronization, instead of about the business problem your application is trying to solve.
But as shown above, when coding with immutables, we can often avoid the need for synchronization because all threads will always see immutable objects in a consistent state without any extra work!
Immutables help you create non-blocking code
When you have to synchronize on any object that is shared between threads, you create code that is vulnerable to deadlocks. Imagine that there are two shared objects A and B. Two threads are using both objects. Thread 1 synchronizes on object A first, then on B, while Thread 2 does it in the reverse order. It’s very easily possible to create a deadlock here, each thread gets the first lock and waits for the other one forever.
So any programmer concerned with performance and reliable execution should do his best to avoid synchronization, if at all possible while still writing correct code. As stated before, immutables will remove this need in many cases, and with it they will remove dead-locks, live locks, and other kinds of undesirable blocking.
[formidable id=3 description=true]