如何避免在java中创建多个字符串对象?

时间:2017-01-24 12:15:10

标签: java string

我有一个使用Query对象和StringBuffer的场景,我将在其中构建一个要执行的SQL查询。

这是:

countQueryBuf.append("Select count(e) From " + clz.getSimpleName()
            + " e");

我的大四学生告诉我,使用像"Select count(e) from "这样的字符串文字是不好的做法。我们可以做到:

string public final static selectCount="Select count(e) From "` 

在一个接口中,然后在字符串缓冲区中使用它。但在看到这个Interfaces with static fields in java for sharing 'constants'问题之后,我觉得这很糟糕。谁能告诉我什么是最好的方法来证明我的情景?

4 个答案:

答案 0 :(得分:6)

首先,应该强调的是,你质疑的先决条件是有缺陷的。 Java中的字符串文字不会生成多个String实例,因此定义指向这些String实例的常量不会更改实例数。但是,很可能,你的大四学生并不打算用这个建议讨论实例的数量。

字符串文字可以被视为常量值,如12344.1f。当这些值出现在代码中的某个位置时,它们通常被称为“魔术文字”,因为它们看起来是虚假的,没有可识别的来源。在这些情况下,应该首选使用名称解释其来源的命名常量。 E.g。

static final float COMPACT_DISC_FREQ_KHZ = 44.1f;

告诉你一些事情。

相反,常量如

static final int ONE = 1;

不要告诉你任何事情,也没有改进,只是试图伪造更好的编码风格。我认为像

这样的常数
static final String selectCount="Select count(e) From ";

属于同一个荒谬的类别,因为它的名字并没有告诉我任何我从常数值中看不到的东西。

是否将命名常量放入interface或普通class并没有多大区别。但是在过去,常量被放置在接口中,目的是实现接口以基本导入这些常量,以便能够通过简单的名称引用它们。它不是将常量放置到界面中,这使得它成为一种糟糕的编码风格,而是实际上没有意义的类型继承关系,它只是为了节省源代码中的输入而存在。从Java 5开始,您可以使用import static将常量放入您希望的任何类型中,并通过简单的名称引用它们,而不会产生可疑的继承关系。因此,在大多数情况下,您不希望使用interface

正如其他人已经指出的那样,您的代码还存在其他问题。对于大多数用例,StringBuffer已被StringBuilder取代,此外,将String串联与StringBufferStringBuilder用法混合使用没有多大意义

使用

countQueryBuf.append("Select count(e) From ").append(clz.getSimpleName()).append(" e");

一致,如果需要现有的countQueryBuf,即是否有其他要追加的片段。如果查询仅包含这三个片段,则代码如

String query = "Select count(e) From "+clz.getSimpleName()+" e";

是优选的。在Java 5之前,它使用了一个StringBuffer,从Java 5开始,它将被编译为使用StringBuilder并从Java 9开始,它将被编译为使用内置的String concat工厂代替。换句话说,这个简单的表达式将在(重新)编译时自动获得未来改进的好处,而手动处理StringBufferStringBuilder则需要维护并有时重写代码以赶上这样的开发

如果查询的片段代表值,则应始终使用PreparedStatement而不是每次都组合新的查询字符串...

答案 1 :(得分:3)

使用字符串连接没有问题,如

String query = "Select count(e) From " + clz.getSimpleName() + " e";

只要你不在循环中这样做。 在循环或更复杂的String-assembly中,最好使用StringBuilder,这是StringBuffer的非同步版本(通常你不需要同步,除非你有多个线程创建字符串)。

StringBuilder b = new StringBuilder().append("Select count(e) From ")
                                    .append(clz.getSimpleName())
                                    .append(" e");
String query = b.toString();

作为替代方法,您可以将String格式选项与模板String一起使用。

String query = String.format("Select count(e) From %s e", clz.getSimpleName());

在性能方面,StringBuilder或串联比String格式更快。但有些情况下格式会更好(即在属性文件中定义模板)

如果您多次使用相同的字符串文字(即两次以上),则将其定义为常量并使用常量是一种很好的做法。将它放入接口并不是一个好习惯,因为接口通常不适用于此(除非你的字符串是协议的一部分)

我通常将它们作为private static final String放在同一个班级中。如果多个类使用它们,您可以将它们放在final常量类中或使用枚举。

此外,设置StringBuilder的初始容量是一个很好的做法,它应该是4个任意多个(预期大小)(或参见here)或使用初始字符串的大小(>) (初始大小是String + 16的大小),以改善内存分配。

private final static String SELECT = "select ";
private final static String FROM = " from ";
...

String query = new StringBuilder(32).append(SELECT)
                                    .append("count(e)")
                                    .append(FROM)
                                    .append(clz.getSimpleName())
                                    .append(" e")
                                    .toString();

最后要注意的是,当您使用类似SQL的语法查询数据库但没有其他连接选项(即某些时间序列数据库,如InfluxDB)时,将SQL查询构建为字符串才有意义。通过字符串连接构建SQL查询很容易进行SQL注入,除非您正确清理输入。最好使用预准备语句或持久性框架,例如jOOQHibernate / JPA

答案 2 :(得分:3)

这不是字符串结构。它是关于如何创建SQL语句的。您不应该为此任务使用字符串连接,因为它可以对SQL注入进行破坏。

执行此操作的正确方法是使用PreparedStatement对象。

  PreparedStatement updateemp = con.prepareStatement(
     "insert into emp values(?,?,?)");

  updateemp.setInt(1,23);
  updateemp.setString(2,"Frank");
  updateemp.setString(3, "Me");
  updateemp.executeUpdate();

答案 3 :(得分:0)

对接口中常量的建议不适用于类。接口(有时)坏的原因是因为它具有非常低的内聚力,也就是说,这样的接口通常最终会成为来自整个程序的常量的倾销对象,这些常量彼此之间几乎没有关系。如果你的类有一些在该类中使用的常量,那么就有很高的凝聚力;常数与定义它们的地方有关。

但是这个问题中的代码提出了一个更大的问题。就目前而言,您只是根据类名连接表名,但如果您要连接从用户输入获取的值(例如,作为插入的参数或要在WHERE子句中使用的值),您的代码将是易受攻击的进行 SQL注入攻击。

如果您要在SQL查询中连接可能受用户影响的任何数据,请确保使用PreparedStatement