模拟Java中静态方法调用的静态抽象和动态链接

发布于 2024-10-20 04:44:19 字数 1291 浏览 1 评论 0原文

简介

作为免责声明,我读过 为什么静态方法不能Java 中的抽象,即使我恭敬地不同意关于“逻辑矛盾”的公认答案,我也不想要任何关于静态抽象有用性的答案,只是我的答案问题;)

我有一个类层次结构,表示数据库中的一些表。每个类都继承Entity 类,该类包含许多用于访问数据库、创建查询、转义字符等的实用方法。

类的每个实例都是数据库中的一行。

问题

现在,为了分解尽可能多的代码,我想为每个类添加有关相关列和表名的信息。这些信息必须无需类实例即可访问,并将在Entity 中用于构建查询等。

存储这些数据的明显方法是每个类中的静态方法返回的静态字段。问题是你不能强制类实现这些静态方法,也不能在 Java 中对静态方法调用进行动态链接。

我的解决方案

  1. 使用 HashMap 或任何类似的数据结构来保存信息。问题:如果信息丢失,错误将出现在运行时而不是编译时。
  2. 对实用程序函数使用并行类层次结构,其中每个相应的类都可以实例化并使用动态链接。问题:代码繁重,如果类不存在,则会出现运行时错误

问题:

您将如何应对抽象方法上缺少抽象静态和动态链接的情况?

在完美的世界中,如果缺少类的信息,并且应该可以从实体类中轻松访问数据,则给定的解决方案应该生成编译错误。

答案不需要是 Java,C# 也可以,并且欢迎任何关于如何在没有任何语言的特定代码的情况下做到这一点的见解。

需要明确的是,除了简单性之外,我没有任何要求。没有什么必须是静态的。我只想从 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";
}

Introduction

As a disclaimer, I'v read Why can't static methods be abstract in Java and, even if I respectfully disagree with the accepted answer about a "logical contradiction", I don't want any answer about the usefulness of static abstract just an answer to my question ;)

I have a class hierarchy representing some tables from a database. Each class inherits the Entity class which contains a lot of utility methods for accessing the database, creating queries, escaping characters, etc.

Each instance of a class is a row from the database.

The problem

Now, in order to factorize as much code as possible, I want to add information about related columns and table name for each class. These informations must be accessible without a class instance and will be used in Entity to build queries among other things.

The obvious way to store these data are static fields returned by static methods in each class. Problem is you can't force the class to implement these static methods and you can't do dynamic linking on static methods call in Java.

My Solutions

  1. Use a HashMap, or any similar data structure, to hold the informations. Problem : if informations are missing error will be at runtime not compile time.
  2. Use a parallel class hierarchy for the utility function where each corresponding class can be instantiated and dynamic linking used. Problem : code heavy, runtime error if the class don't exist

The question

How will you cope with the absence of abstract static and dynamic linking on abstract method ?

In a perfect world, the given solution should generate a compile error if the informations for a class are missing and data should be easily accessible from withing the Entity class.

The answer doesn't need to be in Java, C# is also ok and any insight on how to do this without some specific code in any language will be welcomed.

Just to be clear, I don't have any requirement at all besides simplicity. Nothing have to be static. I only want to retrieve table and columns name from Entity to build a query.

Some code

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

孤独患者 2024-10-27 04:44:19

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

在你的注释处理中,你应该检查注释是否设置,值是否正确,并使编译失败。

完整的文档可在 javax.annotation.processing

此外,如果您搜索“java 注解处理”,还可以在 Internet 上找到一些教程。

我不会更深入地讨论这个主题,因为我自己以前从未使用过该技术。

You should use the Java annotation coupled with the javac annotation processor, as it's the most efficient solution. It's however a bit more complicated than the usual annotation paradigm.

This link shows you how you can implement an annotation processor that will be used at the compile time.

If I reuse your example, I'd go this way:

@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.
  }
}

In your annotation processing, you then should check whether the annotation is set, whether the values are correct, and make the compilation fail.

The complete documentation is available in the documentation for the package javax.annotation.processing.

Also, a few tutorials are available on the Internet if you search for "java annotation processing".

