首页 > 其他分享 >集合

集合

时间:2022-11-27 23:23:51浏览次数:32  
标签:System println add 集合 new out

集合

集合概述

数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据。

集合为什么说在开发中使用较多?
集合是一个容器,是一个载体,可以一次容纳多个对象。
在实际开发中,假设连接数据库,数据库当中有10条记录,
那么假设把这10条记录查询出来,在java程序中会将10条
数据封装成10个java对象,然后将10个java对象放到某一个
集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。

集合中存储什么?

集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,
集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
list.add(100);

​ //自动装箱Integer
注意:集合在java中本身是一个容器,是一个对象。集合中任何时候存储的都是“引用”。

image-20221120191524231

不同的集合对应不同的数据结构

在java中每一个不同的集合,底层会对应不同的数据结构

往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。什么是数据结构?

数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:
数组、二叉树、链表、哈希表...
以上这些都是常见的数据结构。

​ 你往集合c1中放数据,可能是放到数组上了。
​ 你往集合c2中放数据,可能是放到二叉树上了。
​ .....
​ 你使用不同的集合等同于使用了不同的数据结构。

​ 你在java集合这一章节,你需要掌握的不是精通数据结构。java中已经将数据结构实现了,已经写好了这些常用的集合类,你只需要掌握怎么用?在什么情况下选择哪一种合适的集合去使用即可。

​ new ArrayList(); 创建一个集合,底层是数组。
​ new LinkedList(); 创建一个集合对象,底层是链表。
​ new TreeSet(); 创建一个集合对象,底层是二叉树。

集合在java JDK中哪个包下?
java.util.*;
所有的集合类和集合接口都在java.util包下。

集合的分类

  1. ​ 一类是单个方式存储元素:
    ​ 单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
  2. ​ 一类是以键值对儿的方式存储元素
    ​ 以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;

集合的继承结构图

image-20221121110716859

image-20221121110839175

Collection

Collection接口中的常用方法

关于java.util.Collection接口中常用的方法。

Collection存放的元素

​ 没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
​ 使用了“泛型”之后,Collection中只能存储某个具体的类型。
​ 集合后期我们会学习“泛型”语法。目前先不用管。Collection中什么都能存,只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。)

Collection中的常用方法

​ boolean add(Object e) 向集合中添加元素
​ int size() 获取集合中元素的个数
​ void clear() 清空集合
​ boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
​ boolean remove(Object o) 删除集合中的某个元素。
​ boolean isEmpty() 判断该集合中元素的个数是否为0
​ Object[] toArray() 调用这个方法可以把集合转换成数组。【作为了解,使用不多。】

public class CollectionTest01 {
    public static void main(String[] args) {
        // 创建一个集合对象
        //Collection c = new Collection(); // 接口是抽象的,无法实例化。
        // 多态
        Collection c = new ArrayList();
        // 测试Collection接口中的常用方法
        c.add(1200); // 自动装箱(java5的新特性。),实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200);
        c.add(3.14); // 自动装箱
        c.add(new Object());
        c.add(new Student());
        c.add(true); // 自动装箱

        // 获取集合中元素的个数
        System.out.println("集合中元素个数是:" + c.size()); // 5

        // 清空集合
        c.clear();
        System.out.println("集合中元素个数是:" + c.size()); // 0

        // 再向集合中添加元素
        c.add("hello"); // "hello"对象的内存地址放到了集合当中。
        c.add("world");
        c.add("浩克");
        c.add("绿巨人");
        c.add(1);

        // 判断集合中是否包含"绿巨人"
        boolean flag = c.contains("绿巨人");
        System.out.println(flag); // true
        boolean flag2 = c.contains("绿巨人2");
        System.out.println(flag2); // false
        System.out.println(c.contains(1)); // true

        System.out.println("集合中元素个数是:" + c.size()); // 5

        // 删除集合中某个元素
        c.remove(1);
        System.out.println("集合中元素个数是:" + c.size()); // 4

        // 判断集合是否为空(集合中是否存在元素)
        System.out.println(c.isEmpty()); // false
        // 清空
        c.clear();
        System.out.println(c.isEmpty()); // true(true表示集合中没有元素了!)

        c.add("abc");
        c.add("def");
        c.add(100);
        c.add("helloworld!");
        c.add(new Student());

        // 转换成数组(了解,使用不多。)
        Object[] objs = c.toArray();
        for(int i = 0; i < objs.length; i++){
            // 遍历数组
            Object o = objs[i];
            System.out.println(o);
        }
    }
}

class Student{

}

Collection迭代


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

/**
 * 关于集合遍历/迭代专题。(重点:五颗星*****)
 */
public class CollectionTest02 {
    public static void main(String[] args) {
        // 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。
        // 在Map集合中不能用。在所有的Collection以及子类中使用。
        // 创建集合对象
        Collection c = new ArrayList(); // 后面的集合无所谓,主要是看前面的Collection接口,怎么遍历/迭代。
        // 添加元素
        c.add("abc");
        c.add("def");
        c.add(100);
        c.add(new Object());
        // 对集合Collection进行遍历/迭代
        // 第一步:获取集合对象的迭代器对象Iterator
        Iterator it = c.iterator();
        // 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
        /*
            以下两个方法是迭代器对象Iterator中的方法:
                boolean hasNext()如果仍有元素可以迭代,则返回 true。
                Object next() 返回迭代的下一个元素。
         */
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }

        // 一直取,不判断,会出现异常:java.util.NoSuchElementException
        /*while(true){
            Object obj = it.next();
            System.out.println(obj);
        }*/

        /*boolean hasNext = it.hasNext();
        System.out.println(hasNext);
        if(hasNext) {
            // 不管你当初存进去什么,取出来统一都是Object。
            Object obj = it.next();
            System.out.println(obj);
        }

        hasNext = it.hasNext();
        System.out.println(hasNext);
        if(hasNext) {
            Object obj = it.next();
            System.out.println(obj);
        }

        hasNext = it.hasNext();
        System.out.println(hasNext);
        if(hasNext) {
            Object obj = it.next();
            System.out.println(obj);
        }

        hasNext = it.hasNext();
        System.out.println(hasNext);
        if(hasNext) {
            Object obj = it.next();
            System.out.println(obj);
        }

        hasNext = it.hasNext();
        System.out.println(hasNext);
        if(hasNext) {
            Object obj = it.next();
            System.out.println(obj);
        }*/
    }
}

image-20221121152452281

迭代器是通用的


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

/*
关于集合的迭代/遍历
 */
public class CollectionTest03 {
    public static void main(String[] args) {
        // 创建集合对象
        Collection c1  = new ArrayList(); // ArrayList集合:有序可重复
        // 添加元素
        c1.add(1);
        c1.add(2);
        c1.add(3);
        c1.add(4);
        c1.add(1);

        // 迭代集合
        Iterator it = c1.iterator();
        while(it.hasNext()){
            // 存进去是什么类型,取出来还是什么类型。
            Object obj = it.next();
            /*if(obj instanceof Integer){
                System.out.println("Integer类型");
            }*/
            // 只不过在输出的时候会转换成字符串。因为这里println会调用toString()方法。
            System.out.println(obj);
        }

        // HashSet集合:无序不可重复
        Collection c2 = new HashSet();
        // 无序:存进去和取出的顺序不一定相同。
        // 不可重复:存储100,不能再存储100.
        c2.add(100);
        c2.add(200);
        c2.add(300);
        c2.add(90);
        c2.add(400);
        c2.add(50);
        c2.add(60);
        c2.add(100);
        Iterator it2 = c2.iterator();
        while(it2.hasNext()){
            System.out.println(it2.next());
        }
    }
}

003-迭代原理

contains方法

深入Collection集合的contains方法:
boolean contains(Object o)
判断集合中是否包含某个对象o
如果包含返回true, 如果不包含返回false。

contains方法是用来判断集合中是否包含某个元素的方法,
那么它在底层是怎么判断集合中是否包含某个元素的呢?
调用了equals方法进行比对。
equals方法返回true,就表示包含这个元素。

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

public class CollectionTest04 {
    public static void main(String[] args) {
        // 创建集合对象
        Collection c = new ArrayList();

        // 向集合中存储元素
        String s1 = new String("abc"); // s1 = 0x1111
        c.add(s1); // 放进去了一个"abc"

        String s2 = new String("def"); // s2 = 0x2222
        c.add(s2);

        // 集合中元素的个数
        System.out.println("元素的个数是:" + c.size()); // 2

        // 新建的对象String
        String x = new String("abc"); // x = 0x5555
        // c集合中是否包含x?结果猜测一下是true还是false?
        System.out.println(c.contains(x)); //判断集合中是否存在"abc" true
    }
}

002-Collection的contains方法

contains源码解析(remove方法)


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

/*
测试contains方法
测试remove方法。
结论:存放在一个集合中的类型,一定要重写equals方法。
 */
public class CollectionTest05 {
    public static void main(String[] args) {
        // 创建集合对象
        Collection c = new ArrayList();
        // 创建用户对象
        User u1 = new User("jack");
        // 加入集合
        c.add(u1);

        // 判断集合中是否包含u2
        User u2 = new User("jack");

        // 没有重写equals之前:这个结果是false
        //System.out.println(c.contains(u2)); // false
        // 重写equals方法之后,比较的时候会比较name。
        System.out.println(c.contains(u2)); // true

        c.remove(u2);
        System.out.println(c.size()); // 0

        /*Integer x = new Integer(10000);
        c.add(x);

        Integer y = new Integer(10000);
        System.out.println(c.contains(y)); // true*/

        // 创建集合对象
        Collection cc = new ArrayList();
        // 创建字符串对象
        String s1 = new String("hello");
        // 加进去。
        cc.add(s1);

        // 创建了一个新的字符串对象
        String s2 = new String("hello");
        // 删除s2
        cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。
        // 集合中元素个数是?
        System.out.println(cc.size()); // 0
    }
}

class User{
    private String name;
    public User(){}
    public User(String name){
        this.name = name;
    }

    // 重写equals方法
    // 将来调用equals方法的时候,一定是调用这个重写的equals方法。
    // 这个equals方法的比较原理是:只要姓名一样就表示同一个用户。
    public boolean equals(Object o) {
        if(o == null || !(o instanceof User)) return false;
        if(o == this) return true;
        User u = (User)o;
        // 如果名字一样表示同一个人。(不再比较对象的内存地址了。比较内容。)
        return u.name.equals(this.name);
    }

}

第一个重点:把集合继承结构图背会。

第二个重点:把Collection接口中常用方法测试几遍。

第三个重点:把迭代器弄明白。

第四个重点:Collection接口中的remove方法和contains方法底层都会调用equals,
这个弄明白。

List接口

常用方法

测试List接口中常用方法
1、List集合存储元素特点:有序可重复
有序:List集合中的元素有下标。
从0开始,以1递增。
可重复:存储一个1,还可以再存储1.
2、List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法:
以下只列出List接口特有的常用的方法:
void add(int index, Object element)
Object set(int index, Object element)
Object get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
Object remove(int index)

​ 以上几个方法不需要死记硬背,可以自己编写代码测试一下,理解一下,
​ 以后开发的时候,还是要翻阅帮助文档。


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

public class ListTest01 {
    public static void main(String[] args) {
        // 创建List类型的集合。
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素。
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        //在列表的指定位置插入指定元素(第一个参数是下标)
        // 这个方法使用不多,因为对于ArrayList集合来说效率比较低。
        myList.add(1, "KING");

        // 迭代
        Iterator it = myList.iterator();
        while(it.hasNext()){
            Object elt = it.next();
            System.out.println(elt);
        }

        // 根据下标获取元素
        Object firstObj = myList.get(0);
        System.out.println(firstObj);

        // 因为有下标,所以List集合有自己比较特殊的遍历方式
        // 通过下标遍历。【List集合特有的方式,Set没有。】
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }

        // 获取指定对象第一次出现处的索引。
        System.out.println(myList.indexOf("C")); // 3

        // 获取指定对象最后一次出现处的索引。
        System.out.println(myList.lastIndexOf("C")); // 4

        // 删除指定下标位置的元素
        // 删除下标为0的元素
        myList.remove(0);
        System.out.println(myList.size()); // 5

        System.out.println("====================================");

        // 修改指定位置的元素
        myList.set(2, "Soft");

        // 遍历集合
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }
    }
}

/*
计算机英语:
    增删改查这几个单词要知道:
        增:add、save、new
        删:delete、drop、remove
        改:update、set、modify
        查:find、get、query、select
 */

ArrayList集合初始化容量以及扩容

ArrayList集合:
1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
2、集合底层是一个Object[]数组。
3、构造方法:
new ArrayList();
new ArrayList(20);
4、ArrayList集合的扩容:
增长到原容量的1.5倍。
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合
的时候预估计元素的个数,给定一个初始化容量。
5、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
6、数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
7、向数组末尾添加元素,效率很高,不受影响。
8、面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。
另外,我们检索/查找某个元素的操作比较多。

​ 9、ArrayList集合是非线程安全的。(不是线程安全的集合。)


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

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

        // 默认初始化容量是10
        // 数组的长度是10
        List list1 = new ArrayList();
        // 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
        System.out.println(list1.size()); // 0

        // 指定初始化容量
        // 数组的长度是20
        List list2 = new ArrayList(20);
        // 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
        System.out.println(list2.size()); // 0

        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
        list1.add(5);
        list1.add(6);
        list1.add(7);
        list1.add(8);
        list1.add(9);
        list1.add(10);

        System.out.println(list1.size());

        // 再加一个元素
        list1.add(11);
        System.out.println(list1.size()); // 11个元素。
        /*
        int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity,oldCapacity >> 1);
         */
        // 100 二进制转换成10进制: 00000100右移一位 00000010 (2)  【4 / 2】
        // 原先是4、现在增长:2,增长之后是6,增长之后的容量是之前容量的:1.5倍。
        // 6是4的1.5倍
    }
}

计算机位运算分为:逻辑位运算 和 位移运算,不懂的应该去了解下就明白了

新的长度newlength,是在第一参数基础上,增加第二参数,第二参数为2,所以新容量就为6

ArrayList的构造方法


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

/*
集合ArrayList的构造方法
 */
public class ArrayListTest02 {
    public static void main(String[] args) {

        // 默认初始化容量10
        List myList1 = new ArrayList();

        // 指定初始化容量100
        List myList2 = new ArrayList(100);

        // 创建一个HashSet集合
        Collection c = new HashSet();
        // 添加元素到Set集合
        c.add(100);
        c.add(200);
        c.add(900);
        c.add(50);

        // 通过这个构造方法就可以将HashSet集合转换成List集合。
        List myList3 = new ArrayList(c);
        for(int i = 0; i < myList3.size(); i++){
            System.out.println(myList3.get(i));
        }
    }
}

单向链表数据结构

/*
链表类。(单向链表)
 */
public class Link<E> {
    public static void main(String[] args) {
        Link<String> link = new Link<>();
        link.add("abc");

        // 类型不匹配。
        //link.add(123);
    }

    // 头节点
    Node header;

    int size = 0;

    public int size(){
        return size;
    }

    // 向链表中添加元素的方法(向末尾添加)
    public void add(E data){
    //public void add(Object data){
        // 创建一个新的节点对象
        // 让之前单链表的末尾节点next指向新节点对象。
        // 有可能这个元素是第一个,也可能是第二个,也可能是第三个。
        if(header == null){
            // 说明还没有节点。
            // new一个新的节点对象,作为头节点对象。
            // 这个时候的头节点既是一个头节点,又是一个末尾节点。
            header = new Node(data, null);
        }else {
            // 说明头不是空!
            // 头节点已经存在了!
            // 找出当前末尾节点,让当前末尾节点的next是新节点。
            Node currentLastNode = findLast(header);
            currentLastNode.next = new Node(data, null);
        }
        size++;
    }

    /**
     * 专门查找末尾节点的方法。
     */
    private Node findLast(Node node) {
        if(node.next == null) {
            // 如果一个节点的next是null
            // 说明这个节点就是末尾节点。
            return node;
        }
        // 程序能够到这里说明:node不是末尾节点。
        return findLast(node.next); // 递归算法!
    }

    // 删除链表中某个数据的方法
    public void remove(Object obj){

    }

    // 修改链表中某个数据的方法
    public void modify(Object newObj){

    }

    // 查找链表中某个元素的方法。
    public int find(Object obj){
        return 1;
    }
}


/*
单链表中的节点。
节点是单向链表中基本的单元。
每一个节点Node都有两个属性:
    一个属性:是存储的数据。
    另一个属性:是下一个节点的内存地址。
 */
public class Node {

    // 存储的数据
    Object data;

    // 下一个节点的内存地址
    Node next;

    public Node(){

    }

    public Node(Object data, Node next){
        this.data = data;
        this.next = next;
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建了一个集合对象
        Link link = new Link();

        // 往集合中添加元素
        link.add("abc");
        link.add("def");
        link.add("xyz");

        // 获取元素个数
        System.out.println(link.size());

    }
}

005-链表(单向链表)

链表的优点和缺点

链表的优点:

​ 由于链表上的元素在空间存储上内存地址不连续。
​ 所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
​ 在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议
​ 使用LinkedList。

链表的缺点:

​ 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
​ 节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率
​ 较低。

ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class LinkedListTest01 {
    public static void main(String[] args) {
        // LinkedList集合底层也是有下标的。
        // 注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
        // LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。
        List list = new LinkedList();
        list.add("a");
        list.add("b");
        list.add("c");

        for(int i = 0; i <list.size(); i++){
            Object obj = list.get(i);
            System.out.println(obj);
        }
----------------------------------------------
        // LinkedList集合有初始化容量吗?没有。
        // 最初这个链表中没有任何元素。first和last引用都是null。
        // 不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是哪个集合。
        // 因为我们要面向接口编程,调用的方法都是接口中的方法。
        //List list2 = new ArrayList(); // 这样写表示底层你用了数组。
        List list2 = new LinkedList(); // 这样写表示底层你用了双向链表。

        // 以下这些方法你面向的都是接口编程。
        list2.add("123");
        list2.add("456");
        list2.add("789");

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

    }
}

双向链表006-双向链表

LinkedList源码分析

image-20221124201651789

image-20221124202026946

007-LinkedList内存图

Vector

1、底层也是一个数组。
2、初始化容量:10
3、怎么扩容的?
扩容之后是原容量的2倍。
10--> 20 --> 40 --> 80

4、ArrayList集合扩容特点:
ArrayList集合扩容是原容量1.5倍。

5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,
是线程安全的。效率比较低,使用较少了。

6、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:
java.util.Collections;

​ java.util.Collection 是集合接口。
​ java.util.Collections 是集合工具类。

public class VectorTest {
    public static void main(String[] args) {
        // 创建一个Vector集合
        List vector = new Vector();
        //Vector vector = new Vector();

        // 添加元素
        // 默认容量10个。
        vector.add(1);
        vector.add(2);
        vector.add(3);
        vector.add(4);
        vector.add(5);
        vector.add(6);
        vector.add(7);
        vector.add(8);
        vector.add(9);
        vector.add(10);

        // 满了之后扩容(扩容之后的容量是20.)
        vector.add(11);

        Iterator it = vector.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }

        // 这个可能以后要使用!!!!
        List myList = new ArrayList(); // 非线程安全的。

        // 变成线程安全的
        Collections.synchronizedList(myList); // 这里没有办法看效果,因为多线程没学,你记住先!

        // myList集合就是线程安全的了。
        myList.add("111");
        myList.add("222");
        myList.add("333");
    }
}

泛型机制

1、JDK5.0之后推出的新特性:泛型
2、泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)
3、使用了泛型好处是什么?
第一:集合中存储的元素类型统一了。
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”!

4、泛型的缺点是什么?
导致集合中存储的元素缺乏多样性!
大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可。

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

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

        /*
        // 不使用泛型机制,分析程序存在缺点
        List myList = new ArrayList();

        // 准备对象
        Cat c = new Cat();
        Bird b = new Bird();

        // 将对象添加到集合当中
        myList.add(c);
        myList.add(b);

        // 遍历集合,取出每个Animal,让它move
        Iterator it = myList.iterator();
        while(it.hasNext()) {
            // 没有这个语法,通过迭代器取出的就是Object
            //Animal a = it.next();

            Object obj = it.next();
            //obj中没有move方法,无法调用,需要向下转型!
            if(obj instanceof Animal){
                Animal a = (Animal)obj;
                a.move();
            }
        }
         */

        // 使用JDK5之后的泛型机制
        // 使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据。
        // 用泛型来指定集合中存储的数据类型。
        List<Animal> myList = new ArrayList<Animal>();

        // 指定List集合中只能存储Animal,那么存储String就编译报错了。
        // 这样用了泛型之后,集合中元素的数据类型更加统一了。
        //myList.add("abc");

        Cat c = new Cat();
        Bird b = new Bird();

        myList.add(c);
        myList.add(b);

        // 获取迭代器
        // 这个表示迭代器迭代的是Animal类型。
        Iterator<Animal> it = myList.iterator();
        while(it.hasNext()){
            // 使用泛型之后,每一次迭代返回的数据都是Animal类型。
            //Animal a = it.next();
            // 这里不需要进行强制类型转换了。直接调用。
            //a.move();

            // 调用子类型特有的方法还是需要向下转换的!
            Animal a = it.next();
            if(a instanceof Cat) {
                Cat x = (Cat)a;
                x.catchMouse();
            }
            if(a instanceof Bird) {
                Bird y = (Bird)a;
                y.fly();
            }
        }
    }
}

class Animal {
    // 父类自带方法
    public void move(){
        System.out.println("动物在移动!");
    }
}

class Cat extends Animal {
    // 特有方法
    public void catchMouse(){
        System.out.println("猫抓老鼠!");
    }
}

class Bird extends Animal {
    // 特有方法
    public void fly(){
        System.out.println("鸟儿在飞翔!");
    }
}

类型自动推断

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

/*
JDK之后引入了:自动类型推断机制。(又称为钻石表达式)
 */
public class GenericTest02 {
    public static void main(String[] args) {

​        // ArrayList<这里的类型会自动推断>(),前提是JDK8之后才允许。
​        // 自动类型推断,钻石表达式!
​        List<Animal> myList = new ArrayList<>();

​        myList.add(new Animal());
​        myList.add(new Cat());
​        myList.add(new Bird());

​        // 遍历
​        Iterator<Animal> it = myList.iterator();
​        while(it.hasNext()){
​            Animal a = it.next();
​            a.move();
​        }

​        List<String> strList = new ArrayList<>();

​        // 类型不匹配。
​        //strList.add(new Cat());
​        strList.add("http://www.126.com");
​        strList.add("http://www.baidu.com");
​        strList.add("http://www.b.com");

​        // 类型不匹配。
​        //strList.add(123);

​        //System.out.println(strList.size());

​        // 遍历
​        Iterator<String> it2 = strList.iterator();
​        while(it2.hasNext()){
​            // 如果没有使用泛型
​            /*
​            Object obj = it2.next();
​            if(obj instanceof String){
​                String ss = (String)obj;
​                ss.substring(7);
​            }
​             */
​            // 直接通过迭代器获取了String类型的数据
​            String s = it2.next();
​            // 直接调用String类的substring方法截取字符串。
​            String newString = s.substring(7);
​            System.out.println(newString);
​        }
​    }
}

自定义泛型

自定义泛型可以吗?可以
自定义泛型的时候

