本章涵盖了 Java 编程语言编程的基础。

面向对象编程概念

面向对象编程概念(Object-Oriented Programming Concept)教你如何面向对象编程的核心概念:对象,消息,类和继承。

什么是对象?

对象(Object)是相关状态和行为的软件包。软件对象通常用于建模您在日常生活中发现的真实世界对象。本节讲解了一个对象中状态和行为是如何表现的,介绍了数据封装的概念,并解释了以这种方式设计软件的好处。

对象是理解面向对象技术的关键。软件对象包括状态(state)和相关行为(behavior)。对象将其状态存储在字段(某些编程语言中的变量)中,并通过方法(某些编程语言中的函数)公开其行为。方法对对象内部状态进行操作,并作为对象间通信的主要机制。隐藏内部状态并要求通过对象的方法执行所有交互被称为数据封装(data encapsulation)—— 面向对象编程的基本原理。

将代码捆绑到单独的软件对象中提供了许多好处,包括:

  • 模块化(Modularity):对象的源代码可以与其他对象的源代码独立编写和维护。一旦创建,一个对象可以很容易地传递到系统内部。
  • 信息隐藏(Information-hiding):仅通过与对象的方法进行交互,其内部实现的细节将从外部世界隐藏起来。
  • 代码重用(Code re-use):如果对象已经存在(也许由另一个软件开发人员编写),可以在程序中使用该对象。这允许专家实施/测试/调试复杂的、任务特定的对象,然后您可以信任其在自己的代码中运行。
  • 可插拔性和调试简便性(Pluggability and debugging ease):如果特定的对象变得有问题,您可以将其从应用程序中删除,并插入不同的对象作为替换。

什么是类?

类(class)是创建对象的蓝图或原型。本节定义了一个类,用于对现实世界对象的状态和行为进行建模。它有意地集中在基础上,显示一个简单的类甚至可以干净地模拟状态和行为。

什么是继承?

继承(Inheritance)为组织和构建软件提供了强大而自然的机制。本节将介绍类如何从其超类继承状态和行为,并说明如何使用 Java 编程语言提供的简单语法从另一个派生一个类。

面向对象编程允许类从其他类继承常用的状态和行为。在 Java 编程语言中,每个类都允许有一个直接的超类,每个超类具有无限数量的子类的潜力。

创建子类的语法很简单。在类声明开始时,使用extends关键字,后跟继承的类的名称:

class MountainBike extends Bicycle {

    // new fields and methods defining 
    // a mountain bike would go here

}

什么是接口?

接口(Interface)是一个类与外界的契约。当一个类实现一个接口时,它承诺提供该接口发布的行为。本节定义了一个简单的接口,并解释了实现它的任何类的必要更改。

在最常见的形式中,接口是一组具有空白体的相关方法。如果指定为接口,其形式如下:

interface Bicycle {

    //  wheel revolutions per minute
    void changeCadence(int newValue);

    void changeGear(int newValue);

    void speedUp(int increment);

    void applyBrakes(int decrement);
}

为了实现这个接口,你的类名称会改变,你可以在类声明中使用implements关键字:

class ACMEBicycle implements Bicycle {

    int cadence = 0;
    int speed = 0;
    int gear = 1;

   // The compiler will now require that methods
   // changeCadence, changeGear, speedUp, and applyBrakes
   // all be implemented. Compilation will fail if those
   // methods are missing from this class.

    void changeCadence(int newValue) {
         cadence = newValue;
    }

    void changeGear(int newValue) {
         gear = newValue;
    }

    void speedUp(int increment) {
         speed = speed + increment;   
    }

    void applyBrakes(int decrement) {
         speed = speed - decrement;
    }

    void printStates() {
         System.out.println("cadence:" +
             cadence + " speed:" + 
             speed + " gear:" + gear);
    }
}

实现一个接口允许一个类对它承诺提供的行为变得更正式。接口形成了类和外部世界之间的合同,并且该编译器在构建时执行该合同。如果您的类声称实现了一个接口,则该接口定义的所有方法在其成功编译之前必须出现在其源代码中。

什么是包?

包(Package)是用于以逻辑方式组织类和接口的命名空间。将代码放入程序包可以使大型软件项目更易于管理。本节介绍了为什么这是有用的,并将介绍给 Java 平台提供的应用程序编程接口(API)。

一个包是组合一组相关类和接口的命名空间。由于用 Java 编程语言编写的软件可以由数百或数千个单独的类组成,通过将相关的类和接口放入包中来保持组织是有意义的。

Java 平台提供了一个适用于您自己的应用程序的庞大的类库(一组包)。这个库被称为「应用程序编程接口」或简称为「API」。其包代表了大多数与通用编程相关的任务。例如,String 对象包含字符串的状态和行为; File 对象允许程序员轻松创建,删除,检查,比较或修改文件系统上的文件; Socket 对象允许创建和使用网络套接字;各种 GUI 对象控制按钮和复选框以及与图形用户界面相关的任何内容。有几千个类可供选择。这使您、程序员能够专注于特定应用程序的设计,而不是工作于运行所需的基础架构。

Java Platform API 规范包含 Java SE 平台提供的所有软件包,接口,类,字段和方法的完整列表。作为程序员,它将成为您最重要的参考文献文档。

语言基础

语言基础(Language Basic)描述了语言的传统特征,包括变量,数组,数据类型,运算符和控制流。

变量

您已经知道对象将其状态存储在字段中。 然而,Java 编程语言也使用术语「变量(variable)」。 本节讨论此关系,以及变量命名规则和约定,基本数据类型(原始类型,字符串和数组),默认值和字面量(literal)。

Java编程语言定义了以下类型的变量:

  • 实例变量(非静态字段)「Instance Variables (Non-Static Fields)」
    从技术上讲,对象将其各自的状态存储在「非静态字段」中,即不使用static关键字声明的字段。非静态字段也称为实例变量(instance variables),因为它们的值对于每个类的实例是唯一的(换句话说就是每个对象)。
  • 类变量(静态字段)「Class Variables (Static Fields)」
    类变量是使用static修饰符(modifier)声明的任何字段;这告诉编译器,正好存在该变量的一个副本,而不管类被实例化了多少次。代码static int numGears = 6;将创建一个静态字段。此外,可以添加关键字final,以表示该静态字段不会改变。
  • 局部变量「Local Variables」
    类似于对象如何将其状态存储在字段中,方法通常会将其临时状态存储在局部变量中。声明局部变量的语法类似于声明一个字段(例如,int count = 0;)。没有特殊的关键字指定一个变量作为本地的;这完全取决于声明变量的位置 - 其在方法的开始和结束大括号之间。因此,局部变量只对它们被声明的方法可见;他们不能从类的其余部分访问。
  • 参数「Parameters」
    在类的方法public static void main(String[] args)args变量是这个方法的参数。重要的是要记住,参数总是被分类为「变量」而不是「字段」。这也适用于其他接受参数的构造器(如构造函数和异常处理程序)。

命名(Naming)

每个编程语言都有自己的一套规则和约定,您可以使用这些类型的名称,Java 编程语言也没有什么不同。用于命名变量的规则和约定可以归纳如下:

  • 变量名称区分大小写(case-sensitive)。变量的名称可以是任何合法标识符 - 无限长度序列的Unicode字母和数字,以字母,美元符号「$」或下划线字符「」开头。然而,惯例是始终使用一个字母开始变量名,而不是「$」或「」。此外,按照惯例,美元符号字符根本就不会被使用。您可能会发现某些情况下,自动生成的名称将包含美元符号,但您的变量名称应始终避免使用它。下划线字符存在类似的约定;而以「_」开始的变量名称在技术上是合法的,这种做法是不鼓励的。空白字符(White space)是不允许的。
  • 后续字符可能是字母,数字,美元符号或下划线字符。公约(和常识)也适用于此规则。为变量选择名称时,请使用完整的单词而不是隐含的缩写(cryptic abbreviations)。这样做会使您的代码更容易阅读和理解。在许多情况下,它也会使您的代码自我文档化;例如,字段命名cadencespeed比缩写(abbreviated)版本(如sc)更加直观(intuitive)。还要记住,您选择的名称不能是关键字(keyword)或保留字(reserved word)。
  • 如果您选择的名称只包含一个单词,请用所有小写字母拼写该单词。如果它由多个单词组成,则大写每个后续单词的第一个字母。名称gearRatiocurrentGear是这个惯例的主要例子。如果您的变量存储一个常量值,例如static final int NUM_GEARS = 6,则约定将略有更改,将每个字母大写,并将后续单词与下划线字符分隔开。按照惯例,下划线字符从未在其他地方使用。

原始(Primitive)数据类型

Java 编程语言是静态类型的(statically-typed),这意味着所有变量必须先被声明才能使用。这包括说明变量的类型和名称。

