Pull to refresh

Вычисление имен свойств во время выполнения в языке Java

Reading time 3 min
Views 8.4K
Некоторые инструменты могут использовать имена свойств виде значений типа String. Обычно они существуют как константы, заданные литералами. Что же не так? А вот что: во время рефакторинга имена свойств могут измениться, более того, свойства могут вообще исчезнуть. А в константах останутся старые, неактуальные значения.

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

Ниже — пример использования инструмента, который родился, когда надоело бегать по граблям вокруг Hibernate'а и его замечательного Criteria API.

package ru.bdm.reflection;

import junit.framework.Assert;
import org.junit.Test;

import java.util.Date;
import java.util.List;

import static ru.bdm.reflection.PathExtractor.Example;

/**
 * User: D.Brusentsov
 * Date: 22.04.13
 * Time: 20:21
 */
public class PathExtractorUsageForHabrahabr {

    public static class Pet {
        private String name;
        private Human owner;

        //getters and setters omitted

    }

    public static class Human {

        private String name;
        private Date birth;
        private List<Human> relatives;

        //getters and setters omitted

    }

    @Test
    public void getPetName() {
        String name = PathExtractor.getPath(new Example<Pet>() {
            @Override
            public void example(Pet pet) {
                pet.getName();
            }
        });

        Assert.assertEquals("name", name);
    }

    @Test
    public void getPetOwnerName() {
        String ownerName = PathExtractor.getPath(new Example<Pet>() {
            @Override
            public void example(Pet pet) {
                pet.getOwner().getName();
            }
        });

        Assert.assertEquals("owner.name", ownerName);
    }

    @Test
    public void getPetOwnerRelativesBirth() {
        String ownerRelativesBirth = PathExtractor.getPath(new Example<Pet>() {
            @Override
            public void example(Pet pet) {
                PathExtractor.mask(pet.getOwner().getRelatives()).getBirth();
            }
        });

        Assert.assertEquals("owner.relatives.birth", ownerRelativesBirth);
    }
}


Итак, у нас есть класс класс со свойствами. Для того, чтобы вычислить динамически имя нужного свойства, мы передаем в метод PathExtractor.getPath экземпляр анонимного класса, который расширяет интерфейс Example. Интерфейс Example определяет одноименный метод, в теле которого нужно обратиться к свойству, чье имя нас интересует.
Если имя свойства меняется руками, то код, вычисляющий это имя, перестает компилироваться. Если же используются инструменты для автоматического рефакторинга, то код изменится автоматически. То есть мы узнаем об ошибке на этапе компиляции или она не произойдет вообще.

Внутри метода PathExtractor.getPath создается прокси, который передается в метод Example.example. Этот прокси запоминает все вызовы, и, если это возможно, возвращает подобный прокси как результат каждого вызова. Таким образом становится возможно узнать не только имя одного свойства, но и построить цепочку имен до любого свойства любого уровня вложенности.

Код выглядит довольно громоздко, но переход на Java 8 с лямбдами или хотя бы на IDE, способную отображать анонимные вложенные классы как лямбды, полностью решает эту проблему.

Минусы:
  • Не работает для методов с модификатором final.
  • Класс, свойства которого мы вычисляем, должен обладать пустым конструктором.


Скачать исходный код можно с Google Drive.

Update:

Вдохновленный комментарием пользователя vladimir_dolzhenko и возможностями Java 8, немного доработал инструмент. Теперь пример использования выглядит более лаконично:

package ru.bdm.reflection;

import org.junit.Test;

import java.util.Date;
import java.util.List;

import static junit.framework.Assert.assertEquals;
import static ru.bdm.reflection.PathExtractorJava8.of;

/**
 * User: D.Brusentsov
 * Date: 22.04.13
 * Time: 20:21
 */
public class PathExtractorJava8UsageForHabrahabr {

    public static class Pet {
        private String name;
        private Human owner;

        //getters and setters omitted
    }

    public static class Human {

        private String name;
        private Date birth;
        private List<Human> relatives;

        //getters and setters omitted
    }

    @Test
    public void getPetName() {
        String name = of(Pet.class, Pet::getName).end();

        assertEquals("name", name);
    }

    @Test
    public void getPetOwnerName() {
        String ownerName = of(Pet.class, Pet::getOwner).then(Human::getName).end();

        assertEquals("owner.name", ownerName);
    }

    @Test
    public void getPetOwnerRelativesBirth() {
        String ownerRelativesBirth = of(Pet.class, Pet::getOwner)
                .thenMask(Human::getRelatives)
                .then(Human::getBirth).end();

        assertEquals("owner.relatives.birth", ownerRelativesBirth);
    }
}


Скачать исходники с Google Drive.
Tags:
Hubs:
+12
Comments 11
Comments Comments 11

Articles