在《一口一口吃掉Hibernate(一)——使用SchemaExport生成数据表》中介绍了如何生成数据表。但是这只是最基本的。hibernate在生成或者操作数据库时,会受一些限制。比如一开始设计表的时候,直接写成了user(id,name,password,createTime)  这种格式的。但是客户后来要求表名称要以“t_”开头,字段名要以“stu_”开头,可以是一些数据库的关键字,单词之间以“_”隔开,方便他们维护.例如:T_User(stu_Id,stu_Name,stu_Create_Time)。

对于上面的问题,常规的方法就是去修改Hibernate_cfg.xml中的表,为每个表添加“t_”,或者写table属性,字段也重新使用column属性来修改。如果你有100张表,每个表平均有10个字段,你还会去一个一个修改么?还有可能漏掉或者改错。最“可恶”的是,如果客户再让你改成其他的呢,是不是还要全部修改一次?

有没有一种方法,不修改原先的配置,添加并配置一下表和字段的前后缀及分隔符等等,既可以完成呢??经过尝试,终于被我拿下了,现在拿出来与大家分享。

针对上述问题,我主要用的是NamingStrategy接口。这个接口很有意思,可以作为业务类规范和数据库表规范的桥梁,它可以表和列的映射关系的转变和处理。例如一个数据对象User,对应数据库表是T_USER,如果所有映射关系都是这样的情况,可以使用NamingStrategy做为一个桥梁衔接,而不用再hbm.xml映射文件中指定class对应的物理table。

先说一下Hibernate读取映射文件的简要过程:循环读取class标签,判断class标签的table属性值是否为null,若为null,则调用NamingStrategy接口的classToTableName方法将class值转换并处理(按命名策略NamingStrategy处理)的表名。若不为null,则说明设置了table,那么调用NamingStrategy接口的tableName方法获取处理过的表名。循环遍历property,判断property标签的column属性值是否为null,若为null,则首先调用propertyToColumnName方法,将属性值转化为列名,再调用logicalColumnName方法。若不为null,则直接调用NamingStrategy接口的logicalColumnName方法,再调用columnName方法进行处理(按命名策略NamingStrategy处理)。

了解了上面说的这个过程,只要我们实现NamingStrategy这个接口,就可以随心所欲的修改表映射了。不多说了,直接上代码吧:

【MyNamingStrategy】自定义的命名策略,可对表、字段进行统一处理。

  1. package com.tgb.hibernate.neatlyDB;
  2. import org.hibernate.cfg.Configuration;
  3. import org.hibernate.cfg.NamingStrategy;
  4. import org.hibernate.util.StringHelper;
  5. /**
  6. * 数据库命名策略,可定制表、字段等的前后缀
  7. *
  8. * @author Longxuan
  9. *
  10. */
  11. public class MyNamingStrategy implements NamingStrategy {
  12. /**
  13. * 数据库命名策略单一实例
  14. */
  15. private static MyNamingStrategy instance = null;
  16. /**
  17. * 数据库配置信息类
  18. */
  19. private static DatabaseConfiguration dbcfg = null;
  20. /**
  21. * 私有化构造方法
  22. */
  23. private MyNamingStrategy() {
  24. }
  25. /**
  26. * 使用单利模式,获取数据库命名策略的唯一实例
  27. *
  28. * @return
  29. */
  30. public static synchronized MyNamingStrategy getInstance() {
  31. // 默认读取hibernate.cfg.xml文件
  32. Configuration cfg = new Configuration().configure();
  33. return getInstance(cfg);
  34. }
  35. /**
  36. * 使用单利模式,获取数据库命名策略的唯一实例,同时使用默认Hibernate.cfg.xml更新命名策略信息
  37. *
  38. * @param cfg
  39. *            配置文件信息
  40. * @return
  41. */
  42. public static synchronized MyNamingStrategy getInstance(Configuration cfg) {
  43. if (instance == null) {
  44. instance = new MyNamingStrategy();
  45. }
  46. MyNamingStrategy.dbcfg = new DatabaseConfiguration(cfg);
  47. return instance;
  48. }
  49. /**
  50. * 使用单利模式,获取数据库命名策略的唯一实例,同时更新命名策略信息
  51. *
  52. * @param cfg
  53. *            配置文件信息
  54. * @return
  55. */
  56. public static synchronized MyNamingStrategy getInstance(
  57. DatabaseConfiguration dbcfg) {
  58. if (instance == null) {
  59. instance = new MyNamingStrategy();
  60. }
  61. MyNamingStrategy.dbcfg = dbcfg;
  62. return instance;
  63. }
  64. /**
  65. * 设置或更新数据库配置信息
  66. *
  67. * @param dbcfg
  68. */
  69. public void setDBConfig(DatabaseConfiguration dbcfg) {
  70. MyNamingStrategy.dbcfg = dbcfg;
  71. }
  72. /**
  73. * 转化为物理表名
  74. *
  75. * @param className hbm.xml中的对应表的class值
  76. */
  77. @Override
  78. public String classToTableName(String className) {
  79. if(className == null ) return null;
  80. //改变大小写
  81. String str =UpdateStyle(className,dbcfg.getTableStyle().name());
  82. //添加前后缀
  83. str = addPrefixORStuffix(str, dbcfg.getTablePrefix(),dbcfg.getTableSuffix());
  84. //添加分隔符
  85. str = addSeparator(str,dbcfg.getTableSeparator());
  86. return str;
  87. }
  88. @Override
  89. public String collectionTableName(String ownerEntity,
  90. String ownerEntityTable, String associatedEntity,
  91. String associatedEntityTable, String propertyName) {
  92. return null;
  93. }
  94. /**
  95. * 将读取到的列名传递过来进行加工处理,变为物理表的字段名
  96. *
  97. * @param columnName logicalColumnName方法读取到的列名
  98. */
  99. @Override
  100. public String columnName(String columnName) {
  101. //先调用logicalColumnName方法,拿到处理过的值,
  102. //再传递到该方法中,所以可以在这两个方法中任一选择修改。
  103. //return columnName;
  104. if(columnName == null ) return null;
  105. //改变大小写
  106. String str =UpdateStyle(columnName,dbcfg.getColumnTyle().name());
  107. //添加前后缀
  108. str = addPrefixORStuffix(StringHelper.unqualify(str), dbcfg.getColumnPrefix(),dbcfg.getColumnSuffix());
  109. //添加分隔符
  110. str = addSeparator(str,dbcfg.getColumnSeparator());
  111. return str;
  112. }
  113. /**
  114. * 处理外键列名
  115. *
  116. */
  117. @Override
  118. public String foreignKeyColumnName(String propertyName,
  119. String propertyEntityName, String propertyTableName,
  120. String referencedColumnName) {
  121. if(referencedColumnName == null ) return null;
  122. //改变大小写
  123. String str =UpdateStyle(referencedColumnName,dbcfg.getColumnTyle().name());
  124. //添加前后缀
  125. str = addPrefixORStuffix(StringHelper.unqualify(str), dbcfg.getColumnPrefix(),dbcfg.getColumnSuffix());
  126. //添加分隔符
  127. str = addSeparator(str,dbcfg.getColumnSeparator());
  128. return str;
  129. }
  130. /**
  131. * 关联键的名称
  132. */
  133. @Override
  134. public String joinKeyColumnName(String joinedColumn, String joinedTable) {
  135. if(joinedColumn == null ) return null;
  136. //改变大小写
  137. String str =UpdateStyle(joinedColumn,dbcfg.getColumnTyle().name());
  138. //添加前后缀
  139. str = addPrefixORStuffix(StringHelper.unqualify(str), dbcfg.getColumnPrefix(),dbcfg.getColumnSuffix());
  140. //添加分隔符
  141. str = addSeparator(str,dbcfg.getColumnSeparator());
  142. return str;
  143. }
  144. @Override
  145. public String logicalCollectionColumnName(String columnName,
  146. String propertyName, String referencedColumn) {
  147. return null;
  148. }
  149. @Override
  150. public String logicalCollectionTableName(String tableName,
  151. String ownerEntityTable, String associatedEntityTable,
  152. String propertyName) {
  153. return null;
  154. }
  155. /**
  156. * 处理逻辑列名
  157. * <b>若设置了column属性,会直接调用调用该方法</b>
  158. *
  159. * @param columnName    列名
  160. * @param propertyName  实体列名
  161. */
  162. @Override
  163. public String logicalColumnName(String columnName, String propertyName) {
  164. //name值对应propertyName,column值对应columnName
  165. //判断columnName是否设置,若设置了,则返回column值,否则返回propertyName值
  166. return columnName == null || columnName =="" ? propertyName:columnName;
  167. }
  168. /**
  169. * 处理逻辑列名
  170. * <b>若未设置column属性,则先调用该方法,再调用logicalColumnName方法</b>
  171. *
  172. * @param propertyName  实体列名
  173. */
  174. @Override
  175. public String propertyToColumnName(String propertyName) {
  176. if(propertyName == null ) return null;
  177. //改变大小写
  178. String str =UpdateStyle(propertyName,dbcfg.getColumnTyle().name());
  179. //添加前后缀
  180. str = addPrefixORStuffix(StringHelper.unqualify(str), dbcfg.getColumnPrefix(),dbcfg.getColumnSuffix());
  181. //添加分隔符
  182. str = addSeparator(str,dbcfg.getColumnSeparator());
  183. return str;
  184. }
  185. /**
  186. * 处理表名
  187. * <b>若设置了table属性,才会调用该方法</b>
  188. */
  189. @Override
  190. public String tableName(String tableName) {
  191. if(tableName == null ) return null;
  192. //改变大小写
  193. String str =UpdateStyle(tableName,dbcfg.getTableStyle().name());
  194. //添加前后缀
  195. str = addPrefixORStuffix(StringHelper.unqualify(str), dbcfg.getTablePrefix(),dbcfg.getTableSuffix());
  196. //添加分隔符
  197. str = addSeparator(str,dbcfg.getTableSeparator());
  198. return str;
  199. }
  200. /**
  201. * 单词分隔符
  202. *
  203. * @param name
  204. * @return
  205. */
  206. private String addSeparator(String str, String flag) {
  207. StringBuffer buf = new StringBuffer(str.substring(str.lastIndexOf(".")+1));
  208. for (int i = 1; i < buf.length() - 1; i++) {
  209. if ('_' != buf.charAt(i - 1)
  210. && Character.isUpperCase(buf.charAt(i))
  211. && !Character.isUpperCase(buf.charAt(i + 1))) {
  212. // buf.insert(i++, '_');
  213. buf.insert(i++, flag);
  214. }
  215. }
  216. return buf.toString();
  217. }
  218. /**
  219. * 添加前后缀
  220. *
  221. * @param str
  222. *            字符串
  223. * @param prefix
  224. *            前缀
  225. * @param suffix
  226. *            后缀
  227. * @return
  228. */
  229. private String addPrefixORStuffix(String str, String prefix, String suffix) {
  230. StringBuffer buf = new StringBuffer(str.substring(str.lastIndexOf(".")+1));
  231. buf.insert(buf.length(), suffix).insert(0, prefix);
  232. return buf.toString();
  233. }
  234. /**
  235. * 更新样式
  236. * @param str
  237. * @param style
  238. * @return
  239. */
  240. private String UpdateStyle(String str, String style) {
  241. if ("AU".equals(style)) {
  242. str = str.toUpperCase();
  243. } else if ("AL".equals(style)) {
  244. str = str.toLowerCase();
  245. } else if ("FU".equals(style)) {
  246. StringBuffer buf = new StringBuffer(str.substring(str.lastIndexOf(".")+1));
  247. String upper =buf.substring(0,1).toUpperCase();
  248. buf.delete(0, 1).insert(0, upper);
  249. str = buf.toString();
  250. } else if ("FL".equals(style)) {
  251. StringBuffer buf = new StringBuffer(str.substring(str.lastIndexOf(".")+1));
  252. String lower =buf.substring(0,1).toLowerCase();
  253. buf.delete(0, 1).insert(0, lower);
  254. str = buf.toString();
  255. }  else{
  256. }
  257. return str;
  258. }
  259. }

【DatabaseConfiguration】数据库样式配置,包括前后缀、分隔符、大小写等

  1. package com.tgb.hibernate.neatlyDB;
  2. import org.hibernate.cfg.Configuration;
  3. /**
  4. * 数据库模式配置
  5. * @author Longxuan
  6. *
  7. */
  8. public final class DatabaseConfiguration {
  9. /**
  10. * 列名样式
  11. * AU-all upper case
  12. * AL-all lower case
  13. * FU-first upper case per word
  14. * FL-first lower case ,Other first letter upper case
  15. */
  16. public static enum ColumnTyle {
  17. AU,AL,FU,FL;
  18. }
  19. /**
  20. * 表名称样式
  21. * AU-all upper case
  22. * AL-all lower case
  23. * FU-first upper case per word
  24. * FL-first lower case ,Other first letter upper case
  25. */
  26. public static enum TableStyle{
  27. AU,AL,FU,FL;
  28. }
  29. /**
  30. * 列名样式
  31. * AU-all upper case
  32. * AL-all lower case
  33. * FU-first upper case per word
  34. * FL-first lower case ,Other first letter upper case
  35. */
  36. private ColumnTyle columnTyle = ColumnTyle.FL;
  37. /**
  38. * 表名称样式
  39. * AU-all upper case
  40. * AL-all lower case
  41. * FU-first upper case per word
  42. * FL-first lower case ,Other first letter upper case
  43. */
  44. private TableStyle tableStyle = TableStyle.FU;
  45. /**
  46. * 表名称前缀
  47. */
  48. private String tablePrefix = "";
  49. /**
  50. * 表名称后缀
  51. */
  52. private String tableSuffix = "";
  53. /**
  54. * 表名称分隔符
  55. */
  56. private String tableSeparator = "";
  57. /**
  58. * 列名前缀
  59. */
  60. private String columnPrefix = "";
  61. /**
  62. * 列名后缀
  63. */
  64. private String columnSuffix = "";
  65. /**
  66. * 列名分隔符
  67. */
  68. private String columnSeparator = "";
  69. public ColumnTyle getColumnTyle() {
  70. return columnTyle;
  71. }
  72. public void setColumnTyle(ColumnTyle columnTyle) {
  73. this.columnTyle = columnTyle;
  74. }
  75. public TableStyle getTableStyle() {
  76. return tableStyle;
  77. }
  78. public void setTableStyle(TableStyle tableStyle) {
  79. this.tableStyle = tableStyle;
  80. }
  81. public String getTablePrefix() {
  82. return tablePrefix;
  83. }
  84. public void setTablePrefix(String tablePrefix) {
  85. this.tablePrefix = tablePrefix;
  86. }
  87. public String getTableSuffix() {
  88. return tableSuffix;
  89. }
  90. public void setTableSuffix(String tableSuffix) {
  91. this.tableSuffix = tableSuffix;
  92. }
  93. public String getTableSeparator() {
  94. return tableSeparator;
  95. }
  96. public void setTableSeparator(String tableSeparator) {
  97. this.tableSeparator = tableSeparator;
  98. }
  99. public String getColumnPrefix() {
  100. return columnPrefix;
  101. }
  102. public void setColumnPrefix(String columnPrefix) {
  103. this.columnPrefix = columnPrefix;
  104. }
  105. public String getColumnSuffix() {
  106. return columnSuffix;
  107. }
  108. public void setColumnSuffix(String columnSuffix) {
  109. this.columnSuffix = columnSuffix;
  110. }
  111. public String getColumnSeparator() {
  112. return columnSeparator;
  113. }
  114. public void setColumnSeparator(String columnSeparator) {
  115. this.columnSeparator = columnSeparator;
  116. }
  117. /**
  118. * 初始化,读取配置参数,默认读取Hibernate.cfg.xml
  119. * @param cfg
  120. */
  121. public DatabaseConfiguration(){
  122. ReadConfig(new Configuration().configure());
  123. }
  124. /**
  125. * 初始化,读取配置文件
  126. * @param cfg
  127. */
  128. public DatabaseConfiguration(Configuration cfg){
  129. ReadConfig(cfg);
  130. }
  131. /**
  132. * 自动读取配置信息
  133. * @param cfg
  134. */
  135. private void ReadConfig(Configuration cfg){
  136. tableStyle = cfg.getProperty("tablestyle")==""?TableStyle.FU:TableStyle.valueOf(cfg.getProperty("tablestyle"));
  137. tablePrefix = cfg.getProperty("tablePrefix");
  138. tableSuffix = cfg.getProperty("tablesuffix");
  139. tableSeparator = cfg.getProperty("tableseparator");
  140. columnTyle = cfg.getProperty("columntyle")==""?ColumnTyle.FL:ColumnTyle.valueOf(cfg.getProperty("columntyle"));
  141. columnPrefix = cfg.getProperty("columnPrefix");
  142. columnSuffix = cfg.getProperty("columnsuffix");
  143. columnSeparator = cfg.getProperty("columnseparator");
  144. }
  145. }

【hibernate.cfg.xml】设置数据库样式配置信息

  1. <!DOCTYPE hibernate-configuration PUBLIC
  2. "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  3. "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  4. <hibernate-configuration>
  5. <session-factory name="foo">
  6. <!--
  7. name:       配置的名称
  8. style:      类型 枚举AU-all upper case
  9. AL-all lower case
  10. FU-first letter upper case per word
  11. FL-first letter lower case ,Other first letters upper case
  12. befix:      前缀
  13. suffix:     后缀
  14. separator:  分隔符
  15. -->
  16. <property name="tablestyle">FU</property>
  17. <property name="tablePrefix" >T_</property>
  18. <property name="tablesuffix" ></property>
  19. <property name="tableseparator" >_</property>
  20. <property name="columntyle">FU</property>
  21. <property name="columnPrefix">[stu_</property>
  22. <property name="columnsuffix">]</property>
  23. <property name="columnseparator">_</property>
  24. <property name="hibernate.dialect" >org.hibernate.dialect.MySQLDialect</property>
  25. <property name="hibernate.show_sql">true</property><!-- 设置是否显示生成sql语句 -->
  26. <property name="hibernate.format_sql">false</property><!-- 设置是否格式化sql语句-->
  27. <mapping resource="com/bjpowernode/hibernate/User.hbm.xml" />
  28. </session-factory>
  29. </hibernate-configuration>

【User.hbm.xml】映射文件可以比较随意的修改或者设定了

  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  5. <hibernate-mapping>
  6. <class name="com.bjpowernode.hibernate.User" table="Users">
  7. <id name="id" column="pid">
  8. <generator class="uuid" />
  9. </id>
  10. <property name="name" column="userName"></property>
  11. <property name="password" column="pwd"></property>
  12. <property name="createTime"></property>
  13. <property name="expireTime"></property>
  14. </class>
  15. </hibernate-mapping>

最后修改一下【ExportDB】,主要是修改必须先设置NamingStrategy,在调用configure方法

  1. package com.bjpowernode.hibernate;
  2. import org.hibernate.cfg.Configuration;
  3. import org.hibernate.tool.hbm2ddl.SchemaExport;
  4. import com.tgb.hibernate.neatlyDB.DatabaseConfiguration;
  5. import com.tgb.hibernate.neatlyDB.MyNamingStrategy;
  6. /**
  7. * 将hbm生成ddl
  8. * @author Longxuan
  9. *
  10. */
  11. public class ExportDB {
  12. /**
  13. * @param args
  14. */
  15. public static void main(String[] args) {
  16. //默认读取hibernate.cfg.xml文件,并按照命名策略修改表、字段的名称
  17. //Configuration cfg = new Configuration().configure();
  18. //注意,必须先设置NamingStrategy,在调用configure方法
  19. Configuration cfg = new Configuration().setNamingStrategy(MyNamingStrategy.getInstance()).configure();
  20. SchemaExport export = new SchemaExport(cfg);
  21. //true 在控制台打印ddl语句,true 导入ddl语句到数据库,即可执行
  22. export.create(true, true);
  23. }
  24. }

看一下结果图吧:表名,字段名都已经修改过了。

上面演示的是生成数据表。在对数据库的增删改查时,只要同样设置NamingStrategy即可。

为了大家更为方便的使用,已经做成jar包分享给大家了,点这里下载。

