We use static links to object properties using lambdas

It so happened historically that in Java for the properties of objects (properties) no physical entity is provided. Properties in Java are some conventions in the naming of fields and methods for accessing them (accessors). And, although the presence of physical properties in the language would simplify a lot of cases (starting from the silly generation of setter getters), it seems that in the near future the situation in Java will not change.







Nevertheless, when developing multilayer business applications and using various frameworks for mapping and binding data, it is often necessary to pass a reference to an object property. Let's consider what options are for this.







Use property name



So far, the only generally accepted way to refer to an object property is a string with its name. The underlying library uses reflection or introspection to search for accessor methods and access fields. To reference nested objects, the following notation is usually used:







person.contact.address.city
      
      





The problem with this method is the lack of any control over the spelling of the name and type of property with all that it implies:









As a result, we still want to have a static type-safe property reference. A getter is the best candidate for this role, because:









How can I refer to a getter?







Proxying



One of the interesting ways is to proxy (or get wet) objects to intercept the getter call chain, which is used in some libraries: Mockito , QueryDSL , BeanPath . About the last on Habré there was an article from the author.

The idea is quite simple, but not trivial to implement (an example from the mentioned article).







 Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) );
      
      





Using dynamic code generation, a special proxy class is created that inherits from the bean class and intercepts all getter calls in the chain, constructing a path in the ThreadLocal variable. In this case, the call of real getters of the object does not occur.







In this article we will consider an alternative method.







Method Links



With the advent of Java 8, lambdas and the ability to use method references came along. Therefore, it would be natural to have something like:







 Person person = … assertEquals("name", $(Person::getName).getPath());
      
      





The $ method accepts the following lambda in which the getter reference is passed:







 public interface MethodReferenceLambda<BEAN, TYPE> extends Function<BEAN, TYPE>, Serializable {} ... public static <BEAN, TYPE> BeanProperty<BEAN, TYPE> $(MethodReferenceLambda<BEAN, TYPE> methodReferenceLambda)
      
      





The problem is that due to type erasure, there is no way to get BEAN and TYPE types in runtime, and there is also no information about the getter name: the method that is called “outside” is Function.apply ().







Nevertheless, there is a certain trick - this is the use of serialized lambda.







 MethodReferenceLambda<Person,String> lambda = Person::getName(); Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); writeMethod.setAccessible(true); SerializedLambda serLambda = (SerializedLambda) writeMethod.invoke(lambda); String className = serLambda.getImplClass().replaceAll("/", "."); String methodName = serLambda.getImplMethodName();
      
      





The SerializedLambda class contains all the necessary information about the called class and method. Next is a matter of technology.

Since I work a lot with data structures, this method encouraged me to write a small library for static access to properties.







BeanRef Library



Using the library looks something like this:







 Person person = ... //     final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); assertEquals("contact.address.city", personCityProperty.getPath());
      
      





and does not require the magic of code generation and third-party dependencies. Instead of a getter chain, a lambda chain is used with reference to getters. At the same time, type safety is respected and IDE-based auto-completion works quite well:







You can use the getter name in both standard notation (getXXX () / isXXX ()) and non-standard (xxx ()). The library will try to find the corresponding setter, and if it is absent, then the property is declared read-only.







To speed up performance, resolved properties are cached, and when you call it again with the same lambda, the result is already saved.







In addition to the name of the property / path, using the BeanPath object, you can access the property value of the object:







 Person person = ... final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); String personCity = personCityProperty.get(person);
      
      





Moreover, if the intermediate object in the chain is null, then the corresponding call will also return null instead of NPE. This will greatly simplify the code without requiring verification.







Through BeanPath, you can also change the value of an object property if it is not read-only:







 personCityProperty.set(person, “Madrid”);
      
      





Following the same idea - as little NPE as possible - in this case, if one of the intermediate objects in the chain is null, the library will automatically try to create it and save it in the field. To do this, the corresponding property must be writeable, and the object class must have a public constructor without parameters.







As an experimental feature, the opportunity to work with collections is offered. For some special cases, sometimes it is necessary to construct paths, referring to objects within the collection. To do this, the $$ method is provided, which constructs a link to the last element of the collection (considering it to be the only one).







 final BeanPath<Person, String> personPhonePath = $(Person::getContact).$$(Contact::getPhoneList).$(Phone::getPhone); assertEquals("contact.phoneList.phone", personPhonePath.getPath()); assertEquals(personPhonePath.get(person), person.getContact().getPhoneList() .get(person.getContact().getPhoneList().size()-1).getPhone());
      
      





The project is hosted here: https://github.com/throwable/beanref , binaries are available from the jcenter maven repository.







Useful



java.beans.Introspector

The Introspector class from standard Java Java allows resolving the properties of bins.







Apache Commons BeanUtils

The most comprehensive library for working with Java Beans.







Beanpath

Mentioned library that does the same through proxying.







Objenesis

We instantiate an object of any class with any set of constructors.







QueryDSL Aliases

Using proxied classes to set criteria in QueryDSL







Jinq

An interesting library that uses lambdas to set criteria in JPA. A lot of magic: proxying, serializing lambdas, interpreting bytecode.








All Articles