CodeGym /Java 博客 /China /ArrayList 类
作者
Volodymyr Portianko
Java Engineer at Playtika

ArrayList 类

已在 China 群组中发布
你好!在前面的课程中,我们深入研究了数组,回顾了使用数组的常见示例。 一般而言,数组超级方便。你已经知道,数组可以做很多事情。:)不过,数组的缺点也不少。
  • 大小受限。在创建数组时,你便需要知道数组需要包含多少元素。如果你低估了数量,则空间就不够了。如果高估了数量,数组一半是空的,这也不太好。毕竟,你分配的内存仍然比需要的多。

  • 数组没有添加元素的方法。你必须始终明确指出要添加元素的位置的下标。如果你不小心为某个位置指定了下标,而该位置被你需要的某个值所占据,那么该值将被覆盖。

  • 没有删除项目的方法。值只能被“归零”。


public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat[] cats = new Cat[3];
       cats[0] = new Cat("Thomas");
       cats[1] = new Cat("Behemoth");
       cats[2] = new Cat("Lionel Messi");

       cats[1] = null;

      
      
       System.out.println(Arrays.toString(cats));
   }

   @Override
   public String toString() {
       return "Cat{" +
               "name='" + name + '\'' +
               '}';
   }
}
Output:
[Cat{name='托马斯'}, null, Cat{name='莱昂内尔·梅西'}]
非常幸运,Java 的创建者对数组的优缺点了如指掌,因此创建了一个非常有趣的数据结构 ArrayList。 简而言之,ArrayList 是一个提高了效率且包含很多新功能的数组。 创建起来非常轻松:

ArrayList<Cat> cats = new ArrayList<Cat>();
现在我们创建了一个存储 Cat 对象的列表。注意,我们并未指定 ArrayList 的大小,但它会自动扩展。 这怎么可能?实际上非常简单。这可能让你非常惊讶,但 ArrayList 就是在普通数组之上构建的 :)是的,它包含一个数组,即存储元素的地方。 但是 ArrayList 采用特殊的方式来处理此数组:
  • 当填充内部数组时,ArrayList 会在内部创建一个新数组。新数组的大小是旧数组的大小乘以 1.5 再加 1。
  • 旧数组中的所有数据都将复制到新数组中
  • 旧数组被垃圾回收器清理。
这种机制支持 ArrayList(与普通数组不同)实现用于添加新元素的方法。这是 add() 方法

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<Cat>();
   cats.add(new Cat("Behemoth"));
}
将新项目添加到列表末尾。现在没有溢出数组的风险,因而这个方法是完全安全的。 顺便说一下,ArrayList 不仅可以通过其下标来查找对象,而且可以通过对象查找下标:它可以使用一个引用来查找 ArrayList 中对象的下标!这正是 indexOf() 方法的用途: 我们传递所需对象的引用,而 indexOf() 返回其下标:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   int thomasIndex = cats.indexOf(thomas);
   System.out.println(thomasIndex);
}
输出:
0
这就对了。托马斯对象确实存储在元素 0 中。 数组并非只有缺点。毫无疑问,数组还有很多优点。其中一个优点就是按下标搜索元素的功能。这是因为指向一个下标也就是指向一个特定的内存地址,因此以这种方式搜索数组就非常快。ArrayList 也知道如何实现这一点:get() 方法实现下面的代码:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   Cat secondCat = cats.get(1);

   System.out.println(secondCat);
}
输出:
Cat{name='比蒙斯'}
此外,你可以轻松查找 ArrayList 是否包含特定的对象。这是通过使用 contains() 方法实现的:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   cats.remove(fluffy);
   System.out.println(cats.contains(fluffy));
}
该方法会检查 ArrayList 的内部数组是否包含元素,并返回布尔值(true 或 false)。 ArrayList 类 - 1输出:
false
另一个有关插入的重要信息。 ArrayList 支持你使用下标不仅在数组末端而且在任何地方插入元素。 可以使用两个方法实现此功能:
  • add(int index, Cat element)
  • set(int index, Cat element)
