Removing Duplicate Code With Lambda Expressions
If you don't want to use frameworks to handle duplicated code or JPA transactions, you can do it using Java's own syntax—lambda expressions and anonymous classes.
Join the DZone community and get the full member experience.
Join For FreeIn this article, we'll go over a concrete example of removing duplicated code by using lambda expressions.
For this demonstration, I will be showing a basic example requiring us to persist objects using JPA (Java Persistence API).
Suppose we are asked to write some code to do a simple save, update, and retrieve of a specific Entity class, using JPA (more on this is at copypasteisforword). To implement that, I will write a class called ADataAccessObject (just to name it somehow). Below, you will find what I would have written in my first development iteration — the make it work step.
public class ADataAccessObject {
private EntityManagerFactory emf;
public ADataAccessObject(EntityManagerFactory emf) {
this.emf = emf;
}
public void save(AnEntity anEntity) {
EntityManager em = this.emf.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
em.persist(anEntity);
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw new RuntimeException(e);
} finally {
em.close();
}
}
public void update(long id, String aNewPropertyValue) {
EntityManager em = this.emf.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
AnEntity anEntity = em.find(AnEntity.class, id);
anEntity.setAProperty(aNewPropertyValue);
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw new RuntimeException(e);
} finally {
em.close();
}
}
public AnEntity byId(long id) {
EntityManager em = this.emf.createEntityManager();
try {
return em.find(AnEntity.class, id);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
em.close();
}
}
}
Now let's write the client code that uses the class above:
public class Main {
public static void main(String arg[]) {
EntityManagerFactory emf = null;
try {
emf = Persistence
.createEntityManagerFactory("a-persistence-name");
ADataAccessObject dao = new ADataAccessObject(emf);
//store anEntity ...
dao.store(new AnEntity("aValuForTheProperty"));
//update anEntity ...
dao.update(1 l, "anotherValueForTheProperty");
//retrieving ...
AnEntity e = dao.byId(1 l);
} finally {
emf.close();
}
}
What doesn't look nice from the class ADataAccessObject above? As you might have noticed, each method seems to be pretty similar. The try/catch/finally skeleton with the creation of the EntityManager, the Transaction#commit, Transaction#rollback and the EntityManager#close, is duplicated.
To clarify a bit more, below are the sentences unique per method:
//from ADataAccessObject#store
em.persist(anEntity);
//from ADataAccessObject#update
AnEntity anEntity = em.find(AnEntity.class, id);
anEntity.setAProperty(aNewPropertyValue);
//from ADataAccessObject#byId
return em.find(AnEntity.class, id);
The other lines of code are duplicated in the three methods.
How can we remove that duplicated code? There are some options. You can use Dynamic Proxies or AspectJ. Or incorporate a framework like Spring to handle JPA transactions for you. Do we have a simpler approach? I don’t want to incorporate any frameworks, and I would love for the language itself to provide the syntactical constructions to do it.
What if the programming language allows you to pass a block of code as a method parameter? That sounds great because I can create a private method in my ADataAccessObject class with the try/catch/finally structure and receive each unique block of sentences by parameter.
Before Java 8, this was possible using Anonymous Classes. Lets moving ahead with this approach then.
For implementing this approach we have to create an Interface to specify the signature of the block of code that we need to pass by parameter. Described next:
public interface ABlockOfCode {
AnEntity execute(EntityManager em);
}
That Inteface above is the type of my block of code. Each block of code will respond to the execute method. It must receive the EntityManager by parameter and must return AnEntity.
Let's go through the second iteration of my code — the make it better step. I will refactor my ADataAccessObject Class a bit, adding some anonymous classes to eliminate duplicated code.
public class ADataAccessObject {
private EntityManagerFactory emf;
public ADataAccessObject(EntityManagerFactory emf) {
this.emf = emf;
}
public void store(AnEntity anEntity) {
transactionExecute(new ABlockOfCode() {
@Override
public AnEntity execute(EntityManager em) {
em.persist(anEntity);
return null;
}
});
}
public void update(long id, String aNewPropertyValue) {
transactionExecute(new ABlockOfCode() {
@Override
public AnEntity execute(EntityManager em) {
AnEntity anEntity = em.find(AnEntity.class, id);
anEntity.setAProperty(aNewPropertyValue);
return null;
}
});
}
public AnEntity byId(long id) {
return transactionExecute(new ABlockOfCode() {
@Override
public AnEntity execute(EntityManager em) {
return em.find(AnEntity.class, id);
}
});
}
private AnEntity transactionExecute(ABlockOfCode aBlockOfCode) {
EntityManager em = this.emf.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
AnEntity a = aBlockOfCode.execute(em);
tx.commit();
return a;
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw new RuntimeException(e);
} finally {
em.close();
}
}
}
As you can see, I have created a private method called transactionExecute, which expects as parameter an instance of ABlockOfCode. In each public method, I’m creating these instances as anonymous classes, implementing the execute method of the ABlockOfCode with the sentences unique per method described before. Then, each method calls transactionExecute, passing these ABlockOfCode instances by parameter. Finally, note that inside the transactionExecute method, there is a call to the execute method of the ABlockOfCode instance, inside the try/catch/finally skeleton.
Not bad right? Let’s do it even better in my third development iteration. I’m going to replace the anonymous classes with lambdas. In Java 8, lambdas are a prettier way of writing anonymous classes (not exactly the case for functional programming languages, but that is a different talk). They incorporate a syntactic sugar plus a type inference system, which make the code cleaner and easier to read.
The code below starts moving the transactionExecute private method to its own class.
public class TransactionTemplate {
public EntityManagerFactory emf;
public TransactionTemplate(EntityManagerFactory emf) {
this.emf = emf;
}
public AnEntity execute(ABlockOfCode aBlockOfCode) {
EntityManager em = this.emf.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
AnEntity returnValue = aBlockOfCode.execute(em);
tx.commit();
return returnValue;
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw new RuntimeException(e);
} finally {
em.close();
}
}
}
public class ADataAccessObject {
private TransactionTemplate transaction;
public ADataAccessObject(TransactionTemplate transaction) {
this.transaction = transaction;
}
public void store(AnEntity anEntity) {
transaction.execute(
(em) - > {
em.persist(anEntity);
return null;
});
}
public void update(long id, String aNewPropertyValue) {
transaction.execute(
(em) - > {
AnEntity anEntity = em.find(AnEntity.class, id);
anEntity.setAProperty(aNewPropertyValue);
return null;
});
}
public AnEntity byId(long id) {
return transaction.execute(
(em) - > {
return em.find(AnEntity.class, id);
});
}
}
A lambda expression (lines 40-42, 47-50 and 55-57), is composed of two parts separated by the lexical token “->”: (parameters) -> { block of code }. There is no need to specify the type of the parameter in the lambda expression, it will be inferred from an interface, in this case, the ABlockOfCode. If you look at this interface, you will note that each lambda expression receives an instance of the EntityManager as a parameter, (em), and must return an instance of AnEntity. Nice, duplicated code removed and less verbose code.
As a final development iteration, I will make this more generic. My ABlockOfCode interface and TransactionTemplate class should support any object type, not only AnEntity. So next, I will change the AnEntity type for a generic type T.
Starting with the ABlockOfCode interface:
public interface ABlockOfCode<T> {
T execute(EntityManager em);
}
I have just replaced AnEntity with the generic type T, and I have declared that type T as a generic type, using the <T> syntax (on line 1).
Next, I will make the TransactionTemplate#execute method generic:
public < T > T execute(ABlockOfCode < T > aBlockOfCode) {
EntityManager em = this.emf.createEntityManager();
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
T returnValue = aBlockOfCode.execute(em);
tx.commit();
return returnValue;
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw new RuntimeException(e);
} finally {
em.close();
}
}
On line 1, I’m declaring the generic type <T>, changing the signature of the method returning T and expecting ABlockOfCode<T> as a parameter. Note also that the return type of the aBlockOfCode.execute(em) sentence has changed from AnEntity to T, on line 8.
With this last change, we have made the TransactionTemplate#execute method generic to be used by any instance of ABlockOfCode that requires a JPA transaction context. The ADataAccessObject class does not need to change because, as I explained before, the lambdas infer their type from the ABlockOfCode interface.
We just went through some development iterations in order to remove duplicated code. The nature of the duplicated code shown here is not able to be removed using simple refactoring techniques like the extract method or extract class. It requires more powerful language features to allow passing sentences, or blocks of code, by parameter. That was achieved using anonymous classes first, and then with lambda expressions since Java 8.
Published at DZone with permission of Enrique Molinari. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments