在本章中,我们将介绍以下内容:
- Java 7 中的处理周
- 在 Java7 中使用货币
- 使用 NumericShaper.Range 枚举支持数字显示
- Java7 中的 JavaBean 改进
- 在 Java7 中处理区域设置和 Locale.Builder 类
- 处理空引用
- 在 Java7 中使用新的位集方法
本章将介绍 Java 7 中许多新添加的内容,这些内容不适合前面的章节。其中许多增强具有潜在的广泛应用,例如在 Java7 配方中的处理语言环境和 Locale.Builder 类中讨论的 java.lang.Objects
类和 java.util.Locale
类改进。其他的则更加专业化,比如对 java.util.BitSet
类的改进,这在中使用了 Java7配方中的新位集方法。
在处理周数和货币方面有了一些改进。当前周和每年周数的计算受区域设置的影响。此外,现在可以确定平台上可用的货币。这些问题在 Java 7 和中的处理周中说明,在 Java 7配方中使用货币。
添加了一个新的枚举,简化了不同语言中数字的显示。使用 NumericShaper.Range 枚举来支持数字配方的显示,其中讨论了 java.awt.font.NumericShaper
类在这方面的使用。Java7 配方中的JavaBean 改进讨论了 JavaBeans 支持方面的改进。
还有一些增强功能,它们不保证单独的配方。本导言的其余部分将专门讨论这些主题。
Unicode 6.0是 Unicode 标准的最新版本。Java7 支持这个版本,增加了数千个字符和许多新方法。此外,正则表达式模式匹配支持 Unicode 6.0,使用**\u或\x**转义序列。
许多新的字符块被添加到 Character.UnicodeBlock
类中。Java 7 中增加了 Character.UnicodeScript
枚举,以表示在Unicode 标准附录 24:脚本名称中定义的字符脚本。
有关 Unicode 标准附件 24:脚本名称的更多信息,请访问http://download.oracle.com/javase/7/docs/api/index.html 。
Character
类中添加了几个方法以支持 Unicode 操作。下面说明了它们在字符串中的用法朝鲜圆, 这是基于区域设置的朝鲜元中文显示名称,以及中国大陆使用的简化脚本。将以下代码序列添加到新应用程序:
int codePoint = Character.codePointAt("朝鲜圆", 0);
System.out.println("isBmpCodePoint: " + Character.isBmpCodePoint(codePoint));
System.out.println("isSurrogate: " + Character.isSurrogate('朝'));
System.out.println("highSurrogate: " + (int)Character.highSurrogate(codePoint));
System.out.println("lowSurrogate: " + (int)Character.lowSurrogate(codePoint));
System.out.println("isAlphabetic: " + Character.isAlphabetic(codePoint));
System.out.println("isIdeographic: " + Character.isIdeographic(codePoint));
System.out.println("getName: " + Character.getName(codePoint));
执行时,输出应如下所示:
isBmpCodePoint:true
发布错误:错误
高级代理:55257
低代:57117
isAlphabetic:对
isIdeographic:true
getName:CJK 统一表意文字 671D
由于字符不是 Unicode 代理代码, highSurrogate
和 lowSurrogate
方法结果没有用处。
有关 Unicode 6.0 的更多信息,请访问http://www.unicode.org/versions/Unicode6.0.0/ 。
Java7 引入了新的静态方法来比较原始数据类型 Boolean, byte, long, short
和 int
。每个包装器类现在都有一个 compare
方法,该方法将数据类型的两个实例作为参数,并返回一个表示比较结果的整数。例如,您以前可能需要使用 compareTo
方法来比较两个布尔变量 x 和 y,如下所示:
Boolean.valueOf(x).compareTo(Boolean.valueOf(y))
您现在可以按如下方式使用 compare
方法:
Boolean.compare(x,y);
虽然对于布尔数据类型而言,这对 Java 来说是新的, compare
方法以前可用于 doubles
和 floats
。此外,在 7 中,用于将字符串转换为数值的 parse, valueof
和 decode
方法将接受数据类型为 Byte, Short, Integer, Long
和 BigInteger
的前导加号(+),以及之前接受该符号的 Float, Double
和 BigDecimal
。
java.util.logging.Logger
类有一个新方法 getGlobal
,用于检索名为 GLOBAL_LOGGER_NAME
的全局记录器对象。当 Logger
类与 LogManager
类一起使用时, Logger
类的静态字段全局容易发生死锁,因为这两个类将互相等待以完成初始化。 getGlobal
方法是访问 global logger
对象的首选方法,以防止此类死锁。
从 Java7 开始,JavaDocs 有了显著的改进。从结构的角度来看,HTML 页面的生成现在是通过使用 HTMLTree
类创建文档树来完成的,这将导致更准确的 HTML 生成和更少的无效页面。
JavaDocs 也有一些外部更改,其中一些更改是为了遵守新的第 508 节可访问性指南。开发这些功能是为了确保用于将基于 web 的文本转换为音频输出的屏幕阅读器能够准确地翻译 HTML 页面。这主要导致在表格上添加了更多的标题和标题。JavaDocs 现在还使用 CSS 样式表来简化对页面外观的更改。
Java HotSpotTM 虚拟机的性能得到了改进。这些改进中的大多数不在开发人员的控制之下,并且本质上是专门化的。感兴趣的读者将在上找到有关这些增强功能的更多详细信息 http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html 。
有些申请涉及一年中的周数和一年中的当前周数。众所周知,一年中有 52 周,但 52 周乘以每周 7 天等于每年 364 天,而不是实际的 365 天。周号用于表示一年中的一周。但这是如何计算的呢?Java7 引入了几种方法来支持确定一年中的星期。在本食谱中,我们将研究这些方法,并了解如何计算周相关值。ISO 8601标准提供了表示日期和时间的方法。 java.util.GregorianCalendar
类支持本标准,以下章节中描述的除外。
要使用这些基于周的方法,我们需要:
- 创建
Calendar
类的实例。 - 适当使用其方法。
抽象 java.util.Calendar
类的一些实现不支持周计算。为了确定 Calendar
实现是否支持周计算,我们需要执行 isWeekDateSupported
方法。如果提供了支持,则返回 true
。要返回当前日历年的周数,请使用 getWeeksInWeekYear
方法。要确定当前日期的星期,请使用以 WEEK_OF_YEAR
为参数的 get
方法。
-
创建一个新的控制台应用程序。在
main
方法中增加以下代码:Calendar calendar = Calendar.getInstance(); if(calendar.isWeekDateSupported()) { System.out.println("Number of weeks in this year: " + calendar.getWeeksInWeekYear()); System.out.println("Current week number: " + calendar.get(Calendar.WEEK_OF_YEAR)); }
-
Execute the application. Your output should appear as follows, but the values will be dependent upon the date the application was executed:
本年周数:53
本周编号:48
创建了 Calendar
类的一个实例。这通常是 GregorianCalendar
类的一个实例。 if
语句由 isWeekDateSupported
方法控制。它返回了 true
,这导致了 getWeeksInWeekYear
和 get
方法的执行。在字段 WEEK_OF_YEAR
中传递了 get
方法,该字段返回当前周数。
可以使用 setWeekDate
方法设置日期。此方法有三个参数指定年、周和日。它提供了一种基于周设置日期的便捷技术。下面通过将年份设置为 2012 年,将一周设置为一年中的第 16 周,将一天设置为一周中的第三天来说明此过程:
calendar.setWeekDate(2012, 16, 3);
System.out.println(DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG).format(calendar.getTime()));
执行此代码时,我们得到以下输出:
2012 年 4 月 17 日 12:00:08 下午 CDT
计算一年中第一周和最后一周的方式取决于区域设置。 GregorianCalendar
类的 WEEK_OF_YEAR
字段范围从 1 到 53,其中 53 表示闰周。一年的第一周是:
- 最早的七天时期
- 从一周的第一天开始(
getFirstDayOfWeek
- 至少包含一周中的最少天数(
getMinimalDaysInFirstWeek
)
getFirstDayOfWeek
和 getMinimalDaysInFirstWeek
方法依赖于区域设置。例如, getFirstDayOfWeek
方法返回一个整数,表示区域设置一周中的第一天。在美国是星期天,但在法国是星期一。
一周中的第一周和最后一周可能有不同的日历年。考虑下面的代码序列。日历设置为 2022 年第一周的第一天:
calendar.setWeekDate(2022, 1, 1);
System.out.println(DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG).format(calendar.getTime()));
执行时,我们得到以下输出:
2021 年 12 月 26 日 12:15:39 下午中央标准时间
这表明这一周实际上是从上一年开始的。
此外, TimeZone
和 SimpleTimeZone
类有一个 observesDaylightTime
方法,如果时区遵守夏令时,则返回 true
。下面的代码序列创建一个 SimpleTimeZone
类的实例,然后确定是否支持夏令时。使用的时区为中央标准时间(CST):
SimpleTimeZone simpleTimeZone = new SimpleTimeZone(
-21600000,
"CST",
Calendar.MARCH, 1, -Calendar.SUNDAY,
7200000,
Calendar.NOVEMBER, -1, Calendar.SUNDAY,
7200000,
3600000);
System.out.println(simpleTimeZone.getDisplayName() + " - " +
simpleTimeZone.observesDaylightTime());
执行此序列时,应获得以下输出:
中央标准时间-真
java.util.Currency
类引入了四种新方法来检索有关可用货币及其属性的信息。此配方说明了以下方法的使用:
getAvailableCurrencies:
此方法返回一组可用货币getNumericCode:
此方法返回货币的 ISO 4217 数字代码getDisplayName:
此重载方法返回表示货币显示名称的字符串。一个方法是传递一个Locale
对象。返回的字符串特定于该区域设置。
getAvailableCurrencies
方法是静态的,所以应该针对类名执行。其他方法针对 Currency
类的实例执行。
-
创建一个新的控制台应用程序。在
main
方法中增加以下代码:Set<Currency> currencies = Currency.getAvailableCurrencies(); for (Currency currency : currencies) { System.out.printf("%s - %s - %s\n", currency.getDisplayName(), currency.getDisplayName(Locale.GERMAN), currency.getNumericCode()); }
-
When the application is executed, you should get output similar to the following. However, the first part of each may differ depending on the current locale.
朝鲜元-北欧元-408
欧元-欧元-978
荷兰盾-荷兰盾-528
福克兰群岛镑-福克兰基金-238
丹麦克朗-Dänische 克朗-208
伯利兹元-伯利兹元-84
代码序列从生成表示当前系统配置的 Currency
对象的 Set
开始。重载的 getDisplayName
方法针对集合的每个元素执行。 Locale.GERMAN
参数用于说明此方法的使用。最后显示的值是货币的数字代码。
在此配方中,我们将演示如何使用 java.awt.font.NumericShaper.Range
枚举来支持使用 java.awt.font.NumericShaper
类显示数字。有时,希望使用与当前使用的语言不同的语言显示数字。例如,在关于蒙古语的英语教程中,我们可能希望用英语解释数字系统,但使用蒙古语数字显示数字。 NumericShaper
类提供了这种支持。新的 NumericShaper.Range
枚举简化了这种支持。
要使用 NumericShaper.Range
枚举显示数字:
- 创建一个
HashMap
来保存显示属性信息。 - 创建一个
Font
对象来定义要使用的字体。 - 指定要显示文本的 Unicode 字符范围。
- 创建一个
FontRenderContext
对象来保存有关如何测量要显示的文本的信息。 - 创建一个
TextLayout
实例,并在paintComponent
方法中使用它来呈现文本。
我们将演示如何使用 NumericShaper.Range
枚举来显示蒙古语数字。这是中示例的简化版本 http://download.oracle.com/javase/tutorial/i18n/text/shapedDigits.html 。
-
创建一个扩展
JFrame
类的应用程序,如下所示。我们将说明NumericShaper
类在NumericShaperPanel
类中的用法:public class NumericShaperExample extends JFrame { public NumericShaperExample() { Container container = this.getContentPane(); container.add("Center", new NumericShaperPanel()); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setTitle("NumericShaper Example"); this.setSize(250, 120); } public static void main(String[] args) { new NumericShaperExample();.setVisible(true) } NumericShaper.Range enumeration using, for digit display}
-
接下来,将
NumericShaperPanel
类添加到项目中,如下所示:public class NumericShaperPanel extends JPanel { private TextLayout layout; public NumericShaperPanel() { String text = "0 1 2 3 4 5 6 7 8 9"; HashMap map = new HashMap(); Font font = new Font("Mongolian Baiti", Font.PLAIN, 32); map.put(TextAttribute.FONT, font); map.put(TextAttribute.NUMERIC_SHAPING, NumericShaper.getShaper(NumericShaper.Range. MONGOLIAN)); FontRenderContext fontRenderContext = new FontRenderContext(null, false, false); layout = new TextLayout(text, map, fontRenderContext); } public void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; layout.draw(g2d, 10, 50); } }
-
执行应用程序。您的输出应如下所示:
在 main
方法中,创建了 NumericShaperExample
类的一个实例。在其构造函数中,创建了一个 NumericShaperPanel
类的实例,并将其添加到窗口的中心。设置了窗口的标题、默认关闭操作和大小。接下来,窗口被显示出来。
在 NumericShaperPanel
类的构造函数中,创建了一个文本字符串和一个 HashMap
来保存显示的基本特性。此映射与要显示的字符串和映射一起用作 TextLayout
构造函数的参数。文本以蒙古文显示,使用蒙古文白体字体和蒙古文范围。我们使用这个字体来演示 NumericShaper
类的新方法。
NumericShaper
类添加了新的方法,使得用不同的语言显示数值更容易。 getShaper
方法重载,有一个版本接受 NumericShaper.Range
枚举值。该值指定要使用的语言。添加了 NumericShaper.Range
枚举,以表示给定语言中数字的一系列 Unicode 字符。
在 paintComponent
方法中, Graphics2D
对象被用作 draw
方法的参数,以将字符串呈现给窗口。
getContextualShaper
方法用于控制与不同脚本一起使用时数字的显示方式。这意味着如果在数字之前使用日语脚本,则显示日语数字。该方法采用一组 NumericShaper.Range
枚举值。
shape
方法还使用一个范围来指定在数组中给定开始和结束索引的 char 数组所使用的脚本。 getRangeSet
方法返回 NumericShaper
实例使用的一组 NumericShaper.Range
。
JavaBean是一种为 Java 应用程序构建可重用组件的方法。它们是遵循某些命名约定的 Java 类。Java7 中添加了几个 JavaBean 增强。在这里,我们将重点关注 java.beans.Expression
类,它在执行方法时非常有用。已添加 execute
方法以促进此功能。
使用 Expression
类执行方法:
- 如果需要,为方法创建一个参数数组。
- 创建
Expression
类的实例,指定要针对其执行方法的对象、方法名称和所需的任何参数。 - 对表达式调用
execute
方法。 - 如有必要,使用
getValue
方法获得方法执行的结果。
-
创建一个新的控制台应用程序。创建两个类:
JavaBeanExample
,其中包含main
方法和Person
类。Person
类包含一个名称字段以及构造函数、getter 方法和 setter 方法:public class Person { private String name; public Person() { this("Jane", 23); } public Person(String name, int age) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
在
JavaBeanExample
类的main
方法中,我们将创建Person
类的一个实例,并使用Expression
类执行其getName
和setName
方法:public static void main(String[] args) throws Exception { Person person = new Person(); String arguments[] = {"Peter"}; Expression expression = new Expression(null, person, "setName", arguments); System.out.println("Name: " + person.getName()); expression.execute(); System.out.println("Name: " + person.getName()); System.out.println(); expression = new Expression(null, person, "getName", null); System.out.println("Name: " + person.getName()); expression.execute(); System.out.println("getValue: " + expression.getValue()); }
-
Execute the application. Its output should appear as follows:
姓名:简
姓名:彼得
姓名:彼得
获取值:彼得
Person
类使用了一个字段 name。 getName
和 setName
方法来自 main
方法,其中创建了一个 Person
实例。 Expression
类的构造函数有四个参数。本例中未使用第一个参数,但可用于定义所执行方法的返回值。第二个参数是将对其执行方法的对象。第三个参数是包含方法名称的字符串,最后一个参数是包含方法使用的参数的数组。
在第一个序列中,使用参数 Peter
执行 setName
方法。应用程序的输出显示名称最初为 Jane
,但在执行 execute
方法后更改为 Peter
。
在第二个序列中,执行 getName
方法。 getValue
方法返回方法执行的结果。输出显示 getName
方法返回 Peter
。
java.bean
包的类还有其他增强功能。例如, toString
方法在 FeatureDescriptor
和 PropertyChangeEvent
类中被重写,以提供更有意义的描述。
Introspector
类提供了一种学习 JavaBean 的属性、方法和事件的方法,而无需使用反射 API,这可能会很繁琐。该类添加了一个 getBeanInfo
方法,该方法使用 Inspector
类的控制标志来影响返回的 BeanInfo
对象。
添加了 Transient
注释以控制包含的内容。属性的 true
值表示应忽略带注释的特征。
一个新的构造函数被添加到接受一个 InputSource
对象的 XMLDecoder
类中。此外,还添加了 createHandler
方法,该方法返回一个 DefaultHandler
对象。此处理程序用于解析由 XMLEncoder
类创建的 XML 存档。
XMLEncoder
类中添加了一个新的构造函数。这允许使用具有特定缩进的特定字符集将 JavaBeans 写入到 OutputStream
。
java.util.Locale.Builder
类已添加到 Java7 中,并提供了创建区域设置的简单方法。 Locale.Category
枚举也是新的,它使使用不同的区域设置进行显示和格式化变得容易。我们将首先研究 Locale.Builder
类的使用,然后研究其他语言环境改进,以及中 Locale.Category
枚举的使用。。。部分
要构建并使用新的 Locale
对象:
- 创建
Builder
类的实例。 - 使用类的相关方法设置所需的属性。
- 根据需要使用
Locale
对象。
-
创建一个新的控制台应用程序。在
main
方法中,添加以下代码。我们将创建一个基于东亚美尼亚语的新语言环境,使用意大利的拉丁语脚本。通过使用setWeekDate
方法显示 2012 年第 16 周第三天的日期来演示区域设置。此方法在 Java 7 配方中的*处理周Calendar calendar = Calendar.getInstance(); calendar.setWeekDate(2012, 16, 3); Builder builder = new Builder(); builder.setLanguage("hy"); builder.setScript("Latn"); builder.setRegion("IT"); builder.setVariant("arevela"); Locale locale = builder.build(); Locale.setDefault(locale); System.out.println(DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG).format(calendar.getTime())); System.out.println("" + locale.getDisplayLanguage());
中进行了更详细的讨论*
-
第二个示例使用简化脚本构建基于中文的区域设置,在中国大陆使用:
builder.setLanguage("zh"); builder.setScript("Hans"); builder.setRegion("CN"); locale = builder.build(); Locale.setDefault(locale); System.out.println(DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG).format(calendar.getTime())); System.out.println("" + locale.getDisplayLanguage());
-
When executed, the output should appear as follows:
2012 年 4 月 17 日 CDT 时间下午 7:25:42
亚美尼亚语
2012 年 4.月 17 日 下午 07 时 25 分 42 秒
中文
Builder
对象已创建。使用这个对象,我们应用了一些方法来设置语言环境的语言、脚本和区域。然后执行 build
方法并返回 Locale
对象。我们使用此区域设置来显示日期和区域设置的显示语言。这是执行了两次。首先是亚美尼亚语,然后是汉语。
能够标记一条信息以指示所使用的语言非常重要。标签用于此目的。IETF BCP 47标准定义了一组标准标签。Java7 符合这个标准,并添加了几个方法来处理标记。
该标准支持标记扩展的概念。这些扩展可用于提供有关区域设置的更多信息。有两种类型:
- Unicode 语言环境扩展
- 私用扩展
Unicode 语言环境扩展由Unicode 通用语言环境数据存储库(CLDR)(定义 http://cldr.unicode.org/ )。这些扩展涉及非语言信息,例如货币和日期。CLDR 维护区域设置信息的标准存储库。专用扩展用于指定特定于平台的信息,例如与操作系统或编程语言相关的信息。
有关 IETF BCP 47 标准的更多信息,请参见http://tools.ietf.org/rfc/bcp/bcp47.txt 。
扩展由键/值对组成。该键为单个字符,值的格式如下:
SUBTAG ('-' SUBTAG)*
SUBTAG
由一系列字母数字字符组成。对于 Unicode 区域设置扩展,该值必须至少为两个字符,但长度不得超过 8 个字符。对于专用扩展名,允许使用 1 到 8 个字符。所有扩展字符串都不区分大小写。
Unicode 语言环境扩展的密钥是u,而对于私用扩展,则是x。可以将这些扩展添加到区域设置以提供附加信息,例如要使用的日历编号类型。
下表列出了可使用的键:
|关键代码
|
描述
| | --- | --- | | ca | 确定日期的日历算法 | | co | 排序规则类型语言中使用的顺序 | | ka | 用于指定排序的排序规则参数 | | 铜 | 货币类型信息 | | nu | 编号系统 | | va | 通用变型 |
键和类型的示例见下表:
|键/类型
|
意思
| | --- | --- | | 努阿姆洛 | 亚美尼亚小写数字 | | 加州印第安人 | 印度历法 |
添加了几个方法来使用这些扩展。 getExtensionKeys
方法返回与区域设置一起使用的所有键的一组 Character
对象。类似地, getUnicodeLocaleAttributes
和 getUnicodeLocaleKeys
方法返回一组字符串,列出可用的属性和 Unicode 键。如果没有可用的扩展,则这些方法返回一个空集。如果键已知, getExtension
方法或 getUnicodeLocaleType
方法将返回包含该键值的字符串。
对于给定的区域设置, getScript, getDisplayScript
和 toLanguageTag
方法分别返回脚本、脚本的可显示名称和区域设置的格式良好的BCP 47标记。 getDisplayScript
方法还将返回脚本的可显示名称,给定区域设置作为参数。
下一节将讨论如何使用 setDefault
方法来控制同时使用两个不同区域设置的信息显示。
Locale.Category
枚举已添加到 Java 7 中。它有两个值, DISPLAY
和 FORMAT
。这允许为格式类型资源(日期、数字和货币)和显示资源(应用程序的 GUI 方面)设置默认区域设置。例如,应用程序的一部分可以设置格式以适应一种语言环境,例如 JAPANESE
,同时在另一种语言环境中显示相关信息,例如 GERMAN.
考虑下面的例子:
Locale locale = Locale.getDefault();
Calendar calendar = Calendar.getInstance();
calendar.setWeekDate(2012, 16, 3);
System.out.println(DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG).format(calendar.getTime()));
System.out.println(ocale.getDisplayLanguage());
Locale.setDefault(Locale.Category.FORMAT, Locale.JAPANESE);
Locale.setDefault(Locale.Category.DISPLAY, Locale.GERMAN);
System.out.println(DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG).format(calendar.getTime()));
System.out.println(locale.getDisplayLanguage());
当执行此代码序列时,您应该得到如下类似的输出。初始日期和显示语言可能因默认区域设置而异。
2012 年 4 月 17 日 CDT 时间下午 7:15:14
英语
2012/04/17 19:15:14 CDT
英语
检索默认区域设置,并使用 setWeekDate
方法设置日期。在 Java 7 配方中的使用周中对该方法进行了更详细的讨论。接下来,打印日期和显示语言。重复显示,但使用 setDefault
方法更改了默认区域设置。显示资源更改为使用 Locale.JAPANESE
,格式类型资源更改为 Locale.GERMAN
。产出反映了这一变化。
一个相当常见的例外是 java.lang.NullPointerException
。当试图对包含 null 值的引用变量执行方法时,会发生这种情况。在本配方中,我们将研究可用于解决此类异常的各种技术。
已经引入了 java.util.Objects
类,它提供了许多静态方法,用于解决需要处理空值的情况。此类的使用简化了空值的测试。
还有更多。。。第节检查空列表的使用,可以使用空列表代替返回 null。 java.util.Collections
类有三个返回空列表的方法。
要使用 Objects
类重写 equals
和 hashCode
方法:
- 重写目标类中的方法。
- 使用
Objects
类“equals
方法避免显式代码检查equals
方法中的空值。 - 使用
Objects
类的hashCode
方法可以避免需要显式代码来检查hashCode
方法中的空值。
-
创建一个新的控制台应用程序。我们将创建一个
Item
类来演示Objects
类的使用。在Item
类中,我们将重写equals
和hashCode
方法。这些方法是由 NetBeans 的 insert code 命令生成的。我们使用这些方法,因为它们说明了Objects
类的方法,并且结构良好。首先创建如下类:public class Item { private String name; private int partNumber; public Item() { this("Widget", 0); } public Item(String name, int partNumber) { this.name = Objects.requireNonNull(name); this.partNumber = partNumber; } public String getName() { return name; } public void setName(String name) { this.name = Objects.requireNonNull(name); } public int getPartNumber() { return partNumber; null referenceshandling} public void setPartNumber(int partNumber) { this.partNumber = partNumber; } }
-
接下来,覆盖
equals
和hashCode
方法,如下所示。它们提供检查空值的代码:@Override public boolean equals(Object obj){ if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Item other = (Item) obj; if (!Objects.equals(this.name, other.name)) { return false; } if (this.partNumber != other.partNumber) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 47 * hash + Objects.hashCode(this.name); hash = 47 * hash + this.partNumber; return hash; }
-
通过添加
toString
方法@Override public String toString() { return name + " - " + partNumber; }
完成本课程
-
接下来,在
main
方法中增加以下内容:Item item1 = new Item("Eraser", 2200); Item item2 = new Item("Eraser", 2200); Item item3 = new Item("Pencil", 1100); Item item4 = null; System.out.println("item1 equals item1: " + item1.equals(item1)); System.out.println("item1 equals item2: " + item1.equals(item2)); System.out.println("item1 equals item3: " + item1.equals(item3)); System.out.println("item1 equals item4: " + item1.equals(item4)); item2.setName(null); System.out.println("item1 equals item2: " + item1.equals(item2));
-
Execute the application. Your output should appear as follows:
第 1 项等于第 1 项:真
第 1 项等于第 2 项:真
第 1 项等于第 3 项:假
第 1 项等于第 4 项:假
线程“main”java.lang.NullPointerException中出现异常
位于 java.util.Objects.requirennull(Objects.java:201)
在 packt.Item.setName(Item.java:23)
位于 packt.NullReferenceExamples.main(NullReferenceExamples.java:71)
正如我们将很快看到的, NullPointerException
是尝试为项的名称字段分配空值的结果。
在 equals
方法中,首先进行测试以确定通过的对象是否为空。如果是,则返回 false
。进行了测试,以确保这些类属于同一类型。然后使用 equals
方法查看两个名称字段是否相等。
Objects
类“ equals
方法的行为如下表所示。相等的含义由第一个参数的 equals
方法确定:
第一个论点
|
第二个论点
|
退换商品
|
| --- | --- | --- |
| 非空 | 非空 | true
如果是同一对象,则为 false
|
| 非空 | 无效的 | false
|
| 无效的 | 非空 | false
|
| 无效的 | 无效的 | true
|
最后一个测试比较了两个整数 partNumber
字段是否相等。
在 Item
类的 hashCode
方法中, Objects
类的 hashCode
方法应用于名称字段。如果此方法的参数为 null,则它将返回 0,否则它将返回参数的哈希代码。然后使用 partNumber
来计算散列码的最终值。
注意在两个参数构造函数中使用了 requireNonNull
方法和 setName
方法。该方法检查非空参数。如果参数为 null,则抛出一个 NullPointerException
。这有效地捕获了应用程序早期的潜在错误。
requireNonNull
方法被第二个版本重载,该版本接受第二个字符串参数。当发生异常时,此参数更改生成的消息。将 setName
方法的主体替换为以下代码:
this.name = Objects.requireNonNull(name, "The name field requires a non-null value");
重新执行应用程序。异常消息现在将显示如下:
线程“main”java.lang.NullPointerException 中出现异常:名称字段需要非空值
还有其他几个 Objects
类方法可能会引起兴趣。此外,第二节将检查空迭代器的使用,以避免空指针异常。
Objects
类的 hashCode
方法重载。第二个版本采用数量可变的对象作为参数。该方法将使用此对象序列生成哈希代码。例如, Item
类的 hashCode
方法可以写成:
@Override
public int hashCode() {
return Objects.hash(name,partNumber);
}
deepEquals
方法对两个对象进行了深入的比较。这意味着它比较的不仅仅是参考值。两个空参数被认为是完全相等的。如果两个参数都是数组,则调用 Arrays.deepEqual
方法。对象的相等性由第一个参数的 equals
方法确定。
compare
方法用于比较返回负值、零值或正值的前两个参数,具体取决于参数之间的关系。通常,返回 0 表示参数相同。负值表示第一个参数小于第二个参数。正值表示第一个参数大于第二个参数。
如果该方法的参数相同,或者如果两个参数都为 null,则该方法将返回零。否则,使用 Comparator
接口的 compare
方法确定返回值。
Objects
类“ toString
方法用于确保即使对象为 null 也返回字符串。以下顺序说明了此重载方法的使用:
Item item4 = null;
System.out.println("toString: " + Objects.toString(item4));
System.out.println("toString: " + Objects.toString(item4, "Item is null"));
执行时,该方法的第一次使用显示单词null。在第二个版本中,字符串参数显示如下:
toString:null
toString:项为空
避免 NullPointerException
的一种方法是在无法创建列表时返回非空值。返回一个空的 Iterator
可能是有益的。
在 Java 7 中, Collections
类添加了三个新方法,它们返回一个 Iterator
、一个 ListIterator
或一个 Enumeration
,所有这些方法都是空的。通过返回空,可以使用它们,而不会引发空指针异常。
为了演示空列表迭代器的使用,创建一个新方法,返回一个泛型 ListIterator<String>
,如下代码所示。 if
语句用于返回 ListIterator
或空 ListIterator:
public static ListIterator<String> returnEmptyListIterator() {
boolean someConditionMet = false;
if(someConditionMet) {
ArrayList<String> list = new ArrayList<>();
// Add elements
ListIterator<String> listIterator = list.listIterator();
return listIterator;
}
else {
return Collections.emptyListIterator();
}
}
使用以下 main
方法测试迭代器的行为:
public static void main(String[] args) {
ListIterator<String> list = returnEmptyListIterator();
while(())String item: list {
System.out.println(item);
}
}
当它执行时,应该没有输出。这表示迭代器为空。如果我们返回 null,我们将收到一个 NullPointerException
。
Collections
类的静态 emptyListIterator
方法返回一个 ListIterator
,其方法工作如下表所示:
方法
|
行为
|
| --- | --- |
| hasNext``hasPrevious
| 始终返回 false
|
| next``Previous
| 总是抛出 NoSuchElementException
|
| remove``set
| 总是抛出 IllegalStateException
|
| add
| 总是抛出UnsupportedOperationException
|
| nextIndex
| 始终返回 0 |
| previousIndex
| 总是返回-1 |
emptyIterator
方法将返回一个具有以下行为的空迭代器:
方法
|
行为
|
| --- | --- |
| hasNext
| 始终返回 false
|
| next
| 总是抛出 NoSuchElementException
|
| remove
| 总是抛出 IllegalStateException
|
emptyEnumeration
方法返回一个空枚举。其 hasMoreElements
将始终返回 false
,其 nextElement
将始终抛出 NoSuchElementException
异常。
java.util.BitSet
类通过最新版本的 Java 获得了几个新方法。它们旨在简化对大型位集的操作,并提供对位位置信息的更容易访问。位集可用于优先级队列或压缩数据结构。这个食谱展示了一些新方法。
要使用新的 BitSet
方法:
- 创建一个
BitSet
的实例。 - 根据需要对
BitSet
对象执行方法。
-
创建一个新的控制台应用程序。在
main
方法中,创建BitSet
对象的实例。然后声明一个长数字数组,并使用静态valueOf
方法将BitSet
对象设置为该长数组的值。添加一个println
语句,这样我们可以看到我们的长数字在BitSet:
BitSet bitSet = new BitSet(); long[] array = {1, 21, 3}; bitSet = BitSet.valueOf(array); System.out.println(bitSet);
中的表示方式
-
接下来,使用
toLongArray
方法将BitSet
转换回一个长数字数组。使用 for 循环打印出数组中的值:long[] tmp = bitSet.toLongArray(); for (long number : tmp) { System.out.println(number); }
-
Execute the application. You should see the following output:
{0,64,66,68,128,129}
1
21
3
在创建 BitSet
对象之后,我们创建了一个包含三个 long
数字的数组,这些数字表示我们希望在 BitSet
中使用的位序列。 valueOf
方法采用这种表示并将其转换为位序列。
当我们打印出 BitSet
时,我们看到序列{0,64,66,68,128,129}。 BitSet
中的每个数字代表在我们的位序列中设置的位的索引。例如,0 表示数组中的 long
数字 1,因为用于表示该数字的位的索引位于位置 0。同样,位 64、66 和 68 被设置为表示我们的 long
编号 21。序列中的第 128 位和第 129 位被设置为代表我们的 long
数字 3。在下一节中,当我们使用 toLongArray
方法将 BitSet
返回到其原始形式时,我们颠倒了这个过程。
在我们的示例中,我们使用了一个 long
数字数组。 byte, LongBuffer
和 ByteBuffer
阵列也有类似的 valueOf
方法。当使用 LongBuffer
或 ByteBuffer
数组时, valueOf
方法不会修改缓冲区, BitSet
不能转换回缓冲区。相反,必须使用 toLongArray
方法或将 BitSet
转换为字节数组的类似 toByteArray
方法来转换 BitSet
。
有两种新方法可用于定位集合或清除 BitSet
中的位。方法 previousSetBit
将表示特定索引的整数作为其参数,并返回表示所设置的 BitSet
中最近位的整数。例如,在前面的示例中添加以下代码序列(使用长数字 {1, 21, 3}):
表示的 BitSet
System.out.println(bitSet.previousSetBit(1));
这将导致输出为整数 0。这是因为我们将索引 1 的参数传递给了 previousSetBit
方法,在 BitSet
中设置的最接近的前一位位于索引 0 处。
previousClearBit
方法以类似的方式运行。如果我们在前面的示例中执行以下代码:
System.out.println(bitSet.previousClearBit(66));
我们将得到整数 65 的输出。位于索引 65 处的位与我们的论点 66 最接近。如果 BitSet
中不存在这样的位,两种方法都将返回-1。