3.容器的学习(1/2)

1.泛型简介

为了能够更好的学习容器,我们首先要先来学习一个概念:泛型。

泛型基本概念

泛型是JDK5.0以后增加的新特性。

泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。我们可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型

参数化类型,白话说就是:

  1. 把类型当作是参数一样传递。
  2. <数据类型> 只能是引用类型。

泛型的好处

在不使用泛型的情况下,我们可以使用Object类型来实现任意的参数类型,但是在使用时需要我们强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是,在编译期我们无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。

总结一下,就是使用泛型主要是两个好处:

  1. 代码可读性更好【不用强制转换】
  2. 程序更加安全【只要编译时期没有警告,运行时期就不会出现ClassCastException异常】

类型擦除

编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。

泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。

泛型主要是方便了程序员的代码编写,以及更好的安全性检测。

2.泛型_泛型类泛型标记

定义泛型时,一般采用几个标记:E、T、K、V、N、?。他们约定俗称的含义如下:

泛型标记对应单词说明
EElement在容器中使用,表示容器中的元素
TType表示普通的JAVA类
KKey表示键,例如:Map中的键Key
VValue表示值
NNumber表示数值类型
表示不确定的JAVA类型

泛型类的使用

语法结构

public class 类名<泛型标识符号> {
}


public class 类名<泛型标识符号,泛型标识符号> {
}

示例

public class Generic<T> {
    private T  flag;


    public void setFlag(T flag){
        this.flag = flag;
    }


    public T getFlag(){
        return this.flag;
    }
}

public class Test {
    public static void main(String[] args) {
        //创建对象时,指定泛型具体类型。
        Generic<String> generic = new Generic<>();
        generic.setFlag("admin");
        String flag = generic.getFlag();
        System.out.println(flag);
        
        //创建对象时,指定泛型具体类型。
        Generic<Integer> generic1 = new Generic<>();
        generic1.setFlag(100);
        Integer flag1 = generic1.getFlag();
        System.out.println(flag1);
    }
}

3.泛型_泛型接口

泛型接口和泛型类的声明方式一致。

泛型接口的使用

语法结构

public interface 接口名<泛型标识符号> {
}


public interface 接口名<泛型标识符号,泛型标识符号> {
}

示例

public interface IGeneric<T> {
    T getName(T name);
}

//在实现接口时传递具体数据类型
public class IgenericImpl implements Igeneric<String> {
    @Override
    public String getName(String name) {
        return name;
    }
}


//在实现接口时仍然使用泛型作为数据类型
public class IGenericImpl2<T> implements IGeneric<T>{
    @Override
    public T getName(T name) {
        return name;
    }
}

public class Test {
    public static void main(String[] args) {
        IGeneric<String> igeneric= new IGenericImpl();
        String name = igeneric.getName("oldlu");
        System.out.println(name);


        IGeneric<String> igeneric1 = new IGenericImpl2<>();
        String name1 = igeneric1.getName("itbz");
        System.out.println(name1);
    }
}

4.泛型_泛型方法的使用

类上定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。

调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型

泛型方法的使用

非静态方法

非静态方法可以使用泛型类中所定义的泛型,也可以将泛型定义在方法上。

语法结构

//无返回值方法
public <泛型标识符号> void getName(泛型标识符号 name){
}


//有返回值方法
public <泛型标识符号> 泛型标识符号 getName(泛型标识符号 name){
}

示例

public class MethodGeneric {
    public <T> void setName(T name){
        System.out.println(name);
    }
    public <T> T getAge(T age){
        return age;
    }
}

public class Test2 {
    public static void main(String[] args) {
        MethodGeneric methodGeneric = new MethodGeneric();
        methodGeneric.setName("oldlu");
        Integer age = methodGeneric.getAge(123);
        System.out.println(age);
    }

静态方法

静态方法中使用泛型时有一种情况需要注意一下,那就是静态方法无法访问类上定义的泛型,所以必须要将泛型定义在方法上。

语法结构

//无返回值静态方法
public static <泛型标识符号> void setName(泛型标识符号 name){
}


//有返回值静态方法
public static <泛型标识符号> 泛型表示符号 getName(泛型标识符号 name){
}

示例

public class MethodGeneric {
    public static <T> void setFlag(T flag){
        System.out.println(flag);
    }


    public static <T> T getFlag(T flag){
        return flag;
    }
}

public class Test4 {
    public static void main(String[] args) {
        MethodGeneric.setFlag("oldlu");
        Integer flag1 = MethodGeneric.getFlag(123123);
        System.out.println(flag1);
    }
}

5.泛型_泛型方法与可变参数

在泛型方法中,泛型也可以定义可变参数类型。

语法结构

public <泛型标识符号> void showMsg(泛型标识符号... agrs){
}

示例

public class MethodGeneric {
    public <T> void method(T...args){
        for(T t:args){
            System.out.println(t);
        }
    }


}

6泛型_泛型中的通配符

无界通配符

“?”表示类型通配符,用于代替具体的类型。它只能在“<>”中使用。可以解决当具体类型不确定的问题。

语法结构

public void showFlag(Generic<?> generic){
}

示例

public class Generic<T> {
    private T  flag;


    public void setFlag(T flag){
        this.flag = flag;
    }


    public T getFlag(){
        return this.flag;
    }
}

public class ShowMsg {
    public void showFlag(Generic<?> generic){
        System.out.println(generic.getFlag());
    }
}

public class Test3 {
    public static void main(String[] args) {
        ShowMsg showMsg = new ShowMsg();
        Generic<Integer> generic = new Generic<>();
        generic.setFlag(20);
        showMsg.showFlag(generic);


        Generic<Number> generic1 = new Generic<>();
        generic1.setFlag(50);
        showMsg.showFlag(generic1);


        Generic<String> generic2 = new Generic<>();
        generic2.setFlag("oldlu");
        showMsg.showFlag(generic2);
    }
}

7泛型_通配符的上限限定

对通配符的上限的限定:<? extends 类型>

?实际类型可以是上限限定中所约定的类型,也可以是约定类型的子类型;

语法结构

public void showFlag(Generic<? extends Number> generic){
}

示例

public class ShowMsg {
    public void showFlag(Generic<? extends Number> generic){
        System.out.println(generic.getFlag());
    }
}

public class Test4 {
    public static void main(String[] args) {
        ShowMsg showMsg = new ShowMsg();
        Generic<Integer> generic = new Generic<>();
        generic.setFlag(20);
        showMsg.showFlag(generic);


        Generic<Number> generic1 = new Generic<>();
        generic1.setFlag(50);
        showMsg.showFlag(generic1);
    }
}

8泛型_通配符的下限限定

对通配符的下限的限定:<? super 类型>

?实际类型可以是下限限定中所约定的类型,也可以是约定类型的父类型;

语法结构

public void showFlag(Generic<? super Integer> generic){
}

示例

public class ShowMsg {
    public void showFlag(Generic<? super Integer> generic){
        System.out.println(generic.getFlag());
    }
}

public class Test6 {
    public static void main(String[] args) {
        ShowMsg showMsg = new ShowMsg();
        Generic<Integer> generic = new Generic<>();
        generic.setFlag(20);
        showMsg.showFlag(generic);


        Generic<Number> generic1 = new Generic<>();
        generic1.setFlag(50);
        showMsg.showFlag(generic1);
    }
}

9泛型_泛型总结

泛型局限性和常见错误

泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。 类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况是错误的:

  1. 基本类型不能用于泛型

    Test<int> t; 这样写法是错误,我们可以使用对应的包装类Test<Integer> t ;

  2. 不能通过类型参数创建对象

    T elm = new T(); 运行时类型参数T会被替换成Object,无法创建T类型的对象,容易引起误解,java干脆禁止这种写法。

10容器_容器结构介绍

容器的结构

结构图

在这里插入图片描述

单例集合

在这里插入图片描述

双例集合

在这里插入图片描述

11.容器_Collection接口介绍

单例集合

Collection接口介绍

Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。
在这里插入图片描述

Collection接口中定义的方法

方法说明
boolean add(Object element)增加元素到容器中
boolean remove(Object element)从容器中移除元素
boolean contains(Object element)容器中是否包含该元素
int size()容器中元素的数量
boolean isEmpty()容器是否为空
void clear()清空容器中所有元素
Iterator iterator()获得迭代器,用于遍历所有元素
boolean containsAll(Collection c)本容器是否包含c容器中的所有元素
boolean addAll(Collection c)将容器c中所有元素增加到本容器
boolean removeAll(Collection c)移除本容器和容器c中都包含的元素
boolean retainAll(Collection c)取本容器和容器c中都包含的元素,移除非交集元素
Object[] toArray()转化成Object数组

由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。

JDK8之后,Collection接口新增的方法(将在JDK新特性和函数式编程中介绍):

新增方法说明
removeIf作用是删除容器中所有满足filter指定条件的元素
stream parallelStreamstream和parallelStream 分别返回该容器的Stream视图表示,不同之处在于parallelStream()返回并行的Stream,Stream是Java函数式编程的核心类。
spliterator可分割的迭代器,不同以往的iterator需要顺序迭代,Spliterator可以分割为若干个小的迭代器进行并行操作,可以实现多线程操作提高效率

12容器_List介绍

List接口特点

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

**有序:**有序(元素存入集合的顺序和取出的顺序一致)。List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。

**可重复:**List允许加入重复的元素。更确切地讲,List通常允许满足 e1.equals(e2) 的元素重复加入容器。

List接口中的常用方法

除了Collection接口中的方法,List多了一些跟顺序(索引)有关的方法,参见下表:

方法说明
void add (int index, Object element)在指定位置插入元素,以前元素全部后移一位
Object set (int index,Object element)修改指定位置的元素
Object get (int index)返回指定位置的元素
Object remove (int index)删除指定位置的元素,后面元素全部前移一位
int indexOf (Object o)返回第一个匹配元素的索引,如果没有该元素,返回-1.
int lastIndexOf (Object o)返回最后一个匹配元素的索引,如果没有该元素,返回-1

13容器_ArrayList的基本使用

ArrayList是List接口的实现类。是List存储特征的具体实现。

ArrayList底层是用数组实现的存储。 特点:查询效率高,增删效率低,线程不安全。

public class ArrayListTest {
    public static void main(String[] args) {
        //实例化ArrayList容器
        List<String> list  = new ArrayList<>();




        //添加元素
        boolean flag1 = list.add("oldlu");
        boolean flag2 = list.add("itbz");
        boolean flag3 = list.add("sxt");
        boolean flag4 = list.add("sxt");
        System.out.println(flag1+"\t"+flag2+"\t"+flag3+"\t"+flag4);
        
        
        //删除元素
        boolean flag4 = list.remove("oldlu");
        System.out.println(flag4);


        //获取容器中元素的个数
        int size = list.size();
        System.out.println(size);


        //判断容器是否为空
        boolean empty = list.isEmpty();
        System.out.println(empty);


        //容器中是否包含指定的元素
        boolean value = list.contains("itbz");
        System.out.println(value);
        
        
        //清空容器
        list.clear();
        Object[] objects1 = list.toArray();
        System.out.println(Arrays.toString(objects1));
    }
}

14容器_ArrayList的索引操作

public class ArrayListTest2 {
    public static void main(String[] args) {
        //实例化容器
        List<String> list = new ArrayList<>();
        //添加元素
        list.add("oldlu");
        list.add("itbz");


        //向指定位置添加元素
        list.add(0,"sxt");


        System.out.println("获取元素");
        String value1 = list.get(0);
        System.out.println(value1);


        System.out.println("获取所有元素方式一");
        //使用普通for循环
        for(int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        }


        System.out.println("获取所有元素方式二");
        //使用Foreach循环
        for(String str:list){
            System.out.println(str);
        }




        System.out.println("元素替换");
        list.set(1,"kevin");
        for(String str:list){
            System.out.println(str);
        }




        System.out.println("根据索引位置删除元素);
        String value2 = list.remove(1);
        System.out.println(value2);
        System.out.println("----------------");
        for(String str:list){
            System.out.println(str);
        }


        System.out.println("查找元素第一次出现的位置");
        int value3 = list.indexOf("sxt");
        System.out.println(value3);


        System.out.println("查找元素最后一次出现的位置");
        list.add("sxt");
        for(String str:list){
            System.out.println(str);
        }
        int value4 = list.lastIndexOf("sxt");
        System.out.println(value4);
        
    }
}

15容器_ArrayList的并集、交集、差集

并集

        //并集操作:将另一个容器中的元素添加到当前容器中
        List<String> a  = new ArrayList<>();
        a.add("a");
        a.add("b");
        a.add("c");


        List<String> b = new ArrayList<>();
        b.add("a");
        b.add("b");
        b.add("c");


        //a并集b
        a.addAll(b);
        for(String str :a){
            System.out.println(str);
        }

交集

 //交集操作:保留相同的,删除不同的
        List<String> a1  = new ArrayList<>();
        a1.add("a");
        a1.add("b");
        a1.add("c");


        List<String> b1 = new ArrayList<>();
        b1.add("a");
        b1.add("d");
        b1.add("e");
        //交集操作
        a1.retainAll(b1);
        for(String str :a1){
            System.out.println(str);
        }

差集

//差集操作:保留不同的,删除相同的
        List<String> a2  = new ArrayList<>();
        a2.add("a");
        a2.add("b");
        a2.add("c");


        List<String> b2= new ArrayList<>();
        b2.add("b");
        b2.add("c");
        b2.add("d");
        a2.removeAll(b2);
        for(String str :a2){
            System.out.println(str);
        }

16容器_Vector的基本使用

Vector底层是用数组实现的,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记。

Vector的使用

Vector的使用与ArrayList是相同的,因为他们都实现了List接口,对List接口中的抽象方法做了具体实现。

public class VectorTest {
    public static void main(String[] args) {


        //实例化Vector
        List<String> v = new Vector<>();
        v.add("a");
        v.add("b");
        v.add("a");


        for(int i=0;i<v.size();i++){
            System.out.println(v.get(i));
        }
        System.out.println("----------------------");
        for(String str:v){
            System.out.println(str);
        }
    }
}

17容器_Vector底层源码分析

成员变量

/**
 * The array buffer into which the components of the vector are
 * stored. The capacity of the vector is the length of this array buffer,
 * and is at least large enough to contain all the vector's elements.
 *
 * <p>Any array elements following the last element in the Vector are null.
 *
 * @serial
 */
protected Object[] elementData;


    /**
     * The number of valid components in this {@code Vector} object.
     * Components {@code elementData[0]} through
     * {@code elementData[elementCount-1]} are the actual items.
     *
     * @serial
     */
    protected int elementCount;


    /**
     * The amount by which the capacity of the vector is automatically
     * incremented when its size becomes greater than its capacity.  If
     * the capacity increment is less than or equal to zero, the capacity
     * of the vector is doubled each time it needs to grow.
     *
     * @serial
     */
    protected int capacityIncrement;

构造方法

public Vector() {
    this(10);
}

添加元素

/**
 * Appends the specified element to the end of this Vector.
 *
 * @param e element to be appended to this Vector
 * @return {@code true} (as specified by {@link Collection#add})
 * @since 1.2
 */
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

数组扩容

/**
 * This implements the unsynchronized semantics of ensureCapacity.
 * Synchronized methods in this class can internally call this
 * method for ensuring capacity without incurring the cost of an
 * extra synchronization.
 *
 * @see #ensureCapacity(int)
 */
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
//判断是否需要扩容,数组中的元素个数-数组长度,如果大于0表明需要扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
//扩容2倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

18容器_LinkedList介绍

LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。

双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。

LinkedList的存储结构图
在这里插入图片描述

每个节点都应该有3部分内容:

1
class  Node<E> {
2
        Node<E>  previous;    //前一个节点
3
        E  element;          //本节点保存的数据
4
        Node<E> next;        //后一个节点
5
}

List实现类的选用规则

如何选用ArrayList、LinkedList、Vector?

