Java学习16(泛型、foreach、MAP)

Java学习16

就是不怎么想看那个课题,还是看看老杜吧哈哈

泛型(Generic)

JDK5.0之后推出的新特性:泛型 <>

用泛型来指定集合中存储的数据类型
用了泛型之后,集合中的元素类型就更统一了

泛型这种机制,只在程序编译阶段起作用,只是给编译器参考的(运行阶段意义不大)

泛型的好处:
  第一:集合中存储的元素类型统一了
  第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
泛型的缺点:
  导致集合中存储的元素缺乏多样性!
  在实际业务中,一般一个集合中都是一种类型,所以泛型被认可

JDK8之后引入了:自动类型推断机制(又称位钻石表达式)
List myList = new ArrayList<>();
//后面的泛型<>里面可以空着,里面的类型会自动推断,被称为钻石表达式(JDK8之后才允许)

也可以自定义泛型:中的E是一个标识符,随便写,但是一般写E或者T,E是element单词首字母,T是type首字母

foreach 增强for循环

JDK5.0之后的新特性
语法:

   for(元素类型 变量名:数组或集合){     
       System.out.println(变量名);     //变量名代表的就是数组/集合中的每一个元素
   }                           

foreach有一个缺点,没有下标,在需要使用下标时,不建议使用foreach

Set集合

HashSet集合无序不可重复
TreeSet无序不可重复,但会自动排序

Map集合

  1. 与Collection没有继承关系

  2. 以key和Value的方式存储数据:键值对
    key和value都是引用数据类型
    key和value存储的都是内存地址
    key起主导地位,value是key的一个附属品

  3. Map接口中常用方法
    void clear() 清空Map
    boolean containsKey(Object key) 判断Map中是否包含某个key
    boolean containsValue(Object value) 判断Map中是否包含某个value
    V get(Object key) 通过key获取value
    Set keySet() 获取Map集合中所有的key,所有的键是一个Set集合
    boolean isEmpty() 判断集合中的元素个数是否为0
    V put(K key, V value) 向Map集合中添加键值对
    V remove(Object key) 通过key删除键值对
    int size() 获取Map集合中键值对的个数
    Collection values() 获取Map集合中所有的value,返回一个Collection

    Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合,Set集合中元素的类型是Map.Entry<K,V>,Map.Entry是一个Map中静态内部类

    例如:map1集合对象:

     key             value
     1               zhangsan
     2               lisi
     3               wangwu
    

    Set set = map1.entrySet();
    set集合对象形式如下:
    1=zhangsan
    2=lisi
    在这里插入图片描述

		Map<String, String> map = new HashMap<>();
        map.put("1","11");
        map.put("2","22");
        for(Map.Entry<String, String> entry : map.entrySet()){
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + " " + value);
        }
        System.out.println(map.entrySet());   //输出 [1=11, 2=22]

Map集合的遍历

第一种方式:先获取所有的key,通过遍历key,来遍历value
第二种方式:把map集合转换成Set集合,再遍历Set集合
第二种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值,这种方式比较适合大数据量

		//遍历map集合
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");
		//第一种,根据key遍历value
        Set<Integer> keySet = map.keySet();
        Iterator<Integer> it = keySet.iterator();
        while(it.hasNext()){
            Integer key = it.next();
            String value = map.get(key);
            System.out.println(key+"="+value);
        }
        //增强for循环,foreach
        for(Integer key2 : keySet){
            System.out.println(map.get(key2));
        }
        System.out.println("===========================");
        //第二种,转换成Set集合
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while(it2.hasNext()) {
            Map.Entry<Integer, String> node = it2.next();
            //也可行
            //System.out.println(node);
            Integer key3 = node.getKey();
            String value3 = node.getValue();
            System.out.println(key3 + "=" + value3);
        }
输出:
1=张三
2=李四
3=王五
张三
李四
王五
===========================
1=张三
2=李四
3=王五

HashMap集合:

  1. HashMap集合底层是哈希表/散列表数据结构
  2. 哈希表是一个怎样的数据结构?
    哈希表是一个数组和单向链表的结合体(加红黑树,这里估计是因为是初学,所以没讲)
    哈希表将两种数据结构融合在一起,充分发挥各自的优点
  3. HashMap底层实际上是一个数组(一维数组),一维数组的每一个元素是一个单向链表
    有一个静态的内部类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; // 下一个节点的内存地址。

哈希表/散列表数据结构

