实现List接口的ArrayList和LinkedList

发布于:2024-07-03 ⋅ 阅读:(76) ⋅ 点赞:(0)
package study;

import java.util.*;

public class day01_list {
    public static void main(String[] args) {
        // <Integer> 这个尖括号表示的是 Java 的泛型(Generics)
        // 泛型是 Java 5 引入的一项特性,它允许你在 类、接口和方法 中使用类型参数,从而使代码更具通用性和类型安全性
        // List 是一个接口,代表一个有序的集合,其中可以包含重复的元素
        // <Integer> 是泛型参数,指定这个列表只能存储Integer类型的对象

        /*
          ArrayList是List接口的一个具体实现类,使用动态数组来存储元素
          不能使用new List<>(),因为List是一个接口,不能直接实例化,接口只是定义了一组方法,没有具体实现
          必须实例化一个 List 接口的实现类,比如 ArrayList、LinkedList

          钻石操作符 <> 是 Java 7 引入的一个简化语法,用于推断泛型参数类型。
          在右边使用 new ArrayList<>() 时,编译器会自动推断出这个 ArrayList 的类型参数是 Integer,因为左边明确指定了 List<Integer>
         */

        /*
            List 是一个接口,而 ArrayList 是这个接口的一个具体实现类。
            在编写代码时,使用接口类型来声明变量,而用具体的实现类来实例化对象,这是面向对象编程中的一种最佳实践,
            称为“接口和实现分离”。

            多态性:
            通过使用接口类型,你可以利用多态性来处理对象。
            例如,你可以编写一个方法,它接收 List 类型的参数,这样这个方法就可以处理任何 List 的实现类(如 ArrayList、LinkedList、Vector 等)
            public void processList(List<Integer> list) {
                for (Integer number : list) {
                    System.out.println(number);
                }
            }

            虽然 List 是一个接口,但在 Java 中,接口类型的变量可以引用任何实现这个接口的对象。这是因为接口定义了一组方法,而实现类提供了这些方法的具体实现。
         */
        // 多态性
        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();
        processList(arrayList);
        processList(linkedList);

        /*
            使用接口类型声明变量的限制
            当你使用接口类型(例如 List)声明一个变量时,你只能调用接口中定义的方法
            然而,如果你需要调用实现类特有的方法,例如 ArrayList 的 ensureCapacity 方法,你就不能直接通过 List 类型的引用来调用。以下代码是非法的:
         */
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        System.out.println(list.size()); // 这行代码是合法的,因为 add 和 size 是 List 接口中的方法
        // list.ensureCapacity(100); // 编译错误,因为 List 接口中没有 ensureCapacity 方法
    }

    public static void processList(List<Integer> list) {
        for (Integer number : list) {
            System.out.println(number);
        }
    }
}


interface Animal {
    void eat();
}

interface jiekou {
    void t();
}

// 一个类只能继承一个父类(单继承),但可以实现多个接口(多实现)
// 一个接口可以继承多个其他接口(多继承)
interface Dog extends Animal, jiekou {}

class ArrayList_use {
    /*
    Iterable<E> (接口)
           ↑
    Collection<E> (接口) - 继承自 Iterable<E>
           ↑
    List<E> (接口) - 继承自 Collection<E>
           ↑
    AbstractCollection<E> (抽象类) - 部分实现 Collection<E>
           ↑
    AbstractList<E> (抽象类) - 继承自 AbstractCollection 部分实现 List<E>
           ↑
    ArrayList<E> (类) - 继承自 AbstractList<E> 并实现 List<E> 和 Collection<E>

    ArrayList 类除了实现 List 接口的方法外,还提供了一些独有的方法。这些方法在 List<> list 中无法直接使用:
    clone(): ...
    trimToSize(): 调整 ArrayList 的容量大小,使其等于当前列表的大小,减少内存使用。
    ensureCapacity(int minCapacity): 增加 ArrayList 的容量,确保它至少可以容纳指定数量的元素。
     */

    // 数组可以包含基本数据类型和引用类型,ArrayList只能包含引用类型
    // 数组的大小在定义后不可以调整大小。ArrayList是基于动态数组实现的,可以通过内部扩容自动调整容量
    public static void main(String[] args) {
        // 三种构造方法
        List<Integer> list0 = new ArrayList<>(); // 构造一个初始容量为 10 的空列表
        list0.add(1);
        System.out.println(list0.size());

        List<Integer> list1 = new ArrayList<>(0); // 直接指定初始大小
        list1.add(1);
        System.out.println(list1.size());

        List<Integer> existingList = Arrays.asList(1, 2, 3);  // 返回一个ArrayList对象
        List<Integer> list2 = new ArrayList<>(existingList);  // 使用现成的list来构建
        System.out.println(list2.size());
        Set<Integer> existingSet = new HashSet<>(Arrays.asList(1, 2, 3, 4));
        List<Integer> list3 = new ArrayList<>(existingSet);  // 使用现成的set来构建
        System.out.println(list3.size());

        // 尾插数据时速度是o(1)
        // 非尾插数据,会涉及元素的移动,在添加元素时会涉及到扩容的问题。
        // 超出初始长度时,会创建新的数组,将原数组数据赋值到新数组中。扩容:原来数组的大小+原来数组的一半(1.5倍)

        // ArrayList 是线程不安全的:
        // 线程不安全表示当多个线程同时访问一个对象时,如果没有适当的同步机制,可能会导致数据不一致或其他错误。
        // 在 ArrayList 中,多个线程同时修改列表可能会导致未定义的行为,包括但不限于数据丢失、重复或异常

        // 1. add
        List<String> list4 = new ArrayList<>(Arrays.asList("A", "B", "C"));
        list4.add("D");
        System.out.println(list4);

        list4.add(1, "X");
        System.out.println(list4);

        // 2. addAll
        List<String> temp = Arrays.asList("A", "B", "C");
        list4.addAll(temp);
        System.out.println(list4);
        list4.addAll(1, temp);
        System.out.println(list4);

        // 3. forEach
        list4.forEach(System.out::println);

        // 4. clear
        list4.clear();
        System.out.println(list4);

        // 5. clone
        // clone() 方法是 ArrayList 类特有的方法,而不是 List 接口的方法。
        // 因此,如果你需要克隆一个 ArrayList,你需要确保你的变量类型是 ArrayList 而不是 List。
        ArrayList<String> existingListTempClone = new ArrayList<>(Arrays.asList("A", "C"));
        ArrayList<String> clonedArrayList = (ArrayList<String>) existingListTempClone.clone();
        List<String> cloneList = (List<String>) existingListTempClone.clone();
        // 修改克隆list的对象看看会不会对原来造成影响
        // 6. set
        cloneList.set(0, "xiugai");
        // 7. get
        System.out.println(cloneList.get(0));
        System.out.println(existingListTempClone.get(0));

        // 8.contains
        System.out.println(cloneList.contains("xiugai"));
        // 9. containsAll
        List<String> containsList0 = new ArrayList<>(Arrays.asList("xiugai", "C"));
        List<String> containsList1 = new ArrayList<>(Arrays.asList("xiugai", "X"));
        System.out.println(cloneList.containsAll(containsList0));
        System.out.println(cloneList.containsAll(containsList1));

        // 10. indexOf
        List<Integer> indexOfList = new ArrayList<>(Arrays.asList(1, 2, 3));
        System.out.println(indexOfList.indexOf(2));

        // 11. removeAll
        List<Integer> removeList = new ArrayList<>(Arrays.asList(2, 3, 2));
        indexOfList.removeAll(removeList);

        // 12. remove
        List<Integer> removeList1 = new ArrayList<>(Arrays.asList(1, 2, 3));
        removeList1.remove(0);
        removeList1.remove(Integer.valueOf(2));  // Integer.valueOf(2) 将整数 2 包装为 Integer 对象
        System.out.println(removeList1);

        // 13. size
        // 14. isEmpty
        // 15. subList
        // 16. toArray
        // 17. toString
        // 18. lastIndexOf
        List<Double> doubleList = new ArrayList<>(Arrays.asList(1.0, 5.0, 2.0, 2.0));
        System.out.println(doubleList.size());
        System.out.println(doubleList.isEmpty());
        System.out.println(doubleList.subList(1, 2));
        System.out.println(doubleList.toArray()[2]);
        System.out.println(doubleList.toString());
        System.out.println(doubleList.lastIndexOf(Double.valueOf(2.0)));
    }
}

class LinkedList_use {
    /*
    Iterable<E> (接口)
           ↑
    Collection<E> (接口) - 继承自 Iterable<E>
           ↑
    List<E> (接口) - 继承自 Collection<E>
           ↑
    AbstractCollection<E> (抽象类) - 部分实现 Collection<E>
           ↑
    AbstractList<E> (抽象类) - 继承自 AbstractCollection 部分实现 List<E>
           ↑
    AbstractSequentialList<E> (抽象类) - 继承自 AbstractList<E>
           ↑
    LinkedList<E> (类) - 继承自 AbstractSequentialList<E> 并实现 List<E>, Deque<E>, Queue<E>, Collection<E>


    Iterable<E> (接口)
           ↑
    Collection<E> (接口) - 继承自 Iterable<E>
           ↑
    Queue<E> (接口) - 继承自 Collection<E>
           ↑
    Deque<E> (接口) - 继承自 Queue<E>  Double Ended Queue

    LinkedList 类除了实现 List 接口的方法外,
    还实现了 Deque 接口,因此提供了一些双端队列(Deque)和队列(Queue)的特有方法。这些方法在 List<> list 中无法直接使用:
    void addFirst(E e)
    void addLast(E e)
    boolean offerFirst(E e)  // 头部插入元素,返回是否成功,成功为 true,失败为 false
    boolean offerLast(E e)
    E removeFirst()
    E removeLast()
    E pollFirst()  // 检索并删除此列表的第一个元素,如果此列表为空则返回null
    E pollLast()
    E getFirst()
    E getLast()
    E peekFirst()  // 返回头部元素,不删除
    E peekLast()

    LinkedList也是多线程环境下不安全
     */
    LinkedList<Integer> list = new LinkedList<>();

}

/**
 * ArrayList和LinkedList
 *
 * 数据结构
 * ArrayList底层是动态数组,在空间中是一段连续的内存
 * LinkedList底层是链表,内存不连续,对内存要求低。
 *
 * 空间利用:
 * ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间
 * LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
 *
 * 尾插数据:对ArrayList和LinkedList而言,在列表末尾增加一个元素时间复杂度都是 o(1)。
 * 对ArrayList来说,尾插速度非常快,但是会涉及到一个扩容的问题,会进行大量的复制操作。
 * 而对LinkedList而言,使用了链表结构,不需要维护大小,但是他每次添加需要新建entry对象,进行赋值操作。
 * 总得来说如果不涉及到扩容的问题,ArrayList的尾插会更快一些。
 *
 * 非尾插:
 * 在ArrayList的中间插入或删除一个元素会导致列表中剩余的元素都会被移动,因此效率较低,插入位置越靠前,需要复制调整的元素也越多,效率也就越慢。
 * LinkedList的非尾插,首先要通过循环找到需要插入的位置。如果此位置处于前半段,则从前往后找;若其位置处于后半段,则从后往前找。所以在靠前和靠后的位置插入非常高效,但是在拥有大量元素的情况下,在链表的中间插入元素效率很低。(首尾复杂度为o(1),其余为o(n),整体来说还是o(n))
 *
 * 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会有更好的性能。
 * 当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
 */
class TestArrayListAndLinkedList {
    public static void main(String[] args) {
        List<String> arraylist = new ArrayList<>();
        int listSize = 100;
        for(int i=0; i<listSize; i++){
            arraylist.add("初始数据");
        }
        System.out.println("ArrayList插数据用时:" + insert(arraylist));

        List<String> linkedList = new LinkedList<>();
        for(int i=0; i<listSize; i++){
            linkedList.add("初始数据");
        }
        System.out.println("LinkedList插数据用时:" + insert(linkedList));
    }
    // index=10插入:
    // ArrayList数据用时:3
    // LinkedList数据用时:0

    // list.size()/2插入
    // ArrayList插数据用时:4
    // LinkedList插数据用时:1

    // list.size()-10
    // ArrayList插数据用时:4
    // LinkedList插数据用时:2

    // 尾插:
    // ArrayList插数据用时:3
    //LinkedList插数据用时:0
    //插入数据,分别在头部、中间、尾部(四种插入位置:10、list.size()/2、list.size()-10、尾插)
    public static Long insert(List<String> list){
        long startTime = System.currentTimeMillis();
        int insertNum = 500;
        for (int i=0; i<insertNum; i++){
            list.add(10,"数据"+i);  // 中间靠前部分插入
//            list.add(list.size()/2,"数据"+i);  //中间插入
//            list.add(list.size()/2+10,"数据"+i);  //中间靠后插入
//            list.add("数据"+i);  // 尾插
        }
        long endTime = System.currentTimeMillis();
        return endTime-startTime;
    }

}




接口和抽象类是 Java 编程语言中用于实现抽象的两种主要机制。它们允许你定义类的骨架和行为规范,而不提供具体的实现。

接口(Interface)

概述
接口是一个完全抽象的类,它只包含方法的声明(没有方法体)和常量。接口用于定义一组方法,这些方法必须由实现接口的类提供具体的实现。

特点

  • 完全抽象:接口中的方法默认是 public 和 abstract,不能有方法体(直到 Java 8 引入默认方法和静态方法)。
  • 多重继承:一个类可以实现多个接口,打破了 Java 单继承的限制。
  • 变量:接口中的变量默认是 public、static 和 final。
  • 默认方法和静态方法:从 Java 8 开始,接口可以包含默认方法(有方法体)和静态方法。
  • 私有方法:从 Java 9 开始,接口可以包含私有方法,这些方法只能在接口中调用。

示例

public interface Animal {
    void eat();  // 抽象方法,默认是 public abstract
    void sleep();

    // 默认方法
    default void breathe() {
        System.out.println("Breathing...");
        log("breathe");
    }

    // 静态方法
    static void info() {
        System.out.println("Animal Interface");
        logStatic("info");
    }

    // 私有方法
    private void log(String activity) {
        System.out.println("Logging activity: " + activity);
    }

    // 私有静态方法
    private static void logStatic(String activity) {
        System.out.println("Logging static activity: " + activity);
    }
}

抽象类(Abstract Class)

抽象类在 Java 中是用于定义类的骨架的类,包含具体方法(有方法体)和抽象方法(没有方法体)。抽象类不能被实例化,必须被子类继承,并且,如果子类不是抽象类,那么子类必须实现所有抽象方法。

  • 抽象方法和具体方法:抽象类可以包含抽象方法和具体方法。抽象方法没有方法体,具体方法有方法体。子类必须实现抽象方法,或者自身也声明为抽象类。
  • 成员变量:抽象类可以包含成员变量,可以使用各种访问修饰符(public、protected、private)。
  • 构造方法:抽象类可以有构造方法,但不能用来创建对象实例。构造方法通常用于初始化类的成员变量。
  • 继承:一个类只能继承一个抽象类(单继承),但抽象类可以被多个类继承。
  • 实现接口:抽象类可以实现接口,并且不需要实现接口中的所有方法。子类可以选择实现剩余的方法。
public abstract class Animal {
    private String name;  // 成员变量

    // 构造方法
    public Animal(String name) {
        this.name = name;
    }

    // 抽象方法
    public abstract void eat();
    public abstract void sleep();

    // 具体方法
    public void breathe() {
        System.out.println(name + " is Breathing...");
    }

    // 获取名称的方法
    public String getName() {
        return name;
    }
}