更好的方法是使用枚举而不是具有常量的类 Java。

huangapple go评论79阅读模式
英文:

Better approach on use of enum vs class with constants java

问题

The explicit call of the toString method in the enum approach may have a slight impact on performance compared to the class approach, as it involves an additional method invocation. However, the performance difference is typically negligible in most applications, especially when dealing with logging.

Ultimately, the choice between using a class or an enum for log categories should be based on code readability, maintainability, and adherence to best practices. If using an enum improves code organization and makes the logging code more intuitive and cleaner, it may be a reasonable choice despite the minor performance trade-off.

Consider discussing this trade-off with your team and making a decision based on the overall design goals and conventions of your project.

英文:

I'm still learning Java, and I have an issue where two, more experienced, engineers are asking me to follow two different approaches.
I'd like an opinion from more experienced java developers to choose one or the other approach and being able to defend that choice.

We're logging events using Log4j2. I've been asked to add categories to each log, as a prefix, as a way to tell where that log was created.

To avoid writing the category each time I was asked first to use a class, something like

public class LogCategory()
{
    public static final String CATEGORY_ONE   = "CategoryOne";
    public static final String CATEGORY_TWO   = "CategoryTwo";
    public static final String CATEGORY_THREE = "CategoryThree";
    ...
}

And it will be called using a log4j2 logger like

private Logger logger = LogManager.getLogger("MyLogger");
logger.info("{}: This is a log", LogCategory.CATEGORY_ONE);

The{} works as the placeholder to prefix the string.

this method works fine. But then, another engineer asked me to replace that class for an enum, since this would be what the enum should be used for.
The enum was then declared like this

public enum LoggingCategory 
{
    CATEGORY_ONE("CategoryOne"),
    CATEGORY_TWO("CategoryTwo"),
    CATEGORY_THREE("CategoryThree"),
    ...

    private final String str;

    LoggingCategory(final String str)
    {
        this.str = str;
    }


    @Override
    public String toString()
    {
        return str;
    }
}

But due to the multiple signature that log4j2 has for loggin methods, the toString method won't work unless explicitly called.
And it will be called using a log4j2 logger like

private Logger logger = LogManager.getLogger("MyLogger");
logger.info("{}: This is a log", LoggingCategory.CATEGORY_ONE.toString());

Now I've been told that this explicit call of the toString method will affect performance, and it would be better to go back to the class.

So, is it true? The explicit call of the method affects performance?

答案1

得分: 3

首先,您应该避免使用这种自定义日志记录方式,而应该使用适当的工厂方法

private static final Logger logger = LogManager.getLogger("HelloWorld");

这将在日志消息中为您添加适当的标识符,它将作为您想要人为引入的类别。

无论如何,根据良好的编程实践,我认为在这里使用枚举作为某种开销,然后说调用 toString 会影响性能只是毫无意义的,或者更礼貌地说,过早优化。

无论您使用哪种方法 - 在这种特定情况下,这两种方法都基本上都可以接受,而且都可能并不真正必要。

英文:

First of all you should avoid this kind of custom logging and instead use appropriate factory method

private static final Logger logger = LogManager.getLogger("HelloWorld");

This will add you appropriate identifier in your log message that will work as the category you want to introduce artificially.

Anyway - due to the good programming practices, however I find using enum here as some kind of overhead then saying that calling toString will affect performance is just a nonsense or, more politely saying, premature optimization.

No matter which approach you will use - both are basically just fine and both are probably not really necessary in this particular scenario.

答案2

得分: 3

我总是喜欢在可能的情况下使用 enum,因为它们具有类型安全性。这并不太适用于库类/方法,因为你无法改变它们的签名(这基本上是你的情况)。但如果在你的代码中可以掌握的话...

我仍然会使用 enum 并声明它们如下:

public enum LoggingCategory {

    ONE,
    TWO,
    THREE,
    ...
}

因为:

  • 每个枚举代表一个类别,在枚举的名称中已经声明。比较使用 LoggingCategory.CATEGORY_ONELoggingCategory.ONE
  • 枚举不需要成员变量、构造函数和 toString(),因为它提供了一个隐式的 toString() 方法,该方法返回枚举常量的名称,如在声明中包含的那样。通常情况下,不需要或不希望覆盖此方法。当存在更“友好”的字符串形式时,枚举类应该覆盖此方法。
  • 类别名称全部大写,以便与类名(在日志中)区分开来。
  • 如果将枚举放在外部(与业务库不相关的)commons 库中,其他人也可以使用它们,而无需导入和引用完整的业务类(隐式加载到堆内存中)。
