Symulowanie statycznego abstrakcyjnego i dynamicznego łączenia na statycznym wywołaniu metody w Javie

Wprowadzenie

Jako zastrzeżenie przeczytałem dlaczego metody statyczne nie mogą być abstrakcyjne w Javie i nawet jeśli z całym szacunkiem nie zgadzam się z przyjętą odpowiedzią na "logiczną sprzeczność", nie chcę żadnej odpowiedzi na temat użyteczności static abstract tylko odpowiedzi na moje pytanie;)

Mam hierarchię klas reprezentującą niektóre tabele z bazy danych. Każda klasa dziedziczy klasę Entity, która zawiera wiele metod użytkowych do uzyskiwania dostępu do bazy danych, tworzenia zapytań, znaki ucieczki itp.

Każda instancja klasy jest wierszem z bazy danych.

Problem

Teraz, aby faktoryzować jak najwięcej kodu, chcę dodać informacje o powiązanych kolumnach i nazwach tabeli dla każdej klasy. Informacje te muszą być dostępne bez instancji klasy i będą używane między innymi w Entity do budowania zapytań.

Oczywistym sposobem przechowywania tych danych są statyczne pola zwracane przez statyczne metody w każdej klasie. Problem polega na tym, że nie możesz wymusić na klasie implementacji tych statycznych metod i nie możesz wykonywać dynamicznego łączenia przy wywołaniu statycznych metod w Javie.

Moje Rozwiązania

  1. Użyj HashMap, lub innej podobnej struktury danych, aby przechowywać informacje. Problem: jeśli brakuje informacji, błąd będzie w czasie wykonywania, a nie podczas kompilacji.
  2. użyj równoległej hierarchii klas dla funkcji użytkowej, gdzie każda odpowiadająca jej klasa może być instancjowana i używane jest dynamiczne łączenie. Problem: kod ciężki, błąd runtime, jeśli klasa nie istnieje

Pytanie

Jak poradzisz sobie z brakiem abstract static i dynamicznego linkowania na metodzie abstrakcyjnej ?

W idealnym świecie, dane rozwiązanie powinno generować błąd kompilacji, jeśli brakuje informacji dla klasy, a dane powinny być łatwo dostępne z poziomu klasy encji.

Odpowiedź nie musi być w Javie, C# też jest ok i każdy wgląd jak to zrobić bez jakiegoś konkretnego kodu w dowolnym język będzie mile widziany.

Żeby było jasne, nie mam żadnych wymagań poza prostotą. Nic nie musi być statyczne. Chcę tylko pobrać nazwę tabeli i kolumn z Entity, aby zbudować zapytanie.

Jakiś kod

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";
}
Author: Community, 2011-03-05

8 answers

Powinieneś używać adnotacji Java w połączeniu z procesorem adnotacji javac, ponieważ jest to najbardziej wydajne rozwiązanie. Jest to jednak nieco bardziej skomplikowane niż zwykły paradygmat adnotacji.

Ten link pokazuje, jak zaimplementować procesor adnotacji, który będzie używany podczas kompilacji.

Gdybym ponownie wykorzystał twój przykład, poszedłbym w ten sposób:

@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's a compile-time error, not a simple exceptional condition.
    }
    String table = metaData.table();
    // do whatever you need.
  }
}

Podczas przetwarzania adnotacji należy sprawdzić, czy adnotacja jest ustawiona, czy wartości są poprawne i sprawiają, że kompilacja nie powiedzie się.

Pełna dokumentacja jest dostępna w dokumentacji pakietu javax.annotation.processing.

Również kilka samouczków jest dostępnych w Internecie, jeśli szukasz "java annotation processing".

Nie będę zagłębiał się w temat, ponieważ nigdy wcześniej nie korzystałem z tej technologii.
 21
Author: Olivier Grégoire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-20 09:17:00

Napotkałem te same problemy, co ty, i używam teraz następującego podejścia. Przechowuj metadane kolumn jako adnotacje i analizuj je w czasie wykonywania. Przechowuj te informacje na mapie. Jeśli naprawdę chcesz, aby pojawiły się błędy w czasie kompilacji, większość IDE (np. Eclipse) obsługuje niestandardowe typy builderów, które mogą walidować klasy w czasie kompilacji.

Możesz również użyć narzędzia do przetwarzania adnotacji w czasie kompilacji, które jest dostarczane z Javą, które można również zintegrować z kompilatorami IDE. Przeczytaj i spróbuj.

 3
Author: Daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-05 12:47:18

W Javie najbardziej zbliżonym podejściem do "klas statycznych"są statyczne liczby.

enum elementy są przekazywane jako stałe statyczne, więc można je uzyskać z dowolnego kontekstu statycznego.

enum może definiować jeden lub więcej prywatnych konstruktorów, akceptując niektóre parametry intializacji (może to być nazwa tabeli, zestaw kolumn itp.).

Klasa enum może definiować metody abstrakcyjne , które muszą być zaimplementowane przez konkretne elementów, w celu kompilacji.

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();

}

Wtedy, aby uzyskać dostęp do konkretnych metadanych encji, wystarczy:

EntityMetadata.TABLE_B.doSomethingWeirdAndExclusive();

Możesz również odwoływać się do nich z implementacji encji, zmuszając każdego do odwoływania się do EntityMetadata elementu:

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, takie podejście będzie szybkie i lekkie.

Ciemną stroną tego jest to, że jeśli twój typ enum musi być naprawdę złożony, z wieloma różnymi paramami lub kilkoma różnymi złożonymi nadrzędnymi metodami, kod źródłowy enum może się trochę popsuć.

 1
