Java - List(泛型、包装类、List)

本节目标

  • 学习一个新的 java 语法泛型的使用
  • 学习一个新的 java 概念,包装类
  • List / ArrayList / LinkedList 的基本使用
  • ArrayList 类的使用
  • LinkedList 类的使用

1. 预备知识-泛型(Generic)

1.1 泛型的引入

问题:我们之前实现过的顺序表,只能保存 int 类型的元素,如果现在需要保存 指向 Person 类型对象的引用的顺序表,请问应该如何解决?如果又需要保存指向 Book 对象类型的引用呢?
回答

  1. 首先,我们在学习多态过程中已知一个前提,基类的引用可以指向子类的对象。
  2. 其次,我们也已知 Object 是 java 中所有类的祖先类。

那么,要解决上述问题,我们很自然的想到一个解决办法,将我们的顺序表的元素类型定义成 Object 类型,这样我们的 Object 类型的引用可以指向 Person 类型的对象或者指向 Book 类型的对象了。

示例代码:

class MyArrayList {
    private int[] elem;
    private int usedSize;
    public MyArrayList() {
        this.elem = new int[10];
        //this.elem = new E[10];
    }
    public void add(int val) {
        this.elem[usedSize] = val;
        usedSize++;
    }
    public int get(int pos) {
        return this.elem[pos];
    }
}

变为

class MyArrayList {
    private Object[] elem;
    private int usedSize;
    public MyArrayList() {
        this.elem = new Object[10];
        //this.elem = new E[10];
    }
    public void add(Object val) {
        this.elem[usedSize] = val;
        usedSize++;
    }
    public Object get(int pos) {
        return this.elem[pos];
    }
}

这样,我们可以就可以很自由的存储指向任意类型对象的引用到我们的顺序表了。
示例代码:

public static void main(String[] args) {
    MyArrayList myArrayList = new MyArrayList();
    myArrayList.add(1);
    myArrayList.add("hello");
//遗留问题:现在的 MyArrayList 虽然可以做到添加任意类型的引用到其中了
//但遇到以下代码就会产生问题。
    int ret = (int)myArrayList.get(1);
// 将 Object 类型转换为 int 类型,需要类型转换才能成功
// 这里编译正确,但运行时会抛出异常 ClassCastException
    System.out.println(ret);
}
//结果为:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at TestDemo2.main(TestDemo2.java:93)

Process finished with exit code 1

提示:问题暴露的越早,影响越小。编译期间的问题只会让开发者感觉到,运行期间的错误会让所有的软件使用者承受错误风险。

所以我们需要泛型这种机制,可以
1. 增加编译期间的类型检查
2. 自动进行强制类型转换,不需要手动做类型转换

1.2 泛型的分类

1. 泛型类
2. 泛型方法

1.3 泛型类的定义的简单演示

关于泛型类的定义,这里只是了解即可,我们重点学习泛型类的使用。

// 1. 尖括号 <> 是泛型的标志
// 2. E 是类型变量(Type Variable),变量名一般要大写
// 3. E 在定义时是形参,代表的意思是 MyArrayList 最终传入的类型,但现在还不知道
public class MyArrayList<E> {
    private E[] array;
    private int size;
    ...
}

注意: 泛型类可以一次有多个类型变量,用逗号分割。

1.4 泛型类的使用

// 定义了一个元素是 Book 引用的 MyArrayList
MyArrayList<Book> books = new MyArrayList<Book>();
books.add(new Book());

//泛型已经自动进行强制类型转换,不需要手动做类型转换
Book book = book.get(0);

//此处会产生编译错误,Person 类型无法转换为 Book 类型
//实现了编译期间的类型检查 
books.add(new Person());

//此处会产生编译错误,book.get(0)类型的返回值为 BOOK,无法转换为 Person 类型
//实现了编译期间的类型检查 
Person person = book.get(0);

通过以上代码,我们可以看到泛型类的一个使用方式:只需要在所有类型后边跟尖括号,并且尖括号内是真正的类型,即 E 可以看作的最后的类型。
注意: Book 只能想象成 E 的类型,但实际上 E 的类型还是 Object。

泛型实现顺序表 示例

class MyArrayList<E> {
    private E[] elem;
    private int usedSize;
    public MyArrayList() {
        this.elem = (E[])new Object[10]; 
//这一行虽然可以使用,但是也是错误的,因为实际上还是Object[]数组,
//此种强转成泛型的方法有可能出错,例如下面的main5()方法,
//Object[] 类整个强转成为String[] 类,就会报异常ClassCastException。
//要想真正创建数据的存放,需要用到反射。
        //this.elem = new E[10];   //这一行是错误的,会出现编译错误
    }
    public void add(E val) {
        this.elem[usedSize] = val;
        usedSize++;
    }
    public E get(int pos) {
        return this.elem[pos];
    }
//    public  T[] getArray(int size) {
//     T[] genericArray = new T[size]; // suppose this is allowed
//        return genericArray;
//    }

    public Object[] getArray(int size) {
        Object[] genericArray = new Object[size];
        return genericArray;
    }
}

public class TestDemo2 {
    public static void main5(String[] args) {
        MyArrayList<String> myArrayList1 = new MyArrayList<>();
        String[] rets = (String[])myArrayList1.getArray(10);
    }
//getArray()方法返回Object[]类,Object[]类整个强转成为String[]类,
//就会报异常ClassCastException。

    public static void main4(String[] args) {
        MyArrayList<String> myArrayList1 = new MyArrayList<>();
        System.out.println(myArrayList1);
        
        MyArrayList<Integer> myArrayList2 = new MyArrayList<>();   
        System.out.println(myArrayList2);
        
        MyArrayList<Boolean> myArrayList3 = new MyArrayList<>();
        System.out.println(myArrayList3);
    }
//func4()结果为:
MyArrayList@1b6d3586
MyArrayList@4554617c
MyArrayList@74a14482
    
    public static void main3(String[] args) {
        MyArrayList<String> myArrayList1 = new MyArrayList<>();
        myArrayList1.add("ABC");
        myArrayList1.add("bit");
        String ret = myArrayList1.get(1);
        System.out.println(ret);

        MyArrayList<Integer> myArrayList2 = new MyArrayList<>();
        myArrayList2.add(1);
        myArrayList2.add(12);
        myArrayList2.add(31);
        int ret2 = myArrayList2.get(1);
        System.out.println(ret2);
    }

    public static void main2(String[] args) {
        MyArrayList<String> myArrayList1 = new MyArrayList<>();
        MyArrayList<Integer> myArrayList2 = new MyArrayList<>();
        MyArrayList<Boolean> myArrayList3 = new MyArrayList<>();
    }
}

注意

  1. 泛型不能实例化对象,如下图
//this.elem = new E[10];   //这一行是错误的,会出现编译错误
  1. 泛型的语法上必须匹配。使用子类或超类,也必须使用通配符来匹配,比如这里要方法参数需要定义为List即可接受List的对象
  2. 子类之间不能互相引用或者继承(下图为特例)
Object[] o2 = new coffee[10];   //特例,平级关系也可以引用。
  1. func()4的代码没有打印<>中的包装类的原因是:泛型当中尖括号<>中的内容,不参与类型的组成。

Object[] 强制类型转换的思考:对于main5()方法的解释

因此我们总结一下,运行时异常ClassCastException出现试图将一个对象强制转换为一个并非是自己或自己的子类对象的时候

至此回到我们文章开头的问题,为什么我将Object,强制转为Coffee[]就会抛错呢?

抛错分析
实际上,在java中,Object是所有class类型的根,数组当然也是一种class类型,因此Object[]类型和Coffee[]类型都是Object类型的子类型
但是对于Object[]类型和Coffee[]类型,二者同为数组类型,可他们之间并没有什么父子关系,而是平级的关系,所以无法强制类型转换

正是因为很多同学误以为Object[]应该是Coffee[]的父类型,才会随手写出导致程序运行时ClassCastException异常

二者区别
Object[]类型与Coffee[]类型的区别在于其数组中可以存放任意Object类型的对象,而Coffee[]则只能存放所有的Coffee类型(包括其子类)对象
Java - List(泛型、包装类、List)_第1张图片

总结
因此,对于日常开发中,我们应该重视对Object[] 类型的返回值的处理,不能想当然的认为他就是Xxx[],随手进行强制类型转换,而导致出现运行时类型转换异常。
不建议对Object[]数组进行强制类型转换。

1.父类引用引用父类对象,父类引用无法进行子类的强制类型转换为子类。
2.父类引用引用子类对象,父类引用可以进行子类的强制类型转换为子类。
3.子类转换成父类,父类引用只能操作其非特有的属性(由父类继承而来的属性)。

具体查看 Java - 基本数据类型 - 强制类型转换部分

来源链接:https://www.jianshu.com/p/9783e29d7c20

MyArrayList<String> myArrayList1 = new MyArrayList<>();  
//左边尖括号写了类型,右边尖括号就可以不写,

数组和泛型之间的一个重要区别是它们如何强制执行类型检查。
具体来说,数组在运行时存储和检查类型信息。然而,泛型在编译时检查类型错误,并且在运行时没有类型信息。
泛型创建数组最正确的方式:通过反射进行创建。
Java - List(泛型、包装类、List)_第2张图片

面试问题:泛型是怎么编译的?
泛型是编译时期的一种机制,擦除机制。
例如把上面的代码中String、Integer、Boolean类全部擦除,变成Object类。

1. 泛型是作用在编译期间的一种机制,即运行期间没有泛型的概念。
2. 泛型代码在运行期间,就是我们上面提到的,利用 Object 达到的效果(这里不是很准确,以后会做说明)。

1.5 泛型总结

  1. 泛型是为了解决某些容器、算法等代码的通用性而引入,并且能在编译期间自动对类型进行检查。
  2. 泛型可以自动对类型进行强制类型转换。
  3. 泛型利用的是 Object 是所有类的祖先类,并且父类的引用可以指向子类对象。
  4. 泛型是一种编译期间的机制,即 MyArrayList 和 MyArrayList 在运行期间是一个类型。
  5. 泛型是 java 中的一种合法语法,标志就是尖括号 <>

2. 预备知识-包装类(Wrapper Class)

Object 引用可以指向任意类型的对象,但有例外出现了,8 种基本数据类型不是对象,那岂不是刚才的泛型机制要失效了?
实际上也确实如此,为了解决这个问题,java 引入了一类特殊的类,即这 8 种基本数据类型的包装类,在使用过程中,会将类似 int 这样的值包装到一个对象中去。

2.1 基本数据类型和包装类直接的对应关系

Java - List(泛型、包装类、List)_第3张图片
基本就是类型的首字母大写,除了 Integer 和 Character。

2.2 包装类的使用,装箱(boxing)和拆箱(unboxing)

Integer a = 123;//装箱  装包【隐式的】
int b = a;//拆箱  拆包【隐式的】

Integer a2 = Integer.valueOf(123);//显示的装包
Integer a3 = new Integer(123);//显示的装包

int b2 = a2.intValue();//显示的拆包
double d = a2.doubleValue();//显示的拆包
int i = 10;//显示的初始化

可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制。

int i = 10;
Integer ii = i; // 自动装箱
Integer ij = (Integer)i; // 自动装箱
int j = ii; // 自动拆箱
int k = (int)ii; // 自动拆箱

注意:自动装箱和自动拆箱是工作在编译期间的一种机制。

面试题示例
注意一下,Integer装包的时候,当给定的值在[-128,127]之间的时候,就会直接返回一个加上128的值。
当不在这个范围内的时候,Integer就会new一个新的Integer(i),此时a和b的地址值就不相同了。
Java - List(泛型、包装类、List)_第4张图片

2.4 javap 反编译工具

这里我们刚好学习一个 jdk 中一个反编译工具来查看下自动装箱和自动拆箱过程,并且看到这个过程是发生在编译期间的。

javap -c 类名称
Compiled from "Main.java"
public class Main {
public Main();
 Code:
   0: aload_0
   1: invokespecial #1         // Method java/lang/Object."":()V
   4: return
public static void main(java.lang.String[]);
 Code:
   0: bipush    10
   2: istore_1
   3: iload_1
   4: invokestatic #2         // Method java/lang/Integer.valueOf:
(I)Ljava/lang/Integer;
   7: astore_2
   8: iload_1
   9: invokestatic #2         // Method java/lang/Integer.valueOf:
(I)Ljava/lang/Integer;
  12: astore_3
  13: aload_2
  14: invokevirtual #3         // Method java/lang/Integer.intValue:()I
  17: istore    4
  19: aload_2
  20: invokevirtual #3         // Method java/lang/Integer.intValue:()I
  23: istore    5
  25: return
}

3. List 的使用

Java - List(泛型、包装类、List)_第5张图片

List的官方文档
ArrayList的官方文档
LinkedList的官方文档

3.1 常见方法

List(线性表)
Java - List(泛型、包装类、List)_第6张图片
Java - List(泛型、包装类、List)_第7张图片

集合转数组用方法,
比如:list.toArray(new String[list.size()]);此处要使用包装类

Integer[] tmp = new Integer[integerList.size()];
integerList.toArray(tmp);

ArrayList(顺序表)
Java - List(泛型、包装类、List)_第8张图片

LinkedList(链表)
在这里插入图片描述

3.2 示例

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
public class ListDemo {
  public static void main(String[] args) {
    List<String> courses = new ArrayList<>();
    courses.add("C 语言");
    courses.add("Java SE");
    courses.add("Java Web");
    courses.add("Java EE");
    // 和数组一样,允许添加重复元素
    courses.add("C 语言");
    // 按照添加顺序打印
    System.out.println(courses);
    // 类似数组下标的方式访问
    System.out.println(courses.get(0));
   
    System.out.println(courses);
    courses.set(0, "计算机基础");
    System.out.println(courses);
    // 截取部分 [1, 3)
    List<String> subCourses = courses.subList(1, 3);
    System.out.println(subCourses);
    // 重新构造
    List<String> courses2 = new ArrayList<>(courses);
    System.out.println(courses2);
    List<String> courses3 = new LinkedList<>(courses);
    System.out.println(courses3);
    // 引用的转换
    ArrayList<String> courses4 = (ArrayList<String>)courses2;
    System.out.println(courses4);
    // LinkedList c = (LinkedList)course2; 错误的类型
    LinkedList<String> courses5 = (LinkedList<String>)courses3;
    System.out.println(courses5);
    // ArrayList c = (ArrayList)course3; 错误的类型
 }
}

运行结果:

[C 语言, Java SE, Java Web, Java EE, C 语言]
C 语言
[C 语言, Java SE, Java Web, Java EE, C 语言]
[计算机基础, Java SE, Java Web, Java EE, C 语言]
[Java SE, Java Web]
[计算机基础, Java SE, Java Web, Java EE, C 语言]
[计算机基础, Java SE, Java Web, Java EE, C 语言]
[计算机基础, Java SE, Java Web, Java EE, C 语言]
[计算机基础, Java SE, Java Web, Java EE, C 语言]

4. ArrayList与顺序表

【本节目标】

  1. ArrayList的简介
  2. ArrayList使用
  3. ArrayList的扩容机制
  4. 扑克牌
  5. ArrayList的模拟实现

1. ArrayList简介

在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:
Java - List(泛型、包装类、List)_第9张图片

【说明】

  1. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
  2. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
  3. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的(把一个对象转变为字符串)
  4. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择使用线程安全的Vector或者
    CopyOnWriteArrayList
  5. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表

2. ArrayList的构造

Java - List(泛型、包装类、List)_第10张图片

public static void main(String[] args) {
	// ArrayList创建,推荐写法
	// 构造一个空的列表
	List<Integer> list1 = new ArrayList<>();
	
	// 构造一个具有10个容量的列表
	List<Integer> list2 = new ArrayList<>(10);
	list2.add(1);
	list2.add(2);
	list2.add(3);
	// list2.add("hello"); // 编译失败,List已经限定了,list2中只能存储整形元素
	
	// list3构造好之后,与list中的元素一致
	ArrayList<Integer> list3 = new ArrayList<>(list2);
	
	// 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
	List list4 = new ArrayList();
	list4.add("111");
	list4.add(100);
}

3. ArrayList常见操作

ArrayList虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,同学们自行查看ArrayList的帮助文档。
Java - List(泛型、包装类、List)_第11张图片

public static void main(String[] args) {
	List<String> list = new ArrayList<>();
	list.add("JavaSE");
	list.add("JavaWeb");
	list.add("JavaEE");
	list.add("JVM");
	list.add("测试课程");
	System.out.println(list);
	
	// 获取list中有效元素个数
	System.out.println(list.size());
	
	// 获取和设置index位置上的元素,注意index必须介于[0, size)间
	System.out.println(list.get(1));
	list.set(1, "JavaWEB");
	System.out.println(list.get(1));
	
	// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
	list.add(1, "Java数据结构");
	System.out.println(list);
	// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
	list.remove("JVM");
	System.out.println(list);
	// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
	list.remove(list.size()-1);
	System.out.println(list);
	
	// 检测list中是否包含指定元素,包含返回true,否则返回false
	  if(list.contains("测试课程")){
	  list.add("测试课程");
	}
	
	// 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
	list.add("JavaSE");
	System.out.println(list.indexOf("JavaSE"));
	System.out.println(list.lastIndexOf("JavaSE"));
	
	// 使用list中[0, 4)之间的元素构成一个新的ArrayList返回
	List<String> ret = list.subList(0, 4);
	System.out.println(ret);
	
	list.clear();
	System.out.println(list.size());
}

subList注意:subList方法实际上没有new新的list,只是把要截取的部分的起始地址直接给到了subList在这里插入图片描述代码示例:

public static void main(String[] args) {
    ArrayList<String> list2 = new ArrayList<>();
    list2.add("a");
    list2.add("c");
    list2.add("g");
    list2.add("a");
    list2.add("o");
    List<String> sub = list2.subList(1,3);
    System.out.println(sub);
    System.out.println(list2);
    System.out.println("==================");
    sub.set(0,"p");
    System.out.println(sub);
    System.out.println(list2);
}
//结果为:
[c, g]
[a, c, g, a, o]
==================
[p, g]
[a, p, g, a, o]

4. ArrayList的遍历

ArrayList 可以使用四种方式遍历:for循环+下标、foreach、使用Iterator、使用ListIterator(继承自Iterator,拥有自己独特的add()方法和remove()方法)

public static void main2(String[] args) {
    ArrayList<String> list2 = new ArrayList<>();
    list2.add("hello");
    list2.add("bit");
    list2.add("haha");
    System.out.println(list2);
    System.out.println("================");
    for(int i = 0; i< list2.size();i++) {
        System.out.print (list2.get(i)+" ");
    }
    System.out.println();
    System.out.println("==================");
    for (String s : list2) {
        System.out.print(s+" ");
    }
    System.out.println();
    System.out.println("========迭代器打印==========");
    Iterator<String> it = list2.iterator();
    while (it.hasNext()) {
        System.out.print(it.next()+" ");
    }

    System.out.println();
    System.out.println("========迭代器List相关打印==========");
    ListIterator<String> it2 = list2.listIterator();
    while (it2.hasNext()) {
        System.out.print(it2.next()+" ");
    }
}

ListIterator需要注意
首先需要使用next方法迭代出集合中的元素 ,然后才能调用remove方法,否则集合可能会因为对同一个Iterator remove了多次而抛出java .lang.IllegalStateException异常。

public static void main3(String[] args) {
    ArrayList<String> list2 = new ArrayList<>();
    list2.add("hello");
    list2.add("bit");
    list2.add("haha");        

    ListIterator<String> it2 = list2.listIterator();
    while (it2.hasNext()) {
        String ret = it2.next();
        if(ret.equals("hello")) {
            it2.remove();//首先需要使用next方法迭代出集合中的元素 ,然后才能调用remove方法
        }else {
            System.out.print(ret + " ");
        }
    }
public static void main(String[] args) {
    ArrayList<String> list2 = new ArrayList<>();
    //CopyOnWriteArrayList list2 = new CopyOnWriteArrayList<>();
    list2.add("hello");
    list2.add("bit");
    list2.add("haha");
    
    ListIterator<String> it2 = list2.listIterator();
    while (it2.hasNext()) {
        String ret = it2.next();
        if(ret.equals("bit")) {
//          list2.add("gaobo");  //要是用迭代器进行add(),会报错
            it2.add("gaobo");
        }else {
            System.out.print(ret + " ");
        }
    }
    System.out.println("=================");
    System.out.println(list2);
}

5. ArrayList的扩容机制

下面代码有缺陷吗?为什么?

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        list.add(i);
    }
}

ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容:以下是ArrayList源码中扩容方式

Object[] elementData;  // 存放元素的空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  // 默认空间
private static final int DEFAULT_CAPACITY = 10;  // 默认容量大小
public boolean add(E e) {
  ensureCapacityInternal(size + 1);  // Increments modCount!!
  elementData[size++] = e;
  return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    return Math.max(DEFAULT_CAPACITY, minCapacity);
 }
  return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
  modCount++;
  // overflow-conscious code
  if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
  // 获取旧空间大小
  int oldCapacity = elementData.length;
 
  // 预计按照1.5倍方式扩容
  int newCapacity = oldCapacity + (oldCapacity >> 1);
 
  // 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
  if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
 
  // 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
  if (newCapacity - MAX_ARRAY_SIZE > 0)
      newCapacity = hugeCapacity(minCapacity);
 
  // 调用copyOf扩容
  elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
  // 如果minCapacity小于0,抛出OutOfMemoryError异常
  if (minCapacity < 0)
    throw new OutOfMemoryError();
 
  return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

【总结】

  1. 检测是否真正需要扩容,如果是调用grow准备扩容
  2. 预估需要库容的大小
    1. 如果ArrayList调用,不带参数的构造方法,那么顺序表的大小是0,当第一次add的时候整个顺序表的大小才变为10
    2. 当这10个放满了,就按照1.5倍大小扩容
    3. 如果调用的是给定容量的构造方法,那么你的顺序表的大小,就是你给定的容量,放满了还是1.5倍大小进行扩容
    4. 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
  3. 使用copyOf进行扩容

6. 扑克牌

public class Card {
  public int rank; // 牌面值
  public String suit; // 花色
 
  @Override
  public String toString() {
    return String.format("[%s %d]", suit, rank);
 }
}
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
public class CardDemo {
  public static final String[] SUITS = {"♠", "♥", "♣", "♦"};
  // 买一副牌
  private static List<Card> buyDeck() {
    List<Card> deck = new ArrayList<>(52);
    for (int i = 0; i < 4; i++) {
      for (int j = 1; j <= 13; j++) {
        String suit = SUITS[i];
        int rank = j;
        Card card = new Card();
        card.rank = rank;
        card.suit = suit;
       
        deck.add(card);
     }
   }
   
    return deck;
 }
  private static void swap(List<Card> deck, int i, int j) {
    Card t = deck.get(i);
    deck.set(i, deck.get(j));
    deck.set(j, t);
 }
  private static void shuffle(List<Card> deck) {
    Random random = new Random(20190905);
    for (int i = deck.size() - 1; i > 0; i--) {
      int r = random.nextInt(i);
      swap(deck, i, r);
   }
 }
 
  public static void main(String[] args) {
    List<Card> deck = buyDeck();
    System.out.println("刚买回来的牌:");
    System.out.println(deck);
    shuffle(deck);
    System.out.println("洗过的牌:");
    System.out.println(deck);
    // 三个人,每个人轮流抓 5 张牌
    List<List<Card>> hands = new ArrayList<>();
    hands.add(new ArrayList<>());
    hands.add(new ArrayList<>());
    hands.add(new ArrayList<>());
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 3; j++) {
        hands.get(j).add(deck.remove(0));
     }
   }
    System.out.println("剩余的牌:");
    System.out.println(deck);
    System.out.println("A 手中的牌:");
    System.out.println(hands.get(0));
    System.out.println("B 手中的牌:");
    System.out.println(hands.get(1));
    System.out.println("C 手中的牌:");
    System.out.println(hands.get(2));
 }
}

运行结果:

刚买回来的牌:
[[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12],
[13], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11],
[12], [13], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10],
[11], [12], [13], [1], [2], [3], [4], [5], [6], [7], [8], [9],
[10], [11], [12], [13]]
洗过的牌:
[[11], [6], [13], [10], [13], [2], [1], [9], [12], [5], [8], [6], [3], [5], [1], [6], [13], [12], [12], [5], [4], [3], [7], [3], [2], [1], [2], [4], [8], [10], [11], [10], [7], [9], [4], [8], [7], [8], [9], [12], [11], [11], [10], [5], [13], [9], [7],
[6], [4], [2], [1], [3]]
剩余的牌:
[[6], [13], [12], [12], [5], [4], [3], [7], [3], [2], [1], [2],
[4], [8], [10], [11], [10], [7], [9], [4], [8], [7], [8], [9],
[12], [11], [11], [10], [5], [13], [9], [7], [6], [4], [2], [1], [3]]
A 手中的牌:
[[11], [10], [1], [5], [3]]
B 手中的牌:
[[6], [13], [9], [8], [5]]
C 手中的牌:
[[13], [2], [12], [6], [1]]

7. 杨辉三角

杨辉三角

8. ArrayList的模拟实现

import java.util.*;
/**
* Created with IntelliJ IDEA.
* User: GB
* Date: 2021/11/26
* Time: 13:31
* Description:
*/
class MyArrayList<E> {
	private Object[] array;
	private int size;
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	//构造方法
	public MyArrayList() {
	    this.array = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
	}
	public MyArrayList(int initCapacity) {
        if (initCapacity > 0) {
        array = new Object[initCapacity];
        } else if (initCapacity == 0) {
        array = new Object[0];
        } else {
			throw new IllegalArgumentException("初始容量为负数");
		}
	}
	
	public int size() {
		return size;
	}
	
	public boolean isEmpty() {
		return 0 == size;
	}
	
	// 尾插
	public boolean add(E e) {
    //容量变为size + 1 后是否需要扩容,注意第一次size为0的时候
    ensureCapacityInternal(size + 1);
    array[size++] = e;
    return true;
 }
/**
* 存放元素之前,确定内部的容量
* @param minCapacity
*/
	private void ensureCapacityInternal(int minCapacity) {
		//1、先计算
		int capacity = calculateCapacity(array, minCapacity);
		//2、确保该容量是否可以分配
		ensureExplicitCapacity(capacity);
	}

	//默认容量
	private static final int DEFAULT_CAPACITY = 10;
	
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
		//1、说明调用了不带参数的构造方法
		if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		//此时默认容量分配10
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
		//2、给了参数,返回,你指定的参数
		return minCapacity;
	}
	private void ensureExplicitCapacity(int minCapacity) {
		//计算出来的容量大就要扩容,否则什么都不做
		if (minCapacity - array.length > 0)
			grow(minCapacity);
	}
	// 扩容
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	private void grow(int initCapacity) {
		int oldCapacity = array.length;
		int newCapacity = oldCapacity + (oldCapacity >> 1);
		//当第一次newCapacity==0的时候,大小为给定的容量
		if (newCapacity < initCapacity) {
			newCapacity = initCapacity;
		}
		if (newCapacity > MAX_ARRAY_SIZE) {
			newCapacity = MAX_ARRAY_SIZE;
		}
		array = Arrays.copyOf(array, newCapacity);
	}
	// 为指定位置插入元素e
	public void add(int index, E e) {
		rangeCheckForAdd(index);
		ensureCapacityInternal(size + 1);
		// 将index及其以后的元素统一往后搬移一个位置
		for (int i = size - 1; i >= index; i--) {
			array[i + 1] = array[i];
		}
		array[index] = e;
		size++;
	}
	// 检测插入时下标是否异常
	private void rangeCheckForAdd(int index) {
		if (index <0 || index > size) {
			throw new IllegalArgumentException("add下标越界");
		}
	}
	// 删除index位置上元素
	public E remove(int index) {
		rangeCheck(index);
		E e = (E) array[index];
		// 将index之后的元素统一往前搬移一个位置
		for (int i = index; i < size-1; ++i) {
	    array[i] = array[i+1];
	}
	array[size] = null;
	size--;
	return e;
	}
	// 检测下标是否异常
	private void rangeCheck(int index) {
		if (index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("下标越界");
		}
	}
	// 获取o第一次出现的位置
	public int indexOf(Object o) {
		if (null == o) {
			for (int i = 0; i < size; i++) {
				if (array[i] == null) {
				return i;
				}
			}
		} else {
			for (int i = 0; i < size; i++) {
				if (array[i].equals(o)) {
					return i;
				}
			}
		}
		return -1;
	}
	// 如果o存在,则删除
    public boolean remove(Object o) {
		int index = indexOf(o);
        if (index == -1) {
            return false;
        }
        remove(index);
        return true;
    }
    // 获取index位置上的元素
    public E get(int index) {
        rangeCheck(index);
        return (E) array[index];
    }
    // 将index位置上元素设置为e
    public E set(int index, E e) {
        rangeCheck(index);
        array[index] = e;
        return e;
    }
    // 清空
    public void clear() {
        for (int i = 0; i < size; i++) {
            array[i] = null;
        }
        size = 0;
    }
    @Override
    public String toString() {
        String s = "[";
        if (size > 0) {
            for (int i = 0; i < size - 1; i++) {
                s += array[i];
                s += ", ";
            }
            s += array[size - 1];
        }
        s += "]";
        return s;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArrayList<Integer> arrayList = new MyArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);
        System.out.println(arrayList.size());//4
        System.out.println(arrayList);
        arrayList.add(0,999999);
        arrayList.add(0);
        System.out.println(arrayList);
        System.out.println(arrayList.indexOf(0));
        arrayList.remove(0);
        System.out.println(arrayList);
        arrayList.clear();
        System.out.println(arrayList);
    }
}

你可能感兴趣的:(数据结构,链表,java,list)