Java 中文官方教程 2022 版(五)
多态性
原文:
docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html
多态性的词典定义指的是生物学中的一个原则,即一个生物体或物种可以具有许多不同的形式或阶段。这个原则也可以应用于面向对象编程和像 Java 语言这样的语言中。类的子类可以定义自己独特的行为,同时共享父类的一些功能。
多态性可以通过对Bicycle类进行微小修改来演示。例如,可以向类中添加一个printDescription方法,该方法显示实例中当前存储的所有数据。
public void printDescription(){
System.out.println("\nBike is " + "in gear " + this.gear
+ " with a cadence of " + this.cadence +
" and travelling at a speed of " + this.speed + ". ");
}
要在 Java 语言中演示多态特性,请使用MountainBike和RoadBike类扩展Bicycle类。对于MountainBike,添加一个suspension字段,这是一个String值,指示自行车是否有前减震器,Front。或者,自行车有前后减震器,Dual。
这是更新后的类:
public class MountainBike extends Bicycle {
private String suspension;
public MountainBike(
int startCadence,
int startSpeed,
int startGear,
String suspensionType){
super(startCadence,
startSpeed,
startGear);
this.setSuspension(suspensionType);
}
public String getSuspension(){
return this.suspension;
}
public void setSuspension(String suspensionType) {
this.suspension = suspensionType;
}
public void printDescription() {
super.printDescription();
System.out.println("The " + "MountainBike has a" +
getSuspension() + " suspension.");
}
}
请注意覆盖的printDescription方法。除了之前提供的信息外,输出还包括有关悬架的额外数据。
接下来,创建RoadBike类。因为公路或赛车自行车有细轮胎,所以添加一个属性来跟踪轮胎宽度。这是RoadBike类:
public class RoadBike extends Bicycle{
// In millimeters (mm)
private int tireWidth;
public RoadBike(int startCadence,
int startSpeed,
int startGear,
int newTireWidth){
super(startCadence,
startSpeed,
startGear);
this.setTireWidth(newTireWidth);
}
public int getTireWidth(){
return this.tireWidth;
}
public void setTireWidth(int newTireWidth){
this.tireWidth = newTireWidth;
}
public void printDescription(){
super.printDescription();
System.out.println("The RoadBike" + " has " + getTireWidth() +
" MM tires.");
}
}
请注意,printDescription方法再次被覆盖。这次,显示了有关轮胎宽度的信息。
总结一下,有三个类:Bicycle,MountainBike和RoadBike。这两个子类覆盖了printDescription方法并打印了独特的信息。
这是一个创建三个Bicycle变量的测试程序。每个变量分配给三个自行车类中的一个。然后打印每个变量。
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;
bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, "Dual");
bike03 = new RoadBike(40, 20, 8, 23);
bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
}
}
以下是测试程序的输出:
Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10\.
Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10\.
The MountainBike has a Dual suspension.
Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20\.
The RoadBike has 23 MM tires.
Java 虚拟机(JVM)调用与每个变量引用的对象相对应的方法。它不调用变量类型定义的方法。这种行为称为虚拟方法调用,展示了 Java 语言中重要的多态性特性的一个方面。
隐藏字段
原文:
docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html
在一个类中,如果一个字段与超类中的字段同名,即使它们的类型不同,该字段也会隐藏超类的字段。在子类中,无法通过简单名称引用超类中的字段。相反,必须通过super来访问该字段,这将在下一节中介绍。一般来说,我们不建议隐藏字段,因为这会使代码难以阅读。
使用关键字 super
访问超类成员
如果您的方法覆盖了其超类的方法之一,您可以通过关键字super调用被覆盖的方法。您还可以使用super来引用隐藏字段(尽管不建议隐藏字段)。考虑这个类,Superclass:
public class Superclass {
public void printMethod() {
System.out.println("Printed in Superclass.");
}
}
这里是一个名为Subclass的子类,覆盖了printMethod():
public class Subclass extends Superclass {
// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();
System.out.println("Printed in Subclass");
}
public static void main(String[] args) {
Subclass s = new Subclass();
s.printMethod();
}
}
在Subclass中,简单名称printMethod()指的是在Subclass中声明的那个,它覆盖了Superclass中的那个。因此,要引用从Superclass继承的printMethod(),Subclass必须使用一个限定名称,使用super如所示。编译和执行Subclass将打印以下内容:
Printed in Superclass.
Printed in Subclass
子类构造函数
以下示例说明了如何使用super关键字调用超类的构造函数。回想一下Bicycle示例中MountainBike是Bicycle的子类。这是MountainBike(子类)构造函数,它调用超类构造函数,然后添加自己的初始化代码:
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
调用超类构造函数必须是子类构造函数中的第一行。
调用超类构造函数的语法是
super();
或者:
super(parameter list);
使用super()时,将调用超类的无参数构造函数。使用super(parameter list)时,将调用具有匹配参数列表的超类构造函数。
注意:如果构造函数没有显式调用超类的构造函数,Java 编译器会自动插入对超类的无参数构造函数的调用。如果超类没有无参数构造函数,您将会得到一个编译时错误。Object确实有这样一个构造函数,所以如果Object是唯一的超类,就不会有问题。
如果子类构造函数显式或隐式地调用其超类的构造函数,您可能会认为会有一整个构造函数链被调用,一直回溯到Object的构造函数。事实上,情况确实如此。这被称为构造函数链,当存在长串的类继承时,您需要注意这一点。
作为超类的对象
原文:
docs.oracle.com/javase/tutorial/java/IandI/objectclass.html
Object类,位于java.lang包中,位于类层次结构树的顶部。每个类都是Object类的后代,直接或间接的。你使用或编写的每个类都继承了Object的实例方法。你不需要使用这些方法中的任何一个,但是如果选择这样做,可能需要用特定于你的类的代码重写它们。本节讨论的从Object继承的方法有:
-
protected Object clone() throws CloneNotSupportedException创建并返回此对象的副本。
-
public boolean equals(Object obj)指示某个其他对象是否"等于"这个对象。
-
protected void finalize() throws Throwable垃圾回收器在对象上调用的方法
集合确定没有更多引用指向该对象
-
public final Class getClass()返回对象的运行时类。
-
public int hashCode()为对象返回一个哈希码值。
-
public String toString()返回对象的字符串表示形式。
Object的notify、notifyAll和wait方法在程序中独立运行的线程的活动同步中起着作用,这将在后面的课程中讨论,这里不会涉及。这些方法有五个:
-
public final void notify() -
public final void notifyAll() -
public final void wait() -
public final void wait(long timeout) -
public final void wait(long timeout, int nanos)
注意: 这些方法中有一些微妙的方面,特别是clone方法。
clone()方法
如果一个类或其父类实现了Cloneable接口,你可以使用clone()方法从现有对象创建一个副本。要创建一个克隆,你需要编写:
*aCloneableObject*.clone();
Object的这个方法的实现会检查调用clone()的对象是否实现了Cloneable接口。如果对象没有实现,该方法会抛出一个CloneNotSupportedException异常。异常处理将在后面的课程中介绍。目前,你需要知道clone()必须声明为
protected Object clone() throws CloneNotSupportedException
或:
public Object clone() throws CloneNotSupportedException
如果你要编写一个clone()方法来覆盖Object中的方法。
如果调用clone()的对象确实实现了Cloneable接口,Object的clone()方法的实现会创建一个与原始对象相同类的对象,并初始化新对象的成员变量为与原始对象对应的成员变量相同的值。
使你的类可克隆的最简单方法是在类的声明中添加implements Cloneable,然后你的对象可以调用clone()方法。
对于一些类,Object 的 clone() 方法的默认行为就很好用。然而,如果一个对象包含对外部对象的引用,比如 ObjExternal,你可能需要重写 clone() 来获得正确的行为。否则,一个对象对 ObjExternal 的更改也会在其克隆中可见。这意味着原始对象和其克隆不是独立的为了解耦它们,你必须重写 clone(),使其克隆对象和 ObjExternal。然后原始对象引用 ObjExternal,而克隆引用 ObjExternal 的克隆,这样对象和其克隆就是真正独立的。
equals() 方法
equals() 方法用于比较两个对象是否相等,如果它们相等则返回 true。Object 类中提供的 equals() 方法使用身份运算符 (==) 来确定两个对象是否相等。对于基本数据类型,这会给出正确的结果。然而,对于对象来说,这并不适用。Object 提供的 equals() 方法测试对象的引用是否相等也就是说,如果比较的对象是完全相同的对象。
要测试两个对象是否在等价性意义上相等(包含相同的信息),你必须重写 equals() 方法。下面是一个重写 equals() 的 Book 类的示例:
public class Book {
String ISBN;
public String getISBN() {
return ISBN;
}
public boolean equals(Object obj) {
if (obj instanceof Book)
return ISBN.equals((Book)obj.getISBN());
else
return false;
}
}
考虑下面这段代码,用于测试 Book 类的两个实例是否相等:
// Swing Tutorial, 2nd edition
Book firstBook = new Book("0201914670");
Book secondBook = new Book("0201914670");
if (firstBook.equals(secondBook)) {
System.out.println("objects are equal");
} else {
System.out.println("objects are not equal");
}
即使 firstBook 和 secondBook 引用了两个不同的对象,这个程序会显示 objects are equal。它们被认为是相等的,因为比较的对象包含相同的 ISBN 号码。
如果身份运算符对于你的类不合适,你应该始终重写 equals() 方法。
注意:如果你重写了 equals(),你必须同时重写 hashCode()。
finalize() 方法
Object 类提供了一个回调方法 finalize(),当对象变为垃圾时可能会被调用。Object 的 finalize() 实现什么也不做你可以重写 finalize() 来进行清理,比如释放资源。
finalize() 方法可能会被系统自动调用,但是何时调用,甚至是否调用都是不确定的。因此,不要依赖这个方法来清理资源。例如,如果在执行 I/O 操作后没有关闭文件描述符,并且期望 finalize() 为您关闭它们,那么可能会耗尽文件描述符。相反,使用 try-with 资源语句来自动关闭应用程序的资源。参见 The try-with-resources Statement 和 Finalization and Weak, Soft, and Phantom References 中的 Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide。
getClass() 方法
你不能重写 getClass。
getClass()方法返回一个Class对象,该对象有一些方法可以获取关于类的信息,比如它的名称(getSimpleName())、它的父类(getSuperclass())和它实现的接口(getInterfaces())。例如,下面的方法获取并显示对象的类名:
void printClassName(Object obj) {
System.out.println("The object's" + " class is " +
obj.getClass().getSimpleName());
}
Class类,位于java.lang包中,有大量的方法(超过 50 个)。例如,你可以测试类是否为注解(isAnnotation())、接口(isInterface())或枚举(isEnum())。你可以查看对象的字段(getFields())或方法(getMethods()),等等。
hashCode()方法
hashCode()返回的值是对象的哈希码,是由哈希算法生成的整数值。
根据定义,如果两个对象相等,它们的哈希码也必须相等。如果你重写了equals()方法,改变了两个对象相等的方式,那么Object的hashCode()实现就不再有效。因此,如果你重写了equals()方法,你也必须重写hashCode()方法。
toString()方法
你应该始终考虑在你的类中重写toString()方法。
Object的toString()方法返回对象的String表示,对于调试非常有用。对象的String表示完全取决于对象本身,这就是为什么你需要在你的类中重写toString()。
使用toString()和System.out.println()可以显示对象的文本表示,比如Book的实例:
System.out.println(firstBook.toString());
对于正确重写的toString()方法,会打印出有用的信息,就像这样:
ISBN: 0201914670; The Swing Tutorial; A Guide to Constructing GUIs, 2nd Edition
编写最终类和方法
你可以将类的一些或所有方法声明为最终。在方法声明中使用final关键字表示该方法不能被子类重写。Object类就是这样做的——它的一些方法是final的。
如果一个方法有一个不应该被更改的实现,并且对对象的一致状态至关重要,你可能希望将其设置为最终方法。例如,你可能想要将ChessAlgorithm类中的getFirstPlayer方法设置为最终方法:
class ChessAlgorithm {
enum ChessPlayer { WHITE, BLACK }
...
final ChessPlayer getFirstPlayer() {
return ChessPlayer.WHITE;
}
...
}
从构造函数中调用的方法通常应该声明为最终方法。如果构造函数调用一个非最终方法,子类可能重新定义该方法,导致意想不到或不希望的结果。
请注意,你也可以声明整个类为最终类。声明为最终类的类不能被子类化。例如,当创建像String类这样的不可变类时,这是非常有用的。
抽象方法和类
抽象类 是一个声明为 abstract 的类它可能包含或不包含抽象方法。抽象类不能被实例化,但可以被子类化。
抽象方法 是一种声明但没有实现的方法(没有大括号,后面跟着一个分号),如下所示:
abstract void moveTo(double deltaX, double deltaY);
如果一个类包含抽象方法,那么这个类本身必须声明为 abstract,如下所示:
public abstract class GraphicObject {
// declare fields
// declare nonabstract methods
abstract void draw();
}
当一个抽象类被子类化时,子类通常为其父类中的所有抽象方法提供实现。但是,如果没有提供实现,则子类也必须声明为 abstract。
注意: 接口 中的方法(参见 接口 部分)如果没有声明为默认或静态,则隐式是抽象的,因此不需要使用 abstract 修饰符。 (可以使用,但是不必要。)
抽象类与接口的比较
抽象类类似于接口。你不能实例化它们,它们可能包含一些声明有或没有实现的方法。然而,使用抽象类,你可以声明非静态和非最终的字段,并定义公共、受保护和私有的具体方法。使用接口,所有字段都自动是公共的、静态的和最终的,你声明或定义的所有方法(作为默认方法)都是公共的。此外,你只能扩展一个类,无论它是否是抽象的,而你可以实现任意数量的接口。
你应该使用抽象类还是接口?
-
如果你的情况符合以下任何一种情况,请考虑使用抽象类:
-
你想在几个密切相关的类之间共享代码。
-
你期望扩展你的抽象类的类有许多共同的方法或字段,或者需要除了 public 之外的访问修饰符(比如 protected 和 private)。
-
你想声明非静态或非最终字段。这使你能够定义可以访问和修改它们所属对象状态的方法。
-
-
如果你的情况符合以下任何一种情况,请考虑使用接口:
-
你期望不相关的类会实现你的接口。例如,
Comparable和Cloneable这些接口被许多不相关的类实现。 -
你想指定特定数据类型的行为,但不关心谁实现它的行为。
-
你想利用类型的多重继承。
-
JDK 中抽象类的一个示例是AbstractMap,它是集合框架的一部分。它的子类(包括 HashMap、TreeMap 和 ConcurrentHashMap)共享许多方法(包括 get、put、isEmpty、containsKey 和 containsValue),这些方法是由 AbstractMap 定义的。
JDK 中实现多个接口的一个类的示例是HashMap,它实现了接口 Serializable、Cloneable 和 Map<K, V>。通过阅读这些接口列表,你可以推断出 HashMap 的实例(无论是哪个开发者或公司实现的类)可以被克隆,是可序列化的(这意味着它可以被转换为字节流;参见可序列化对象部分),并且具有映射功能。此外,Map<K, V> 接口已经通过许多默认方法(如 merge 和 forEach)进行了增强,而旧类实现了该接口的类不必定义这些方法。
请注意,许多软件库同时使用抽象类和接口;HashMap 类实现了多个接口,并且还扩展了抽象类 AbstractMap。
抽象类示例
在面向对象的绘图应用程序中,你可以绘制圆、矩形、线条、贝塞尔曲线和许多其他图形对象。这些对象都具有某些状态(例如:位置、方向、线条颜色、填充颜色)和行为(例如:moveTo、rotate、resize、draw)是共同的。其中一些状态和行为对所有图形对象都是相同的(例如:位置、填充颜色和 moveTo)。其他需要不同实现(例如,resize 或 draw)。所有 GraphicObject 必须能够自行绘制或调整大小;它们只是在如何执行这些操作上有所不同。这是一个抽象超类的完美情况。你可以利用相似之处,并声明所有图形对象都继承自相同的抽象父对象(例如 GraphicObject),如下图所示。

类 Rectangle、Line、Bezier 和 Circle 继承自 GraphicObject
首先,声明一个抽象类 GraphicObject,以提供所有子类完全共享的成员变量和方法,例如当前位置和 moveTo 方法。GraphicObject 还声明了抽象方法,例如 draw 或 resize,这些方法需要所有子类实现,但必须以不同的方式实现。GraphicObject 类可能如下所示:
abstract class GraphicObject {
int x, y;
...
void moveTo(int newX, int newY) {
...
}
abstract void draw();
abstract void resize();
}
每个非抽象子类 GraphicObject,如 Circle 和 Rectangle,必须提供 draw 和 resize 方法的实现:
class Circle extends GraphicObject {
void draw() {
...
}
void resize() {
...
}
}
class Rectangle extends GraphicObject {
void draw() {
...
}
void resize() {
...
}
}
当抽象类实现接口时
在接口部分中指出,实现接口的类必须实现所有接口的方法。然而,可以定义一个不实现所有接口方法的类,只要该类声明为abstract。例如,
abstract class X implements Y {
// implements all but one method of Y
}
class XX extends X {
// implements the remaining method in Y
}
在这种情况下,类X必须是abstract,因为它没有完全实现Y,但事实上,类XX实现了Y。
类成员
一个抽象类可能有static字段和static方法。您可以像使用其他类一样使用这些静态成员,通过类引用(例如,AbstractClass.staticMethod())。
继承摘要
原文:
docs.oracle.com/javase/tutorial/java/IandI/summaryinherit.html
除了Object类外,一个类只有一个直接的父类。一个类从所有直接或间接的父类那里继承字段和方法。子类可以重写继承的方法,或者隐藏继承的字段或方法。(请注意,隐藏字段通常是不良的编程实践。)
在覆盖和隐藏方法部分的表格显示了声明具有与超类中方法相同签名的方法的效果。
Object类是类层次结构的顶部。所有类都是从这个类继承的后代,并从中继承方法。从Object继承的有用方法包括toString(),equals(),clone()和getClass()。
通过在类的声明中使用final关键字,可以防止类被子类化。同样,通过将方法声明为最终方法,可以防止子类覆盖它。
抽象类只能被子类化;它不能被实例化。抽象类可以包含抽象方法声明但未实现的方法。然后子类为抽象方法提供实现。
问题和练习:继承
原文:
docs.oracle.com/javase/tutorial/java/IandI/QandE/inherit-questions.html
问题
1. 考虑以下两个类:
public class ClassA {
public void methodOne(int i) {
}
public void methodTwo(int i) {
}
public static void methodThree(int i) {
}
public static void methodFour(int i) {
}
}
public class ClassB extends ClassA {
public static void methodOne(int i) {
}
public void methodTwo(int i) {
}
public void methodThree(int i) {
}
public static void methodFour(int i) {
}
}
a. 哪个方法覆盖了超类中的方法?
b. 哪个方法隐藏了超类中的方法?
c. 其他方法做什么?
2. 考虑你在问题和练习:类中编写的Card、Deck和DisplayDeck类。每个类应该覆盖哪些Object方法?
练习
1. 编写你在问题 2 中回答的方法的实现。
检查你的答案。
课程:数字和字符串
数字
这一部分从讨论Number类(位于java.lang包中)及其子类开始。特别是,本节讨论了在何种情况下您会使用这些类的实例化而不是原始数据类型。此外,本节还讨论了您可能需要与数字一起工作的其他类,例如格式化或使用数学函数来补充语言内置的运算符。最后,还讨论了自动装箱和拆箱,这是一种简化代码的编译器功能。
字符串
字符串在 Java 编程中被广泛使用,它们是字符序列。在 Java 编程语言中,字符串是对象。本节描述了使用String类来创建和操作字符串。它还比较了String和StringBuilder类。
数字
本节开始讨论java.lang包中的Number类,它的子类,以及在何种情况下您会使用这些类的实例化而不是原始数字类型。
本节还介绍了PrintStream和DecimalFormat类,它们提供了用于编写格式化数字输出的方法。
最后,讨论了java.lang中的Math类。它包含了用于补充语言内置运算符的数学函数。该类具有三角函数、指数函数等方法。
数字类
原文:
docs.oracle.com/javase/tutorial/java/data/numberclasses.html
在处理数字时,大多数情况下您会在代码中使用原始类型。例如:
int i = 500;
float gpa = 3.65f;
byte mask = 0x7f;
然而,有理由使用对象代替原始类型,并且 Java 平台为每种原始数据类型提供了包装类。这些类将原始类型“包装”在对象中。通常,编译器会执行包装操作如果您在期望对象的地方使用原始类型,编译器会为您将原始类型装箱到其包装类中。同样,如果您在期望原始类型的地方使用数字对象,编译器会为您拆箱对象。有关更多信息,请参阅自动装箱和拆箱
所有数字包装类都是抽象类Number的子类:

注意: 还有四个Number的子类没有在此处讨论。BigDecimal和BigInteger用于高精度计算。AtomicInteger和AtomicLong用于多线程应用程序。
有三个原因可能会使用Number对象而不是原始类型:
-
作为期望对象的方法的参数(在操作数字集合时经常使用)。
-
要使用类定义的常量,如
MIN_VALUE和MAX_VALUE,提供数据类型的上限和下限。 -
要使用类方法将值转换为其他原始类型,将值转换为字符串,以及在不同数字系统之间进行转换(十进制、八进制、十六进制、二进制)。
以下表格列出了所有Number类的子类实现的实例方法。
所有Number子类实现的方法
| 方法 | 描述 |
|---|
| `byte byteValue() short shortValue()
int intValue()
long longValue()
float floatValue()
double doubleValue() | 将此Number`对象的值转换为返回的原始数据类型。 |
| `int compareTo(Byte anotherByte) int compareTo(Double anotherDouble)
int compareTo(Float anotherFloat)
int compareTo(Integer anotherInteger)
int compareTo(Long anotherLong)
int compareTo(Short anotherShort) | 将此Number`对象与参数进行比较。 |
| boolean equals(Object obj) | 确定此数字对象是否等于参数。如果参数不为null且为相同类型且具有相同数值的对象,则方法返回true。
对于Double和Float对象,还有一些额外的要求,这些要求在 Java API 文档中有描述。 |
每个Number类包含其他方法,用于将数字转换为字符串,以及在不同数字系统之间进行转换。以下表格列出了Integer类中的这些方法。其他Number子类的方法类似:
转换方法,Integer类
| 方法 | 描述 |
|---|---|
static Integer decode(String s) |
将字符串解码为整数。可以接受十进制、八进制或十六进制数字的字符串表示作为输入。 |
static int parseInt(String s) |
返回一个整数(仅限十进制)。 |
static int parseInt(String s, int radix) |
返回一个整数,给定十进制、二进制、八进制或十六进制(radix分别等于 10、2、8 或 16)数字的字符串表示作为输入。 |
String toString() |
返回表示此Integer值的String对象。 |
static String toString(int i) |
返回表示指定整数的String对象。 |
static Integer valueOf(int i) |
返回一个包含指定原始值的Integer对象。 |
static Integer valueOf(String s) |
返回一个Integer对象,其中包含指定字符串表示形式的值。 |
static Integer valueOf(String s, int radix) |
返回一个Integer对象,其中包含指定字符串表示形式的整数值,使用基数进行解析。例如,如果s = "333"且radix = 8,则该方法返回八进制数 333 的十进制整数等价值。 |
格式化数字打印输出
原文:
docs.oracle.com/javase/tutorial/java/data/numberformat.html
之前你看到了使用print和println方法将字符串打印到标准输出(System.out)的示例。由于所有数字都可以转换为字符串(稍后将会看到),你可以使用这些方法打印任意混合的字符串和数字。然而,Java 编程语言还有其他方法,允许你在包含数字时更好地控制打印输出。
printf和format方法
java.io包中包含一个PrintStream类,其中有两个格式化方法可用于替换print和println。这些方法,format和printf,是等效的。你一直在使用的熟悉的System.out恰好是一个PrintStream对象,因此你可以在System.out上调用PrintStream方法。因此,在你以前使用print或println的代码中,你可以使用format或printf。例如,
System.out.format(.....);
这两个java.io.PrintStream方法的语法是相同的:
public PrintStream format(String format, Object... args)
其中format是一个指定要使用的格式化的字符串,args是要使用该格式化打印的变量列表。一个简单的示例可能是
System.out.format("The value of " + "the float variable is " +
"%f, while the value of the " + "integer variable is %d, " +
"and the string is %s", floatVar, intVar, stringVar);
第一个参数,format,是一个格式字符串,指定了第二个参数args中的对象如何被格式化。格式字符串包含普通文本以及格式说明符,这些是特殊字符,用于格式化Object... args的参数。(Object... args的表示法称为可变参数,意味着参数的数量可能变化。)
格式说明符以百分号(%)开始,并以转换器结束。转换器是指示要格式化的参数类型的字符。在百分号(%)和转换器之间,你可以有可选的标志和说明符。有许多转换器、标志和说明符,这些都在java.util.Formatter中有文档记录。
这里是一个基本示例:
int i = 461012;
System.out.format("The value of i is: %d%n", i);
%d指定单个变量为十进制整数。%n是一个与平台无关的换行符。输出为:
The value of i is: 461012
printf和format方法是重载的。每个都有以下语法版本:
public PrintStream format(Locale l, String format, Object... args)
例如,要在法国系统中打印数字(在英文浮点数表示中使用逗号代替小数点),你可以使用:
System.out.format(Locale.FRANCE,
"The value of the float " + "variable is %f, while the " +
"value of the integer variable " + "is %d, and the string is %s%n",
floatVar, intVar, stringVar);
一个示例
下表列出了在接下来的示例程序TestFormat.java中使用的一些转换器和标志。
在TestFormat.java中使用的转换器和标志
| 转换器 | 标志 | 说明 |
|---|---|---|
| d | 十进制整数。 | |
| f | 一个浮点数。 | |
| n | 适用于运行应用程序的平台的换行符。应始终使用%n,而不是\n。 |
|
| tB | 日期和时间转换月份的区域设置全名。 | |
| td, te | 日期和时间转换月份中的 2 位数字。td 根据需要带有前导零,te 则不带。 | |
| ty, tY | 日期和时间转换ty = 2 位年份,tY = 4 位年份。 | |
| tl | 日期和时间转换12 小时制钟表中的小时。 | |
| tM | 日期和时间转换以 2 位数字表示的分钟,必要时带有前导零。 | |
| tp | 日期和时间转换区域设置特定的上午/下午(小写)。 | |
| tm | 日期和时间转换月份以 2 位数字表示,必要时带有前导零。 | |
| tD | 日期和时间转换日期为 %tm%td%ty | |
| 08 | 宽度为 8 个字符,必要时带有前导零。 | |
| + | 包括符号,无论是正数还是负数。 | |
| , | 包括特定于区域设置的分组字符。 | |
| - | 左对齐。 | |
| .3 | 小数点后三位。 | |
| 10.3 | 宽度为 10 个字符,右对齐,小数点后三位。 |
以下程序展示了您可以使用format进行的一些格式化。输出在嵌入式注释中用双引号显示:
import java.util.Calendar;
import java.util.Locale;
public class TestFormat {
public static void main(String[] args) {
long n = 461012;
System.out.format("%d%n", n); // --> "461012"
System.out.format("%08d%n", n); // --> "00461012"
System.out.format("%+8d%n", n); // --> " +461012"
System.out.format("%,8d%n", n); // --> " 461,012"
System.out.format("%+,8d%n%n", n); // --> "+461,012"
double pi = Math.PI;
System.out.format("%f%n", pi); // --> "3.141593"
System.out.format("%.3f%n", pi); // --> "3.142"
System.out.format("%10.3f%n", pi); // --> " 3.142"
System.out.format("%-10.3f%n", pi); // --> "3.142"
System.out.format(Locale.FRANCE,
"%-10.4f%n%n", pi); // --> "3,1416"
Calendar c = Calendar.getInstance();
System.out.format("%tB %te, %tY%n", c, c, c); // --> "May 29, 2006"
System.out.format("%tl:%tM %tp%n", c, c, c); // --> "2:34 am"
System.out.format("%tD%n", c); // --> "05/29/06"
}
}
注意: 本节讨论仅涵盖了format和printf方法的基础知识。更详细的信息可以在基础教程的基本 I/O部分的“格式化”页面中找到。
使用String.format创建字符串的方法在字符串中有介绍。
DecimalFormat 类
您可以使用java.text.DecimalFormat类来控制前导和尾随零、前缀和后缀、分组(千位)分隔符以及小数点分隔符的显示。DecimalFormat在数字格式化方面提供了很大的灵活性,但可能会使您的代码变得更加复杂。
接下来的示例创建了一个DecimalFormat对象myFormatter,通过将模式字符串传递给DecimalFormat构造函数来创建。然后通过myFormatter调用从NumberFormat继承的format()方法它接受一个double值作为参数,并以字符串形式返回格式化的数字:
这是一个演示DecimalFormat使用的示例程序:
import java.text.*;
public class DecimalFormatDemo {
static public void customFormat(String pattern, double value ) {
DecimalFormat myFormatter = new DecimalFormat(pattern);
String output = myFormatter.format(value);
System.out.println(value + " " + pattern + " " + output);
}
static public void main(String[] args) {
customFormat("###,###.###", 123456.789);
customFormat("###.##", 123456.789);
customFormat("000000.000", 123.78);
customFormat("$###,###.###", 12345.67);
}
}
输出为:
123456.789 ###,###.### 123,456.789
123456.789 ###.## 123456.79
123.78 000000.000 000123.780
12345.67 $###,###.### $12,345.67
以下表格解释了每行输出。
DecimalFormat.java 输出
| 值 | 模式 | 输出 | 解释 |
|---|---|---|---|
| 123456.789 | ###,###.### | 123,456.789 | 井号(#)表示数字,逗号是分组分隔符的占位符,句点是小数分隔符的占位符。 |
| 123456.789 | ###.## | 123456.79 | value小数点右侧有三位数字,但pattern只有两位。format方法通过四舍五入处理这个问题。 |
| 123.78 | 000000.000 | 000123.780 | pattern指定了前导和尾随零,因为使用的是 0 字符而不是井号(#)。 |
| 12345.67 | $###,###.### | $12,345.67 | pattern中的第一个字符是美元符号($)。请注意,它紧跟在格式化output中最左边的数字之前。 |
超出基本算术
原文:
docs.oracle.com/javase/tutorial/java/data/beyondmath.html
Java 编程语言支持基本算术运算,使用算术运算符:+,-,*,/和%。java.lang包中的Math类提供了更高级数学计算的方法和常量。
Math类中的方法都是静态的,因此您可以直接从类中调用它们,如下所示:
Math.cos(angle);
注意: 使用static import语言特性,您不必在每个数学函数前面写Math:
import static java.lang.Math.*;
这使您可以通过简单名称调用Math类的方法。例如:
cos(angle);
常量和基本方法
Math类包括两个常量:
-
Math.E,即自然对数的底数,以及 -
Math.PI,即圆周与直径的比值。
Math类还包括 40 多个静态方法。以下表格列出了一些基本方法。
基本数学方法
| 方法 | 描述 |
|---|
| `double abs(double d) float abs(float f)
int abs(int i)
long abs(long lng) | 返回参数的绝对值。 |
double ceil(double d) |
返回大于或等于参数的最小整数。返回为 double 类型。 |
|---|---|
double floor(double d) |
返回小于或等于参数的最大整数。返回为 double 类型。 |
double rint(double d) |
返回与参数最接近的整数值。返回为 double 类型。 |
long round(double d) int round(float f) |
返回最接近参数的 long 或 int,如方法的返回类型所示。 |
| `double min(double arg1, double arg2) float min(float arg1, float arg2)
int min(int arg1, int arg2)
long min(long arg1, long arg2) | 返回两个参数中较小的值。 |
| `double max(double arg1, double arg2) float max(float arg1, float arg2)
int max(int arg1, int arg2)
long max(long arg1, long arg2) | 返回两个参数中较大的值。 |
以下程序,BasicMathDemo,演示了如何使用其中一些方法:
public class BasicMathDemo {
public static void main(String[] args) {
double a = -191.635;
double b = 43.74;
int c = 16, d = 45;
System.out.printf("The absolute value " + "of %.3f is %.3f%n",
a, Math.abs(a));
System.out.printf("The ceiling of " + "%.2f is %.0f%n",
b, Math.ceil(b));
System.out.printf("The floor of " + "%.2f is %.0f%n",
b, Math.floor(b));
System.out.printf("The rint of %.2f " + "is %.0f%n",
b, Math.rint(b));
System.out.printf("The max of %d and " + "%d is %d%n",
c, d, Math.max(c, d));
System.out.printf("The min of of %d " + "and %d is %d%n",
c, d, Math.min(c, d));
}
}
这个程序的输出如下:
The absolute value of -191.635 is 191.635
The ceiling of 43.74 is 44
The floor of 43.74 is 43
The rint of 43.74 is 44
The max of 16 and 45 is 45
The min of 16 and 45 is 16
指数和对数方法
下表列出了Math类的指数和对数方法。
指数和对数方法
| 方法 | 描述 |
|---|---|
double exp(double d) |
返回自然对数的底数 e 的参数次幂。 |
double log(double d) |
返回参数的自然对数。 |
double pow(double base, double exponent) |
返回第一个参数的值的第二个参数的幂。 |
double sqrt(double d) |
返回参数的平方根。 |
下面的程序,指数演示,显示了e的值,然后对任意选择的数字调用前面表中列出的每个方法:
public class ExponentialDemo {
public static void main(String[] args) {
double x = 11.635;
double y = 2.76;
System.out.printf("The value of " + "e is %.4f%n",
Math.E);
System.out.printf("exp(%.3f) " + "is %.3f%n",
x, Math.exp(x));
System.out.printf("log(%.3f) is " + "%.3f%n",
x, Math.log(x));
System.out.printf("pow(%.3f, %.3f) " + "is %.3f%n",
x, y, Math.pow(x, y));
System.out.printf("sqrt(%.3f) is " + "%.3f%n",
x, Math.sqrt(x));
}
}
当你运行指数演示时,你将看到以下输出:
The value of e is 2.7183
exp(11.635) is 112983.831
log(11.635) is 2.454
pow(11.635, 2.760) is 874.008
sqrt(11.635) is 3.411
三角函数方法
Math类还提供了一系列三角函数,总结在下表中。传递给这些方法的值是以弧度表示的角度。你可以使用toRadians方法将角度转换为弧度。
三角函数方法
| 方法 | 描述 |
|---|---|
double sin(double d) |
返回指定双精度值的正弦值。 |
double cos(double d) |
返回指定双精度值的余弦值。 |
double tan(double d) |
返回指定双精度值的正切值。 |
double asin(double d) |
返回指定双精度值的反正弦值。 |
double acos(double d) |
返回指定双精度值的反余弦值。 |
double atan(double d) |
返回指定双精度值的反正切值。 |
double atan2(double y, double x) |
将直角坐标(x, y)转换为极坐标(r, theta)并返回theta。 |
double toDegrees(double d) double toRadians(double d) |
将参数转换为度或弧度。 |
这里有一个程序,三角函数演示,使用每个方法来计算 45 度角的各种三角函数值:
public class TrigonometricDemo {
public static void main(String[] args) {
double degrees = 45.0;
double radians = Math.toRadians(degrees);
System.out.format("The value of pi " + "is %.4f%n",
Math.PI);
System.out.format("The sine of %.1f " + "degrees is %.4f%n",
degrees, Math.sin(radians));
System.out.format("The cosine of %.1f " + "degrees is %.4f%n",
degrees, Math.cos(radians));
System.out.format("The tangent of %.1f " + "degrees is %.4f%n",
degrees, Math.tan(radians));
System.out.format("The arcsine of %.4f " + "is %.4f degrees %n",
Math.sin(radians),
Math.toDegrees(Math.asin(Math.sin(radians))));
System.out.format("The arccosine of %.4f " + "is %.4f degrees %n",
Math.cos(radians),
Math.toDegrees(Math.acos(Math.cos(radians))));
System.out.format("The arctangent of %.4f " + "is %.4f degrees %n",
Math.tan(radians),
Math.toDegrees(Math.atan(Math.tan(radians))));
}
}
该程序的输出如下:
The value of pi is 3.1416
The sine of 45.0 degrees is 0.7071
The cosine of 45.0 degrees is 0.7071
The tangent of 45.0 degrees is 1.0000
The arcsine of 0.7071 is 45.0000 degrees
The arccosine of 0.7071 is 45.0000 degrees
The arctangent of 1.0000 is 45.0000 degrees
随机数
random()方法返回一个在 0.0 和 1.0 之间伪随机选择的数字。范围包括 0.0 但不包括 1.0。换句话说:0.0 <= Math.random() < 1.0。要获得不同范围的数字,可以对随机方法返回的值进行算术运算。例如,要生成 0 到 9 之间的整数,你可以这样写:
int number = (int)(Math.random() * 10);
通过将值乘以 10,可能值的范围变为0.0 <= number < 10.0。
使用Math.random在需要生成单个随机数时效果很好。如果需要生成一系列随机数,应该创建一个java.util.Random的实例,并在该对象上调用方法来生成数字。
数字总结
原文:
docs.oracle.com/javase/tutorial/java/data/numbersummary.html
您可以使用包装类之一 - Byte、Double、Float、Integer、Long 或 Short - 将原始类型的数字包装在对象中。在必要时,Java 编译器会自动为您包装(装箱)原始类型,并在必要时对其进行拆箱。
Number 类包括常量和有用的类方法。MIN_VALUE 和 MAX_VALUE 常量包含该类型对象可以包含的最小值和最大值。byteValue、shortValue 和类似方法将一个数值类型转换为另一个数值类型。valueOf 方法将字符串转换为数字,toString 方法将数字转换为字符串。
要格式化包含数字的字符串以进行输出,可以使用 PrintStream 类中的 printf() 或 format() 方法。或者,您可以使用 NumberFormat 类使用模式自定义数字格式。
Math 类包含各种执行数学函数的类方法,包括指数、对数和三角函数方法。Math 还包括基本算术函数,如绝对值和四舍五入,以及用于生成随机数的 random() 方法。
问题和练习:数字
原文:
docs.oracle.com/javase/tutorial/java/data/QandE/numbers-questions.html
问题
-
使用 API 文档找到以下问题的答案:
-
你可以使用哪个
Integer方法将int转换为以十六进制表示数字的字符串?例如,哪个方法将整数 65 转换为字符串“41”? -
你会使用哪个
Integer方法将以基数 5 表示的字符串转换为相应的int?例如,如何将字符串“230”转换为整数值 65?展示你将用于完成此任务的代码。 -
你可以使用哪个 Double 方法来检测浮点数是否具有特殊值“非数字”(
NaN)?
-
-
以下表达式的值是多少,为什么?
Integer.valueOf(1).equals(Long.valueOf(1))
练习
-
将
MaxVariablesDemo更改为显示最小值而不是最大值。您可以删除与变量aChar和aBoolean相关的所有代码。输出是什么? -
创建一个程序,从命令行读取未指定数量的整数参数并将它们相加。例如,假设您输入以下内容:
java Adder 1 3 2 10程序应显示
16,然后退出。如果用户只输入一个参数,程序应显示错误消息。您可以基于ValueOfDemo编写您的程序。 -
创建一个类似于上一个程序但具有以下区别的程序:
-
它不是读取整数参数,而是读取浮点参数。
-
它显示参数的总和,小数点右侧正好有两位。
例如,假设您输入以下内容:
java FPAdder 1 1e2 3.0 4.754程序将显示
108.75。根据您的区域设置,小数点可能是逗号(,)而不是句号(.)。 -
检查你的答案。
字符
原文:
docs.oracle.com/javase/tutorial/java/data/characters.html
大多数情况下,如果您使用单个字符值,您将使用原始的char类型。例如:
char ch = 'a';
// Unicode for uppercase Greek omega character
char uniChar = '\u03A9';
// an array of chars
char[] charArray = { 'a', 'b', 'c', 'd', 'e' };
然而,有时您需要将 char 用作对象例如,作为期望对象的方法参数。Java 编程语言提供了一个包装器类,用于将char包装在Character对象中。Character类型的对象包含一个单一字段,其类型为char。这个Character类还提供了许多有用的类(即静态)方法来操作字符。
您可以使用Character构造函数创建一个Character对象:
Character ch = new Character('a');
Java 编译器在某些情况下也会为您创建一个Character对象。例如,如果您将一个原始的char传递给一个期望对象的方法,编译器会自动为您将char转换为Character。这个特性被称为自动装箱或者拆箱,如果转换是另一种方式的话。有关自动装箱和拆箱的更多信息,请参阅自动装箱和拆箱。
注意: Character类是不可变的,一旦创建,Character对象就无法更改。
以下表列出了Character类中一些最有用的方法,但并非详尽无遗。要查看此类中所有方法的完整列表(超过 50 个),请参考java.lang.Character API 规范。
Character类中的有用方法
| 方法 | 描述 |
|---|---|
boolean isLetter(char ch) boolean isDigit(char ch) |
确定指定的 char 值是字母还是数字。 |
boolean isWhitespace(char ch) |
确定指定的 char 值是否为空格。 |
boolean isUpperCase(char ch) boolean isLowerCase(char ch) |
确定指定的 char 值是大写还是小写。 |
char toUpperCase(char ch) char toLowerCase(char ch) |
返回指定 char 值的大写或小写形式。 |
toString(char ch) |
返回表示指定字符值的String对象 也就是一个字符的字符串。 |
转义序列
一个由反斜杠(\)引导的字符是一个转义序列,对编译器具有特殊含义。下表显示了 Java 转义序列:
转义序列
| 转义序列 | 描述 |
|---|---|
\t |
在文本中插入一个制表符。 |
\b |
在文本中插入一个退格符。 |
\n |
在文本中插入一个换行符。 |
\r |
在文本中插入一个回车符。 |
\f |
在文本中插入一个换页符。 |
\' |
在文本中插入一个单引号字符。 |
\" |
在文本中插入一个双引号字符。 |
\\ |
在文本中插入一个反斜杠字符。 |
当在打印语句中遇到转义序列时,编译器会相应地解释它。例如,如果你想在引号内放置引号,你必须使用转义序列 \" 来处理内部引号。要打印这个句子
She said "Hello!" to me.
你需要写
System.out.println("She said \"Hello!\" to me.");
字符串
在 Java 编程中广泛使用的字符串是一系列字符。在 Java 编程语言中,字符串是对象。
Java 平台提供了String类来创建和操作字符串。
创建字符串
创建字符串的最直接方法是写:
String greeting = "Hello world!";
在这种情况下,Hello world!是一个字符串字面值——代码中用双引号括起来的一系列字符。每当编译器在您的代码中遇到字符串字面值时,它会创建一个带有其值的String对象——在本例中为Hello world!。
与任何其他对象一样,您可以使用new关键字和构造函数创建String对象。String类有十三个构造函数,允许您使用不同的来源(如字符数组)提供字符串的初始值:
char[] helloArray = { 'h', 'e', 'l', 'l', 'o', '.' };
String helloString = new String(helloArray);
System.out.println(helloString);
此代码片段的最后一行显示hello。
注意:String类是不可变的,因此一旦创建了String对象,就无法更改。String类有许多方法,其中一些将在下面讨论,看起来修改了字符串。由于字符串是不可变的,这些方法实际上是创建并返回一个包含操作结果的新字符串。
字符串长度
用于获取有关对象信息的方法称为访问器方法。您可以与字符串一起使用的一个访问器方法是length()方法,它返回字符串对象中包含的字符数。执行以下两行代码后,len等于 17:
String palindrome = "Dot saw I was Tod";
int len = palindrome.length();
回文是一个对称的单词或句子——它正向和反向拼写相同,忽略大小写和标点符号。这是一个简短且低效的程序来反转一个回文字符串。它调用了String方法charAt(i),该方法返回字符串中第 i 个字符,从 0 开始计数。
public class StringDemo {
public static void main(String[] args) {
String palindrome = "Dot saw I was Tod";
int len = palindrome.length();
char[] tempCharArray = new char[len];
char[] charArray = new char[len];
// put original string in an
// array of chars
for (int i = 0; i < len; i++) {
tempCharArray[i] =
palindrome.charAt(i);
}
// reverse array of chars
for (int j = 0; j < len; j++) {
charArray[j] =
tempCharArray[len - 1 - j];
}
String reversePalindrome =
new String(charArray);
System.out.println(reversePalindrome);
}
}
运行程序会产生以下输出:
doT saw I was toD
为了实现字符串反转,程序必须将字符串转换为字符数组(第一个for循环),将数组反转为第二个数组(第二个for循环),然后再转换回字符串。String类包括一个方法,getChars(),用于将字符串或字符串的一部分转换为字符数组,以便我们可以用以下代码替换上面程序中的第一个for循环
palindrome.getChars(0, len, tempCharArray, 0);
字符串连接
String类包括一个用于连接两个字符串的方法:
string1.concat(string2);
这将返回一个新字符串,其中在字符串 1 的末尾添加了字符串 2。
您还可以使用concat()方法与字符串字面值,如:
"My name is ".concat("Rumplestiltskin");
字符串更常用地使用+运算符连接,如
"Hello," + " world" + "!"
这导致
"Hello, world!"
+运算符广泛用于print语句中。例如:
String string1 = "saw I was ";
System.out.println("Dot " + string1 + "Tod");
打印
Dot saw I was Tod
这样的连接可以是任何对象的混合。对于每个不是String的对象,都会调用它的toString()方法将其转换为String。
注意: Java 编程语言不允许文字字符串跨越多行出现在源文件中,因此您必须在多行字符串的每一行末尾使用+连接运算符。例如:
String quote =
"Now is the time for all good " +
"men to come to the aid of their country.";
使用+连接运算符在行之间断开字符串,在print语句中再次非常常见。
创建格式化字符串
您已经看到了使用printf()和format()方法打印带有格式化数字的输出。String类有一个等效的类方法format(),它返回一个String对象而不是一个PrintStream对象。
使用String的静态format()方法允许您创建一个格式化的字符串,您可以重复使用,而不是一次性的打印语句。例如,而不是
System.out.printf("The value of the float " +
"variable is %f, while " +
"the value of the " +
"integer variable is %d, " +
"and the string is %s",
floatVar, intVar, stringVar);
你可以写
String fs;
fs = String.format("The value of the float " +
"variable is %f, while " +
"the value of the " +
"integer variable is %d, " +
" and the string is %s",
floatVar, intVar, stringVar);
System.out.println(fs);
在数字和字符串之间进行转换
原文:
docs.oracle.com/javase/tutorial/java/data/converting.html
将字符串转换为数字
经常情况下,程序最终会在字符串对象中包含数值数据例如用户输入的值。
包装原始数值类型的Number子类(Byte、Integer、Double、Float、Long和Short)每个都提供一个名为valueOf的类方法,将字符串转换为该类型的对象。以下是一个示例,ValueOfDemo,从命令行获取两个字符串,将它们转换为数字,并对这些值执行算术运算:
public class ValueOfDemo {
public static void main(String[] args) {
// this program requires two
// arguments on the command line
if (args.length == 2) {
// convert strings to numbers
float a = (Float.valueOf(args[0])).floatValue();
float b = (Float.valueOf(args[1])).floatValue();
// do some arithmetic
System.out.println("a + b = " +
(a + b));
System.out.println("a - b = " +
(a - b));
System.out.println("a * b = " +
(a * b));
System.out.println("a / b = " +
(a / b));
System.out.println("a % b = " +
(a % b));
} else {
System.out.println("This program " +
"requires two command-line arguments.");
}
}
}
当您使用4.5和87.2作为命令行参数时,程序的输出如下:
a + b = 91.7
a - b = -82.7
a * b = 392.4
a / b = 0.0516055
a % b = 4.5
注意:包装原始数值类型的每个Number子类还提供一个parseXXXX()方法(例如,parseFloat()),可用于将字符串转换为原始数值。由于返回的是原始类型而不是对象,因此parseFloat()方法比valueOf()方法更直接。例如,在ValueOfDemo程序中,我们可以使用:
float a = Float.parseFloat(args[0]);
float b = Float.parseFloat(args[1]);
将数字转换为字符串
有时您需要将数字转换为字符串,因为您需要在其字符串形式上进行操作。有几种简单的方法可以将数字转换为字符串:
int i;
// Concatenate "i" with an empty string; conversion is handled for you.
String s1 = "" + i;
或者
// The valueOf class method.
String s2 = String.valueOf(i);
每个Number子类都包括一个类方法toString(),将其原始类型转换为字符串。例如:
int i;
double d;
String s3 = Integer.toString(i);
String s4 = Double.toString(d);
ToStringDemo示例使用toString方法将数字转换为字符串。然后程序使用一些字符串方法来计算小数点前后的数字位数:
public class ToStringDemo {
public static void main(String[] args) {
double d = 858.48;
String s = Double.toString(d);
int dot = s.indexOf('.');
System.out.println(dot + " digits " +
"before decimal point.");
System.out.println( (s.length() - dot - 1) +
" digits after decimal point.");
}
}
该程序的输出为:
3 digits before decimal point.
2 digits after decimal point.
操作字符串中的字符
原文:
docs.oracle.com/javase/tutorial/java/data/manipstrings.html
String类有许多方法用于检查字符串的内容,在字符串中查找字符或子字符串,更改大小写以及其他任务。
通过索引获取字符和子字符串
您可以通过调用charAt()访问器方法在字符串中获取特定索引处的字符。第一个字符的索引为 0,而最后一个字符的索引为length()-1。例如,以下代码获取字符串中索引为 9 的字符:
String anotherPalindrome = "Niagara. O roar again!";
char aChar = anotherPalindrome.charAt(9);
索引从 0 开始,因此索引为 9 的字符是'O',如下图所示:

如果要从字符串中获取多个连续字符,可以使用substring方法。substring方法有两个版本,如下表所示:
String类中的substring方法
| 方法 | 描述 |
|---|---|
String substring(int beginIndex, int endIndex) |
返回一个新的字符串,该字符串是此字符串的子字符串。子字符串从指定的beginIndex开始,延伸到索引endIndex - 1的字符。 |
String substring(int beginIndex) |
返回一个新的字符串,该字符串是此字符串的子字符串。整数参数指定第一个字符的索引。在这里,返回的子字符串延伸到原始字符串的末尾。 |
以下代码从尼亚加拉回文中获取从索引 11 到索引 15 之前的子字符串,即单词"roar":
String anotherPalindrome = "Niagara. O roar again!";
String roar = anotherPalindrome.substring(11, 15);

用于操作字符串的其他方法
以下是用于操作字符串的几种其他String方法:
String类中用于操作字符串的其他方法
| 方法 | 描述 |
|---|---|
String[] split(String regex) String[] split(String regex, int limit) |
搜索由字符串参数指定的匹配项(其中包含正则表达式)并相应地将此字符串拆分为字符串数组。可选的整数参数指定返回数组的最大大小。正则表达式在标题为"正则表达式"的课程中介绍。 |
CharSequence subSequence(int beginIndex, int endIndex) |
返回从beginIndex索引开始直到endIndex - 1的新字符序列。 |
String trim() |
返回删除前导和尾随空格的此字符串的副本。 |
String toLowerCase() String toUpperCase() |
返回转换为小写或大写的此字符串的副本。如果不需要转换,则这些方法返回原始字符串。 |
在字符串中搜索字符和子字符串
以下是一些用于在字符串中查找字符或子字符串的其他String方法。String类提供了返回特定字符或子字符串在字符串中位置的访问方法:indexOf()和lastIndexOf()。indexOf()方法从字符串的开头向前搜索,而lastIndexOf()方法从字符串的末尾向后搜索。如果未找到字符或子字符串,indexOf()和lastIndexOf()将返回-1。
String类还提供了一个搜索方法contains,如果字符串包含特定的字符序列,则返回 true。当您只需要知道字符串包含一个字符序列,但精确位置并不重要时,请使用此方法。
以下表格描述了各种字符串搜索方法。
String类中的搜索方法
| 方法 | 描述 |
|---|---|
int indexOf(int ch) int lastIndexOf(int ch) |
返回指定字符的第一个(最后一个)出现的索引。 |
int indexOf(int ch, int fromIndex) int lastIndexOf(int ch, int fromIndex) |
返回指定字符的第一个(最后一个)出现的索引,从指定索引向前(向后)搜索。 |
int indexOf(String str) int lastIndexOf(String str) |
返回指定子字符串的第一个(最后一个)出现的索引。 |
int indexOf(String str, int fromIndex) int lastIndexOf(String str, int fromIndex) |
返回指定子字符串的第一个(最后一个)出现的索引,从指定索引向前(向后)搜索。 |
boolean contains(CharSequence s) |
如果字符串包含指定的字符序列,则返回 true。 |
注意: CharSequence是一个由String类实现的接口。因此,您可以将字符串作为contains()方法的参数。
将字符和子字符串替换为字符串
String类几乎没有用于在字符串中插入字符或子字符串的方法。一般情况下,这些方法是不需要的:您可以通过将您从字符串中删除的子字符串与您要插入的子字符串连接起来创建一个新的字符串。
String类确实有四种用于替换找到的字符或子字符串的方法。它们是:
String类中用于操作字符串的方法
| 方法 | 描述 |
|---|---|
String replace(char oldChar, char newChar) |
返回一个新字符串,该字符串由将此字符串中所有旧字符的出现替换为新字符而生成。 |
String replace(CharSequence target, CharSequence replacement) |
用指定的替换序列替换此字符串中与字面目标序列匹配的每个子字符串。 |
String replaceAll(String regex, String replacement) |
用给定替换替换此字符串中与给定正则表达式匹配的每个子字符串。 |
String replaceFirst(String regex, String replacement) |
用给定的替换字符串替换与给定正则表达式匹配的此字符串的第一个子字符串。 |
一个示例
以下类,Filename,演示了使用lastIndexOf()和substring()来分离文件名的不同部分。
注意:以下Filename类中的方法没有进行任何错误检查,并假定它们的参数包含完整的目录路径和带有扩展名的文件名。如果这些方法是生产代码,它们将验证它们的参数是否正确构造。
public class Filename {
private String fullPath;
private char pathSeparator,
extensionSeparator;
public Filename(String str, char sep, char ext) {
fullPath = str;
pathSeparator = sep;
extensionSeparator = ext;
}
public String extension() {
int dot = fullPath.lastIndexOf(extensionSeparator);
return fullPath.substring(dot + 1);
}
// gets filename without extension
public String filename() {
int dot = fullPath.lastIndexOf(extensionSeparator);
int sep = fullPath.lastIndexOf(pathSeparator);
return fullPath.substring(sep + 1, dot);
}
public String path() {
int sep = fullPath.lastIndexOf(pathSeparator);
return fullPath.substring(0, sep);
}
}
这里有一个程序,FilenameDemo,它构造了一个Filename对象并调用了它的所有方法:
public class FilenameDemo {
public static void main(String[] args) {
final String FPATH = "/home/user/index.html";
Filename myHomePage = new Filename(FPATH, '/', '.');
System.out.println("Extension = " + myHomePage.extension());
System.out.println("Filename = " + myHomePage.filename());
System.out.println("Path = " + myHomePage.path());
}
}
以下是程序的输出:
Extension = html
Filename = index
Path = /home/user
如下图所示,我们的extension方法使用lastIndexOf来定位文件名中句点(.)的最后一个出现位置。然后substring使用lastIndexOf的返回值来提取文件名扩展名,即从句点到字符串末尾的子字符串。此代码假定文件名中有一个句点;如果文件名中没有句点,lastIndexOf将返回-1,而substring方法将抛出StringIndexOutOfBoundsException。

还要注意,extension方法使用dot + 1作为substring的参数。如果句点字符(.)是字符串的最后一个字符,则dot + 1等于字符串的长度,这比字符串的最大索引大 1(因为索引从 0 开始)。这是substring的合法参数,因为该方法接受一个等于但不大于字符串长度的索引,并将其解释为“字符串的结尾”。
比较字符串和字符串部分
原文:
docs.oracle.com/javase/tutorial/java/data/comparestrings.html
String类有许多用于比较字符串和字符串部分的方法。以下表格列出了这些方法。
比较字符串的方法
| 方法 | 描述 |
|---|---|
boolean endsWith(String suffix) boolean startsWith(String prefix) |
如果此字符串以指定为方法参数的子字符串结尾或以其开头,则返回true。 |
boolean startsWith(String prefix, int offset) |
考虑从索引offset开始的字符串,并返回如果以指定为参数的子字符串开头则返回true。 |
int compareTo(String anotherString) |
按字典顺序比较两个字符串。返回一个整数,指示此字符串是否大于(结果为> 0)、等于(结果为= 0)或小于(结果为< 0)参数。 |
int compareToIgnoreCase(String str) |
按字典顺序比较两个字符串,忽略大小写差异。返回一个整数,指示此字符串是否大于(结果为> 0)、等于(结果为= 0)或小于(结果为< 0)参数。 |
boolean equals(Object anObject) |
如果参数是表示与此对象相同字符序列的String对象,则返回true。 |
boolean equalsIgnoreCase(String anotherString) |
如果参数是表示与此对象相同字符序列的String对象,则返回true,忽略大小写差异。 |
boolean regionMatches(int toffset, String other, int ooffset, int len) |
测试此字符串的指定区域是否与 String 参数的指定区域匹配。区域长度为len,从此字符串的索引toffset和另一个字符串的索引ooffset开始。 |
boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) |
测试此字符串的指定区域是否与 String 参数的指定区域匹配。区域长度为len,从此字符串的索引toffset和另一个字符串的索引ooffset开始。布尔参数指示是否应忽略大小写;如果为 true,则在比较字符时忽略大小写。 |
boolean matches(String regex) |
测试此字符串是否与指定的正则表达式匹配。正则表达式在标题为“正则表达式”的课程中讨论。 |
以下程序RegionMatchesDemo使用regionMatches方法在另一个字符串中搜索字符串:
public class RegionMatchesDemo {
public static void main(String[] args) {
String searchMe = "Green Eggs and Ham";
String findMe = "Eggs";
int searchMeLength = searchMe.length();
int findMeLength = findMe.length();
boolean foundIt = false;
for (int i = 0;
i <= (searchMeLength - findMeLength);
i++) {
if (searchMe.regionMatches(i, findMe, 0, findMeLength)) {
foundIt = true;
System.out.println(searchMe.substring(i, i + findMeLength));
break;
}
}
if (!foundIt)
System.out.println("No match found.");
}
}
此程序的输出为Eggs。
该程序逐个字符遍历searchMe引用的字符串。对于每个字符,程序调用regionMatches方法来确定从当前字符开始的子字符串是否与程序正在查找的字符串匹配。
StringBuilder 类
StringBuilder 对象类似于 String 对象,不同之处在于它们可以被修改。在内部,这些对象被视为包含一系列字符的可变长度数组。在任何时候,通过方法调用可以更改序列的长度和内容。
除非字符串生成器在代码更简单(请参见本节末尾的示例程序)或性能更好方面提供优势,否则应始终使用字符串。例如,如果需要连接大量字符串,则追加到StringBuilder对象更有效率。
长度和容量
StringBuilder 类,类似于 String 类,具有返回生成器中字符序列长度的 length() 方法。
与字符串不同,每个字符串生成器还有一个容量,即已分配的字符空间数。容量由 capacity() 方法返回,始终大于或等于长度(通常大于),并且会根据需要自动扩展以容纳对字符串生成器的添加。
StringBuilder 构造函数
| 构造函数 | 描述 |
|---|---|
StringBuilder() |
创建一个容量为 16(16 个空元素)的空字符串生成器。 |
StringBuilder(CharSequence cs) |
构造一个包含与指定 CharSequence 相同字符的字符串生成器,CharSequence 后面还有额外的 16 个空元素。 |
StringBuilder(int initCapacity) |
创建一个具有指定初始容量的空字符串生成器。 |
StringBuilder(String s) |
创建一个字符串生成器,其值由指定的字符串初始化,字符串后面还有额外的 16 个空元素。 |
例如,以下代码
// creates empty builder, capacity 16
StringBuilder sb = new StringBuilder();
// adds 9 character string at beginning
sb.append("Greetings");
会生成一个长度为 9,容量为 16 的字符串生成器:

StringBuilder 类有一些与长度和容量相关的方法,而 String 类没有:
长度和容量方法
| 方法 | 描述 |
|---|---|
void setLength(int newLength) |
设置字符序列的长度。如果newLength小于length(),则字符序列中的最后字符将被截断。如果newLength大于length(),则在字符序列的末尾添加空字符。 |
void ensureCapacity(int minCapacity) |
确保容量至少等于指定的最小值。 |
一些操作(例如,append(),insert()或setLength())可以增加字符串构建器中字符序列的长度,使得结果的length()大于当前的capacity()。当这种情况发生时,容量会自动增加。
StringBuilder 操作
StringBuilder上的主要操作,这些操作在String中不可用,是append()和insert()方法,这些方法被重载以接受任何类型的数据。每个方法将其参数转换为字符串,然后将该字符串的字符附加或插入到字符串构建器中的字符序列中。append方法总是在现有字符序列的末尾添加这些字符,而insert方法在指定点添加字符。
这里是StringBuilder类的一些方法。
各种StringBuilder方法
| 方法 | 描述 |
|---|
| `StringBuilder append(boolean b) StringBuilder append(char c)
StringBuilder append(char[] str)
StringBuilder append(char[] str, int offset, int len)
StringBuilder append(double d)
StringBuilder append(float f)
StringBuilder append(int i)
StringBuilder append(long lng)
StringBuilder append(Object obj)
StringBuilder append(String s)` | 将参数附加到此字符串构建器。在附加操作发生之前,数据将被转换为字符串。 |
StringBuilder delete(int start, int end) StringBuilder deleteCharAt(int index) |
第一个方法删除StringBuilder的字符序列中从start到end-1(包括end-1)的子序列。第二个方法删除位于index处的字符。 |
|---|
| `StringBuilder insert(int offset, boolean b) StringBuilder insert(int offset, char c)
StringBuilder insert(int offset, char[] str)
StringBuilder insert(int index, char[] str, int offset, int len)
StringBuilder insert(int offset, double d)
StringBuilder insert(int offset, float f)
StringBuilder insert(int offset, int i)
StringBuilder insert(int offset, long lng)
StringBuilder insert(int offset, Object obj)
StringBuilder insert(int offset, String s)` | 将第二个参数插入到字符串构建器中。第一个整数参数表示要插入数据之前的索引。在插入操作发生之前,数据将被转换为字符串。 |
StringBuilder replace(int start, int end, String s) void setCharAt(int index, char c) |
替换此字符串构建器中指定的字符。 |
|---|---|
StringBuilder reverse() |
反转此字符串构建器中的字符序列。 |
String toString() |
返回一个包含构建器中字符序列的字符串。 |
注意: 您可以通过首先使用StringBuilder类的toString()方法将字符串构建器转换为字符串,然后使用StringBuilder(String str)构造函数将字符串转换回字符串构建器,从而在StringBuilder对象上使用任何String方法。
一个示例
在标题为“字符串”的部分列出的StringDemo程序是一个例子,如果使用StringBuilder而不是String会更有效率。
StringDemo反转了一个回文。这里再次列出它的代码:
public class StringDemo {
public static void main(String[] args) {
String palindrome = "Dot saw I was Tod";
int len = palindrome.length();
char[] tempCharArray = new char[len];
char[] charArray = new char[len];
// put original string in an
// array of chars
for (int i = 0; i < len; i++) {
tempCharArray[i] =
palindrome.charAt(i);
}
// reverse array of chars
for (int j = 0; j < len; j++) {
charArray[j] =
tempCharArray[len - 1 - j];
}
String reversePalindrome =
new String(charArray);
System.out.println(reversePalindrome);
}
}
运行程序会产生这个输出:
doT saw I was toD
为了实现字符串反转,程序将字符串转换为字符数组(第一个for循环),将数组反转为第二个数组(第二个for循环),然后再转换回字符串。
如果你将palindrome字符串转换为一个字符串生成器,你可以使用StringBuilder类中的reverse()方法。这样代码会更简单,更易于阅读:
public class StringBuilderDemo {
public static void main(String[] args) {
String palindrome = "Dot saw I was Tod";
StringBuilder sb = new StringBuilder(palindrome);
sb.reverse(); // reverse it
System.out.println(sb);
}
}
运行这个程序会产生相同的输出:
doT saw I was toD
注意,println()打印一个字符串生成器,如下所示:
System.out.println(sb);
因为sb.toString()会被隐式调用,就像在println()调用中对任何其他对象一样。
注意: 还有一个StringBuffer类,与StringBuilder类完全相同,唯一的区别是它的方法是同步的,因此是线程安全的。线程将在并发课程中讨论。


浙公网安备 33010602011771号