30Java的容器&泛型(源码简直不能更精彩)

容器(又名集合)

数组也是一种容器,可以放置对象或基本数据类型数据。

数组的优势:访问速度快,效率高;

数组的劣势:数组容量不灵活,不能改变长度。

容器是Java的重要内容,顾名思义,用来装其他对象的对象。

接口:定义规范。就是定义不变的东西,接口里面只能定义抽象方法和常量。

容器有四大接口:

在这里插入图片描述

  • Collection接口:定义了存取一组对象的抽象方法,其子接口Set和List分别定义了存储方式:
    • Set中的数据对象没有顺序且不可重复
    • List中的数据对象有顺序且可以重复
  • Map接口定义了存储 “ 键(key)— 值(value) 映射对 ” 的方法,通过一个对象找到另一个对象。

容器(接口)里面都是抽象方法,使用容器就要实现它的抽象方法。

泛型(Generic)

泛型的本质是 “数据类型的参数化” 。可以把 “泛型” 理解为数据类型的占位符(形式参数),即告诉编译器,在调用泛型时,必须传入实际类型。给容器贴上标签,放入同一类型的物品,方便取出。

泛型的声明处增加泛型列表,一般使用<T,E,V>表示,当然也可以使用其他字母,下面看程序示例:

package mycollection;

/**
 * @author 发达的范
 * @date 2020/10/31 11:01
 * @description 这是测试容器泛型的类
 */
public class TestCollection {
    public static void main(String[] args) {
        //此处指定容器泛型的对象类型,也就是说这里实例化的容器只能放String类型的数据,取出的数据类型也是String
        MyCollection<String> myCollection = new MyCollection();
        myCollection.setObject("发达的范", 0);
//        myCollection.setObject(1234, 1);
        String str=myCollection.getObject(0);//不用强制转换
        System.out.println(str);
//        int a = (int )myCollection.getObject(1);
//        System.out.println(a);
//        Integer b = (Integer) myCollection.getObject(1);
//        System.out.println(b);
    }
}

class MyCollection<E> {//在此处声明泛型
    Object[] object = new Object[5];

    public void setObject(E obj, int index) {//可使用自定义的泛型代替原来的Object类型
        object[index] = obj;
    }

    public E getObject(int index) {
        return (E)object[index];//自定义泛型之后,需要把Object类型强制转换成自定义的泛型类型
    }
}

运行结果:在这里插入图片描述

那么现在问题来了,既然容器的目的就是:“可以放任何类型的对象”,现在又使用泛型贴标签,只能放以一种类型的对象,何必呢?

现在画一个图来解释这个问题,如下,

在这里插入图片描述

实例化不同标签(泛型)的对象,那这个容器就只能保存这个类型的对象,简直是面向对象的精髓所在啊!

Collection接口

这是一段Collection接口的官方解释:

The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The JDK does not provide any direct implementations of this interface: it provides implementations of more specific subinterfaces like Set and List. This interface is typically used to pass collections around and manipulate them where maximum generality is desired.

下面看Collection接口的程序实例:(这里使用的方法都是操作单个容器)

package mycollection;

import java.util.*;

/**
 * @author 发达的范
 * @date 2020/10/31 15:50
 * @description 测试Collection接口中的方法
 */
public class TestCollection {
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList();//自定义泛型容器
        System.out.println(collection.size());//输出容器的大小,指实际存储的对象的个数
        collection.add("发达的范");//使用add方法添加字符串对象
        collection.add("The Yellow River");
        System.out.println(collection);
        System.out.println(collection.size());
        collection.remove("The Yellow River");//移除指定的字符串对象,此处有个注意点
        System.out.println(collection);
        //使用toArray方法把容器里的成员转换成一个Object数组,至于有什么用暂时未知
        Object[] objects=collection.toArray();
        System.out.println(objects);
        System.out.println(collection.contains("发达的范"));
        collection.clear();//移除容器中的所有成员
        System.out.println(collection.size());
    }
}

其中:ArrayList类是Collection接口的一个实现类,继承关系如下,与前面的容器的接口实现图相同:

在这里插入图片描述

其中remove()方法并不是删除元素,而是删除容器中某个元素的地址,在内存中这个元素还是存在的,原理图如下:

在这里插入图片描述

Collection的clear()方法也是如此。

当然,Collection还有一些其他常用的操作两个容器的方法,下面看程序实例:

package mycollection;

import java.util.*;

/**
 * @author 发达的范
 * @date 2020/10/31 15:50
 * @description 测试Collection接口中的方法
 */
public class TestCollection {
    public static void main(String[] args) {
        test02();
    }
    public static void test02() {
        List<String> stringList1 = new ArrayList<>();
        stringList1.add("发达的范");
        stringList1.add("学Java");
        stringList1.add("是菜鸡");
        stringList1.add("hahaha");
        List<String> stringList2 = new ArrayList<>();
        stringList2.add("716");
        stringList2.add("都是");
        stringList2.add("大佬");
        stringList2.add("hahaha");
		//把stringList2中的全部成员添加到stringList1中
        stringList1.addAll(stringList2);
        System.out.println(stringList1);
		//把stringList1中与stringList2相同的全部元素移除
        stringList1.removeAll(stringList2);
        System.out.println(stringList1);
		//判断stringList1是不是包含stringList2的所有对象
        System.out.println(stringList1.containsAll(stringList2));
		//把stringList1中所有与stringList2相同的赋值给stringList1,就是取交集
        stringList1.retainAll(stringList2);
        System.out.println(stringList1);
    }
}

输出结果:在这里插入图片描述

需要注意的是:在执行stringList1.removeAll(stringList2);时,此时的stringList1已经把stringList2的内容也添加进来了,,所以移除与stringList2所有相同的元素的输出如上。同样,当执行stringList1.retainAll(stringList2);时,stringList1已经与stringList2完全没有交集了。

List容器的索引

List是有序、可重复的容器。

  • 有序:List容器中每个元素都有索引,可以根据元素的索引(在List中的位置)访问元素。
  • 可重复:List中允许加入重复的元素,这里的重复指的是满足e1.equals(e2),重复的元素是把不同的地址保存在不同的List索引处。

List接口常用的实现类有三个:ArrayList,LinkedList和Vector。

ArrayList和Vector使用数组实现,其中Vector是线程安全的。LinkedList是使用链表实现

下面看List容器程序实例:

package mycollection;

import java.util.*;

/**
 * @author 发达的范
 * @date 2020/10/31 
 * @description 测试Collection接口中的方法
 */
public class TestCollection {
    public static void main(String[] args) {
        test03();
    }
	public static void test03() {
        List<String> list = new ArrayList<>();
        list.add("高人");
        list.add("竟在");
        list.add("我");
        list.add("身边");
        System.out.println(list);
        list.add(3, "的");//在指定位置插入对象,不删除原有的的成员,只是向后移
        System.out.println(list);
        list.remove(3);
        System.out.println(list);//移除指定索引位置的对象
        list.set(0, "我");//把更改索引位置的元素
        list.set(2, "高人");
        System.out.println(list);//移除指定索引位置的对象
        list.add("高人");
        System.out.println(list);
        System.out.println(list.indexOf("高人"));//如果不存在则返回-1
        System.out.println(list.lastIndexOf("高人"));
    }
}

运行结果:在这里插入图片描述

ArrayList

ArrayList底层是使用数组实现的存储。

特点:查询效率高,增删效率低,线程不安全。

LinkedList增删效率高,Vector线程安全。

数组的长度是有限的,而ArrayList是可以存放任意数量的对象,长度不受限制,它的实现方法就是数组的扩容,之前在自己实现容器的代码中使用过。

ArrayList容器的初始大小是10个,下面分析一下ArrayList容器源码是如何扩容的:

首先看ArrayList的add()方法的源码:

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

可以看到,ArrayList容器在添加元素时首先判断Object数组的空间够不够,进入ensureCapacityInternal()方法,

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

其中elementData就是容器对象,直接看这一行代码可能看不出什么,下面我来进行一下验证操作,假设此时容器中存储了三个对象,那么此时size=3,进入ensureCapacityInternal()方法,minCapacity=size+1=4;然后进入calculateCapacity()方法,下面是calculateCapacity方法的源码:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

这是判断当前容器数组对象elementData是不是非空,返回值就是minCapacity=size+1=4。

接下来进入ensureExplicitCapacity()方法:

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

看这个if语句,判断当前容器对象是否满了,此时minCapacity=4,elementData.length=10,显然不满足条件,不执行grow()方法。但是如果此时已经存入了10个对象,那么if语句的条件就满足了,然后进入grow()方法,下面看源码:

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//保存原来的容器大小
    int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity=10+10/2=15
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);//把原来的数组元素copy到新数组中
}

扩容之后的数组的地址赋值给elementData,此时原来的数组被遗弃,由垃圾回收器进行回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值