<> 尖括号中的是一个标识符,随便写。
java源代码中经常出现的是:

E是Element单词首字母。
T是Type单词首字母。


public class GenericTest03<标识符随便写> {

    public void doSome(标识符随便写 o){
        System.out.println(o);
    }

    public static void main(String[] args) {

        // new对象的时候指定了泛型是:String类型
        GenericTest03<String> gt = new GenericTest03<>();

        // 类型不匹配
        //gt.doSome(100);

        gt.doSome("abc");

        // =============================================================
        GenericTest03<Integer> gt2 = new GenericTest03<>();
        gt2.doSome(100);

        // 类型不匹配
        //gt2.doSome("abc");

        MyIterator<String> mi = new MyIterator<>();
        String s1 = mi.get();

        MyIterator<Animal> mi2 = new MyIterator<>();
        Animal a = mi2.get();

        // 不用泛型就是Object类型。
        /*GenericTest03 gt3 = new GenericTest03();
        gt3.doSome(new Object());*/
    }
}

class MyIterator<T> {
    public T get(){
        return null;
    }
}

增强for循环(foreach)

/*
JDK5.0之后推出了一个新特性:叫做增强for循环,或者叫做foreach
 */
public class ForEachTest01 {
    public static void main(String[] args) {

        // int类型数组
        int[] arr = {432,4,65,46,54,76,54};

        // 遍历数组(普通for循环)
        for(int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        // 增强for(foreach)
        // 以下是语法
        /*for(元素类型 变量名 : 数组或集合){
            System.out.println(变量名);
        }*/

        System.out.println("======================================");
        // foreach有一个缺点:没有下标。在需要使用下标的循环中,不建议使用增强for循环。
        for(int data : arr) {
            // data就是数组中的元素(数组中的每一个元素。)
            System.out.println(data);
        }
    }
}
/*
集合使用foreach
 */
public class ForEachTest02 {
    public static void main(String[] args) {
        // 创建List集合
        List<String> strList = new ArrayList<>();

        // 添加元素
        strList.add("hello");
        strList.add("world!");
        strList.add("kitty!");

        // 遍历,使用迭代器方式
        Iterator<String> it = strList.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

        // 使用下标方式(只针对于有下标的集合)
        for(int i = 0; i < strList.size(); i++){
            System.out.println(strList.get(i));
        }

        // 使用foreach
        for(String s : strList){ // 因为泛型使用的是String类型,所以是:String s
            System.out.println(s);
        }

        List<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(200);
        list.add(300);
        for(Integer i : list){ // i代表集合中的元素
            System.out.println(i);
        }
    }
}

HashSet集合特点

/*
HashSet集合:
    无序不可重复。
 */
public class HashSetTest01 {
    public static void main(String[] args) {
        // 演示一下HashSet集合特点
        Set<String> strs = new HashSet<>();

        // 添加元素
        strs.add("hello3");
        strs.add("hello4");
        strs.add("hello1");
        strs.add("hello2");
        strs.add("hello3");
        strs.add("hello3");
        strs.add("hello3");
        strs.add("hello3");

        // 遍历
        /*
        hello1
        hello4
        hello2
        hello3
        1、存储时顺序和取出的顺序不同。
        2、不可重复。
        3、放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
         */
        for(String s : strs){
            System.out.println(s);
        }
    }
}

TreeSet集合

import java.util.Set;
import java.util.TreeSet;

/*
TreeSet集合存储元素特点:
    1、无序不可重复的,但是存储的元素可以自动按照大小顺序排序!
    称为:可排序集合。

    2、无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。
 */
public class TreeSetTest01 {
    public static void main(String[] args) {
        // 创建集合对象
        Set<String> strs = new TreeSet<>();
        // 添加元素
        strs.add("A");
        strs.add("B");
        strs.add("Z");
        strs.add("Y");
        strs.add("Z");
        strs.add("K");
        strs.add("M");
        // 遍历
        /*
            A
            B
            K
            M
            Y
            Z
        从小到大自动排序!
         */
        for(String s : strs){
            System.out.println(s);
        }
    }
}

Map接口常用方法



import java.util.HashSet;
import java.util.Set;

public class MyClass {

    // 声明一个静态内部类
    private static class InnerClass {

        // 静态方法
        public static void m1(){
            System.out.println("静态内部类的m1方法执行");
        }

        // 实例方法
        public void m2(){
            System.out.println("静态内部类中的实例方法执行!");
        }

    }

    public static void main(String[] args) {

        // 类名叫做:MyClass.InnerClass
        MyClass.InnerClass.m1();

        // 创建静态内部类对象
        MyClass.InnerClass mi =  new MyClass.InnerClass();
        mi.m2();

        // 给一个Set集合
        // 该Set集合中存储的对象是:MyClass.InnerClass类型
        Set<MyClass.InnerClass> set = new HashSet<>();

        // 这个Set集合中存储的是字符串对象。
        Set<String> set2 = new HashSet<>();

        Set<MyMap.MyEntry<Integer, String>> set3 = new HashSet<>();
    }
}


class MyMap {
    public static class MyEntry<K,V> {

    }
}

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/*
java.util.Map接口中常用的方法:
    1、Map和Collection没有继承关系。
    2、Map集合以key和value的方式存储数据:键值对
        key和value都是引用数据类型。
        key和value都是存储对象的内存地址。
        key起到主导的地位,value是key的一个附属品。
    3、Map接口中常用方法:
        V put(K key, V value) 向Map集合中添加键值对
        V get(Object key) 通过key获取value
        void clear()    清空Map集合
        boolean containsKey(Object key) 判断Map中是否包含某个key
        boolean containsValue(Object value) 判断Map中是否包含某个value
        boolean isEmpty()   判断Map集合中元素个数是否为0
        V remove(Object key) 通过key删除键值对
        int size() 获取Map集合中键值对的个数。
        Collection<V> values() 获取Map集合中所有的value,返回一个Collection

        Set<K> keySet() 获取Map集合所有的key(所有的键是一个set集合)

        Set<Map.Entry<K,V>> entrySet()
            将Map集合转换成Set集合
            假设现在有一个Map集合,如下所示:
                map1集合对象
                key             value
                ----------------------------
                1               zhangsan
                2               lisi
                3               wangwu
                4               zhaoliu

                Set set = map1.entrySet();
                set集合对象
                1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】
                2=lisi     【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】
                3=wangwu
                4=zhaoliu ---> 这个东西是个什么?Map.Entry
 */
public class MapTest01 {
    public static void main(String[] args) {
        // 创建Map集合对象
        Map<Integer, String> map = new HashMap<>();
        // 向Map集合中添加键值对
        map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");
        // 通过key获取value
        String value = map.get(2);
        System.out.println(value);
        // 获取键值对的数量
        System.out.println("键值对的数量:" + map.size());
        // 通过key删除key-value
        map.remove(2);
        System.out.println("键值对的数量:" + map.size());
        // 判断是否包含某个key
        // contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
        System.out.println(map.containsKey(new Integer(4))); // true
        // 判断是否包含某个value
        System.out.println(map.containsValue(new String("wangwu"))); // true

        // 获取所有的value
        Collection<String> values = map.values();
        // foreach
        for(String s : values){
            System.out.println(s);
        }

        // 清空map集合
        map.clear();
        System.out.println("键值对的数量:" + map.size());
        // 判断是否为空
        System.out.println(map.isEmpty()); // true
    }
}

遍历Map集合


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/*
Map集合的遍历。【非常重要】
 */
public class MapTest02 {
    public static void main(String[] args) {

        // 第一种方式:获取所有的key,通过遍历key,来遍历value
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "zhangsan");
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");
        // 遍历Map集合
        // 获取所有的key,所有的key是一个Set集合
        Set<Integer> keys = map.keySet();
        // 遍历key,通过key获取value
        // 迭代器可以
        /*Iterator<Integer> it = keys.iterator();
        while(it.hasNext()){
            // 取出其中一个key
            Integer key = it.next();
            // 通过key获取value
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }*/
        // foreach也可以
        for(Integer key : keys){
            System.out.println(key + "=" + map.get(key));
        }

        // 第二种方式:Set<Map.Entry<K,V>> entrySet()
        // 以上这个方法是把Map集合直接全部转换成Set集合。
        // Set集合中元素的类型是:Map.Entry
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        // 遍历Set集合,每一次取出一个Node
        // 迭代器
        /*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while(it2.hasNext()){
            Map.Entry<Integer,String> node = it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + "=" + value);
        }*/

        // foreach
        // 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
        // 这种方式比较适合于大数据量。
        for(Map.Entry<Integer,String> node : set){
            System.out.println(node.getKey() + "--->" + node.getValue());
        }
    }
}

image-20221127132615148

新方法


哈希表数据结构

     HashMap集合

​ 1、HashMap集合底层是哈希表/散列表的数据结构。
​ 2、哈希表是一个怎样的数据结构呢?
​ 哈希表是一个数组和单向链表的结合体。
​ 数组:在查询方面效率很高,随机增删方面效率很低。
​ 单向链表:在随机增删方面效率较高,在查询方面效率很低。
​ 哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
​ 3、HashMap集合底层的源代码:
​ public class HashMap{
​ // HashMap底层实际上就是一个数组。(一维数组)
​ Node<K,V>[] table;
​ // 静态的内部类HashMap.Node
​ static class Node<K,V> {
​ final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
​ final K key; // 存储到Map集合中的那个key
​ V value; // 存储到Map集合中的那个value
​ Node<K,V> next; // 下一个节点的内存地址。
​ }
​ }
​ 哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
​ 4、最主要掌握的是:
​ map.put(k,v)
​ v = map.get(k)
​ 以上这两个方法的实现原理,是必须掌握的。
​ 5、HashMap集合的key部分特点:
​ 无序,不可重复。
​ 为什么无序? 因为不一定挂到哪个单向链表上。
​ 不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。
​ 如果key重复了,value会覆盖。