英文:

I always like to go with enums whenever possible because of their type safety. That doesn't apply that much to library classes/methods the signature of which you can't change anyway (which basically is your case). But if it is in your own hands in your code...

I'd use enum's anyway and declare them just as:

public enum LoggingCategory {

    ONE,
    TWO,
    THREE,
    ...
}

Because:

  • that each of these represent a category is already stated in the enum's name. Compare the usage LoggingCategory.CATEGORY_ONE to LoggingCategory.ONE.

  • the enum doesn't need the members: variable, constructor, toString() since it supplies an implicit toString() which:

    > Returns the name of this enum constant, as contained in the declaration. This method may be overridden, though it typically isn't necessary or desirable. An enum class should override this method when a more "programmer-friendly" string form exists.

  • All uppercase for categories' names is better to distinguish them from class names (in the logs).

  • If you place your enums in an external (to your business library) commons library they also can be used by others without the burden of importing and referencing (and loading into heap memory implicitily) full-featured business classes.

答案3

得分: 2

toString()方法的性能不是问题。如果它有影响,那么影响远远小于日志调用的性能。

在我看来,使用枚举有些过于复杂,因为你并没有真正从它引入的类型安全中获益。但如果你想使用它,你应该更喜欢以下方式:

logger.info("{}: 这是一个日志", LoggingCategory.CATEGORY_ONE);

而不是

logger.info("{}: 这是一个日志", LoggingCategory.CATEGORY_ONE.toString());

因为第一行只会在启用INFO级别时调用toString()

关于一般方法的注意事项

将日志类别作为消息字符串添加可能不是最佳方法,因为你会失去大多数过滤功能。我更倾向于使用以下方式:

  1. 多个日志记录器:
    public class Loggers {
        public static final Logger CATEGORY_ONE = LogManager.getLogger("CategoryOne");
        public static final Logger CATEGORY_TWO = LogManager.getLogger("CategoryTwo");
        public static final Logger CATEGORY_THREE = LogManager.getLogger("CategoryThree");
        ...
    }
    

    通常情况下,日志记录器的命名是根据使用它们的类命名的,但没有什么阻止你使用自己的名称。它们具有出色的过滤功能,并且受到所有Log4j API后端的支持。日志后端已经优化以根据日志记录器名称进行过滤。

  2. 或者我会使用标记(Markers):
    public class Markers {
        public static final Marker CATEGORY_ONE = MarkerManager.getMarker("CategoryOne");
        public static final Marker CATEGORY_TWO = MarkerManager.getMarker("CategoryTwo");
        public static final Marker CATEGORY_THREE = MarkerManager.getMarker("CategoryThree");
        ...
    }
    

    你可以这样使用它们:

    logger.info(Markers.CATEGORY_ONE, "这是一个日志");
    

    这些只受Logback和Log4j 2.x Core支持,但仍然提供了适当的过滤功能,尽管这些功能会带来轻微的性能成本。

英文:

The performance of the toString() is not an issue. If it has an impact, it is several orders of magnitude smaller than the performance of the logging call.

Using an enum is IMHO an overkill, since you don't really profit from the type safety it introduces. But if you want to use it, you should prefer:

logger.info("{}: This is a log", LoggingCategory.CATEGORY_ONE);

over

logger.info("{}: This is a log", LoggingCategory.CATEGORY_ONE.toString());

since the first line will only call toString() if INFO is enabled.

Note about the general approach

Adding the logging category as a string in the message is probably not the best idea, since you lose most filtering features. I would rather use:

  1. Multiple loggers:
    public class Loggers {
        public static final Logger CATEGORY_ONE = LogManager.getLogger("CategoryOne");
        public static final Logger CATEGORY_TWO = LogManager.getLogger("CategoryTwo");
        public static final Logger CATEGORY_THREE = LogManager.getLogger("CategoryThree");
        ...
    }
    

    Loggers are usually named after the class that uses them, but nothing prevents you from using you own names. They have excellent filtering features and are supported by all Log4j API backends. Logging backends are optimized to filter according to logger names.

  2. Or I would use markers:
    public class Markers {
        public static final Marker CATEGORY_ONE = MarkerManager.getMarker("CategoryOne");
        public static final Marker CATEGORY_TWO = MarkerManager.getMarker("CategoryTwo");
        public static final Marker CATEGORY_THREE = MarkerManager.getMarker("CategoryThree");
        ...
    }
    

    that you can use as:

    logger.info(Markers.CATEGORY_ONE, "This is a log");
    

    These are only supported by Logback and Log4j 2.x Core, but still offer adequate filtering features, although these come at a slight performance cost.

答案4

得分: 1

> "所以,这是真的吗?直接调用方法会影响性能?"

这里讨论的是你是在专业环境中工作还是不是。

如果这些仅仅是朋友或熟人提供的建议,那我会简单地选择提供更可扩展接口的方法。

性能上的差别可能是毫秒级的。

如果是专业环境,那么后一种建议是正确的;将枚举(enum)转换为字符串会降低性能。

争论是否提供某种延迟阈值,比如1毫秒与5毫秒,不是一个话题,因为性能要求只是业务间的标准化。如果可以在图表上显示出来,那就是合乎逻辑的。

我更喜欢枚举类,它们提供了快速枚举一组唯一值的能力,同时还创建了一个易于扩展的接口。

相反,第一个建议——使用常量字符串值——可能是一种微不足道的设计实践。

例如,在另一种情况下,你可能会存储数字值,它们的顺序值取决于调用对象。可扩展性会大大降低。

Pattern类使用这个概念来定义正则表达式模式。我也在Android API中看到过它。如果我没记错的话,我相信Java AWT框架也广泛使用了这种模式。

考虑以下情况。

static int VALUE_A = 1;
static int VALUE_B = 2;
static int VALUE_C = 3;
static int VALUE_D = 4;

如果在程序的将来版本中,我需要移除VALUE_C,那就需要我现在编辑VALUE_D的值,以及后续的任何字段。结果,如果不更改,程序会在运行时出错。

由于你使用的是字符串值,情况并非如此。正如你所知道的,它会起作用,因为可扩展性实际上不是一个问题。这可以类比于在语句上方添加@SuppressWarnings注解。你知道有一个更好的设计,并告诉编译器忽略它。

最后,我认为之所以让你选择更古老的方法是因为这实质上是在编译时将枚举对象转换成了一个带有静态字段的对象。不要引用我的话。"_

英文:

> "So, is it true? The explicit call of the method affects performance?"

The argument here is whether you're working in a professional environment or not.

If these are merely friends, or acquaintances, who are providing recommendations, then I would simply choose whichever approach offers the more scalable interface, than the other.
The mark in performance would be a millisecond measurement.

If it is a professional environment, then the latter recommendation is correct; that the enum's conversion to a String will decrease performance.
The debate to provide some sort of latency threshold—e.g., 1 ms versus 5 ms—is not a topic, since the performance requirement is simply a standardization among the business.
If it can be displayed on a graph, then it's logical.

I prefer enum classes, they provide the ability to quickly enumerate a set of unique values, while also creating an easily scalable interface.

On the contrary, the first recommendation—of constant string values—can be a trivial design practice.

For example, in another situation, where you may be storing numerical values, in which their sequencing values are depended on, by calling objects.
The scalability is reduced significantly.

The Pattern class uses this concept, to define regular-expression modes.
I've also seen it within the Android API.
And, if I'm not mistaken, I believe the Java AWT framework uses this pattern quite extensively.

Consider the following.

static int VALUE_A = 1;
static int VALUE_B = 2;
static int VALUE_C = 3;
static int VALUE_D = 4;

If, in a future version of the program, I need to remove VALUE_C, it would require me to now edit the value of VALUE_D, along with any subsequent fields.
Which, consequently, would error the program at run-time, if not changed.

Since you are using String values, this isn't the case.  As, you know for a fact that it will work, since the scalability isn't really an issue.
This can be analogous to adding an @SuppressWarnings annotation above a statement.
You know there is a better design, and are telling the compiler to ignore it.

As a final note, I believe that the reason you are being told to choose the more archaic approach is because that is what, essentially, an enum object is converted into, at compile time.
An object with static fields.
Don't quote me on that.

huangapple
  • 本文由 发表于 2023年6月12日 04:01:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76452309.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定