Java基础整理(十五)

集合(上)

传送门(下部分)

1. 基本概念

  • 集合就是一个容器 ,是一个载体,可以存放对象的引用。即集合不能直接存储基本数据类型,也不能直接存储java对象,存储的是java对象的内存地址
  • 所有集合类和接口都在java.util包下
  • java集合分为两大类
    • 一类是单个方式存储元素,这一类集合中的超级父接口:java.util.Collection
    • 一类是以键值对儿的方式存储元素,这一类集合中的超级父接口:java.util.Map

2. 集合的UML

UML是一种统一建模语言,适用于所有面向对象的编程语言,它描述类和类之间的关系、程序执行流程、对象的状态等

下图为集合中常用的类和接口的UML图:

  • Collection集合

在这里插入图片描述

  • Map集合

在这里插入图片描述

  • 实现类总结

    • ArrayList:底层是数组
    • LinkedList:底层是双向链表
    • Vector:底层是数组,线程安全,效率较低,使用较少
    • HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分
    • TreeSet:底层是TreeMap,放到TreeMap集合中的元素等同于放大TreeMap集合key部分
    • HashMap:底层是哈希表
    • Hashtable:底层是哈希表,线程安全,效率较低,使用较少
    • Properties:线程安全的,并且key和value只能存储字符串String
    • TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序
  • 各集合特点

    • List集合存储元素特点:有序可重复

      有序:存进去的顺序和取出的顺序相同,每一个元素都有下标

      可重复:存进去1,可以再存储1

    • Set集合存储元素的特点:无序不可重复

      无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标

      不可重复:存进去1,不能再存储1

    • SortedSet(SortedMap)集合存储元素特点:无序不可重复,集合中的元素可按大小排序

      无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标

      不可重复:存进去1,不能再存储1

      可排序:按照大小进行排序

    • Map集合的key,就是一个Set集合

      在Set集合中存放数据,实际上放到了Map集合的key部分


3. 数组与集合的区别

  • 数组和集合都是Java中的容器
  • 数组的长度是固定的,集合的长度是可变的
  • 数组只能存储相同数据类型的数据,这里的数据类型可以是基本数据类型,也可以是引用类型
  • 集合可以存储不同数据类型的对象的引用(但一般情况下,我们会使用泛型来约定只使用1种数据类型),但不能存储基本数据类型

4. Collection集合接口常用方法

  • Collection中能存放什么元素?

    没有使用“泛型”之前,Collection中可以存储Object的所有子类型

    使用了“泛型”之后,Colection中只能存储某个具体的类型

    ==注:==集合中不能直接存储基本数据类型,也不能存储java对象,只是存储java对象的内存地址

    //Collection c = new Collection; 接口是抽象的,无法实例化
    //多态
    Collection c = new ArrayList();
    
(1) add
//boolean add(Object e) 向集合中添加元素
c.add(1200); //自动装箱,实际上放进去了一个对象的内存地址 Integer x = new Integer(1200);
c.add(3.14);
c.add(new Object());

(2) size
//int size() 获取集合中元素的个数,不是获取集合的容量
c.size() //3

(3) clear
// void clear() 清空集合
c.clear();
c.size(); //0

(4) contains
//boolean contains(Object o) 判断当前集合中是否包含元素o
c.add("hello");
c.add("hi");
c.add("li");
c.add(100);
c.add(new Object());
boolean flag = c.contains("hi"); //true
boolean flag1 = c.contains("???"); //false

(5) remove
//boolean remove(Object o)  删除集合中的某个元素(此方法只会删除一个)
c.remove("hello");

(6) isEmpty
//boolean isEmpty() 判断集合中元素个数是否为0
c.isEmpty(); //false

(7) toArray
//Object[] toArray() 调用方法可以把集合转换成数组
Object[] obj = c.toArray();
for(int i=0;i<c.size;i++){
    Object o = obj[i];
    System.out.println(o)// hi li 100 Object@XXXX  
                           //String 和 Integer 的 toString方法重写
}

(8) 重点:contains和remove的特殊性

分析以下代码:为什么结果为true?

public class Main {
    public static void main(String[] args){
        Collection c = new ArrayList();
        String s1 = new String("abc");
        c.add(s1);
        String s2 = new String("abc");
        System.out.println(c.contains(s2)); //true
    }
}

分析以下代码:为什么成功删除了?

public class Main {
    public static void main(String[] args){
        Collection c = new ArrayList();
        String s1 = new String("abc");
        c.add(s1);
        String s2 = new String("abc");
        c.remove(s2);
        System.out.println(c.size()); //0
    }
}

原因:实际上无论是contains方法还是remove方法,它们在底层代码中调用的都是equals方法

contains方法调用equals方法来判断是否相等,相等即包含。remove方法底层调用equals方法来判断是否为要删除的元素。

而String类中又重写了equals方法,即equals方法判断的是对象内的内容,而不是判断地址。所以可以实现上述代码的操作。

如果没有重写equals方法,依旧比较对象的内存地址,那么上述代码结果为 false和1。

==注:不重写equals方法,调用的是Object类中的equals方法,而Object类中equals方法是用“”来比较,而“ == ”比较的是内存地址

所以可以得出结论:存放在一个集合中的类型,一定要重写equals方法。


5. 迭代器

以下方法是Collection通用的一种方式。在Map集合中不能使用,在所有的Collection以及子类中使用

(1) 使用方法
  • 预备工作

    创建集合对象,并添加元素

    Collection c = new ArrayList();
    c.add("abc");
    c.add("def");
    c.add(100);
    c.add(new Object());
    
  • 获取集合对象的迭代器对象Iterator

    Iterator it = c.iterator();
    
  • 通过以上获取的迭代器对象开始迭代集合

    迭代器对象Iterator中的两个方法:

    • boolean hasNext() 如果有元素可以迭代,返回true
    • Object next() 返回迭代的下一个元素

    :不管存进去的是什么,取出来的都是Object,但底层不变

    while(it.hasNext()){
        Object obj = it.next();
        if(obj instanceof Integer)  //底层不变
            System.out.println("Integer类型");
        System.out.println(obj); //输出的时候转换成字符串,因为println会调用toString()方法
    }
    

在这里插入图片描述


(2) 迭代器的特殊性

集合结构只要发生改变,迭代器必须重新获取

  • 分析以下代码为什么报错?

    public class Demo{
        public static void main(String[] args){
            Collection c = new ArrayList();
            //此时获取的迭代器,指向的是集合中没有元素状态下的迭代器
    		Iterator it = c.iterator();
    		c.add("abc");
    		c.add("def");
    		c.add(100);
    		while(it.hasNext()){
    		    Object obj = it.next();
    		    System.out.println(obj); 
    		}
        }
    }
    
  • 分析以下代码为什么报错?

    public class Demo{
        public static void main(String[] args){
            Collection c = new ArrayList();	
    		c.add("abc");
    		c.add("def");
    		c.add(100);
            Iterator it = c.iterator();
    		while(it.hasNext()){
    		    Object obj = it.next();
                //删除元素后,集合的结构发生了变化,没有通知迭代器,导致迭代器的快照和原集合状态不同,需要重新获取迭代器,否则会报错
                c.remove(obj);
    		    System.out.println(obj); 
    		}
        }
    }
    

    上述代码解决办法:

    public class Demo{
        public static void main(String[] args){
            Collection c = new ArrayList();	
    		c.add("abc");
    		c.add("def");
    		c.add(100);
            Iterator it = c.iterator();
    		while(it.hasNext()){
    		    Object obj = it.next();  
                //调用迭代器的remove()方法
                it.remove(); //删除的是当前迭代器指向的元素
    		    System.out.println(obj); 
    		}
            System.out.println(c.size()); //0
        }
    }
    

    原因:Iterator it = c.iterator();获取的迭代器对象,迭代器用来遍历集合,此时相当于对当前集合的状态拍了一个快照,迭代器迭代时会参照这个快照进行迭代。而调用集合对象的remove()方法时,集合中的元素改变了,但迭代器的快照并没有改变,还会继续遍历,出现错误。采用迭代器的remove()方法时,迭代器会先将快照内的集合元素删除,再修改集合本身中的元素。

  • 总结

    • 集合结构只要发生变化,就需要重新获取迭代器
    • 在迭代集合元素的过程中,不能调用集合对象的remove()方法,一定要使用迭代器Iterator的remove()方法

6. List集合特有方法

(1) add

在列表的指定位置插入指定元素(第一个参数是下标)

//void add(int index, Object element)
List myList = new ArrayList();
myList.add("a");
mylist.add("b");
mylist.add(1,"KING") //在下标为1的位置插入“KING”

(2) get

根据下标获取元素

//Object get(int index)
Object firstObj = myList.get(0);

(3) indexOf

获取指定对象第一次出现处的索引

//int indexOf(Object o)
int index=myList.indexof("a");

(4) lastIndexOf

获取指定对象最后一次出现处的索引

//int lastIndexOf(Object o)
int indexLast = myList.lastIndexOf("b");

(5) remove

删除指定下标位置的元素

//Object remove(int index)
myList.remove(0);

(6) set

修改指定位置的元素

//Object set(int index,Object element)
myList.set(1,"niihao");

7. ArrayList集合

  • ArrayList集合底层是Object[]数组,非线程安全的

  • ArrayList集合有三种构造方法

    • 默认初始化容量为10

      List list = new ArrayList();
      
    • 指定初始化容量

      List list = new ArrayList(20);
      
    • 将其他集合转换成List集合

      Collection c = new HashSet();
      List list = new ArrayList(c);
      
  • ArrayList集合底层是数组,怎么优化?

    尽可能少的去扩容,因为数组扩容效率比较低。ArrayList集合的检索效率比较高,向数组尾部添加元素的效率比较高,但增删元素效率比较低。

  • ArrayList集合在开发中使用较多,因为向尾部添加元素效率不受影响,其次做检索/查找某个元素的操作比较多

  • 如何将ArrayList集合变为线程安全的

    使用集合工具类:java.util.Collections

    List list = new ArrayList(); //非线程安全
    Collections.synchroizedList(list); //安全
    

    java.util.Collection 是集合接口

    java.util.Collections 是集合工具类


8. LinkedList集合

  • LinkedList集合底层是双向链表,底层也有下标

    :ArrayList集合检索效率高,不单纯是因为有下标,因为LinkedList也有,而是因为底层数组发挥的作用

  • LinkedList集合检索/查找某个元素效率较低,但是随机删除和插入效率较高,但在末尾加元素时,还是使用ArrayList

  • LinkedList集合没有初始化容量,最初这个链表没有任何元素,first和last引用都是null

  • 链表在空间存储上,内存地址不连续

List list = new LinkedList();

9. Vector集合

  • Vector集合底层是一个数组
  • 初始化容量为10,每次扩容为原容量的2倍
  • Vector集合中所有方法都是线程同步的,线程安全的,都带有synchronized关键字。但是效率较低,很少使用
List list = new Vector();

10.泛型机制

(1) 基本概念
  • Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,**也就是说所操作的数据类型被指定为一个参数(**type parameter)这种参数类型可以用在接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

  • 泛型只是在程序编译阶段起作用,只是给编译器参考的

  • List<String> mylist = new ArrayList<String>();
    List<String> mylist = new ArrayList<>(); //自动类型推断  两种均可
    Iterator<String> it = mylist.iterator();//迭代器使用泛型
    

    其中尖括号内的类可以替换。使用泛型List之后,表示集合mylist只能存储String类型的数据


(2) 为什么使用泛型

对比下面两段代码

  • 不使用泛型

    public class Demo{
    
        public static void main(String[] args) {
    
            List mylist = new ArrayList();
            
            Animal a1 = new Animal();
            Animal a2 = new Animal();
            
            mylist.add(a1);
            mylist.add(a2);
    
            Iterator it = mylist.iterator();
    
            while(it.hasNext()){
                //Animal a = it.next(); 不可以!!
                Object obj = it.next();  //取出来的都是Object对象
                if(obj instanceof Animal){  //需要进行判断 再进行强制转换 因为Object类中没有doSome方法
                    Animal a = (Animal)obj;
                    a.doSome();
                }
            }
        }
    }
    
    class Animal{
        public void doSome(){
            System.out.println("Animal");
        }
    }
    
  • 使用泛型

    public class Demo{
    
        public static void main(String[] args) {
    
            List<Animal> mylist = new ArrayList();
            
            Animal a1 = new Animal();
            Animal a2 = new Animal();
            
            mylist.add(a1);
            mylist.add(a2);
    
            Iterator<Animal> it = mylist.iterator();
    
            while(it.hasNext()){
                Animal a = it.next();
                a.doSome();
    
            }
        }
    }
    
    class Animal{
        public void doSome(){
            System.out.println("Animal");
        }
    }
    
  • 使用泛型优点

    • 集合中存储的元素统一
    • 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”

(3) 自定义泛型

自定义泛型的时候,< >尖括号中的是一个标识符,随便写

  • 示例1:
public class GenericTest<标识符随便写> {
    public void doSome(标识符随便写 o){
        System.out.println(o);
    }
    
    public static void main(String[] args){
        GenericTest<String> gt = new GenericTest<>();
        gt.doSome("nihao");  //nihao 传入的必须是泛型内的类
        
        GenericTest gt1 = new GenericTest(); 
        gt1.doSome(new Object()); //不使用泛型 默认为Object类
    }
}
  • 示例2:
public class Demo{

    public static void main(String[] args) {
        
        MyIterator<String> mi = new MyIterator<>();
        String s = mi.get();
        
    }
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值