​ 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
​ 所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

​ 6、哈希表HashMap使用不当时无法发挥性能!
​ 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了
​ 纯单向链表。这种情况我们成为:散列分布不均匀。
​ 什么是散列分布均匀?
​ 假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,
​ 是散列分布均匀的。
​ 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
​ 不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。
​ 也是散列分布不均匀。
​ 散列分布均匀需要你重写hashCode()方法时有一定的技巧。
​ 7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
​ 8、HashMap集合的默认初始化容量是16,默认加载因子是0.75
​ 这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。

​ 重点,记住:

HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,
​ 这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。

*/


import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapTest01 {
    public static void main(String[] args) {
        // 测试HashMap集合key部分的元素特点
        // Integer是key,它的hashCode和equals都重写了。
        Map<Integer,String> map = new HashMap<>();
        map.put(1111, "zhangsan");
        map.put(6666, "lisi");
        map.put(7777, "wangwu");
        map.put(2222, "zhaoliu");
        map.put(2222, "king"); //key重复的时候value会自动覆盖。

        System.out.println(map.size()); // 4

        // 遍历Map集合
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        for(Map.Entry<Integer,String> entry : set){
            // 验证结果:HashMap集合key部分元素:无序不可重复。
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

equals返回true 代表就是同一个对象 同一个对象的内存空间都是一个,哈希值相同,哈希算法算出下标都相同,然后单向链表上只有一个

比如我们还拿 Set 集合举例,Set 首先会调用对象的 hashCode 方法寻找对象的存储位置,如果两个相同的对象调用 hashCode 方法得到的结果不同,那么造成的后果就是 Set 中存储了相同的元素,而这样的结果肯定是不对的。所以就要求 调用 equals 返回 true 的两个对象必须具有相等的哈希码

  1. 调用 equals 返回 true 的两个对象必须具有相等的哈希码。
  2. 如果两个对象的 hashCode 返回值相同,调用它们 equals 方法不一返回 true 。

标签:System,println,add,集合,new,out
From: https://www.cnblogs.com/LDCnc-lili/p/16930987.html

相关文章

  • Scala学习(除列表集合等)
    Idea配置安装 进入idea后,选取file中的setting选项,在plugins中安装scala,之后重启idea。 右键点击项目,选取AddFrameworkSupport,并选取对应的scala版本  新建......
  • 学生管理系统-通过集合-遍历学生类
    packagecom.集合;importjava.util.ArrayList;importjava.util.Scanner;publicclass学生管理系统{publicstaticvoidmain(String[]args){System.out.......
  • springboot集合efk搭建日志平台
    springboot继承efk实现日志收集1.安装es和kibana我使用的云服务器centos7,2核+4G内存,跑起来内存使用率50%左右建议使用最低配置和我一样,1+2的配置kibana应该跑不起来,......
  • 集合初认识
    /*需求:1.学会导入集合的包2.学会给集合赋值集合对象.add(赋给集合的值)3.学会给集合指定的位置赋值集合对象.add(索引值,赋给集合的值)*/  /*当插入......
  • 集合的常用的方法
    /*需求:学会使用集合的常用方法1.A.remove()删除指定集合中的值,并且返回ture或者false,括号里写你想要删除的值2.A.remove()删除指定索引处集合中的值,并且返回被删除的集......
  • 集合
    集合集合概述数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据。集合为什么说在开发中使用较多?集合是一个容器,是一个载体,可以一次容纳多个对......
  • HTML网址集合
    <!DOCTYPEhtml><html><head><metacharset="utf-8"><title>网址</title></head><body><h1>网址</h1><ahref="https://www.baidu.com">百度</a><ahref="ht......
  • C# 集合
    C#集合usingSystem.Data;usingSystem.Collections;//数组(Array)string[]arr={"Hello","World"};int[]nums={1,99,2,66,15,8};//哈希表(Hasht......
  • 记一次List集合存入(null)空元素
    1问题原因误将空元素null值存入到了List集合中,导致后续的调用中出现了空指针。后续修改使用Stream过滤掉了空元素。2集合特性Java容器分为Collection和Map两大类,Coll......
  • 07. 常见集合
    Rust标准库包含了一系列非常有用的被称为集合(collections)的数据结构。大部分的数据结构都代表着某个特定的值,但集合却可以包含多个值。与内置的数组与元组类型不同,这些集合......