Author: Tomas Narros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-11 09:34:36

Mi idea, jest pominięcie rzeczy tabel, i odnoszą się do "nie ma abstrakcyjnych metod statycznych". Użyj metod "pseudo-abstrakcyjno-statycznych".

Najpierw zdefiniuj wyjątek, który będzie ocurr po wykonaniu abstrakcyjnej statycznej metody:

public class StaticAbstractCallException extends Exception {

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

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

Metoda " abstrakcyjna "oznacza, że będzie nadpisywana w podklasach, więc możesz chcieć zdefiniować klasę bazową, używając metod statycznych, które powinny być"abstrakcyjne".

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

... I na koniec zdefiniuj podklasy, które nadpisują metody "pseudo-abstrakcyjne".

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

Po wywołaniu statycznego programu myStringLibrary zostanie wygenerowany wyjątek.

 1
Author: umlcat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-12 17:23:54

Znam rozwiązanie zapewniające wszystko, co chcesz, ale jest to ogromny hack, którego nie chciałbym w moim własnym kodzie:

Jeśli Entity mogą być abstrakcyjne, po prostu dodaj swoje metody dostarczające metadane do tej klasy bazowej i zadeklaruj je abstract.

W przeciwnym razie stwórz interfejs, z metodami dostarczającymi wszystkie Twoje dane w ten sposób

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

Wszystkie podklasy Entity musiałyby zaimplementować ten interfejs.

Teraz Twoim problemem jest wywołanie tych metod ze statycznego metoda użytkowa, ponieważ nie masz tam instancji. Więc musisz utworzyć instancję. Używanie Class.newInstance() nie jest wykonalne, ponieważ potrzebujesz konstruktora zerowego, a może być kosztowna inicjalizacja lub inicjalizacja z efektami ubocznymi w konstruktorze, nie chcesz tego wyzwalać.

Hack proponuję użyć Objenesis do utworzenia instancji klasy. Biblioteka ta pozwala na zainstalowanie dowolnej klasy bez wywoływania konstruktora. Nie ma takiej potrzeby. konstruktor też. Robią to z ogromnymi hakami wewnętrznymi, które są dostosowane do wszystkich głównych JVM.

Więc Twój kod będzie wyglądał tak:

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();
    ...
}

Oczywiście należy buforować instancje za pomocą Map<Class,Entity>, co zmniejsza koszt działania do praktycznie zera (jedno wyszukiwanie w mapie buforowania).

Używam Objenesis w jednym projekcie, który pozwolił mi stworzyć piękne, płynne API. To była dla mnie tak wielka wygrana, że znosiłem Ten hack. Więc Ja mogę powiedzieć, że to naprawdę działa. Używałem mojej biblioteki w wielu środowiskach z wieloma różnymi wersjami JVM.

Ale to nie jest dobry projekt! Odradzam używanie takiego hacka, nawet jeśli na razie działa, może się zatrzymać w następnym JVM. A potem będziesz musiał się modlić o aktualizację Objenezy...

Na Twoim miejscu, przemyślałbym swój projekt, prowadzący do spełnienia wszystkich wymagań. Lub zrezygnować z sprawdzania czasu kompilacji i używać adnotacji.

 1
Author: the.duckman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-13 14:52:26

Twój wymóg posiadania metody static nie pozostawia wiele miejsca na czyste rozwiązanie. Jednym z możliwych sposobów jest połączenie statyki i dynamiki i utrata części procesora za cenę oszczędzania na PAMIĘCI RAM: {]}

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);
   }
}

Bardziej chciałbym zmarnować referencję, ale mieć ją dynamiczną:

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) {
      ...;
   }
}

Można nawet pozbyć się EntityMetadata w Entity CAŁKOWICIE i opuścić ją tylko w fabryce. Tak, nie wymusiłoby to dostarczenia go dla każdej klasy w czasie kompilacji, ale można to łatwo wymusić w runtime. Błędy w czasie kompilacji są świetne, ale nie są to święte krowy, ponieważ zawsze dostajesz błąd natychmiast, jeśli klasa nie dostarczyła odpowiedniej części metadanych.

 0
Author: mindas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-05 14:53:46

Usunąłbym wszystkie metadane dla encji (nazwy tabel, nazwy kolumn) do usługi nieznanej przez encje. Byłoby o wiele czystsze niż posiadanie tych informacji wewnątrz jednostek

MetaData md = metadataProvider.GetMetaData<T>();
String tableName = md.getTableName();
 0
Author: Marcus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-07 13:31:01

Po pierwsze, powiem ci, że Zgadzam się z Tobą chciałbym mieć sposób na wymuszenie statycznej metody, aby być obecnym w klasach.
Jako rozwiązanie można "wydłużyć" czas kompilacji za pomocą niestandardowego zadania ANT, które sprawdza obecność takich metod, i uzyskać błąd w czasie kompilacji. Oczywiście nie pomoże Ci to wewnątrz IDE, ale możesz użyć niestandardowego analizatora kodu statycznego, takiego jak PMD i utworzyć niestandardową regułę, aby sprawdzić to samo.
A tam java compile (no, prawie skompilować) i Sprawdzanie błędów w czasie edycji.
Dynamiczna emulacja linkowania...to jest trudniejsze. Nie jestem pewien, czy rozumiem, co masz na myśli. Czy możesz napisać przykład tego, czego oczekujesz?

 0
Author: Pablo Grisafi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-09 17:32:22