This small library aims to provide a blueprint for value-based classes on JVM before Project Valhalla becomes widely available.
These blueprints are designed to hold a single, usually primitive, value while at the same time giving them distinct type information and thus leveraging Java type system. The aim is to help you create your complex applications by spending less time on frustrating tasks fixing bugs that are the result of using wrong variable in function call, which is often the case in Stringly-typed frameworks (we've all seen these).
For example, let's say that you have an use case where you have to update account balance for a customer, while saving Point Of Sale identifier and an optional comment:
public void updateAccountBalance( Long customerId,
Long pointOfSaleId,
Long delta,
@Nullable String comment ) {
// [...]
}
Now, somewhere in the middle of your business logic you do something that boils down to:
public void processTransaction( TransactionContext context, Customer customer ) {
// [...]
updateAccountBalance( customer.getId(),
context.getId(),
context.getPriceInCents(),
context.getTransactionTitle() );
// [...]
}
Now, this is fine and dandy, but what happens when the method to update the balance changes its signature without changing all invocations? How soon would you know that something isn't right? Of course, this should come up in unit tests, but that depends on their quality.
Instead of relying on something that might be there and might find the mistake, the idea is to enforce correctness at compilation stage. This way most of the mistakes will be weeded out by the time the tests are ran.
Side note: Check out errorprone too - it tries to achieve similar goal by being performing the static analysis at compile time. Findbugs and PMD are helpful as well. All three have Maven plugins for easy integration into your build.
Let's take a look at this method signature:
public void updateAccountBalance( CustomerId customerId,
PointOfSaleId pointOfSaleId,
PriceInCents delta,
@Nullable String comment ) {
// [...]
}
...and tell me, how hard you would have to try to make an error in here to pass as an honest mistake? :-)
Of course, you should use these value-based objects where it is reasonable. Data model classes, complicated APIs and business logic are a good places for them. On the other hand tight loops in graphics processing are not.
The JDK Record feature is proposed to target JDK14: https://openjdk.java.net/jeps/359
This feature would replace most of what this library is providing with JDK built-in stuff. If you can migrate when JDK14 is released, you should migrate. If your project cannot migrate or you are stuck on a lower JDK version, you can keep on using this library.
I don't plan on archiving/abandoning/removing it anytime soon (hey, i use it myself ;) ), but at the same time there's just not that much one can innovate in this space without making breaking changes, and I don't want to make breaking changes. I don't want to bump the JDK requirement either, because I know people are still using JDK 1.8 and below.
The library is available in Maven Central repository. You can use it in your projects via this dependency:
<dependency>
<groupId>com.tguzik</groupId>
<artifactId>valueclasses</artifactId>
<version>1.0.2</version>
</dependency>
See Javadoc or the demo code below for more information.
Here are some examples of different types of value-based classes you can create with this library:
import com.tguzik.value.adapters.JaxbStringValueAdapter;
import com.tguzik.value.StringValue;
// [...]
@Immutable
@XmlJavaTypeAdapter( value = FirstName.Adapter.class )
public final class FirstName extends StringValue
{
private FirstName( String value ) {
super( value );
}
public static FirstName valueOf( String firstName ) {
return new FirstName( StringUtils.trimToEmpty( firstName ) );
}
public static class Adapter extends JaxbStringValueAdapter<FirstName>
{
@Override
protected FirstName createNewInstance( String value ) {
return FirstName.valueOf( value );
}
}
}
import com.tguzik.value.adapters.JaxbValueAdapter;
import com.tguzik.value.Value;
// [...]
@Immutable
@XmlJavaTypeAdapter( value = CustomerId.Adapter.class )
public final class CustomerId extends Value<Long>
{
private CustomerId( Long value ) {
super( value );
}
public static CustomerId valueOf( Long value ) {
/* You can plug additional validation (recommended) or an instance cache here, if you need one. */
return new CustomerId( value );
}
/** For the purpose of this example let's assume you use JaxB-compatible library */
public static class Adapter extends JaxbValueAdapter<Long, CustomerId>
{
@Override
protected ClientId createNewInstance( Long value ) {
return ClientId.valueOf( value );
}
}
}
public final class LastName extends StringValue { [...] }
public final class EmailAddress extends StringValue { [...] }
Now, assuming that you didn't go out of your way to create these value-based objects mutable (which you shouldn't), you can create this class to hold the data about customer:
/*
* Reflection-based .hashCode(), .equals() and .toString() are already defined in
* the com.tguzik.objects.BaseObject class.
*/
@Immutable
@ParametersAreNonnullByDefault
public final class Customer extends BaseObject {
private final CustomerId customerId;
private final FirstName firstName;
private final LastName lastName;
private final EmailAddress emailAddress;
// ..and whatever else you need
public Customer( CustomerId customerId,
FirstName firstName,
LastName lastName,
EmailAddress emailAddress ) {
this.customerId = Objects.requireNonNull( customerId );
this.firstName = Objects.requireNonNull( firstName );
this.lastName = Objects.requireNonNull( lastName );
this.emailAddress = Objects.requireNonNull( emailAddress );
}
public CustomerId getCustomerId() {
return customerId;
}
public FirstName getFirstName() {
return firstName;
}
public LastName getLastName() {
return lastName;
}
public EmailAddress getEmailAddress() {
return emailAddress;
}
}
Outside of unit test dependencies, this project requires following third party libraries:
org.apache.commons:commons-lang3
javax.xml.bind:jaxb-api
com.tguzik:annotatons
(includes JSR 305 annotations)- JDK 1.7+
Source code in this repository is available under MIT License.