Java 带有延迟评估的 String.format
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/943367/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
String.format with lazy evaluation
提问by Juha Syrj?l?
I need something similar to String.format(...)method, but with lazy evaluation.
我需要类似于String.format(...)方法的东西,但需要惰性求值。
This lazyFormat method should return some object whose toString() method would then evaluate the format pattern.
这个 lazyFormat 方法应该返回一些对象,其 toString() 方法将评估格式模式。
I suspect that somebody has already done this. Is this available in any libararies?
我怀疑有人已经这样做了。这在任何库中都可用吗?
I want to replace this (logger is log4j instance):
我想替换这个(记录器是 log4j 实例):
if(logger.isDebugEnabled() ) {
logger.debug(String.format("some texts %s with patterns %s", object1, object2));
}
with this:
有了这个:
logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2));
I need lazyFormat to format string only if debug logging is enabled.
仅当启用调试日志记录时,我才需要lazyFormat 来格式化字符串。
采纳答案by Andreas Petersson
if you are looking for a "simple" solution:
如果您正在寻找“简单”的解决方案:
public class LazyFormat {
public static void main(String[] args) {
Object o = lazyFormat("some texts %s with patterns %s", "looong string", "another loooong string");
System.out.println(o);
}
private static Object lazyFormat(final String s, final Object... o) {
return new Object() {
@Override
public String toString() {
return String.format(s,o);
}
};
}
}
outputs:
输出:
some texts looong string with patterns another loooong string
一些带有图案的文本 looong 字符串 另一个 looong 字符串
you can of course add any isDebugEnabled()
statement inside lazyFormat if you will.
isDebugEnabled()
如果愿意,您当然可以在 lazyFormat 中添加任何语句。
回答by Andreas Petersson
if you are looking for lazy concatenation for the sake of efficient logging, take a look at Slf4Jthis allows you to write:
如果您为了高效日志记录而寻找惰性连接,请查看Slf4J这允许您编写:
LOGGER.debug("this is my long string {}", fatObject);
the string concatenation will only take place if the debug level is set.
只有在设置了调试级别时才会进行字符串连接。
回答by VonC
You could defined a wrapper in order to call the String.format()
only if needed.
您可以定义一个包装器,以便String.format()
仅在需要时调用。
See this questionfor a detailed code example.
有关详细的代码示例,请参阅此问题。
The same question has also a variadic function example, as suggested in Andreas's answer.
正如 Andreas 的回答中所建议的那样,同一个问题也有一个可变参数函数 example。
回答by coobird
Building upon Andreas' answer, I can think of a couple of approaches to the issue of only performing the formatting if the Logger.isDebugEnabled
returns true
:
基于Andreas 的回答,我可以想到几种方法来解决仅在Logger.isDebugEnabled
返回时执行格式化的问题true
:
Option 1: Pass in a "do formatting" flag
选项 1:传入“进行格式化”标志
One option is to have a method argument that tells whether or not to actually perform the formatting. A use case could be:
一种选择是使用一个方法参数来说明是否实际执行格式化。一个用例可能是:
System.out.println(lazyFormat(true, "Hello, %s.", "Bob"));
System.out.println(lazyFormat(false, "Hello, %s.", "Dave"));
Where the output would be:
输出将是:
Hello, Bob.
null
The code for lazyFormat
is:
代码为lazyFormat
:
private String lazyFormat(boolean format, final String s, final Object... o) {
if (format) {
return String.format(s, o);
}
else {
return null;
}
}
In this case, the String.format
is only executed when the format
flag is set to true
, and if it is set to false
it will return a null
. This would stop the formatting of the logging message to occur and will just send some "dummy" info.
在这种情况下,String.format
仅在format
标志设置为 时才执行true
,如果设置为false
,它将返回一个null
。这将停止发生日志消息的格式,并且只会发送一些“虚拟”信息。
So a use case with the logger could be:
因此,记录器的用例可能是:
logger.debug(lazyFormat(logger.isDebugEnabled(), "Message: %s", someValue));
This method doesn't exactly fit the formatting that is asked for in the question.
此方法并不完全符合问题中要求的格式。
Option 2: Check the Logger
选项 2:检查记录器
Another approach is to ask the logger directly if it isDebugEnabled
:
另一种方法是直接询问记录器是否isDebugEnabled
:
private static String lazyFormat(final String s, final Object... o) {
if (logger.isDebugEnabled()) {
return String.format(s, o);
}
else {
return null;
}
}
In this approach, it is expected that logger
will be visible in the lazyFormat
method. And the benefit of this approach is that the caller will not need to be checking the isDebugEnabled
method when lazyFormat
is called, so the typical use can be:
在这种方法中,预计logger
将在lazyFormat
方法中可见。这种方法的好处是调用者不需要在调用时检查isDebugEnabled
方法lazyFormat
,所以典型的用途可以是:
logger.debug(lazyFormat("Debug message is %s", someMessage));
回答by Andrew Newdigate
You could wrap the Log4J logger instance inside your own Java5-compatible/String.format compatible class. Something like:
您可以将 Log4J 记录器实例包装在您自己的 Java5-compatible/String.format 兼容类中。就像是:
public class Log4jWrapper {
private final Logger inner;
private Log4jWrapper(Class<?> clazz) {
inner = Logger.getLogger(clazz);
}
public static Log4jWrapper getLogger(Class<?> clazz) {
return new Log4jWrapper(clazz);
}
public void trace(String format, Object... args) {
if(inner.isTraceEnabled()) {
inner.trace(String.format(format, args));
}
}
public void debug(String format, Object... args) {
if(inner.isDebugEnabled()) {
inner.debug(String.format(format, args));
}
}
public void warn(String format, Object... args) {
inner.warn(String.format(format, args));
}
public void error(String format, Object... args) {
inner.error(String.format(format, args));
}
public void fatal(String format, Object... args) {
inner.fatal(String.format(format, args));
}
}
To use the wrapper, change your logger field declaration to:
要使用包装器,请将您的记录器字段声明更改为:
private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class);
The wrapper class would need a few extra methods, for example it does not currently handle of logging exceptions (ie logger.debug(message, exception)), but this shouldn't be hard to add.
包装类需要一些额外的方法,例如它当前不处理日志记录异常(即 logger.debug(message, exception)),但这应该不难添加。
Using the class would be almost identical to log4j, except strings are formatted:
使用该类几乎与 log4j 相同,除了字符串被格式化:
logger.debug("User {0} is not authorized to access function {1}", user, accessFunction)
回答by Peter Lawrey
Or you could write it as
或者你可以把它写成
debug(logger, "some texts %s with patterns %s", object1, object2);
with
和
public static void debug(Logger logger, String format, Object... args) {
if(logger.isDebugEnabled())
logger.debug(String.format("some texts %s with patterns %s", args));
}
回答by yurilo
It can be done by using parameter substitution in newest log4j 2.X version http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf:
可以通过在最新的 log4j 2.X 版本http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf 中使用参数替换来完成:
4.1.1.2 Parameter Substitution
Frequently the purpose of logging is to provide information about what is happening in the system, which requires including information about the objects being manipulated. In Log4j 1.x this could be accomplished by doing:
4.1.1.2 参数替换
通常,日志记录的目的是提供有关系统中正在发生的事情的信息,这需要包括有关正在操作的对象的信息。在 Log4j 1.x 中,这可以通过执行以下操作来完成:
if (logger.isDebugEnabled()) {
logger.debug("Logging in user " + user.getName() + " with id " + user.getId());
}
Doing this repeatedly has the effect of making the code feel like it is more about logging than the actual task at hand. In addition, it results in the logging level being checked twice; once on the call to isDebugEnabled and once on the debug method. A better alternative would be:
重复执行此操作会使代码感觉它更多是关于日志记录而不是手头的实际任务。此外,它会导致日志级别被检查两次;一次调用 isDebugEnabled 一次,一次调用调试方法。更好的选择是:
logger.debug("Logging in user {} with id {}", user.getName(), user.getId());
With the code above the logging level will only be checked once and the String construction will only occur when debug logging is enabled.
使用高于日志级别的代码将只检查一次,并且字符串构造只会在启用调试日志记录时发生。
回答by chaotic3quilibrium
IMPORTANT NOTE:It is strongly recommended all logging code be moved to use SLF4J(especially log4j 1.x). It protects you from being stuck with any sort of idiosyncratic issues (i.e. bugs) with specific logging implementations. Not only does it have "fixes" for well know backend implementation issues, it also works with newer faster implementations which have emerged over the years.
重要说明:强烈建议将所有日志代码移至使用SLF4J(尤其是 log4j 1.x)。它可以保护您免受特定日志记录实现的任何类型的特殊问题(即错误)的困扰。它不仅对众所周知的后端实现问题进行了“修复”,而且还适用于多年来出现的更新更快的实现。
In direct response to your question, here what it would look like using SLF4J:
直接回答您的问题,这里使用SLF4J会是什么样子:
LOGGER.debug("some texts {} with patterns {}", object1, object2);
The most important bit of what you have provided is the fact you are passing two Object instances. The object1.toString()
and the object2.toString()
methods are not immediately evaluated. More importantly, the toString()
methods are only evaluated if the data they return is actually going to be used; i.e. the real meaning of lazy evaluation.
您提供的最重要的一点是您正在传递两个 Object 实例。该object1.toString()
和object2.toString()
方法不立即进行评估。更重要的是,这些toString()
方法只有在它们返回的数据实际上会被使用时才会被评估;即懒惰评估的真正含义。
I tried to think of a more general pattern I could use which didn't require my having to override toString()
in tons of classes (and there are classes where I don't have access to do the override). I came up with a simple drop-in-place solution. Again, using SLF4J, I compose the string only if/when logging for the level is enabled. Here's my code:
我试图想出一个我可以使用的更通用的模式,它不需要我必须覆盖toString()
大量的类(并且有些类我无权进行覆盖)。我想出了一个简单的就地解决方案。同样,使用SLF4J,我仅在启用级别日志记录时才编写字符串。这是我的代码:
class SimpleSfl4jLazyStringEvaluation {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSfl4jLazyStringEvaluation.class);
...
public void someCodeSomewhereInTheClass() {
//all the code between here
LOGGER.debug(
"{}"
, new Object() {
@Override
public String toString() {
return "someExpensiveInternalState=" + getSomeExpensiveInternalState();
}
}
//and here can be turned into a one liner
);
}
private String getSomeExpensiveInternalState() {
//do expensive string generation/concatenation here
}
}
And to simplify into the one-liner, you can shorten the LOGGER line in someCodeSomewhereInTheClass() to be:
为了简化为one-liner,您可以将 someCodeSomewhereInTheClass() 中的 LOGGER 行缩短为:
LOGGER.debug("{}", new Object(){@Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}});
I have now refactored all my logging code to follow this simple model. It has tidied things up considerably. And now when I see any logging code which does not use this, I refactor the logging code to use this new pattern even if it is needed yet. That way, if/when a change is made later to need to add some "expensive" operation, the infrastructure boilerplate is already there simplifying the task to just adding the operation.
我现在已经重构了我所有的日志代码以遵循这个简单的模型。它已经把事情整理得很好。现在当我看到任何不使用它的日志代码时,我重构日志代码以使用这个新模式,即使它是需要的。这样,如果/当稍后进行更改以需要添加一些“昂贵”的操作时,基础设施样板已经存在,将任务简化为仅添加操作。
回答by Victor
Introduced in Log4j 1.2.16 are two classes that will do this for you.
Log4j 1.2.16 中引入的两个类将为您执行此操作。
org.apache.log4j.LogMF
which uses a java.text.MessageFormat
for format you messages and org.apache.log4j.LogSF
which uses the "SLF4J pattern syntax" and is said to be faster.
org.apache.log4j.LogMF
它使用java.text.MessageFormat
for 格式您的消息并org.apache.log4j.LogSF
使用“SLF4J 模式语法”,据说速度更快。
Here are examples:
以下是示例:
LogSF.debug(log, "Processing request {}", req);
and
和
LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5);
回答by smakks
If you like the String.format Syntax better than the {0} Syntax and can use Java 8 / JDK 8you can use lambdas / Suppliers:
如果您更喜欢 String.format 语法而不是 {0} 语法并且可以使用Java 8 / JDK 8,则可以使用 lambdas / Suppliers:
logger.log(Level.FINER, () -> String.format("SomeOperation %s took %04dms to complete", name, duration));
logger.log(Level.FINER, () -> String.format("SomeOperation %s took %04dms to complete", name, duration));
()->...
acts as a Supplier here and will be evaluated lazily.
()->...
在这里充当供应商,将被懒惰地评估。