在这里插入图片描述

  1. map.put(k,v)实现原理:
    第一步:先将k,v封装到Node对象中
    第二步:底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组下标,
    下标位置上如果没有任何元素,就把Node加到这个位置上;
    如果下标对应的位置上有链表,此时会拿着k和链表上的每一个节点的k进行equals,如果所有的equals方法都返回false,那么这个新节点将被添加到链表的末尾;如果其中有一个equals返回的是true,那么这个节点的value将会被覆盖

  2. v=map.get(k)实现原理:
    底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组下标,
    下标位置上如果没有任何元素,返回null;
    如果这个位置上有单向链表,那会拿着k和单向链表上的每个节点的k进行equals,如果所有equals方法返回false,那么get方法返回null;
    只要其中有一个equals方法返回ture,那么此时这个节点的value就是我们要找的value,get方法最终返回这个value

  3. 为什么哈希表的随机增删,以及查询效率都高?
    增删是在链表上完成
    查询也不需要都扫描,只需要部分扫描
    HashMap集合的key,会先后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都要重写

  4. HashMap集合key的特点:
    无序不可重复
    为什么无序?因为不一定会挂到哪个单向链表上
    不可重复?equals方法来保证HashMap集合的key不可重复,如果重复了就覆盖了

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

  6. 同一个单向链表上所有节点的hash相同(也可以不同,存在哈希碰撞),因为他们的数组下标是一样的
    但同一个链表上的key和key的equals方法返回都是false,都不相等

  7. 哈希表HashMap使用不当时无法发挥性能!
    假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了
    纯单向链表。这种情况我们成为:散列分布不均匀。
    什么是散列分布均匀?
    假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
    假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
    不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。
    也是散列分布不均匀。散列分布均匀需要你重写hashCode()方法时有一定的技巧。

  8. 重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法

  9. HashMap集合的默认初始化容量为16,加载因子是0.75,扩容2倍
    这个默认的加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容,扩容之后的容量是原容量的2倍

    重点,记住:HashMap集合初始化容量必须是2的次幂,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。

  10. 在JDK8之后,如果哈希表单向链表中的元素大于等于8个,单向链表这种数据结构会变成红黑树这种数据结构;红黑树上的元素少于等于6个,会变回单向链表(这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围,提高效率)

重写hashCode()和equals()方法

  1. 向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法
    equals方法有可能调用,也有可能不调用。
    拿put(k,v)举例,什么时候equals不会调用?
    k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
    拿get(k)举例,什么时候equals不会调用?
    k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。

  2. 注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
    并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
    equals方法返回true表示两个对象相同,在同一个单向链表上比较。
    那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
    所以hashCode()方法的返回值也应该相同。

  3. 终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法

  4. 对于哈希表数据结构来说:
    如果o1和o2的hash值相同,一定是放到同一个单向链表上。
    当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

  5. HashMap集合key部分允许null吗?
    允许
    但是要注意:HashMap集合的key null值只能有一个。(有可能面试的时候遇到这样的问题)

Hashtable

  1. Hashtable的key可以为null吗?
    Hashtable的key和value都是不能为null的。
    HashMap集合的key和value都是可以为null的。
  2. Hashtable方法都带有synchronized:线程安全的。
    线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。
    Hashtable和HashMap一样,底层都是哈希表数据结构。
    Hashtable的初始化容量是11,默认加载因子是:0.75f
    Hashtable的扩容是:原容量 * 2 + 1

Properties

目前只需要掌握Properties属性类对象的相关方法即可。
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。

掌握两个方法:存和取
存:setProperty (调用map的put方法)
取:getProperty 通过键获得值

TreeSet

1、TreeSet集合底层实际上是一个TreeMap
2、TreeMap集合底层是一个二叉树。
3、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。
称为:可排序集合。

对自定义的类型来说,TreeSet可以排序吗?
  在没有指定排序规则之前,无法排序,谁大谁小没有说明
  运行时会出现java.lang.ClassCastException: Person cannot be cast to class java.lang.Comparable

出现这个异常的原因是:
  自定义的类(Person)没有实现java.lang.Comparable接口

因此,放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
并且实现compareTo方法。equals可以不写。
需要在compareTo方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较

compareTo方法的返回值很重要:
  返回0表示相同,value会覆盖。
  返回>0,会继续在右子树上找。
  返回<0,会继续在左子树上找。

最终的结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
  第一种:放在集合中的元素实现java.lang.Comparable接口。
  第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象Comparator。
  (传入比较器可以改变比较规则)

Comparable和Comparator怎么选择呢?
   当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
   如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
Comparator接口的设计符合OCP原则。

自平衡二叉树

1.TreeSet/TreeMap是自平衡二叉树,遵循左小右大原则存放
2.遍历二叉树的时候有三种方式:
  前序遍历:根左右
  中序遍历:左根右
  后序遍历:左右根
  //前中后说的是根的位置
3.TreeSet/TreeMap集合采用的是:中序遍历方式
  Iterator迭代器采用的是中序遍历方式

集合工具类Collections

1.将list集合变成线程安全的:Collections.synchronizedList(list);
2.排序:Collections.sort(list);
  对List集合中元素排序,需要保证List集合中元素实现了Comparable接口
  或者采用这种方法:Collections.sort(list集合, 比较器对象);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值