I will not go deeper in the subject as I never used the technology myself before.

苹果你个爱泡泡 2024-10-27 04:44:19

我遇到了和你一样的问题,现在正在使用以下方法。将有关列的元数据存储为注释并在运行时解析它们。将此信息存储在地图中。如果您确实希望出现编译时错误,大多数 IDE(例如 Eclipse)都支持自定义构建器类型,可以在构建时验证类。

您还可以使用java附带的编译时注释处理工具,它也可以集成到IDE构建中。阅读并尝试一下。

I have run into the same problems as you, and am using the following approach now. Store Metadata about columns as annotations and parse them at runtime. Store this information in a map. If you really want compile time errors to appear, most IDEs (Eclipse e.g.) support custom builder types, that can validate the classes during build time.

You could also use the compile time annotation processing tool which comes with java, which can also be integrated into the IDE builds. Read into it and give it a try.

衣神在巴黎 2024-10-27 04:44:19

在 Java 中,与“静态类”最相似的方法是静态枚举。

enum 元素作为静态常量传递,因此可以从任何静态上下文访问它们。

enum 可以定义一个或多个私有构造函数,接受一些初始化参数(因为它可以是表名、一组列等)。

enum类可以定义抽象方法,这些方法必须由具体元素实现才能编译。

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,这种方法将是快速且轻量。

它的阴暗面是,如果您的枚举类型需要非常复杂,具有许多不同的参数,或者一些不同的复杂重写方法,则枚举的源代码可能会变得有点混乱。

In Java the most similar approach to "static classes" are the static enums.

The enum elements are handed as static constants, so they can be accesed from any static context.

The enum can define one or more private constructors, accepting some intialization parameters (as it could be a table name, a set of columns, etc).

The enum class can define abstract methods, which must be implemented by the concrete elements, in order to compile.

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

}

Then to access a concrete entity metadata this would be enough:

EntityMetadata.TABLE_B.doSomethingWeirdAndExclusive();

You could also reference them from an Entity implemetation, forcing each to refer an EntityMetadata element:

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, this approach will be fast and light-weight.

The dark side of it is that if your enum type needs to be really complex, with lot of different params, or a few different complex overriden methods, the source code for the enum can become a little messy.

星星的軌跡 2024-10-27 04:44:19

我的想法是跳过表格的东西,并与“没有抽象静态方法”相关。使用“伪抽象静态”方法。

首先定义一个在执行抽象静态方法时会发生的异常:

public class StaticAbstractCallException extends Exception {

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

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

“抽象”方法意味着它将在子类中被重写,因此您可能需要定义一个基类,其中的静态方法被假定为“抽象”。

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 时,将产生异常。

Mi idea, is to skip the tables stuff, and relate to the "There are not abstract static methods". Use "pseudo-abstract-static" methods.

First define an exception that will ocurr when an abstract static method is executed:

public class StaticAbstractCallException extends Exception {

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

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

An "abstract" method means it will be overriden in subclasses, so you may want to define a base class, with static methods that are suppouse to be "abstract".

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

...
And finally, define the subclasses that override the "pseudo-abstract" methods.

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

When the static myStringLibrary doSomething is called, an exception will be generated.

烟织青萝梦 2024-10-27 04:44:19

我确实知道一个解决方案可以提供您想要的一切,但这是一个巨大的黑客,我现在不想在自己的代码中使用:

如果 Entity 可能是抽象的,只需添加提供元数据的方法即可基类并声明它们抽象

否则创建一个接口,使用提供所有数据的方法,就像这样,

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

但是实体的所有子类都必须实现这个接口。

现在您的问题是从静态实用程序方法中调用这些方法,因为您在那里没有实例。所以你需要创建一个实例。使用 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 缓存您的实例,这将运行时成本降低到几乎为零(在缓存映射中进行单次查找)。

我在自己的一个项目中使用 Objenesis,它使我能够创建一个漂亮、流畅的 API。这对我来说是一个巨大的胜利,所以我忍受了这个黑客攻击。所以我可以告诉你,它确实有效。我在许多具有许多不同 JVM 版本的环境中使用了我的库。

但这不是好的设计!我建议不要使用这样的 hack,即使它现在有效,它可能会在下一个 JVM 中停止。然后你将不得不祈祷 Objenesis 的更新......

如果我是你,我会重新考虑我的设计导致整个需求。或者放弃编译时检查而使用注释。

I do know of a solution providing all you want, but it's a huge hack I wouldn't want in my own code nowadays:

If Entity may be abstract, simply add your methods providing the meta data to that base class and declare them abstract.

Otherwise create an interface, with methods providing all your data like this

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

All subclasses of Entity would have to implement this interface though.

Now your problem is to call these methods from your static utility method, since you don't have an instance there. So you need to create an instance. Using Class.newInstance() is not feasable, since you'd need a nullary constructor, and there might be expensive initialization or initialization with side-effects happening in the constructor, you don't want to trigger.

The hack I propose is to use Objenesis to instantiate your Class. This library allows instatiating any class, without calling the constructor. There's no need for a nullary constructor either. They do this with some huge hacks internally, which are adapted for all major JVMs.

So your code would look like this:

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

Obviously you should cache your instances using a Map<Class,Entity>, which reduces the runtime cost to practically nothing (a single lookup in your caching map).

I am using Objenesis in one project of my own, where it enabled me to create a beautiful, fluent API. That was such a big win for me, that I put up with this hack. So I can tell you, that it really works. I used my library in many environments with many different JVM versions.

But this is not good design! I advise against using such a hack, even if it works for now, it might stop in the next JVM. And then you'll have to pray for an update of Objenesis...

If I were you, I'd rethink my design leading to the whole requirement. Or give up compile time checking and use annotations.

许你一世情深 2024-10-27 04:44:19

您对静态方法的要求不会为干净的解决方案留下太多空间。一种可能的方法是混合静态和动态,并以节省 RAM 为代价损失一些 CPU:

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 中,仅保留在工厂。是的,它不会强制在编译时为每个类提供它,但您可以在运行时轻松强制执行。编译时错误很严重,但它们毕竟不是圣牛,因为如果类没有提供相关的元数据部分,您总是会立即收到错误。

Your requirement to have static method doesn't leave much space for clean solution. One of the possible ways is to mix static and dynamic, and lose some CPU for a price of saving on 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);
   }
}

The way I would like more would be to waste a reference but have it dynamic:

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

You could get even get rid of EntityMetadata in Entity completely and leave it factory only. Yes, it would not force to provide it for each class in compile-time, but you can easily enforce that in the runtime. Compile-time errors are great but they aren't holy cows after all as you'd always get an error immediately if a class hasn't provided a relevant metadata part.

人海汹涌 2024-10-27 04:44:19

我会将实体的所有元数据(表名、列名)抽象到实体本身不知道的服务中。比将这些信息放在实体内部要干净得多

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

I would have abstracted away all meta data for the entities (table names, column names) to a service not known by the entities them selfs. Would be much cleaner than having that information inside the entities

MetaData md = metadataProvider.GetMetaData<T>();
String tableName = md.getTableName();
征棹 2024-10-27 04:44:19

首先,让我告诉你我同意你的观点,我希望有一种方法来强制静态方法出现在类中。
作为解决方案,您可以通过使用自定义 ANT 任务来“延长”编译时间,该任务检查此类方法是否存在,并在编译时间中出现错误。当然,它不会对您在 IDE 内部提供帮助,但您可以使用可自定义的静态代码分析器(如 PMD)并创建自定义规则来检查相同的内容。
然后进行 java 编译(好吧,几乎编译)并进行编辑时错误检查。
动态链接模拟……嗯,这更难。我不确定我明白你的意思。你能写一个你期望发生的事情的例子吗?

First, let me tell you I agree with you I would like to have a way to enforce static method to be present in classes.
As a solution you can "extend" compile time by using a custom ANT task that checks for the presence of such methods, and get error in compilation time. Of course it won't help you inside you IDE, but you can use a customizable static code analyzer like PMD and create a custom rule to check for the same thing.
And there you java compile (well, almost compile) and edit time error checking.
The dynamic linking emulation...well, this is harder. I'm not sure I understand what you mean. Can you write an example of what you expect to happen?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文