如果说现在要求你定义100个整型变量,按照以往的方式,定义100个整型变量:
int i1, i2, i3, ... i100;
但是这个时候如果按照此类方式定义就会非常麻烦,因为这些变量彼此之间没有任何的关联,也就是说如果现在突然再有一个要求,要求你输出这100个变量的内容,意味着你要编写System.out.println()语句100次。
而Java 语言中提供的数组可以用来存储固定大小的同类型元素。数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
要使用Java的数组,必须经过两个步骤:
首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:
元素类型[] 数组名; // 建议使用的方式 元素类型 数组名[]; // 效果相同,这种风格类似 C/C++ 语言
数组名”是用来统一这组相同数据类型的元素的名称,建议使用有意义的名称为数组命名。
我们声明一个数组,并赋值为null,如下:
int[] score = null; // 推荐方式,null表示引用数据类型的默认值 // int socre[] = null; // 非推荐方式
该语句会在栈内存中创建一个变量score,如下图:
数组声明后实际上是在栈内存中保存了此数组的名称,接下来便是要在堆内存中分配数组所需的内存,其中“元素的个数”(数组的长度)是告诉编译器,所声明的数组要存放多少个元素,而“new”则是命令编译器根据括号里的长度开辟空间。
数组名 = new 元素类型[元素的个数];
我们为上面声明的socre数组开辟空间:
score = new int[3];
数组操作中,在栈内存中保存的永远是数组的名称,只开辟了栈内存空间数组是永远无法使用的,必须有指向的堆内存才可以使用,要想开辟新的堆内存则必须使用new关键字,之后只是将此堆内存的使用权交给了对应的栈内存空间,而且一个堆内存空间可以同时被多个栈内存空间指向,即:一个人可以有多个名字,人就相当于堆内存,名字就相当于栈内存。
我们可以在声明数组的同时分配内存:
元素类型 数组名[] = new 元素类型[元素的个数];
下面我们声明一个元素个数为10的整型数组score,同时开辟一块内存空间供其使用:
int[] score = new int[10];
在Java中,由于整数数据类型所占用的空间为4个bytes,而整型数组score可保存的元素有10个,所以上例中占用的内存共有4 * 10 = 40个字节。
数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。
public class ArrayTest { public static void main(String[] args) { //声明并开辟空间 String [] stringArray = new String[3]; //各元素的值默认为String类型的初始值null for(int i=0;i<stringArray.length;i++) { System.out.println(stringArray[i]); } } }
输出如下:
null null null
想要访问数组里的元素,可以利用索引来完成。Java的数组索引编号由0开始,以一个的score[10]的整形数组为例,score[0]代表第1个元素,score[1]代表第2个元素,score[9]为数组中第10个元素(也就是最后一个元素)。
程序中可以发现,对于数组的访问采用“数组名称[index]的方式,score一共开辟了10个空间大小的数组,所以下标的取值是0~9,假设程序中取出的内容超过了这个下标,如score[10],则程序运行的时候会出现数组下标越界的错误提示:java.lang.ArrayIndexOutOfBoundsException。
为数组中的元素赋值并进行输出:
public static void Test1() { //[1] 声明数组,但未开辟堆内存 int[] score = null; //[2] 为数组开辟堆内存空间,大小为3 score = new int[3]; // [3] 为每一个元素赋值 for (int x = 0; x < score.length; x++) { score[x] = x * 2 + 1 ; } // 使用循环依次输出数组中的全部内容 for (int x = 0; x < 3; x++) { System.out.println("score[" + x + "] = " + score[x]); } }
执行该函数,输出如下:
score[0] = 1 score[1] = 3 score[2] = 5
我们对以上代码进行内存分析,【1】【2】【3】处分别对应下面这三张图:
数组的内容分为动态初始化和静态初始化两种,前面所讲解的全部代码是采用先声明数组之后为数组中的每个内容赋值的方式完成的。那么也可以通过数组静态初始化在数组声明时就为数组元素分配空间并赋值。
元素类型 数组名 = {元素1,元素2...}; 元素类型 数组名 = new 元素类型[]{元素1,元素2...};
数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本循环,尤其是for循环。JDK 1.5 引进了一种新的循环类型,被称为 foreach 循环或者加强型循环,它能在不使用下标的情况下遍历数组。
public static void Test2() { double[] arr = {1.5, 2.5, 3.5, 3.5, 5.5}; // 打印所有数组元素 for (double element: arr) { System.out.println(element); } }
多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组(每个一维数组都可以具有任意的长度)。我们以二维数组为例介绍多维数组的初始化。
(1)二维数组的动态初始化,直接为每一维分配空间:
元素类型 变量名 = new 元素类型[行数][列数];
声明整型数组score,同时为其开辟一块内存空间:
int[][] score = new int[4][3];
整型数据score可保存的元素有4*3 = 12个,而在Java中,int数据类型所占用的空间为4个字节,因此该整型数组占用的内存共为4*12 = 48个字节。
public static void Test3() { // 声明并实例化二维数组 int score[][] = new int[4][3]; // 为数组中的部分内容赋值 score[0][1] = 30 ; score[1][0] = 31 ; score[2][2] = 32 ; score[3][1] = 33 ; score[1][1] = 30 ; for (int i = 0; i < score.length; i++) { for(int j=0;j<score[i].length;j++){ System.out.print(score[i][j] + "\t"); } // 换行 System.out.println("") ; } }
输出结果如下:
0 30 0 31 30 0 0 0 32 0 33 0
(2)二维数组的静态初始化:
元素类型 变量名 = {{元素A1,元素A2...}, {元素B1,元素B2}...};
例如:
int score[][] = {{ 67, 61 }, { 78, 89, 83 }, { 99, 100, 98, 66, 95 }};
一般来讲,操作二维数组不应使用常数来控制维数。具体方法是array.length表示行数,array[row].length来表示row行的列数。这样当数组行数和列数不相等时,代码可以自动调整为正确的值。
public static void Test4() { //静态初始化一个二维数组,每行的数组元素个数不一样 int[][] score = {{ 67, 61 }, { 78, 89, 83 }, { 99, 100, 98, 66, 95 }}; for (int i = 0; i < score.length; i++) { for (int j = 0; j < score[i].length; j++) { System.out.print(score[i][j] + "\t"); } // 换行 System.out.println(""); } System.out.println(Arrays.toString(score)); System.out.println(Arrays.deepToString(score)); }
输出如下:
67 61
78 89 83
99 100 98 66 95
[[I@15db9742, [I@6d06d69c, [I@7852e922]
[[67, 61], [78, 89, 83], [99, 100, 98, 66, 95]]
有一点需要注意,如果想将多维数组转换为多个String,正如从输出中所看到的那样,我们需要使用Arrays.deepToString()方法。
有时候,我们在写一个方法,我们希望它的返回不止一个值,而是一组值。这对于C和C++这样的语言来说有点困难,因为它们不能返回一个数组,而只能返回指向数组的指针。这回造成一些问题,因为它使得控制数组的声明周期变得很困难,并且容易造成内存泄露。
在Java中,我们可以直接返回一个数组,当我们使用完这个数组后,垃圾回收器会清理掉它。
下面演示如何返回String型数组:
import java.util.*; public class IceCream { private static Random rand = new Random(47); static final String[] FLAVORS = { "Chocolate","Strawberry","Vanilla Fudge Swirl", "Mint Chip","Mocha Almond Fudge","Rum Raisin", "Praline Crean","Mud Pie" }; public static String[] flavorSet(int n) { if(n > FLAVORS.length) throw new IllegalArgumentException("Set too big"); String [] results = new String[n]; boolean [] picked = new boolean[FLAVORS.length]; for(int i=0;i<n;i++) { int t; do { t = rand.nextInt(FLAVORS.length); }while(picked[t]); results[i] = FLAVORS[t]; picked[i] = true; } return results; } public static void main(String[] args) { for(int i=0;i<7;i++) { System.out.println(Arrays.toString(flavorSet(3))); } } }
输出如下:
[Rum Raisin, Mint Chip, Mocha Almond Fudge]
[Chocolate, Strawberry, Mocha Almond Fudge]
[Strawberry, Mint Chip, Mocha Almond Fudge]
[Rum Raisin, Vanilla Fudge Swirl, Mud Pie]
[Vanilla Fudge Swirl, Mocha Almond Fudge, Praline Crean]
[Strawberry, Praline Crean, Mocha Almond Fudge]
[Mocha Almond Fudge, Strawberry, Mocha Almond Fudge]
通常,数组和泛型不能很好的结合,你不能实例化一个泛型数组:
List<String>[] ls = new List<String>[10];
由于泛型的擦除特性,会擦除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。
编译器虽然不允许你实例化泛型数组,但是,它允许你创建这种数组的引用。例如:
List<String>[] ls ;
这条语句可以顺利的通过编译器而不报任何错误。而且,尽管你不能创建实际的持有泛型的数组对象,但是你可以创建非泛型的输入,然后将其转型:
import java.util.*; class BerylliumSphere{ private static long counter; private final long id = counter++; public String toString() { return "Sphere " + id; } } public class ArrayOfGenerics { public static void main(String[] args) { List<String> [] ls; List[] la = new List[10]; //"Unchecked" warning ls = (List<String>[])la; ls[0] = new ArrayList<String>(); //Compile-time checkng produces an error //ls[1] = new ArrayList<Integer>(); //The problem: List<String> is a subtype of Object Object[] objects = ls; //So assignment is Ok //Compiles and runs without complaint objects[1] = new ArrayList<Integer>(); List<BerylliumSphere> [] spheres = (List<BerylliumSphere> [])new List[10]; for(int i=0;i<spheres.length;i++) { spheres[i] = new ArrayList<BerylliumSphere>(); } } }
一旦拥有了对List<String>[] 的引用,你就会看到某些编译器检查。问题是数组是协变类型的,因此List<String>[] 也是一个Object[],并且你可以利用这一点,将一个ArrayList<Integer>赋值到你的数组中,而不会有任何编译期或运行时错误。
如果你知道将来不会向上转型,并且需求也相对比较简单,那么你仍然可以创建泛型数组,它可以提供基本的编译期类型检查。
一般而言,你会发现泛型在类或方法的边界处很有效,而在类或方法的内部,擦除通常使得泛型变得不适用。例如,你不能创建泛型数组:
class ArrayOfGenericsType<T>{ T[] array; @SuppressWarnings("unchecked") public ArrayOfGenericsType(int size) { //array = new T[size]; //Illegal array = (T[]) new Object[size]; //"unckecked" Warning } }
擦除在这里成为了障碍——本例试图创建的类型已被擦除,因而是类型位置的数组。但是我们可以创建Object数组,然后将其转型,如果没有@SuppressWarnings("unchecked")注解,你将在编译期得到一个”不受检查“的错误消息,因为这个数组没有真正持有或动态检查类型T,也就是说,如果我创建一个String[],Java在编译期和运行期都会强制要求我只能将String对象置于该数组中,但是,如果创建的是Object[],那么我就可以将除基本类型之外的任何对象置于该数组中。
有时候在程序测试的时候,我们需要快速生成一个数组,主要可以采用以下方式。
Java标准库Arrays提供了一个fill()方法:针对值类型,用同一个值填充数组各个位置,而针对对象类型,就是复制同一个引用进行填充。下面是一个示例:
import java.util.*; public class FillingArrays { public static void main(String[] args){ int size = 6; boolean[] a1 = new boolean[size]; byte[] a2 = new byte[size]; char[] a3 = new char[size]; short[] a4 = new short[size]; int[] a5 = new int[size]; long[] a6 = new long[size]; float[] a7 = new float[size]; double[] a8 = new double[size]; String[] a9 = new String[size]; Arrays.fill(a1, true); System.out.println("a1=" + Arrays.toString(a1)); Arrays.fill(a2, (byte)11); System.out.println("a2=" + Arrays.toString(a2)); Arrays.fill(a3, ‘x‘); System.out.println("a3=" + Arrays.toString(a3)); Arrays.fill(a4, (short)17); System.out.println("a4=" + Arrays.toString(a4)); Arrays.fill(a5, 19); System.out.println("a5=" + Arrays.toString(a5)); Arrays.fill(a6, 23); System.out.println("a6=" + Arrays.toString(a6)); Arrays.fill(a7, 29); System.out.println("a7=" + Arrays.toString(a7)); Arrays.fill(a8, 47); System.out.println("a8=" + Arrays.toString(a8)); Arrays.fill(a9,"Hello"); System.out.println("a9=" + Arrays.toString(a9)); Arrays.fill(a9,3,5, "world"); System.out.println("a9=" + Arrays.toString(a9)); } }
输出如下:
a1=[true, true, true, true, true, true] a2=[11, 11, 11, 11, 11, 11] a3=[x, x, x, x, x, x] a4=[17, 17, 17, 17, 17, 17] a5=[19, 19, 19, 19, 19, 19] a6=[23, 23, 23, 23, 23, 23] a7=[29.0, 29.0, 29.0, 29.0, 29.0, 29.0] a8=[47.0, 47.0, 47.0, 47.0, 47.0, 47.0] a9=[Hello, Hello, Hello, Hello, Hello, Hello] a9=[Hello, Hello, Hello, world, world, Hello]
使用Arrays.fill()可以填充整个数组,或者像最后两条语句所示,只填充数组的某个区域。
为了以灵活的方式创建更有意义的数组,我们可以使用生成器。首先我们来展示一个例子:
//定义Generator接口 interface Generator<T>{ public T next(); } public class CountingGenerator { //Boolean类型生成器 public static class Boolean implements Generator<java.lang.Boolean>{ private boolean value = false; public java.lang.Boolean next(){ //反转 value = !value; return value; } }; //Byte类型生成器 public static class Byte implements Generator<java.lang.Byte>{ private byte value = 0; public java.lang.Byte next(){ return value++; } }; private static char[] chars = ("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); //Character类型生成器 public static class Character implements Generator<java.lang.Character>{ private int index = -1; public java.lang.Character next(){ index = (index + 1)%chars.length; return chars[index]; } }; //String类型生成器 public static class String implements Generator<java.lang.String>{ private int length = 7; Generator<java.lang.Character> cg = new Character(); public String() {} public String(int length) {this.length = length;} public java.lang.String next(){ char[] buf = new char[length]; for(int i=0;i<length;i++) { buf[i] = cg.next(); } return new java.lang.String(buf); } }; //Short类型生成器 public static class Short implements Generator<java.lang.Short>{ private short value = 0; public java.lang.Short next(){ return value++; } }; //Integer类型生成器 public static class Integer implements Generator<java.lang.Integer>{ private int value = 0; public java.lang.Integer next(){ return value++; } }; //Long类型生成器 public static class Long implements Generator<java.lang.Long>{ private long value = 0; public java.lang.Long next(){ return value++; } }; //Float类型生成器 public static class Float implements Generator<java.lang.Float>{ private float value = 0; public java.lang.Float next(){ return value++; } }; //Double类型生成器 public static class Double implements Generator<java.lang.Double>{ private double value = 0; public java.lang.Double next(){ return value++; } }; }
下面我们通过反射来测试这个类:
//生成器测试 public class GeneratorsTest { public static int size = 10; public static void test(Class<?> surroundingClass) { //获取这个surroundingClass中的所有类的Class对象 for(Class<?> type:surroundingClass.getClasses()) { System.out.print(type.getSimpleName()+":"); try { Generator<?> g = (Generator<?>)type.newInstance(); for(int i=0;i<size;i++) { System.out.printf(g.next() + " "); } System.out.println(); }catch(Exception e) { throw new RuntimeException(e); } } } public static void main(String[] args) { test(CountingGenerator.class); } }
输出如下:
Boolean:true false true false true false true false true false Byte:0 1 2 3 4 5 6 7 8 9 Character:a b c d e f g h i j Double:0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 Float:0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 Integer:0 1 2 3 4 5 6 7 8 9 Long:0 1 2 3 4 5 6 7 8 9 Short:0 1 2 3 4 5 6 7 8 9 String:abcdefg hijklmn opqrstu vwxyzAB CDEFGHI JKLMNOP QRSTUVW XYZabcd efghijk lmnopqr
这里假设待测试类包含一组嵌套的Generator实现类,其中每个Generator都有一个默认构造器。由于反射方法getClass()可以获取所有的嵌套类的Class对象。因此通过test()方法可以为Generator的每一种实现创建一个实例,然后打印通过调用10次next()方法而产生的结果。
下面的是一组使用随机数生成器的Generator。因为Random的构造器使用常量进行初始化,所以,每次用这些Generator中的一个来运行程序时、所产生的输出都是可重复的:
import java.util.*; public class RandomGenerator { private static Random r = new Random(7); //Boolean类型生成器 public static class Boolean implements Generator<java.lang.Boolean>{ public java.lang.Boolean next(){ return r.nextBoolean(); } }; //Byte类型生成器 public static class Byte implements Generator<java.lang.Byte>{ public java.lang.Byte next(){ return (byte)r.nextInt(); } }; private static char[] chars = ("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); //Character类型生成器 public static class Character implements Generator<java.lang.Character>{ public java.lang.Character next(){ return CountingGenerator.chars[r.nextInt(CountingGenerator.chars.length)]; } }; //String类型生成器 public static class String extends CountingGenerator.String{ {cg = new Character();} public String() {} public String(int length) {super(length);} } //Short类型生成器 public static class Short implements Generator<java.lang.Short>{ public java.lang.Short next(){ return (short)r.nextInt(); } }; //Integer类型生成器 public static class Integer implements Generator<java.lang.Integer>{ private int mod = 1000; public Integer() {} public Integer(int modulo) {mod = modulo;} public java.lang.Integer next(){ return r.nextInt(mod); } }; //Long类型生成器 public static class Long implements Generator<java.lang.Long>{ private int mod = 1000; public Long() {} public Long(int modulo) {mod = modulo;} public java.lang.Long next(){ return new java.lang.Long(r.nextInt(mod)); } }; //Float类型生成器 public static class Float implements Generator<java.lang.Float>{ public java.lang.Float next(){ int trimmed = Math.round(r.nextFloat()*100); return ((float)trimmed)/100; } }; //Double类型生成器 public static class Double implements Generator<java.lang.Double>{ public java.lang.Double next(){ long trimmed = Math.round(r.nextDouble()*100); return ((double)trimmed)/100; } }; }
其中,RandomGenerator.String继承自CountingGenerator.String,并且只是插入了新的Character生成器。
为了不生成过大的数字, RandomGenerator.Integer默认使用的模数为10000,但是重载的构造器允许选择更小的值。同样的方式也应用到RandomGenerator.Long上。对于Float和Double生成器,小数点之后的数字被裁掉了。
我们复用GeneratorTest来测试RandomGenerator:
public class RandomGeneratorTest { public static void main(String[] args) { GeneratorsTest.test(RandomGenerator.class); } }
输出如下:
Boolean:true true true false false false true true true true Byte:0 -95 -23 -33 -65 -4 2 -27 22 -50 Character:k q J B o i d S C V Double:0.38 0.77 0.22 0.31 0.87 0.1 0.44 0.52 0.1 0.6 Float:0.75 0.0 0.39 0.77 0.83 0.86 0.46 0.77 0.45 0.45 Integer:674 525 896 829 272 305 820 105 529 347 Long:454 652 637 720 114 396 980 221 31 598 Short:26209 24236 16342 21060 -3366 -26047 3920 -7806 7282 -4356 String:oAhjszO BzFCoFO BplkgxC nvsQPpA xrtFbBn fCvhWSf DSryYmD YneWpFx EOOqDuQ TLGdVlb
Arrays类位于 java.util 包中,主要包含了操纵数组的各种方法:
该方法返回的是Arrays内部静态类ArrayList,而不是我们平常使用的ArrayList,,该静态类ArrayList没有覆盖父类的add, remove等方法,所以如果直接调用,会报UnsupportedOperationException异常。
将数组转换为集合,接收一个可变参:
List<Integer> list = Arrays.asList(1, 2, 3); list.forEach(System.out::println); // 1 2 3
Integer[] data = {1, 2, 3}; List<Integer> list = Arrays.asList(data); list.forEach(System.out::println); // 1 2 3
如果将基本数据类型的数组作为参数传入, 该方法会把整个数组当作返回的List中的第一个元素
int[] data = {1, 2, 3}; List<int[]> list = Arrays.asList(data); System.out.println(list.size()); // 1 System.out.println(Arrays.toString(list.get(0))); // [1, 2, 3]
Arrays.fill(Object[] array, Object obj):用指定元素填充整个数组(会替换掉数组中原来的元素)
Integer[] data = {1, 2, 3, 4}; Arrays.fill(data, 9); System.out.println(Arrays.toString(data)); // [9, 9, 9, 9]
Arrays.fill(Object[] array, int fromIndex, int toIndex, Object obj):用指定元素填充数组,从起始位置到结束位置,取头不取尾(会替换掉数组中原来的元素)
Integer[] data = {1, 2, 3, 4}; Arrays.fill(data, 0, 2, 9); System.out.println(Arrays.toString(data)); // [9, 9, 3, 4]
Arrays.sort(Object[] array):对数组元素进行排序(串行排序)
String[] data = {"1", "4", "3", "2"}; System.out.println(Arrays.toString(data)); // [1, 4, 3, 2] Arrays.sort(data); System.out.println(Arrays.toString(data)); // [1, 2, 3, 4]
Arrays.sort(T[] array, Comparator<? super T> comparator):使用自定义比较器,对数组元素进行排序(串行排序)
String[] data = {"1", "4", "3", "2"}; System.out.println(Arrays.toString(data)); // [1, 4, 3, 2] // 实现降序排序,返回-1放左边,1放右边,0保持不变 Arrays.sort(data, (str1, str2) -> { if (str1.compareTo(str2) > 0) { return -1; } else { return 1; } }); System.out.println(Arrays.toString(data)); // [4, 3, 2, 1]
Arrays.sort(Object[] array, int fromIndex, int toIndex):对数组元素的指定范围进行排序(串行排序)
String[] data = {"1", "4", "3", "2"}; System.out.println(Arrays.toString(data)); // [1, 4, 3, 2] // 对下标[0, 3)的元素进行排序,即对1,4,3进行排序,2保持不变 Arrays.sort(data, 0, 3); System.out.println(Arrays.toString(data)); // [1, 3, 4, 2]
Arrays.sort(T[] array, int fromIndex, int toIndex, Comparator<? super T> c):使用自定义比较器,对数组元素的指定范围 进行排序(串行排序)
String[] data = {"1", "4", "3", "2"}; System.out.println(Arrays.toString(data)); // [1, 4, 3, 2] // 对下标[0, 3)的元素进行降序排序,即对1,4,3进行降序排序,2保持不变 Arrays.sort(data, 0, 3, (str1, str2) -> { if (str1.compareTo(str2) > 0) { return -1; } else { return 1; } }); System.out.println(Arrays.toString(data)); // [4, 3, 1, 2]
参考文章
[1]Java数组
原文:https://www.cnblogs.com/zyly/p/10951981.html