12.10.4 使用DefaultTreeCellRenderer改变节点外观

对比图12.38和图12.41所示的两棵树,不难发现图12.38所示的树更美观,因为图12.38所示的树节点的图标非常丰富,而图12.41所示的树节点的图标太过于单一
实际上,JTree也可以改变树节点的外观,包括改变节点的图标、字体等,甚至可以自由绘制节点外观。为了改变树节点的外观,可以通过为树指定自己的CellRenderer来实现,JTree默认使用DefaultTreeCelIRenderer来绘制每个节点。DefaultTreeCellRendererJLabel的子类,该JLabel包含了该节点的图标和文本。

如何改变树节点的外观样式

改变树节点的外观样式,可以有如下三种方式。

  1. 使用DefaultTreeCellRenderer直接改变节点的外观,这种方式可以改变整棵树所有节点的字体颜色和图标
  2. JTree指定DefaultTreeCellRenderer的扩展类对象作为JTree的节点绘制器,该绘制器负责为不同节点使用不同的字体、颜色和图标。通常使用这种方式来改变节点的外观。
  3. JTree指定一个实现TreeCellRenderer接口的节点绘制器,该绘制器可以为不同的节点自由绘制任意内容,这是最复杂但最灵活的节点绘制器。

使用DefaultTreeCellRenderer直接改变节点的外观

第一种方式最简单,但灵活性最差,因为它会改变整棵树所有节点的外观。在这种情况下,JTree的所有节点依然使用相同的图标,相当于整体替换了JTree中节点的所有默认图标。用户指定的节点图标未必就比JTree默认的图标美观。

DefaultTreeCellRenderer提供了如下几个方法来修改节点的外观

  • setBackgroundNonSelectionColor(ColorNewColor:设置用于非选定节点的背景颜色。
  • setBackgroundSelection Color( Color new Color):设置节点在选中状态下的背景颜色。
  • set Selection Color( Color newColor):设置选中状态下节点的边框颜色。
  • setclosedIcon( Icon newIcon):设置处于折叠状态下非叶子节点的图标。
  • setFont( Font font):设置节点文本的字体。
  • setleaficon( Icon newSon):设置叶子节点的图标。
  • setOpenIcon( con newson):设置处于展开状态下非叶子节点的图标。
  • settextNon Selection color( Color new Color):设置绘制非选中状态下节点文本的颜色
  • set TextSelection Color( Color new Color):设置绘制选中状态下节点文本的颜色

程序 直接使用DefaultTreeCellRenderer来改变树节点的外观

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
import java.awt.*;
import javax.swing.*;
import javax.swing.tree.*;

public class ChangeAllCellRender {
JFrame jf = new JFrame("改变所有节点的外观");
JTree tree;
// 定义几个初始节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");

public void init() {
// 通过add()方法建立树节点之间的父子关系
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
root.add(guangdong);
root.add(guangxi);
// 以根节点创建树
tree = new JTree(root);
// 创建一个DefaultTreeCellRender对象
DefaultTreeCellRenderer cellRender = new DefaultTreeCellRenderer();
// 设置非选定节点的背景色。
cellRender.setBackgroundNonSelectionColor(new Color(220, 220, 220));
// 设置节点在选中状态下的背景颜色。
cellRender.setBackgroundSelectionColor(new Color(140, 140, 140));
// 设置选中状态下节点的边框颜色。
cellRender.setBorderSelectionColor(Color.BLACK);
// 设置处于折叠状态下非叶子节点的图标。
cellRender.setClosedIcon(new ImageIcon("icon/close.gif"));
// 设置节点文本的字体。
cellRender.setFont(new Font("SansSerif", Font.BOLD, 16));
// 设置叶子节点的图标。
cellRender.setLeafIcon(new ImageIcon("icon/leaf.png"));
// 设置处于展开状态下非叶子节点的图标。
cellRender.setOpenIcon(new ImageIcon("icon/open.gif"));
// 设置绘制非选中状态下节点文本的颜色。
cellRender.setTextNonSelectionColor(new Color(255, 0, 0));
// 设置绘制选中状态下节点文本的颜色。
cellRender.setTextSelectionColor(new Color(0, 0, 255));
tree.setCellRenderer(cellRender);
// 设置是否显示根节点的“展开/折叠”图标,默认是false
tree.setShowsRootHandles(true);
// 设置节点是否可见,默认是true
tree.setRootVisible(true);
jf.add(new JScrollPane(tree));
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}

public static void main(String[] args) {
new ChangeAllCellRender().init();
}
}

上面程序中的粗体字代码创建了一个DefaultTreeCellRenderer对象,并通过该对象改变了JTree中所有节点的字体、颜色和图标。运行上面程序,会看到如图12.44所示的效果。

从图12.44中可以看出,JTree中的所有节点全部被改变了,相当于完全替代了JTree中所有节点的默认图标、字体和颜色。但所有的叶子节点依然保持相同的外观,所有的非叶子节点也保持相同的外观。这种改变依然不能满足更复杂的需求,例如,如果需要不同类型的节点呈现出不同的外观,则不能直接使用DefaultTreeCellRenderer来改变节点的外观,可以采用扩展DefaultTreeCellrenderer的方式来实现该需求

如何修改展开折叠图标

不要试图通过TreeCellRenderer来改变表示节点展开/折叠的图标,因为该图标是由Metal风格决定的。如果需要改变该图标,则可以考虑改变该JTree的外观风格

12.10.3 监听节点事件

JTree专门提供了一个TreeSelectionModel对象来保存该JTree选中状态的信息。也就是说,JTree组件背后隐藏了两个model对象,其中TreeModel用于保存该JTree的所有节点数据,而TreeSelectionModel用于保存该JTree的所有选中状态的信息

TreeSelectionModel

对于大部分开发者而言,无须关心TreeSelectionModel的存在,程序可以通过JTree提供的getSelectionPath()方法和getSelectionPaths方法来获取该JTree被选中的TreePath,但实际上这两个方法底层实现依然依赖于TreeSelectionModel,只是普通开发者一般无须关心这些底层细节而已。

改变JTree的选择模式

程序可以改变JTree的选择模式,但必须先获取该JTree对应的TreeSelectionModel对象,再调用该对象的setSelectionModel()方法来设置该JTree的选择模式。setSelectionMode方法支持如下三个参数。

  • TreeSelectionModel_CONTIGUOUS_TREE_SELECTION:可以连续选中多个TreePath
  • TreeSelectionModel_DISCONTIGUOUS_TREE_SELECTION:该选项对于选择没有任何限制。
  • TreeSelectionModel_DISCONTIGUOUS_TREE_SELECTION:每次只能选择一个TreePath
方法 描述
void setSelectionMode(int mode) Sets the selection model, which must be one of SINGLE_TREE_SELECTION, CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.

JList操作类似,按下Ctrl辅助键,用于添加选中多个JTree节点:按下Shift辅助键,用于选择连续区域里的所有JTree节点。

方法 描述
void addTreeExpansionListener(TreeExpansionListener tel) 添加树节点展开/折叠事件的监听器
void addTreeSelectionListener(TreeSelectionListener tsl) 添加树节点选择事件的监听器

程序

下面程序设置JTree只能选择单个TreePath,并为节点选择事件添加事件监听器。

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
import javax.swing.*;
import javax.swing.tree.*;

public class SelectJTree {
JFrame jf = new JFrame("监听树的选择事件");
JTree tree;
// 定义几个初始节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国");
DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东");
DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西");
DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山");
DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头");
DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林");
DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁");
JTextArea eventTxt = new JTextArea(5, 20);

public void init() {
// 通过add()方法建立树节点之间的父子关系
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
root.add(guangdong);
root.add(guangxi);
// 以根节点创建树
tree = new JTree(root);
// 设置只能选择一个TreePath
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
// 添加监听树节点选择事件的监听器
// 当JTree中被选择节点发生改变时,将触发该方法
tree.addTreeSelectionListener(e -> {
if (e.getOldLeadSelectionPath() != null)
eventTxt.append("原选中的节点路径:" + e.getOldLeadSelectionPath().toString() + "\n");
eventTxt.append("新选中的节点路径:" + e.getNewLeadSelectionPath().toString() + "\n");
});
// 设置是否显示根节点的“展开/折叠”图标,默认是false
tree.setShowsRootHandles(true);
// 设置根节点是否可见,默认是true
tree.setRootVisible(true);
Box box = new Box(BoxLayout.X_AXIS);
box.add(new JScrollPane(tree));
box.add(new JScrollPane(eventTxt));
jf.add(box);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}

public static void main(String[] args) {
new SelectJTree().init();
}
}

上面程序中设置了该JTree对象采用SINGLE_TREE_SELECTION选择模式,即每次只能选中该JTree的一个TreePath。第二段粗体字代码为该JTree添加了个节点选择事件的监听器,当该JTre中被选择节点发生改变时,该监听器就会被触发。运行上面程序,会看到如图12.43所示的效
果。

不要通过监听鼠标事件来监听所选节点的变化,因为JTree中节点的选择完全可以通过键盘来操作,不通过鼠标单击亦可

4.7 本章小结

本章主要介绍了Java的两种程序流程结构:分支结构和循环结构。本章详细讲解了Java提供的ifswitch分支结构,并详细介绍了Java提供的whiledo whilefor循环结构,以及详细分析了三种循环结构的区别和联系。
除此之外,数组也是本章介绍的重点,本章通过示例程序详细示范了数组的定义、初始化、使用等基本知识,并结合大量示意图深入分析了数组在内存中的运行机制、数组引用变量和数组之间的关系、多维数组的实质等内容。
本章最后还示范了一个多维数组的示例程序:五子棋,希望以此来激发读者的编程热情。

本章练习

1.使用循环输出九九乘法表。输出如下结果:

2.使用循环输出等腰三角形。例如给定4,输出如下结果:

3.通过API文档查询Math类的方法,打印出如右所示的近似圆,只要给定不同半径,圆的大小就会随之发生改变(如果需要使用复杂的数学运算,则可以查阅Math类的方法或者参考7.3节的内容)。

4.实现一个按字节来截取字符串的子串的方法,功能类似于String类的substring方法,String类是按字符截取的,例如”中国abe" substring(1,3),将返回国a”。这里要求按字节截取,一个英文字符当个字节,一个中文字符当两个字节
5.编写一个程序,将浮点数转换成人民币读法字符串,例如,将1006.333转换为壹千零陆元叁角叁分。

6.编写控制台的五子棋游戏。

4.6.6 数组的应用举例

数组的用途是很广泛的,如果程序中有多个类型相同的变量,而且它们具有逻辑的整体性,则可以把它们定义成一个数组。

一维数组 朗读金钱

例如,在实际开发中的一个常用工具函数:需要将一个浮点数转换成人民币读法字符串,这个程序就需要使用数组。实现这个函数的思路是:
首先把这个浮点数分成整数部分和小数部分。
提取整数部分很容易,直接将这个浮点数强制类型转换成一个整数即可,这个整数就是浮点数的整数部分;
再使用浮点数减去整数将可以得到这个浮点数的小数部分
然后分开处理整数部分和小数部分,其中小数部分的处理比较简单,直接截断到保留2位数字,转换成几角几分的字符串。整数部分的处理则稍微复杂一点,但只要认真分析不难发现,中国的数字习惯是4位一节的,一个4位的数字可被转成几千几百几十几,至于后面添加什么单位则不确定,如果这节4位数字出现在14位,则后面添加单位元;
如果这节4位数字出现在5
8位,则后面添加单位万;
如果这节4位数字出现在9~12位,则后面添加单位亿;
多于12位就暂不考虑了。

程序示例

因此实现这个程序的关键就是把一个4位数字字符串转换成一个中文读法。下面程序把这个需求实现了一部分。

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
import java.util.Arrays;

public class Num2Rmb {
private String[] hanArr = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };
private String[] unitArr = { "十", "百", "千" };

/**
* 把一个浮点数分解成整数部分和小数部分字符串
*
* @param num 需要被分解的浮点数
* @return 分解出来的整数部分和小数部分。第一个数组元素是整数部分,第二个数组元素是小数部分。
*/
private String[] divide(double num) {
// 将一个浮点数强制类型转换为long,即得到它的整数部分
long zheng = (long) num;
// 浮点数减去整数部分,得到小数部分,小数部分乘以100后再取整得到2位小数
long xiao = Math.round((num - zheng) * 100);
// 下面用了2种方法把整数转换为字符串
return new String[] { zheng + "", String.valueOf(xiao) };
}

/**
* 把一个四位的数字字符串变成汉字字符串
*
* @param numStr 需要被转换的四位的数字字符串
* @return 四位的数字字符串被转换成的汉字字符串。
*/
private String toHanStr(String numStr) {
String result = "";
int numLen = numStr.length();
// 依次遍历数字字符串的每一位数字
for (int i = 0; i < numLen; i++) {
// 把char型数字转换成的int型数字,因为它们的ASCII码值恰好相差48
// 因此把char型数字减去48得到int型数字,例如'4'被转换成4。
int num = numStr.charAt(i) - 48;
// 如果不是最后一位数字,而且数字不是零,则需要添加单位(千、百、十)
if (i != numLen - 1 && num != 0) {
result += hanArr[num] + unitArr[numLen - 2 - i];
}
// 否则不要添加单位
else {
result += hanArr[num];
}
}
return result;
}

public static void main(String[] args) {
Num2Rmb nr = new Num2Rmb();
// 测试把一个浮点数分解成整数部分和小数部分
System.out.println(Arrays.toString(nr.divide(236711125.123)));
// 测试把一个四位的数字字符串变成汉字字符串
System.out.println(nr.toHanStr("609"));
// System.out.println(nr.toHanStr("6092"));
}
}

运行上面程序,看到如下运行结果:

1
2
[236711125, 12]
陆百零玖

从上面程序的运行结果来看,初步实现了所需功能,但这个程序并不是这么简单,对零的处理比较复杂。例如,有两个零连在一起时该如何处理呢?如果最高位是零如何处理呢?最低位是零又如何处理呢?因此这个程序还需要继续完善,希望读者能把这个程序写完。

二维数组 实现 五子棋

