集合实际可理解成是一个容器,可以用来容纳其他类型的数据,数组其实就是一个集合。
集合做为一个载体,在实际开发中,将从数据库中查询到的多条数据封装成多个对象,并将他们通过集合传到前端,然后遍历集合,展现数据。
集合不能直接存储基本数据类型,也不能直接存储Java对象,集合当中存储的都是Java对象的内存地址(引用)。
不同的集合对应不同的数据结构,在Java中,每一个不同类型的集合,底层会对应不同的数据结构,往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。
这里的数据结构,即数据存储的结构,不同的数据结构,数据存储方式不同。如:
new不同的集合对象,底层使用不同的数据结构:
//该集合底层为数组
new ArrayList();
//该集合对象底层是链表
new LinkedList();
//该集合对象底层是二叉树
new TreeSet();
所有的集合类和集合接口都在JDK的java.util包下。集合在Java中分为两大类:
Collection之List集合的特点:
Collection之Set集合的特点:
HashMap和Hashtable集合底层都是哈希表数据结构,但前者非线程安全。Hashtable类中的所有方法均带synchronized,线程安全,但效率低。
Collection中能存放什么元素?
--------
没有使用泛型之前,可存储Object的所有子类型
使用泛型之后,只能存储某个具体的类型
1)😉 boolean add (Object e)方法
//Collection是一个接口,无法实例化
Collection c = new Collection();
//多态
Collection c = new ArrayList();
//集合中只存Java对象的内存地址,此处是自动装箱后放进去了对象的内存地址。
c.add(1200);
c.add(new Object());
//自动装箱
c.add(true);
2)😉 int size()方法
System.out.println("集合中的元素个数是:"+c.size());
3)😉 void clear()方法
c.clear();
4)😉 boolean remove(Object o)方法
//删除某个元素
c.remove(true);
5)😉 boolean contains(Object o)方法
//判断当前集合中是否包含元素o
boolean query = c.contains("hello");
6)😉 boolean isEmpty()方法
//判断集合中元素个数是否为0
System.out.println( c.isEmpty() );
7)😉 Object[] toArray()方法
//集合转数组
Object[] objs = c.toArray();
注意是Collection集合的迭代,在Map集合中不能用。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Collection1 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(1200);
c.add(new Object());
c.add(true);
c.add("code-9527");
System.out.println("集合中的元素个数是:"+c.size());
Iterator it = c.iterator();
//当有下一个元素的时候
while(it.hasNext()){
//取出下一个元素
Object obj = it.next();
System.out.println(obj);
}
}
}
步骤:
Iterator it = c.iterator();
//迭代器对象可以调用两个方法:
boolean hasNext()
如果仍有元素可以迭代,则返回true
Object next()
返回迭代的下一个元素
Collection c = new ArrayList();
String s1 = new String("abc");
c.add(s1);
c.add(new String("def"));
String s2 = new String("abc");
//true
System.out.println(c.contains(s2));
结果分析:
contains方法底层源码中调用了equals,而String类的equals是重写成了比较对象的内容。这时候的contains就像在中国找小明,不管你是住河北的小明,还是住河南的小明。即不管内存地址是啥,String对象内容相同就当是包含。
Collection c = new ArrayList();
String s1 = new String("code-9527");
c.add(s1);
String s2 = new String("code-9527");
c.remove(s2);
//0
System.out.println(c.size());
remove方法底层也调用了equals方法,而String类的equals方法已经重写成了比较对象内容,所以传入s2可以将内容相同的s1去除。(若不是String类,是另一个没有重写equals的类,则没有这个效果)
Collection c = new ArrayList();
Iterator it = c.iterator();
//获取迭代器对象后,集合结构发生变更
c.add(1);//Integer
c.add(2);
while(it.hasNext()){
//next()方法的返回值类型必须是Object(不考虑泛型)
Object obj = it.next();
System.out.println(obj);
}
运行报错:
当集合结构发生变化,迭代器对象没有获取最新的时候,调用next()方法报错java.util.ConcurrentModificationException
Iterator it = c.iterator();获取迭代器对象,相当于给当前集合的状态打了一个快照,当底层代码运行时,会将快照与实际的集合状态进行对比
而用迭代器.remove() 删除元素,迭代器和集合中的元素都会放生变化,二者保持一致,就不会有java.util.ConcurrentModificationException错误。用集合.remove() 只是删除了集合中的元素。
Collection c = new ArrayList();
Iterator iterator = c.iterator();
while(iterator.hasNext()){
Object o = iterator.next();
//集合.remove()
//c.remove(o);
//改为迭代器.remove
iterator.remove();
System.out.println(o);
}
上面是Collection类中的一些方法,List接口除了从父接口collection中继承的方法,还有其特有的方法,和Set接口没关系。
List集合存储元素的特点是:
有序可重复,即List集合中的元素有下标,从0开始
//new LinkedList()
//new Vector()
List myList = new ArrayList();
myList.add("a");
myList.add("b");
myList.add(1,"code-9527");
除了Collection公共的迭代器遍历,List有其特有的下标遍历
//根据下标取元素
for(int i=0;i<myList.size();i++){
System.out.println(myList.get(i));
}
//获取指定对象第一次出现处的索引
//相对应的获取最后一次出现的索引:
//int lastIndexOf(Object o)
return myList.indexOf("aaa");
//根据下标删除元素
myList.remove(0);
//修改指定位置的元素
myList.set(2,"new");
//默认,容量为10
List list1 = new ArrayList();
//选择ArrayList的有参构造方法
//指定初始化容量为20
List list2 = new ArrayList(20);
//注意,size方法获取的是元素个数,不是集合容量
list1.size();
Collection c = new HashSet();
c.add("9527");
//通过这个构造方法,将HashSet转成ArrayList
List list = new ArrayList(c);
总结:
ArrayList集合用的最多,虽然随机增删元素效率较低,但常在末尾,效率不受影响,且查找效率高。最后,ArrayList集合是非线程安全的。
位运算符复习:
😉 对于链表数据结构来说,基本单元是节点Node,对于单向链表来说,任何一个节点都有两个属性,一个是存储数据,一个是存储下一个节点的内存地址。
public class Node{
//存储的数据
Object data;
//下一个节点的内存地址
Node next;
public Node(){
}
...
由图,链表的优点是:随机增删元素效率较高(不涉及大量元素的位移)。缺点是:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历
😉 LinkedList集合属于List集合,也是有下标的,可以下标遍历,但是查询效率不高(ArrayList是底层是个数组的原因,有无下标和查询效率没有必然的联系),这是因为链表中的元素在空间存储上内存地址不连续,不能通过简单的数学表达式就计算出被查找元素的内存地址。
ArrayList把检索发挥到了极致,LinkedList把随机增删发挥到了极致
//递归查找末尾节点
//一个几点的next为空,则为末尾节点
private Node findLast(Node node){
if(node.next == null){
return node;
}
return findLast(node.next);
😉 双向链表
双向链表的基本单元还是节点Node
LinkedList集合总结:
Vector vector = new Vector();
List vector = new Vector();
//非线程安全的
List myList = new ArrayList();
myList.add(1);
//调用synchronizedList方法,变成线程安全的
Collections.synchronizedList(myList);
注意:
java.util.Collection 是集合接口
java.util.Collections 是集合工具类
//素材
class Animal{
public void move(){
System.out.println("动物在移动");
}
}
class Cat extends Animal{
public void move(){
System.out.println("猫抓老鼠");
}
}
class Birds extends Animal{
public void move(){
System.out.println("鸟在飞");
}
}
不使用泛型:
List myList = new ArrayList();
Animal c = new Cat();
Animal b = new Birds();
myList.add(c);
myList.add(b);
Iterator iterator = myList.iterator();
while(iterator.hasNext()){
//没使用泛型前,这里返回类型只能是Object
Object obj = iterator.next();
//但Object类中没有move方法
//obj.move();报错
//需要频繁进行向下转型
if(obj instanceof Animal){
Animal animal = (Animal)obj;
animal.move();
}
}
使用泛型之后:(用泛型来指定集合中存储元素的类型)
//使用泛型List即表示这个List集合中只允许存储Animal类型的数据
List<Animal> myList = new ArrayList<Animal>();
//myList.add("abc");传入String类型此时就error
Cat c = new Cat();
Birds b = new Birds();
myList.add(c);
myList.add(b);
//即迭代器类型是Animal类型
Iterator<Animal> iterator = myList.iterator();
while(iterator.hasNext()){
//此时next()方法的返回值类型就是Animal
Animal obj = iterator.next();
//这里也就不用向下转型了
obj.move();
}
但这里如果要继续调用Animal子类的特有方法,也得向下转型:
Iterator<Animal> iterator = myList.iterator();
while(iterator.hasNext()){
Animal obj = iterator.next();
obj.move();
//访问子类特有的方法
if(obj instanceof Cat){
Cat cat = (Cat)obj;
cat.catchMouse();
}
if(obj instanceof Birds){
Birds birds = (Birds)obj;
birds.fly();
}
}
List<Animal> myList = new ArrayList<Animal>();
使用自动类型推断机制后,可写成:
List<Animal> myList = new ArrayList<>();
//这里的中的T是标识符,随便写,但和常用些
public class GenericTest<T>{
public void doSome(T x){
System.out.println(x);
}
public static void main(String[] args) {
GenericTest<String> g1 = new GenericTest<>();
g1.doSome("code");
GenericTest<Integer> g2 = new GenericTest<>();
g2.doSome(9527);
//写了泛型而不用,那就是Object类型
GenericTest g3 = new GenericTest();
//这里传参是Object类型
g3.doSome();
}
}
增强for循环不涉及下标,对无下标场景的遍历很实用
语法:
for(元素类型 变量名:数组或者集合){
....
}
举例:
int[] array = {96,97,98,99};
//普通股for循环遍历
for(int i=0;i<array.length;i++){
System.out.println(array[i]);
}
//增强for循环
for(int data:array){
System.out.println(data);
}
List集合的三种遍历:
List<String> myList = new ArrayList<>();
myList.add("code");
myList.add("-9529");
myList.add("Fight!");
//迭代器遍历
Iterator<String> iterator = myList.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//下标遍历
for(int i=0;i<myList.size();i++){
System.out.println(myList.get(i));
}
//增强for循环遍历
for(String s:myList){
System.out.println(s);
}
特点:
Set<String> mySet = new HashSet<>();
mySet.add("code1");
mySet.add("code2");
mySet.add("code3");
mySet.add("code1");
mySet.add("code1");
for(String str:mySet){
System.out.println(str);
}
运行结果:
myTreeSet.add("A");
myTreeSet.add("X");
myTreeSet.add("a");
myTreeSet.add("L");
for(String s:myTreeSet){
System.out.println(s);
}
运行结果: