定义方法的形参时,最好不超过5个;
1 概念
数组,是指按一定顺序排列的相同数据类型的集合(若干变量);
元素,是指数组中的每个数据;
数组中的元素以索引表示其存放位置,索引从0开始,步长为1;
数据类型包括基本数据类型(byte、char、short、int、long、float、double、boolean)、引用数据类型(类、接口、数组);
2 定义
<数组元素的数据类型>[] <数组名>; // 定义方式1,如int[] ages;
<数组元素的数据类型> <数组名>[]; // 定义方式2,如int ages[];
注:
推荐使用方式1进行数组定义,因为可以把<数组元素的数据类型>[]看成一个整体;
3 数组的初始化
数组必须先初始化才能使用,因为初始化表示在内存中分配空间;
数组初始化有两种方式——静态和动态初始化;
但无论以哪种方式进行初始化,一旦完成初始化,数组的长度固定,不能改变,除非重新初始化——数组是定长的;
(1)静态初始化
a 概念
由程序员为每个数组元素设置初始值,而数组的长度由系统决定;
<数组元素的数据类型>[] <数组名> = new <数组元素的数据类型>[]{元素1, 元素2, 元素3, ...}; // 如int[] ages = new int[]{17, 18, 19};
<数组元素的数据类型>[] <数组名> = {元素1, 元素2, 元素3, ...}; // 简单形式的初始化
注:
数组长度计算——<数组名>.length
简单形式的初始化必须声明后立即初始化,不能先声明后初始化;
b 内存分析
(2)动态初始化
a 概念
由程序员设置数组长度,每个数组元素的初始值由JVM决定;
int <数组元素的数据类型>[] <数组名> = new <数组元素的数据类型>[<int类型的数组长度>]; // 如int[] ages = new int[3];
b 不同数据类型的初始值
c 内存分析
注:
不能同时使用静态初始化和动态初始化,如int[] ages = new int[3]{17, 18, 19};语句是错误的;
动/静态初始化的使用情形 —— 当事先知道存储哪些数据,使用静态初始化,否则使用动态初始化;
4 数组基本操作
(1)获取元素
通过索引获取相应的元素;
<数组元素的数据类型> <变量名> = <数组名>[<索引index>];
(2)设置元素
<数组名>[<index>] = <数值>;
(3)遍历元素
使用for循环语句;
int[] ages = new int[]{1,2,3};
for(int index = 0; index < ages.length; index ++){
System.out.println(ages[index]);
}
(4)获取数组长度
int len = <数组名>.length // length是属性,而非方法
(5)获取索引范围
[ 0, <数组名>.length-1]
5 操作数组常见异常
(1)NullPointerException
空指针异常,即空引用;
当数组还未初始化就直接对其操作,会引起该异常;
(2)ArrayIndexOutOfBoundsException
数组索引越界异常,即索引不在正确范围内;
6 数组编程
(1)获取数组最大/小元素
static int maxValue(int num[]){
if( num == null || num.length == 0){ // 首先判断传递过来的数组是否为空,或者数组长度为0,若是则直接返回,否则才进行后续操作
return -1;
}
int len = num.length;
int max = num[0];
for(int i = 1; i < len; i ++){
if(max < num[i]){
max = num[i];
}
}
return max;
}
static int minValue(int num[]){
if( num == null || num.length == 0){
return -1;
}
int len = num.length;
int min = num[0];
for(int i = 1; i < len; i ++){
if(min > num[i]){
min = num[i];
}
}
return min;
}
(2)按格式打印数组元素
static String printArray(int[] num){
if( num == null || num.length == 0){
return "[]";
}
int len = num.length;
String str = "[";
for(int i = 0; i < len; i++){
str += num[i];
if(i != len - 1){
str += ", ";
}
}
str += "]";
return str;
}
(3)逆序排列数组
static void reverseArray(int[] num){
if(num == null || num.length == 0){
return;
}
int len = num.length;
int halfLen = num.length / 2;
int temp;
for(int i = 0; i < halfLen; i++){
temp = num[i];
num[i] = num[len - 1 - i];
num[len - 1 - i] = temp;
}
return;
}
(4)元素第一次/最后一次出现索引(线性搜索)
static int firstIndex(int[] arr, int num){
if(arr == null || arr.length == 0){
return -1;
}
int len = arr.length;
for(int i = 0; i < len; i++){
if(num == arr[i]){
return i;
}
}
return -1;
}
static int lastIndex(int[] arr, int num){
if(arr == null || arr.length == 0){
return -1;
}
int len = arr.length;
for(int i = len - 1; i >= 0; i--){
if(arr[i] == num){
return i;
}
}
return -1;
}
7 方法参数的值传递机制
(1)main方法的数组参数
main方法是static修饰的,可直接使用所在类的类名进行调用;
底层是JVM通过类名.main(new String[]{});运行程序的;
main方法的String数组参数,其实是暴露给程序运行者,用于给程序传递一个数据信息——任何程序交互的窗口;
args[0]表示第一个参数,以此类推;
使用Windows命令行窗口编译后,使用“java <类名> <参数列表>”命令运行程序;
如下程序,用于输出String数组参数的长度和内容
class Hello
{
public static void main(String[] args)
{
System.out.println(args.length);
for(int i = 0; i < args.length; i ++){
System.out.print(args[i] + "\t");
}
System.out.println();
}
}
运行结果如下:
(2)基本数据类型
基本数据类型作为参数进行传递时,采用值传递机制,传递的是基本类型的值的副本;
如下,意图改变基本数据类型的值
class ParameterDemo
{
static void change(int x){
System.out.println("change方法修改前: " + x); // 10
x = 50;
System.out.println("change方法修改后: " + x); // 50
}
public static void main(String[] args)
{
int x = 10;
System.out.println("main方法修改前: " + x); // 10
ParameterDemo.change(x); // 改变x变量
System.out.println("main方法修改后: " + x); // 10,这里易出错
}
}
对应的内存分配图如下:
因为数据是基本数据类型,所以分类内存空间的是栈,而非堆;
传递基本数据类型的参数,其实是将该参数的值的副本传递给调用的方法;
(3)引用数据类型
引用数据类型作为参数进行传递时,也采用值传递机制,传递的是引用的地址值的副本;
class ParameterDemo
{
static void printArray(int[] arr){
if(arr == null || arr.length == 0){
return;
}
String str = "[";
for(int i = 0; i < arr.length; i++){
str += arr[i];
if(i != arr.length - 1){
str += ", ";
}
}
str += "]";
System.out.println(str);
}
static void swap(int[] arr){
System.out.print("change方法修改前: "); // [10, 99]
ParameterDemo.printArray(arr);
int temp;
temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
System.out.print("change方法修改后: "); // [99, 10]
ParameterDemo.printArray(arr);
}
public static void main(String[] args)
{
int[] arr = new int[]{10, 99};
System.out.print("main方法修改后: "); // [10, 99]
ParameterDemo.printArray(arr);
ParameterDemo.swap(arr);
System.out.print("main方法修改后: "); // [99, 10]
ParameterDemo.printArray(arr);
}
}
对应的内存分配图如下:
8 多维数组
(1)二维数组概念
严格来说,在Java中不存在多维数组的概念,与C语言有别(C语言要求二维数组中每个元素(即一维数组)中的元素个数相等,但Java中不要求,见下例),一般称为数组中的数组;
一维数组 —— 数组中的每个元素都是一个值(基本类型或引用类型的值);(常见)
二维数组 —— 数组中的每个元素又是一个一维数组;(常见)
三维数组 —— 数组中的每个元素又是一个二维数组;(少见)
int[][] arr = {
{1, 2, 3},
{4, 5},
{6}
};
对应内存分配图如下:
(2)二维数组的操作
a 初始化操作
静态初始化:
int[][] <二维数组名> = new int [][]{一维数组1, 一维数组2, 一维数组3, ...};
int[][] <二维数组名> = {一维数组1, 一维数组2, 一维数组3, ...};
动态初始化:
int[][] <二维数组名> = new int[<行数m>][<列数n>]; // 创建一个长度为m的二维数组,其中每个元素(一维数组)的长度是n
b 迭代操作
使用嵌套for循环进行遍历操作;
对于n维数组,遍历需要n个循环嵌套;
for(int i = 0; i < arr1.length; i ++){
for(int j = 0; j < arr1[i].length; j++){
// 相应操作
}
}
9 Java5对数组的新语法支持
(1)增强for循环 —— foreach
有时在循环迭代数组时,不关心迭代变量(数组的索引);
只操作数组元素,不操作数组索引,其语法定义如下:
for(<数组元素的数据类型> <变量> : <数组名>){
// 相应操作
}
如下,打印数组所有元素
int[] nums = {10, 20, 30, 40, 50};
for(int num : nums){
System.out.println(num);
}
注:
foreach在底层依然使用for循环+索引操作数组,因此把增强for循环称为编译器的新特性——语法糖(让程序员写更少、更简单的代码完成相同的功能);
使用foreach循环时,对元素进行修改,不会改变数组中的元素,因为实际执行中先将元素的值赋值给一个变量,再对该变量进行修改,只影响变量的值,而非数组元素;
for循环比foreach循环功能更强大;
若迭代数组元素,不关心数组索引时,推荐使用foreach循环;
(2)方法的可变参数
这里可变指参数的个数可变;
方法的可变参数也是编译器的新特性——语法糖,让程序员编写代码更简单,其底层就是一个数组类型;
定义的方法中,...位于参数类型和参数名之间;
注:
可变参数适用于参数个数不确定、数据类型确定的情况,Java把可变参数当作数组处理;
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组形式访问可变参数,且可变参数不会出现空指针异常;
可变参数必须作为方法的最后一个参数,避免参数的歧义;
方法的参数列表中最多只能有一个可变参数;
10 数组算法
(1)实现int类型数组元素拷贝
从src数组中拷贝3,4,5,6元素到dest数组中的5,6,7,8索引位置上,代码实现如下:
class ParameterDemo
{
static void printArray(int[] arr){
if(arr == null || arr.length == 0){
return;
}
for(int i = 0; i < arr.length; i ++){
System.out.print(arr[i]);
if(i != arr.length - 1){
System.out.print("\t");
}
}
System.out.println();
}
static void copy(int[] src, int[] dest, int srcPos, int destPos, int length){
for(int i = srcPos, j = destPos; i < srcPos + length; i ++, j ++){
dest[j] = src[i];
}
}
public static void main(String[] args)
{
int[] src = new int[]{1,2,3,4,5,6,7,8,9,10};
int[] dest = new int[10];
ParameterDemo.printArray(dest);
ParameterDemo.copy(src, dest, 2, 5, 4);
ParameterDemo.printArray(dest);
}
}
代码存在的问题 —— 只能拷贝int类型的数组,代码不够健壮(如srcPos<0等情况下的讨论);
(2)System类中的arraycopy方法
数组拷贝操作是经常使用的,SUN直接把数组的拷贝操作存放在JDK中的System类中;
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
从指定源数组中复制一个数组,复制从指定位置开始,到目标数组的指定位置结束;
其中,Object是Java语言中的根类,可表示任意数据类型;
该方法没有方法体,使用native修饰符(本地方法),底层使用了C/C++语言实现,Java直接调用其他语言编写好的功能;
查阅API文档(Java帮助文档),在什么类中有什么功能的方法 —— 文档在手,天下我有!
如,使用System.arraycopy()方法实现上例中的数组拷贝
class ArrayCopyDemo
{
static void printArray(int[] arr){
if(arr == null || arr.length == 0){
return;
}
for(int i = 0; i < arr.length; i ++){
System.out.print(arr[i]);
if(i != arr.length - 1){
System.out.print("\t");
}
}
System.out.println();
}
public static void main(String[] args)
{
int[] src = {1,2,3,4,5,6,7,8,9,10};
int[] dest = new int[10];
ArrayCopyDemo.printArray(dest);
System.arraycopy(src, 2, dest, 5, 4);
ArrayCopyDemo.printArray(dest);
}
}
(3)排序算法 —— 冒泡算法
a 概念
排序按照指定顺序(升序:从小到大/降序:从大到小)排列;
注:
在实际应用开发中,不需要自己编写排序算法代码,直接使用JDK自带的排序算法方法;只有在面试时才会被问到;
b 分类
选择排序 —— 直接选择排序、堆排序
交换排序 —— 冒泡排序、快速排序
插入排序 —— 直接插入排序、二分法插入排序、希尔排序
归并排序 等
这里只讲解冒泡和选择排序的升序算法;
冒泡排序
最简单的排序算法,基本思路是对未排序的各元素从头到尾依次比较相邻元素大小,若大于则交换顺序,经过第一轮比较排序后得到最大值,再使用同样方法把剩下的元素逐个比较即可;
如,有int类型数组待排序 int[] arr = {2, 9, 6, 7, 4, 1};
class BubbleSortDemo {
static void printArray(int[] arr){
if(arr == null || arr.length == 0){
return;
}
for(int i = 0; i < arr.length; i ++){
System.out.print(arr[i]);
if(i != arr.length - 1){
System.out.print("\t");
}
}
System.out.println();
}
static void swap(int[] arr, int i, int j){
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void bubbleSort(int[] arr){
if(arr == null || arr.length == 0){
return;
}
int temp;
for(int i = 0; i < arr.length - 1; i ++){
for(int j = 1; j < arr.length - i; j ++){
if(arr[j - 1] > arr[j]){
BubbleSortDemo.swap(arr, j - 1, j);
}
}
}
}
public static void main(String[] args) {
int[] arr = {2, 9, 6, 7, 4, 1};
BubbleSortDemo.printArray(arr);
BubbleSortDemo.bubbleSort(arr);
BubbleSortDemo.printArray(arr);
}
}
选择排序
基本思路是选择某个索引位置的元素,然后和后面的元素依次比较,若大于则交换位置,经过第一轮比较排序后得出最小值,再使用同样的方法把剩下的元素依次比较即可;
如上例,对int类型数组使用选择排序,代码如下:
class SelectSortDemo
{
static void printArray(int[] arr){
if(arr == null || arr.length == 0){
return;
}
for(int i = 0; i < arr.length; i ++){
System.out.print(arr[i]);
if(i != arr.length - 1){
System.out.print("\t");
}
}
System.out.println();
}
static void swap(int[] arr, int i, int j){
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void selcetSort(int[] arr){
if(arr == null || arr.length == 0){
return;
}
for(int i = 0; i < arr.length - 1; i ++){
for(int j = i + 1; j < arr.length; j ++){
if(arr[i] > arr[j]){
SelectSortDemo.swap(arr, i, j);
}
}
}
}
public static void main(String[] args)
{
int[] arr = {2, 9, 6, 7, 4, 1};
SelectSortDemo.printArray(arr);
SelectSortDemo.selcetSort(arr);
SelectSortDemo.printArray(arr);
}
}
(4)搜索算法
a 概念
从指定数组中搜索某一元素的索引;
b 分类
线性搜索
从头到尾/从末到头逐一搜索;
对于元素过多的数组,搜索性能极低;
二分搜索/二分查找/折半查找
前提是数组元素有序;
如,查找数组中等于4的索引值,代码如下:
class MidFindDemo
{
static void printArray(int[] arr){
if(arr == null || arr.length == 0){
return;
}
for(int i = 0; i < arr.length; i ++){
System.out.print(arr[i]);
if(i != arr.length - 1){
System.out.print("\t");
}
}
System.out.println();
}
static int midFind(int[] arr, int num){
if(arr == null || arr.length == 0){
return -1;
}
int low = 0;
int high = arr.length - 1;
int mid = (low + high) / 2;
while(low <= high){
if(arr[mid] < num){
low = mid + 1;
}else if(arr[mid] > num){
high = mid - 1;
}else{
return mid;
}
mid = (low + high) / 2;
}
return -1;
}
public static void main(String[] args)
{
int[] arr = {1, 2, 4, 6, 7, 9};
int num = 4;
MidFindDemo.printArray(arr);
System.out.println(num);
System.out.println(MidFindDemo.midFind(arr, num));
}
}
(5)自行封装数组操作的工具类ArrayUtil
(6)Java内置数组工具类Arrays
数组的算法操作频繁,SUN公司直接在JDK提供了一个数组的工具类java.util.Arrays;
int binarySearch(type[] arr, type key) —— 使用二分法查找数组中某元素并返回其索引,若找不到则返回负数
void sort(type[] arr) —— 使用调优后的快速法对指定数组排序
String toString(type[] arr) —— 返回指定数组内容的字符串表示形式
public static type[] copyOf(type[] original, int newLength) —— 复制指定数组,截取或用0补充(如有必要),以使副本具有指定长度