Java static builder method

Method Builder With Lombok @Builder

Join the DZone community and get the full member experience.

Overview

In this tutorial, we are going to explore the possibilities of generating method builders with Lombok’s @Builder annotation. The aim is to improve usability by providing a flexible way of calling a given method even if it has a lot of parameters.

@Builder on Simple Methods

How to provide a flexible usage for methods is a general topic that might take multiple inputs. Take a look at the following example:

void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, Long fifthParam, @NotNull Object sixthParam)

If the parameters that are not marked as not null are optional, the method might accept all the following calls:

method("A", "B", null, null, null, new Object()); method("A", "B", "C", null, 2L, "D"); method("A", "B", null, null, 3L, this); . 

This example already shows some problematic points such as:

  • The caller should know which parameter is which (e.g. in order to change the first call to provide a Long too, the caller must know Long is expected to be the fifth parameter).
  • Inputs must be set in a given order.
  • Names of the input parameters are not transparent.

In the meantime, from the provider’s perspective, providing methods with fewer parameters would mean massive overloading of the method name, such as:

void method(@NotNull String firstParam, @NotNull String secondParam, @NotNull Object sixthParam); void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, @NotNull Object sixthParam); void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, @NotNull Object sixthParam); void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, Long fifthParam, @NotNull Object sixthParam); . 

To achieve better usability and avoid boilerplate code, method builders could be introduced. Project Lombok already provides an annotation in order to make the use of builders simple. The example method above could be annotated in the following way:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call") void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, Long fifthParam, @NotNull Object sixthParam)

Thus, calling the method would look like:

methodBuilder() .firstParam("A") .secondParam("B") .sixthParam(new Object()) .call(); methodBuilder() .firstParam("A") .secondParam("B") .thirdParam("C") .fifthParam(2L) .sixthParam("D") .call(); methodBuilder() .firstParam("A") .secondParam("B") .fifthParam(3L) .sixthParam(this) .call();

In this way, the method call is much easier to understand and change later. Some remarks:

  • By default, a builder method (method to obtain a builder instance) on a static method is going to be itself a static method.
  • By default, the call() method will have the same throw signature as the original method.

Default Values

In many cases, it can be really helpful to define default values for the input parameters. Unlike some other languages, Java does not have a language element to support this need. Therefore, in most cases, this is reached via method overloading, having structures like:

method() < method("Hello"); >method(String a) < method(a, "builder"); >method(String a, String b) < method(a, b, "world!"); >method(String a, String b, String c)

While using Lombok builders, a builder class is going to be generated within the target class. This builder class:

  • has the same number of properties and arguments as the method.
  • has setters for its arguments.
Читайте также:  Изменение элементов массива питон

It is also possible to define the class manually, which gives the possibility to define default values for the parameters. In this way, the method above would look like this:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder") method(String a, String b, String c) < . acutal logic here . >private class MethodBuilder

With this addition, if the caller does not specify a parameter, the default value defined in the builder class is going to be used.

Note: In this case, we do not have to declare all the input parameters of the method in the class. If an input parameter of the method is not present in the class, Lombok will generate an additional property accordingly.

Typed Methods

It is a common need to define the return type of a given method through one of the inputs, such as:

public T read(byte[] content, Class type)

In this case, the builder class will also be a typed class, but the builder method will create an instance without a bounded type. Take a look at the following example:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder") public T read(byte[] content, Class type)

In this case, the methodBuilder method is going to create an instance of MethodBuilder without bounded type parameters. This leads to the fact that the following code will not compile (as would be required by Class , and is provided by Class ):

methodBuilder() .content(new byte[]<>) .type(String.class) .call();

This can be resolved by casting the input of type and use it as:

methodBuilder() .content(new byte[]<>) .type((Class)String.class) .call();

It will compile, but there is another aspect to mention: the return type of call method is not going to be String in this case, but still unbound T. Therefore, the client has to cast the return type like this:

String result = (String)methodBuilder() .content(new byte[]<>) .type((Class)String.class) .call();

This solution works, but it also requires the caller to cast both the input and the result. As the original motivation is to provide a caller-friendly way to invoke the methods, it is recommended to consider one of the two following options.

Override the Builder Method

As stated above, the root of the problem is that the builder method creates an instance of the builder class without a specific type parameter. It is still possible to define the builder method in the class and create an instance of the builder class with the desired type:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder") public T read(final byte[] content, final Class type) public MethodBuilder methodBuilder(final Class type) < return new MethodBuilder().type(type); > public class MethodBuilder  < private Classtype; public MethodBuilder type(Class type) < this.type = type; return this; >public T call() < return read(content, type); >>

In this case, the caller does not have to cast anytime and the call looks like this:

List result = methodBuilder(List.class) .content(new byte[]<>) .call();

Casting in the Setter

It is also possible to cast the builder instance within the setter of the type parameter:

@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder") public T read(final byte[] content, final Class type) public class MethodBuilder  < private Classtype; public MethodBuilder type(final Class type) < this.type = (Class)type; return (MethodBuilder) this; > public T call() < return read(content, type); >>

Using this way, there is no need to define the builder method manually, and from the caller’s perspective, the type parameter is handed over just as any other parameter.

Читайте также:  Как обновить настройки php

Conclusion

Using @Builder on methods can bring the following advantages:

  • More flexibility on the caller’s side
  • Default input values without method overloads
  • Improved readability of the method calls
  • Allowing similar calls via the same builder instance

In the meantime, is also worth mentioning that in some cases, using method builders can bring unnecessary complexity on the provider’s side. Various examples for method builders are available on GitHub.

Opinions expressed by DZone contributors are their own.

Источник

Builder¶

The Builder pattern is another way to create objects. It is most often used to create objects that contain many fields. Objects that contain many fields (e.g. 4 or more) can also be created using constructors, but then if we want to initialize only some of these fields:

  • we have to write a very large number of constructors.
  • we could pass null in the constructor (using null as arguments is a bad approach).
  • we have to remember the order of arguments in the constructor.

The Builder pattern eliminates all these problems.

Construction¶

In order to create a builder class for a certain class, we have to create:

Both approaches are similar, but the first one prevents users from using the constructor to create a target object. It doesn’t matter which one you choose, we always have to implement:

  • «configuring» methods for each field, which look like regular setters, but also give the option of calling the next builder method after the dot.
  • the «build» method, which creates the target object based on the values set by the «configuring» methods.

With this approach, we can call any subset of «configuring» methods in any order.

ATTENTION: To call subsequent methods after the «configuring» method, each of them must return the this reference.

Inside class builder¶

The following example shows an example implementation of the Builder pattern.

In the example above, notice that the constructor of the Weapon class is private. The Builder class is a static class defined inside the Weapon class. This class has access to all fields and methods (even private) defined directly in the Weapon class. The Weapon class in this case can only be created with the help of a builder. Access to the static class defined in the class is obtained exactly the same as to the static field, i.e. after the . character, e.g. Weapon.Builder . The Builder class has a default public constructor, which we can use as follows: new Weapon.Builder() .

ATTENTION: «Configuring» methods can have any name. One convention is to use the word ‘with’ in their name. The build method is often called build or create but its name is also unrestricted.

Builder in a separate class¶

We can also create a Builder in a separate class, e.g.:

Читайте также:  Installing java on linux debian

Note that the ToyBuilder class uses the constructor of the Toy class, which has the package private (default) access. ToyBuilder class is in the same package as the Toy class. If these classes are in different packages, the constructor of the Toy class have to be public.

Builder with Lombok use¶

The builder implementations are very similar for each class. The creators of the lombok library wanted to remove the need to write the builder code, it can be generated using the @Builder annotation. The following example also uses other annotations from this library:

  • @Data — generates getters and setters for all fields, along with toString , equals and hashCode methods
  • @NoArgsConstructor — generates a no-argument constructor
  • @AllArgsConstructor — generates a constructor for all the fields of the class

Remember that to get to the lombok generated builder, we need to call the static builder method.

Источник

Паттерн (Шаблон) Builder

Java-университет

Этот шаблон рекомендуется использовать, когда класс имеет большое количество однотипных параметров и трудно запомнить их порядок и назначение.

Паттерн (Шаблон) Builder - 1

 public class Good < public final int a; public final int b; public final int c; public final int d; public final int e; public final int f; //Реализация Builder через статический внутренний класс public static class Builder< //Обязательные параметры public int a; public int b; //Необязательные параметры со значениями по умолчанию public int c = 0; public int d = 0; public int e = 0; public int f = 0; //Конструктор с обязательными параметрами public Builder(int a, int b)< this.a=a; this.b=b; >//Методы с возвращающим типом Builder для необязательного параметра с, d, e, f, public Builder c (int val) < c = val; return this; >public Builder d (int val) < d = val; return this; >public Builder e (int val) < e = val; return this; >public Builder f (int val) < f = val; return this; >//Метод с возвращающим типом Good для генерации объекта public Good buidl() < return new Good (this); >private Good(Builder builder)

Теперь в методе main при создании объекта вызывается конструктор статического класса Builder с обязательными параметрами. Затем через точку вызываются все необходимые необязательные параметры. В завершение вызывается метод buidl(); для генерации объекта.

 Good good = new Good.Builder(40, 20) .c(2) .d(4) .e(23) .f(9) .buidl(); 

Если хочу создать объект с двумя обязательными и одним необязательным параметром, это будет выглядеть так:

 Good good = new Good.Builder(40, 20) .c(2) .buidl(); 
 Good good = new Good.Builder(40, 20) .buidl(); 

Источник

Оцените статью