一、初识Java
1.1 什么是Java
Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程式员以优雅的思维方式进行复杂的编程。
Java具有简单性、面向对象、分散式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程式、Web应用程式、分散式系统和嵌入式系统应用程式等。
1.2 Java的特点
简单来说,Java具有以下特点:
-
简单性
Java看起来设计得很像C++,但为了使语言更简洁和易于学习,设计者们去掉了C++中很少被使用的许多特性。例如,Java不支持goto语句,而是提供了break和continue语句以及异常处理机制。此外,Java还删除了C++中的运算符重载和多继承特性,以及主文件和预处理器。由于Java中没有结构体,数组和字符串都是对象,因此不需要使用指针。Java能够自动处理对象的引用和间接引用,实现自动的垃圾回收,使开发人员无需担心内存管理问题,能够更多地投入时间和精力进行开发。 -
面向对象
Java是一种面向对象的语言。对于程序员来说,这意味着要关注数据和操作数据的方法(方法),而不是严格按照过程思考。在面向对象的系统中,类是数据和操作数据的方法的集合。数据和方法一起描述了对象的状态和行为。每个对象都是其状态和行为的封装。类按照一定的层次结构排列,使得子类可以从超类继承行为。在这个类的层次结构中有一个根类,它具有通用行为。Java程序是使用类来组织的。 -
分布性
Java被设计成支持在网络上应用,它是一种分布式语言。Java既支持各种层次的网络连接,又通过Socket类支持可靠的流式网络连接,因此用户可以创建分布式的客户端和服务器。
网络成为软件应用的分布载体。Java程序只需要编写一次,就可以在各个平台上运行。
- 编译和解释性
Java编译程序生成字节码(byte-code),而不是通常的机器码。Java字节码提供了与体系结构无关的目标文件格式,代码被设计成可以有效地在多个平台上传输。Java程序可以在任何实现了Java解释程序和运行时系统的系统上运行。
在解释性环境中,程序开发的标准的“链接”阶段大大减少了。如果说Java还有一个链接阶段,那只是将新类加载到环境中的过程,它是增量式、轻量级的过程。因此,Java支持快速原型和容易试验,这与传统的耗时的“编译、链接和测试”形成了鲜明对比的精巧的开发过程。
- 稳健性
Java最初被设计为编写消费类家用电子产品软件的语言,因此它是被设计成编写高可靠性和稳健性软件的。Java消除了一些编程错误,使得使用它编写可靠的软件相当容易。
其稳健性体现在以下方面:
- Java是一种强类型语言,它允许扩展编译时检查潜在的类型不匹配问题的功能。Java要求显式声明方法,不支持C风格的隐式声明。这些严格的要求确保编译器能够捕捉调用错误,从而产生更可靠的程序。
- 在可靠性方面,Java的存储模型是最重要的改进之一。Java不支持指针,从而消除了重写存储和误用数据的可能性。类似地,Java自动进行"垃圾回收",预防了内存泄漏和其他与动态内存分配和释放相关的有害错误。Java解释器还执行许多运行时检查,例如验证所有数组和字符串访问是否在界限内。
- 异常处理是Java中使程序更加稳健的另一个特性。异常是一种类似于错误的异常情况的信号。通过使用try/catch/finally语句,程序员可以找到处理错误的代码,简化了错误处理和恢复的任务。
- 安全性
Java的存储分配模型是防御恶意代码的主要方法之一。Java没有指针,因此程序员无法访问隐藏的内存并伪造指针来指向内存。更重要的是,Java编译器不处理存储分配决策,因此程序员无法通过查看声明来猜测类的实际存储分配。Java编译的代码中的存储引用在运行时由Java解释器决定实际的存储地址。
Java运行时系统使用字节码验证来确保在网络上加载的代码不违反任何Java语言的限制。这种安全机制部分包括类的加载方式。例如,加载的类被放置在独立的命名空间中,而不是局部类,防止恶意的小应用程序用其自己的版本替代标准的Java类。
- 可移植性
Java使语言的声明与实现无关。例如,Java明确说明了每种基本数据类型的大小和其运算行为(这些数据类型由Java语法描述)。
Java环境本身对于新的硬件平台和操作系统是可移植的。Java编译器也是用Java编写的,而Java运行时系统是用ANSI C语言编写的。
- 高性能
Java是一种先编译后解释的语言,因此它的执行速度不如完全编译的语言快。但在某些情况下,性能是非常重要的。为了支持这些情况,Java设计者创建了"即时"编译器,它可以在运行时将Java字节码翻译成特定CPU的机器代码,实现全面编译。
Java字节码的格式设计考虑到这些"即时"编译器的需求,因此生成机器代码的过程非常简单,能够产生高效的代码。
- 多线程性
Java是一种多线程语言,它提供对多线程执行(也称为轻量级进程)的支持,使得具有线程的程序设计变得容易。Java的java.lang
包提供了Thread
类,它支持启动线程、运行线程、停止线程和检查线程状态的方法。
Java的线程支持还包括一组同步原语。这些原语基于监视器和条件变量模型,是由C.A.R. Hoare开发的广泛使用的同步方案。通过
synchronized
关键字,程序员可以指定某些方法在类中不能并发运行。这些方法在监视器的控制下,确保变量保持在一致的状态。
- 动态性
Java语言被设计成适应变化的环境,它是一种动态语言。例如,Java中的类是按需加载的,有些甚至可以通过网络获取。这使得Java程序可以在运行时根据需要动态加载和使用类。
此外,Java还支持通过反射机制在运行时检查和修改类、对象和方法。反射允许程序在运行时动态地获取类的信息,调用对象的方法,甚至在编译时未知的类和方法。这种动态性使得Java在许多应用程序中具有灵活性和适应性。
总的来说,Java的设计目标包括简单性、面向对象、分布性、编译和解释性、稳健性、安全性、可移植性、高性能、多线程性和动态性。这些特性使Java成为一种广泛应用于各种领域的编程语言,从桌面应用程序到企业级应用程序和大型系统。
1.3 第一个Java程序
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
这个程序是一个经典的"Hello, World!"程序,用于演示Java语言的基本结构。
程序的解释如下:
public
:这是一个访问修饰符,表示该类是公共的,可以从其他类中访问。class
:关键字用于定义一个类。HelloWorld
:这是类的名称,需要与文件名相匹配。main
:这是程序的入口点,是Java程序执行的起点。String[] args
:这是main方法的参数,它是一个字符串数组,用于接收命令行参数。System.out.println("Hello, World!");
:这是打印输出语句,用于将文本输出到控制台。
要运行这个程序,我们需要将代码保存到一个后缀名为.java
的文件中,例如HelloWorld.java
。然后,使用Java编译器将其编译为字节码文件。在命令行中,可以使用以下命令来编译和运行程序:
javac HelloWorld.java // 编译Java源文件,生成字节码文件
java HelloWorld // 运行程序
编译成功后,运行程序将在控制台上看到输出结果:“Hello, World!”
二、数据类型与变量
2.1 基本数据类型
Java的基本数据类型用于存储基本的数据值,如整数、浮点数、字符和布尔值。Java的基本数据类型有以下几种:
-
整数类型:
byte
:1字节,范围为-128到127。short
:2字节,范围为-32,768到32,767。int
:4字节,范围为-2,147,483,648到2,147,483,647。long
:8字节,范围为-9,223,372,036,854,775,808到9,223,372,036,854,775,807。
-
浮点数类型:
float
:4字节,范围为±1.4e-45到±3.4028235e+38,具有7位有效数字。double
:8字节,范围为±4.9e-324到±1.7976931348623157e+308,具有15位有效数字。
-
字符类型:
char
:2字节,表示一个Unicode字符,范围为’\u0000’到’\uffff’。
-
布尔类型:
boolean
:表示真(true)或假(false)。
除了基本数据类型外,Java还提供了引用数据类型,例如类、接口、数组等。引用数据类型存储对象的引用而不是实际的数据。
2.2 基本数据类型对应的包装类
在Java中,每个基本数据类型都有对应的包装类,用于提供一些额外的功能和操作基本数据类型的对象。以下是八个基本数据类型及其对应的包装类:
byte
对应的包装类是Byte
short
对应的包装类是Short
int
对应的包装类是Integer
long
对应的包装类是Long
float
对应的包装类是Float
double
对应的包装类是Double
char
对应的包装类是Character
boolean
对应的包装类是Boolean
包装类提供了一些有用的方法和功能,例如将基本数据类型转换为字符串、字符串转换为基本数据类型、比较两个对象的值等。此外,包装类还可以用于在集合类中存储基本数据类型的对象。
以下是一些使用包装类的示例:
// 使用包装类创建对象
Integer num = new Integer(10); // 创建一个Integer对象,值为10
Double price = new Double(3.99); // 创建一个Double对象,值为3.99
Boolean flag = new Boolean(true); // 创建一个Boolean对象,值为true
// 使用包装类提供的方法和属性
int intValue = num.intValue(); // 将Integer对象转换为int类型
double doubleValue = price.doubleValue(); // 将Double对象转换为double类型
boolean booleanValue = flag.booleanValue(); // 将Boolean对象转换为boolean类型
String str = Integer.toString(100); // 将int类型转换为字符串
int parsedInt = Integer.parseInt("200"); // 将字符串解析为int类型
// 包装类之间的比较
Integer a = new Integer(5);
Integer b = new Integer(10);
int result = a.compareTo(b); // 比较两个Integer对象的值,返回-1表示a < b
// 包装类在集合中的使用
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(1); // 自动装箱,将int类型转换为Integer对象
int value = numbers.get(0); // 自动拆箱,将Integer对象转换为int类型
需要注意的是,在Java 5及以后的版本中,引入了自动装箱(Autoboxing)和自动拆箱(Unboxing)的特性,使得基本数据类型与其对应的包装类之间的转换更加方便。例如,可以直接将基本数据类型赋值给包装类,或将包装类赋值给基本数据类型,编译器会自动进行转换。
2.3 变量
在Java中,可以声明变量来存储数据。声明变量时需要指定变量的类型,并可以给变量赋予初始值。例如:
int age; // 声明一个int类型的变量age
double pi = 3.14; // 声明一个double类型的变量pi,并赋予初始值3.14
char grade = 'A'; // 声明一个char类型的变量grade,并赋予初始值'A'
boolean isStudent = true; // 声明一个boolean类型的变量isStudent,并赋予初始值true
在声明变量后,可以使用变量名来访问和操作变量的值。例如:
age = 25; // 给age变量赋值为25
int anotherAge = age; // 声明一个新的int类型变量anotherAge,并将age的值赋给它
System.out.println(age); // 输出变量age的值,结果为25
System.out.println(anotherAge); // 输出变量anotherAge的值,结果也为25
注意,Java是一种静态类型语言,变量在声明时必须指定类型,并且不能改变其类型。
2.4 类型转换
在Java中,存在不同类型之间的转换,包括两种类型转换:自动类型转换(隐式类型转换)和强制类型转换(显式类型转换)。
-
自动类型转换(隐式类型转换):
- 小范围数据类型可以自动转换为大范围数据类型。例如,
int
类型可以自动转换为long
类型。 - 浮点类型可以自动转换为更大范围的浮点类型。例如,
float
类型可以自动转换为double
类型。 - 字符可以自动转换为整数类型。例如,
char
类型可以自动转换为int
类型。 - 自动类型转换是在类型兼容的情况下进行的,编译器会自动完成类型转换。
- 小范围数据类型可以自动转换为大范围数据类型。例如,
-
强制类型转换(显式类型转换):
- 大范围数据类型需要显式转换为小范围数据类型。例如,
long
类型需要显式转换为int
类型。 - 浮点类型需要显式转换为更小范围的浮点类型。例如,
double
类型需要显式转换为float
类型。 - 强制类型转换可能会导致数据丢失或溢出,需要在进行转换时注意。
- 强制类型转换使用圆括号将目标类型括起来,放在要转换的值之前。
- 大范围数据类型需要显式转换为小范围数据类型。例如,
以下是一些类型转换的示例:
// 自动类型转换(隐式类型转换)
int a = 10;
long b = a; // int自动转换为long
float c = 3.14f;
double d = c; // float自动转换为double
char e = 'A';
int f = e; // char自动转换为int
// 强制类型转换(显式类型转换)
double g = 3.14159;
int h = (int) g; // double强制转换为int,会丢失小数部分
float i = 5.99f;
int j = (int) i; // float强制转换为int,会丢失小数部分
int k = 65;
char l = (char) k; // int强制转换为char,根据ASCII码转换为对应字符
// 特殊情况下的类型转换
byte m = 100;
byte n = 20;
byte sum = (byte) (m + n); // 进行byte类型相加,需要进行强制类型转换
需要注意的是,在进行类型转换时,应确保目标类型能够容纳原始值,否则可能会导致数据溢出或失真。
2.5 字符串类型及其与数字之间的转换
在Java中,字符串类型是String
,可以通过以下方法实现字符串与数字之间的转换:
- 字符串转换为数字:
- 使用包装类的静态方法进行转换,如
Integer.parseInt()
、Double.parseDouble()
、Float.parseFloat()
等。这些方法将字符串解析为对应的数字类型,并返回相应的基本数据类型。 - 使用包装类的构造方法进行转换,如
new Integer(str)
、new Double(str)
、new Float(str)
等。这些构造方法接受字符串参数,并返回相应的包装类对象。
- 使用包装类的静态方法进行转换,如
String str = "123";
int a = Integer.parseInt(str); // 字符串转换为int类型
String str2 = "3.14";
double b = Double.parseDouble(str2); // 字符串转换为double类型
String str3 = "5.67";
float c = Float.parseFloat(str3); // 字符串转换为float类型
需要注意的是,当字符串无法正确解析为数字时,这些方法可能会抛出 NumberFormatException
异常。
- 数字转换为字符串:
- 使用
String.valueOf()
方法将数字转换为字符串。这个方法接受基本数据类型或包装类对象作为参数,并返回对应的字符串表示。 - 使用包装类的
toString()
方法将数字转换为字符串。这个方法可用于基本数据类型或包装类对象。
- 使用
int a = 456;
String str1 = String.valueOf(a); // int类型转换为字符串
double b = 2.71828;
String str2 = Double.toString(b); // double类型转换为字符串
float c = 3.14f;
String str3 = Float.toString(c); // float类型转换为字符串
这些方法将数字转换为字符串,使其可以进行字符串拼接、输出或存储等操作。
三、运算符
在Java中,有多种类型的运算符,用于执行各种操作。以下是一些常见的运算符:
3.1 算术运算符
算术运算符用于执行基本的数学运算。以下是Java中常用的算术运算符:
-
加法运算符 (
+
):将两个操作数相加。 -
减法运算符 (
-
):从第一个操作数中减去第二个操作数。 -
乘法运算符 (
*
):将两个操作数相乘。 -
除法运算符 (
/
):将第一个操作数除以第二个操作数。 -
取模运算符 (
%
):计算两个操作数相除的余数。
注意事项:
- 运算符中的操作数可以是整数类型(如int、long)、浮点数类型(如float、double)或其他数值类型。
- 如果操作数中有一个为浮点数类型,那么结果将为浮点数类型。
- 整数相除时,如果结果不能整除,则会舍弃小数部分。
- 取模运算符的结果符号与被除数相同。
下面是一些示例:
int num1 = 10;
int num2 = 3;
int sum = num1 + num2; // 结果为 13
int difference = num1 - num2; // 结果为 7
int product = num1 * num2; // 结果为 30
double quotient = num1 / num2; // 结果为 3.3333333333333335
int remainder = num1 % num2; // 结果为 1
3.2 赋值运算符
赋值运算符用于将一个值赋给变量,它将右侧的表达式的值赋给左侧的变量。以下是Java中常用的赋值运算符:
-
等号运算符 (
=
):将右侧的值赋给左侧的变量。 -
加法赋值运算符 (
+=
):将右侧的值与左侧的变量相加,并将结果赋给左侧的变量。 -
减法赋值运算符 (
-=
):将右侧的值从左侧的变量中减去,并将结果赋给左侧的变量。 -
乘法赋值运算符 (
*=
):将右侧的值与左侧的变量相乘,并将结果赋给左侧的变量。 -
除法赋值运算符 (
/=
):将左侧的变量除以右侧的值,并将结果赋给左侧的变量。 -
取模赋值运算符 (
%=
):将左侧的变量对右侧的值取模,并将结果赋给左侧的变量。
其他的赋值运算符还包括位运算赋值运算符(如&=
、|=
、^=
、>>=
、<<=
等),用于执行位操作后将结果赋给左侧的变量。
注意事项:
- 赋值运算符的右侧可以是常量、变量或表达式。
- 赋值运算符将右侧的值计算后赋给左侧的变量,并更新该变量的值。
- 可以连续使用赋值运算符,例如
x = y = z = 10;
将把值 10 赋给变量 x、y 和 z。
下面是一些示例:
int x = 10;
int y = 5;
x += y; // x = x + y,结果为 15
x -= y; // x = x - y,结果为 10
x *= y; // x = x * y,结果为 50
x /= y; // x = x / y,结果为 10
x %= y; // x = x % y,结果为 0
3.3 关系运算符
关系运算符用于比较两个值之间的关系,并返回一个布尔值(true或false)。以下是Java中常用的关系运算符:
-
相等运算符 (
==
):检查两个值是否相等,如果相等则返回true,否则返回false。 -
不等运算符 (
!=
):检查两个值是否不相等,如果不相等则返回true,否则返回false。 -
大于运算符 (
>
):检查左侧的值是否大于右侧的值,如果是则返回true,否则返回false。 -
小于运算符 (
<
):检查左侧的值是否小于右侧的值,如果是则返回true,否则返回false。 -
大于等于运算符 (
>=
):检查左侧的值是否大于或等于右侧的值,如果是则返回true,否则返回false。 -
小于等于运算符 (
<=
):检查左侧的值是否小于或等于右侧的值,如果是则返回true,否则返回false。
注意事项:
- 关系运算符的操作数可以是常量、变量或表达式。
- 关系运算符的结果是一个布尔值,即true或false。
- 关系运算符通常用于条件语句(如if语句、while循环等)中进行条件判断。
下面是一些示例:
int x = 5;
int y = 10;
boolean isEqual = (x == y); // false
boolean isNotEqual = (x != y); // true
boolean isGreater = (x > y); // false
boolean isLess = (x < y); // true
boolean isGreaterOrEqual = (x >= y); // false
boolean isLessOrEqual = (x <= y); // true
3.4 逻辑运算符
逻辑运算符用于对布尔值进行操作,并返回一个布尔结果。以下是Java中常用的逻辑运算符:
-
逻辑与运算符 (
&&
):当且仅当两个操作数都为true时,结果才为true。否则,结果为false。 -
逻辑或运算符 (
||
):当至少一个操作数为true时,结果为true。只有两个操作数都为false时,结果才为false。 -
逻辑非运算符 (
!
):用于取反操作,将true变为false,将false变为true。
注意事项:
- 逻辑运算符的操作数可以是布尔值、布尔变量或布尔表达式。
- 逻辑运算符通常与条件语句(如if语句、while循环等)结合使用,用于控制程序的流程。
- 逻辑与运算符和逻辑或运算符都具有短路求值的特性。即如果根据左侧的操作数已经能够确定整个表达式的结果,则不会计算右侧的操作数。
下面是一些示例:
boolean a = true;
boolean b = false;
boolean result1 = (a && b); // false
boolean result2 = (a || b); // true
boolean result3 = !a; // false
3.5 位运算符
位运算符用于对整数类型的数据进行位级操作。这些运算符将操作数的二进制表示视为一个位序列,并对每个位执行相应的操作。以下是Java中常用的位运算符:
-
按位与运算符 (
&
):对操作数的每个对应位执行逻辑与操作,如果两个位都为1,则结果位为1,否则为0。 -
按位或运算符 (
|
):对操作数的每个对应位执行逻辑或操作,如果两个位中至少有一个为1,则结果位为1,否则为0。 -
按位异或运算符 (
^
):对操作数的每个对应位执行逻辑异或操作,如果两个位不同,则结果位为1,否则为0。 -
按位取反运算符 (
~
):对操作数的每个位执行逻辑取反操作,将1变为0,将0变为1。
请注意以下事项:
- 位运算符通常用于处理底层的位操作,例如位掩码、位字段和位标志。
- 位运算符只能用于整数类型,包括
int
、long
、short
和byte
。 - 对于有符号的整数类型,右移运算符会保留符号位,因此结果可能是负数。
- 位运算符的操作数会先被转换为二进制表示,然后执行位级操作。
下面是一些示例:
int a = 5; // 0101
int b = 3; // 0011
int result1 = a & b; // 0001 -> 1
int result2 = a | b; // 0111 -> 7
int result3 = a ^ b; // 0110 -> 6
int result4 = ~a; // 补码:1...1010 -> 反码 1...1001 -> 原码 -> 10...0110 -> -6
3.6 位移运算符
位移运算符是Java中的一类位运算符,用于对操作数进行位移操作。Java提供了三种位移运算符:
-
左移运算符 (
<<
):将操作数的二进制表示向左移动指定的位数。右侧用0填充,相当于将操作数乘以2的指定位数次幂。 -
右移运算符 (
>>
):将操作数的二进制表示向右移动指定的位数。左侧用符号位填充,对于正数用0填充,对于负数用1填充。相当于将操作数除以2的指定位数次幂取整。 -
无符号右移运算符 (
>>>
):将操作数的二进制表示向右移动指定的位数。左侧用0填充,忽略符号位。适用于无符号整数类型。
需要注意以下几点:
- 位移运算符只能用于整数类型,包括
int
、long
、short
和byte
。 - 对于有符号的整数类型,右移运算符会保留符号位,因此结果可能是负数。
- 无符号右移运算符只能用于无符号整数类型,例如
int
和long
。
下面是一些示例:
int a = 1;
int b = -2;
int result1 = a << 1; // 2
int result2 = b >> 1; // -1
int result3 = b >>> 1; // 2147483647
这些位移运算符在一些特定的场景中非常有用,例如位操作、位标志和位掩码处理等。
3.7 条件运算符(三元运算符)
条件运算符,也称为三元运算符,是Java中的一种特殊运算符,用于根据条件的真假选择不同的值。它的语法如下:
condition ? expression1 : expression2
其中,condition
是一个布尔表达式或可转换为布尔值的表达式,expression1
和 expression2
是两个可能的结果表达式。
运算规则如下:
- 如果
condition
为真(true
),则整个条件表达式的值为expression1
的值。 - 如果
condition
为假(false
),则整个条件表达式的值为expression2
的值。
条件运算符可以用于简化简单的条件判断和赋值操作,使代码更加简洁和紧凑。例如:
int a = 10;
int b = 5;
int max = (a > b) ? a : b; // 如果 a > b,max 的值为 a,否则为 b
在上面的示例中,如果 a
大于 b
,则将 a
的值赋给 max
,否则将 b
的值赋给 max
。
需要注意的是,条件运算符是一个表达式,它可以嵌套使用,但要注意保持清晰的代码结构,以避免过于复杂和难以理解的嵌套条件。
四、逻辑控制
4.1 顺序结构
顺序结构是程序中最简单的控制结构之一,它按照代码的书写顺序依次执行各个语句,没有条件判断或循环。顺序结构是程序的默认执行方式,每条语句依次执行,直到程序结束。
在Java中,顺序结构是通过按顺序编写语句来实现的。每条语句按照出现的顺序依次执行,没有分支或跳转。
以下是一个示例代码片段,展示了顺序结构的使用:
public class SequenceExample {
public static void main(String[] args) {
// 顺序执行的代码块
int a = 5;
int b = 3;
int sum = a + b;
System.out.println("Sum: " + sum);
System.out.println("End of program");
}
}
在上面的示例中,代码按照顺序执行。首先定义了变量a
和b
,然后计算它们的和并将结果存储在变量sum
中。最后,使用System.out.println
语句输出结果,并打印"End of program"。这些语句按照从上到下的顺序依次执行,没有任何条件判断或循环控制。
顺序结构非常简单,但它是构建更复杂程序的基础。通过组合顺序结构、条件语句、循环语句等不同的控制结构,可以实现更灵活和功能丰富的程序逻辑。
4.2 分支结构
分支结构是程序中的一种控制结构,用于根据条件的真假执行不同的代码块。在Java中,主要有两种分支结构:if语句和switch语句。
- if语句:
if语句用于基于一个条件的真假来选择性地执行代码块。它的语法如下:
if (condition) {
// 如果条件为真,执行这里的代码块
} else {
// 如果条件为假,执行这里的代码块
}
可以只使用if语句,也可以结合else语句来提供在条件为假时执行的备选代码块。
示例代码:
int num = 10;
if (num > 0) {
System.out.println("Number is positive");
} else {
System.out.println("Number is negative or zero");
}
- switch语句:
switch语句根据一个表达式的值,选择性地执行匹配的代码块。它的语法如下:
switch (expression) {
case value1:
// 如果expression等于value1,执行这里的代码块
break;
case value2:
// 如果expression等于value2,执行这里的代码块
break;
// 可以有更多的case
default:
// 如果expression不匹配任何case,执行这里的代码块
break;
}
switch语句根据expression的值在各个case中进行匹配,找到匹配的case后执行相应的代码块。如果没有匹配的case,可以提供一个default语句块,它将在没有匹配时执行。
示例代码:
int day = 3;
String dayName;
switch (day) {
case 1:
dayName = "Monday";
break;
case 2:
dayName = "Tuesday";
break;
case 3:
dayName = "Wednesday";
break;
case 4:
dayName = "Thursday";
break;
case 5:
dayName = "Friday";
break;
default:
dayName = "Invalid day";
break;
}
System.out.println("Day: " + dayName);
分支结构允许根据不同的条件执行不同的代码逻辑,从而实现程序的灵活性和可控性。可以根据具体的需求选择使用if语句还是switch语句。
注意事项:
- 在Java的
switch
语句中,支持的基本数据类型有byte
、short
、char
、int
和String
。 - 不支持的基本数据类型包括
long
、float
和double
。原因是switch
语句的设计初衷是用于处理离散的取值集合,而这些不支持的类型是连续的数据范围,无法被准确匹配。对于这些类型,可以使用一系列的if-else
语句来实现类似的功能。 - 另外,在Java 12及更高版本中,也支持使用
switch
语句处理enum
类型。
4.3 循环结构
循环结构是程序中的一种控制结构,用于重复执行一段代码块,直到满足特定条件为止。在Java中,主要有三种循环结构:for循环、while循环和do-while循环。
- for循环:
for循环在指定的初始条件下,重复执行一段代码块,然后更新条件,直到达到指定的终止条件为止。它的语法如下:
for (initialization; condition; update) {
// 循环体,会重复执行的代码块
}
initialization用于初始化循环控制变量,condition用于指定循环的终止条件,update用于更新循环控制变量的值。
示例代码:
for (int i = 0; i < 5; i++) {
System.out.println("Iteration: " + i);
}
- while循环:
while循环在指定的条件为真时,重复执行一段代码块,直到条件变为假为止。它的语法如下:
while (condition) {
// 循环体,会重复执行的代码块
}
condition用于指定循环的条件,只有当条件为真时,循环体中的代码块才会被执行。
示例代码:
int i = 0;
while (i < 5) {
System.out.println("Iteration: " + i);
i++;
}
- do-while循环:
do-while循环首先执行一段代码块,然后检查指定的条件是否为真。如果条件为真,则重复执行代码块,直到条件变为假为止。它的语法如下:
do {
// 循环体,会重复执行的代码块
} while (condition);
先执行循环体中的代码块,然后检查条件是否为真,只有当条件为真时,循环体中的代码块才会被重复执行。
示例代码:
int i = 0;
do {
System.out.println("Iteration: " + i);
i++;
} while (i < 5);
循环结构允许根据条件重复执行一段代码,从而实现程序中的迭代和重复操作。可以根据具体的需求选择使用for循环、while循环还是do-while循环。
4.4 输入输出
在Java中,可以使用输入输出流来进行输入和输出操作。主要使用的类是java.util.Scanner
和java.io.PrintStream
。
输入操作:
- 使用
java.util.Scanner
类来读取用户的输入。首先,需要创建Scanner
对象,然后可以使用其提供的方法来读取不同类型的输入。常用的方法有:nextInt()
:读取下一个整数。nextDouble()
:读取下一个双精度浮点数。nextLine()
:读取下一行文本。
示例代码:
import java.util.Scanner;
public class InputExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter your name: ");
String name = scanner.nextLine();
System.out.println("Hello, " + name + "!");
System.out.print("Enter your age: ");
int age = scanner.nextInt();
System.out.println("You are " + age + " years old.");
}
}
输出操作:
- 使用
System.out
对象的print()
和println()
方法进行输出。print()
用于输出文本,println()
用于输出文本并换行。
示例代码:
public class OutputExample {
public static void main(String[] args) {
int x = 10;
double y = 3.14;
String name = "John";
System.out.println("The value of x is: " + x);
System.out.println("The value of y is: " + y);
System.out.print("Hello, " + name + "!");
}
}
另外,System.out
对象的printf()
方法用于格式化输出,其基本使用方法和C语言的printf
基本上一样。允许使用占位符来指定输出的格式,并将相应的值插入到占位符中。
下面是System.out.printf()
方法的基本语法:
System.out.printf(format, arg1, arg2, ...);
其中:
format
是一个字符串,包含了格式化指示符和占位符。arg1, arg2, ...
是要插入到占位符的值,可以是单个值或多个值。
下面是一些常用的格式化指示符:
%d
:用于整数类型的占位符。%f
:用于浮点数类型的占位符。%s
:用于字符串类型的占位符。%c
:用于字符类型的占位符。%b
:用于布尔类型的占位符。
下面是一个示例,演示如何使用System.out.printf()
方法进行格式化输出:
int age = 25;
double height = 1.75;
String name = "John";
System.out.printf("Name: %s, Age: %d, Height: %.2f", name, age, height);
输出结果:
Name: John, Age: 25, Height: 1.75
在上面的示例中,%s
表示字符串类型的占位符,%d
表示整数类型的占位符,%.2f
表示浮点数类型的占位符,并指定了小数点后保留两位小数。
4.5 猜数字程序
利用循环和分支结构实现一个猜 1 ~ 100 之间的数字的小游戏,使用Random
类来生成随机数字,并在判断用户猜测的过程中使用了continue
语句来重新开始下一轮循环。最后,当用户猜对数字时,会输出恭喜的消息,并使用break
语句跳出循环。
public static void main(String[] args) {
// 1. 生产一个1 - 100 的随机数字
Random random = new Random();
int randomNumber = random.nextInt(100);
Scanner scanner = new Scanner(System.in);
while (true){
// 2. 从键盘输入一个数字
System.out.print("请输入您猜测的数字(1 ~ 100):");
int guessNumber = scanner.nextInt();
if(guessNumber > randomNumber){
System.out.println("您输入的数字偏大,请重新输入!");
continue;
}
else if(guessNumber < randomNumber){
System.out.println("您输入的数字偏小,请重新输入!");
continue;
}
else{
System.out.println("恭喜你,猜中了数字:" + randomNumber + "!");
break;
}
}
}
五、方法概念及使用
5.1 方法的定义即使用
方法的定义和使用是在Java中实现代码模块化和复用的重要方式之一。定义一个方法可以将一系列相关的代码封装在一起,并赋予其一个可调用的名称。通过调用方法,可以重复使用该代码块,提高代码的可读性和可维护性。下面是定义和使用方法的基本语法:
方法的定义:
修饰符 返回类型 方法名(参数列表) {
// 方法体
// 执行的代码逻辑
// 可能包含 return 语句
}
- 修饰符:指定方法的访问修饰符,如
public
、private
等。如果不需要访问修饰符,可以使用默认修饰符。 - 返回类型:指定方法执行完毕后返回的值的数据类型。如果方法不返回任何值,则使用
void
关键字。 - 方法名:指定方法的名称,用于标识方法并进行调用。
- 参数列表:指定方法接受的输入参数,可以有零个或多个参数。每个参数由其数据类型和名称组成。
- 方法体:包含在方法中执行的代码逻辑。可以执行各种操作,包括变量声明、条件语句、循环语句等。
方法的使用:
返回类型 变量名 = 方法名(参数值);
- 返回类型:如果方法定义了返回类型,则可以将方法调用的结果赋给一个变量,该变量的类型与返回类型相匹配。
- 变量名:用于存储方法调用的结果的变量名称。
- 方法名:要调用的方法的名称。
- 参数值:传递给方法的实际参数值。
下面是一个简单的示例:
public class Example {
// 定义一个方法,接受两个整数参数,并返回它们的和
public int add(int a, int b) {
int sum = a + b;
return sum;
}
public static void main(String[] args) {
// 创建 Example 对象
Example example = new Example();
// 调用 add 方法,并将结果赋给变量 result
int result = example.add(5, 3);
// 打印结果
System.out.println(result);
}
}
在上面的示例中,Example
类定义了一个名为 add
的方法,该方法接受两个整数参数 a
和 b
,并返回它们的和。在 main
方法中,创建了 Example
对象,然后调用 add
方法并将结果赋给变量 result
,最后将结果打印到控制台。
通过方法的定义和使用,可以将复杂的程序逻辑拆分成更小、更可管理的部分,提高代码的可读性和可维护性,并实现代码的重用。方法还可以接受不同的参数组合,通过参数的不同值来执行不同的操作。
5.2 方法重载
方法重载(Method Overloading)是指在同一个类中可以定义多个同名但参数列表不同的方法。方法重载的目的是为了提供一种便捷的方式来使用相似功能但具有不同参数的方法。
方法重载的规则如下:
- 方法名必须相同。
- 方法的参数列表必须不同,要么是参数的个数不同,要么是参数的类型不同,或者两者都不同。
- 方法的返回类型可以相同也可以不同。
- 方法的修饰符可以相同也可以不同。
下面是一个简单的示例,展示了方法重载的使用:
public class Example {
// 方法重载:两个整数相加
public int add(int a, int b) {
return a + b;
}
// 方法重载:三个整数相加
public int add(int a, int b, int c) {
return a + b + c;
}
// 方法重载:两个浮点数相加
public double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
Example example = new Example();
int sum1 = example.add(5, 3);
int sum2 = example.add(2, 4, 6);
double sum3 = example.add(2.5, 3.7);
System.out.println(sum1); // 输出:8
System.out.println(sum2); // 输出:12
System.out.println(sum3); // 输出:6.2
}
}
在上面的示例中,Example
类定义了三个同名但参数列表不同的 add
方法。第一个 add
方法接受两个整数参数,返回它们的和;第二个 add
方法接受三个整数参数,返回它们的和;第三个 add
方法接受两个浮点数参数,返回它们的和。在 main
方法中,通过创建 Example
对象并调用不同版本的 add
方法,可以根据实际需求进行参数的选择,从而实现方法的重载。
方法重载使得代码更加灵活和易于使用,可以根据不同的参数组合调用相同的方法名,提高代码的可读性和可维护性。在实际开发中,方法重载经常被用于提供多种形式的方法,以满足不同的需求。
5.3 方法递归
方法递归(Method Recursion)是指在方法的定义中调用自身的过程。递归可以用来解决那些可以被分解成相同问题的子问题的问题,从而简化问题的解决过程。
递归方法通常包含两个部分:
- 递归终止条件:定义递归方法的结束条件,当满足该条件时,递归将停止调用自身并返回结果。这是避免无限递归的关键。
- 递归调用:在方法的定义中调用自身,每次调用将问题分解为一个或多个规模更小的子问题,然后通过递归调用解决子问题,最终得到整个问题的解。
下面是一个经典的递归示例:计算阶乘(factorial)。
public class Example {
public int factorial(int n) {
// 递归终止条件
if (n == 0 || n == 1) {
return 1;
}
// 递归调用
return n * factorial(n - 1);
}
public static void main(String[] args) {
Example example = new Example();
int result = example.factorial(5);
System.out.println("5的阶乘为:" + result); // 输出:5的阶乘为:120
}
}
在上面的示例中,Example
类定义了一个 factorial
方法,用于计算给定整数 n
的阶乘。在 factorial
方法中,首先检查递归终止条件,当 n
的值为 0 或 1 时,直接返回 1。否则,通过递归调用 factorial(n - 1)
来计算 n - 1
的阶乘,然后将结果乘以 n
,得到 n
的阶乘。在 main
方法中,通过创建 Example
对象并调用 factorial
方法来计算 5 的阶乘。
递归方法在解决一些问题时非常有用,特别是那些具有递归结构的问题,例如树的遍历、排列组合、回溯算法等。但需要注意,在使用递归时要确保设置合适的终止条件,避免无限递归导致程序崩溃。此外,递归的效率通常较低,可能会消耗大量的内存和处理时间,因此在实际使用中需要权衡利弊。
六、数组
6.1 数组的定义
数组是一种数据结构,用于存储多个相同类型的元素。它提供了一种有效的方式来组织和访问数据。
在Java中,数组的定义包括以下几个要素:
- 数据类型:数组中的元素类型必须相同,可以是任何合法的数据类型,如整数、浮点数、字符、布尔值等。
- 数组名:用于标识数组的名称,可以根据需要自定义。
- 大小:数组的大小表示数组可以存储的元素个数,大小在创建数组时确定且不能更改。
数组的定义语法如下:
dataType[] arrayName; // 声明一个数组
其中,dataType
是数组中元素的数据类型,arrayName
是数组的名称。
创建数组并分配内存空间的语法如下:
arrayName = new dataType[arraySize]; // 创建一个大小为 arraySize 的数组
其中,arraySize
表示数组的大小,即数组可以存储的元素个数。
可以将数组的声明和创建合并为一行代码,如下所示:
dataType[] arrayName = new dataType[arraySize]; // 声明并创建一个大小为 arraySize 的数组
以下是一些示例:
int[] numbers = new int[5]; // 声明一个包含 5 个整数的数组
double[] grades = new double[10]; // 声明一个包含 10 个浮点数的数组
char[] letters = new char[26]; // 声明一个包含 26 个字符的数组
String[] names = new String[3]; // 声明一个包含 3 个字符串的数组
在上述示例中,通过指定数组的数据类型、数组名称和数组大小,可以创建具有相应类型和大小的数组。请注意,数组的大小表示数组可以存储的元素个数,而不是数组的索引范围。数组的索引范围从 0 到 数组大小减 1。
创建数组后,可以使用索引访问和操作数组的元素。例如,numbers[0]
表示数组 numbers
的第一个元素,grades[2]
表示数组 grades
的第三个元素。
需要注意的是,在使用数组时,要确保索引不超出数组的有效范围,否则会导致数组越界异常。
6.2 数组的使用
- 数组的初始化:
- 静态初始化:在声明数组时直接为数组元素赋初值。
- 动态初始化:先声明数组,然后通过赋值语句为数组元素赋值。
静态初始化示例:
int[] numbers = {1, 2, 3, 4, 5}; // 静态初始化一个整数数组
String[] names = {"Alice", "Bob", "Charlie"}; // 静态初始化一个字符串数组
动态初始化示例:
int[] numbers = new int[5]; // 动态初始化一个包含 5 个整数的数组
double[] grades = new double[10]; // 动态初始化一个包含 10 个浮点数的数组
- 访问数组中的元素:
使用索引来访问数组中的元素,索引从 0 开始,到数组长度减 1。可以使用方括号[]
运算符来访问指定索引位置的元素。
示例:
int[] numbers = {1, 2, 3, 4, 5};
int firstNumber = numbers[0]; // 获取数组中的第一个元素
int thirdNumber = numbers[2]; // 获取数组中的第三个元素
- 遍历数组:
可以使用循环结构(如for
循环或foreach
循环)来遍历数组中的所有元素。
示例:
int[] numbers = {1, 2, 3, 4, 5};
// 使用 for 循环遍历数组
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// 使用 foreach 循环遍历数组
for (int number : numbers) {
System.out.println(number);
}
上述示例中,使用 for
循环和索引 i
遍历数组 numbers
,通过 numbers[i]
获取每个元素并进行操作。另外,使用 foreach
循环遍历数组 numbers
,可以直接访问每个元素而不需要索引。
注意:在访问数组元素时,要确保索引不超出数组的有效范围,否则会导致数组越界异常。在遍历数组时,可以使用
length
属性获取数组的长度,这样可以避免超出数组范围的访问错误。
6.3 数组的使用场景
数组在Java中有广泛的使用场景,比如保存数据、作函数参数、函数返回值等等。
- 保存数据
数组经常用于保存一组相关的数据。例如,一个学生成绩表可以使用数组来存储每个学生的成绩,一个商品库存列表可以使用数组来存储每个商品的库存数量。
int[] scores = {85, 90, 78, 92, 88}; // 学生成绩数组
int[] inventory = {10, 5, 20, 15}; // 商品库存数组
- 作函数参数
数组可以作为函数的参数传递,以便在函数中操作和处理数组数据。例如,一个函数可以接受一个整数数组并计算数组中所有元素的总和。
public static int calculateSum(int[] numbers) {
int sum = 0;
for (int i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
int[] numbers = {1, 2, 3, 4, 5};
int sum = calculateSum(numbers); // 调用函数计算数组元素的总和
System.out.println("数组元素的总和为: " + sum);
- 作函数返回值
数组也可以作为函数的返回值,用于返回多个相关的值。例如,一个函数可以返回一个包含某个范围内的所有偶数的数组。
public static int[] getEvenNumbers(int start, int end) {
int size = (end - start) / 2 + 1;
int[] evenNumbers = new int[size];
int index = 0;
for (int i = start; i <= end; i++) {
if (i % 2 == 0) {
evenNumbers[index] = i;
index++;
}
}
return evenNumbers;
}
int[] evenNumbers = getEvenNumbers(1, 10); // 调用函数获取范围内的所有偶数
System.out.println("范围内的偶数数组: " + Arrays.toString(evenNumbers));
这些例子展示了数组的使用场景,它们可以存储和处理一组相关的数据,作为函数的参数进行操作,并作为函数的返回值提供结果。数组的灵活性使其成为处理多个数据的有用工具。