Image source: PIRO4D pixabay.com
Probably you already had to deal with objects that change their structure according to the laws of their internal development or depending on external conditions. In practice, this means that parts of the object for the outside world can sometimes be accessed, and sometimes not.
What are these objects? Many service companies, online shops and banks provide different types of services depending on the time of day. Some devices may or may not perform certain actions depending on their state. If you read this text from the screen of a smartphone or laptop, it can stop showing it if the battery charge falls below a certain limit.
In this and the following post we will consider one simple object of this kind – a combination of a coffee machine and an electric boiler. But at first we are discussing how in Java you can implement access to elements with a dynamic structure, for example – our device.
It should immediately be stipulated that the physical structure of the device does not change with time. Speaking about the dynamics of the structure, we look at the device with the eyes of the user. If for some reason the device can not cook coffee for the customer, then this functionality is absent for him or her.
How we can express this with Java?
Suppose that we are given the task for programming of an control unit for such device and we need a method like
What type of object should this method return? After all, sometimes the device can give the customer coffee, and sometimes not.
There are not so many ways to solve this problem (at least known to me).
Return either object or null
The most common solution is to return some object in the positive case and null in the negative. The user must therefore always check the return value and use it only if it is not null.
The problem is that the user of your object should somehow guess about the possibility of returning the Null-object. A programmer calling your method in his code can pass it without checking as a parameter in a chain of calls to other functions, write to a list, etc. This means that a payout in the form of a NullPointerException can come much later and at a location substantially remote from the function call.
NullPointerException is a notorious champion among the errors of Java programmers, both in quantity and in time spent and nerves spent on their search and elimination.
It is interesting to note that this catastrophe has an author who called it his “billion-dollar mistake”. Null appeared much earlier than Java in 1965 when developing the language ALGOL.
After 44 years, Tony Hoare, the creator of this monster (but also a wonderful scientist) wrote in his article “Null References: The Billion Dollar Mistake” (QCon, August 25, 2009), (https://www.infoq.com/presentations/ Null-References-The-Billion-Dollar-Mistake-Tony-Hoare).
I called it my billion-dollar mistake … My goal was to ensure that all of the references were absolutely safe. But I could not resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
As the developer of the object, you can somewhat mitigate the situation using the annotated return result, for example:
@Nullabble CoffeePortion getCoffeePortion()
If the programmer uses a modern IDE, then in certain situations it will indicate to him the necessity of checking for a Null-object. But this does not always happen.
So, returning null as the result of an operation is bad. So we must avoid this. But how?
First, ask politely
One solution is to supply each get … method with a partner has … The user of the object should be somehow informed that before requesting a get .. it should ask if there is a value that can to be returned. In our case, the pair would look like this:
boolean hasCofeePortion() @Nullabble CoffeePortion getCoffeePortion()
If the first function returns true, you call its buddy function. The user can theoretically try not to call the first has...function. If the value at the time of the call is not available, the function get ... throws an Exception.
Thus, the problem of transferring a “dangerous” object through a chain of calls is excluded. This is the advantage of the approach. But the approach has two significant drawbacks. First, the number of functions is increased. Secondly, there is no guarantee that if at the time of the call has … the object was available, and by the time of calling get … it stills available. (In our simple example, this is not possible, but in complex systems where the state of objects depends on time or external influences, and to prepare the get … call one needs to process other operations, this danger is real.
So, first ask, and then take – not a good strategy. And why not vice versa? It is this idea that uses the following approach.
The object always returns, but not always alive
In this approach, the object always returns and it contains an additional attribute. This attribute tells us, roughly speaking, whether he is alive (available, active, present etc.) or not. By sharpening to the extreme, you can formulate this approach as follows: you get either a living object or its corpse. But even if it’s a corpse, it can a little talking.
Namely, the approach says that you can always call a object’s method of type isAvailable(), isActive(), isPresent() and so on. . Even by some “dead” object. And with a positive response, you can work with the object further.
An obvious drawback of this approach is the attaching on the object of a technical attribute, which is not explained in terms of its own functioning (business logic). In certain situations, this attribute may conflict with this logic. For example, imagine that a certain object is associated with two other objects. From the point of view of one object, it can be active or “alive”, but from the point of view of the other it can be “dead.”
However, from a technical point of view, this approach can have its own, sometimes unexpected, advantages. For example, if we need to save or send a composite object in the form of JSON or XML, it’s reasonable to send it completely, including its currently deactivated parts. It is so because after the its retriving (reading) from a file or stream it can be be activated.
So, this approach is good because it does not result in NullPointerException, but is “non-natural”. Is it possible to find something similar, but without the described drawbacks?
An array or list returns, possibly empty
One of the most recommended by experts before the appearance in Java 8 Optional approaches was the approach described below.
The idea of it is extremely simple. Instead of the object, we return an array or list of objects. This array or list can be either empty or contain exactly one object.
In fact, we force the users of our method immediately after calling the method to analyse the array or list. It is unlikely that he will transfer this list further as a parameter to other methods.
The disadvantage of the method is its obvious distinction from simular methods. It returns other object type as the method that returns an elementary value or the method that returns an always existing object.
And to overcome this contradiction, as well as for other reasons, Java developers decided implement Optional.
But about this – in the next post. In it we will consider such issues as:
Optional – what is it really?
Using Optional in our example
Using Optional in access chains
Using Optional in special situations of processing objects (Persistence, Validation)
When we must/can use it
Optional – is it part of the functional programming?