Hibernate给表和字段设置前后缀及分隔符的更多相关文章

  1. django 重写User表增加字段设置

    models中: from django.contrib.auth.models import AbstractUser lass User(AbstractUser): mobile = model ...

  2. mysql表时间戳字段设置

    创建时间 修改时间  

  3. Hibernate 3中如何获得库表所有字段的名称

    15问:Hibernate 3中如何获得库表所有字段的名称 答:可以使用以下的程序获得. Configuration conf = new Configuration(); conf.configur ...

  4. 在高并发、高负载的情况下,如何给表添加字段并设置DEFAULT值?

    在高并发.高负载的情况下,如何给表添加字段并设置DEFAULT值? 在Oracle 12c之前,当Oracle表数据量上亿时,对表执行“ALTER TABLE XXX ADD COLUMN_XX VA ...

  5. MySQL 创建表时,设置时间字段自己主动插入当前时间

    MySQL 创建表时,设置时间字段自己主动插入当前时间 DROP TABLE IF EXISTS `CONTENT`; CREATE TABLE `CONTENT` ( `ID` char(20) N ...

  6. 如何更精准地设置 C# / .NET Core 项目的输出路径?(包括添加和删除各种前后缀)

    原文:如何更精准地设置 C# / .NET Core 项目的输出路径?(包括添加和删除各种前后缀) 我们都知道可以通过在 Visual Studio 中设置输出路径(OutputPath)来更改项目输 ...

  7. OneThink学习笔记02----数据字典(即OneThink项目数据库里的表及其字段)

    action 字段 类型 注释 id int(11) unsigned 自增主键 name char(30) 行为唯一标识 title char(80) 行为说明 remark char(140) 行 ...

  8. Hibernate学习笔记(三)Hibernate生成表单ID主键生成策略

    一. Xml方式 <id>标签必须配置在<class>标签内第一个位置.由一个字段构成主键,如果是复杂主键<composite-id>标签 被映射的类必须定义对应数 ...

  9. Ecshop 表结构 字段说明

    ecs_account_log 用户帐号情况记录表,包括资金和积分等 log_id mediumint 自增ID号user_id mediumint 用户登录后保存在session中的id号,跟use ...

随机推荐

  1. python subprocess模块使用总结

    一.subprocess以及常用的封装函数运行python的时候,我们都是在创建并运行一个进程.像Linux进程那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序.在Python ...

  2. maven编译时出现读取XXX时出错invalid LOC header (bad signature)

    问题原因 该包没有下载正确. 解决办法 找到该包的目录,删除该包重新下载即可. 重新下载后用maven test一下,红叉消失.

  3. linux 安装jdk以及nginx详细过程

    一.安装jdk 1:首先下载jdk到本地,然后通过git 上传到linux服务器上 2:进入目录usr,并创建目录java,将jdk的压缩文件移动到该目录下 cd /usr mkdir java mv ...

  4. hdu1249 三角形分割平面---递推

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1249 题目大意: 用N个三角形最多可以把平面分成几个区域? 思路: 知道了直线和折线分割平面的情况这 ...

  5. scrapy爬取极客学院全部课程

    # -*- coding: utf-8 -*- # scrapy爬取极客学院全部课程 import scrapy from pyquery import PyQuery as pq from jike ...

  6. JavaScript sort() 方法

    定义和用法 sort() 方法用于对数组的元素进行排序. 语法 arrayObject.sort(sortby) 参数 描述 sortby 可选.规定排序顺序.必须是函数. 返回值 对数组的引用.请注 ...

  7. JavaScript的数组实现队列与堆栈的方法

    一.队列和堆栈的简单介绍 1.1.队列的基本概念 队列:是一种支持先进先出(FIFO)的集合,即先被插入的数据,先被取出! 如下图所示: 1.2.堆栈的基本概念 堆栈:是一种支持后进先出(LIFO)的 ...

  8. UEditor Golang上传图片与附件

    UEditor图片与附件上传官方只支持ASP.ASP.NET.JSP.PHP四种语言版本,Golang就不在其中.因为自己开发系统的需要,我照着UEditor服务器端的接口自己实现了一个Golang版 ...

  9. C#多线程Thread.Join()的详解

    class TestThread { private static void FirstThreadFun() { ; i < ; i++) { Console.WriteLine(Thread ...

  10. 关于Java中的Null

    什么是Java中的Null? null在Java中是一个非常重要的概念,它最初是为了表示缺少某些东西,例如缺少用户.资源或任何东西而发明出来的.但是这也为Java程序员带来了很多麻烦,比如最常见的空指 ...