变量的数据类型决定它可能包含的值,以及可能对其执行的操作。除了int之外,Java 编程语言还支持七种其他原始数据类型。原始类型由语言预定义,并由保留关键字命名。原始值不与其他原始值共享状态。 Java 编程语言支持的八个基本数据类型有:

  • byte:字节数据类型是 8 位有符号二进制补码整数(two’s complement integer)。它的最小值为 -128,最大值为 127(含)。字节数据类型可以用于在大型数组中节省内存,实际上内存节省很重要。它们也可以用于代替int,其限制有助于澄清您的代码;变量范围有限的事实可以作为一种文档形式。

  • short:short数据类型是一个 16 位有符号二进制补码整数。最小值为 -32,768,最大值为 32,767(含)。与字节一样,适用相同的指导原则:在实际上节省内存很重要的情况下,您可以使用short在大型数组中节省内存。

  • int:默认情况下,int 数据类型是一个 32 位有符号二进制补码整数,最小值为 -231,最大值为 231-1。在 Java SE 8 及更高版本中,可以使用int数据类型来表示无符号的 32 位整数,其最小值为 0,最大值为 232-1。使用 Integer 类将 int 数据类型用作无符号整数。有关详细信息,请参阅「数字类」一节。诸如 compareUnsigneddivideUnsigned 等的静态方法已经添加到 Integer 类中,以支持无符号整数的算术运算。

  • long:long 数据类型是 64 位二进制补码整数。有符号的long的最小值为-263,最大值为 263-1。在 Java SE 8 及更高版本中,您可以使用 long 数据类型来表示无符号的 64 位长,最小值为 0,最大值为 264-1。当您需要的范围宽于 int 所提供的值时,请使用此数据类型。 Long 类还包含 compareUnsigneddivideUnsigned 等方法,以支持 unsigned long 的算术运算。

  • float:float 数据类型是单精度(single-precision) 32 位 IEEE 754 浮点数。与 byteshort 的建议一样,如果需要将浮点数的大型数组保存在内存中,请使用浮点数(而不是双精度值)。不应将此数据类型用于精确值,例如货币。为此,您将需要使用 java.math.BigDecimal 类代替。数字和字符串涵盖了 Java 平台提供的 BigDecimal 和其他有用的类。

  • double:double 数据类型是双精度(double-precision) 64 位 IEEE 754 浮点数。对于十进制值,该数据类型通常是默认选择。如上所述,这种数据类型不应该用于精确的值,例如货币。

  • boolean:布尔数据类型只有两个可能的值:truefalse。将此数据类型用于跟踪真/假条件的简单标志。该数据类型表示一位信息,但其「大小(size)」不是精确定义的。

  • char:char 数据类型是一个 16 位 Unicode 字符。它的最小值为「\u0000」(或0),最大值为「\uffff」(或包含 65,535)。

除了上面列出的八个基本数据类型之外,Java 编程语言还通过 java.lang.String 类提供对字符串的特殊支持。双引号封闭的字符串将自动创建一个新的 String 对象;例如,String s = "this is a string"; String 对象是*不可变(immutable)*的,这意味着一旦创建,它们的值不能被更改。 String 类在技术上不是原始数据类型,但考虑到语言给予的特殊支持,您可能会倾向于将其视为这样。

默认值

声明字段时不一定要分配一个值。 声明但未初始化的字段将被编译器设置为合理的默认值。 一般来说,这个默认值将为零或null,具体取决于数据类型。 然而,依赖于这样的默认值通常被认为是坏的编程风格。

以下图表总结了上述数据类型的默认值。

数据类型 默认值(字段)
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char ‘\ u0000’.
String(或任何对象) null
boolean false

局部变量略有不同; 编译器从不将默认值分配给未初始化的局部变量。 如果您不能初始化生命的局部变量,请确保在尝试使用该值之前为其分配一个值。 访问未初始化的局部变量将导致编译时错误。

字面量(Literals)

您可能已经注意到,在初始化原始类型的变量时,不会使用 new 关键字。 原始类型是语言中内置的特殊数据类型; 它们不是从类创建的对象。 *字面值(literal)*是固定值的源代码表示; 文字直接展现在您的代码中,而不需要计算。 如下所示,可以为原始类型的变量分配字面值:

boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;
整数字面量

如果以字母 Ll 结尾,则整数字面量是 long 类型;否则为 int 类型。建议您使用大写字母 L,因为小写字母 l 与数字 1 难以区分。

整数类型 byteshortintlong 的值可以从 int 字面量创建。类型 long 的值超过 int 的范围可以从 long 字面值创建。整数字面值可以由这些数字系统表示:

  • 十进制(Decimal):基数 10,其数字由数字 0 到 9 组成。
  • 十六进制(Hexadecimal):基数 16,其数字由数字 0 到 9 和字母 A 到 F 组成
  • 二进制(Binary):基数 2,其数字由数字 0 和 1 组成(您可以在 Java SE 7 及更高版本中创建二进制字面量)

对于通用编程,十进制系统可能是您将要使用的唯一数字系统。但是,如果需要使用另一个数字系统,以下示例显示正确的语法。前缀0x表示十六进制,0b表示二进制:

// The number 26, in decimal
int decVal = 26;
//  The number 26, in hexadecimal
int hexVal = 0x1a;
// The number 26, in binary
int binVal = 0b11010;
浮点字面量

如果以字母Ff结尾,则浮点(floating-point)字面值的类型为float; 否则其类型是double,它可以可选地以字母Dd结尾。

浮点类型(floatdouble)也可以使用Ee(用于科学记数法),Ff(32位浮点字面值)和Dd(64位 double 字面值;这是默认值,约定省略)来表示。

double d1 = 123.4;
// same value as d1, but in scientific notation
double d2 = 1.234e2;
float f1  = 123.4f;
字符和字符串字面值

charString 类型的字面量可能包含任何 Unicode(UTF-16)字符。如果您的编辑器和文件系统允许,您可以直接在代码中使用这些字符。如果不允许,您可以使用「Unicode转义」,例如 「\u0108」(带有音调符号 [circumflex] 的大写C)。对于字符(char)字面值始终使用「单引号」,字符串(String)使用「双引号」。 Unicode转义序列可能在程序中的其他位置(例如,字段名称)中使用,而不仅仅是charString字面值。

Java编程语言还支持一些charString字面值的特殊的转义序列:\b(退格,backspace),\t(水平制表,tab),\n(换行,line feed),\f(换页,form feed),\r(回车,carriage return) \“(双引号,double quote),\'(单引号,single quote)和\\(反斜杠,backslash)。

还有一个特殊的 null 字面值,可以用作任何引用类型的值。除原始类型的变量外,可以将 null 分配给任何变量。除了测试其存在之外,您可能很少使用空值(null)。因此,通常在程序中使用null作为标记来指示某些对象不可用。

最后,还有一种特殊的字面量类型,称为类字面量(class literal),通过采用类型名称并附加「.class」形成;例如String.class。这是指代表类型本身的对象(类型为Class)。

在数字字面量中使用下划线字符

在 Java SE 7 及更高版本中,任何数量的下划线字符(_)都可以出现在数字字面值的数字之间的任何位置。例如,这个功能可以让你在数字字面值中分隔数字组,这可以提高代码的可读性。

例如,如果您的代码包含多个数字的数字,则可以使用下划线字符将三位数字分隔,与使用逗号或空格的标点符号(punctuation mark)作为分隔符类似。

以下示例显示了可以在数字文字中使用下划线的其他方法:

long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi =  3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;

您只能在数字之间放置下划线;您不能在以下地方放置下划线:

  • 在数字的开头或结尾
  • 与浮点数字面值的小数点(decimal point)相邻
  • FL后缀之前
  • 在需要一串数字字符串(a string of digits)的位置

以下示例说明了数字字面值中有效和无效的下划线展示位置(突出显示):

// Invalid: cannot put underscores
// adjacent to a decimal point
float pi1 = 3_.1415F;
// Invalid: cannot put underscores 
// adjacent to a decimal point
float pi2 = 3._1415F;
// Invalid: cannot put underscores 
// prior to an L suffix
long socialSecurityNumber1 = 999_99_9999_L;

// OK (decimal literal)
int x1 = 5_2;
// Invalid: cannot put underscores
// At the end of a literal
int x2 = 52_;
// OK (decimal literal)
int x3 = 5_______2;

// Invalid: cannot put underscores
// in the 0x radix prefix
int x4 = 0_x52;
// Invalid: cannot put underscores
// at the beginning of a number
int x5 = 0x_52;
// OK (hexadecimal literal)
int x6 = 0x5_2; 
// Invalid: cannot put underscores
// at the end of a number
int x7 = 0x52_;

数组

数组(array)是一个容器对象,它保存单个类型的固定数量的值。 当创建数组时,设置数组的长度。 创建后,其长度是固定的。

数组中的每个条目称为元素(element),每个元素都通过其数字索引访问。编号从0开始。

声明一个变量来引用数组

上述程序使用以下代码行声明一个数组(名为anArray):

// declares an array of integers
int[] anArray;

像其他类型的变量的声明一样,数组声明有两个组件:数组的类型和数组的名称。 数组的类型被写为 type[],其中 type 是包含元素的数据类型; 括号是表示该变量保存数组的特殊符号。 数组的大小不是其类型的一部分(这就是为什么括号为空)。 数组的名称可以是任何您想要的名称,只要它遵循以前在命名部分中讨论的规则和约定。 与其他类型的变量一样,声明实际上并不创建数组; 它只是告诉编译器这个变量将保存一个指定类型的数组。

类似地,您可以声明其他类型的数组:

byte[] anArrayOfBytes;
short[] anArrayOfShorts;
long[] anArrayOfLongs;
float[] anArrayOfFloats;
double[] anArrayOfDoubles;
boolean[] anArrayOfBooleans;
char[] anArrayOfChars;
String[] anArrayOfStrings;

