文章目录
集合
1. 概述
集合和数组的比较
-
数据存储长度和类型:
数组定义后,数据类型确定,长度固定
集合数据类型可以不固定(但是一般会通过泛型约束数据类型),大小可变
-
存储元素类型
数组可以存储 基本类型和引用类型
集合只可以存储 引用类型 ,想存基础类型需要用到包装类
-
适合场景
数组适用于数据个数和类型确定的场景
集合适合数据类型和长度不确定,需要增删操作,和大量其他操作的场景,因为集合的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
-
整体:有序,可重复
-
特点:有索引,所以很多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集合的底层原理
-
定义之后,添加第一个元素时,会生成一个长度为10的数组,
此时插入下标0和数组长度0相等,故扩容
-
会有一个指针size指向数组中最后一个元素的下一个位置,也就是即将插入的位置,该下标同时也是当前数组所含数据个数
-
直到再一次插入下标10和数组长度10相等,再扩容,到原来的1.5倍
-
删除元素时:size向前走一位,对应元素 从前往后 向前移
-
添加元素时: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. 泛型的概述和优势
理论
-
泛型只能包括引用型数据类型 ,你填个 int 是会报错的
-
所有集合的接口和实现类都支持泛型
-
泛型的优势在于 在编译阶段就约束了数据类型
(集合和数组的一个区别就是,数组定义后,数据类型就定下来了,而集合中的数据类型是不固定的,但是最终还是用泛型约束了……而且数组也可以通过和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. 自定义泛型接口
理论
作用:泛型接口可以约束实现类的数据类型 ,实现类在实现接口的时候传入需要操作的数据类型,这样重写的方法都是针对该类型的操作
实操
-
Data泛型接口
public interface Data<T> { void add(T t); void delete(int id); void update(T t); T queryById(int id); }
-
Student类和Teacher类(只是建立走个形式)
-
两个实现类 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{
}