除此之外,还可以利用二维数组来完成五子棋、连连看、俄罗斯方块、扫雷等常见小游戏。下面简单介绍利用二维数组实现五子棋。先定义一个二维数组作为下棋的棋盘,每当一个棋手下一步棋后,也就是为二维数组的一个数组元素赋值。下面程序完成了这个程序的初步功能。

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
import java.io.*;

public class Gobang {
// 定义棋盘的大小
private static int BOARD_SIZE = 15;
// 定义一个二维数组来充当棋盘
private String[][] board;

public void initBoard() {
// 初始化棋盘数组
board = new String[BOARD_SIZE][BOARD_SIZE];
// 把每个元素赋为"╋",用于在控制台画出棋盘
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
board[i][j] = "╋";
}
}
}

// 在控制台输出棋盘的方法
public void printBoard() {
// 打印每个数组元素
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
// 打印数组元素后不换行
System.out.print(board[i][j]);
}
// 每打印完一行数组元素后输出一个换行符
System.out.print("\n");
}
}

public static void main(String[] args) throws Exception {
Gobang gb = new Gobang();
gb.initBoard();
gb.printBoard();
// 这是用于获取键盘输入的方法
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String inputStr = null;
// br.readLine():每当在键盘上输入一行内容按回车,用户刚输入的内容将被br读取到。
while ((inputStr = br.readLine()) != null) {
// 将用户输入的字符串以逗号(,)作为分隔符,分隔成2个字符串
String[] posStrArr = inputStr.split(",");
// 将2个字符串转换成用户下棋的座标
int xPos = Integer.parseInt(posStrArr[0]);
int yPos = Integer.parseInt(posStrArr[1]);
// 把对应的数组元素赋为"●"。
gb.board[yPos - 1][xPos - 1] = "●";
/*
* 电脑随机生成2个整数,作为电脑下棋的座标,赋给board数组。 还涉及 1.座标的有效性,只能是数字,不能超出棋盘范围 2.如果下的棋的点,不能重复下棋。
* 3.每次下棋后,需要扫描谁赢了
*/
gb.printBoard();
System.out.println("请输入您下棋的座标,应以x,y的格式:");
}
}
}

运行上面程序,将看到如下所示的界面:

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
G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\04\4.6>java Gobang
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
5,5
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋●╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
请输入您下棋的座标,应以x,y的格式:
5,6
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋●╋╋╋╋╋╋╋╋╋╋
╋╋╋╋●╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋
请输入您下棋的座标,应以x,y的格式:

从图4.16来看,程序上面显示的黑点一直是棋手下的棋电脑还没有下棋,电脑下棋可以使用随机生成两个坐标值来控制,当然也可以增加人工智能来控制下棋。
除此之外,读者还需要在这个程序的基础上进行完善,保证用户和电脑下的棋的坐标上不能已经有棋子(通过判断对应数组元素只能是”十”来确定),还需要进行4次循环扫描,判断横、竖、左斜、右斜是否有5个棋连在一起,从而判定胜负。

4.6.5 Java8增强的工具类 Arrays

Java提供的Arrays类里包含的一些static修饰的方法可以直接操作数组,这个Arrays类里包含了如下几个static修饰的方法(static修饰的方法可以直接通过类名调用)。
未完待续…

程序示例

下面程序示范了Arrays类的用法:

Java8增强的Arrays方法

Java8增强了Arrays类的功能,为Arrays类增加了一些工具方法,这些工具方法可以充分利用**多CPU**并行的能力来提高设值、排序的性能。下面是Java8Arrays类增加的工具方法。

binarySearch方法

方法 描述
static int binarySearch(byte[] a, byte key) 使用二分法查询key元素值在a数组中出现的索引;如果a数组不包含key元素值,则返回负数。调用该方法时要求数组中元素已经按升序排列,这样才能得到正确结果
static int binarySearch(byte[] a, int fromIndex, int toIndex, byte key) 这个方法与前一个方法类似,但它只搜索a数组中fromIndextolndex索引的元素。调用该方法时要求数组中元素已经按升序排列,这样才能得到正确结果。
static int binarySearch(char[] a, char key) Searches the specified array of chars for the specified value using the binary search algorithm.
static int binarySearch(char[] a, int fromIndex, int toIndex, char key) Searches a range of the specified array of chars for the specified value using the binary search algorithm.
static int binarySearch(double[] a, double key) Searches the specified array of doubles for the specified value using the binary search algorithm.
static int binarySearch(double[] a, int fromIndex, int toIndex, double key) Searches a range of the specified array of doubles for the specified value using the binary search algorithm.
static int binarySearch(float[] a, float key) Searches the specified array of floats for the specified value using the binary search algorithm.
static int binarySearch(float[] a, int fromIndex, int toIndex, float key) Searches a range of the specified array of floats for the specified value using the binary search algorithm.
static int binarySearch(int[] a, int key) Searches the specified array of ints for the specified value using the binary search algorithm.
static int binarySearch(int[] a, int fromIndex, int toIndex, int key) Searches a range of the specified array of ints for the specified value using the binary search algorithm.
static int binarySearch(long[] a, int fromIndex, int toIndex, long key) Searches a range of the specified array of longs for the specified value using the binary search algorithm.
static int binarySearch(long[] a, long key) Searches the specified array of longs for the specified value using the binary search algorithm.
static int binarySearch(short[] a, int fromIndex, int toIndex, short key) Searches a range of the specified array of shorts for the specified value using the binary search algorithm.
static int binarySearch(short[] a, short key) Searches the specified array of shorts for the specified value using the binary search algorithm.
static int binarySearch(Object[] a, int fromIndex, int toIndex, Object key) Searches a range of the specified array for the specified object using the binary search algorithm.
static int binarySearch(Object[] a, Object key) Searches the specified array for the specified object using the binary search algorithm.
static <T> int binarySearch(T[] a, int fromIndex, int toIndex, T key, Comparator<? super T> c) Searches a range of the specified array for the specified object using the binary search algorithm.
static <T> int binarySearch(T[] a, T key, Comparator<? super T> c) Searches the specified array for the specified object using the binary search algorithm.

copyOf方法

方法 描述
static boolean[] copyOf(boolean[] original, int newLength) 这个方法将会把original数组复制成一个新数组,其中length是新数组的长度。如果length小于original数组的长度,则新数组就是原数组的前面length个元素;如果length大于original数组的长度,则新数组的前面元素就是原数组的所有元素,后面补充0(数值类型)、false(布尔类型)或者null(引用类型)
static byte[] copyOf(byte[] original, int newLength) Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length.
static char[] copyOf(char[] original, int newLength) Copies the specified array, truncating or padding with null characters (if necessary) so the copy has the specified length.
static double[] copyOf(double[] original, int newLength) Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length.
static float[] copyOf(float[] original, int newLength) Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length.
static int[] copyOf(int[] original, int newLength) Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length.
static long[] copyOf(long[] original, int newLength) Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length.
static short[] copyOf(short[] original, int newLength) Copies the specified array, truncating or padding with zeros (if necessary) so the copy has the specified length.
static <T> T[] copyOf(T[] original, int newLength) Copies the specified array, truncating or padding with nulls (if necessary) so the copy has the specified length.
static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) Copies the specified array, truncating or padding with nulls (if necessary) so the copy has the specified length.

copyOfRange方法

方法 描述
static boolean[] copyOfRange(boolean[] original, int from, int to) 这个方法与前面方法相似,但这个方法只复制original数组的from索引到to索引的元素。
static byte[] copyOfRange(byte[] original, int from, int to) Copies the specified range of the specified array into a new array.
static char[] copyOfRange(char[] original, int from, int to) Copies the specified range of the specified array into a new array.
static double[] copyOfRange(double[] original, int from, int to) Copies the specified range of the specified array into a new array.
static float[] copyOfRange(float[] original, int from, int to) Copies the specified range of the specified array into a new array.
static int[] copyOfRange(int[] original, int from, int to) Copies the specified range of the specified array into a new array.
static long[] copyOfRange(long[] original, int from, int to) Copies the specified range of the specified array into a new array.
static short[] copyOfRange(short[] original, int from, int to) Copies the specified range of the specified array into a new array.
static <T> T[] copyOfRange(T[] original, int from, int to) Copies the specified range of the specified array into a new array.
static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) Copies the specified range of the specified array into a new array.

fill方法

方法 描述
static void fill(boolean[] a, boolean val) 该方法将会把a数组的所有元素都赋值为val
static void fill(boolean[] a, int fromIndex, int toIndex, boolean val) 该方法与前一个方法的作用相同,区别只是该方法仅仅将a数组的fromIndextoIndex索引的数组元素赋值为val
static void fill(byte[] a, byte val) Assigns the specified byte value to each element of the specified array of bytes.
static void fill(byte[] a, int fromIndex, int toIndex, byte val) Assigns the specified byte value to each element of the specified range of the specified array of bytes.
static void fill(char[] a, char val) Assigns the specified char value to each element of the specified array of chars.
static void fill(char[] a, int fromIndex, int toIndex, char val) Assigns the specified char value to each element of the specified range of the specified array of chars.
static void fill(double[] a, double val) Assigns the specified double value to each element of the specified array of doubles.
static void fill(double[] a, int fromIndex, int toIndex, double val) Assigns the specified double value to each element of the specified range of the specified array of doubles.
static void fill(float[] a, float val) Assigns the specified float value to each element of the specified array of floats.
static void fill(float[] a, int fromIndex, int toIndex, float val) Assigns the specified float value to each element of the specified range of the specified array of floats.
static void fill(int[] a, int val) Assigns the specified int value to each element of the specified array of ints.
static void fill(int[] a, int fromIndex, int toIndex, int val) Assigns the specified int value to each element of the specified range of the specified array of ints.
static void fill(long[] a, int fromIndex, int toIndex, long val) Assigns the specified long value to each element of the specified range of the specified array of longs.
static void fill(long[] a, long val) Assigns the specified long value to each element of the specified array of longs.
static void fill(short[] a, int fromIndex, int toIndex, short val) Assigns the specified short value to each element of the specified range of the specified array of shorts.
static void fill(short[] a, short val) Assigns the specified short value to each element of the specified array of shorts.
static void fill(Object[] a, int fromIndex, int toIndex, Object val) Assigns the specified Object reference to each element of the specified range of the specified array of Objects.
static void fill(Object[] a, Object val) Assigns the specified Object reference to each element of the specified array of Objects.

sort方法

方法 描述
static void sort(byte[] a) 该方法对a数组的数组元素进行排序
static void sort(byte[] a, int fromIndex, int toIndex) 该方法与前一个方法相似,区别是该方法仅仅对fromIndextoIndex索引的元素进行排序。
static void sort(char[] a) Sorts the specified array into ascending numerical order.
static void sort(char[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending order.
static void sort(double[] a) Sorts the specified array into ascending numerical order.
static void sort(double[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending order.
static void sort(float[] a) Sorts the specified array into ascending numerical order.
static void sort(float[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending order.
static void sort(int[] a) Sorts the specified array into ascending numerical order.
static void sort(int[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending order.
static void sort(long[] a) Sorts the specified array into ascending numerical order.
static void sort(long[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending order.
static void sort(short[] a) Sorts the specified array into ascending numerical order.
static void sort(short[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending order.
static void sort(Object[] a) Sorts the specified array of objects into ascending order, according to the natural ordering of its elements.
static void sort(Object[] a, int fromIndex, int toIndex) Sorts the specified range of the specified array of objects into ascending order, according to the natural ordering of its elements.
static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) Sorts the specified range of the specified array of objects according to the order induced by the specified comparator.
static <T> void sort(T[] a, Comparator<? super T> c) Sorts the specified array of objects according to the order induced by the specified comparator.

equals方法

方法 描述
static boolean equals(boolean[] a, boolean[] a2) 如果a数组和a2数组的长度相等,而且a数组和a2数组的数组元素也一一相同,该方法将返回true
static boolean equals(boolean[] a, int aFromIndex, int aToIndex, boolean[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of booleans, over the specified ranges, are equal to one another.
static boolean equals(byte[] a, byte[] a2) Returns true if the two specified arrays of bytes are equal to one another.
static boolean equals(byte[] a, int aFromIndex, int aToIndex, byte[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of bytes, over the specified ranges, are equal to one another.
static boolean equals(char[] a, char[] a2) Returns true if the two specified arrays of chars are equal to one another.
static boolean equals(char[] a, int aFromIndex, int aToIndex, char[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of chars, over the specified ranges, are equal to one another.
static boolean equals(double[] a, double[] a2) Returns true if the two specified arrays of doubles are equal to one another.
static boolean equals(double[] a, int aFromIndex, int aToIndex, double[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of doubles, over the specified ranges, are equal to one another.
static boolean equals(float[] a, float[] a2) Returns true if the two specified arrays of floats are equal to one another.
static boolean equals(float[] a, int aFromIndex, int aToIndex, float[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of floats, over the specified ranges, are equal to one another.
static boolean equals(int[] a, int[] a2) Returns true if the two specified arrays of ints are equal to one another.
static boolean equals(int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of ints, over the specified ranges, are equal to one another.
static boolean equals(long[] a, int aFromIndex, int aToIndex, long[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of longs, over the specified ranges, are equal to one another.
static boolean equals(long[] a, long[] a2) Returns true if the two specified arrays of longs are equal to one another.
static boolean equals(short[] a, int aFromIndex, int aToIndex, short[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of shorts, over the specified ranges, are equal to one another.
static boolean equals(short[] a, short[] a2) Returns true if the two specified arrays of shorts are equal to one another.
static boolean equals(Object[] a, int aFromIndex, int aToIndex, Object[] b, int bFromIndex, int bToIndex) Returns true if the two specified arrays of Objects, over the specified ranges, are equal to one another.
static boolean equals(Object[] a, Object[] a2) Returns true if the two specified arrays of Objects are equal to one another.
static <T> boolean equals(T[] a, int aFromIndex, int aToIndex, T[] b, int bFromIndex, int bToIndex, Comparator<? super T> cmp) Returns true if the two specified arrays of Objects, over the specified ranges, are equal to one another.
static <T> boolean equals(T[] a, T[] a2, Comparator<? super T> cmp) Returns true if the two specified arrays of Objects are equal to one another.

toString方法

方法 描述
static String toString(boolean[] a) 该方法将一个数组转换成一个字符串。该方法按顺序把多个数组元素连缀在一起,多个数组元素使用英文逗号(,)和空格隔开。
static String toString(byte[] a) Returns a string representation of the contents of the specified array.
static String toString(char[] a) Returns a string representation of the contents of the specified array.
static String toString(double[] a) Returns a string representation of the contents of the specified array.
static String toString(float[] a) Returns a string representation of the contents of the specified array.
static String toString(int[] a) Returns a string representation of the contents of the specified array.
static String toString(long[] a) Returns a string representation of the contents of the specified array.
static String toString(short[] a) Returns a string representation of the contents of the specified array.
static String toString(Object[] a) Returns a string representation of the contents of the specified array.

程序示例 Arrays类Java8新增方法

下面程序示范了Java8Arrays类新增的方法:

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
import java.util.Arrays;

public class ArraysTest {
public static void main(String[] args) {
// 定义一个a数组
int[] a = new int[] { 3, 4, 5, 6 };
// 定义一个a2数组
int[] a2 = new int[] { 3, 4, 5, 6 };
// a数组和a2数组的长度相等,每个元素依次相等,将输出true
System.out.println("a数组和a2数组是否相等:" + Arrays.equals(a, a2));
// 通过复制a数组,生成一个新的b数组
int[] b = Arrays.copyOf(a, 6);
System.out.println("a数组和b数组是否相等:" + Arrays.equals(a, b));
// 输出b数组的元素,将输出[3, 4, 5, 6, 0, 0]
System.out.println("b数组的元素为:" + Arrays.toString(b));
// 将b数组的第3个元素(包括)到第5个元素(不包括)赋为1
Arrays.fill(b, 2, 4, 1);
// 输出b数组的元素,将输出[3, 4, 1, 1, 0, 0]
System.out.println("b数组的元素为:" + Arrays.toString(b));
// 对b数组进行排序
Arrays.sort(b);
// 输出b数组的元素,将输出[0, 0, 1, 1, 3, 4]
System.out.println("b数组的元素为:" + Arrays.toString(b));
}
}

System类的arrayCopy复制数组

除此之外,在System类里也包含了一个arrayCopy方法,该方法可以将src数组里的元素值赋给dest数组的元素,其中srcPos指定从src数组的第几个元素开始赋值,length参数指定将src数组的多少个元素值赋给dest数组的元素。

方法 描述
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array.

java8新增方法

Java8增强了Arrays类的功能,为Arrays类增加了一些工具方法,这些工具方法可以充分利用多CPU并行的能力来提高设值、排序的性能。下面是Java8Arrays类增加的工具方法。
下面的方法中,所有parallel开头的方法都表示该方法可利用CPU并行的能力来提高性能

parallelPrefix方法

方法 描述
static void parallelPrefix(double[] array, DoubleBinaryOperator op) 该方法使用op参数指定的计算公式计算得到的结果作为新的元素。op计算公式包括letright两个形参,其中left代表数组中前一个索引处的元素,ight代表数组中当前索引处的元素,当计算第一个新数组元素时,left的值默认为1
static void parallelPrefix(double[] array, int fromIndex, int toIndex, DoubleBinaryOperator op) 该方法与上一个方法相似,区别是该方法仅重新计算fromIndextolndex索引的元素。
static void parallelPrefix(int[] array, int fromIndex, int toIndex, IntBinaryOperator op) Performs parallelPrefix(int[], IntBinaryOperator) for the given subrange of the array.
static void parallelPrefix(int[] array, IntBinaryOperator op) Cumulates, in parallel, each element of the given array in place, using the supplied function.
static void parallelPrefix(long[] array, int fromIndex, int toIndex, LongBinaryOperator op) Performs parallelPrefix(long[], LongBinaryOperator) for the given subrange of the array.
static void parallelPrefix(long[] array, LongBinaryOperator op) Cumulates, in parallel, each element of the given array in place, using the supplied function.
static <T> void parallelPrefix(T[] array, int fromIndex, int toIndex, BinaryOperator<T> op) Performs parallelPrefix(Object[], BinaryOperator) for the given subrange of the array.
static <T> void parallelPrefix(T[] array, BinaryOperator<T> op) Cumulates, in parallel, each element of the given array in place, using the supplied function.

setAll方法

方法 描述
static void setAll(double[] array, IntToDoubleFunction generator) 该方法使用指定的生成器(generator)为所有数组元素设置值,该生成器控制数组元素的值的生成算法。
static void setAll(long[] array, IntToLongFunction generator) Set all elements of the specified array, using the provided generator function to compute each element.
static void setAll(int[] array, IntUnaryOperator generator) Set all elements of the specified array, using the provided generator function to compute each element.
static <T> void setAll(T[] array, IntFunction<? extends T> generator) Set all elements of the specified array, using the provided generator function to compute each element.

parallelSetAll方法

方法 描述
static void parallelSetAll(double[] array, IntToDoubleFunction generator) 该方法的功能与上一个方法相同,只是该方法增加了并行能力,可以利用多CPU并行来提高性能
static void parallelSetAll(int[] array, IntUnaryOperator generator) Set all elements of the specified array, in parallel, using the provided generator function to compute each element.
static void parallelSetAll(long[] array, IntToLongFunction generator) Set all elements of the specified array, in parallel, using the provided generator function to compute each element.
static <T> void parallelSetAll(T[] array, IntFunction<? extends T> generator) Set all elements of the specified array, in parallel, using the provided generator function to compute each element.

parallelSort方法

方法 描述
static void parallelSort(byte[] a)该方法的功能与Arrays类以前就有的sort()方法相似,只是该方法增加了并行能力,可以利用多CPU并行来提高性能。
static void parallelSort(byte[] a, int fromIndex, int toIndex) 该方法与上一个方法相似,区别是该方法仅加了并行能力,可以利用多CPU并行来提高性能。
static void parallelSort(char[] a) Sorts the specified array into ascending numerical order.
static void parallelSort(char[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending numerical order.
static void parallelSort(double[] a) Sorts the specified array into ascending numerical order.
static void parallelSort(double[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending numerical order.
static void parallelSort(float[] a) Sorts the specified array into ascending numerical order.
static void parallelSort(float[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending numerical order.
static void parallelSort(int[] a) Sorts the specified array into ascending numerical order.
static void parallelSort(int[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending numerical order.
static void parallelSort(long[] a) Sorts the specified array into ascending numerical order.
static void parallelSort(long[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending numerical order.
static void parallelSort(short[] a) Sorts the specified array into ascending numerical order.
static void parallelSort(short[] a, int fromIndex, int toIndex) Sorts the specified range of the array into ascending numerical order.
static <T extends Comparable<? super T>> void parallelSort(T[] a)
Sorts the specified array of objects into ascending order, according to the natural ordering of its elements. static <T extends Comparable<? super T>>
void parallelSort(T[] a, int fromIndex, int toIndex) Sorts the specified range of the specified array of objects into ascending order, according to the natural ordering of its elements.
static <T> void parallelSort(T[] a, int fromIndex, int toIndex, Comparator<? super T> cmp) Sorts the specified range of the specified array of objects according to the order induced by the specified comparator.
static <T> void parallelSort(T[] a, Comparator<? super T> cmp) Sorts the specified array of objects according to the order induced by the specified comparator.

spliterator方法

方法 描述
static Spliterator.OfDouble spliterator(double[] array) 将该数组的所有元素转换成对应的Spliterator对象。
static Spliterator.OfDouble spliterator(double[] array, int startInclusive, int endExclusive) 该方法与上一个方法相似,区别是该方法仅转换startInclusiveendExclusive索引的元素。
static Spliterator.OfInt spliterator(int[] array) Returns a Spliterator.OfInt covering all of the specified array.
static Spliterator.OfInt spliterator(int[] array, int startInclusive, int endExclusive) Returns a Spliterator.OfInt covering the specified range of the specified array.
static Spliterator.OfLong spliterator(long[] array) Returns a Spliterator.OfLong covering all of the specified array.
static Spliterator.OfLong spliterator(long[] array, int startInclusive, int endExclusive) Returns a Spliterator.OfLong covering the specified range of the specified array.
static <T> Spliterator<T> spliterator(T[] array) Returns a Spliterator covering all of the specified array.
static <T> Spliterator<T> spliterator(T[] array, int startInclusive, int endExclusive) Returns a Spliterator covering the specified range of the specified array.

stream方法

方法 描述
static DoubleStream stream(double[] array) 该方法将数组转换为Stream,StreamJava8新增的流式编程的API
static DoubleStream stream(double[] array, int startInclusive, int endExclusive) 该方法与上一个方法相似,区别是该方法仅将fromIndextoIndex索引的元素转换为Stream
static IntStream stream(int[] array) Returns a sequential IntStream with the specified array as its source.
static IntStream stream(int[] array, int startInclusive, int endExclusive) Returns a sequential IntStream with the specified range of the specified array as its source.
static LongStream stream(long[] array) Returns a sequential LongStream with the specified array as its source.
static LongStream stream(long[] array, int startInclusive, int endExclusive) Returns a sequential LongStream with the specified range of the specified array as its source.
static <T> Stream<T> stream(T[] array) Returns a sequential Stream with the specified array as its source.
static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) Returns a sequential Stream with the specified range of the specified array as its source.

程序示例 Arrays类java8新增方法

下面程序示范了Java8Arrays类新增的方法:

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
import java.util.Arrays;
import java.util.function.*;

public class ArraysTest2 {
public static void main(String[] args) {
int[] arr1 = new int[] { 3, -4, 25, 16, 30, 18 };
// 对数组arr1进行并发排序
Arrays.parallelSort(arr1);
System.out.println("arr1="+Arrays.toString(arr1));


int[] arr2 = new int[] { 3, -4, 25, 16, 30, 18 };
Arrays.parallelPrefix(arr2, new IntBinaryOperator() {
// left代表数组中前一个所索引处的元素,计算第一个元素时,left为1
// right代表数组中当前索引处的元素
public int applyAsInt(int left, int right) {
return left * right;
}
});
System.out.println("arr2="+Arrays.toString(arr2));


int[] arr3 = new int[5];
Arrays.parallelSetAll(arr3, new IntUnaryOperator() {
// operand代表正在计算的元素索引
public int applyAsInt(int operand) {
return operand * 5;
}
});
System.out.println("arr3="+Arrays.toString(arr3));
}
}

上面程序中调用了parallelSort()方法对数组执行排序,该方法的功能与传统sort()方法大致相似,只是在多CPU机器上会有更好的性能。

第二段代码使用的计算公式为left*right,其中left代表数组中前一个索引处的元素,right代表数组中当前索引处的元素。程序使用的数组为

1
[-4, 3, 16, 18, 25, 30]

计算新的数组元素的方式为:

1
[1*3=3,3*-4=-12,-12*25=-300,-300*16=-48000,-48000*30=-144000,-144000*18=-2592000]

因此将会得到如下新的数组元素:

1
[3, -12, -300, -4800, -144000, -2592000]

第三段代码使用公式operand*5来设置数组元素,该公式中operand代表正在计算的数组元素的索引。因此第三段粗体字代码计算得到的数组为:

1
[0, 5, 10, 15, 20]

4.6.4 没有多维数组

Java语言里提供了支持多维数组的语法。但本书还是想说,没有多维数组一一如果从数组底层的运行机制上来看

Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。
回到前面定义数组类型的语法:type[] ArrName;,这是典型的一维数组的定义语法,其中type是数组元素的类型。如果希望数组元素也是一个引用,而且是指向int数组的引用,则可以把type具体成int[],那么上面定义数组的语法就是int[][] ArrName.

二维数组语法

如果把int这个类型扩大到Java的所有类型(不包括数组类型),则出现了定义二维数组的语法:

1
type[][] arrName;

Java语言采用上面的语法格式来定义二维数组,但它的实质还是一维数组,只是其数组元素也是引用,数组元素里保存的引用指向一维数组。
接着对这个“二维数组”执行初始化,同样可以把二维数组当成一维数组来初始化,把这个“二维数组”当成一个一维数组,其元素的类型是type[]类型,则可以采用如下语法进行初始化

1
arrName = new type [length][];

上面的初始化语法相当于初始化了一个一维数组,这个一维数组的长度是length。同样,因为这个一维数组的数组元素是引用类型(数组类型)的,所以系统为每个数组元素都分配初始值:null

这个二维数组实际上完全可以当成一维数组使用:使用new Type[length]初始化一维数组后,相当于定义了lengthtype类型的变量;类似的,使用new Type[length][]初始化这个数组后,相当于定义了lengthtype[]类型的变量,当然,这些type[]类型的变量都是数组类型,因此必须再次初始化这些数组。

程序示例 以一维数组的方式处理二维数组

下面程序示范了如何把二维数组当成一维数组处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个二维数组
int[][] a;
// 把a当成一维数组进行初始化,初始化a是一个长度为4的数组
// a数组的数组元素又是引用类型
a = new int[4][];
// 把a数组当成一维数组,遍历a数组的每个数组元素
for (int i = 0, len = a.length; i < len; i++) {
System.out.println(a[i]);
}
// 初始化a数组的第一个元素
a[0] = new int[2];
// 访问a数组的第一个元素所指数组的第二个元素
a[0][1] = 6;
// a数组的第一个元素是一个一维数组,遍历这个一维数组
for (int i = 0, len = a[0].length; i < len; i++) {
System.out.println(a[0][i]);
}

上面程序把a这个二维数组当成一维数组处理,只是每个数组元素都是null所看到输出结果都是null下面结合示意图来说明这个程序的执行过程。

程序的第一行int[][] a;,将在栈内存中定义一个引用变量,这个变量并未指向任何有效的内存空间,此时的堆内存中还未为这行代码分配任何存储区.

程序对a数组执行初始化:a = new int[4][];,这行代码让a变量指向一块长度为4的数组内存,这个长度为4的数组里每个数组元素都是引用类型(数组类型),系统为这些数组元素分配默认的初始值:null此时a数组在内存中的存储示意图如图4.12所示:

这里有一张图片

从图4.12来看,虽然声明a是一个二维数组,但这里丝毫看不出它是一个二维数组的样子,完全是一维数组的样子。这个一维数组的长度是4,只是这4个数组元素都是引用类型,它们的默认值是null,所以程序中可以把a数组当成一维数组处理,依次遍历a数组的每个元素,将看到每个数组元素的值都是null

由于a数组的元素必须是int[]数组,所以接下来的程序对a[0]元素执行初始化也就是让图4.12右边堆内存中的第一个数组元素指向一个有效的数组内存,指向一个长度为2的int数组。因为程序采用动态初始化a[0]数组,因此系统将为a[0]所引用数组的每个元素分配默认的初始值:0,然后程序显式为a[0]数组的第二个元素赋值为6。此时在内存中的存储示意图如图4.13所示:

这里有一张图片

图4.13中灰色覆盖的数组元素就是程序显式指定的数组元素值。接着迭代输出a[0]数组的每个数组元素,将看到输出0和6。

初始化多维数组时 可以只指定最左边维度的大小

从上面程序中可以看出,初始化多维数组时,可以只指定最左边维的大小;

初始化多维数组时 可以同时指定所有维度的大小

当然,也可以一次指定每一维的大小。例如下面代码:

1
2
// 同时初始化二维数组的2个维数
int[][] b = new int[3][4];

上面代码将定义一个b数组变量,这个数组变量指向一个长度为3的数组,这个数组的每个数组元素又是一个数组类型,它们各指向对应的长度为4的int[]数组,每个数组元素的值为0。这行代码执行后在内存中的存储示意图如图4.14所示
这里有一张图片

使用静态初始化

还可以使用静态初始化方式来初始化二维数组。使用静态初始化方式来初始化二维数组时,二维数组的每个数组元素都是一维数组,因此必须指定多个一维数组作为二维数组的初始化值。如下代码所示

1
2
3
4
5
6
// 使用静态初始化的语法来初始化一个二维数组
String[][] str1 = new String[][] { new String[3], new String[] { "hello" } };
// 使用简化的静态初始化语法来初始化二维数组
String[][] str2 = { new String[3], new String[] { "hello" } };
System.out.println(str1[1][0]);
System.out.println(str2[1][0]);

上面代码执行后内存中的存储示意图如图4.15所示
这里有一张图片
通过上面讲解可以得到一个结论:

  • 二维数组是一维数组,其数组元素是一维数组;
  • 三维数组也是一维数组,其数组元素是二维数组

从这个角度来看,Java语言里没有多维数组

4.6.3 引用类型数组的初始化

引用类型数组的数组元素是引用,因此情况变得更加复杂。每个数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了有效数据。

程序示例 引用类型数组 初始化 示意图

为了更好地说明引用类型数组的运行过程,下面先定义一个Person类:

1
2
3
4
5
6
7
8
9
class Person {
public int age; // 年龄
public double height; // 身高
// 定义一个info方法

public void info() {
System.out.println("我的年龄是:" + age + ",我的身高是:" + height);
}
}

下面程序将定义一个Person数组,接着动态初始化这个Person数组,并为这个数组的每个数组元素指定值。

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
public class ReferenceArrayTest {
public static void main(String[] args) {
// 定义一个students数组变量,其类型是Person[]
Person[] students;
// 执行动态初始化
students = new Person[2];
// 创建一个Person实例,并将这个Person实例赋给zhang变量
Person zhang = new Person();
// 为zhang所引用的Person对象的age、height赋值
zhang.age = 15;
zhang.height = 158;
// 创建一个Person实例,并将这个Person实例赋给lee变量
Person lee = new Person();
// 为lee所引用的Person对象的age、height赋值
lee.age = 16;
lee.height = 161;
// 将zhang变量的值赋给第一个数组元素
students[0] = zhang;
// 将lee变量的值赋给第二个数组元素
students[1] = lee;
// 下面两行代码的结果完全一样,因为lee
// 和students[1]指向的是同一个Person实例。
lee.info();
students[1].info();
}
}

上面代码的执行过程代表了引用类型数组初始化的典型过程。下面将结合示意图详细介绍这段代码的执行过程。
执行Person[] students;代码时,这行代码仅仅在栈内存中定义了一个引用变量,也就是一个指针,这个指针并未指向任何有效的内存区。此时内存中存储示意图如图4.8所示:
这里有一张图片
在如图4.8所示的栈内存中定义了一个students变量,它仅仅是一个引用,并未指向任何有效的内存。直到执行初始化,本程序对students数组执行动态初始化,动态初始化由系统为数组元素分配默认的初始值:null,即每个数组元素的值都是null执行动态初始化后的存储示意图如图4.9所示
这里有一张图片
从图4.9中可以看出,students数组的两个数组元素都是引用,而且这个引用并未指向任何有效的内存,因此每个数组元素的值都是null。这意味着依然不能直接使用students数组元素,因为每个数组元素都是null,这相当于定义了两个连续的Person变量,但这个变量还未指向任何有效的内存区,所以这两个连续的Person变量(students数组的数组元素)还不能使用。

接着的代码定义了zhanglee两个Person实例,定义这两个实例实际上分配了4块内存,在栈内存中存储了zhanglee两个引用变量,还在堆内存中存储了两个Person实例。此时的内存存储示意图如图4.10所示。
这里有一张图片
此时students数组的两个数组元素依然是null,直到程序依次将zhang赋给students数组的第一个元素,把lee赋给students数组的第二个元素,students数组的两个数组元素将会指向有效的内存区。此时的内存存储示意图如图4.11所示。
这里有一张图片
从图4.11中可以看出,此时zhangstudents[0]指向同一个内存区,而且它们都是引用类型变量,因此通过zhangstudents[0]来访问Person实例的实例变量和方法的效果完全一样,不论修改students[0]所指向的Person实例的实例变量,还是修改zhang变量所指向的Person实例的实例变量,所修改的其实是同一个内存区,所以必然互相影响。同理,leestudents[1]也是引用同一个Person对象,也具有相同的效果。

4.6.2 基本类型数组的初始化

对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将数组元素的值存入对应数组元素中。

程序示例 基本类型数组的初始化 示意图

下面程序定义了一个int类型的数组变量,采用动态初始化的方式初始化了该数组,并显式为每个数组元素赋值

1
2
3
4
5
6
7
8
9
10
11
12
public class PrimitiveArrayTest {
public static void main(String[] args) {
// 定义一个int[]类型的数组变量
int[] iArr;
// 动态初始化数组,数组长度为5
iArr = new int[5];
// 采用循环方式为每个数组元素赋值。
for (int i = 0; i < iArr.length; i++) {
iArr[i] = i + 10;
}
}
}

上面代码的执行过程代表了基本类型数组初始化的典型过程。下面将结合示意图详细介绍这段代码的执行过程。

执行第一行代码int[] iArr;时,仅定义一个数组变量,此时内存中的存储示意图如图4.5所示。
这里有一张图片
执行了int[] iArr;代码后,仅在栈内存中定义了一个空引用(就是iArr数组变量),这个引用并未指向任何有效的内存,当然无法指定数组的长度。
当执行iArr = new int[5];动态初始化后,系统将负责为该数组分配内存空间,并分配默认的初始值:所有数组元素都被赋为值0,此时内存中的存储示意图如图4.6所示:
这里有一张图片
此时iArr数组的每个数组元素的值都是0,当使用循环为该数组的每个数组元素依次赋值后,此时每个数组元素的值都变成程序显式指定的值。显式指定每个数组元素值后的存储示意图如图4.7所示:
这里有一张图片
从图4.7中可以看到基本类型数组的存储示意图,每个数组元素的值直接存储在对应的内存中。操作基本类型数组的数组元素时,实际上相当于操作基本类型的变量

4.6 深入数组

数组是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的。下面将深入介绍数组在内存中的运行机制

4.6.1 内存中的数组

数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当该引用指向有效内在后,才可通过该数组变量来访问数组元素。
与所有引用变量相同的是,引用变量是访问真实对象的根本方式。也就是说,如果希望在程序中访问数组对象本身,则只能通过这个数组的引用变量来访问它。

数组对象存储在堆内存中

实际的数组对象被存储在堆(heap)内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中。数组在内存中的存储示意图如图4.2所示:
这里有一张图片
如果需要访问如图4.2所示堆内存中的数组元素,则程序中只能通过p[index]的形式实现。也就是说,数组引用变量是访问堆内存中数组元素的根本方式

栈内存 堆内存

方法中定义的局部变量保存在占栈内存

当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈存里。

方法结束 栈内存自动销毁

随着方法的执行结束,这个方法的内存栈也将自然销毁。
因此,所有在方法中定义的局部变量都是放在栈内存中的;

对象存储在堆内存

在程序中创建一个对象时,因为对象的创建成本通常较大,所以将对象保存到运行时数据区中,以便反复利用,这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(在方法的参数传递时很常见),则这个对象依然不会被销毁。

对象由垃圾回收器回收

只有当一个对象没有任何引用变量引用它时,系统的圾回收器才会在合适的时候回收它

如果不再有任何引用变量指向堆内存中的数组,则这个数组将成为垃圾,该数组所占的内存将会被系统的垃圾回收机制回收。因此,为了让垃圾回收机制回收一个数组所占的内存空间,可以将该数组变量赋为null,也就切断了数组引用变量和实际数组之间的引用关系,实际的数组也就成了垃圾。

改变数组引用变量的指向

只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这种操作会让人产生数组的长度可变的错觉。
如下代码所示:

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
public class ArrayInRam
{
public static void main(String[] args)
{
// 定义并初始化数组,使用静态初始化
int[] a = {5, 7 , 20};
// 定义并初始化数组,使用动态初始化
int[] b = new int[4];
// 输出b数组的长度
System.out.println("b数组的长度为:" + b.length);
// 循环输出a数组的元素
for (int i = 0 ,len = a.length; i < len ; i++ )
{
System.out.println(a[i]);
}
// 循环输出b数组的元素
for (int i = 0 , len = b.length; i < len ; i++ )
{
System.out.println(b[i]);
}
// 因为a是int[]类型,b也是int[]类型,所以可以将a的值赋给b。
// 也就是让b引用指向a引用指向的数组
b = a;
// 再次输出b数组的长度
System.out.println("b数组的长度为:" + b.length);
}
}

运行上面代码后,将可以看到先输出b数组的长度为4,然后依次输出a数组和b数组的每个数组元素,接着会输出b数组的长度为3。看起来似乎数组的长度是可变的,但这只是一个假象。必须牢记:**定义并初始化一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身
下面将结合示意图来说明上面程序的运行过程:

当程序定义并初始化了a、b两个数组后,系统内存中实际上产生了4块内存区,其中栈内存中有两个引用变量:a和b。
堆内存中也有两块内存区,分别用于存储a和b引用所指向的数组本身。此时计算机内存的存储示意图如图4.3所示:
这里有一张图片
从图4.3中可以非常清楚地看出a引用和b引用各自所引用的数组对象,并可以很清楚地看出a变量所引用的数组长度是3,b变量所引用的数组长度是4。

当执行代码b=a;时,系统将会把a的值赋给b,ab都是引用类型变量,存储的是地址。
因此把a的值赋给b后,就是让b指向a所指向的地址。此时计算机内存的存储示意图如图4.4所示:
这里有一张图片
从图4.4中可以看出,当执行了b=a;之后,堆内存中的第一个数组具有了两个引用:a变量和b变量都引用了第一个数组。此时第二个数组失去了引用,变成垃圾,只有等待垃圾回收机制来回收它,但它的长度依然不会改变,直到它彻底消失.

4.5.5 foreach循环

Java5之后,Java提供了一种更简单的循环:foreach循环,这种循环遍历数组和集合更加简洁。
使用toread-h循环遍历数组和集合元素时,无须获得数组和集合长度,无须根据索引来访问数组元素和集合元素,foreach循环自动遍历数组和集合的每个元素。

foreach循环的语法格式

foreach循环的语法格式如下:

1
2
3
for(type variableName : arrayName){
// variableName自动迭代访问每个元素
}

在上面语法格式中,type是数组元素或集合元素的类型,variableName是一个形参名,foreach循环将自动将数组元素、集合元素依次赋给该变量。

程序示例

下面程序示范了如何使用foreach循环来遍历数组元素。

1
2
3
4
5
6
7
8
9
10
public class ForEachTest {
public static void main(String[] args) {
String[] books = { "Hello", "World", "Java" };
// 使用foreach循环来遍历数组元素,
// 其中book将会自动迭代每个数组元素
for (String book : books) {
System.out.println(book);
}
}
}

从上面程序可以看出,使用foreach循环遍历数组元素时无须获得数组长度,也无须根据索引来访问数组元素foreach循环和普通循环不同的是,它无须循环条件,无须循环迭代语句,这些部分都由系统来完成,foreach循环自动迭代数组的每个元素,当每个元素都被迭代一次后,foreach循环自动结束。

不要对foreach循环变量赋值

当使用foreach循环来迭代输出数组元素或集合元素时,通常不要对循环变量进行赋值,虽然这种赋值在语法上是允许的,但没有太大的实际意义,而且极容易引起错误。
例如下面程序:

1
2
3
4
5
6
7
8
9
10
11
public class ForEachErrorTest {
public static void main(String[] args) {
String[] books = { "Hello", "World", "Java" };
// 使用foreach循环来遍历数组元素,其中book将会自动迭代每个数组元素
for (String book : books) {
book = "JSP";
System.out.println(book);
}
System.out.println(books[0]);
}
}

运行上面程序,将看到如下运行结果:

1
2
3
4
JSP
JSP
JSP
Hello

从上面运行结果来看,在foreach循环中对数组元素进行赋值,则不能正确遍历数组元素,不能正确地取出每个数组元素的值。
而且当再次访问第一个数组元素时,发现数组元素的值依然没有改变。

foreach的循环变量是临时变量 不是数组元素

使用foreach来迭代访问数组元素时,foreach中的循环变量相当于一个临时变量,系统会把数组元素依次赋给这个临时变量,而这个临时变量并不是数组元素,它只是保存了数组元素的值。因此,如果希望改变数组元素的值,则不能使用这种foreach循环
使用foreach循环迭代数组元素时,并不能改变数组元素的值,因此不要对foreach的循环变量进行赋值