Iterator迭代器、增强for、forEach方法、泛型、数据结构、对象哈希值、哈希表

Iterator迭代器

迭代 ==》遍历

什么是迭代器;

  1. 一个用来遍历集合的对象,该对象实现了Iterator接口。
  2. 对 collection 进行遍历的迭代器。
  • 不能遍历数组

如何获得迭代器对象:

通过集合对象调用该方法:

  • Iterator<E> iterator()        获得迭代器对象。

例如:

ArrayList<String> list = new ArrayList<>();
Iterator<String> it = list.iterator();

Iterator接口常用方法:

  1. boolean hasNext()  :  判断是否有下一个元素,有返回true,否则false
  2. E next()  :   先将指针下移指向下一个元素,并将当前指针指向位置的元素作为返回值返回。
  3. void remove  :  删除当前指针元素。

Iterator示例代码:

public class IteratorDemo02 {
    public static void main(String[] args){
        // 创建集合对象
        ArrayList<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        // 使用普通for循环遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
        // 获得迭代器对象
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
}

如果集合中没有元素可迭代了,仍然调用next方法获得元素,则会抛出异常。

java.util.No such Element Exception:没有元素异常。

迭代器基本使用:

使用循环改进。

迭代器的好处:

  1. 屏蔽了众多集合的内部实现,对外提供统一的遍历方式。
  2. 所有的单列集合都可以使用迭代器进行遍历。

迭代器使用的注意事项:

  1. hasNext方法仅仅是判断是否有下一个元素,不会移动指针位置。
  2. hasNext方法和next方法必须成对出现,调用一次haxNext就对应一次next方法。
  3. 使用迭代器遍历集合时不能对集合进行增删操作,否则会抛出异常。

java.util.ConcurrentModificationException: 并发修改异常

在使用迭代器遍历集合的过程中对集合元素进行了增删操作,则会抛出该异常。

注意:

迭代器 只能用一次,若要再遍历一次,则需要再创建一个迭代器。

增强for(foreach)

概述:

  1. JDK1.5新特性。
  2. 专门用来遍历集合和数组。
  • 数组和单列集合可以直接遍历,双列集合不可以直接遍历。

本质:

迭代器

  • 所以里面不能增删。

格式:

for(数据类型  变量名: 数组名或集合名){
        // 循环体
    }

快捷键:

iter

forEach

用forEach方法遍历集合:

1)单列集合

  • List.forEach(new Consum<T>)

​​​​​​​2)双列集合

  • Map.forEach(new BiConsumer<T,U>)

本质:

Consum与BiConsumer都是接口,实现都是重写他们里面的accept抽象方法。

Consum与BiConsumer的accept方法:

public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}
public interface BiConsumer<T, U> {

    /**
     * Performs this operation on the given arguments.
     *
     * @param t the first input argument
     * @param u the second input argument
     */
    void accept(T t, U u);
}

示例代码:

public class LambdaDemo06 {
    public static void main(String[] args){
        // 创建集合对象
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("凤姐",20,100));
        list.add(new Student("如花",22,90));
        list.add(new Student("芙蓉姐姐",18,95));
        list.add(new Student("小苍",30,70));

        // 使用匿名内部类遍历集合
        list.forEach(new Consumer<Student>() {
            @Override
            public void accept(Student student) {
                System.out.println(student);
            }
        });
        System.out.println("-----------------");
        // 使用lambda标准格式遍历集合
        // void accept(T t);
        list.forEach((Student student) ->{
            System.out.println(student);
        });
        System.out.println("------------------");
        // 使用lambda省略格式遍历集合
        // void accept(T t);
        Consumer<Student> c  = student -> System.out.println(student);
        list.forEach(c);


        System.out.println("------遍历Set集合------------");
        Set<Integer> set = new HashSet<>();
        Collections.addAll(set, 2,31,23,13,13,1,3);
        set.forEach(num-> System.out.println(num));

        System.out.println("------遍历Map集合------------");
        Map<String,String> map = new HashMap<>();
        map.put("name", "jack");
        map.put("gender", "男");
        // void accept(T t, U u);
        map.forEach((key,value)-> System.out.println(key+"="+value));
    }
}

泛型

什么是泛型:

数据类型参数化。

伪泛型:

泛型只会存在于编译期,编译完成之后就会被擦除。

  • java中都是伪泛型

泛型概述:

  1. JDK1.5新特性。
  2. 泛型可以使用在方法上,类上,接口上。
  3. 泛型变量可以理解为是某种数据类型的占位符。
  4. 泛型变量还可以理解为是某种数据类型的变量。
  5. 泛型变量的命名规则:只要是合法的标识符就可以,一般使用一个大写字母表示
  • ​​​​​​​常用的泛型变量名有:T type E element K key V value

泛型在集合中的使用

泛型在集合中的使用:

  1. 创建集合同时指定集合存储的数据类型。
  2. 指定数据类型时,要么指定左边,要么两边都执行相同的数据类型。
  3. 在JDK1.7之前,必须两边都要指定并且要相同的数据类型。
  4. 在JDK1.7之后,指定左边即可。
  5. 在泛型中没有多态的概念。

泛型在集合中使用的好处:

  1. 将运行时错误转换为编译期错误,增强了集合的安全性。
  2. 省去了数据类型强制转换的麻烦。

泛型方法

引入:

定义一个方法,方法可以接受任意类型的数据。

概念:

在定义方法时定义了泛型变量的方法就是泛型方法。

定义格式:

修饰符 <T> 返回值类型 方法名(参数列表){}

注意事项:

  1. 泛型变量的具体数据类型是由调用者调用方法时传参决定。
  2. 泛型变量的具体数据类型不能是基本数据类型,如果要使用基本数据类型则需要使用对应的包装类类型。
  • ​​​​​​​包装类类型.valueof(基本数据类型  变量名)

自己的总结:

  • 如果参数类型跟返回类型是一样的,则返回类型可以写T。
  • 如果参数类型跟返回类型不一样,则必须要写所需要的具体返回值类型。

泛型方法示例代码:

public class Demo01 {
    public static void main(String[] args){
        Integer in =  test(123);
        String str = test("abc");
        Student stu = test(new Student());
        double d = test(0.6);//自动拆箱
        int age = 10;//自动拆箱
        System.out.println(age);
    }
//泛型方法
public static <T> T test(T param){
        T a = null;
        return a;
    }
}

泛型类

概念:

在定义类的同时定义了泛型变量的类。

定义格式:

class 类名<T>{
        // 在该类中可以将泛型变量T当成一种数据类型使用。
    }

注意事项:

  1. 泛型类泛型变量的具体数据类型是在创建该类对象时由创建者指定。
  2. 如果创建泛型类对象时没有指定泛型变量的具体数据类型,则默认是Object。
  3. 静态方法不能使用类上定义的泛型变量,如果该方法中要使用泛型变量,则需要将该定义为泛型方法。

使用场景:

当类中很多方法都使用泛型时,则将类定义为泛型。

泛型类示例代码:

public class MyArrays<T>{
    /*
        方法参数接收一个任意类型的数组
            ‐ 一个方法的功能是将数组的元素反转.
     */
    public void reverse(T[] arr){
        for (int i = 0,j = arr.length ‐ 1; i < j ; i++,j‐‐) {
            T temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    public static <T> void reverse02(T[] arr){
        for (int i = 0,j = arr.length ‐ 1; i < j ; i++,j‐‐) {
            T temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
}

泛型接口

分层开发:

  • 表现层
  • 业务层
  • 数据访问层:直接和数据库打交道,对数据库直接增删改查操作。

泛型接口的概念:

在定义接口的同时定义了泛型变量的接口。

泛型接口的定义格式:

interface 接口名<T>{
        // 可以将泛型变量T当成一种数据类型使用
    }

泛型接口的实现方式:

方式1:

实现接口的同时指定泛型变量的具体数据类型。(不够灵活)

  • 例:
  • public interface Dao<T>{}
  • public class ProductDao implements Dao<Product>{}
  • ProductDao productDao = new ProductDao();

方式2:

实现接口的时不指定泛型变量的具体数据类型,那么此时需要将该实现类定义为泛型类,由使用者创建实现类对象时指定泛型变量的数据类型。(推荐使用)

  • 例:
  • public interface Dao<T>{}
  • public class BaseDao<T> implements Dao<T> {}
  • BaseDao<Product> baseDao02 = new BaseDao<>();

泛型上下限

泛型通配符:

  1.     ? : 泛型通配符,可以匹配任意类型的数据。
  2.     ? 一般不会单独使用,一般会结合泛型的上下限使用。
  3.     ? 不能用来定义泛型方法,泛型类,泛型接口。
  4.     ? 不能在方法体中当成一种数据类型使用、

泛型上限:

  • ? extends Number:可以接收Number或Number子类类型的数据。

​​​​​​​泛型下限:

  • ? super Integer : 可以接收Integer或Integer父类类型的数据。
  • 这里的 ? 如果换成 T 或者其他字母(泛型)都可以。

数据结构

java中常见的数据结构:

  1. 队列
  2. 数组
  3. 链表
  4. 红黑树

特点:

先进后出(First In Last Out)简称:FILO

结构图:

队列

特点:

  1. 先进先出(First In First Out)简称:FIFO
  2. 是受限的线性表,入口、出口各一侧。

结构图:

数组

特点:

  1. 增删慢:每次增删元素时需要创建新的数组,需要复制旧数组的元素到新数组中。
  2. 查询快:可以根据索引查询指定的元素。

结构图:

链表

类型:

  1. 单链表
  2. 双链表

单链表结构图:

双链表结构图:

特点:

  1. 增删快:每次增删元素时不需要移动元素位置,只需要修改上一个元素记住下一个元素的地址值。
  2. 查询慢:每次根据索引查询元素时都需要从链表头或链表尾部开始遍历查询。
  • ​​​​​​​多个结点之间,通过地址进行连接。

增加结点结构图:

删除结点结构图:

红黑树(了解)

概述:

红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。

二叉树结构图:

对象哈希值

什么是对象哈希值:

  1. 哈希值就是一个十进制的整数,每一个对象都会有对应的哈希值。
  2. 哈希值默认是对象在内存中的地址值。

如何获得哈希值:

通过对象调用hashCode方法获得。

哈希值的作用:

哈希值是对象存储到哈希表的重要依据。

字符哈希值小结:

  1. 字符串内容相同,哈希值一定相同。
  2. 字符串内容不一样,哈希值也可能相同。

哈希表

什么是哈希表:

  1. 在JDK1.8之前,哈希表是数组+链表
  2. 在JDK1.8之后,哈希表是数组+链表+红黑树。当链表长度大于等于8时,将链表转换为红黑树,目的为了提高查询
    效率。

哈希表存储元素,底层依赖元素的:

hashCode 与 equals 方法。

  • 所以当存储自定义对象时,如果希望两个对象成员变量值都相同时只存储一个,则需要在自定义类中重写 hashCode 与 equals 方法。

哈希表存储过程

先创建一个容量为16的数组。

  • 每存入一个新的元素都要走以下五步

​​​​​​​1)调用对象的hashCode()方法,获得要存储元素的哈希值。

2)将哈希值与表的长度(即数组的长度)进行求余运算得到一个整数值,该值就是新元素要存放的位置(即是索引值)。

  • 如果索引值对应的位置上没有存储任何元素,则直接将元素存储到该位置上。 
  • 如果索引值对应的位置上已经存储了元素,则执行第3步。 

​​​​​​​3)遍历该位置上的所有旧元素,依次比较每个旧元素的哈希值和新元素的哈希值是否相同。

  • 如果有哈希值相同的旧元素,则执行第4步。 
  • 如果没有哈希值相同的旧元素,则执行第5步。

​​​​​​​4)比较新元素和旧元素的地址是否相同。

  • 如果地址值相同则用新的元素替换老的元素。停止比较。
  • 如果地址值不同,则新元素调用equals方法与旧元素比较内容是否相同。 
  1. ​​​​​​​如果返回true,用新的元素替换老的元素,停止比较。
  2. 如果返回false,则回到第3步继续遍历下一个旧元素。 

5)说明没有重复,则将新元素存放到该位置上并让新元素记住之前该位置的元素。​​​​​​​

哈希表的扩容:

加载因子是0.75,说明哈希表里面的数组有超过4分之3的空格有元素,就会自动扩容,每一次扩大为原来的1.5倍。

猜你喜欢

转载自blog.csdn.net/weixin_41630924/article/details/81406936