Java 编程问题:五、数组、集合和数据结构3

简介: Java 编程问题:五、数组、集合和数据结构

计算平均值


计算一组数字(在本例中为整数)的平均值可以通过两个简单的步骤实现:


  1. 计算数组中元素的和。
  2. 将此总和除以数组的长度。

在代码行中,我们有以下内容:

public static double average(int[] arr) {
  return sum(arr) / arr.length;
}
public static double sum(int[] arr) {
  double sum = 0;
  for (int elem: arr) {
    sum += elem;
  }
  return sum;
}


整数数组的平均值为 2.0:

double avg = MathArrays.average(integers);


在 Java8 函数式风格中,此问题的解决方案需要一行代码:

double avg = Arrays.stream(integers).average().getAsDouble();


对于第三方库支持,请考虑 Apache Common Lang(ArrayUtil)和 Guava 的CharsIntsLongs以及其他类。



105 反转数组


这个问题有几种解决办法。它们中的一些改变了初始数组,而另一些只是返回一个新数组


假设以下整数数组:

int[] integers = {-1, 2, 3, 1, 4, 5, 3, 2, 22};



让我们从一个简单的实现开始,它将数组的第一个元素与最后一个元素交换,第二个元素与倒数第二个元素交换,依此类推:

public static void reverse(int[] arr) {
  for (int leftHead = 0, rightHead = arr.length - 1; 
      leftHead < rightHead; leftHead++, rightHead--) {
    int elem = arr[leftHead];
    arr[leftHead] = arr[rightHead];
    arr[rightHead] = elem;
  }
}


前面的解决方案改变了给定的数组,这并不总是期望的行为。当然,我们可以修改它以返回一个新的数组,也可以依赖 Java8 函数样式,如下所示:

// 22, 2, 3, 5, 4, 1, 3, 2, -1
int[] reversed = IntStream.rangeClosed(1, integers.length)
  .map(i -> integers[integers.length - i]).toArray();


现在,让我们反转一个对象数组。为此,让我们考虑一下Melon类:

public class Melon {
  private final String type;
  private final int weight;
  // constructor, getters, equals(), hashCode() omitted for brevity
}



另外,让我们考虑一个Melon数组:

Melon[] melons = {
  new Melon("Crenshaw", 2000), 
  new Melon("Gac", 1200),
  new Melon("Bitter", 2200)
};

第一种解决方案需要使用泛型来塑造实现,该实现将数组的第一个元素与最后一个元素交换,将第二个元素与最后一个元素交换,依此类推:

public static <T> void reverse(T[] arr) {
  for (int leftHead = 0, rightHead = arr.length - 1; 
      leftHead < rightHead; leftHead++, rightHead--) {
    T elem = arr[leftHead];
    arr[leftHead] = arr[rightHead];
    arr[rightHead] = elem;
  }
}


因为我们的数组包含对象,所以我们也可以依赖于Collections.reverse()。我们只需要通过Arrays.asList()方法将数组转换成List

// Bitter(2200g), Gac(1200g), Crenshaw(2000g)
Collections.reverse(Arrays.asList(melons));

前面的两个解决方案改变了数组的元素。Java8 函数式风格可以帮助我们避免这种变异:

// Bitter(2200g), Gac(1200g), Crenshaw(2000g)
Melon[] reversed = IntStream.rangeClosed(1, melons.length)
  .mapToObj(i -> melons[melons.length - i])
  .toArray(Melon[]:new);

对于第三方库支持,请考虑 Apache Common Lang(ArrayUtils.reverse()和 Guava 的Lists类。




106 填充和设置数组


有时,我们需要用一个固定值填充数组。例如,我们可能希望用值1填充整数数组。实现这一点的最简单方法依赖于一个for语句,如下所示:


int[] arr = new int[10];
// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
for (int i = 0; i < arr.length; i++) {
  arr[i] = 1;
}



但我们可以通过Arrays.fill()方法将此代码简化为一行代码。对于基本体和对象,此方法有不同的风格。前面的代码可以通过Arrays.fill(int[] a, int val)重写如下:

// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
Arrays.fill(arr, 1);



Arrays.fill() also come with flavors for filling up just a segment/range of an array. For integers, this method is fill(int[] a, int fromIndexInclusive, int toIndexExclusive, int val).

现在,应用一个生成函数来计算数组的每个元素怎么样?例如,假设我们要将每个元素计算为前一个元素加 1。最简单的方法将再次依赖于for语句,如下所示:


// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
for (int i = 1; i < arr.length; i++) {
  arr[i] = arr[i - 1] + 1;
}


根据需要应用于每个元素的计算,必须相应地修改前面的代码。

对于这样的任务,JDK8 附带了一系列的Arrays.setAll()和Arrays.parallelSetAll()方法。例如,前面的代码片段可以通过setAll(int[] array, IntUnaryOperator generator)重写如下:

// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Arrays.setAll(arr, t -> {
  if (t == 0) {
    return arr[t];
  } else {
    return arr[t - 1] + 1;
  }
});


除此之外,我们还有setAll(double[] array, IntToDoubleFunction generator)、setAll(long[] array, IntToLongFunction generator)和setAll(T[] array, IntFunction<? extends T> generator)。


根据生成器的功能,此任务可以并行完成,也可以不并行完成。例如,前面的生成器函数不能并行应用,因为每个元素都依赖于前面元素的值。尝试并行应用此生成器函数将导致不正确和不稳定的结果。


但是假设我们要取前面的数组(1,2,3,4,5,6,7,8,9,10),然后将每个偶数值乘以它本身,将每个奇数值减去 1。因为每个元素都可以单独计算,所以在这种情况下我们可以授权一个并行进程。这是Arrays.parallelSetAll()方法的完美工作。基本上,这些方法是用来并行化Arrays.setAll()方法的。


现在我们将parallelSetAll(int[] array, IntUnaryOperator generator)应用于这个数组:

// 0, 4, 2, 16, 4, 36, 6, 64, 8, 100
Arrays.parallelSetAll(arr, t -> {
  if (arr[t] % 2 == 0) {
    return arr[t] * arr[t];
  } else {
    return arr[t] - 1;
  }
});


对于每个Arrays.setAll()方法,都有一个Arrays.parallelSetAll()方法。

作为奖励,Arrays附带了一组名为parallelPrefix()的方法。这些方法对于将数学函数应用于数组的元素(累积和并发)非常有用。


例如,如果我们要将数组中的每个元素计算为前面元素的和,那么我们可以如下所示:

// 0, 4, 6, 22, 26, 62, 68, 132, 140, 240
Arrays.parallelPrefix(arr, (t, q) -> t + q);



107 下一个更大的元素


NGE 是一个涉及数组的经典问题。


基本上,有一个数组和它的一个元素e,我们要获取下一个(右侧)大于e的元素。例如,假设以下数组:

int[] integers = {1, 2, 3, 4, 12, 2, 1, 4};


获取每个元素的 NGE 将产生以下对(-1 被解释为右侧的元素不大于当前元素):

1 : 2   2 : 3   3 : 4   4 : 12   12 : -1   2 : 4   1 : 4   4 : -1


这个问题的一个简单解决方案是循环每个元素的数组,直到找到一个更大的元素或者没有更多的元素要检查。如果我们只想在屏幕上打印对,那么我们可以编写一个简单的代码,如下所示:

public static void println(int[] arr) {
  int nge;
  int n = arr.length;
  for (int i = 0; i < n; i++) {
    nge = -1;
    for (int j = i + 1; j < n; j++) {
      if (arr[i] < arr[j]) {
        nge = arr[j];
        break;
      }
    }
    System.out.println(arr[i] + " : " + nge);
  }
}

另一个解决方案依赖于栈。主要是,我们在栈中推送元素,直到当前处理的元素大于栈中的顶部元素。当这种情况发生时,我们弹出那个元素。本书附带的代码中提供了解决方案。



108 更改数组大小


增加数组的大小并不简单。这是因为 Java 数组的大小是固定的,我们不能修改它们的大小。这个问题的解决方案需要创建一个具有所需大小的新数组,并将所有值从原始数组复制到这个数组中。这可以通过Arrays.copyOf()方法或System.arraycopy()(由Arrays.copyOf()内部使用)完成。


对于一个原始数组(例如,int),我们可以将数组的大小增加 1 后将值添加到数组中,如下所示:

public static int[] add(int[] arr, int item) {
  int[] newArr = Arrays.copyOf(arr, arr.length + 1);
  newArr[newArr.length - 1] = item;
  return newArr;
}



或者,我们可以删除最后一个值,如下所示:

public static int[] remove(int[] arr) {
  int[] newArr = Arrays.copyOf(arr, arr.length - 1);
  return newArr;
}


或者,我们可以按如下所示调整给定长度数组的大小:

public static int[] resize(int[] arr, int length) {
  int[] newArr = Arrays.copyOf(arr, arr.length + length);
  return newArr;
}



捆绑到本书中的代码还包含了System.arraycopy()备选方案。此外,它还包含泛型数组的实现。签名如下:

public static <T> T[] addObject(T[] arr, T item);
public static <T> T[] removeObject(T[] arr);
public static <T> T[] resize(T[] arr, int length);


在有利的背景下,让我们将一个相关的主题引入讨论:如何在 Java 中创建泛型数组。以下操作无效:

T[] arr = new T[arr_size]; // causes generic array creation error


有几种方法,但 Java 在copyOf(T[] original, int newLength)中使用以下代码:

// newType is original.getClass()
T[] copy = ((Object) newType == (Object) Object[].class) ?
  (T[]) new Object[newLength] :
  (T[]) Array.newInstance(newType.getComponentType(), newLength);




109 创建不可修改/不可变的集合


在 Java 中创建不可修改/不可变的集合可以很容易地通过Collections.unmodifiableFoo()方法(例如,unmodifiableList())完成,并且从 JDK9 开始,通过来自List、Set、Map和其他接口的一组of()方法完成。


此外,我们将在一组示例中使用这些方法来获得不可修改/不可变的集合。主要目标是确定每个定义的集合是不可修改的还是不可变的。

在阅读本节之前,建议先阅读第 2 章、“对象、不变性和switch表达式”中有关不变性的问题。

好吧。对于原始类型来说,这非常简单。例如,我们可以创建一个不可变的整数List,如下所示:


private static final List<Integer> LIST 
  = Collections.unmodifiableList(Arrays.asList(1, 2, 3, 4, 5));
private static final List<Integer> LIST = List.of(1, 2, 3, 4, 5);


对于下一个示例,让我们考虑以下可变类:

public class MutableMelon {
  private String type;
  private int weight;
  // constructor omitted for brevity
  public void setType(String type) {
    this.type = type;
  }
  public void setWeight(int weight) {
    this.weight = weight;
  }
  // getters, equals() and hashCode() omitted for brevity
}




问题 1 (Collections.unmodifiableList())


让我们通过Collections.unmodifiableList()方法创建MutableMelon列表:

// Crenshaw(2000g), Gac(1200g)
private final MutableMelon melon1 
  = new MutableMelon("Crenshaw", 2000);
private final MutableMelon melon2 
  = new MutableMelon("Gac", 1200);
private final List<MutableMelon> list 
  = Collections.unmodifiableList(Arrays.asList(melon1, melon2));
melon1.setWeight(0);
melon2.setWeight(0);


现在,列表将显示以下西瓜(因此列表发生了变异):

Crenshaw(0g), Gac(0g)



问题 2 (Arrays.asList())

我们直接在Arrays.asList()中硬编码实例,创建MutableMelon列表:

private final List<MutableMelon> list 
  = Collections.unmodifiableList(Arrays.asList(
    new MutableMelon("Crenshaw", 2000), 
    new MutableMelon("Gac", 1200)));


那么,这个列表是不可修改的还是不变的?答案是不可更改的。当增变器方法抛出UnsupportedOperationException时,硬编码实例可以通过List.get()方法访问。一旦可以访问它们,它们就可以变异:

MutableMelon melon1 = list.get(0);
MutableMelon melon2 = list.get(1);
melon1.setWeight(0);
melon2.setWeight(0);


现在,列表将显示以下西瓜(因此列表发生了变异):

Crenshaw(0g), Gac(0g)




问题 3 (Collections.unmodifiableList()和静态块)


让我们通过Collections.unmodifiableList()方法和static块创建MutableMelon列表:

private static final List<MutableMelon> list;
static {
  final MutableMelon melon1 = new MutableMelon("Crenshaw", 2000);
  final MutableMelon melon2 = new MutableMelon("Gac", 1200);
  list = Collections.unmodifiableList(Arrays.asList(melon1, melon2));
}


那么,这个列表是不可修改的还是不变的?答案是不可更改的。虽然增变器方法会抛出UnsupportedOperationException,但是硬编码的实例仍然可以通过List.get()方法访问。一旦可以访问它们,它们就可以变异:

MutableMelon melon1l = list.get(0);
MutableMelon melon2l = list.get(1);
melon1l.setWeight(0);
melon2l.setWeight(0);


现在,列表将显示以下西瓜(因此列表发生了变异):

Crenshaw(0g), Gac(0g)




问题 4 (List.of())

让我们通过List.of()创建MutableMelon的列表:

private final MutableMelon melon1 
  = new MutableMelon("Crenshaw", 2000);
private final MutableMelon melon2 
  = new MutableMelon("Gac", 1200);
private final List<MutableMelon> list = List.of(melon1, melon2);


那么,这个列表是不可修改的还是不变的?答案是不可更改的。虽然增变器方法会抛出UnsupportedOperationException,但是硬编码的实例仍然可以通过List.get()方法访问。一旦可以访问它们,它们就可以变异:

MutableMelon melon1l = list.get(0);
MutableMelon melon2l = list.get(1);
melon1l.setWeight(0);
melon2l.setWeight(0);


现在,列表将显示以下西瓜(因此列表发生了变异):

Crenshaw(0g), Gac(0g)


对于下一个示例,让我们考虑以下不可变类:

public final class ImmutableMelon {
  private final String type;
  private final int weight;
  // constructor, getters, equals() and hashCode() omitted for brevity
}




问题 5(不可变)

现在我们通过Collections.unmodifiableList()List.of()方法创建ImmutableMelon列表:

private static final ImmutableMelon MELON_1 
  = new ImmutableMelon("Crenshaw", 2000);
private static final ImmutableMelon MELON_2 
  = new ImmutableMelon("Gac", 1200);
private static final List<ImmutableMelon> LIST 
  = Collections.unmodifiableList(Arrays.asList(MELON_1, MELON_2));
private static final List<ImmutableMelon> LIST 
  = List.of(MELON_1, MELON_2);


那么,这个列表是不可修改的还是不变的?答案是不变的。增变器方法会抛出UnsupportedOperationException,我们不能对ImmutableMelon的实例进行变异。


根据经验,如果集合是通过unmodifiableFoo()或of()方法定义的,并且包含可变数据,则集合是不可修改的;如果集合是不可修改的,并且包含可变数据(包括原始类型),则集合是不可修改的。


需要注意的是,不可穿透的不变性应该考虑 Java 反射 API 和类似的 API,它们在操作代码时具有辅助功能。


对于第三方库支持,请考虑 Apache Common Collection、UnmodifiableList(和同伴)和 Guava 的ImmutableList(和同伴)。


在Map的情况下,我们可以通过unmodifiableMap()或Map.of()方法创建一个不可修改/不可修改的Map。


但我们也可以通过Collections.emptyMap()创建一个不可变的空Map:


Map<Integer, MutableMelon> emptyMap = Collections.emptyMap();


与emptyMap()类似,我们有Collections.emptyList()和Collections.emptySet()。在返回一个Map、List或Set的方法中,这些方法作为返回非常方便,我们希望避免返回null。

或者,我们可以通过Collections.singletonMap(K key, V value)用单个元素创建一个不可修改/不可变的Map:


// unmodifiable
Map<Integer, MutableMelon> mapOfSingleMelon 
  = Collections.singletonMap(1, new MutableMelon("Gac", 1200));
// immutable
Map<Integer, ImmutableMelon> mapOfSingleMelon 
  = Collections.singletonMap(1, new ImmutableMelon("Gac", 1200));

类似于singletonMap(),我们有singletonList()singleton()。后者用于Set

此外,从 JDK9 开始,我们可以通过一个名为ofEntries()的方法创建一个不可修改的Map。此方法以Map.Entry为参数,如下例所示:

// unmodifiable Map.Entry containing the given key and value
import static java.util.Map.entry;
...
Map<Integer, MutableMelon> mapOfMelon = Map.ofEntries(
  entry(1, new MutableMelon("Apollo", 3000)),
  entry(2, new MutableMelon("Jade Dew", 3500)),
  entry(3, new MutableMelon("Cantaloupe", 1500))
);

或者,不可变的Map是另一种选择:

Map<Integer, ImmutableMelon> mapOfMelon = Map.ofEntries(
  entry(1, new ImmutableMelon("Apollo", 3000)),
  entry(2, new ImmutableMelon("Jade Dew", 3500)),
  entry(3, new ImmutableMelon("Cantaloupe", 1500))
);

另外,可以通过 JDK10 从可修改/可变的Map中获得不可修改/不可变的MapMap.copyOf(Map<? extends K,? extends V> map)方法:

Map<Integer, ImmutableMelon> mapOfMelon = new HashMap<>();
mapOfMelon.put(1, new ImmutableMelon("Apollo", 3000));
mapOfMelon.put(2, new ImmutableMelon("Jade Dew", 3500));
mapOfMelon.put(3, new ImmutableMelon("Cantaloupe", 1500));
Map<Integer, ImmutableMelon> immutableMapOfMelon 
  = Map.copyOf(mapOfMelon);

作为这一节的奖励,让我们来讨论一个不可变数组。


问题:我能用 Java 创建一个不可变数组吗?


答案:不可以。或者。。。有一种方法可以在 Java 中生成不可变数组:

static final String[] immutable = new String[0];


因此,Java 中所有有用的数组都是可变的。但是我们可以在Arrays.copyOf()的基础上创建一个辅助类来创建不可变数组,它复制元素并创建一个新数组(在幕后,这个方法依赖于System.arraycopy()

因此,我们的辅助类如下所示:

import java.util.Arrays;
public final class ImmutableArray<T> {
  private final T[] array;
  private ImmutableArray(T[] a) {
    array = Arrays.copyOf(a, a.length);
  }
  public static <T> ImmutableArray<T> from(T[] a) {
    return new ImmutableArray<>(a);
  }
  public T get(int index) {
    return array[index];
  }
  // equals(), hashCode() and toString() omitted for brevity
}


用法示例如下:

ImmutableArray<String> sample =
  ImmutableArray.from(new String[] {
    "a", "b", "c"
  });




110 映射的默认值


在 JDK8 之前,这个问题的解决方案依赖于辅助方法,它基本上检查Map中给定键的存在,并返回相应的值或默认值。这种方法可以在工具类中编写,也可以通过扩展Map接口来编写。通过返回默认值,我们可以避免在Map中找不到给定键时返回null。此外,这是依赖默认设置或配置的方便方法。


从 JDK8 开始,这个问题的解决方案包括简单地调用Map.getOrDefault()方法。此方法获取两个参数,分别表示要在Map方法中查找的键和默认值。当找不到给定的键时,默认值充当应该返回的备份值。


例如,假设下面的Map封装了多个数据库及其默认的host:port

Map<String, String> map = new HashMap<>();
map.put("postgresql", "127.0.0.1:5432");
map.put("mysql", "192.168.0.50:3306");
map.put("cassandra", "192.168.1.5:9042");


我们来看看这个Map是否也包含 Derby DB 的默认host:port

map.get("derby"); // null


由于映射中没有 Derby DB,因此结果将是null。这不是我们想要的。实际上,当搜索到的数据库不在映射上时,我们可以在69:89.31.226:27017上使用 MongoDB,它总是可用的。现在,我们可以很容易地将此行为塑造为:

// 69:89.31.226:27017
String hp1 = map.getOrDefault("derby", "69:89.31.226:27017");
// 192.168.0.50:3306
String hp2 = map.getOrDefault("mysql", "69:89.31.226:27017");


这种方法可以方便地建立流利的表达式,避免中断代码进行null检查。请注意,返回默认值并不意味着该值将被添加到MapMap保持不变。



111 计算映射中是否不存在/存在


有时,Map并不包含我们需要的准确的开箱即用条目。此外,当条目不存在时,返回默认条目也不是一个选项。基本上,有些情况下我们需要计算我们的入口。


对于这种情况,JDK8 提供了一系列方法:compute()、computeIfAbsent()、computeIfPresent()和merge()。在这些方法之间进行选择是一个非常了解每种方法的问题。


现在让我们通过示例来看看这些方法的实现。



相关文章
|
2天前
|
存储 Java 程序员
【数据结构】初识集合&深入剖析顺序表(Arraylist)
Java集合框架主要由接口、实现类及迭代器组成,包括Collection和Map两大类。Collection涵盖List(有序、可重复)、Set(无序、不可重复),Map则由键值对构成。集合通过接口定义基本操作,具体实现由各类如ArrayList、HashSet等提供。迭代器允许遍历集合而不暴露其实现细节。List系列集合元素有序且可重复,Set系列元素无序且不可重复。集合遍历可通过迭代器、增强for循环、普通for循环及Lambda表达式实现,各有适用场景。其中ArrayList实现了动态数组功能,可根据需求自动调整大小。
23 11
|
1天前
|
Java 数据库
JAVA并发编程-一文看懂全部锁机制
曾几何时,面试官问:java都有哪些锁?小白,一脸无辜:用过的有synchronized,其他不清楚。面试官:回去等通知! 今天我们庖丁解牛说说,各种锁有什么区别、什么场景可以用,通俗直白的分析,让小白再也不怕面试官八股文拷打。
|
1天前
|
Java
深入理解Java中的多线程编程
本文将探讨Java多线程编程的核心概念和技术,包括线程的创建与管理、同步机制以及并发工具类的应用。我们将通过实例分析,帮助读者更好地理解和应用Java多线程编程,提高程序的性能和响应能力。
13 4
|
1天前
|
安全 Java 开发者
Java并发编程中的锁机制解析
本文深入探讨了Java中用于管理多线程同步的关键工具——锁机制。通过分析synchronized关键字和ReentrantLock类等核心概念,揭示了它们在构建线程安全应用中的重要性。同时,文章还讨论了锁机制的高级特性,如公平性、类锁和对象锁的区别,以及锁的优化技术如锁粗化和锁消除。此外,指出了在高并发环境下锁竞争可能导致的问题,并提出了减少锁持有时间和使用无锁编程等策略来优化性能的建议。最后,强调了理解和正确使用Java锁机制对于开发高效、可靠并发应用程序的重要性。
9 3
|
1天前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
6天前
|
存储 Java
Java编程中的对象序列化与反序列化
【9月更文挑战第12天】在Java的世界里,对象序列化与反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何通过实现Serializable接口来标记一个类的对象可以被序列化,并探索ObjectOutputStream和ObjectInputStream类的使用,以实现对象的写入和读取。我们还将讨论序列化过程中可能遇到的问题及其解决方案,确保你能够高效、安全地处理对象序列化。
|
2天前
|
Java 开发者
Java编程之旅:探索面向对象的力量
【9月更文挑战第16天】在编程的世界中,Java以其强大的面向对象编程特性而闻名。本文将带你走进Java的世界,一起探索类与对象的奥秘,学习如何通过封装、继承和多态性构建健壮的软件系统。无论你是初学者还是有经验的开发者,本文都旨在提供实用的代码示例,帮助你提升Java技能。准备好开始这段旅程了吗?让我们启程吧!
|
4月前
|
前端开发 Java
java前端:删除数组中指定元素的方法
java前端:删除数组中指定元素的方法
|
29天前
|
Java 索引
Java系列 之 Java复制(拷贝)数组的4种方法:arraycopy()方法、clone() 方法、copyOf()和copyOfRan
这篇文章介绍了Java中数组复制的四种方法:`Arrays.copyOf()`、`Arrays.copyOfRange()`、`System.arraycopy()`和`clone()`方法,以及它们的使用场景和示例代码。
|
2月前
|
存储 Java 容器
Java数组的初始化方法
Java数组的初始化方法