Modern Java - do we need explicit class declarations?
Tags: java, functional, designpatterns, architecture
Published: 2023-12-31 15:10:39

When we develop a **public API**, we hope it will live long or forever.
Do we? Then why do we still write and use **public constructors**? They **do not present any abstraction**. Once you mentioned in your code some constructors, you nailed them to your program for eternity.

In this article I will explain, how to create objects in a better way, without declaring constructors or classes explicitly.

## We need to write a program

Now, let us imagine we want to write a program that would paint the following image :
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rysz8l022zggzept2agm.png)
Let us write code then.
First, we have to declare common abstractions.
```
import java.awt.Graphics2D;
interface Drawing
{
    void draw(Graphics2D graphics);
}
```
Good enough.
Now we may write a code that uses our `Drawing` interface to paint something into image :
```
static BufferedImage paint(int width, int height, Drawing drawing)
{
    var image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    drawing.draw((Graphics2D)image.getGraphics());
    return image;
}
```
After that, we need an implementation for a **circle drawing** :
```
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Color;
class Circle implements Drawing
{
    Point center;
    int radius;
    Color color;
    Circle(Point center, int radius, Color color)
    {
        this.center = center;
        this.radius = radius;
        this.color = color;
    }
    @Override
    public void draw(Graphics2D graphics)
    {
        graphics.setColor(color);
        int dr = radius << 1;
        graphics.fillOval(center.x - radius, center.y - radius, dr, dr);
    }
}
```
Now a **box drawing** :
```
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Rectangle;
class Box implements Drawing
{
    Rectangle rect;
    Color color;
    Box(Rectangle rect, Color color)
    {
        this.rect = rect;
        this.color = color;
    }
    @Override
    public void draw(Graphics2D graphics)
    {
        graphics.setColor(color);
        graphics.fillRect(rect.x, rect.y, rect.width, rect.height);
    }
}
```
As we see, our `paint` method does accept only one `Drawing` object, so, we need an implementation that would **combine many drawings into one** :
```
class Scene implements Drawing
{
    Drawing[] drawings;

    Scene(Drawing... drawings)
    {
        this.drawings = drawings;
    }

    @Override
    public void draw(Graphics2D graphics)
    {
        for(var drawing : drawings) drawing.draw(graphics);
    }
}
```
And, all we have left is to write the `main` function :
```
public static void main(String[] args) throws Throwable
{
    var file = new File(args[0]);

    int width = 200;
    int height = 200;

    var drawing = new Scene(
        new Box(new Rectangle(0, 0, width, height), Color.WHITE),
        new Circle(new Point(100, 100), 50, Color.RED),
        new Box(new Rectangle(25, 25, 100, 50), Color.GREEN));
    var image = paint(width, height, drawing);
    ImageIO.write(image, "png", file);
}
```
It took **85** lines of code. This code is flexible, maintainable, readable. But, don't you see its redundant parts? Almost **30%** of lines are about **fields and constructors declaration**.
Also, we have repeated code here : `Graphics2D.setColor` calls.
**How may we improve this code?**

## Static functions are better than constructors

What constructor is? A function, that we call to create a new object. Wait...a function, that we call to create a new object...**do we need constructors at all**?

**Java** has a brilliant syntaxes - anonymous class expression and lambda expression. Using such expressions we may write code without declaring any classes.

## What we are going to do

Here is an example :
```
interface FloatFunction
{
    float apply(float a);
}
class SumFloatFunction implements FloatFunction
{
    float addition;
    SumFloatFunction(float addition)
    {
        this.addition = addition;
    }
    @Override
    public float apply(float a)
    {
        return a + addition;
    }
}
```
Let us replace class declaration with a static function declaration inside of the `FloatFunction` interface :
```
interface FloatFunction
{
    float apply(float a);

    static FloatFunction sum(float addition)
    {
        return a -> a + addition;
    }
}
```
Do you see, how small our code now is?
More than that, now we may see all `FloatFunction` interface implementations available - we don't have to read documentation each time to refresh information about existing implementations. All what we need is declared in the one place.

Using our new approach, let us change solution with `Drawing` interface.
```
interface Drawing
{
    void draw(Graphics2D graphics);

    static Drawing box(Rectangle rect, Color color)
    {
        return graphics ->
        {
            graphics.setColor(color);
            graphics.fillRect(rect.x, rect.y, rect.width, rect.height);
        };
    }
    static Drawing circle(Point center, int radius, Color color)
    {
        return graphics ->
        {
            graphics.setColor(color);
            int dr = radius << 1;
            graphics.fillOval(center.x - radius, center.y - radius, dr, dr);
        };
    }
    static Drawing scene(Drawing... drawings)
    {
        return graphics ->
        {
            for(var drawing : drawings) drawing.draw(graphics);
        };
    }
}
```
And `main` function also has changes :
```
public static void main(String[] args) throws Throwable
{
    var file = new File(args[0]);

    int width = 200;
    int height = 200;

    var drawing = Drawing.scene(
        Drawing.box(new Rectangle(0, 0, width, height), Color.WHITE),
        Drawing.circle(new Point(100, 100), 50, Color.RED),
        Drawing.box(new Rectangle(25, 25, 100, 50), Color.GREEN));
    var image = paint(width, height, drawing);
    ImageIO.write(image, "png", file);
}
```
Looks much better? Right. And that was only a first step.

## Methods with default implementation

As I said earlier, I don't like we have `Graphics2D.setColor` calls repeated. If we would paint 1000 boxes of the same color, do we need to call `setColor` method 1000 times? No.

**What then we may do?**

Let us create a wrapping function, that will change color as many times as we need :
```
default Drawing ofColor(Color color)
{
    return graphics ->
    {
        graphics.setColor(color);
        draw(graphics);
    };
}
```
And, we are going to change `Drawing` static functions as well, because now they have no business with `Color`.
```
interface Drawing
{
    void draw(Graphics2D graphics);

    static Drawing box(Rectangle rect)
    {
        return graphics ->
        {
            graphics.fillRect(rect.x, rect.y, rect.width, rect.height);
        };
    }
    static Drawing circle(Point center, int radius)
    {
        return graphics ->
        {
            int dr = radius << 1;
            graphics.fillOval(center.x - radius, center.y - radius, dr, dr);
        };
    }
    static Drawing scene(Drawing... drawings)
    {
        return graphics ->
        {
            for(var drawing : drawings) drawing.draw(graphics);
        };
    }
    default Drawing ofColor(Color color)
    {
        return graphics ->
        {
            graphics.setColor(color);
            draw(graphics);
        };
    }
}
```
Then code that creates many boxes of the same color would look like that :
```
static Drawing manyBoxes(Color color, Rectangle... rects)
{
    var boxes = Stream.of(rects).map(Drawing::box).toArray(Drawing[]::new);
    return Drawing.scene(boxes).ofColor(color);
}
```
## Conclusion

I don't propose to always avoid use of classes. We may need them to describe **data structures or program entries**. But, anywhere else, I would like to use lambda or anonymous class expressions only.

After all, functions let us to decouple from a real program structure and to forget about exact implementation. You can't redirect a constructor call, but you can redirect a function call.
When you develop a large product, **you can't rename class or change its accessibility**, if it had once a public constructor. **Give self more comfort and space, use functions instead of constructors**.
That's all for today, thanks if you read this article to the end.
**Please, share your thoughts in comments.**