集合体系概述以及Collection集合常用API + 迭代器详解 + CopyOnWriteArrayList-CSDN博客

86 阅读14分钟

目录

一. 集合

二. 集合的体系特点

集合类体系结构

 Collection集合体系

 Collection集合体系的特点、使用场景总结

 泛型

泛型相当于是一个标签

三. Collection集合常用API 

 四. Collection集合的遍历方式

方式一:迭代器      迭代器是不能遍历数组的

迭代器详解:迭代器之Iterable & Iterator

迭代器之fail-fast 

CopyOnWriteArrayList 

方式二:foreach/增强for循环

增强for循环

  方式三:Lambda表达式

 五. Collection集合存储自定义类型的对象


一. 集合

  • 集合与数组类似,都是一种容器。
  • 集合是Java中存储对象数据的一种容器。
  • 集合也被称为对象容器。

数组的特点

集合的特点

  • 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球,可大可小。
  • 集合非常适合做元素个数不能确定,且要做元素的增删操作的场景。
  • 同时,集合提供的种类特别丰富,功能也是非常强大的,开发中集合用的更多。
  • 注意:集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。
  • 包装类:把基本数据类型变成了一个引用数据类型,不是基本数据而是对象数据。
  • 数据也可以称为元素。

二. 集合的体系特点

集合类体系结构

 Collection集合体系

  • Collection接口是单列集合的祖宗接口。
  • Collection集合不支持索引,不能用fori来遍历。
  • List接口和Set接口都继承了Collection接口。
  • ArrayList和LinkedList实现了List接口,实现类就是一种所谓的子类。
  • ArrayList和LinkedList它们底层存储数据的结构是不一样的,在以后做增删操作的时候,它们的性能是有差别的。
  • Collection集合并没有规定什么特点,它只是代表了一个普通集合,真正分特点的是由List和Set分特点的。
  • 添加的元素有序:先加了的元素在前面,后加了的元素在后面。
  • 可重复:允许有同样的值的元素。
  • 有索引:就是说第一个元素的索引是0,第二个元素的索引是1。 
  • 添加的元素无序:先加的元素可能是跑到后面去了,后加的元素可能是跑到前面去了。
  • 不重复:就是不允许有两个同样的值在里面,它会去重复的。
  • 集合打内容,因为集合本身已经重写了toString方法了,所以它里面打的是内容。 

 Collection集合体系的特点、使用场景总结

 泛型

  • 泛型相当于是一个标签

 

package com.gch.d1_collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

/**
   目标:明确Collection集合体系的特点
 */
public class CollectionDemo1 {
    public static void main(String[] args) {
        // List系列集合:添加的元素是有序、可重复、有索引
        // 把子类对象给到父类接口,父类引用指向子类对象,这是多态的写法
        Collection list = new ArrayList();
        list.add("Java");
        list.add("Java");
        list.add("Mybatis");
        list.add(23);
        list.add(23);
        list.add(false);
        list.add(false);
        System.out.println(list); // [Java, Java, Mybatis, 23, 23, false, false]

        // Set系列集合:添加的元素是无序、不重复、无索引
        Collection list1 = new HashSet();
        list1.add("Java");
        list1.add("Java");
        list1.add("Mybatis");
        list1.add(23);
        list1.add(23);
        list1.add(false);
        list1.add(false);
        System.out.println(list1); // [Java, false, 23, Mybatis]

        System.out.println("------------------泛型-------------------");
//        Collection<String> list2 = new ArrayList<String>();
        // 泛型相当于是一个标签
        Collection<String> list2 = new ArrayList<>(); // 从JKD7开始之后后面的泛型类型申明可以省略不写
        list2.add("Java");
//        list2.add(23); // 直接报错,添加的类型为String
        list2.add("Mybatis");

        // 集合也被称为对象容器
        // 集合和泛型不支持基本数据类型,只能支持引用数据类型
        Collection<Integer> list3 = new ArrayList<>();
        list3.add(23); // 自动装箱
        list3.add(233);
        list.add(2333);

        Collection<Double> list4 = new ArrayList<>();
        list4.add(23.4);
        list4.add(23.0);
        list4.add(233.3);
    }
}