作为参数,这两种方法都采用要插入位置的下标和对对象本身的引用。 区别是使用 set() 插入将覆盖旧值。使用 add() 插入时,首先将数组从 [index] 到末尾的所有元素移动一位,然后在所产生的空位置添加指定的对象。 下面是一个示例:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);

   System.out.println(cats.toString());

   cats.set(0, lionel);// Now we have a list of 2 cats. Adding a 3rd using set

   System.out.println(cats.toString());
}
输出:
[[Cat{name='托马斯'}, Cat{name='比蒙斯'}] [Cat{name='莱昂内尔·梅西'}, Cat{name='比蒙斯'}]
我们有一个包含 2 个猫的列表。然后,我们使用 set() 方法插入了另一个猫作为元素 0。因此,旧元素已被新元素替代。

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);

   System.out.println(cats.toString());

   cats.add(0, lionel);// Now we have a list of 2 cats. Adding a 3rd using add

   System.out.println(cats.toString());
}
在这里,add() 的工作方式有些不同。它会将所有元素移到右侧,然后将新元素作为元素 0 写入。 输出:
[Cat{name='托马斯'}, Cat{name='比蒙斯'}] [Cat{name='莱昂内尔·梅西'}, Cat{name='托马斯'}, Cat{name='比蒙斯'}]
要完全清除列表,请使用 clear() 方法:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   cats.clear();

   System.out.println(cats.toString());
}
输出:
[]
一切内容都从列表中移除。 顺便指出,与数组不同,ArrayList 会重写 toString() 方法并且已经将列表适当地显示为字符串。对于普通数组,我们必须使用 Arrays 类实现这一点。 前面我提到了 Arrays:Java 支持你轻松在数组和 ArrayList 之间切换,即从一个转换为另一个。Arrays 类体用了 Arrays.asList() 方法实现此功能。我们使用该方法来获取数组形式的内容,并将这些内容传递给 ArrayList 构造方法:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();


   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   Cat[] catsArray = {thomas, behemoth, lionel, fluffy};

   ArrayList<Cat> catsList = new ArrayList<>(Arrays.asList(catsArray));
   System.out.println(catsList);
}
输出:
[Cat{name='托马斯'}, Cat{name='比蒙斯'}, Cat{name='莱昂内尔·梅西'}, Cat{name='福乐斐'}]
你还可以按相反方向执行:ArrayList 对象获取数组。我们使用 toArray() 方法执行此操作:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();

   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   Cat[] catsArray = cats.toArray(new Cat[0]);

   System.out.println(Arrays.toString(catsArray));
}
注意:我们已将空数组传递给 toArray() 方法。这不是错误。在 ArrayList 类内部,该方法的实现方式是传递一个空数组来提高其性能。只需记住这一点,将来会用到(当然,你可以传递某个特定大小的数组;这也是可以的)。 那现在看看大小。列表的当前大小可以使用 size() 方法找到:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();


   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   System.out.println(cats.size());
}
请务必了解的一点是,与数组的 length 属性不同,ArrayList.size() 方法返回元素的实际数量,而不是原始容量。毕竟,我们在创建 ArrayList 时并未指定大小。不过,你可以指定大小 - ArrayList 提供了合适的构造方法。 但就添加新元素而言,这并没有改变其行为:

public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>(2);// create an ArrayList with an initial capacity of 2


   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   System.out.println(cats.size());
}
控制台输出:
4
我们创建了一个含 2 个元素的列表,但可以在需要时静默扩展。 另一个考虑是,如果我们最初创建一个非常小的列表,就不得不更频繁地扩展,而这会使用部分资源。 在本节课中,我们几乎没有讲述从 ArrayList 中移除元素的过程,但并不意味着我们将其忘在脑后。我们将这个主题放在单独的课程中进行介绍,稍后会看到 :)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION