集合基础知识

集合

1. 概述

集合和数组的比较

  1. 数据存储长度和类型:

    数组定义后,数据类型确定,长度固定

    集合数据类型可以不固定(但是一般会通过泛型约束数据类型),大小可变

  2. 存储元素类型

    数组可以存储 基本类型和引用类型

    集合只可以存储 引用类型 ,想存基础类型需要用到包装类

  3. 适合场景

    数组适用于数据个数和类型确定的场景

    集合适合数据类型和长度不确定,需要增删操作,和大量其他操作的场景,因为集合的API也更丰富!

2. Collection集合的体系特点

集合体系图

在这里插入图片描述

Collection集合

Collection集合族谱

在这里插入图片描述

List家族 有序,可重复,有索引

Set家族 不可重复 且均无索引(仅指上面图中举到的例子) ,有无序的、有序的、升序的,

实例
public class CollectionDemo1 {
    public static void main(String[] args) {
        //目标: 明确Collection集合体系特点
        Collection list1 = new ArrayList();
        list1.add("ww");
        list1.add("嗨喽");
        list1.add(22);
        list1.add(false);
        list1.add(false);

        System.out.println(list1);
        System.out.println("---------------");

        Collection list2 = new HashSet();
        list2.add("ww");
        list2.add("嗨喽");
        list2.add(22);
        list2.add(false);
        list2.add(false);
        System.out.println(list2);
    }
}
集合对于泛型的支持

java是一个强类型语言,虽然希望在定义的时候指出数据类型,但还是支持泛型的,比如上面的例子,将整数、字符串、布尔类型的值放进同一个集合中均可以!!!

但是泛型和集合都只支持引用数据类型,所以 集合又称 对象容器

如果非要用 基本类型 ,使用包装类: int ->Integer double->Double

在这里插入图片描述

演示

public class CollectionDemo2 {
    public static void main(String[] args) {
        //目标: 运用泛型的Collection集合

        Collection<String> list1 = new ArrayList<>();
        list1.add("hhhh");
        list1.add("iiii");
        list1.add("jjjj");
        list1.add("kkkk");
        //但是  list1.add(23); 就会报错了

        //所以,比较下面两个 对象构建时的不同
        //Collection list2 = new ArrayList();
        //Collection<String> list3 = new ArrayList<>();

        Collection<Integer> listInt = new ArrayList<>();
        listInt.add(23);
        listInt.add(2333);
        Collection<Double>  listDouble = new HashSet<>();
        listDouble.add(23.2);//放入23就会报错,得存23.0
        listDouble.add(23.22);

    }
}

3. Collection常用API

why要学习

Collection是单列集合的祖宗接口,其功能是所有单列集合都可以继承使用的

常用API

public class CollectionAPIDemo {
    public static void main(String[] args) {
        //目标:  使用Collection的常用API

        //这样定义 ,list肯定可以使用Collection接口的方法,因为list现在是属于Collection类
        //但是不能使用ArrayList的功能,除非将这个list对象转成ArrayList类型,如下:
        Collection<Object> collection = new ArrayList<>();
        //报错: collection.trimToSize();
        ArrayList<Object> list1 = (ArrayList<Object>) collection;
        list1.trimToSize();


        //1.添加元素
        collection.add('a');
        collection.add("jjjj");
        collection.add(false);
        collection.add(1222);

        //2.清空数组
//        collection.clear();

        //3.判空
        System.out.println(collection.isEmpty());

        //4.获取集合大小
        System.out.println(collection.size());

        //5.判断集合中是否包含某个元素
        System.out.println(collection.contains("jjjj"));
        System.out.println(collection.contains(true));

        //6.删除元素
        //注意,这里collection是Collection接口,
        //而只有List才有索引,所以它删除的时候只能通过元素删,不能通过索引删,
        //除非转成Arraylist呗


        System.out.println(collection);
        System.out.println(((ArrayList<Object>) collection).remove(1));
        System.out.println(collection);
        System.out.println("--------------------");

        //7.把集合转成数组
        //即使是存储String类型的集合,转成数组时也是默认Object数组,
        // 因为假如中间混入了整数类型呢,就像假如有男同学翻墙进入女厕所呢,
        // 所以保险起见,存到Object数组中

//        Collection<String> c = new ArrayList<>();
//        c.add("hh");
//        String[] strings = (String[]) c.toArray();


        Object[] objects = collection.toArray();

        System.out.println(collection);
        //下面这样输出  输出的是object的地址,
        // 再下面Arrays类的toString的方法,则输出数组内容,因为已经帮你写好了object类的toString方法
        //如果是自己定义的类,想将对象内容输出的时候,要记得重写toString方法,就像这个Student类一样
//         System.out.println(objects.toString());
        System.out.println("数组:"+Arrays.toString(objects));
    }
}

在这里插入图片描述

4. Collection集合的遍历方式

1. 迭代器(只用于集合)

在这里插入图片描述

public class CollectionIterator {
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
        collection.add("hhh");
        collection.add("kkk");
        collection.add("ddd");
        collection.add("qqq");
        collection.add("hhhh");
        Iterator<String> iterator = collection.iterator();
        while (iterator.hasNext()){ // 使用迭代器,开始迭代器指向集合第一个元素前
            System.out.println(iterator.next());
        }
        //如果迭代器越界,会出现 NoSuchElementException
    }
}

2. foreach/增强for循环(可用于集合和数组)

在这里插入图片描述

//foreach 循环
        int[] ages = {11,22,34};
        for (int age : ages) {
            System.out.println(age);
            //但是注意:这只是将ages中的数据拷贝出来再输出的,所以在这里改元素是不会保留的
            //比如下面的测试代码
            if (age < 18) {
                age = 100;  //这里编译也没有给颜色
            }
        }

        //而fori循环是可以修改元素值的!!!这就是foreach和fori的差别吧
        for (int i = 0; i < ages.length; i++) {
            if (ages[i] < 18) {
                ages[i] = 100;
            }
        }
        System.out.println(Arrays.toString(ages));

3. lambda表达式(jdk8之后)

public class CollectionLambda {
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
        collection.add("hhhh");
        collection.add("hhhh");
        collection.add("kkkk");
        //这个Consumer 也是一个函数式接口(是个接口,只有一个抽象方法),可用lambda简化
        collection.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

        //lambda简化
        collection.forEach(s -> System.out.println(s));

        //还可将lambda替换为方法引用
        collection.forEach(System.out::println);
    }
}

不过感觉常用的还是foreach遍历(但它无法修改元素 !),但是假如只是输出遍历的元素,用最后的forEach匿名对象类的方法引用形式最方便!

但如果输出的话,集合不是可以直接用sout输出嘛,所以这种遍历输出应该更多是用在输出存储自定义类型对象时候吧(但是给这个类重写toString方法,应该不行,那是Arrays的toString方法,是数组!到下面再看看)

5. Collection集合存储自定义类型的对象

例子

public class CollectionOtherClass {
    public static void main(String[] args) {
        Collection<Movie> movies = new ArrayList<>();
        Movie[] movies1 = new Movie[3];
        movies1[0] = new Movie("唐人街探案",9.0,"王宝强,刘昊然,美女");
        movies1[1] = new Movie("美丽心灵",9.3,"未知");
        movies1[2] = new Movie("肖申克的救赎",9.5,"帅哥");

        movies.add(new Movie("唐人街探案",9.0,"王宝强,刘昊然,美女"));
        movies.add(new Movie("美丽心灵",9.3,"未知"));
        movies.add(new Movie("肖申克的救赎",9.5,"帅哥"));

        //对于集合和数组,它们就是要输出每个元素的地址,方法也是不一样的,如下
        System.out.println(movies);
        System.out.println(Arrays.toString(movies1));
        //如果想输出内容,那就在movie类中重写toString方法,因为toString方法默认是输出地址

        //或者遍历集合输出
        movies.forEach(System.out::println);
    }
}

内存表示

栈内存和堆内存

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

foreach遍历的底层原理
在这里插入图片描述

集合中存储的元素是什么信息?

是元素的地址!!!所以直接输出集合是集合中每个元素的地址,如果想要内容,要么遍历,要么重写对应类的toString方法,

但是好像如果集合中存储的是String,那就不是输出地址,而是直接输出内容!!!

6. 常见数据结构

1. 数据结构概述、栈、队列

数据结构:计算机底层怎样存储、组织数据的方式,数据相互之间是以什么方式排列在一起的,让计算机运行更加高效

栈:后进先出;设置游戏时,子弹上膛时,后上的先出

队列:先进先出;在餐馆排队,订餐系统的号码编排就是队列形式

2. 数组

查询快

增删慢

连续存储

3. 链表

4. 二叉树、二叉查找树

5. 平衡二叉树

6. 红黑树

7. List系列集合

1.List集合特点,特有API

  1. 整体:有序,可重复

  2. 特点:有索引,所以很多API都可以用索引来完成,是Collection没有的

public class ListAPI {
    public static void main(String[] args) {
        //一行经典代码,多态!!!
        List<String> list = new ArrayList<>();
        list.add("MySQL");
        list.add("HTML");
        list.add("Java");

        //1.在某位置 添加 元素
        list.add(2,"Lambda");
        System.out.println(list);
        //2.根据索引 删除 元素
        list.remove(1);
        System.out.println(list);
        //3.根据索引 获取 元素
        System.out.println(list.get(2));
        //4.根据索引 修改 元素
        //返回的是修改前的元素值
        System.out.println(list.set(0, "Regex"));
        System.out.println(list);

    }
}

总结
在这里插入图片描述

2. List集合的遍历方式小结

四种:

Collection具有的三种:Iterator ,foreach,Lambda

再加一个 fori循环 因为List有索引

3. ArrayList集合的底层原理

  1. 定义之后,添加第一个元素时,会生成一个长度为10的数组,

    此时插入下标0和数组长度0相等,故扩容

  2. 会有一个指针size指向数组中最后一个元素的下一个位置,也就是即将插入的位置,该下标同时也是当前数组所含数据个数

  3. 直到再一次插入下标10和数组长度10相等,再扩容,到原来的1.5倍

  4. 删除元素时:size向前走一位,对应元素 从前往后 向前移

  5. 添加元素时:size向后走一位,对应元素 从后向前 向后移

在这里插入图片描述

4. LinkedList集合的底层原理

8. 补充:集合的并发修改异常问题

例子

public class ListBianli {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Java");
        list.add("HTML");
        list.add("Lambda");
        list.add("Regex");
        list.add("Collection");

        //下面用四种方式遍历list,
        // 均在遍历过程中判断并删除"java"

        //1.Iterator遍历
//        Iterator<String> iterator = list.iterator();
//        while(iterator.hasNext()){
//            String ele = iterator.next();
//            if("Java".equals(ele)){
                list.remove("Java");//这样会报错
//                iterator.remove();
//            }
//        }
//        System.out.println(list);


        //2.foreach循环
//        for (String ele : list) {
//            if("Java".equals(ele)){
//                list.remove("Java");//这样会报错,但是不像迭代器一样可以更改
//            }
//        }
//        System.out.println(list);


        //3.forEach  Lambda循环
        list.forEach(ele->{
            if ("Java".equals(ele)) {
                list.remove("Java");
                //同样也会报错,而且forEach循环底层就是通过foreach实现的
            }
        });
        System.out.println(list);

        //4.fori循环
//        for (int i = 0; i < list.size(); i++) {
//            String ele = list.get(i);
//            if ("Java".equals(ele)) {
//                list.remove("Java");
//                //虽然不会报错,但是并不正确,而且不知道原理很难实现!!!
//            }
//        }
//        System.out.println(list);
        //可以通过结果知道,for i循环在删除的时候,是边删除边移动元素的,
        // 所以我们可以从后往前遍历,
        // 这样删除了当前元素,后面元素往前移,i--,不会漏掉任何一个元素



        //底层:在遍历前,会将集合的长度存储下来,
        // 每次循环前查看,假如集合长度发生了变化,就会停止循环,爆出异常
        //那我每次删了一个元素,再加一个元素,不就好了……,具体的检测肯定另有其因
    }
}

总结

在这里插入图片描述

9.补充:泛型深入

1. 泛型的概述和优势

理论
  1. 泛型只能包括引用型数据类型 ,你填个 int 是会报错的

  2. 所有集合的接口和实现类都支持泛型

  3. 泛型的优势在于 在编译阶段就约束了数据类型

    (集合和数组的一个区别就是,数组定义后,数据类型就定下来了,而集合中的数据类型是不固定的,但是最终还是用泛型约束了……而且数组也可以通过和Object的结合放各种基本的引用数据类型,所以集合的这个优势被弱化了,但集合还是有自己不可替代的好处的,泛型肯定也是带来了更多方便……)

    在这里插入图片描述

实例解释
public class GenerationDemo1 {
    public static void main(String[] args) {
        //在编译而非运行的时候就已经确定是String类型了
        //下面add的方法中可以看到提示中都是String类型
        List<String> list = new ArrayList<>();
        list.add("hhhh");
        list.add("kkkk");
        list.add("jjjj");
        

        //下面这种能加入各种引用类型的元素,
        //但在强转的时候极易出现错误
        //虽然好像设置成Object元素类型,强转也不行……就当作一个规定吧!!!
        List list1 = new ArrayList();
        list1.add(false);
        list1.add(23.3);
        list1.add("hhhh");

        for (Object o : list1) {
//            String ele = (String) o;  //强转不行嘛,double无法强转成String
//            String ele2 = String.valueOf(o);   //这样可以
            String ele1 = o + "";  //这样也可以!
            System.out.println(ele1);

        }

    }
}

2. 自定义泛型类

理论

在这里插入图片描述

作用:可在编译阶段 约束操作的数据类型

实操

test类

public class Test {
    public static void main(String[] args) {
        //目标: 模拟Arraylist设计一个MyArrayList类,关注泛型设计

        MyArrayList<String> list = new MyArrayList<>();
        list.add("Python");
        list.add("Map");
        list.add("LinkedList");

        System.out.println(list);
    }
}

MyArrayList类

public class MyArrayList<T> {
   //嘿嘿,“盗用”芯片大师的方法,实际上就是ArrayList类……
   //重点在泛型的利用上!!!
    private ArrayList<T> list = new ArrayList<>();
    public void add(T e){
        list.add(e);
    }
    public void remove(T e){
        list.remove(e);
    }

    @Override
    public String toString() {
        return  list.toString();
    }
}

3. 自定义泛型方法

理论

在这里插入图片描述

作用:方法中可以使用泛型接收一切实际类型的参数 ,方法更具备通用性

实操

这里有新的知识点,StringBuilder的append可以追加很多类型数据!!!真的好用

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

        Object[] objects = {"hhh",false,23.4,222222222};
        String[] str = {"hhh","kkk","2344"};
        printArrays(objects);
        printArrays(str);
        printArrays(null);
    }
    public static <T> void printArrays(T[] arr){
        if (arr == null) {
            System.out.println(arr);
            return;
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        for (int i = 0; i < arr.length; i++) {
            stringBuilder.append(arr[i]).append(i == arr.length-1?"":", ");
        }
        stringBuilder.append("]");
        System.out.println(stringBuilder);
    }

运行结果:

[hhh, false, 23.4, 222222222]
[hhh, kkk, 2344]
null

4. 自定义泛型接口

理论

在这里插入图片描述

作用:泛型接口可以约束实现类的数据类型 ,实现类在实现接口的时候传入需要操作的数据类型,这样重写的方法都是针对该类型的操作

实操

  1. Data泛型接口

    public interface Data<T> {
        void add(T t);
        void delete(int id);
        void update(T t);
        T queryById(int id);
    }
    
  2. Student类和Teacher类(只是建立走个形式)

  3. 两个实现类 TeacherData和StudentData

    public class StudentData implements Data<Student>{
        @Override
        public void add(Student student) {
    
        }
    
        @Override
        public void delete(int id) {
    
        }
    
        @Override
        public void update(Student student) {
    
        }
    
        @Override
        public Student queryById(int id) {
            return null;
        }
    }
    

    TeacherData则同理

5. 泛型通配符、上下限

泛型通配符和上下限

在这里插入图片描述

实操
public class GenerationTPF {
    public static void main(String[] args) {

        ArrayList<BMW> bmws = new ArrayList<>();
        bmws.add(new BMW());
        bmws.add(new BMW());
        bmws.add(new BMW());
        go(bmws);
        
        ArrayList<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        dogs.add(new Dog());
        dogs.add(new Dog());
        
        ArrayList<BENZ> benzs = new ArrayList<>();
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        
        go(benzs);
        //因为
        // ArrayList<Cars>  
        // ArrayList<BMW>
        // ArrayList<BENZ>
        //这三个东西是没有任何关联的(虽然BMW和BENZ是Car的子类)
        //所以,引进了通配符?,使得Car,BMW,BENZ均可使用非泛型方法的go方法,
        // 因为假如go方法是泛型方法,形参就可以直接写成ArrayList<T>这样
        
        
//        go(dogs);
        //明明是汽车比赛,现在连狗都能进!!!
        //所以SUN公司又创建了一个上下限的概念,
        // 规定只能是某个类的子类或父类
    }
    
    static void go(ArrayList<? extends Car> cars){
    }
}

class Dog{
    
}
class BMW extends Car{
    
}
class BENZ extends Car{
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值