三. Collection集合常用API 

package com.gch.d2_collection_api;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
    目标:Collection集合的常用API.
    Collection是集合的祖宗类,它的功能是全部集合都可以继承使用的,所以要学习它。
    Collection API如下:
         - public boolean add(E e):  把给定的对象添加到当前集合中 。
         - public void clear() :清空集合中所有的元素。
         - public boolean remove(E e): 把给定的对象在当前集合中删除。
         - public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
         - public boolean isEmpty(): 判断当前集合是否为空。
         - public int size(): 返回集合中元素的个数。
         - public Object[] toArray(): 把集合中的元素,存储到数组中。
    小结:
        记住以上API。
 */
public class CollectionDemo {
    public static void main(String[] args) {
        // HashSet:添加的元素是无序,不重复,无索引。

        Collection<String> c = new ArrayList<>();
        // List集合允许元素重复,基本上添加元素不会失败
        // 1.添加元素, 添加成功返回true。
        c.add("Java");
        c.add("HTML");
        System.out.println(c.add("HTML"));
        c.add("MySQL");
        c.add("Java");
        System.out.println(c.add("黑马"));
        System.out.println(c); // [Java, HTML, HTML, MySQL, Java, 黑马]

        // 2.清空集合的元素。
        // c.clear(); // 直接清空集合的元素
        // System.out.println(c); // []

        // 3.判断集合是否为空 是空返回true,反之。
        // System.out.println(c.isEmpty());

        // 4.获取集合的大小。
        System.out.println(c.size());

        // 5.判断集合中是否包含某个元素。 精准匹配的
        System.out.println(c.contains("Java"));  // true
        System.out.println(c.contains("java")); // false
        System.out.println(c.contains("黑马")); // true

        // 6.删除某个元素:如果有多个重复元素默认删除前面的第一个!
        // 只能通过元素值来删,不能通过索引删,因为这个集合是Collection类型的一个集合。
        System.out.println(c.remove("java")); // false
        System.out.println(c);
        System.out.println(c.remove("Java")); // true
        System.out.println(c);

        // 7.把集合转换成数组  [HTML, HTML, MySQL, Java, 黑马]
        Object[] arrs = c.toArray(); // 数组:[HTML, HTML, MySQL, Java, 黑马]
        System.out.println("数组:" + Arrays.toString(arrs));

        System.out.println("----------------------拓展----------------------");
        Collection<String> c1 = new ArrayList<>();
        c1.add("java1");
        c1.add("java2");
        Collection<String> c2 = new ArrayList<>();
        c2.add("赵敏");
        c2.add("殷素素");
        // addAll把c2集合的元素全部倒入到c1中去。c2里面的数据还是在的
        c1.addAll(c2);
        System.out.println(c1); // [java1, java2, 赵敏, 殷素素]
        System.out.println(c2); // [赵敏, 殷素素]
    }
}

 四. Collection集合的遍历方式

  • 方式一:迭代器
  • 方式二:foreach/增强for循环
  • 方式三:lambda表达式

方式一:迭代器      迭代器是不能遍历数组的

迭代器详解:迭代器之Iterable & Iterator

Java中的迭代器是一个接口,名为Iterator,它主要有两个抽象方法让子类实现。

Iterator迭代器

这两个方法不像List的get()方法那样依赖索引获取数据,也不像Queue的poll()方法那样,依赖特定规则获取数据,因此迭代器的方法将通用性做到了极致 可以访问不同特性的集合数据,而无需关心它们的内部实现。

 

迭代器的访问方式特别像游标卡尺,每访问一个数据,游标就会前进一格,就这样不断将游标前移,从而达到了数据遍历的效果。

注意:

  • 集合并不是直接去实现Iterator接口,而是去实现Iterable接口,用这个Iterable定义的方法去返回当前集合的迭代器。
  • Collection就继承了Interable接口,所以Collection体系的集合都得按照这种方式返回迭代器,以供访问数据。

 

Interable接口

为什么集合不直接实现迭代器Iterator,而要去实现Iterable接口,并用Iterable接口中定义的方法去返回当前集合的迭代器呢?

  • 因为集合如果直接实现迭代器Iterator的话,那别人调用了当前集合的next()方法,就会影响到你遍历数据,你本来希望从头开始遍历所有数据,然后别人可能已经将数据遍历完了,你就拿不到数据了,而通过实现Iterable这种方式,就可以每次返回新的迭代器,不同迭代器之间遍历数据时互不影响,所以这里也就能看出迭代器是具有独立性和隔离性。

总结:

  • Iterable用于返回迭代器实现了该接口的类就算是可迭代对象了,可以直接使用for-each循环遍历访问数据。
  • Iterator就是迭代器,用来遍历集合的数据,并无需关心集合的内部实现。for-each循环底层用的就是迭代器。
  • **集合不能直接实现Iterator接口,而要实现Iterable接口,以便每次返回新的迭代器,这么做是为了保证迭代器的独立性{**不同迭代器之间遍历元素时互不影响 **}和隔离性{**如果集合增加或删除了元素,不能影响到已有的迭代器 比如:我8点整获取了一个迭代器,访问的就是集合八点整的元素,如果集合在8点十分增删了元素,不能影响到我八点整获取到的迭代器 }。

迭代器之fail-fast 

  • 通过Iterable接口定义的方法,每次返回不同的迭代器,这只是独立性和隔离性的前提,要完全满足这两个特性还需要做其它处理。

如何才能满足这两个特性?

  • 最简单粗暴的方法就是每次获取迭代器时,将集合内所有的元素复制一份到迭代器中,这样集合的增删操作自然就不会影响到迭代器。
  • 不过这种做法有很多问题,首先我有多少个迭代器我就得复制多少份数据,这些重复的数据是一种严重的资源浪费,然后复制数据本身就是一种比较耗时的操作,为了保证复制时不会有其它线程在对元素进行增删,还得用上  锁  等机制来保证线程安全,复制数据就更加耗时了,这种做法既浪费了资源又性能低下,空间和时间都不行,自然不能用在生产中。

  • 为了优化性能,Java标准库中常用集合类的做法,是在获取迭代器时让迭代器保存一个int数值,这个int数值是集合的成员属性modCount,用来记录集合增删操作的次数: 当集合增删元素时,该数值就会+1。

  • 因为迭代器是集合的成员内部类,所以可以随时访问集合的成员属性: 迭代器在遍历元素时,会检查modCount是否和当初保存的数值一致,如果不一致就代表集合在获取迭代器之后进行了增删操作,此时迭代器就会抛出异常,停止迭代 {fail-fast} 。 

  • 如果碰到了影响正常逻辑的情况,自己无法处理时可以选择抛出异常终止逻辑,我们将这种处理方式称为fail-fast,即快速失败机制。
  • 当迭代器发现集合进行了增删后便选择抛出异常,就是一种典型的快速失败机制。

代码试验:

package com.gch.iterator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
   快速失败机制(fail-fast)
   当迭代器发现集合集合进行了增删后便选择抛出异常
   java.util.ConcurrentModificationException
 */
public class Demo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("洋宝");
        Iterator iterator = list.iterator();

        list.add("加一");
        /**
         * 定义while循环,问一次取一次
         * while循环一旦条件是false,循环结束
         * 迭代器的默认位置是在集合元素的第一个位置,也就是索引为0的位置
         * Iterator中的常用方法:
         *   boolean hasNext():询问当前位置是否又元素存在,存在返回true,不存在返回false
         *   E next():获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界
         */
        while(iterator.hasNext()){
            String ele = (String) iterator.next();
            System.out.println(ele);
        }
    }
}

  • 所以,在for-each循环中直接对集合进行增删,也可能会抛出异常(因为for-each循环底层是一个Iterator迭代器)。
  • 因为for-each实际上就是迭代器的语法糖。
package com.gch.iterator;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * for-each实际上是迭代器的语法糖
 * 在for-each循环中直接对集合进行增删,也会抛异常
 * java.util.ConcurrentModificationException
 */
public class Demo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("洋宝");
        list.add("国宝");

        for (String s : list) {
            if(Objects.equals(s,"国宝")){
                list.remove(s);
            }
        }

    }
}

  • 对集合直接进行增删后再进行迭代,自然会触发快速失败机制所以,如果想要遍历元素的同时进行一些增删操作,建议使用其它方式。
  • 迭代器的fail-fast机制能够保证迭代器的独立性和隔离性,但可能会引发并发修改异常。
  • 快速失败机制虽然尽可能地避免了意外情况的发生,从而保证了迭代器的独立性和隔离性,但抛出异常的威力太大,可能会导致整个程序逻辑终止。

有没有其它的解决方案,可以在不影响迭代器的同时对进行增删,并且还能保持较高性能呢?

  • 是有一种办法可以做到的,那就是CopyOnWrite{写入时复制}技术,简称COW

总结:

  • 其实,数据隔离的处理方式:无论是复制所有元素,还是fail-fast机制,还是后面要讲的CopyOnWrite,其本质都是保存迭代器当时的数据,只不过保存的数据不同而已。
  • 数据隔离的几种方式本质都是复制当时的数据,只不过复制的东西不同而已。

数据隔离的处理方式

CopyOnWriteArrayList => 读写分离

  • 迭代器的fail-fast机制能够保证迭代器的独立性和隔离性,但可能会引发并发修改异常,可有时候我们就是想在迭代元素的同时,集合还能安全的进行增删操作,并且还能保持较高的性能,针对这种场景,Java标准库提供了CopyOnWriteArrayList类

  • CopyOnWriteArrayList使用了CopyOnWrite机制,即写入时复制,简称COW, 它的原理并不复杂,我们知道数据隔离的几种方式本质上都是复制当时的数据,只不过复制的东西不同而已。

  • COW只会复制数据的引用,并不会复制数据本身,所以在获取迭代器时速度会很快。此时迭代器和集合都是持有的同一数组引用。

  • 为了避免增删元素时影响到迭代器,COW集合增删元素时就不是在原数组上操作了,而是会新建一个数组,然后将原数组的元素挨个复制过去,再在新数组上增删元素,修改完之后再将修改后的数组赋值回去,这样就做到了在增删元素时不会影响到之前的迭代器,可以保证写操作不会影响读操作了{读写分离=> 因为当你调用写操作时(add,set,remove),CopyOnWriteArrayList会创建一个原数组的副本,并在副本上执行写操作,因此,读操作仍然可以并发地访问原数组,不受写操作的干扰。完成写操作后,新的副本数组将替换成原数组 }
  • 副本指的是原数组的一个拷贝或者复制。
  • COW集合写入操作不会阻塞读取操作只有写写才会互斥

提问:这不还是复制了元素,性能不照样低下吗?而且每次增删改操作{写操作}时都要再复制一               份,岂不是更离谱?

  • 在集合增删时确实性能比较低,但是它在获取迭代器时或者说在获取数据时性能比较高,所以COW技术只适用于读多写少、且数据量不大的情况 ,比如像配置、商品类目这些变更很少的数据,你读取个成百上千次都不一定会有一次数据变更,偶尔的低性能换来绝大部分时间的高效操作是非常值得的,这也是COW很受欢迎的原因。 

CopyOnWriteArrayList的源码:

  • 该集合的迭代器会保存一份数组引用,用next()方法获取元素时,已经不需要比较什么modCount了,有剩余元素的话就直接返回下一个元素,根本不需要操心那么多。

 

  • 然后集合的增删方法和理论完全一致,都是复制一份数组后再去增删元素每次写操作都需要通过Arrays.copyOf()方法复制底层数组,时间复杂度为O(n),且会占用额外的内存空间。
  • CopyOnWriteArrayList增删改{add,remove,set}操作{写操作}还加上了锁,加上了synchronized同步代码块,这是为了保证线程安全,因此CopyOnWriteArrayList还是一个线程安全的List集合类,它不只是为了优化迭代器而设计,它还考虑到了并发环境下的操作,所以CopyOnWriteArrayList类还特别适用于并发场景中。
  • 加锁的缺点: 写操作时:增删改{add,remove,set}每个方法执行的时候都要去获得锁,性能比较低,性能会大大下降。

  • 这里要注意:CopyOnWriteArrayList的读操作{get(index)}并没有加锁,这是为了提高读操作的性能。 性能提高的同时就带来了一个缺点: 那就是在读取数据时可能读不到最新的数据比如集合增加元素才执行了一半,这时候去读数据,读到的就是老数据,这也就是我们常说的数据一致性问题。

总结:

缺点:

  • 每次写操作都需要通过Arrays.copyOf()复制底层数组,时间复杂度为O(n),且会占用额外的内存空间。因为CopyOnWriteArrayList适用于读多写少的场景,在写操作不频繁且内存资源充足的情况下,可以提升系统的性能表现。
  • 增删改操作时会复制多份数据,内存占用较大,容易引发GC;
  • 然后读数据时会有数据一致性问题。
Arrays.copyOf()补充:
  • Arrays.copyOf()方法的时间复杂度为O(n),其中n表示需要复制的数组长度。
  • 因为这个方法的底层实现原理是先创建一个新的数组,然后将原数组中的数据复制到新数组中,最后返回新数组。
  • 这个方法会复制整个数组,因此时间复杂度与数组长度成正比,即O(n)。

 =========================================================================

package com.gch.d3_collection_traversal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
    a.迭代器遍历集合。
        --流程:
            1.先获取当前集合的迭代器
                Iterator<String> it = lists.iterator();
            2.定义一个while循环,问一次取一次。
              通过it.hasNext()询问是否有下一个元素,有就通过
              it.next()取出下一个元素。
    小结:
        记住代码。
 */
public class CollectionDemo01 {
    public static void main(String[] args) {
        ArrayList<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("素素");
        lists.add("灭绝");
        System.out.println(lists); // [赵敏, 小昭, 素素, 灭绝]
        // [赵敏, 小昭, 素素, 灭绝]
        //   it

        System.out.println("--------------1.迭代器遍历--------------");
        // public interface Iterator<E>  Iterator是一个泛型接口
        // 1、得到当前集合的迭代器对象。 这个迭代器就是遍历器的意思
        Iterator<String> it = lists.iterator();

//        // next是先取元素再移位的
//        String ele = it.next();
//        System.out.println(ele); // 赵敏
//        System.out.println(it.next()); // 小昭
//        System.out.println(it.next()); // 素素
//        System.out.println(it.next()); // 灭绝
        // 访问越界了,运行报错
//        System.out.println(it.next()); // NoSuchElementException 出现没有/无此元素异常的错误

        // boolean hasNext():询问当前位置是否有元素存在,存在返回true,不存在返回false
        // E next():获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。

        // 2、定义while循环(问一次取一次)
        while (it.hasNext()){ // while循环一旦条件是false,循环结束
            String ele = it.next();
            System.out.println(ele);
        }
        // 迭代器的默认位置是在集合元素的第一个位置,也就是索引为0的位置
    }
}

方式二:foreach/增强for循环

foreach循环是Java中的一个语法糖,反编译之后就会发现它用的就是迭代器。

增强for循环

  • 增强for循环:既可以遍历集合也可以遍历数组。
  • 它是JDK5之后出现的,其内部原理是一个iterator迭代器,遍历集合相当于是迭代器的简化写法。
  • 实现iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了iterable接口。
  • 格式:

package com.gch.d3_collection_traversal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
     目标:Collection集合的遍历方式。

     什么是遍历? 为什么开发中要遍历?
     遍历就是一个一个的把容器中的元素访问一遍。
     开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。

     Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
     Collection集合的遍历方式有三种:
         (1)迭代器。
         (2)foreach(增强for循环)。
         (3)JDK 1.8开始之后的新技术Lambda表达式。

     b.foreach(增强for循环)遍历集合。
         foreach是一种遍历形式,可以遍历集合或者数组。
         foreach遍历集合实际上是迭代器遍历集合的简化写法。
         foreach遍历的关键是记住格式:
            for(被遍历集合或者数组中元素的类型 变量名称 : 被遍历集合或者数组){

            }
 */
public class CollectionDemo02 {
    public static void main(String[] args) {
        System.out.println("-------------------2.foreach遍历/增强for循环------------------------");
        Collection<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("殷素素");
        lists.add("周芷若");
        System.out.println(lists);
        // [赵敏, 小昭, 殷素素, 周芷若]
        //  ele
        System.out.println("------------------foreach遍历/增强for循环遍历集合----------------------");
        // foreach只是个技术名称,真正用到的还是for关键字
        for (String ele : lists) {
            System.out.println(ele);
        }

        System.out.println("--------------------foreach遍历数组/增强for循环遍历数组---------------------------");
        double[] scores = {100, 99.5 , 59.5};
        for (double score : scores) {
            System.out.println(score);
        }

        for (double score : scores) {
            System.out.println(score);
            if(score == 59.5){
                score = 100.0; // foreach遍历修改变量无意义,不会影响数组的元素值。
            }
        }
        System.out.println(Arrays.toString(scores)); // [100.0, 99.5, 59.5]
    }
}

 方式三:Lambda表达式

package com.gch.d3_collection_traversal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;

/**
     目标:Collection集合的遍历方式。
     Collection集合的遍历方式有三种:
         (1)迭代器。
         (2)foreach(增强for循环)。
         (3)JDK 1.8开始之后的新技术Lambda表达式。
     c.JDK 1.8开始之后的新技术Lambda表达式。
 */
public class CollectionDemo03 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("殷素素");
        lists.add("周芷若");
        System.out.println(lists); // [赵敏, 小昭, 殷素素, 周芷若]
        System.out.println("--------------3.Lambda表达式遍历-------------");
        // [赵敏, 小昭, 殷素素, 周芷若]
        //  s
        lists.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

//        lists.forEach(s -> {
//                System.out.println(s);
//        });

        // lists.forEach(s ->  System.out.println(s) );

        lists.forEach(System.out::println);
    }
}

 五. Collection集合存储自定义类型的对象

  • 结论:集合中存储的是元素对象的地址。

package com.gch.d4_collection_object;

public class Movie {
    private String name;
    private double score;
    private String actor;

    public Movie() {
    }

    public Movie(String name, double score, String actor) {
        this.name = name;
        this.score = score;
        this.actor = actor;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    public String getActor() {
        return actor;
    }

    public void setActor(String actor) {
        this.actor = actor;
    }

    @Override
    public String toString() {
        return "Movie{" +
                "name='" + name + '\'' +
                ", score=" + score +
                ", actor='" + actor + '\'' +
                '}';
    }
}
package com.gch.d4_collection_object;

import java.util.ArrayList;
import java.util.Collection;

public class TestDemo {
    public static void main(String[] args) {
        // 1、定义一个电影类
        // 2、定义一个集合对象存储3部电影对象
        Collection<Movie> movies = new ArrayList<>();
        movies.add(new Movie("《你好,李焕英》", 9.5, "张小斐,贾玲,沈腾,陈赫"));
        movies.add(new Movie("《唐人街探案》", 8.5, "王宝强,刘昊然,美女"));
        movies.add(new Movie("《刺杀小说家》",8.6, "雷佳音,杨幂"));

        System.out.println(movies);

        // 3、遍历集合容器中的每个电影对象
        for (Movie movie : movies) {
            System.out.println("片名:" + movie.getName());
            System.out.println("评分:" + movie.getScore());
            System.out.println("主演:" + movie.getActor());
        }
    }
}