  1. 需要线程安全时,用Vector。
  2. 不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)
  3. 不存在线程安全问题时,增加或删除元素较多用LinkedList

19容器_LinkedList容器的使用(List标准)

public class LinkedListTest {
    public static void main(String[] args) {
        //实例化LinkedList容器
        List<String> list = new LinkedList<>();


        //添加元素
        boolean a = list.add("a");
        boolean b = list.add("b");
        boolean c = list.add("c");
        list.add(3,"a");
        System.out.println(a+"\t"+b+"\t"+c);


        for(int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        }
    }
}

20容器_LinkedList容器的使用(非List标准)

方法说明
void addFirst(E e)将指定元素插入到开头
void addLast(E e)将指定元素插入到结尾
getFirst()返回此链表的第一个元素
getLast()返回此链表的最后一个元素
removeFirst()移除此链表中的第一个元素,并返回这个元素
removeLast()移除此链表中的最后一个元素,并返回这个元素
E pop()从此链表所表示的堆栈处弹出一个元素,等效于removeFirst
void push(E e)将元素推入此链表所表示的堆栈 这个等效于addFisrt(E e)
public class LinkedListTest2 {
    public static void main(String[] args) {
        System.out.println("-------LinkedList-------------");
        //将指定元素插入到链表开头
        LinkedList<String> linkedList1 = new LinkedList<>();
        linkedList1.addFirst("a");
        linkedList1.addFirst("b");
        linkedList1.addFirst("c");
        for (String str:linkedList1){
            System.out.println(str);
        }
        System.out.println("----------------------");
        //将指定元素插入到链表结尾
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.addLast("a");
        linkedList.addLast("b");
        linkedList.addLast("c");
        for (String str:linkedList){
            System.out.println(str);
        }


        System.out.println("---------------------------");
        //返回此链表的第一个元素
        System.out.println(linkedList.getFirst());
        //返回此链表的最后一个元素
        System.out.println(linkedList.getLast());


        System.out.println("-----------------------");
        //移除此链表中的第一个元素,并返回这个元素
        linkedList.removeFirst();
        //移除此链表中的最后一个元素,并返回这个元素
        linkedList.removeLast();
        for (String str:linkedList){
            System.out.println(str);
        }


        System.out.println("-----------------------");
        linkedList.addLast("c");


        //从此链表所表示的堆栈处弹出一个元素,等效于removeFirst
        linkedList.pop();
        for (String str:linkedList){
            System.out.println(str);
        }
        System.out.println("-------------------");
        //将元素推入此链表所表示的堆栈  这个等效于addFisrt(E e)
        linkedList.push("h");
        for (String str:linkedList){
            System.out.println(str);
        }
    }
}

21容器_LinkedList源码分析 添加元素

添加元素

节点类

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;


    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

成员变量

transient int size = 0;


/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;


/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

添加元素

/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #addLast}.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}


/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

22容器_LinkedList源码分析 头尾添加元素

头尾添加元素

addFirst

/**
 * Inserts the specified element at the beginning of this list.
 *
 * @param e the element to add
 */
public void addFirst(E e) {
    linkFirst(e);
}


/**
 * Links e as first element.
 */
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

addLast

/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #add}.
 *
 * @param e the element to add
 */
public void addLast(E e) {
    linkLast(e);
}


/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

23容器_LinkedList源码分析 获取元素

获取元素

/**
 * Returns the element at the specified position in this list.
 *
 * @param index index of the element to return
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
 * Tells if the argument is the index of an existing element.
 */
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}        

/**
 * Returns the (non-null) Node at the specified element index.
 */
