Подтвердить что ты не робот

Имитировать статическое абстрактное и динамическое связывание при вызове статического метода в Java

Введение

Как отказ от ответственности, я прочитал Почему статические методы не могут быть абстрактными в Java и даже если я с уважением не согласен с принятым ответом о "логическом противоречие", я не хочу никакого ответа о полезности static abstract просто ответа на мой вопрос;)

У меня есть иерархия классов, представляющая некоторые таблицы из базы данных. Каждый класс наследует класс Entity, который содержит много полезных методов для доступа к базе данных, создания запросов, экранирования символов и т.д.

Каждый экземпляр класса представляет собой строку из базы данных.

Проблема

Теперь, чтобы разделить как можно больше кода, я хочу добавить информацию о связанных столбцах и имени таблицы для каждого класса. Эти сведения должны быть доступны без экземпляра класса и будут использоваться в Entity для создания запросов между прочим.

Очевидным способом хранения этих данных являются статические поля, возвращаемые статическими методами в каждом классе. Проблема в том, что вы не можете заставить класс реализовать эти статические методы, и вы не можете выполнять динамическое связывание при вызове статических методов в Java.

Мои решения

  • Используйте HashMap или любую подобную структуру данных для хранения информации. Проблема: если информация отсутствует, ошибка будет во время выполнения, а не время компиляции.
  • Используйте иерархию параллельного класса для функции утилиты, в которой каждый экземпляр каждого объекта может быть создан и используется динамическое связывание. Проблема: код тяжелый, ошибка выполнения, если класс не существует

Вопрос

Как вы справитесь с отсутствием abstract static и динамической привязкой к абстрактному методу?

В идеальном мире данное решение должно генерировать ошибку компиляции, если информация для класса отсутствует, а данные должны быть легко доступны из класса Entity.

Ответ не обязательно должен быть в Java, С# тоже в порядке, и любое понимание того, как это сделать без какого-либо конкретного кода на любом языке, будет приветствоваться.

Просто, чтобы быть ясным, у меня нет никаких требований, кроме простоты. Ничто не должно быть статичным. Я хочу получить имя таблицы и столбцов из Entity для создания запроса.

Некоторый код

class Entity {
    public static function afunction(Class clazz) { // this parameter is an option
        // here I need to have access to table name of any children of Entity
    }
}

class A extends Entity {
    static String table = "a";
}

class B extends Entity {
    static String table = "b";
}
4b9b3361

Ответ 1

Вы должны использовать аннотацию Java в сочетании с обработчиком аннотаций javac, поскольку это наиболее эффективное решение. Однако это немного сложнее, чем обычная парадигма аннотации.

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

Если я снова использую ваш пример, я бы пошел следующим образом:

@Target(ElementType.TYPE)
@Retention(RetentionType.SOURCE)
@interface MetaData {
  String table();
}

abstract class Entity {}

@MetaData(table="a")
class A extends Entity {}

@MetaData(table="b")
class B extends Entity {}

class EntityGetter {
  public <E extends Entity> E getEntity(Class<E> type) {
    MetaData metaData = type.getAnnotation(MetaData.class);
    if (metaData == null) {
      throw new Error("Should have been compiled with the preprocessor.");
      // Yes, do throw an Error. It a compile-time error, not a simple exceptional condition.
    }
    String table = metaData.table();
    // do whatever you need.
  }
}

В обработке аннотации вы должны проверить, установлена ​​ли аннотация, правильны ли значения и не удалось выполнить компиляцию.

Полная документация доступна в документации для package javax.annotation.processing.

Кроме того, несколько учебных пособий доступны в Интернете, если вы ищете "обработку аннотации java".

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

Ответ 2

Я столкнулся с теми же проблемами, что и вы, и теперь использую следующий подход. Храните метаданные о столбцах как аннотации и анализируйте их во время выполнения. Сохраните эту информацию на карте. Если вы действительно хотите, чтобы возникали ошибки времени компиляции, большинство IDE (Eclipse, например,) поддерживают настраиваемые типы построителей, которые могут проверять классы во время сборки.

Вы также можете использовать инструмент обработки аннотации времени компиляции, который поставляется с java, который также может быть интегрирован в сборки IDE. Прочитайте его и попробуйте.

Ответ 3

В Java наиболее похожим подходом к "статическим классам" являются статические перечисления.

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

Перечисление может определять один или несколько частных конструкторов, принимая некоторые параметры инициализации (поскольку это может быть имя таблицы, набор столбцов и т.д.).

Класс enum может определять методы abstract, которые должны быть реализованы конкретными элементами для компиляции.

public enum EntityMetadata {

    TABLE_A("TableA", new String[]{"ID", "DESC"}) {

        @Override
        public void doSomethingWeirdAndExclusive() {

            Logger.getLogger(getTableName()).info("I'm positively TableA Metadata");

        }
    },
    TABLE_B("TableB", new String[]{"ID", "AMOUNT", "CURRENCY"}) {

        @Override
        public void doSomethingWeirdAndExclusive() {

            Logger.getLogger(getTableName()).info("FOO BAR message, or whatever");

        }
    };  

    private String tableName;
    private String[] columnNames;

    private EntityMetadata(String aTableName, String[] someColumnNames) {
        tableName=aTableName;
        columnNames=someColumnNames;
    }

    public String getTableName() {
        return tableName;
    }

    public String[] getColumnNames() {
        return columnNames;
    }


    public abstract void doSomethingWeirdAndExclusive();

}

Затем для доступа к конкретным метаданным объекта это будет достаточно:

EntityMetadata.TABLE_B.doSomethingWeirdAndExclusive();

Вы также можете ссылаться на них из реализации Entity, заставляя каждого ссылаться на элемент EntityMetadata:

abstract class Entity {

    public abstract EntityMetadata getMetadata();

}

class A extends Entity {

   public EntityMetadata getMetadata() {
       return EntityMetadata.TABLE_A;
   }
}

class B extends Entity {

   public EntityMetadata getMetadata() {
       return EntityMetadata.TABLE_B;
   }
}

IMO, этот подход будет быстрым и легким.

Темная сторона этого заключается в том, что если ваш тип перечисления должен быть действительно сложным, с множеством различных параметров или несколькими различными сложными переопределенными методами, исходный код для перечисления может стать немного грязным.

Ответ 4

Идея Mi - это пропустить материал таблиц и относиться к "Нет абстрактных статических методов". Используйте "псевдо-абстрактные-статические" методы.

Сначала определите исключение, которое будет выполняться при выполнении абстрактного статического метода:

public class StaticAbstractCallException extends Exception {

  StaticAbstractCallException (String strMessage){
    super(strMessage);
   }

   public String toString(){
    return "StaticAbstractCallException";
   }  
} // class

"Абстрактный" метод означает, что он будет переопределен в подклассах, поэтому вы можете захотеть определить базовый класс со статическими методами, которые suppouse будут "абстрактными".

abstract class MyDynamicDevice {
   public static void start() {
       throw new StaticAbstractCallException("MyDynamicDevice.start()"); 
   }

   public static void doSomething() {
       throw new StaticAbstractCallException("MyDynamicDevice.doSomething()"); 
   }

   public static void finish() {
       throw new StaticAbstractCallException("MyDynamicDevice.finish()"); 
   }

   // other "abstract" static methods
} // class

... И, наконец, определите подклассы, которые переопределяют "псевдо абстрактные" методы.

class myPrinterBrandDevice extends MyDynamicDevice {

   public static void start() {
       // override MyStaticLibrary.start()
   }

   /*
   // ops, we forgot to override this method !!!
   public static void doSomething() {
       // ...
   }
   */

   public static void finish() {
       // override MyStaticLibrary.finish()
   }

   // other abstract static methods
} // class

Когда вызывается статическая myStringLibrary doSomething, генерируется исключение.

Ответ 5

Я знаю решение, обеспечивающее все, что вам нужно, но это огромный хак, который я бы не захотел в моем собственном коде в наши дни:

Если Entity может быть абстрактным, просто добавьте свои методы, предоставляющие метаданные этому базовому классу, и объявите их abstract.

В противном случае создайте интерфейс, используя методы, предоставляющие все ваши данные, такие как

public interface EntityMetaData{
    public String getTableName();
    ...
}

Все подклассы Entity должны реализовать этот интерфейс.

Теперь ваша проблема заключается в вызове этих методов из вашего статического метода утилиты, так как у вас нет экземпляра. Поэтому вам нужно создать экземпляр. Использование Class.newInstance() невозможно, так как вам понадобится конструктор с нулевым значением, и может быть дорогостоящая инициализация или инициализация с побочными эффектами, происходящими в конструкторе, вы не хотите запускать.

Я предлагаю использовать Objenesis для создания экземпляра класса. Эта библиотека позволяет установить любой класс без вызова конструктора. Нет необходимости и для конструктора с нулевым значением. Они делают это с помощью огромных хаков внутри, которые адаптированы для всех основных JVM.

Итак, ваш код будет выглядеть так:

public static function afunction(Class clazz) {
    Objenesis objenesis = new ObjenesisStd();
    ObjectInstantiator instantiator = objenesis.getInstantiatorOf(clazz);
    Entity entity = (Entity)instantiator.newInstance();
    // use it
    String tableName = entity.getTableName();
    ...
}

Очевидно, вы должны кэшировать свои экземпляры с помощью Map<Class,Entity>, что практически снижает стоимость выполнения во время работы (один поиск в вашей карте кэширования).

Я использую Objenesis в одном собственном проекте, где он позволил мне создать красивый, свободный API. Это была такая большая победа для меня, что я терпел этот хак. Поэтому я могу сказать вам, что это действительно работает. Я использовал свою библиотеку во многих средах со многими версиями JVM.

Но это не хороший дизайн! Я не рекомендую использовать такой хак, даже если он работает сейчас, он может остановиться в следующей JVM. И тогда вам придется молиться за обновление Objenesis...

Если бы я был вами, я бы переосмыслил свой проект, который привел к полному требованию. Или отказаться от проверки времени компиляции и использования аннотаций.

Ответ 6

Ваше требование иметь метод static не оставляет много места для чистого решения. Один из возможных способов - смешивать статические и динамические и потерять некоторый процессор для экономии памяти в оперативной памяти:

class Entity {
   private static final ConcurrentMap<Class, EntityMetadata> metadataMap = new ...;

   Entity(EntityMetadata entityMetadata) {
      metadataMap.putIfAbsent(getClass(), entityMetadata);
   }

   public static EntityMetadata getMetadata(Class clazz) {
      return metadataMap.get(clazz);
   }
}

То, что я хотел бы больше, - это отбросить ссылку, но ее динамичность:

class Entity {
   protected final EntityMetadata entityMetadata;

   public Entity(EntityMetadata entityMetadata) {
      this.entityMetadata=entityMetadata;
   }
}

class A extends Entity {
  static {
     MetadataFactory.setMetadataFor(A.class, ...);
  }

  public A() {
     super(MetadataFactory.getMetadataFor(A.class));
  }
}

class MetadataFactory {
   public static EntityMetadata getMetadataFor(Class clazz) {
      return ...;
   }

   public static void setMetadataFor(Class clazz, EntityMetadata metadata) {
      ...;
   }
}

Вы можете полностью избавиться от EntityMetadata в Entity и оставить его factory. Да, это не заставило бы обеспечить его для каждого класса во время компиляции, но вы можете легко обеспечить его выполнение во время выполнения. Ошибки компиляции велики, но они не являются святыми коровами, поскольку вы всегда получите ошибку немедленно, если класс не предоставил соответствующую часть метаданных.

Ответ 7

Я бы отвлек все метаданные для сущностей (имена таблиц, имена столбцов) на службу, не известную сущностями, которые им принадлежат. Было бы намного чище, чем иметь эту информацию внутри сущностей.

MetaData md = metadataProvider.GetMetaData<T>();
String tableName = md.getTableName();

Ответ 8

Во-первых, позвольте мне сказать вам, что я согласен с вами. Я хотел бы иметь способ заставить статический метод присутствовать в классах.
В качестве решения вы можете "продлить" время компиляции, используя специальную задачу ANT, которая проверяет наличие таких методов и получает ошибку во время компиляции. Конечно, это не поможет вам внутри IDE, но вы можете использовать настраиваемый статический анализатор кода, такой как PMD, и создать собственное правило для проверки того же. И там вы java компилируете (ну, почти компилируете) и редактируете проверку ошибок времени.
Динамическая компоновка эмуляции... ну, это сложнее. Я не уверен, что понимаю, что вы имеете в виду. Можете ли вы написать пример того, что вы ожидаете?