您也可以在数组名称后放置括号:

// this form is discouraged
float anArrayOfFloats[];

但是,约定不鼓励这种形式; 括号标识数组类型,并应显示类型名称(type designation)。

创建,初始化和访问数组

创建数组的一种方法是使用 new 运算符。 下面语句为 10 个整数元素分配一个具有足够内存的数组,并将数组分配给 anArray 变量。

// create an array of integers
anArray = new int[10];

如果缺少此语句,则编译器会打印如下所示的错误,编译失败:

ArrayDemo.java:4: Variable anArray may not have been initialized.

接下来的几行将值分配给数组的每个元素:

anArray[0] = 100; // initialize first element
anArray[1] = 200; // initialize second element
anArray[2] = 300; // and so forth

每个数组元素都通过其数字索引访问:

System.out.println("Element 1 at index 0: " + anArray[0]);
System.out.println("Element 2 at index 1: " + anArray[1]);
System.out.println("Element 3 at index 2: " + anArray[2]);

或者,您可以使用快捷方式语法来创建和初始化数组:

int[] anArray = { 
    100, 200, 300,
    400, 500, 600, 
    700, 800, 900, 1000
};

这里,数组的长度由大括号之间提供的且由逗号分隔的值的数量确定。

您还可以通过使用两个或多个括号(例如 String[][] 名称来声明数组的数组(也称为多维 [multidimensional] 数组)。因此,每个元素必须由相应数量的索引值访问。

在 Java 编程语言中,多维数组是一个数组,其组件自身也是数组。 这与 C 或 Fortran 中的数组不同。 这样做的结果是允许行的长度变化,如下面的 MultiDimArrayDemo 程序所示:

class MultiDimArrayDemo {
    public static void main(String[] args) {
        String[][] names = {
            {"Mr. ", "Mrs. ", "Ms. "},
            {"Smith", "Jones"}
        };
        // Mr. Smith
        System.out.println(names[0][0] + names[1][0]);
        // Ms. Jones
        System.out.println(names[0][2] + names[1][1]);
    }
}

最后,您可以使用内建(built-in)的 length 属性来确定任何数组的大小。 以下代码将数组的大小打印到标准输出:

System.out.println(anArray.length);

复制数组

System 类具有一个 arraycopy 方法,您可以使用它来有效地将数据从一个数组复制到另一个数组:

public static void arraycopy(Object src, int srcPos,
                             Object dest, int destPos, int length)

两个 Object 参数指定要复制的数组和要复制到的数组。三个 int 参数指定源数组中的起始位置,目标数组中的起始位置和要复制的数组元素的数量。

如下示例:

class ArrayCopyDemo {
    public static void main(String[] args) {
        char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e',
			    'i', 'n', 'a', 't', 'e', 'd' };
        char[] copyTo = new char[7];

        System.arraycopy(copyFrom, 2, copyTo, 0, 7);
        System.out.println(new String(copyTo));
    }
}

数组操作

数组是编程中使用的强大而有用的概念。 Java SE 提供了执行与数组相关的一些最常见操作的方法。

为了方便起见,Java SE 在 java.util.Arrays 类中提供了几种执行数组操作(常用任务,如复制,排序和搜索数组)的方法。例如,可以将上一个示例修改为使用 java.util.Arrays 类的 copyOfRange 方法,您可以在 ArrayCopyOfDemo 示例中看到。不同的是,使用copyOfRange方法不需要在调用方法之前创建目标数组,因为方法返回了目标数组:

class ArrayCopyOfDemo {
    public static void main(String[] args) {
        
        char[] copyFrom = {'d', 'e', 'c', 'a', 'f', 'f', 'e',
            'i', 'n', 'a', 't', 'e', 'd'};
            
        char[] copyTo = java.util.Arrays.copyOfRange(copyFrom, 2, 9);
        
        System.out.println(new String(copyTo));
    }
}

copyOfRange 方法的第二个参数是要复制的范围的初始索引,第三个参数是要复制的范围的最终索引。

java.util.Arrays 类中由方法提供的其他一些有用的操作是:

  • 搜索数组里的特定值以获取其放置的索引(binarySearch 方法)。
  • 比较两个数组以确定它们是否相等(equals 方法)。
  • 填充一个数组以在每个索引处放置一个特定的值(fill 方法)。
  • 将数组排列成升序。这可以使用 sort 方法连续完成,或使用 Java SE 8 中引入的 parallelSort 方法同时完成。在多处理器系统上并行排序大型数组比顺序排序更快。

操作符

本节介绍 Java 编程语言的运算符(Operator)。 它首先提供最常用的运算符,而不太常用的运算符在最后介绍。

运算符是对一个,两个或三个操作数(operand)执行特定操作,然后返回一个结果的特殊符号。

运算符优先级自上而下依次降低,最上面的优先级最高;同一行的运算符优先级相同。具有较高优先级的运算符在具有较低优先级的运算符之前进行计算。除赋值运算符之外,所有的二进制运算符从左到右进行计算;分配运算符从左到右进行计算。

Operators Precedence
postfix expr++ expr–
unary ++expr –expr +expr -expr ~ !
multiplicative * / %
additive + -
shift « » »>
relational < > <= >= instanceof
equality == !=
bitwise AND &
bitwise exclusive OR ^
bitwise inclusive OR |
logical AND &&
logical OR ||
ternary ? :
assignment = += -= *= /= %= &= ^= |= «= »= »>=

赋值、算术和一元运算符

简单的赋值运算符

最常见的运算符之一是简单的赋值运算符「=」,它将其右侧的值分配给左侧的操作数:

int cadence = 0;
int speed = 0;
int gear = 1;

此操作符也可用于对象赋值给对象引用(object references)。

算术运算符

Java 编程语言提供了执行加法(addition)、减法(subtraction)、乘法(multiplication)和除法(division)的操作符。

运算符 说明
  • | 加法运算符(也用于字符串连接)
    
  • | 减法运算符
    
  • | 乘法运算符
    

/ | 除法运算符 % | 求余(Remainder)运算符

一元运算符

一元(unary)运算符只需要一个操作数; 它们执行各种操作,例如将值递增/递减,否定表达式或反转布尔值。

运算符 说明
  • | 一元加运算符;表示正值(否则数字为正数)
    
  • | 一元减运算符;否定一个表达式
    

++ | 递增运算符;将值递增 1 – | 递减运算符;将值减 1 ! | 逻辑互补运算符;反转布尔值的值

相等、关系和条件运算符

相等和关系运算符

相等和关系运算符确定一个操作数是否大于、小于、等于或不等于另一个操作数。

运算符 说明
== 等于
!= 不等于

| 大于 = | 大于等于 < | 小于 <= | 小于等于

条件运算符

&&|| 运算符对两个布尔表达式执行条件与(Conditional-AND)和条件或(Conditional-OR)运算。 这些操作符表现出「短路(short-circuiting)」的行为,这意味着只有在需要时才对第二个操作数进行评估。

运算符 说明
&& 条件与
|| 条件或

另一个条件运算符是 ?:,可以被认为是 if-then-else 语句的简写。该运算符也称为三元运算符(ternary operator),因为它使用三个操作数。

类型比较运算符 instanceof

instanceof 运算符将对象与指定的类型进行比较。您可以使用它来测试对象是否是类的实例、子类的实例或实现特定接口的类实例。

示例如下:

class InstanceofDemo {
    public static void main(String[] args) {

        Parent obj1 = new Parent();
        Parent obj2 = new Child();

        System.out.println("obj1 instanceof Parent: "
            + (obj1 instanceof Parent));
        System.out.println("obj1 instanceof Child: "
            + (obj1 instanceof Child));
        System.out.println("obj1 instanceof MyInterface: "
            + (obj1 instanceof MyInterface));
        System.out.println("obj2 instanceof Parent: "
            + (obj2 instanceof Parent));
        System.out.println("obj2 instanceof Child: "
            + (obj2 instanceof Child));
        System.out.println("obj2 instanceof MyInterface: "
            + (obj2 instanceof MyInterface));
    }
}

class Parent {}
class Child extends Parent implements MyInterface {}
interface MyInterface {}

请记住,当使用 instanceof 运算符时,null 不是任何东西的实例。

位和位移操作符

Java 编程语言还提供对整型类型执行按位(bitwise)和位移(bit shift)操作的操作符。

一元位补码(unary bitwise complement)运算符 反转位模式;它可以应用于任何必须的类型(the integral types),使得「0」为「1」,每「1」为「0」。例如,一个字节包含8位;将该运算符应用于位模式为「00000000」的值将其模式更改为「11111111」。

带符号的左移位运算符<<将位模式向左移动,而带符号的右移位运算符>>将位模式向右移位。由左手操作数给出位模式,并通过右手操作数给出移位位数。无符号右移操作符>>>将零移动到最左侧的位置,而>>后的最左侧位置取决于符号扩展名。

按位&运算符执行按位与(bitwise AND)运算。

按位^运算符执行按位异或(bitwise exclusive OR)运算。

按位|运算符执行按位或(a bitwise inclusive OR)运算。

表达式、语句和块

运算符可用于构建表达式,计算值; 表达式(Expression)是语句(Statement)的核心组成部分; 语句可以分组成块(Block)。

表达式

表达式是由变量、运算符和方法调用组成的结构,它们根据语言的语法构造,并计算为单个值。 您已经看到了表达式的例子,如下图所示:

int cadence = 0;
anArray[0] = 100;
System.out.println(“Element 1 at index 0: " + anArray[0]);

int result = 1 + 2; // result is now 3
if (value1 == value2)
     System.out.println(“value1 == value2”);

表达式返回的值的数据类型取决于表达式中使用的元素。

Java 编程语言允许您从各种较小的表达式构造复合表达式,只要表达式中一部分所需的数据类型与其他表达式的数据类型相匹配。 以下是复合表达式的示例:

1 * 2 * 3

语句

语句大致相当于自然语言中的句子。 一个语句形成一个完整的执行单位。 以下类型的表达式可以通过用分号(;)终止表达式来形成语句。

  • 赋值表达式
  • 任何使用 ++ 或 –
  • 方法调用(Method invocations)
  • 对象创建表达式

这样的语句叫做表达式语句。 以下是表达式语句的一些示例:

// assignment statement
aValue = 8933.234;
// increment statement
aValue++;
// method invocation statement
System.out.println("Hello World!");
// object creation statement
Bicycle myBike = new Bicycle();

除表达式语句外,还有另外两种语句:声明语句(declaration statements)和控制流语句(control flow statements)。 声明语句声明一个变量。 你已经看到很多声明语句的例子:

// declaration statement
double aValue = 8933.234;

最后,控制流程语句调节语句执行的顺序。

块是平衡大括号之间的零个或多个语句的组,可以在允许单个语句的任何地方使用。 以下示例BlockDemo说明了块的使用:

class BlockDemo {
     public static void main(String[] args) {
          boolean condition = true;
          if (condition) { // begin block 1
               System.out.println("Condition is true.");
          } // end block one
          else { // begin block 2
               System.out.println("Condition is false.");
          } // end block 2
     }
}

控制流语句

本节介绍 Java 编程语言支持的控制流语句(Control Flow Statements)。 它涵盖了决策(decisions-making),循环(looping)和分支语句(branching statements),使您的程序能够有条件地执行特定的代码块。

源文件中的语句通常按照它们显示的顺序从上到下执行。 然而,控制流程语句通过使用决策、循环和分支来分解执行流程,使程序有条件地执行特定的代码块。

if-then 和 if-then-else 语句

if-then 语句

if-then 语句是所有控制流语句中最基本的。 它告诉程序只有在特定测试评估为true时执行某段代码。

void applyBrakes() {
    // the "if" clause: bicycle must be moving
    if (isMoving){ 
        // the "then" clause: decrease current speed
        currentSpeed--;
    }
}

另外,只要「then」子句只包含一个语句,打开和关闭大括号是可选的:

void applyBrakes() {
    // same as above, but without braces 
    if (isMoving)
        currentSpeed--;
}

决定何时忽略大括号是个人品味的问题。 省略它们可以使代码更脆弱。 如果第二个语句稍后添加到「then」子句中,常见的错误是忘记添加新的必需大括号。 编译器无法捕获这种错误; 你会得到错误的结果。

if-then-else 语句

if-then-else 语句在「if」子句求值为false时提供了一个辅助的执行路径。

void applyBrakes() {
    if (isMoving) {
        currentSpeed--;
    } else {
        System.err.println("The bicycle has already stopped!");
    } 
}

switch 语句

switch语句可以有多个可能的执行路径。switch可以使用byte、short、char和int原语数据类型。它还可以使用枚举类型、String 类和一些包含某些原始类型的特殊类:Character、Byte、Short 和 Integer。

public class SwitchDemo {
    public static void main(String[] args) {

        int month = 8;
        String monthString;
        switch (month) {
            case 1:  monthString = "January";
                     break;
            case 2:  monthString = "February";
                     break;
            case 3:  monthString = "March";
                     break;
            case 4:  monthString = "April";
                     break;
            case 5:  monthString = "May";
                     break;
            case 6:  monthString = "June";
                     break;
            case 7:  monthString = "July";
                     break;
            case 8:  monthString = "August";
                     break;
            case 9:  monthString = "September";
                     break;
            case 10: monthString = "October";
                     break;
            case 11: monthString = "November";
                     break;
            case 12: monthString = "December";
                     break;
            default: monthString = "Invalid month";
                     break;
        }
        System.out.println(monthString);
    }
}

switch 语句的主体称为 switch 块。switch 块中的语句可以标注一个或多个 case 或 default 标签。switch 语句评估其表达式,然后执行匹配的 case 标签后的所有语句。

另一个兴趣点是 break 语句。每个 break 语句终止封闭的 switch 语句。控制流程继续 switch 块后面的第一个语句。 break语句是必需的,因为没有它们,switch 块中的语句会一直执行(fall through):在匹配的 case 标签之后的所有语句都按顺序执行,而不管后续表达式的 case 标签,直到遇到 break 语句。

从技术上讲,最终的 break 是不需要的,因为流程不在 switch 语句中。 建议使用 break,以便修改代码更容易,更少出错。 default 部分处理由 case 部分未明确处理的所有值。

在 switch 语句中使用 String

在 Java SE 7 及更高版本中,您可以在 switch 语句的表达式中使用 String 对象。

switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            default: 
                monthNumber = 0;
                break;
        }

        return monthNumber;
    }

