七、探索基本 API:第一部分

标准类库的 java.lang 和 java.math 包提供了许多基本的 API,旨在支持语言特性。您已经遇到了一些这样的 API,比如对象和字符串类以及 Throwable 类层次结构。在这一章中,我将向你介绍那些与数学、字符串管理和包相关的基本库 API。

探索数学应用编程接口

第 2 章中,我介绍了 Java 的 + 、 - 、 * 、 / 和 % 运算符,用于对原始类型的值执行基本运算。Java 还提供了用于执行三角学和其他高级数学运算的类,精确地表示货币值,并支持在 RSA 加密(http://en . Wikipedia . org/wiki/RSA _(algorithm和其他上下文中使用的超长整数。

数学和严格数学

java.lang.Math 类声明双常数 E 和 PI 表示自然对数底值(2.71828。。。)和圆的周长与其直径的比值(3.14159。。。). E 初始化为 2.718281828459045 并且 PI 初始化为 3.141592653589793 。 Math 还声明了各种类方法来执行各种数学运算。表 7-1 描述了其中的许多方法。

表 7-1。 数学方法

方法 描述
双 abs(双 d) 返回 d 的绝对值。有四种特殊情况:ABS(-0.0)=+0.0, abs(+infinity) = +infinity ,ABS(-infinity)=+infinity, abs(NaN) = NaN 。
浮动 abs(浮动 f) 返回 f 的绝对值。有四种特殊情况:ABS(-0.0)=+0.0, abs(+infinity) = +infinity ,ABS(-infinity)=+infinity, abs(NaN) = NaN 。
int abs(int i) 返回 i 的绝对值。有一种特殊情况:整数的绝对值。最小值是整数。最小值。
长腹肌(长 l) 返回 l 的绝对值。还有一个特例:龙的绝对值。最小 _ 值为长。最小值。
双 acos(双 d) 返回角度 d 的反余弦值,范围从 0 到 PI。有三种特殊情况: acos(任何东西> 1) = NaN 、 acos(任何东西<T5—1)= NaN、 acos(NaN) = NaN 。
double asin(double d) 返回角度 d 的反正弦在-PI/2 到 PI/2 的范围内。有三种特殊情况: asin(任何东西> 1) = NaN 、 asin(任何东西<T5—1)= NaN、 asin(NaN) = NaN 。
双阿坦(双 d) 返回角度 d 的反正切在-PI/2 到 PI/2 的范围内。有五种特殊情况:阿坦(+0.0) = +0.0 、阿坦(-0.0)=-0.0、阿坦(+无穷大)= +PI/2 、阿坦(-无穷大)=-PI/2、阿坦(NaN)= NaN
双天花板(双 d) 返回不小于 d 且等于整数的最小值(最接近负无穷大)。有六种特殊情况: ceil(+0.0) = +0.0 、ceil(-0.0)=-0.0、ceil(any>-1.0 和<0.0)=-0.0、ceil(+infinity)=+infinity、 ceil(.
双 cos(双 d) 返回角度 d 的余弦值(以弧度表示)。有三种特殊情况: cos(+infinity) = NaN 、cos(-infinity)= NaN、 cos(NaN) = NaN 。
double exp(double d) 返回欧拉数 e 的幂 d 。有三种特殊情况: exp(+infinity) = +infinity 、exp(-infinity)=+0.0、 exp(NaN) = NaN 。
双楼层(双 d) 返回不大于 d 且等于整数的最大值(最接近正无穷大)。有五种特殊情况:楼层(+0.0) = +0.0 、楼层(-0.0)=-0.0、楼层(+无穷大)= +无穷大、楼层(-无穷大)=-无穷大、楼层(NaN) = NaN 。
双对数(双 d) 返回 d 的自然对数(底数 e)。有六种特殊情况:log(+0.0)=-infinity,log(-0.0)=-infinity,log(any<0)= NaN,log(+infinity)=+infinity,log(-infinity)= NaN,以及 log
double log10(double d) 返回 d 以 10 为底的对数。有六种特殊情况:log10(+0.0)=-无穷大,log10(-0.0)=-无穷大, log10(任何东西< 0) = NaN ,log10(+无穷大)=+无穷大,log10(-无穷大)= NaN
双最大值(双 d1,双 d2) 返回 d1 和 d2 中最大的正值(最接近正无穷大)。有四种特殊情况: max(NaN,any)= NaN,max(any,NaN) = NaN , max(+0.0,-0.0) = +0.0 ,max(-0.0,+0.0) = +0.0 。
浮动最大值(浮动 f1,浮动 f2) 返回 f1 和 f2 中最大的正值(最接近正无穷大)。有四种特殊情况: max(NaN,any)= NaN,max(any,NaN) = NaN , max(+0.0,-0.0) = +0.0 ,max(-0.0,+0.0) = +0.0 。
int max(int i1,int i2) 返回 i1 和 i2 中最大的正值(最接近正无穷大)。
长最大值(长 l1,长 l2) 返回 l1 和 l2 的最大正值(最接近正无穷大)。
双最小(双 d1,双 d2) 返回 d1 和 d2 中最负的(最接近负无穷大)。有四种特殊情况: min(NaN,any)= NaN,min(any,NaN) = NaN , min(+0.0,-0.0)=-0.0,min(-0.0,+0.0)=-0.0。
浮点最小值(浮点 f1,浮点 f2) 返回 f1 和 f2 的最大负值(最接近负无穷大)。有四种特殊情况: min(NaN,any)= NaN,min(any,NaN) = NaN , min(+0.0,-0.0)=-0.0,min(-0.0,+0.0)=-0.0。
int min(int i1,int i2) 返回 i1 和 i2 中最负的(最接近负无穷大)。
龙敏(长 l1,长 l2) 返回 l1 和 l2 中最负的(最接近负无穷大)。
双随机() 返回一个介于 0.0(含)和 1.0(不含)之间的伪随机数。
长圆形(双 d) 将 d 的舍入结果返回到长整数。结果相当于 (long) Math.floor(d + 0.5) 。有七种特殊情况: round(+0.0) = +0.0 ,round(-0.0)=+0.0, round(任何东西>长。MAX_VALUE) = Long。MAX_VALUE , round(任何东西<长。MIN_VALUE) = Long。MIN_VALUE , round(+infinity) = Long。MAX_VALUE ,round(-infinity)= Long。最小值和舍入(NaN) = +0.0 。
整数舍入(浮点 f) 将 f 的舍入结果返回到整数。结果相当于 (int) Math.floor(f + 0.5) 。有七种特殊情况: round(+0.0) = +0.0 ,round(-0.0)=+0.0, round(任意>整数。MAX_VALUE) =整数。MAX_VALUE , round(任意值<整数。MIN_VALUE) =整数。MIN_VALUE , round(+infinity) = Integer。MAX_VALUE ,round(-infinity)= Integer。最小值和舍入(NaN) = +0.0 。
双符号(双 d) 返回 d 的符号为 1.0 ( d 小于 0.0)、0.0 ( d 等于 0.0)、1.0 ( d 大于 0.0)。有五种特殊情况: signum(+0.0) = +0.0 、signum(-0.0)=-0.0、 signum(+infinity) = +1.0 、signum(-infinity)=-1.0、 signum(NaN
浮点符号(浮点 f) 返回 f 的符号为 1.0 ( f 小于 0.0)、0.0 ( f 等于 0.0)、1.0 ( f 大于 0.0)。有五种特殊情况: signum(+0.0) = +0.0 、signum(-0.0)=-0.0、 signum(+infinity) = +1.0 、signum(-infinity)=-1.0、 signum(NaN
双 sin(双 d) 返回角度 d 的正弦值(以弧度表示)。有五种特殊情况: sin(+0.0) = +0.0 、sin(-0.0)=-0.0、sin(+无穷大)= NaN 、sin(-无穷大)= NaN 、 sin(NaN) = NaN 。
双 sqrt(双 d) 返回 d 的平方根。有五种特殊情况: sqrt(+0.0) = +0.0 ,sqrt(-0.0)=-0.0,sqrt(any<0)= NaN,sqrt(+infinity)=+infinity, sqrt(NaN) = NaN 。
双谭(双 d) 返回角度 d 的正切值(以弧度表示)。有五种特殊情况: tan(+0.0) = +0.0 ,tan(-0.0)=-0.0,tan(+无穷大)= NaN ,tan(-无穷大)= NaN , tan(NaN) = NaN 。
双角度(双角度) 通过表达式 angrad * 180 / PI 将角度 angrad 从弧度转换为角度。有五种特殊情况: toDegrees(+0.0) = +0.0 ,to degrees(-0.0)=-0.0,to degrees(+infinity)=+infinity,to degrees(-infinity)=-infinity,to degrees(NaN)= 1
双胸(双昂得) 通过表达式 angdeg / 180 * PI 将角度 angdeg 从度转换为弧度。有五种特殊情况:(toRadians(+0.0)=+0.0,toRadians(-0.0)=-0.0,toRadians(+infinity)=+infinity,toRadians(-infinity)=-infinity,以及 toRadians(NaN)= 1

表 7-1 揭示了各种各样有用的面向数学的方法。例如,每个 abs() 方法都返回其参数的绝对值(不考虑符号的数字)。

abs(double) 和 abs(float) 用于安全地比较双精度浮点和浮点值。例如, 0.3 == 0.1 + 0.1 + 0.1 的计算结果为 false,因为 0.1 没有精确的表示形式。但是,您可以将这些表达式与 abs() 和一个公差值进行比较,该公差值表示可接受的误差范围。例如,math . ABS(0.3-(0.1+0.1+0.1))<0.1 返回 true,因为 0.3 和 0.1 + 0.1 + 0.1 之间的绝对差值小于 0.1 容差值。

在前几章中,我演示了其他的数学方法。例如,在第 3 章中,我演示了数学的 sin() 、 toRadians() 、 cos() 和 random() 方法。

正如 Chapter 6 的 Lotto649 应用所揭示的那样, random() (它返回一个看似随机选择的数字,但实际上是通过可预测的数学计算选择的,因此是伪随机)在模拟中(以及在游戏和任何需要概率元素的地方)是有用的。然而,它的 0.0 到(几乎)1.0 的双精度浮点范围并不实用。为了让 random() 更有用,它的返回值必须转换成更有用的范围,也许是 0 到 49 的整数值,也许是-100 到 100。您会发现下面的 rnd() 方法对于进行这些转换非常有用:

static int rnd(int limit)
{
   return (int) (Math.random() * limit);
}

rnd() 通过 limit - 1 整数范围将 random() 的 0.0 到(几乎)1.0 双精度浮点范围转换为 0。例如, rnd(50) 返回一个范围从 0 到 49 的整数。另外, -100 + rnd(201) 通过添加合适的偏移并传递合适的限值值,将 0.0 到(几乎)1.0 转换为 100 到 100。

注意不要指定 (int) Math.random() * limit ,因为这个表达式的值总是 0。该表达式首先转换 random() 的双精度浮点小数值(0.0 到 0.99999。。。)到整数 0,然后将 0 乘以限制,得到 0。

sin() 和 cos() 方法实现正弦和余弦三角函数——参见http://en.wikipedia.org/wiki/Trigonometric_functions。这些函数的用途从研究三角形到模拟周期现象(如简谐运动——见http://en.wikipedia.org/wiki/Simple_harmonic_motion)。

您可以使用 sin() 和 cos() 来产生和显示正弦和余弦波。清单 7-1 给出了一个应用的源代码。

清单 7-1 。绘制正弦波和余弦波

public class Graph
{
   final static int ROWS = 11; // Must be odd
   final static int COLS = 23;

   public static void main(String[] args)
   {
      char[][] screen = new char[ROWS][COLS];
      double scaleX = COLS / 360.0;
      for (int degree = 0; degree < 360; degree++)
      {
         int row = ROWS / 2 +
                   (int) Math.round(ROWS / 2 * Math.sin(Math.toRadians(degree)));
         int col = (int) (degree * scaleX);
         screen[row][col] = 'S';
         row = ROWS / 2 +
               (int) Math.round(ROWS / 2 * Math.cos(Math.toRadians(degree)));
         screen[row][col] = (screen[row][col] == 'S') ? '*' : 'C';
      }
      for (int row = ROWS - 1; row >= 0; row--)
      {
         for (int col = 0; col < COLS; col++)
            System.out.print(screen[row][col]);
         System.out.println();
      }
   }
}

清单 7-1 引入了一个图类,它首先声明了一对常量:行和列。这些常量指定在其上生成图形的数组的维数。必须给行分配一个奇数;否则,抛出 Java . lang . arrayindexoutofboundsexception 类的一个实例。

提示尽可能使用常量是个好主意。源代码更容易维护,因为您只需要在一个地方更改常量的值,而不必在整个源代码中更改每个相应的值。

Graph 接下来声明它的 main() 方法,它首先创建一个二维屏幕字符数组。这个数组用于模拟一个老式的基于字符的屏幕来查看图形。

main() 接下来计算水平比例值,用于水平缩放每个图形,以便 360 个水平(度)位置适合由 COLS 指定的列数。

继续, main() 进入 for 循环,对于每个正弦和余弦图形,为每个度数值创建(行,列)坐标,并在这些坐标处为屏幕数组分配一个字符。正弦图的字符为 S ,余弦图的字符为 C ,余弦图与正弦图相交时的字符为 * 。

行计算调用 toRadians() 将其度参数转换为弧度,这是 sin() 和 cos() 方法所需要的。然后将从 sin() 或 cos()(1 比 1)返回的值乘以 ROWS / 2 ,将该值缩放至屏幕数组行数的一半。通过 long round(double d) 方法将结果舍入到最近的长整数后,使用强制转换将长整数转换为整数,并将该整数添加到 ROW / 2 以偏移行坐标,使其相对于数组的中间行。列计算更简单,将度数值乘以水平比例因子。

屏幕数组通过一对嵌套的 for 循环转储到标准输出设备。外部 for 循环反转数组输出,使其正面朝上—第 0 行应该最后输出。

编译清单 7-1 ( 贾瓦茨 Graph.java)并运行应用( java Graph )。您将看到以下输出:

image

表 7-1 还揭示了一些以+无穷大、无穷大、+0.0、0.0 和 NaN(非数字)开头的新奇事物。

Java 的浮点计算能够返回+无穷大、无穷大、+0.0、0.0 和 NaN,因为 Java 很大程度上符合 IEEE 754(【http://en.wikipedia.org/wiki/IEEE_754】),一种浮点计算的标准。以下是产生这些特殊值的情况:

  • +infinity 返回试图将一个正数除以 0.0 的结果。比如 system . out . println(1.0/0.0);输出无穷大。
  • 尝试将一个负数除以 0.0,返回无穷大。比如 system . out . println(1.0/0.0);输出-无穷大。
  • NaN 从尝试将 0.0 除以 0.0、尝试计算负数的平方根以及尝试其他奇怪的运算返回。比如 system . out . println(0.0/0.0);和 system . out . println(math . sqrt(-1.0));各路输出 NaN 。
  • +0.0 是试图将正数除以+无穷大的结果。比如 system . out . println(1.0/(1.0/0.0));输出 0.0 (+0.0 不带+号)。
  • -0.0 是试图将一个负数除以+无穷大的结果。比如 system . out . println(-1.0/(1.0/0.0));输出 0.0 。

在运算产生+无穷大、-无穷大或 NaN 之后,表达式的其余部分通常等于该特殊值。比如 system . out . println(1.0/0.0 * 20.0);输出无穷大。此外,首先产生+无穷大或-无穷大的表达式可能会转化为 NaN。例如,表达式 1.0 / 0.0 * 0.0 首先得出+无穷大( 1.0 / 0.0 ),然后得出 NaN(+无穷大 * 0.0 )。

另一个好奇心是整数。MAX_VALUE ,整数。最小 _ 值,长。MAX_VALUE 和 Long。最小值。这些项目中的每一个都是一个原始包装类常量,它标识了可以由该类的相关原始类型表示的最大值或最小值。(我在第 8 章中讨论了原始类型包装类。)

最后,你可能想知道为什么 abs() 、 max() 和 min() 重载方法不包括 byte 和 short 版本,就像在 byte abs(byte b) 和 short abs(short s) 中一样。不需要这些方法,因为字节和短整数的有限范围使它们不适合计算。如果你需要这样的方法,查看清单 7-2 。

清单 7-2 。获取字节整数和短整数的绝对值T4

public class AbsByteShort
{
   static byte abs(byte b)
   {
      return (b < 0) ? (byte) -b : b;
   }

   static short abs(short s)
   {
      return (s < 0) ? (short) -s : s;
   }

   public static void main(String[] args)
   {
      byte b = 2;
      System.out.println(abs(b)); // Output: 2
      short s = 3;
      System.out.println(abs(s)); // Output: 3
   }
}

清单 7-2 的(字节)和(短整型)强制转换是必要的,因为 -b 将 b 的值从一个字节转换为一个 int , -s 将 s 的值从一个短整型转换为一个 int 。相比之下, (b < 0) 和 (s < 0) 则不需要这些类型转换,它们会在将 b 和 s 的值与基于 int 的 0 进行比较之前,自动将它们转换为一个 int 。

提示它们在数学中的缺席表明字节和短整型在方法声明中不是很有用。但是,当声明其元素存储小值(如二进制文件的字节值)的数组时,这些类型很有用。如果您声明了一个由 int 或 long 组成的数组来存储这样的值,您最终会浪费堆空间(甚至可能会耗尽内存)。

在搜索 java.lang 包文档时,您可能会遇到一个名为 StrictMath 的类。除了一个更长的名字,这个类看起来和 Math 一样。这些类别之间的差异可以总结如下:

  • StrictMath 的方法在所有平台上返回完全相同的结果。相比之下, Math 的一些方法可能会返回因平台不同而略有不同的值。
  • 因为 StrictMath 不能利用平台特定的特性,如扩展精度数学协处理器,所以 StrictMath 的实现可能不如 Math 的实现有效。

在很大程度上, Math 的方法调用它们的严格数学的对应物。两个例外是 toDegrees() 和 toRadians() 。尽管这些方法在两个类中有相同的代码体,但是 StrictMath 的实现在方法头中包含了保留字 strictfp :

public static strictfp double toDegrees(double angrad)

public static strictfp double tora dians(double angdeg)

维基百科的“strictfp”词条(http://en.wikipedia.org/wiki/Strictfp)提到 strictfp 限制浮点计算以保证可移植性。这个保留字在中间浮点表示和上溢/下溢(生成太大或太小而不适合表示的值)的上下文中实现了可移植性。

如果没有 strictfp ,中间计算就不局限于 Java 支持的 IEEE 754 32 位和 64 位浮点表示。相反,计算可以在支持这种表示的平台上利用更大的表示(可能是 128 位)。

当中间计算的值以 32/64 位表示时会溢出或下溢,而当其值以更多位表示时可能不会溢出/下溢。由于这种差异,可移植性受到了损害。 strictfp 通过要求所有平台使用 32/64 位进行中间计算来实现公平竞争。

当应用于一个方法时, strictfp 确保在该方法中执行的所有浮点计算都是严格符合的。然而, strictfp 可以在类头声明中使用(如在 public strictfp class Fourier transform 中),以确保该类中执行的所有浮点计算都是严格的。

注意 Math 和 StrictMath 被声明为 final 因此它们不能被扩展。此外,它们声明私有的空无参数构造函数,因此它们不能被实例化。最后, Math 和 StrictMath 是工具类的例子,因为它们是作为工具常量和工具(静态)方法的占位符而存在的。

BigDecimal(大十进制)

在第 3 章的中,我引入了一个 SavingsAccount 类,它有一个余额字段,类型为 int 。该字段记录该账户中的美元数量。或者,它可以表示帐户中包含的便士数。

也许你想知道为什么我没有将余额声明为类型 double 或 float 。这样, balance 可以存储 18.26 这样的值(整数部分 18 美元,小数部分 26 便士)。我没有将 balance 声明为 double 或 float ,原因如下:

  • 并非所有可以表示货币数量(美元和美分)的浮点值都可以准确地存储在内存中。例如,0.1(可以用来表示 10 美分)没有精确的存储表示。如果你执行了 double total = 0.1;for(int I = 0;我<50;i++)合计+= 0.1;System.out.println(合计);,你会观察到 5.0999999999999998 而不是正确的 5.1 作为输出。
  • 每个浮点计算的结果都需要四舍五入到最接近的分。否则会引入微小的误差,导致最终结果与正确结果不同。虽然 Math 提供了一对 round() 方法,您可能会考虑使用它们来将计算结果四舍五入到最接近的美分,但是这些方法会四舍五入到最接近的整数(美元)。

清单 7-3 的 InvoiceCalc 应用演示了这两个问题。然而,第一个问题并不严重,因为它对不准确性的贡献很小。更严重的问题发生在执行计算后未能舍入到最接近的分。

清单 7-3 。基于浮点的发票计算导致混乱的结果

import java.text.NumberFormat;

public class InvoiceCalc
{
   final static double DISCOUNT_PERCENT = 0.1; // 10%
   final static double TAX_PERCENT = 0.05; // 5%

   public static void main(String[] args)
   {
      double invoiceSubtotal = 285.36;
      double discount = invoiceSubtotal * DISCOUNT_PERCENT;
      double subtotalBeforeTax = invoiceSubtotal - discount;
      double salesTax = subtotalBeforeTax * TAX_PERCENT;
      double invoiceTotal = subtotalBeforeTax + salesTax;
      NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
      System.out.println("Subtotal: " + currencyFormat.format(invoiceSubtotal));
      System.out.println("Discount: " + currencyFormat.format(discount));
      System.out.println("SubTotal after discount: " +
                         currencyFormat.format(subtotalBeforeTax));
      System.out.println("Sales Tax: " + currencyFormat.format(salesTax));
      System.out.println("Total: " + currencyFormat.format(invoiceTotal));
   }
}

清单 7-3 执行了几个与发票相关的计算,导致了不正确的最终总数。执行完这些计算后,它会获得一个基于货币的格式化程序,用于将双精度浮点值格式化为带有货币符号(如美元符号[ $ )的基于字符串的货币金额。通过调用 java.text.NumberFormat 类的 number format getcurrency instance()方法获得格式化程序。然后,通过将该值作为参数传递给 NumberFormat 的 String format(double value)方法,将该值格式化为货币字符串。

当您运行 InvoiceCalc 时,您将发现以下输出:

Subtotal: $285.36
Discount: $28.54
SubTotal after discount: $256.82
Sales Tax: $12.84
Total: $269.67

此输出显示正确的小计、折扣、折扣后小计和销售税。相比之下,它错误地将 269.67 而不是 269.66 显示为最终总数。即使根据浮点计算,269.67 是正确的值,客户也可能不愿意多付一分钱:

Subtotal: 285.36
Discount: 28.536
SubTotal after discount: 256.824
Sales Tax: 12.8412
Total: 269.6652

这个问题是由于在执行下一次计算之前,没有将每次计算的结果四舍五入到最接近的分位。因此,256.824 中的 0.024 和 12.84 中的 0.0012 构成了最终值,导致 NumberFormat 的 format() 方法 将该值四舍五入为 269.67。

注意切勿使用浮点数或双精度数来表示货币值。

Java 以一个 java.math.BigDecimal 类的形式提供了这两个问题的解决方案。这个不可变的类(一个 BigDecimal 实例不能被修改)表示一个任意精度(位数)的带符号十进制数(比如 23.653),带有一个关联的小数位数(一个指定小数点后位数的整数)。

BigDecimal 声明三个方便常数:一个、十个、零个 。每个常数都是 1、10 和 0 的 BigDecimal 等效值,并且刻度为零。

注意 BigDecimal 声明了几个 ROUND_ 前缀的常量。这些常量在很大程度上已经过时,应该避免使用,还有公共 BigDecimal divide(BigDecimal divisor,int scale,int roundingMode) 和公共 BigDecimal setScale(int newScale,int roundingMode) 方法,它们仍然存在,以便相关的遗留代码继续编译。

BigDecimal 还声明了各种有用的构造函数和方法。在表 7-2 中描述了其中一些构造函数和方法。

表 7-2。 BigDecimal 构造函数和方法

方法 描述
BigDecimal(int val) 将 BigDecimal 实例初始化为 val 的数字。将比例设置为 0。
BigDecimal(字符串 val) 将 BigDecimal 实例初始化为 val 的十进制等效值。将小数位数设置为小数点后的位数,如果没有指定小数点,则设置为 0。当 val 为 null 时,该构造函数抛出 Java . lang . nullpointerexception,当 val 的字符串表示无效(例如包含字母)时,抛出 Java . lang . numberformatexception。
BigDecimal abs() 返回一个新的 BigDecimal 实例,包含当前实例值的绝对值。结果比例与当前实例的比例相同。
bigdecimal add(bigdecimal eye) 返回一个新的 BigDecimal 实例,它包含当前值和参数值的和。结果比例是当前比例和参数比例的最大值。当被加数为 null 时,该方法抛出 NullPointerException 。
BigDecimal divide(BigDecimal 除数) 返回一个新的 BigDecimal 实例,该实例包含当前值除以参数值的商。结果标度是当前标度和参数标度的差。当结果需要更多位数时,可能会进行调整。当除数为 null 或 Java . lang . arithmetic exception 当除数表示 0 或结果无法精确表示时,该方法抛出 NullPointerException 。
最大小数点(BigDecimal val) 返回这个或 val 中的,以包含较大值的 BigDecimal 实例为准。当 val 为 null 时,该方法抛出 NullPointerException 。
bigdecimal min(bigdecimal val) 返回这个或 val 中的,无论哪个 BigDecimal 实例包含较小的值。当 val 为 null 时,该方法抛出 NullPointerException 。
bigdecimal multiple(bigdecimal 乘) 返回一个新的 BigDecimal 实例,它包含当前值和参数值的乘积。结果比例是当前比例和参数比例的总和。当被乘数为 null 时,该方法抛出 NullPointerException 。
否定的 bigdecimal() 返回一个新的 BigDecimal 实例,它包含当前值的负值。结果比例与当前比例相同。
int precision() 返回当前 BigDecimal 实例的精度。
bigdecimal remainder(bigdecimal 除数) 返回一个新的 BigDecimal 实例,该实例包含当前值除以参数值的余数。结果比例是当前比例和参数比例的差。当结果需要更多位数时,可能会进行调整。该方法在除数为 null 时抛出 NullPointerException 或者在除数表示 0 时抛出算术异常。
int scale() 返回当前 BigDecimal 实例的小数位数。
BigDecimal set scale(int new scale,RoundingMode roundingMode) 使用指定的小数位数和舍入模式返回新的 BigDecimal 实例。如果新比例大于旧比例,则在未缩放的值上添加额外的零。在这种情况下,不需要舍入。如果新的刻度小于旧的刻度,尾部数字将被删除。如果这些尾随数字不为零,则剩余的未缩放值必须四舍五入。对于此舍入运算,将使用指定的舍入模式。当舍入模式为 null 时,该方法抛出 NullPointerException ,当舍入模式设置为舍入模式时,该方法抛出算术异常。ROUND _ 不必要的,但是根据当前刻度,舍入是必要的。
大十进制减法(大十进制减数) 返回一个新的 BigDecimal 实例,它包含当前值减去参数值。结果比例是当前比例和参数比例的最大值。当减数为 null 时,该方法抛出 NullPointerException 。
字符串 toString() 返回这个 BigDecimal 实例的字符串表示。必要时使用科学符号。

表 7-2 是指 java.math.RoundingMode ,是一个包含各种舍入模式常量的 enum。这些常数在表 7-3 中描述。

表 7-3。T3【舍入模式常数】T4

常数 描述
天花板 向正无穷大舍入。
向下 向零方向舍入。
楼层 向负无穷大舍入。
半 _ 下 向“最近的邻居”舍入,除非两个邻居等距,在这种情况下向下舍入。
半 _ 偶数 向“最近的邻居”舍入,除非两个邻居等距,在这种情况下,向偶数邻居舍入。
半开状态 向“最近的邻居”舍入,除非两个邻居等距,在这种情况下,向上舍入。(这是学校里普遍教的四舍五入模式。)
不必要的 不需要舍入,因为所请求的操作会产生精确的结果。
上升 正值向正无穷大舍入,负值向负无穷大舍入。

熟悉 BigDecimal 的最好方法是尝试一下。清单 7-4 使用这个类来正确执行清单 7-3 中给出的发票计算。

清单 7-4基于 BigDecimal 的发票计算 不会导致混淆结果

import java.math.BigDecimal;
import java.math.RoundingMode;

public class InvoiceCalc
{
   public static void main(String[] args)
   {
      BigDecimal invoiceSubtotal = new BigDecimal("285.36");
      BigDecimal discountPercent = new BigDecimal("0.10");
      BigDecimal discount = invoiceSubtotal.multiply(discountPercent);
      discount = discount.setScale(2, RoundingMode.HALF_UP);
      BigDecimal subtotalBeforeTax = invoiceSubtotal.subtract(discount);
      subtotalBeforeTax = subtotalBeforeTax.setScale(2, RoundingMode.HALF_UP);
      BigDecimal salesTaxPercent = new BigDecimal("0.05");
      BigDecimal salesTax = subtotalBeforeTax.multiply(salesTaxPercent);
      salesTax = salesTax.setScale(2, RoundingMode.HALF_UP);
      BigDecimal invoiceTotal = subtotalBeforeTax.add(salesTax);
      invoiceTotal = invoiceTotal.setScale(2, RoundingMode.HALF_UP);
      System.out.println("Subtotal: " + invoiceSubtotal);
      System.out.println("Discount: " + discount);
      System.out.println("SubTotal after discount: " + subtotalBeforeTax);
      System.out.println("Sales Tax: " + salesTax);
      System.out.println("Total: " + invoiceTotal);
   }
}

清单 7-4 的 main() 方法首先创建 BigDecimal 对象 invoiceSubtotal 和 discountPercent ,分别初始化为 285.36 和 0.10 。将发票小计乘以折扣百分比并将大小数结果赋给折扣。

此时,贴现包含 28.5360。除了结尾的零,这个值与清单 7–3 中的 invoice subtotal * DISCOUNT _ PERCENT 生成的值相同。应该存储在折扣中的值是 28.54。为了在执行另一个计算之前纠正这个问题, main() 使用这些参数调用 discount 的 setScale() 方法 :

  • 2 :小数点后两位
  • 圆周率。HALF_UP :传统的舍入方法

设置好比例和适当的取整方式后, main() 从 invoiceSubtotal 中减去 discount ,并将得到的 BigDecimal 实例赋给 subtotalbeforeax。 main() 调用 setScale() 对 subtotalbeforeax 进行适当舍入,然后继续下一次计算。

main() 接下来创建一个名为 salesTaxPercent 的 BigDecimal 对象,该对象被初始化为 0.05 。然后,它将 subtotalbeforeax 乘以 salesTaxPercent ,将结果赋给 salesTax ,并在此 BigDecimal 对象上调用 setScale() 以正确舍入其值。

继续, main() 将 salesTax 与 subtotalbeforeax 相加,将结果保存在 invoiceTotal 中,并通过 setScale() 对结果进行舍入。这些对象中的值通过 System.out.println() 发送到标准输出设备,后者调用它们的 toString() 方法返回 BigDecimal 值的字符串表示。

当您运行这个新版本的 InvoiceCalc 时,您将发现以下输出:

Subtotal: 285.36
Discount: 28.54
SubTotal after discount: 256.82
Sales Tax: 12.84
Total: 269.66

注意 BigDecimal 声明了一个 BigDecimal(double val) 构造函数,您应该尽可能避免使用它。该构造函数将 BigDecimal 实例初始化为存储在 val 中的值,使得当 double 无法准确存储时,该实例可以反映无效的表示。例如, BigDecimal(0.1) 导致 0.1000000000000005511151231257827021181583404541015625 存储在实例中。相比之下, BigDecimal("0.1") 恰好存储 0.1 。

BigInteger(大整数)

BigDecimal 将带符号的十进制数存储为 32 位整数刻度的未缩放值。未缩放的值存储在 java.math.BigInteger 类的一个实例中。

BigInteger 是一个不可变的类,表示任意精度的有符号整数。它以二进制补码格式 存储它的值(所有位都翻转—1 到 0 和 0 到 1—并且 1 被加到结果中,以与 Java 的字节整数、短整数、整数和长整数类型使用的二进制补码格式兼容)。

注意查看维基百科的“二进制补码”条目()了解更多关于二进制补码的知识。

BigInteger 声明了三个方便常数:一个、十个、零个 。每个常量都是 1、10 和 0 的大整数。

BigInteger 还声明了各种有用的构造函数和方法。在表 7-4 中描述了其中一些构造函数和方法。

表 7-4。T3】big integer 构造函数和方法

方法 描述
大整数(字节[] val) 将 BigInteger 实例初始化为存储在 val 数组中的整数,其中 val[0] 存储整数的最高有效(最左边)8 位。当 val 为 null 时,该构造函数抛出 NullPointerException ,当 val.length 等于 0 时,抛出 NumberFormatException 。
大整数(字串 val) 将 BigInteger 实例初始化为与 val 相等的整数。当 val 为 null 时,该构造函数抛出 NullPointerException ,当 val 的字符串表示无效(例如包含字母)时,抛出 NumberFormatException 。
大整数 abs() 返回一个新的 BigInteger 实例,包含当前实例值的绝对值。
大整数加(big integer eye) 返回一个新的 BigInteger 实例,它包含当前值和参数值之和。当被加数为 null 时,该方法抛出 NullPointerException 。
大整数除法 返回一个新的 BigInteger 实例,它包含当前值除以参数值的商。当除数为 null 时,该方法抛出 NullPointerException ,当除数表示 0 或结果不能精确表示时,抛出算术异常。
最大整数(BigInteger val) 返回这个或者 val ,无论哪个 BigInteger 实例包含较大的值。当 val 为 null 时,该方法抛出 NullPointerException 。
【big integer min(big integer val) 返回这个或者 val ,无论哪个 BigInteger 实例包含较小的值。当 val 为 null 时,该方法抛出 NullPointerException 。
【big integer 乘】 返回一个新的 BigInteger 实例,它包含当前值和参数值的乘积。当被乘数为 null 时,该方法抛出 NullPointerException 。
否定的大整数() 返回一个新的 BigInteger 实例,它包含当前值的负值。
【big integer remainder(big integer 除数) 返回一个新的 BigInteger 实例,该实例包含当前值除以参数值的余数。该方法在除数为 null 时抛出 NullPointerException ,在除数表示 0 时抛出算术异常。
【大大整数减】 返回一个新的 BigInteger 实例,它包含当前值减去参数值。当减数为 null 时,该方法抛出 NullPointerException 。
字符串 toString() 返回这个 BigInteger 实例的字符串表示。

注意 BigInteger 还声明了几个面向位的方法,比如 big integer and(big integer val)、 BigInteger flipBit(int n) 、big integer shift left(int n)。当您需要执行低级位操作时,这些方法非常有用。

熟悉 BigInteger 的最好方法是尝试一下。清单 7-5 在 factorial() 方法比较上下文中使用了这个类。

清单 7-5 。比较阶乘()方法

import java.math.BigInteger;

public class FactComp
{
   public static void main(String[] args)
   {
      System.out.println(factorial(12));
      System.out.println();
      System.out.println(factorial(20L));
      System.out.println();
      System.out.println(factorial(170.0));
      System.out.println();
      System.out.println(factorial(new BigInteger("170")));
      System.out.println();
      System.out.println(factorial(25.0));
      System.out.println();
      System.out.println(factorial(new BigInteger("25")));
   }

   static int factorial(int n)
   {
      if (n == 0)
         return 1;
      else
         return n * factorial(n - 1);
   }

   static long factorial(long n)
   {
      if (n == 0)
         return 1;
      else
         return n * factorial(n - 1);
   }

   static double factorial(double n)
   {
      if (n == 1.0)
         return 1.0;
      else
         return n * factorial(n - 1);
   }

   static BigInteger factorial(BigInteger n)
   {
      if (n.equals(BigInteger.ZERO))
         return BigInteger.ONE;
      else
         return n.multiply(factorial(n.subtract(BigInteger.ONE)));
   }
}

清单 7-5 比较了递归阶乘()方法的四个版本。这种比较揭示了在返回的阶乘值变得没有意义之前可以传递给前三个方法的最大参数,因为数值类型可以精确表示的值的范围受到限制。

第一个版本基于 int ,有一个从 0 到 12 的有用参数范围。传递任何大于 12 的参数都会导致阶乘无法精确地表示为一个 int 。

您可以通过将参数和返回类型更改为 long 来增加 factorial() 的有用范围,但不会增加太多。做了这些改变后,你会发现有用范围的上限是 20。

为了进一步扩大有用的范围,您可以创建一个版本的 factorial() ,它的参数和返回类型是 double 。这是可能的,因为整数可以精确地表示为 double s。然而,可以传递的最大有用参数是 170.0。任何高于该值的值都会导致 factorial() 返回+无穷大。

您可能需要计算更高的阶乘值,也许是在计算涉及组合或排列的统计问题时。准确计算该值的唯一方法是使用基于 BigInteger 的 factorial() 版本。

当您运行前面的应用时,它会生成以下输出:

479001600

2432902008176640000

7.257415615307994E306

7257415615307998967396728211129263114716991681296451376543577798900561843401706157852350749242617459511490991237838520776666022565442753025328900773207510902400430280058295603966612599658257104398558294257568966313439612262571094946806711205568880457193340212661452800000000000000000000000000000000000000000

1.5511210043330986E25

15511210043330985984000000

前三个值表示基于 int 、基于 long 和基于 doublefactorial()方法可以返回的最高阶乘。第四个值代表最高 double 阶乘的 BigInteger 等效值。

请注意, double 方法无法准确表示 170!(!是阶乘的数学符号)。它的精度简直太小了。尽管该方法试图对最小的数字进行舍入,但舍入并不总是有效,因为数字以 7994 而不是 7998 结尾。正如最后两行输出所显示的,舍入只在参数 25.0 之前是准确的。

探索字符串管理

许多计算机语言实现了一个字符串的概念,一个被视为单个单元(而不是单个字符)的字符序列。例如,C 语言将字符串实现为以空字符( '\0' )结尾的字符数组。相比之下,Java 通过 java.lang.String 类实现字符串。

字符串对象是不可变的:你不能修改一个字符串对象的字符串。各种看似修改字符串对象的字符串方法实际上返回一个新的字符串对象,其中包含修改后的字符串内容。因为返回新的 String 对象通常会造成浪费,所以 Java 提供了 java.lang.StringBuffer 和等效的 Java . lang . stringbuilder 类作为解决方法。

在本节中,我将向您介绍 String 和 String buffer/StringBuilder。

线

字符串 将一个字符串表示为一个字符序列。与 C 字符串不同,该序列不以空字符结束。相反,它的长度是单独存储的。与其他引用类型不同,Java 语言通过提供简化字符串处理的语法糖来特别对待字符串类。比如 Java 识别字符串 favLanguage = " Java 作为字符串文字“Java”到字符串变量 favLanguage 的赋值。没有这个糖,你就得指定 String fav language = new String(" Java ");。Java 语言还重载了 + 和 += 操作符来执行字符串连接。

表 7-5 描述了一些字符串的构造函数以及初始化字符串对象和处理字符串的方法。

表 7-5。 字符串构造函数和方法

方法 描述
字符串(char[]数据) 将这个字符串对象初始化为数据数组中的字符。在初始化这个字符串对象后修改数据对该对象没有影响。
字符串(字符串 s) 将这个字符串对象初始化为的字符串。
char charAt(int index) 返回这个字符串对象的字符串中位于从零开始的索引处的字符。当索引小于 0 或者大于等于字符串长度时,该方法抛出 Java . lang . stringindexoutofboundsexception。
String concat(String s) 返回一个新的字符串对象,包含这个字符串对象的字符串,后跟的参数的字符串。
布尔结束(字符串后缀) 当这个字符串对象的字符串以后缀参数中的字符结束时,当后缀为空(不包含字符)时,或者当后缀包含与这个字符串对象的字符串相同的字符序列时,返回 true。该方法执行区分大小写的比较(例如,A 不等于 A),并在后缀为 null 时抛出 NullPointerException 。
布尔等于(Object object) 当对象的类型为字符串并且此参数的字符串包含与此字符串对象的字符串相同的字符(并且顺序相同)时,返回 true。
布尔值等于忽略大小写(字符串 s) 当 s 和这个字符串对象包含相同的字符时返回 true(忽略大小写)。当字符序列不同时或者当 null 被传递给 s 时,该方法返回 false。
int indexOf(int c) 返回此字符串对象的字符串中由 c 表示的字符的第一个匹配项(从字符串的开头到结尾)的从零开始的索引。当该字符不存在时,返回 1。
int indexOf(字符串 s) 返回的的字符序列在这个字符串对象的字符串中第一次出现的位置(从字符串的开头到结尾)的从零开始的索引。当 s 不存在时返回 1。当 s 为 null 时,该方法抛出 NullPointerException 。
String intern() 在字符串对象的内部表中搜索其字符串等于该字符串对象的字符串的对象。这个字符串对象的字符串在不存在时被添加到表中。返回包含在表中的对象,其字符串等于这个字符串对象的字符串。对于相等的字符串,总是返回相同的字符串对象。
int lastIndexOf(int c) 返回由 c 表示的字符在这个字符串对象的字符串中最后一次出现(从字符串开头到字符串结尾)的从零开始的索引。当该字符不存在时,返回 1。
int lastIndexOf(String s) 返回的的字符序列在这个字符串对象的字符串中最后一次出现的位置(从字符串的开头到结尾)的从零开始的索引。当 s 不存在时返回-1。当 s 为 null 时,该方法抛出 NullPointerException 。
int length() 返回这个字符串对象的字符串中的字符数。
字符串替换(char oldChar,char newChar) 返回一个新的字符串对象,其字符串与这个字符串对象的字符串匹配,除了所有出现的 oldChar 都被替换为 newChar 。
String[] split(字符串表达式) 使用由 expr 指定的正则表达式(字符串的模式用于在字符串中搜索匹配模式的子字符串)将这个字符串对象的字符串拆分成一个由字符串对象组成的数组,作为拆分的基础。当 expr 为 null 时,该方法抛出 NullPointerException ,当 expr 的语法无效时,该方法抛出 Java . util . regex . patternsynctaxexception。
布尔型开头(字符串前缀) 当这个字符串对象的字符串以前缀参数中的字符开始,当前缀为空(不包含字符),或者当前缀包含与这个字符串对象的字符串相同的字符序列时,返回 true。该方法执行区分大小写的比较(例如,A 不等于 A)并在前缀为 null 时抛出 NullPointerException 。
String substring(int start) 返回一个新的字符串对象,其字符串包含这个字符串对象的字符,从位于开始的字符开始。当 start 为负或者大于这个字符串对象的字符串长度时,这个方法抛出 StringIndexOutOfBoundsException。
char[]火炬数组() 返回一个字符数组,包含这个字符串对象的字符串中的字符。
字符串 tolpower case() 返回一个新的字符串对象,其字符串包含这个字符串对象的字符,其中大写字母已被转换为小写字母。这个字符串对象在不包含要转换的大写字母时被返回。
String toUpperCase() 返回一个新的字符串对象,其字符串包含这个字符串对象的字符,其中小写字母已经转换为大写字母。这个字符串对象在不包含要转换的小写字母时被返回。
String trim() 返回一个新的字符串对象,该对象包含这个字符串对象的字符串,当没有前导/尾随空格时,从该字符串或这个字符串对象的开头和结尾删除了空格字符(Unicode 值为 32 或更小的字符)。

表 7-5 揭示了几个关于弦的有趣项目。首先,这个类的字符串(字符串 s) 构造函数没有将字符串对象初始化为字符串文字。相反,它的行为类似于 C++复制构造函数,将字符串对象初始化为另一个字符串对象的内容。这种行为表明,字符串文字比它看起来要复杂。

实际上,字符串文字是一个字符串对象。你可以通过执行 System.out.println("abc ")来证明这一点。length());和 system . out . println(" ABC " instance of String);。第一个方法调用输出 3 ,这是“ABC”字符串对象的字符串长度,第二个方法调用输出 true(“ABC”是一个字符串对象)。

注意字符串被存储在一个叫做常量池 的类文件数据结构中。当加载一个类时,为每个字面值创建一个字符串对象,并存储在一个字符串对象的内部表中。

第二个有趣的项目是 intern() 方法, interns (存储一个唯一的副本)一个字符串对象在一个字符串对象的内部表中。 intern() 可以通过引用和 == 或来比较字符串!= 。这些运算符是比较字符串的最快方法,在对大量字符串进行排序时尤其有用。

默认情况下,由文字字符串(“ABC”)和字符串值常量表达式(“a”+“BC”)表示的 String 对象在该表中被拘留,这也是为什么 system . out . println(" ABC " = =“a”+“BC”);输出真值。但是通过 String 构造函数创建的 String 对象是不被 interned 的,这就是为什么 system . out . println(" ABC " = = new String(" ABC "));输出假。相比之下,system . out . println(" ABC " = = new String(" ABC ")。实习生());输出真值。

注意小心使用这种字符串比较技术(它只比较引用),因为当一个被比较的字符串没有被保留时,你很容易引入一个 bug。如有疑问,使用 equals() 或 equalsIgnoreCase() 方法。比如“ABC”各一个。equals(新字符串(“ABC”)和“ABC”。equalsIgnoreCase(new String(" ABC "))返回 true。

表 7-5 还展示了 charAt() 和 length() 方法,这对于迭代字符串的字符很有用。比如 String s = " ABC ";for(int I = 0;I<s . length();i++)system . out . println(s . charat(I));返回每个 s 的 a 、 b 和 c 字符,并在单独的行上输出每个字符。

最后,表 7-5 展示了 split() ,这是我在第 6 章的 StubFinder 应用中使用的一种方法,用于将一个字符串的逗号分隔值列表拆分成一个由 String 对象组成的数组。此方法使用一个正则表达式来标识字符串拆分所围绕的字符序列。(我会在第 13 章讨论正则表达式。)

注意StringIndexOutOfBoundsException 和 ArrayIndexOutOfBoundsException 是共享一个公共 Java . lang . indexoutofboundsexception 超类的兄弟类。

StringBuffer 和 StringBuilder

字符串对象是不可变的:你不能修改一个字符串对象的字符串。看似修改了字符串对象的字符串方法(比如 replace() )实际上返回了一个新的字符串对象,而不是修改后的字符串内容。因为返回新的 String 对象通常会造成浪费,所以 Java 提供了 java.lang.StringBuffer 和 Java . lang . stringbuilder 类作为解决方法。

StringBuffer 和 StringBuilder 除了 StringBuilder 提供比 StringBuffer 更好的性能,但不能在没有显式线程同步的多线程环境中使用(在第 8 章中讨论)之外,其他都是相同的。

提示在多线程环境中使用 StringBuffer (为了安全),在单线程环境中使用 StringBuilder (为了性能)。

StringBuffer 和 StringBuilder 为高效构建字符串提供了内部字符数组。在创建了一个 string buffer/StringBuilder 对象之后,您可以调用各种方法在数组中添加、删除和插入各种值的字符表示。然后调用 toString() 将数组的内容转换成一个 String 对象并返回这个对象。

表 7-6 描述了一些 StringBuffer 的构造函数和方法,用于初始化 StringBuffer 对象和处理字符串缓冲区。 StringBuilder 的构造函数和方法是相同的,不再讨论。

表 7-6。 StringBuffer 构造函数和方法

方法 描述
串缓冲() 将这个 StringBuffer 对象初始化为一个空数组,初始容量为 16 个字符。
StringBuffer(整数容量) 将这个 StringBuffer 对象初始化为一个空数组,初始容量为 capacity 个字符。当容量为负时,该构造函数抛出 Java . lang . negative earraysizeexception。
串缓冲(串 s) 将这个 StringBuffer 对象初始化为一个包含的字符的数组。这个对象的初始容量是 16 加上 s 的长度。当 s 为 null 时,该构造函数抛出 NullPointerException 。
字串缓冲附录(boolean b) 当 b 为真时将真追加到该 StringBuffer 对象的数组中,当 b 为假时将假追加到该数组中,并返回该 StringBuffer 对象。
串缓冲附录(char ch) 将 ch 的字符追加到这个 StringBuffer 对象的数组中,并返回这个 StringBuffer 对象。
string buffer append(char[]chars) 将 chars 数组中的字符追加到这个 StringBuffer 对象的数组中,并返回这个 StringBuffer 对象。当 chars 为 null 时,该方法抛出 NullPointerException 。
string buffer append(double d) 将 d 的双精度浮点值的字符串表示追加到这个 StringBuffer 对象的数组中,并返回这个 StringBuffer 对象。
string buffer append(float f) 将 f 的浮点值的字符串表示追加到这个 StringBuffer 对象的数组中,并返回这个 StringBuffer 对象。
StringBuffer append(int i) 将 i 的整数值的字符串表示追加到这个 StringBuffer 对象的数组中,并返回这个 StringBuffer 对象。
string buffer append(long l) 将 l 的长整型值的字符串表示追加到这个 StringBuffer 对象的数组中,并返回这个 StringBuffer 对象。
StringBuffer append(对象 obj) 调用 obj 的 toString() 方法,将返回的字符串的字符追加到这个 StringBuffer 对象的数组中。当 null 传递给 obj 时,将 null 追加到数组中。返回这个 StringBuffer 对象。
StringBuffer append(字符串 s) 将 s 的字符串追加到这个 StringBuffer 对象的数组中。当 null 传递给 s 时,将 null 追加到数组中。返回这个 StringBuffer 对象。
int capacity() 返回这个 StringBuffer 对象的数组的当前容量。
char charAt(int index) 返回这个 StringBuffer 对象数组中位于索引的字符。当索引为负或者大于等于这个 StringBuffer 对象的长度时,这个方法抛出 StringIndexOutOfBoundsException。
void ensure capacity(int min) 确保这个 StringBuffer 对象的容量至少是由 min 指定的容量。如果当前容量小于 min ,则创建一个新的具有更大容量的内部数组。新容量设置为最小和当前容量乘以 2 的较大值,结果加 2。当最小值为负或为零时,不采取任何行动。
int length() 返回存储在这个 StringBuffer 对象的数组中的字符数。
字符串缓冲反向() 返回这个 StringBuffer 对象,并反转其数组内容。
见 setharat(int index,char ch) 用 ch 替换索引处的字符。当索引为负或者大于等于这个 StringBuffer 对象的数组长度时,这个方法抛出 StringIndexOutOfBoundsException。
void setLength(int length) 将这个 StringBuffer 对象的数组长度设置为长度。如果长度参数小于当前长度,数组的内容将被截断。如果长度参数大于或等于当前长度,则足够的空字符( '\u0000' )被追加到数组中。当长度为负时,该方法抛出 StringIndexOutOfBoundsException。
String substring(int start) 返回一个新的 String 对象,包含这个 StringBuffer 对象数组中的所有字符,从位于 start 的字符开始。当 start 小于 0 或者大于等于这个 StringBuffer 对象的数组长度时,这个方法抛出 StringIndexOutOfBoundsException。
字符串 toString() 返回一个新的 String 对象,其字符串等于这个 StringBuffer 对象的数组的内容。

一个 StringBuffer 或 StringBuilder 对象的内部数组与容量和长度的概念相关联。容量指的是在数组增长以容纳更多字符之前,数组中可以存储的最大字符数。 Length 指数组中已经存储的字符数。

考虑这样一个场景,您已经编写了将整数值格式化为字符串的代码。作为格式化程序的一部分,您需要在整数前添加特定数量的前导空格。您决定使用以下初始化代码和循环来构建一个带有 3 个前导空格的 space prefix 字符串:

int numLeadingSpaces = 3; // default value
String spacesPrefix = "";
for (int j = 0; j < numLeadingSpaces; j++)
  spacesPrefix += "0";

这个循环是低效的,因为每次迭代都创建一个 StringBuilder 对象和一个 String 对象。编译器将该代码片段转换为以下片段:

int numLeadingSpaces = 3; // default value
String spacesPrefix = "";
for (int j = 0; j < numLeadingSpaces; j++)
  spacesPrefix = new StringBuffer().append(spacesPrefix).append("0").toString();

对前面的循环进行编码的一种更有效的方法是在进入循环之前创建一个 string buffer/StringBuilder 对象,在循环中调用适当的 append() 方法,并在循环之后调用 toString() 。以下代码片段演示了这种更高效的场景:

int numLeadingSpaces = 3; // default value
StringBuffer sb = new StringBuffer();
for (int j = 0; j < numLeadingSpaces; j++)
   sb.append('0');
String spacesPrefix = sb.toString();

注意避免在冗长的循环中使用字符串连接操作符,因为这会导致创建许多不必要的 StringBuilder 和 String 对象。

获取包信息

java.lang.Package 类提供了对包信息的访问(见第 5 章包的介绍)。这些信息包括关于 Java 包的实现和规范的版本细节、包的名称,以及包是否已经被密封的指示(包中的所有类都被归档在同一个 JAR 文件中)。

表 7-7 描述了包的一些方法。

表 7-7。 包法

方法 描述
String getImplementationTitle() 返回此包实现的标题,可能为空。标题的格式未指定。
字串 getImplementationVendor() 返回提供此包实现的供应商或组织的名称。该名称可能为空。名称的格式未指定。
字符串 getImplementationVersion() 返回此包实现的版本号,可能为空。此版本字符串必须是由句点分隔的正十进制整数序列,并且可以有前导零。
字符串 getName() 以标准的点符号返回这个包的名称,例如, java.lang 。
静态包 get Package(String Package name) 当找不到标识为 packageName 的包时,返回与标识为 packageName 的包关联的 Package 对象,或者返回 null。当 packageName 为 null 时,该方法抛出 NullPointerException 。
静态包[]获取包() 返回该方法调用方可以访问的所有包对象的数组。
字符串获取指定标题() 返回这个包的规范的标题,可能为空。标题的格式未指定。
字符串 get spec vendor() 返回提供此包实现的规范的供应商或组织的名称。该名称可能为空。名称的格式未指定。
字符串 get spec version() 返回此包实现的规范的版本号,可能为空。此版本字符串必须是由句点分隔的正十进制整数序列,并且可以有前导零。
boolean 与(所需字符串)兼容 通过将该包的规范版本与所需的版本进行比较,检查该包以确定其是否与指定的版本字符串兼容。当此包的规范版本号大于或等于所需版本号时返回 true(此包兼容);否则,返回 false。当期望的为 null 时,该方法抛出 NullPointerException ,当该包的版本号或期望的版本号不是点格式时,该方法抛出 NumberFormatException 。
布尔 isSealed() 当此包已被密封时返回 true 否则,返回 false。

我已经创建了一个 PackageInfo 应用,演示了表 7-7 的包的大部分方法。清单 7-6 展示了这个应用的源代码。

清单 7-6 。获取关于包的信息

public class PackageInfo
{
   public static void main(String[] args)
   {
      if (args.length == 0)
      {
         System.err.println("usage: java PackageInfo packageName [version]");
         return;
      }
      Package pkg = Package.getPackage(args[0]);
      if (pkg == null)
      {
         System.err.println(args[0] + " not found");
         return;
      }
      System.out.println("Name: " + pkg.getName());
      System.out.println("Implementation title: " +
                         pkg.getImplementationTitle());
      System.out.println("Implementation vendor: " +
                         pkg.getImplementationVendor());
      System.out.println("Implementation version: " +
                         pkg.getImplementationVersion());
      System.out.println("Specification title: " +
                         pkg.getSpecificationTitle());
      System.out.println("Specification vendor: " +
                         pkg.getSpecificationVendor());
      System.out.println("Specification version: " +
                         pkg.getSpecificationVersion());
      System.out.println("Sealed: " + pkg.isSealed());
      if (args.length > 1)
         System.out.println("Compatible with " + args[1] + ": " +
                            pkg.isCompatibleWith(args[1]));
   }
}

编译完清单 7-6(【PackageInfo.java】)之后,在命令行上至少指定一个包名。例如, java PackageInfo java.lang 在 Java 7 下返回以下输出:

Name: java.lang
Implementation title: Java Runtime Environment
Implementation vendor: Oracle Corporation
Implementation version: 1.7.0_06
Specification title: Java Platform API Specification
Specification vendor: Oracle Corporation
Specification version: 1.7
Sealed: false

PackageInfo 还可以让您确定软件包的规范是否与特定的版本号兼容。包与其前身兼容。

比如 Java package info Java . lang 1.6 输出兼容 1.6 的:真,而 Java package info Java . lang 1.8 输出兼容 1.8 的:假。

你也可以在自己的包中使用 PackageInfo ,这些包是你在第 5 章中学到的。例如,那一章介绍了一个日志 包。

将 PackageInfo.class 复制到包含日志包目录(包含编译后的类文件)的目录下,执行 java PackageInfo 日志。

PackageInfo 通过显示以下输出进行响应:

logging not found

出现此错误消息是因为 getPackage() 在返回描述该包的包对象之前,需要从包中加载至少一个 classfile。

消除前面错误信息的唯一方法是从包中加载一个类。通过将下面的代码片段合并到清单 7–6 中来完成这项任务。

if (args.length == 3)
try
{
   Class.forName(args[2]);
}
catch (ClassNotFoundException cnfe)
{
   System.err.println("cannot load " + args[2]);
   return;
}

这段代码片段,必须在包 pkg = Package.getPackage(args[0])之前;,加载由修改后的 PackageInfo 应用的第三个命令行参数命名的类文件。

通过 java PackageInfo logging 1.5 运行新的 PackageInfo 应用。假设 File.class 存在(您需要在指定这个命令行之前编译这个包),这个命令行将 logging 的 File class 标识为要加载的类:

Name: logging
Implementation title: null
Implementation vendor: null
Implementation version: null
Specification title: null
Specification vendor: null
Specification version: null
Sealed: false
Exception in thread "main" java.lang.NumberFormatException: Empty version string
                at java.lang.Package.isCompatibleWith(Unknown Source)
                at PackageInfo.main(PackageInfo.java:41)

看到所有这些空值并不奇怪,因为没有包信息被添加到日志包中。另外,抛出 NumberFormatException 与() 兼容,因为 logging 包不包含点格式的规范版本号(为空)。

也许将包信息放入日志包的最简单方法是创建一个日志. jar 文件,类似于第 5 章中所示的例子。但是首先,您必须创建一个包含包信息的小文本文件。您可以为文件选择任何名称。清单 7-7 揭示了我对 manifest.mf 的选择。

清单 7-7包含包裹信息的 manifest.mf

Implementation-Title: Logging Implementation
Implementation-Vendor: Jeff Friesen
Implementation-Version: 1.0a
Specification-Title: Logging Specification
Specification-Vendor: Jeff "JavaJeff" Friesen
Specification-Version: 1.0
Sealed: true

注意确保在最后一行结束时按下回车键(密封:真)。否则,您可能会在输出中看到【T4 密封:错误,因为这个条目不会被 JDK 的 jar 工具 jar 存储在日志包中。

执行下面的命令行来创建一个 JAR 文件,该文件包括日志及其文件,以及其清单,一个名为清单的特殊文件。MF 存储关于 JAR 文件内容的信息,包含清单 7-7 的内容:

jar cfm logging.jar manifest.mf logging

或者,您可以指定以下稍长的命令行之一,相当于前面的命令行:

jar cfm logging.jar manifest.mf logging\*.class
jar cfm logging.jar manifest.mf logging/*.class

任一命令行都创建一个名为 logging.jar 的 JAR 文件(通过 c 【创建】和 f 【文件】选项)。它还将 manifest.mf 的内容(通过 m【MANIFEST】选项)合并到清单中。MF ,它存储在包的/JAR 文件的 META-INF 目录中。

注意要了解关于 JAR 文件清单的更多信息,请阅读 JDK 文档“JAR 文件规范”页面的“JAR 清单”部分(http://docs . Oracle . com/javase/7/docs/technotes/guides/JAR/JAR . html # JAR _ Manifest)。

假设 jar 工具没有出现错误信息,执行以下面向 Windows 的命令行(或者适合您平台的命令行)运行 PackageInfo ,从日志包中提取包信息:

java -cp logging.jar;. PackageInfo logging 1.0 logging.File

-cp 命令行选项让您指定类路径,它由 logging.jar 和当前目录(由点(表示)组成。)字符)。未能指定点,java 输出一条错误消息,抱怨无法定位 PackageInfo.class 。

这一次,您应该会看到以下输出:

Name: logging
Implementation title: Logging Implementation
Implementation vendor: Jeff Friesen (IV)
Implementation version: 1.0a
Specification title: Logging Specification
Specification vendor: Jeff Friesen (SV)
Specification version: 1.0
Sealed: true
Compatible with 1.0: true

练习

以下练习旨在测试您对第 7 章内容的理解:

  1. Math 声明了哪些常数?
  2. 为什么是 Math.abs(Integer。【T1 最小值】)等于整数。最小值?
  3. Math 的 random() 方法完成了什么?为什么表达式 (int) Math.random() * limit 不正确?
  4. 确定浮点计算过程中可能出现的五个特殊值。
  5. 数学和严格数学有什么不同?
  6. strictfp 的目的是什么?
  7. 什么是 BigDecimal 以及你为什么会使用这个类?
  8. 哪个舍入模式常数描述了学校通常教授的舍入形式?
  9. 什么是 BigInteger ?
  10. 是非判断:字符串文字是一个字符串对象。
  11. 串的实习生()方法的目的是什么?
  12. String 和 StringBuffer 有什么区别?
  13. StringBuffer 和 StringBuilder 有什么不同?
  14. 包的 isSealed() 方法的目的是什么?
  15. 是非判断: getPackage() 在返回描述该包的包对象之前,需要从包中加载至少一个 classfile。
  16. 质数是一个大于 1 的正整数,只能被 1 和它本身整除。创建一个 PrimeNumberTest 应用,该应用确定其唯一的整数参数是否是素数,并输出一个合适的消息。例如, java 素数测试 289 应该输出消息 289 不是素数。检查素性的一个简单方法是从 2 开始循环到整数参数的平方根,并在循环中使用余数运算符来确定参数是否被循环索引整除。例如,因为 6 % 2 产生余数 0 (2 被 6 整除),所以整数 6 不是素数。
  17. 重写下面低效的循环来使用 StringBuffer 。产生的循环应该最小化对象创建:

    ```java String[] imageNames = new String[NUM_IMAGES]; for (int i = 0; i < imageNames.length; i++) imageNames[i] = new String("image" + i + ".png");

    ```

  18. 创建一个接受单个基于整数的命令行参数的 DigitsToWords 应用。这个应用将这个参数转换为一个 int 值(通过 integer . parse int(args[0])),然后将结果传递给一个 String convertDigitsToWords(int integer)类方法,该方法返回一个包含该数字的文本表示的字符串。比如 1 转换为 1, 16 转换为16, 69 转换为六十九, 123 转换为一百二十三, 2938 转换为二千九百三十八当传递给 integer 的值小于 0 或大于 9999 时抛出 Java . lang . illegalargumentexception。使用 StringBuffer 类作为生成文本的存储库。使用示例: java DigitsToWords 2938 。

摘要

标准类库通过其 java.lang 和 java.math 包提供了许多基本的 API。这些 API 包括 Math , StrictMath , BigDecimal , BigInteger , String , StringBuffer , StringBuilder 和 Package 类以及 RoundingMode enum。

数学用高级运算(如三角学)补充基本数学运算( + 、 - 、 * 、 / 、 % )。配套的 StrictMath 类确保所有这些操作在所有平台上产生相同的值。

货币绝不能用浮点和双精度浮点变量来表示,因为不是所有的货币值都能精确表示。相比之下, BigDecimal 类允许您精确地表示和操作这些值。

BigDecimal 依赖于 BigInteger 类来表示其未缩放的值。一个 BigInteger 实例描述了一个任意长度的整数值(受虚拟机内存的限制)。

String 将一个字符串表示为一个字符序列。因为 String 实例是不可变的,Java 提供了 StringBuffer 和 StringBuilder 来更高效地构建字符串。前一个类用于多线程上下文中;后一类(性能更高)是供单线程使用的。

包类提供对包信息的访问。这些信息包括关于 Java 包的实现和规范的版本信息、包的名称以及包是否密封的指示。

在第 8 章中,我继续通过关注基本类型包装类、线程和系统功能来探索基本 API。