Node<E> node(int index) {
    // assert isElementIndex(index);


    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

24容器_Set接口介绍

Set接口继承自Collection接口,Set接口中没有新增方法,它和Collection接口保持完全一致。我们在前面学习List接口的使用方式,在Set中仍然适用。因此,学习Set的使用将没有任何难度。

Set接口特点

Set特点:无序、不可重复。无序指Set中的元素没有索引,我们只能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新元素如果和Set中某个元素通过equals()方法对比为true,则只能保留一个。

Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet。

25.容器_HashSet_HashSet容器的使用

HashSet是Set接口的实现类。是Set存储特征的具体实现。

public class HashSetTest {
    public static void main(String[] args) {
        //实例化HashSet
        Set<String> set = new HashSet<>();
        //添加元素
        set.add("a");
        set.add("b1");
        set.add("c2");
        set.add("d");
        set.add("a");


        //获取元素,在Set容器中没有索引,所以没有对应的get(int index)方法
        for(String str: set){
            System.out.println(str);
        }
        System.out.println("--------------------");
        //删除元素
        boolean flag = set.remove("c2");
        System.out.println(flag);
        for(String str: set){
            System.out.println(str);
        }
        System.out.println("------------------------");
        int size = set.size();
        System.out.println(size);
    }
}

26.容器_HashSet_HashSet存储特征分析

HashSet 是一个不保证元素的顺序且没有重复元素的集合,是线程不安全的。HashSet允许有null 元素。

无序:

在HashSet中底层是使用HashMap存储元素的。HashMap底层使用的是数组与链表实现元素的存储。元素在数组中存放时,并不是有序存放的也不是随机存放的,而是对元素的哈希值进行运算决定元素在数组中的位置。

不重复:

当两个元素的哈希值进行运算后得到相同的在数组中的位置时,会调用元素的equals()方法判断两个元素是否相同。如果元素相同则不会添加该元素,如果不相同则会使用单向链表保存该元素。

27容器_HashSet通过HashSet存储自定义对象

要自定义对象的化要添加三个方法

equals
hashcode

创建Users对象

public class Users {
    private String username;
    private int userage;


    public Users(String username, int userage) {
        this.username = username;
        this.userage = userage;
    }


    public Users() {
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;


        Users users = (Users) o;


        if (userage != users.userage) return false;
        return username != null ? username.equals(users.username) : users.username == null;
    }


    @Override
    public int hashCode() {
        int result = username != null ? username.hashCode() : 0;
        result = 31 * result + userage;
        return result;
    }


    public String getUsername() {
        return username;
    }


    public void setUsername(String username) {
        this.username = username;
    }


    public int getUserage() {
        return userage;
    }


    public void setUserage(int userage) {
        this.userage = userage;
    }


    @Override
    public String toString() {
        return "Users{" +
                "username='" + username + '\'' +
                ", userage=" + userage +
                '}';
    }
}

在HashSet中存储Users对象

public class HashSetTest2 {
    public static void main(String[] args) {
        //实例化HashSet
        Set<Users> set = new HashSet<>();
        Users u = new Users("oldlu",18);
        Users u1 = new Users("oldlu",18);
        set.add(u);
        set.add(u1);
        System.out.println(u.hashCode());
        System.out.println(u1.hashCode());
        for(Users users:set){
            System.out.println(users);
        }
    }
}

https://www.itbaizhan.com/course/id/39439.html

28容器_HashSet_HashSet底层源码分析

成员变量

private transient HashMap<E,Object> map;


// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

添加元素

/**
 * Adds the specified element to this set if it is not already present.
 * More formally, adds the specified element <tt>e</tt> to this set if
 * this set contains no element <tt>e2</tt> such that
 * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
 * If this set already contains the element, the call leaves the set
 * unchanged and returns <tt>false</tt>.
 *
 * @param e element to be added to this set
 * @return <tt>true</tt> if this set did not already contain the specified
 * element
 */
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

29容器_TreeSet_TreeSet容器的使用

TreeSet实现了Set接口,它是一个可以对元素进行排序的容器。底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储元素。 TreeSet内部需要对存储的元素进行排序,因此,我们需要给定排序规则。

排序规则实现方式:

  • 通过元素自身实现比较规则。
  • 通过比较器指定比较规则。
public class TreeSetTest {
    public static void main(String[] args) {
        //实例化TreeSet
        Set<String> set = new TreeSet<>();
        //添加元素
        set.add("c");
        set.add("a");
        set.add("d");
        set.add("b");
        set.add("a");


        //获取元素
        for(String str :set){
            System.out.println(str);
        }
    }
}

String类中有compareTo比较函数

30容器_Set_TreeSet通过元素自身实现比较规则

在元素自身实现比较规则时,需要实现Comparable接口中的compareTo方法,该方法中用来定义比较规则。TreeSet通过调用该方法来完成对元素的排序处理。

创建Users类

public class Users implements Comparable<Users>{
    private String username;
    private int userage;


    public Users(String username, int userage) {
        this.username = username;
        this.userage = userage;
    }


    public Users() {
    }


    @Override
    public boolean equals(Object o) {
        System.out.println("equals...");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;


        Users users = (Users) o;


        if (userage != users.userage) return false;
        return username != null ? username.equals(users.username) : users.username == null;
    }


    @Override
    public int hashCode() {
        int result = username != null ? username.hashCode() : 0;
        result = 31 * result + userage;
        return result;
    }


    public String getUsername() {
        return username;
    }


    public void setUsername(String username) {
        this.username = username;
    }


    public int getUserage() {
        return userage;
    }


    public void setUserage(int userage) {
        this.userage = userage;
    }


    @Override
    public String toString() {
        return "Users{" +
                "username='" + username + '\'' +
                ", userage=" + userage +
                '}';
    }


    //定义比较规则
    //正数:大,负数:小,0:相等
    @Override
    public int compareTo(Users o) {
        if(this.userage > o.getUserage()){
            return 1;
        }
        if(this.userage == o.getUserage()){
           return this.username.compareTo(o.getUsername());
        }
        return -1;
    }
}

Set<Users> set1 = new TreeSet<>();
Users u = new Users("oldlu",18);
Users u1 = new Users("admin",22);
Users u2 = new Users("sxt",22);
set1.add(u);
set1.add(u1);
set1.add(u2);
for(Users users:set1){
    System.out.println(users);
}

31容器_ TreeSet _通过比较器实现比较规则

通过比较器定义比较规则时,我们需要单独创建一个比较器,比较器需要实现Comparator接口中的compare方法来定义比较规则。在实例化TreeSet时将比较器对象交给TreeSet来完成元素的排序处理。此时元素自身就不需要实现比较规则了。

创建Student类

public class Student {
    private String name;
    private int age;


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public Student() {
    }


    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    public String getName() {
        return name;
    }


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


    public int getAge() {
        return age;
    }


    public void setAge(int age) {
        this.age = age;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;


        Student student = (Student) o;


        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }


    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

创建比较器

public class StudentComparator implements Comparator<Student> {


    //定义比较规则
    @Override
    public int compare(Student o1, Student o2) {
        if(o1.getAge() > o2.getAge()){
            return 1;
        }
        if(o1.getAge() == o2.getAge()){
            return o1.getName().compareTo(o2.getName());
        }
        return -1;
    }
}

public class TreeSetTest3 {
    public static void main(String[] args) {
        //创建TreeSet容器,并给定比较器对象
        Set<Student> set = new TreeSet<>(new StudentComparator());
        Student s = new Student("oldlu",18);
        Student s1 = new Student("admin",22);
        Student s2 = new Student("sxt",22);
        set.add(s);
        set.add(s1);
        set.add(s2);
        for(Student student:set){
            System.out.println(student);
        }
    }
}

32容器_ TreeSet_底层源码分析

成员变量

/**
 * The backing map.
 */
private transient NavigableMap<E,Object> m;


// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

构造方法

public TreeSet() {
    this(new TreeMap<E,Object>());
}

添加元素

/**
 * Adds the specified element to this set if it is not already present.
 * More formally, adds the specified element <tt>e</tt> to this set if
 * this set contains no element <tt>e2</tt> such that
 * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
 * If this set already contains the element, the call leaves the set
 * unchanged and returns <tt>false</tt>.
 *
 * @param e element to be added to this set
 * @return <tt>true</tt> if this set did not already contain the specified
 * element
 */
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值