Java基础知识学习
2022-01-23 22:23:00 # Java

Java的基础程序设计结构

一个简单的Java应用程序

1
2
3
4
5
public class Test {
public static void main(String[] args) {
System.out.println("We will not use 'Hello,World!'");
}
}

注释

//表示单行注释 /* */用于表示多行注释

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
System.out.println("We will not use 'Hello,World!'");
}
}
/*例如
这就是
多行注释*/

数据类型

Java是一种强类型语言。这就意味着必须为每一个变量声明一种类型。在Java中,一共有八种基本类型,其中四种整型、两种浮点型、一种字符类型char(用于表示Unicode编码的代码单元)和一种表示真值的boolean类型

注释:Java中有一个能表示任意精度的算数包,通常称为”大数”(big number),它是一个Java对象,并不是一种基本的Java数据类型

整型

整型用于表示没有小数部分得数值,允许是负数。Java一共有四种整型,具体内容如图所示:

1643028102322.png

在Java中,整型的范围与运行Java代码的机器无关。这可以很好的解决多平台运行程序的所带来的诸多问题。长整型数值有一个后缀L或者l(如400000000L)。十六进制数值有一个0x前缀。八进制有一个前缀0,例如八进制010对应着十进制中的8,但是容易混淆,所以最好不使用八进制常数。二进制前缀为0b。还可以在数字间加下划线,让人更易读。例如:1_000_000表示100万。

浮点类型

浮点类型用于表示小数部分的数值。在Java中一共有两种浮点类型:float和double类型。double的数值精度是float类型的两倍。

1643029026049.png

float类型的数值有一个后缀F或f(例如,3.14F)。没有后缀F的浮点数值(如3.14)总是默认为double类型

char类型

原本的char类型用于表示单个字符。现在有些Unicode字符可以用一个char值描述,另外有一些Unicode则需要两个char值。

char类型的字符要用单引号括起来。例如:’A’是编码值为65的字符常量。他与”A”不同,”A”是包含一个字符A的字符串。char类型的值可以表示为十六进制。列如\u03c0表示希腊字母π

boolean类型

布尔类型有两个值:false和true,用来判定逻辑条件。整型值和布尔值之间不能进行相互转换

变量与常量

声明变量

在Java中,每一个变量都有一个类型。在声明变量时,先指定变量类型,然后紧跟变量名。例如:int number;

变量名必须是一个以字母开头并由字母或数字组成。’+’等符号不能出现在变量名中,空格也不行。变量名中所有的字符都是有意义的,并且大小写敏感。变量名的长度基本上没有限制

变量初始化

声明一个变量后,必须用赋值语句去对其进行初始化操作。Java中可以将声明放在任意位置,但尽量放在第一次使用的地方

与C++的区别: C和C++区分变量的声明和定义。例如int i=10;是一个定义而extern int i;是一个声明。在Java中,并不区分变量的声明和定义

常量

在Java中,利用关键字final来指示常量。例如: final int a = 10; 关键字final表示这个变量只能被赋值一次。一旦被赋值后,就不能更改了。

在Java中,如果要在一个类中的多个方法使用某个常量,这种常量称为类常量(class constant)。可以使用关键字static final设置一个类常量。注意:类常量的定义位于main方法的外部。因此,在同一个类中的其他方法也可以使用这个常量。如果一个常量被声明为public,那么其他类的方法也可以使用这个常量。

枚举类型

有的时候,变量的取值只在一个有限的集合内。例如,销售的服装或者披萨只有小、中、大和超大这四种尺寸。针对这种情况可以使用枚举类型。枚举类型包括有限个命名的值。例如:

1
enum Size {SAMLL,MEDIUM,LARGE,EXTRA_LARGE};

现在就可以声明这种类型的变量:

1
Size s = Size.MEDIUM;

Size类型的变量只能存储这个类型声明中给出的某个枚举值或者特殊值null

运算符

算术运算符

在Java中,使用算术运算符+、-、*、/来表示加、减、乘、除运算。当参与/运算的两个操作数都是整数时,表示整数除法;否则表示浮点除法。整数的求余操作(取模)用%表示,例如,15/2=7,15%2=1,15.0/2=7.5。如果整数被0除将会产生异常,浮点数被0除则会得到无穷大或NaN结果。

数学函数与常量

在Math类中,包含了各种数学函数。例如,想计算一个数值的平方根,可以使用sqrt方法:

1
2
3
double x = 4;
double y = Math.sqrt(x);
System.out.println(y);

在Java中,没有幂运算,需要借助Math类中的pow方法。以下语句:

1
double y = Math.pow(x,a);

将y的值设为x的a次幂。pow方法有两个double类型的参数,所以其返回值也为double类型

Math类还提供了一些常用的三角函数: Math.sin、Math.cos、Math.tan、Math.atan、Math.atan2

还有指数函数以及他的反函数: Math.exp、Math.log、Math.log10

Java还提供两个用于表示π和e常量的近似值:Math.PI、Math.E

数值类型之间的转换

我们经常需要将一种数值类型转换为另一种数值类型。下图给出了数据类型之间的合法转换

1643185176561.png

其中实线表示无信息丢失的转换;虚线表示可能有精度缺失的转换。

强制类型转换

在上节内容中,可以看到在必要的时候,int类型的值将自动地转化为double类型。但有时候也需要将double类型转化为int类型。在Java中允许这种数值间的类型转换,当然也会损失一定信息。强制类型转换的语法格式是,在圆括号中给出自己想要转换的目标类型,后面紧跟待转换的变量名。例如:

1
2
double x = 9.997;
int nx = (int) x;

此时变量nx的值为9,强制类型转换会直接截取整数转换为整型,小数部分将会直接被丢弃。

如果需要对浮点数进行四舍五入运算,可以使用Math类中的round方法:

1
2
double x = 9.997;
int nx = (int) Math.round(x);

此时变量nx的值为10。调用round方法时,仍需要用到强制类型转换,因为这个方法返回结果是long类型,因为可能会丢失信息,所以只能使用显式强制类型转换才能将long类型转化为int类型。

结合赋值和运算符

在赋值的时候可以使用二元运算符,例如: x += 4;等价于x = x + 4; 要把运算符放在等号左边。

自增与自减运算符

跟其他语言一样,Java也提供自增与自减运算符,n++等价于n=n+1,n–等价于n=n-1。

关系和boolean运算符

Java包含丰富的关系运算符。如果要检测相等性,可以使用双等号==。例如,3==7的值为false。另外也可以使用!=来检测不相等。例如,3 != 7的值为true。

还有经常使用的<、>、<=、>=运算符。&&运算符需要两个为真才为真,一个为假则假。||运算符则相反。

三元运算符: ?: 例如:condition? expression1 : expression2 如果condition为真,则为第一个表达式的值,否则计算为第二个表达式的值

括号与运算符级别

如果不使用圆括号,就按照给出的运算符优先级次序进行计算。同一级别的运算符按照从左到右的次序进行计算,但右结合运算符除外。例如,由于&&的优先级比||高,所以表达式a && b || c等价于(a && b) || c。又因为+=是又结合运算符,所以表达式a += b += c等价于a += (b += c)

1643205926077.png

字符串

字串

String类的substring方法可以从一个较大的字符串提取出一个字串。例如:

1
2
String greeting = "Hello";
String s = greeting.substring(0,3);

创建一个由字符”Hel”组成的字符串。substring方法的第二个参数是不想复制的第一个位置。这里要复制的位置为0、1和2。substring方法从0开始计数,直到3位置,但不包含3。

拼接

Java允许使用+号拼接两个字符串

1
2
3
String A = "Hello";
String B = "World";
String C = A + B;

上述代码将HelloWorld赋给变量C。当将一个字符串与一个非字符串的值进行拼接时,后者会转换成字符串。例如:

1
2
int age = 13
String rating = "PG" + age;

将rating设置为 “PG13”。如果需要把多个字符串放在一起,用有个界定符分隔,可以使用静态join方法:

1
2
String all = String.join(" / ","S","M","L","XL");
//all is the string "S / M / L / ML"

在Java11中,还提供了一个repeat方法:

1
String repeated = "Java".repeat(3); //repeated is "JavaJavaJava"

不可变字符串

String类中没有提供修改字符串中某个字符的方法。如果想要把greeting的内容改为”Help!”。可以题去想要保留的子串,再与希望替换的字符拼接:

1
2
String A = "Hello";
A = A.substring(0,3) + "p!";

上面的语句会将A变量的当前值修改为Help!。由于不能修改Java字符串中的单个字符,所以在Java文档中将String类对象称为是不可变的(immutable),如同数字3永远是数字3一样,字符串”Hello”永远是”Hello”,你不能修改这些值,但是可以修改字符串变量,让他引用另外一个字符串,这就如同这个以前存放3的变量,现在可以存放4一样

检测字符串是否相等

可以用equals方法检测两个字符串是否相等。对于表达式: s.equals(t)

如果字符串s与字符串t相等,则返回true;否正,返回false。需要注意的是,s与t可以是字符串变量,也可以是字符串本身。例如,以下表达式也是合法的:

1
"Hello".equals(greeting)

如果要检测两个字符串是否相等且不区分大小写的话,可以使用equalsIgnoreCase方法。

1
"Hello".equalsIgnoreCase("hello")

一定不要用 == 运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否存放在同一个位置上。当然,如果在同一个位置上,它们必然相等。但是,完全有可能将内容相同多的字符串副本存放在不同的位置上。

1
2
3
4
5
String greeting = "Hello";
if(greeting == "Hello")...
//probably true
if(greeting.substring(0,3) == "Hel")...
//probably false

如果虚拟机始终将相同的字符串共享,那么就可以使用 == 运算符检测是否相等。但实际上只有字符串字面量是共享的,而 + 或 substring等操作得到的字符串并不共享。因此不能用 == 运算符检测字符串的相等性。

空串与Null串

空串””是长度为0的字符串。可以调用以下代码来检测一个字符串是否为空:

1
if(str.length() == 0) 或 if(str.equals(""))

空串是一个Java对象,有自己的串长度(0)和内容(空)。String变量还可以存放一个特殊的值,名为null,表示目前没有任何对象与该变量关联,要检查一个字符串是否为null,要使用以下条件:

1
if(str==null)

如果需要检查一个字符串既不是null,也不是空串,可以使用如下条件:

1
if(str!=null && str.length!=0)

码点与代码单元

Java字符串由char值序列组成。char数据类型是一个采用UTF-16编码表示Unicode码点的代码单元

length方法将返回采用UTF-16编码表示给定字符串所需要的代码单元量。例如:

1
2
String greeting = "Hello";
int n = greeting.length(); //is 5

要想得到实际的长度,即码点数量,可以调用:

1
int cpCount = greeting.codePointCount(0,greeting.length());

调用s.charAt(n)将返回位置n的代码单元,n介于0~s.length()-1之间。例如:

1
2
char first = greeting.charAt(0);//first is 'H'
char last = greeting.charAt(4);//last is 'o'

要想得到第i个码点,应该使用下列语句

1
2
int index = greeting.offsetByCodePoints(0,i);
int cp = greeting.codePointAt(index);

构建字符串

有时候需要由较短字符串构建字符串,如果采用字符串拼接的方式来达到这个目的,效率会比较低。每次拼接字符串时,都会构建一个新的String对象,既耗时,也浪费空间。使用StringBuilder类就可以避免这个问题的发生。

如果需要用许多小段的字符串来构建一个字符串,那么应该按照下列步骤进行。首先构建一个空的字符串构建器:

1
StringBuilder builder = new StringBuilder();

当每次需要添加一部分内容时,就调用append方法;

1
2
builder.append(ch); //appends a single character
builder.append(str); //appens a string

在字符串构建完成时,就调用toSring方法,将可以得到一个String对象,其中包含了构建器中的字符序列。

1
String completedString = builder.toString();

案例:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
char[] ch = {'X'};
builder.append(ch); //apppends a single character
String str = "str";
builder.append(str); // appends a string
String completedString = builder.toString();//builder:"Xstr"
System.out.println(completedString);
}
}

输入与输出

读取输入

首先需要构建一个与”标准输入流”System.in关联的Scanner对象。

1
Scanner in = new Scanner(System.in);

现在,就可以使用Scanner类的各种方法读取输入了。例如,nextLine方法将读取一行输入。

1
2
System.out.print("What is your name?");
String name = in.nextLine();