while 和 do-while 语句

while 语句在特定条件为真时持续执行一组语句。 其语法可以表示为:

while (expression) {
     statement(s)
}

while语句评估表达式,它必须返回一个布尔值。 如果表达式计算结果为true,则while语句将执行while块中的语句。 while语句继续测试表达式并执行其块,直到表达式计算为false。

您可以使用while语句实现无限循环,如下所示:

while (true){
    // your code goes here
}

Java 编程语言还提供了一个 do-while 语句,可以表示如下:

do {
     statement(s)
} while (expression);

do-while 和 while 之间的区别是 do-while 在循环的底部而不是顶部来评估其表达式。 因此,do 块中的语句总是至少执行一次。

for 语句

for 语句提供了一种在一系列值上迭代(iterate)的紧凑方式。 程序员通常将其称为「for 循环」,因为它是一种反复循环直到满足特定条件的方式。 for 语句的一般形式可以表示如下:

for (initialization; termination;
     increment) {
    statement(s)
}

当使用此版本的 for 语句时,请记住:

  • 初始化表达式初始化循环; 它在循环开始时将执行一次。
  • 当终止表达式计算结果为 false 时,循环终止。
  • 每次迭代循环后调用增量表达式; 该调用完全可以给这个表达式增加或减少一个值。

for循环的三个表达式是可选的; 可以创建无限循环,如下所示:

// infinite loop
for ( ; ; ) {
    
    // your code goes here
}

for 语句还有另一种形式,为迭代集合(Collections)和数组(arrays)而设计。这种形式有时被称为增强型 for 语句,可用于使您的循环更加紧凑和易于阅读。

int[] numbers = {1,2,3,4,5,6,7,8,9,10};

class EnhancedForDemo {
    public static void main(String[] args){
         int[] numbers = 
             {1,2,3,4,5,6,7,8,9,10};
         for (int item : numbers) {
             System.out.println("Count is: " + item);
         }
    }
}

我们建议尽可能使用这种形式的for语句,而不是一般形式。

分支语句

break 语句

break语句有两种形式:有标签(labeled)和无标签(unlabeled)。您还可以使用无标签的 break 来终止 for、while 或 do-while 循环。

for (i = 0; i < arrayOfInts.length; i++) {
    if (arrayOfInts[i] == searchfor) {
        foundIt = true;
        break;
    }
}

无标签的 break 语句终止最内层(innermost)的 switch、for、while 或 do-while 语句,但有标签的 break 终止外部语句(the outer statement)。

search:
    for (i = 0; i < arrayOfInts.length; i++) {
        for (j = 0; j < arrayOfInts[i].length;
             j++) {
            if (arrayOfInts[i][j] == searchfor) {
                foundIt = true;
                break search;
            }
        }
    }

break 语句终止有标签的语句; 它不会将控制流转移到标签上。 控制流程被立即转移到标签(终止)语句之后的语句。

continue 语句

continue 语句跳过for、while 或 do-while 循环的当前迭代。无标签的形式跳到最内循环的正文的末尾,并评估控制循环的布尔表达式。

for (int i = 0; i < max; i++) {
    // interested only in p's
    if (searchMe.charAt(i) != 'p')
        continue;

    // process p's
    numPs++;
}

有标签的 continue 语句跳过标记有给定标签的外部循环的当前迭代。

test:
    for (int i = 0; i <= max; i++) {
        int n = substring.length();
        int j = i;
        int k = 0;
        while (n-- != 0) {
            if (searchMe.charAt(j++) != substring.charAt(k++)) {
                continue test;
            }
        }
        foundIt = true;
            break test;
    }

return 语句

return 语句从当前方法退出,并且控制流程返回到调用该方法的位置。 return 语句有两种形式:一种返回一个值,一个没有返回一个值。 要返回值,只需将return(或计算值的表达式)放在 return 关键字之后即可。

return ++count;

返回值的数据类型必须与方法声明的返回值的类型相匹配。 当一个方法被声明为 void 时,使用不返回值的返回形式。

return;

参考

  1. Learning the Java Language