在这里,使用nextLine方法是因为在输入行中可能包含空格。如果要只读取一个单词(以空格符作为分隔符),可以使用String X = in.next()。要想读取一个整数,就可以调用nextInt方法。

1
2
System.out.print("How old are you?");
int age = in.nextInt();

同理,要想读取下一个浮点数,就调用nextDouble方法。需要在程序最前面,添加import java.util.*;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Le1a;

import java.util.*;

public class InputTest {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);

System.out.print("What is your name?");
String name = in.nextLine();

System.out.print("How old are you?");
int age = in.nextInt();

System.out.println("Hello, "+name+".Next year,you'll be " + (age+1));

}
}
  • Scanner(InputStream in) 用给定的输入流创建一个Scanner对象
  • String nextLine() 读取输入的下一行内容
  • String next() 读取输入的下一个单词(以空格作为分隔符)
  • boolean hasNext() 检测输入中是否还有其他单词。
  • boolean hasNextInt() 检测输入中是否还有其他数字。
  • boolean hasNextDouble() 检测是否还有下一个表示整数或浮点数的字符序列

格式化输出

可以使用System.out.print(x)将数值x输出到控制台。这条命令将以x的类型所允许的最大非0数位个数打印输出x。例如:

1
2
double x =10000.0/3.0;
System.out.print(x);

将会打印输出3333.3333333333335如果希望显示美元、美分数,这就会有问题。可以使用printf方法。例如:

1
System.out.printf("%8.2f",x);

将会打印输出 3333.33,其中包括8个字符,其中会打印一个前导空格和另外精度为小数点后2个字符。

也可以为printf提供多个参数,例如:

1
System.out.printf("Hello,%s. Next year,you'll be %d",name,age);

1643803186456.png

可以使用静态的String.format方法创建一个格式化的字符串,而不打印输出:

1
String message = String.format("Hello,%s. Next year,you'll be %d",name,age);

文件输入与输出

读取文件需要构造一个BufferedReader对象,使用BufferedReader类从字符输入流中读取文本,如下所示:

1
2
3
4
5
6
7
8
import java.io.*;
public class Main {
public static void main(String[] args)throws IOException {
BufferedReader in = new BufferedReader(new FileReader("D:\\Cc\\IntelliJ IDEA 2021.1\\Code\\src\\Le1a\\flag.txt"));
String flag = in.readLine();
System.out.println(flag);
}
}

同理使用BufferedReader类,通过out.write()写入文件。

1
2
3
4
5
6
7
8
9
import java.io.*;
public class Main {
public static void main(String[] args)throws IOException {
BufferedWriter out = new BufferedWriter(new FileWriter("flag.txt"));
out.write("这是你的flag!");
out.close();
System.out.println("文件创建成功!");
}
}

控制流程

块作用域

块(即复合语句)是指由若干条Java语句组成的语句,并用一对大括号括起来。块确定了变量的作用域。一个块可以嵌套在另一个块中。下面就是嵌套在main方法块中的一个块。

1
2
3
4
5
6
7
8
public static void main(String[] args){
int n;
...
{
int k;
...
}k仅仅在这里被定义
}

但是,不能在嵌套的两个块中声明同名的变量。例如,下面的代码就有错误,而无法通过编译:

1
2
3
4
5
6
7
8
public static void main(String[] args){
int n;
...
{
int k;
int n;//报错 无法在内部块中重新定义n
}
}

条件语句

在Java中,条件语句的形式为

1
if(条件) 执行语句

这里的条件必须用小括号括起来,与绝大多数程序设计语言一样,Java常常希望在某个条件为真的时候执行多条语句。在这种情况下就可以使用块语句,形式为:

1
2
3
4
5
if(条件){
执行语句1;
执行语句2;
...
}

下面是if语句和if/else的流程图

1643878698930.png

循环

当条件为true时,while循环执行一条语句(也可以是一个块语句)。一般形式如下:

1
while(条件) 执行语句

如果开始时循环条件的值就为false,那么while循环一次也不执行。在这个示例中,我们会让一个计数器递增,并在循环体中更新当前的累计金额,直到总额超过目标金额为止。

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package Le1a;
import java.util.*;
public class Retirement1 {
public static void main(String[] args) {
//read inputs
Scanner in = new Scanner(System.in);

System.out.print("退休需要多少钱?");
double goal = in.nextDouble();

System.out.print("你每年会捐多少钱?");
double payment = in.nextDouble();

System.out.print("以%为单位的利率: ");

double balance = 0;
int years = 0;

//未达到目标时更新帐户余额
while (balance < goal){
//加上今年的付款和利息
balance +=payment;
years++;
}
System.out.println("你可以在 " + years + "年后退休.");
}
}

示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package Le1a;

import java.util.*;

public class Retirement2 {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);

System.out.print("你每年捐多少钱?");
double payment = in.nextDouble();

System.out.print("利率(以%计): ");
double interestRate = in.nextDouble();

double balance = 0;
int year = 0;


String input;

//在用户尚未准备退休时更新帐户余额
do {
//加上今年的付款和利息
balance +=payment;
double interest = balance * interestRate / 100;
balance += interest;

year++;

//打印当前余额
System.out.printf("经过 %d 年后,你的余额是 %,.2f%n",year,balance);

//ask if ready to retire and get input
System.out.print("准备退休了吗? (Y/N) ");
input = in.next();
}
while (input.equals("N"));
}
}

确定循环

for循环语句是支持迭代的一种通用结构,由一个计数器或类似的变量控制迭代次数,每次迭代后这个变量将会更新。如图3-12所示,下面的循环将数字1~10 输出到屏幕上。

1
2
for(int i=1;i<=10;i++)
System.out.println(i);

for语句的第一部分通常是对计数器初始化;第二部分给出每次新一轮循环前要检测的循环条件;第三部分指定如何更新计数器。

当在for语句的第1部分中声明一个变量之后,这个变量的作用域就扩展到这个for循环体的末尾。

1
2
3
4
for(int i=1;i<=10;i++){
...
}
//i不在这里定义

特别指出,如果在for循环内部定义了一个变量,那么这个变量就不能在循环体之外使用。因此,如果希望在for循环之外使用循环计数器的最终数值,就要确保这个变量在循环之外声明!

1
2
3
4
5
int i;
for(i=1;i<=10;i++){
...
}
//i在这里仍被定义

另一方面,可以在不用的for循环中定义同名的变量:

1
2
3
4
5
6
7
8
for(int i=1;i<=10;i++){
...
}
...
for(int i =11;i<=20;i++) //可以定义另一个名为i的变量
{
...
}

for循环语句只是while循环的一种简化形式。例如:

1
2
3
for(int i=10;i>0;i--){
System.out.println(i);
}

可以重写为:

1
2
3
4
int i=10;
while(i>10){
System.out.println(i);
}

多重选择:switch语句

在处理多个选项时,使用if/else结构显得有些笨拙。Java有一个与C/C++完全一样的switch语句。

例如,如果建立一个如图3-13所示的包含4个选项的菜单系统,可以使用下列代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Scanner;

public class Main {
public static void main(String[] args){
Scanner in = new Scanner(System.in);
System.out.print("选择一个方法(1、2、3、4):");
int choice = in.nextInt();
switch (choice){
case 1:
System.out.println("你选择了方法一!");
break;
case 2:
System.out.println("你选择了方法二!");
break;
case 3:
System.out.println("你选择了方法三!");
break;
case 4:
System.out.println("你选择了方法四");
break;
}
}
}

switch语句将从与选项值相匹配的case标签开始执行,知道遇到break语句,或者执行到switch语句结束位置。如果没有相匹配的case标签,而有default子句,那么就执行这个子句。

1643956802797.png

中断控制流程语句

虽然Java的设计者将goto作为保留字,但实际上并没有在语言中使用它。在Java中新增了一条语句:带标签的break。

下面来看一下不太标签的break语句。与用于退出switch语句的break语句一样,它也可以用于退出循环语句。例如:

1
2
3
4
5
6
7
while(years<=100){
balance+=payment;
double interest = balance * interestRate/100;
balance+=interest;
if (balance >= goal)break;
years++;
}

在循环开始时,如果years>100,或者在循环体中balance>=goal,则退出循环语句。当然,也可以在不使用break的情况下计算years的值,例如:

1
2
3
4
5
6
7
while(years<=100 && balance < goal){
balance+=payment;
double interest = balance * interestRate/100;
balance+=interest;
if (balance < goal) //在这里再做一次判断,如果满足,years就+1
years++;
}

但是,这个版本中检测了两次balance < goal。为了避免重复检测,有些程序员更爱使用break

带标签的break语句,用于跳出多重嵌套的循环语句。在嵌套很深的循环语句中会发生一些不可预料的事情。此时可能更希望跳出多重循环的循环语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Scanner in = new Scanner(System.in);
int n;
read_data;
while (...)
{ //此循环语句使用标签进行标记
...
for(...) //这个内部循环没有标记
{
System.out.println("输入一个大于等于0的数: ");
n = in.nextInt();
if(n<0)//不应该发生这个事,应该跳出循环
break read_data;//跳出循环到标记处
}
}
//此语句在标记为break之后立即执行
if (n<0)//检查是否有不好的情况
{
//处理不好的情况
}
else
{
//进行正常处理
}

还有一个continue语句。与break语句一样,它将中断正常的控制流程。continue语句将控制转移到当层循环首部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Le1a;

import java.util.Scanner;

public class Main {
public static void main(String[] args){
Scanner in = new Scanner(System.in);
int sum = 0;
int goal = 100;
while (sum<goal){
System.out.println("输入一个数字: ");
int n = in.nextInt();
if (n<0){
System.out.println("输入的数字不合法");
continue; //如果n<0,这里将直接跳转到当前循环首部
}
sum+=n;//n<0时不执行
System.out.printf("当前sum的值为:%d,当前n的值为:%d",sum,n);
System.out.println();
}
}
}

大数

如果基本的整数和浮点数精度不能够满足要求,那么就可以使用java.math包中两个很有用的类:BigIntegerBigDecimal。这两个类可以处理包含任意长度数字序列的数值。BigInteger用于任意精度的整型运算,BigDecimal则用于任意精度的浮点数运算。

使用静态的valueOf方法就可以将普通的数值转换为大数:

1
BigInteger a = BigInteger.valueOf(100);

对于更大的数,使用带字符串的构造器:

1
BigInteger reallyBig = new BigInteger("132546542313245456423132456461231456454132154564567")

但是,大数不能使用常用的运算符(+和*等),需要使用大数类的addmultiply方法。

1
BigInteger c = a.add(b); // c=a+b

数组

声明数组

数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标可以访问到数组中的每一个值。例如,如果a是一个整型数组,a[i]就是a数组中下标为i的整数。在声明数组变量时,需要指出数组类型(类型在前,[]紧跟其后)和数组变量的名字。int[] a;声明了整型数组a,但是没有进行初始化,数组必须要经过初始化后才能使用。可以使用new来创建数组:

1
int[] a = new int[100];//or var a =new int[100];

这条语句声明并定义了一个可以存储100个整数的数组。数组长度不一定是常量,可以用new int[n]来常见一个长度为n的整型数组。在Java中,提供了一种创建数组对象并同时提供初始值的简写形式。例如:

int[] smallPrimes={2,3,5,7,11,13};这个语法不需要使用new,也不用指定长度。

也可以声明一个匿名数组:new int[] {17,19,23,29,31,37};

这会分配一个新的数组并填入大括号中提供的值。可以使用这种语法重新初始化一个数组而无需创建新变量。例如:

1
2
int[] anonymous = {17,19,23,29,31,37}
smallPrimes = anonymous;

可以简写为:

1
smallPrimes=new int[] {17,19,23,29,31,37};

访问数组元素

数组中的元素是从0开始而不是从1开始的。一旦创建了数组,就可以在数组中填入元素。例如使用一个循环:

1
2
3
int[] a = new int[n];
for(int i = 0;i < n.length;i++)
a[i] = i; //向数组中填入了0~n-1个数字

创建一个整型数组时,所有元素的初始值都为0,布尔数组的元素初始值都为false。对象数组的元素的初始值则为一个特殊值null,表示这些元素还未存放任何对象。例如:String[] names = new String[10];会创建一个包含10个字符串的数组,所有字符串都为null。如果希望这个数组包含空字符串,必须给元素指定空字符串:for(int i = 0;i < 10;i++) names[i]="";

foreach循环

Java有一种功能很强的循环结构,可以用来依次处理数组(或者其他元素集合)中的每个元素,而不必考虑下标。例如:

1
2
3
4
int[] a = {5,2,0,1,3,1,4};
for(int x:a)//用x来存放a数组中的每一个元素
System.out.print(x + " ");//打印当前x的值
//运行结果为: 5 2 0 1 3 1 4

这个代码等同于用for循环去遍历数组元素然后打印出来,但是for each循环语句显得更简洁,更不容有出错,因为不用考虑下标的起始值和终止值

数组拷贝

在Java中,允许将一个数组变量拷贝到另一个数组变量。这时,两个变量将引用同一个数组:

1
2
int[] luckyNumbers = smallPrimes;
luckyNumbers[5] = 12; //现在 smallPrimes[5]的值 也是 12

与C++的区别:Java数组与堆栈上的C++数组有很大不同,但基本上与在堆(heap)上的分配的数组指针一样。

也就是说,int[] a= new int[100];//Java不同于int a[100];//C++而是等同于int* a = new int[100];//C++

命令行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Le1a;

import java.util.Scanner;

public class Message {
public static void main(String[] args) {
if(args.length==0||args[0].equals("-h"))
System.out.print("Hello,");
else if (args[0].equals("-g"))
System.out.print("Goodbye,");
//打印其他命令行参数
for (int i = 1;i < A.length; i++)
System.out.print(" "+ A[i]);
System.out.println("!");
}
}

如果使用下面这种形式调用这个程序:

1
java Message -g cruel world

args数组将会包含以下内容:

1
2
3
args[0]: "-g"
args[1]: "cruel"
args[2]: "world"

这个程序将会打印如下消息:

Goodbye,cruel world!

数组排序

对数组排序可以调用Arrays类中的sort方法,这个方法采用了优化的快速排序(QuickSort)算法

使用方法:

int[] a = new int[10000];

· · ·

Arrays.sort(a);

下面是一个抽取随机数的程序,假如我们要从0到50的数字中抽取6个,那么可能输出的结果为:4、7、8、19、30、44

首先需要一个数组来存储这些数字的总集合:

1
2
3
4
int[] numbers =new int[n];
for(int i = 0;i < numbers.length;i++){
numbers[i] = i + 1;
}

第二个数组用来存放抽取出来的随机数:

1
int[] result = new int[k];

随机数通过调用Math类中的random方法实现返回一个0到1之前的随机浮点数。用n乘以这个浮点数,再通过强制转换得到从0到n-1之间的一个随机数(n代表数组长度,最大值是n-1)。

1
int r =(int)(Math.random()*n)

result[i] = number[r]来表示这个存储过程,抽中这个值后,为了防止再次抽中这个数,则将数组中最后一个数来覆盖抽中的这个number[r],并将n减1

1
2
number[r] = numbers[n-1];
n--;

在抽取k个数后,使用sort方法对数组进行排序

1
2
3
4
Arrays.sort(result);
for(int r:result){
System.out.println(r);
}

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package Le1a;
import java.util.Scanner;
import java.util.Arrays;
public class Arrayrandom {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入随机数最大值: ");
int n=in.nextInt();
System.out.print("请输入需要生成的随机数的个数: ");
int k=in.nextInt();
//用number数组存放所有的数
int[] number=new int[n];
for (int i=0;i<number.length;i++){
number[i]=i+1;
}
//用一个result数组存放随机数的结果
int[] result = new int[k];
for (int i=0;i<result.length;i++){
int r=(int)(Math.random() * n);
result[i]=number[r];
number[r]=number[n-1];
n--;
}

//调用Arrays的sort排序,并打印result数组
Arrays.sort(result);
System.out.println("随机数生成完毕,请查收!");
for(int r:result)
System.out.print(r+" ");
}
}

多维数组

表格可以用二维数组来存储信息,声明二维数组:double[][] balances;数组必须要经过初始话才可以使用

例如:balances = new double[NYEARS][NRATES]; 如果知道数组的具体元素,则可以不用调用new,直接采用简写方式对多维数组进行初始化,例如:int[][] magicSquare = { {16,3,2,13},{5,10,11,8},{9,6,7,12}};

实例化后,即可利用两个中括号访问各个元素,下面的示例程序中用到了一个存储利率的一维数组interest和一个存储余额的二维数组balances,一维用于表示年份,另一个维表示利率,使用初始余额来初始化这个数组的第一行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package Le1a;

public class Array2 {
public static void main(String[] args) {
final double STARTRATE = 10;
final int NRATES = 6;
final int NYEARS = 10;
//将利率定为 10% . . . 15%
double[] insterestRate = new double[NRATES];
for(int j=0;j< insterestRate.length;j++){
insterestRate[j]=(STARTRATE + j)/100.0;
}

double[][] balances = new double[NYEARS][NRATES];

//将初始余额设置为10000
for (int j=0;j<balances[0].length;j++){
balances[0][j]=10000;
}

//计算未来几年的利息
for (int i=1;i< balances.length;i++){
for (int j=0;j<balances[i].length;j++){
//从上一行获取上一年的余额
double oldBalance = balances[i-1][j];

//计算利息
double interest = oldBalance * insterestRate[j];

//计算今年的余额
balances[i][j]=oldBalance +interest;
}
}
//打印一行利率
for (int j=0;j<insterestRate.length;j++){
System.out.printf("%9.0f%%",100 * insterestRate[j]);
}
System.out.println();

//打印余额表
for (double[] row:balances){
//print table row
for (double b:row){
System.out.printf("%10.2f",b);
}
System.out.println();
}
}
}
/* 运行结果
10% 11% 12% 13% 14% 15%
10000.00 10000.00 10000.00 10000.00 10000.00 10000.00
11000.00 11100.00 11200.00 11300.00 11400.00 11500.00
12100.00 12321.00 12544.00 12769.00 12996.00 13225.00
13310.00 13676.31 14049.28 14428.97 14815.44 15208.75
14641.00 15180.70 15735.19 16304.74 16889.60 17490.06
16105.10 16850.58 17623.42 18424.35 19254.15 20113.57
17715.61 18704.15 19738.23 20819.52 21949.73 23130.61
19487.17 20761.60 22106.81 23526.05 25022.69 26600.20
21435.89 23045.38 24759.63 26584.44 28525.86 30590.23
23579.48 25580.37 27730.79 30040.42 32519.49 35178.76
*/

不规则数组

Java实际上没有多维数组,只有一维数组。多维数组被解释为”数组的数组”。例如前面的示例中,balances数组实际上是一个包含十个元素的数组,而每个元素又是由6个浮点数组成的数组。

1643961551978.png

表达式balances[i]引用第i个子数组,也就是表格的第i行。它本身也是一个数组,balances[i][j]引用这个数组的第j个元素。

还可以构建一个”不规则”数组,即数组的每一行有不同的长度。下面是一个标准的示例。我们将构建一个数组,第i行第j列将存放”从i个数中抽取j个数”可能的结果数。

1
2
3
4
5
6
7
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1

由于j不可能大于i,所以矩阵是三角形的。第i行有i+1个元素(允许抽取0个元素,这种选择只有一种可能)。要想创建一个不规则数组,首先需要分配一个数组包含这些行:

1
int [][] odds = new int[Nmax+1][];

接下来分配这些行:

1
2
3
for(int n=0;n<=odds.length;n++){
odd[n]=new int[n+1];
}

在分配了数组之后,假定没有越界。就可以采用通常的方式访问其中的元素了。

1
2
3
4
5
6
for(int n=0;n<odd.length;n++){
for(int k=0;k<odds[n].length;k++){
...
odds[n][k]=lottertOdds;
}
}

完整程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package Le1a;

public class LotteryArray {
public static void main(String[] args){
final int Nmax=10;
//分配三角形数组
int[][] odds=new int[Nmax+1][];
for (int n=0;n<=Nmax;n++){
odds[n]=new int[n+1];
}
//填充三角形阵列
for (int n=0;n< odds.length;n++) {
for (int k = 0; k < odds[n].length; k++) {
int lottertOdds = 1;
for (int i = 1; i <= k; i++) {
lottertOdds = lottertOdds * (n - i + 1) / i;
}
odds[n][k] = lottertOdds;
}
}
//打印三角形阵列
for (int[] row:odds)
{
for (int odd:row){
System.out.printf("%4d",odd);
}
System.out.println();
}
}
}