java基础(4)注解,集合,

news2024/9/21 9:37:43

注解

什么是注解(Annotation)?注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”

// this is a component:
@Resource("hello")
public class Hello {
    @Inject
    int n;

    @PostConstruct
    public void hello(@Param String name) {
        System.out.println(name);
    }

    @Override
    public String toString() {
        return "Hello";
    }
}

注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。

注解的作用

从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。
Java的注解可以分为三类:

  • 由编译器使用的注解,
例如:

@Override:让编译器检查该方法是否正确地实现了覆写;
@SuppressWarnings:告诉编译器忽略此处代码产生的警告

这类注解不会被编译进class文件中。

  • 第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。一般只被底层库使用,不必自己处理。
  • 第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)

定义一个注解时,还可以定义配置参数。配置参数可以包括:
所有基本类型; String; 枚举类型; 基本类型、String、Class以及枚举的数组。

public class Hello {
    @Check(min=0, max=100, value=55)
    public int n;

    @Check(value=99)
    public int p;

    @Check(99) // @Check(value=99)
    public int x;

    @Check
    public int y;
}

@Check就是一个注解。第一个@Check(min=0, max=100, value=55)明确定义了三个参数,第二个@Check(value=99)只定义了一个value参数,它实际上和@Check(99)是完全一样的。最后一个@Check表示所有参数都使用默认值。

定义注解

java 用@interface定义注解,然后添加参数、默认值:

public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

接着有一些可以用于注解的注解,成为元注解。如

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

@Target定义该注解能注解什么东西
类或接口:ElementType.TYPE;
字段:ElementType.FIELD;
方法:ElementType.METHOD;
构造方法:ElementType.CONSTRUCTOR;
方法参数:ElementType.PARAMETER。

@Retention定义该注解的生命周期,如上就是定义在runtime的时候运行,因为我们自己写的注解一般在runtime的时候执行。
仅编译期:RetentionPolicy.SOURCE;
仅class文件:RetentionPolicy.CLASS;
运行期:RetentionPolicy.RUNTIME。
其次还有@Repeatable定义该注解是否可以重复使用在同一个地方,@Inherited定义是否可以继承父类的注解。

处理注解

Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:

  • SOURCE类型的注解在编译期就被丢掉了;
  • CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
  • RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。
使用注解
// 定义一个注解
@Retention(RetentionPolicy.RUNTIME) // runtime时运行,会打入jvm
@Target(ElementType.FIELD) // 作用于字段
@interface Range {
    int min() default 0;

    int max() default 255;
}

class Person1 {
    @Range(min = 1, max = 20)
    public String name;
    @Range(max = 10)
    public String city;
}

定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。

void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
    // 遍历所有Field:
    for (Field field : person.getClass().getFields()) {
        // 获取Field定义的@Range:
        Range range = field.getAnnotation(Range.class);
        // 如果@Range存在:
        if (range != null) {
            // 获取Field的值:
            Object value = field.get(person);
            // 如果值是String:
            if (value instanceof String s) {
                // 判断值是否满足@Range的min/max:
                if (s.length() < range.min() || s.length() > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            }
        }
    }
}

集合

什么是集合(Collection)?集合就是“由若干个确定的元素所构成的整体”。
在Java中,如果一个Java对象可以在内部持有若干其他Java对象(注意是对象,而不是基础类型,比如Integer可以,但是int不行),并对外提供访问接口,我们把这种Java对象称为集合。很显然,Java的数组可以看作是一种集合:

String[] ss = new String[10]; // 可以持有10个String对象
ss[0] = "Hello"; // 可以放入String对象
String first = ss[0]; // 可以获取String对象

数组的限制,长度固定,数据只能通过索引获取。
我们需要各种不同类型的集合类来处理不同的数据,例如:

  • 可变大小的顺序链表;
  • 保证无重复元素的集合;
Collection

Java标准库自带的java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口
ava的java.util包主要提供了以下三种类型的集合:

  • List 一种有序列表的集合,例如,按索引排列的Student的List,如ArrayList, LinkedList
  • Set 一种保证没有重复元素的集合,例如,所有无重复名称的Student的Set
  • Map 一种通过键值(key-value)查找的映射表集合,例如,根据Student的name查找对应Student的Map

Java集合设计的特点

  • 实现了 接口和 实现类 相分离,如有序表的接口是List,具体的实现类有ArrayList,LinkedList等
  • 支持泛型 List list = new ArrayList<>(); // 只能放入String类型
  • 访问集合都是通过迭代器(Iterator)的形式访问的,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。(类似于js的实现Symbol.iterator,实现对象的迭代,外部无需知道他是怎么存储的。)
List

List是最基础的一种集合:它是一种有序列,数组和List非常相似。但是数组进行增删操作会非常麻烦。反观List
当我们需要增删操作的时候,可以使用ArrayList,

如 一个ArrayList拥有5个元素,实际数组大小为6(即有一个空位)

  • 当添加一个元素并指定索引如2到ArrayList时,ArrayList自动移动需要移动的元素,把2及后面的元素移动。
  • 然后,往内部指定索引的数组位置添加一个元素,然后把size加1
  • 此时继续添加元素的时候,数组已满,没有空闲位置的时候,ArrayList先创建一个更大的新数组,然后把旧数组的所有元素复制到新数组,紧接着用新数组取代旧数组。

可见,ArrayList把添加和删除的操作封装起来,让我们操作List类似于操作数组,却不用关心内部元素如何移动。
List < T >主要的接口方法
在末尾添加一个元素:boolean add(E e)
在指定索引添加一个元素:boolean add(int index, E e)
删除指定索引的元素:E remove(int index)
删除某个元素:boolean remove(Object e)
获取指定索引的元素:E get(int index)
获取链表大小(包含元素的个数):int size()

上述是通过数组实现的List,实际上,使用链表也能实现。
在LinkedList中,它的内部每个元素都指向下一个元素:

        ┌───┬───┐   ┌───┬───┐   ┌───┬───┐   ┌───┬───┐
HEAD ──>│ A │ ●─┼──>│ B │ ●─┼──>│ C │ ●─┼──>│ D │   │
        └───┴───┘   └───┴───┘   └───┴───┘   └───┴───┘

我们来比较一下ArrayList和LinkedList:

123ArrayListLinkedList
获取指定元素速度很快需要从头开始查找元素
添加元素到末尾速度很快速度很快
在指定位置添加/删除需要移动元素不需要移动元素
内存占用较大

使用

	   List<String> list = new ArrayList<>();
    
        list.add("apple");
        list.add("pear");
        List<String> list1 = new LinkedList<>();
        list1.add("test1");
        list1.add("test2");
        // Java 9 
        List<Integer> list2 = List.of(1, 2, 5);
        System.out.println(list);
        System.out.println(list1);

遍历List,使用for循环

 for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }

这种方法并不推荐
一是代码复杂,二是因为get(int)方法只有ArrayList的实现是高效的,换成LinkedList后,索引越大,访问速度越慢。
要始终坚持使用迭代器Iterator来访问List。Iterator本身也是一个对象,但它是由List的实例调用iterator()方法的时候创建的。

 for (Iterator<String> it = list.iterator();it.hasNext();) {
            String s = it.next();
            System.out.println(s);
            
        }

调用iterator获取一个iterator对象,跟js的迭代器很类似。it.hasNext()判断是否还有下一个元素,it.next()返回下一个元素
以上是一种固定写法,第二个参数需要搭配it.hasNext()返回一个boolen判断是否继续循环。
Java的for each循环本身就可以帮我们使用Iterator遍历

for (String it1 : list1) {
     System.out.println(it1);
  }

可以直接遍历获取值。
实际上,只要实现了Iterable接口的集合类都可以直接用for each循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把for each循环变成Iterator的调用原因就在于Iterable接口定义了一个Iterator<E> iterator()方法,强迫集合类必须返回一个Iterator实例。
有点像js中,只要实现了Symbol.iterator方法的对象,就可以使用for of进行遍历。因为Symbol.iteraotr方法返回了一个迭代器对象。

List和Array互相转换
  • 把List变为Array有三种方法,第一种是调用toArray()方法直接返回一个Object[]数组,不常用,因为类型可能会丢失
  • 第二种方式是给toArray(T[])传入一个类型相同的Array,List内部自动把元素复制到传入的Array中。这里的T和List< E>不一样,所以toArray可以传其他类型的数组,但不兼容会报错。
  List<String> list1 = new LinkedList<>();
        list1.add("test1");
        list1.add("test2");
 String[] test2 = list1.toArray(new String[2]);
        Integer[] test3 = list1.toArray(new Integer[2]); //会报错

传入的数组如果不够大,那么List内部会创建一个新的数组,大小一样。
如果传入的数组过大,那么多余的元素全部填充null。

Array to List
// JAVA 9 
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);

// JAVA 9 之前
  List<String> list3 = Arrays.asList(test2);

注意,这种返回的List不一定是ArrayList或者LinkedList,因为返回的List是只读的,在操作会报错。’

编写equals方法

List提供了contains方法,来判断当前list是否包含哪些内容。提供了indexOf(obj)方法来判断obj在当前list的位置

 List<String> list3 = new ArrayList<>();
        list3.add("S");
        list3.add("C");
 System.out.println(list3.contains(new String("S"))); // true
        System.out.println(list3.indexOf("S")); //0

这里contains我们传入了一个全新的String对象,但是结果还是为true,这是因为contains的实现是

public class ArrayList {
    Object[] elementData;
    public boolean contains(Object o) {
        for (int i = 0; i < elementData.length; i++) {
            if (o.equals(elementData[i])) {
                return true;
            }
        }
        return false;
    }
}

拿传入的对象,调用其equals方法,所以,要向正确的使用contains和indexOf,传入的对象就必须正确的实现equals方法。而String和Integer等对象,java已经帮我们实现了equals方法。

如何编写equals方法
  • 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
  • 用instanceof判断传入的待比较的Object是不是当前类型,如果是,继续比较,否则,返回false;
  • 对引用类型用Objects.equals()比较,对基本类型直接用==比较。
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public boolean equals(Object o) {
        if (o instanceof Person) {
            Person p = (Person) o;

            boolean nameEquals = false;
            if (this.name == null && p.name == null) {
                nameEquals = true;
            }
            if (this.name != null) {
                nameEquals = this.name.equals(p.name);
            }
            return nameEquals && this.age == p.age;
        }
        return false;

    }

// 使用Objects.equals()比较两个引用类型是否相等的目的是省去了判断null的麻烦。两个引用类型都是null时它们也是相等的。
    public boolean equals1(Object o) {
        if (o instanceof Person) {
            Person p = (Person) o;
            return Objects.equals(this.name, p.name) && this.age == p.age;
        }
        return false;

    }
}
Map

有个需求,在存放Students 集合 List中查找一个名为小明的,查看他的成绩。
这时候我们需要遍历List,然后判断到name微小明的,就取他的成绩。这样效率很低。
而Map这种key value的形式刚好可以满足。

  Map<String, Person> map = new HashMap<>();
        map.put("lin", new Person("lin", 20));

        Person Target = map.get("lin");

和List类似,Map也是一个接口,最常用的实现类是HashMap。如果只是想查询某个key是否存在,可以调用boolean containsKey(K key)方法。
其他与js的Map类似。
遍历

  Person Target = map.get("lin");
        for (String key : map.keySet()) {
            System.out.println(key);
        }
 for (Map.Entry<String, Person> entry : map.entrySet()) {
            String key = entry.getKey();
            Person value = entry.getValue();
            System.out.println(key);
            System.out.println(value);
        }

map.keySet()获取key,map.entrySet()获取key-value

  • Map不保证顺序
编写equals 和 hashCode

Map之所以能很快通过key获取value,是因为内部通过空间换时间的处理。
Map内部用很大的数组存放数据,然后通过特殊的计算,比如f函数 f(key) = index,传入同一个key,index总是相同的,然后通过index存放value,取数据的时候也是通过f(key) = index计算出index,然后通过数组[index]去获取数据。但这样有个问题,如果传入的两个key是不同的对象呢。

 Map<String, String> map2 = new HashMap<>();
        map2.put("2", "123");
        System.out.println(map2.get(new String("2"))); // 123

传入不同的String还是能拿到正确的值,因为跟List一样,key值得比对也是通过equals方法。
其次,通过key获取index是通过hashCode方法获取的,

正确使用Map必须保证:
  • 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true;
  • 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范:
  • 如果两个对象相等,则两个对象的hashCode()必须相等;
  • 如果两个对象不相等,则两个对象的hashCode()尽量不要相等
public class Person {
    String firstName;
    String lastName;
    int age;

    @Override
    int hashCode() {
        int h = 0;
        h = 31 * h + firstName.hashCode();
        h = 31 * h + lastName.hashCode();
        h = 31 * h + age;
        return h;
    }
}
// 借助Objects.hash
int hashCode() {
    return Objects.hash(firstName, lastName, age);
}

equals()用到的用于比较的每一个字段,都必须在hashCode()中用于计算;
equals()中没有使用到的字段,绝不可放在hashCode()中计算。

这里注意,两个对象的hashCode尽量不要想等,也就是有可能相等。

我们把不同的key具有相同的hashCode()的情况称之为哈希冲突。在冲突的时候,一种最简单的解决办法是用List存储hashCode()相同的key-value。显然,如果冲突的概率越大,这个List就越长,Map的get()方法效率就越低,这就是为什么要尽量满足条件二。
在这里插入图片描述

hashCode()方法编写得越好,HashMap工作的效率就越高。
计算hash code,相同hash code用一个List存放。

EnumMap

HsahMap通过空间换时间,计算index从数组直接拿数据。
如果key是Enum的话,可以用EnumMap ,在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。

public class Main {
    public static void main(String[] args) {
        Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
        map.put(DayOfWeek.MONDAY, "星期一");
        map.put(DayOfWeek.TUESDAY, "星期二");
        map.put(DayOfWeek.WEDNESDAY, "星期三");
        map.put(DayOfWeek.THURSDAY, "星期四");
        map.put(DayOfWeek.FRIDAY, "星期五");
        map.put(DayOfWeek.SATURDAY, "星期六");
        map.put(DayOfWeek.SUNDAY, "星期日");
        System.out.println(map);
        System.out.println(map.get(DayOfWeek.MONDAY));
    }
}
TreeMap

HashMap无序,还有一种Map,它在内部会对Key进行排序,这种Map就是SortedMap。注意到SortedMap是接口,它的实现类是TreeMap。

       ┌───┐
       │Map│
       └───┘
         ▲
    ┌────┴─────┐
    │          │
┌───────┐ ┌─────────┐
│HashMap│ │SortedMap│
└───────┘ └─────────┘
               ▲
               │
          ┌─────────┐
          │ TreeMap │
          └─────────┘

SortedMap保证遍历时以Key的顺序来进行排序。例如,放入的Key是"apple"、“pear”、“orange”,遍历的顺序一定是"apple"、“orange”、“pear”,因为String默认按字母排序

 public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<>();
        map.put("orange", 1);
        map.put("apple", 2);
        map.put("pear", 3);
        for (String key : map.keySet()) {
            System.out.println(key);
        }
        // apple, orange, pear
    }

TreeMap要求实现Comparable方法
没有的话需要在使用TreeMap的时候指定排序方法,

public class Main {
    public static void main(String[] args) {
        Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
            public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }
        });
        map.put(new Person("Tom"), 1);
        map.put(new Person("Bob"), 2);
        map.put(new Person("Lily"), 3);
        for (Person key : map.keySet()) {
            System.out.println(key);
        }
        // {Person: Bob}, {Person: Lily}, {Person: Tom}
        System.out.println(map.get(new Person("Bob"))); // 2
    }
}

Comparator接口要求实现一个比较方法,它负责比较传入的两个元素a和b,如果a<b,则返回负数,通常是-1,如果a==b,则返回0,如果a>b,则返回正数,通常是1。TreeMap内部根据比较结果对Key进行排序

  • SortedMap在遍历时严格按照Key的顺序遍历,最常用的实现类是TreeMap;
  • 作为SortedMap的Key必须实现Comparable接口,或者传入Comparator;
  • 要严格按照compare()规范实现比较逻辑,否则,TreeMap将不能正常工作。
Set

不重复的集合,且不需要value,类似于js的Set

   Set<String> set = new HashSet<>();
        System.out.println(set.add("abc")); // true
        System.out.println(set.add("xyz")); // true
        System.out.println(set.add("xyz")); // false,添加失败,因为元素已存在
         System.out.println(set.contains("xyz")); // true,元素存在
        System.out.println(set.contains("XYZ")); // false,元素不存在
        System.out.println(set.remove("hello")); // false,删除失败,因为元素不存在
         System.out.println(set.size()); // 2,一共两个元素

set.add, set.contains, set.remove, set.resize
Set实际上相当于只存储key、不存储value的Map。我们经常用Set用于去除重复元素。
因为放入Set的元素和Map的key类似,都要正确实现equals()和hashCode()方法,否则该元素无法正确地放入Set

HashSet其实是封装HashMap

public class HashSet<E> implements Set<E> {
    // 持有一个HashMap:
    private HashMap<E, Object> map = new HashMap<>();

    // 放入HashMap的value:
    private static final Object PRESENT = new Object();

    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }

    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    public boolean remove(Object o) {
        return map.remove(o) == PRESENT;
    }
}

在这里插入图片描述

HasHSet是无序的,而SortedSet是有序的,但他只是个接口,实现的类是TreeSet

public class Main {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("apple");
        set.add("banana");
        set.add("pear");
        set.add("orange");
        for (String s : set) {
            System.out.println(s);
        }
    }
}

使用TreeSet和使用TreeMap的要求一样,添加的元素必须正确实现Comparable接口,如果没有实现Comparable接口,那么创建TreeSet时必须传入一个Comparator对象。

Queue

队列,先进先出
它和List的区别在于,List可以在任意位置添加和删除元素,而Queue只有两个操作:

把元素添加到队列末尾;
从队列头部取出元素。
在这里插入图片描述
这些操作有两个方法,一个会抛出错误,一个返回false/null。

// 这是一个List:
List<String> list = new LinkedList<>();
// 这是一个Queue:
Queue<String> queue = new LinkedList<>();

LinkedList(链表)类实现了Queue接口和List接口

PriorityQueue

上述的Queue是先进先出,但是如果有优先级的处理,就像react中优先级高的渲染,就出现了优先级队列PriorityQueue,
PriorityQueue和Queue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。(react-sceduler的实现类似)

public class Main {
    public static void main(String[] args) {
        Queue<String> q = new PriorityQueue<>();
        // 添加3个元素到队列:
        q.offer("apple");
        q.offer("pear");
        q.offer("banana");
        // 按照元素顺序
        System.out.println(q.poll()); // apple
        System.out.println(q.poll()); // banana
        System.out.println(q.poll()); // pear
        System.out.println(q.poll()); // null,因为队列为空
    }
}

放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。
也可以在创建PriorityQeue的时候提供一个comparator对象来判断两个元素的顺序,跟TreeMap类似。

public class Main {
    public static void main(String[] args) {
        Queue<User> q = new PriorityQueue<>(new UserComparator());
        // 添加3个元素到队列:
        q.offer(new User("Bob", "A1"));
        q.offer(new User("Alice", "A2"));
        q.offer(new User("Boss", "V1"));
        System.out.println(q.poll()); // Boss/V1
        System.out.println(q.poll()); // Bob/A1
        System.out.println(q.poll()); // Alice/A2
        System.out.println(q.poll()); // null,因为队列为空
    }
}

class UserComparator implements Comparator<User> {
    public int compare(User u1, User u2) {
        if (u1.number.charAt(0) == u2.number.charAt(0)) {
            // 如果两人的号都是A开头或者都是V开头,比较号的大小:
            return u1.number.compareTo(u2.number);
        }
        if (u1.number.charAt(0) == 'V') {
            // u1的号码是V开头,优先级高:
            return -1;
        } else {
            return 1;
        }
    }
}

Deque双端队列

,允许两头都进,两头都出,这种队列叫双端队列

Java集合提供了接口Deque来实现一个双端队列,它的功能是:

既可以添加到队尾,也可以添加到队首;
既可以从队首获取,又可以从队尾获取。

在这里插入图片描述
Deque是一个接口,它的实现类有ArrayDequeLinkedList

Stack 先进后出

在这里插入图片描述
将十进制转为16进制


 public static void main(String[] args) {
        String hex = toHex(12500);
        System.out.println(hex);
        if (hex.equalsIgnoreCase("30D4")) {
            System.out.println("测试通过");
        } else {
            System.out.println("测试失败");
        }
    }

    static String toHex(int n) {
        Deque<String> dq = new LinkedList<>();
        int num = n % 16;
        int num1 = n / 16;
        while (num1 != 0) {
            dq.addFirst(Integer.toHexString(num));
            num = num1 % 16;
            num1 = num1 / 16;
        }
        dq.addFirst(Integer.toHexString(num));
        String result = "";
        String s = dq.pollFirst();

        System.out.println(s);
        while (s != null) {
            result += s;
            s = dq.pollFirst();
        }

        return result;
    }

循环求余数压入栈中,再循环取出。

Iterator

Java的集合类都可以使用for each循环,List、Set和Queue会迭代每个元素,Map会迭代每个key。因为他们都实现了Iterator对象
Java编译器并不知道如何遍历List。上述代码能够编译通过,只是因为编译器把for each循环通过Iterator改写为了普通的for循环:

for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
     String s = it.next();
     System.out.println(s);
}
for (String s : list) {
    System.out.println(s);
}

类似于js的Symbol.Iterator迭代器,只要实现了Symbol.iterator,就可以用for of循环。
实现一个集合,需要实现他的Iterator方法,并且返回一个Iterator对象

class ReverseList<T> implements Iterable<T> {

    private List<T> list = new ArrayList<>();

    public void add(T t) {
        list.add(t);
    }

    @Override
    public Iterator<T> iterator() {
        return new ReverseIterator(list.size());
    }

    class ReverseIterator implements Iterator<T> {
        int index;

        ReverseIterator(int index) {
            this.index = index;
        }

        @Override
        public boolean hasNext() {
            return index > 0;
        }

        @Override
        public T next() {
            index--;
            return ReverseList.this.list.get(index);
        }
    }
}

可以看到迭代器主要实现hasNext方法和next方法。js的Symbol.Iterator则是实现{done: ture, value: ‘’}来判断是否已经遍历结束。

Collections

Collections是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合。

 // 空集合
        List<String> list2 = Collections.emptyList(); // 返回不可变List
        // 单元素集合
        List<String> list3 = Collections.singletonList("apple"); // 返回不可变List
        List<String> list4 = Arrays.asList("1230", "456", "132"); // 返回不可变List

        // 排序
        Collections.sort(list4); // 排序回修改List元素的位置,所以需要传入可变List
        System.out.println(list4);
        Collections.shuffle(list4); // 打乱List的顺序
        System.out.println(list4);

        // 变为不可变集合 假设List4是可变List
        List<String> list5 = Collections.unmodifiableList(list4);
        // list5.add("orange"); // UnsupportedOperationException!
        list4.add("2222");
        list4 = null; // 转变后立马扔掉老的引用
        System.out.println(list5); // 老的List修改影响不可变List
小结
List
  • Java集合使用统一的Iterator遍历
  • 有序集合 List -> ArrayList(通过数组实现,) LinkedList(通过链表实现),可以用for each遍历。存放List的对象需要实现equals方法,便于调用List的contains和indexOf方法。
Map
  • key-value 映射表 -> Map , Map集合无序,可以通过for each遍历keySet(),也可以通过for each遍历entrySet(),直接获取key-value。最常用的Map就是HashMap

  • HashMap通过数组的形式存放,调用key.hashCode()获取索引,通过key.equals()判断是否相等,所以HashMap存放到对象需要实现hashCode和equals方法,而且equals方法返回true,则hashCode返回的索引一定相等,如果equals返回false,那么hashCode返回的值尽两不要相等。

    • 但是万一相等了呢,HashMap的数组存放的是List<Entry<string, Object>>,当不同的key的HashCode返回的索引相等的时候,会拿到一个List,再通过key.equlas去获取到对应的值。
  • 如果Map的key是enum类型,推荐使用EnumMap,既保证速度,也不浪费空间。使用EnumMap的时候,根据面向抽象编程的原则,应持有Map接口。

  • HashMap是无序的,但是Map下面还有一个SortedMap接口,TreeMap类实现了这个接口,他是有序的。

    • TreeMap怎么排序呢,通过存放的每个对象实现一个comparable接口,或者在定义TreeMap的时候,指定一个自定义排序算法
    •   Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
              public int compare(Person p1, Person p2) {
                  return p1.name.compareTo(p2.name);
              }
          });
      
    • Comparator接口要求实现一个比较方法,它负责比较传入的两个元素a和b,如果a<b,则返回负数,通常是-1,如果a==b,则返回0,如果a>b,则返回正数,通常是1。TreeMap内部根据比较结果对Key进行排序。
Set
  • 只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set。
  • 最常用的Set是HashSet,他是HashMap的封装
  • 此外HashSet是无序的,但同时,SrotedSet是有序的,他是一个接口,TreeSet实现了这个接口
  • 跟TreeMap一样,TreeSet也要求一个compator接口,来判断两个对象的顺序。
Queue 通过LinkedList类实现该接口
  • 队列先进先出,通过add()/offer()方法将元素添加到队尾
  • 通过remove/poll获取队首元素并挪去。
  • 通过element()/peek()获取队首元素但不去掉。
  • 前面的add/remove/eleemtn执行出错会报错,而offer/poll/peek只返回对应的false/null
  • PriorityQueue优先级队列,优先级队列需要实现Comparable接口,priorityQueue根据元素的优先顺序决定谁先出队。
  • PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。
Deque 双头队列 也是LinkedList实现
  • Deque其实也是继承于Queue的
  • addLast/offerLast addFirst/offerFirst 从队头/队尾插入
  • removeFirst/pollFirst removeLast/pollLast 从对头/队尾取出并删除
  • getFirst/peekFirst getLast/peekLast 从对头/队尾取出但不删除
Stack 栈,先进后厨
  • 利用Deque实现栈即可
  • 只调用对应的offerFirst/peekFirst/getFirst三个方法。
  • Stack作用很大,比如计算机调用方法通过执行栈维护等等
Iterator

Iterator是一种抽象的数据访问模型。使用Iterator模式进行迭代的好处有:

  • 对任何集合都采用同一种访问模型;
  • 调用者对集合内部结构一无所知;
  • 集合类返回的Iterator对象知道如何迭代。
  • Java提供了标准的迭代器模型,即集合类实现java.util.Iterable接口,返回java.util.Iterator实例。
Collections

Collections类提供了一组工具方法来方便使用集合类:

  • Collections.emptyList()创建空集合 返回不可变集合
  • Collections.singletonList(“test”)创建单个集合 返回不可变集合
  • Collections.unmodifiableList(可变集合),将可变集合变成不可变集合
  • Collections.sort()排序集合, Collections.shuffle() 将集合的顺序重新打乱(洗牌)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1481220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【嵌入式——QT】数值输入和显示组件

数值输入和显示组件 QSlider&#xff1a;滑动条&#xff0c;通过滑动来设置数值&#xff1b;QScrollBar&#xff1a;卷滚条&#xff0c;与QSlider类似&#xff0c;还可以用于卷滚区域&#xff1b;QProgressBar&#xff1a;进度条&#xff0c;一般用于显示任务进度&#xff0c;…

2024年3月2日 十二生肖 今日运势

小运播报&#xff1a;2024年3月2日&#xff0c;星期六&#xff0c;农历正月廿二 &#xff08;甲辰年丙寅月乙丑日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;鸡、蛇、鼠 需要注意&#xff1a;狗、马、羊 喜神方位&#xff1a;西北方 财神方位&#xff1a;东…

AI大模型提供商有哪些?

AI大模型提供商&#xff1a;引领人工智能创新浪潮 随着人工智能技术的迅猛发展&#xff0c;AI大模型成为了推动行业变革和创新的核心驱动力之一。作为AI领域的重要参与者&#xff0c;AI大模型提供商扮演着关键的角色。本文将围绕这一主题&#xff0c;介绍几家在AI大模型领域具…

SpringBoot3-数据访问

整合SSM场景 SpringBoot 整合 Spring、SpringMVC、MyBatis 进行数据访问场景开发 1. 创建SSM整合项目 <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency><groupId>org.mybatis.spring.boot&…

论文阅读:2020GhostNet华为轻量化网络

创新&#xff1a;&#xff08;1&#xff09;对卷积进行改进&#xff08;2&#xff09;加残差连接 1、Ghost Module 1、利用1x1卷积获得输入特征的必要特征浓缩。利用1x1卷积对我们输入进来的特征图进行跨通道的特征提取&#xff0c;进行通道的压缩&#xff0c;获得一个特征浓…

基于springboot+html实现的衣物捐赠平台

一、系统架构 前端&#xff1a;html | layui | jquery | css 后端&#xff1a;springboot | thymeleaf | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、代码及数据库 三、功能介绍 01. 登录页 02. 注册 03. web页-首页 04. web页-捐赠衣服 05. web页-论坛交流…

echarts鼠标向右/向左绘制实现放大/还原

echarts toolbox 的datazoom提供了绘制放大的功能&#xff0c;但通过鼠标绘制只能进行放大 应需求放大与还原都通过鼠标行为实现&#xff0c;增加从右往左绘制时还原放大结果 demo 结果 重写datazoom的原型方法实现绘制事件的拦截 const comp myChart._model.getComponent(to…

存储过程基本了解

文章目录 介绍存储过程示例1. 目的2. 输入参数3. 输出参数4. 执行逻辑5. 返回值6. 示例用法7. 注意事项 存储过程的关键字有哪些简单实操 介绍 存储过程是一组预编译的SQL语句&#xff0c;以及流程控制语句&#xff0c;封装在数据库服务器中并可以被重复调用。它们可以接收参数…

仿牛客网项目---私信列表和发送列表功能的实现

这篇文章我们来讲一下我的这个项目的另外一个功能&#xff1a;私信列表和发送列表功能。 先来设计DAO层。 Mapper public interface MessageMapper {// 查询当前用户的会话列表,针对每个会话只返回一条最新的私信.List<Message> selectConversations(int userId, int of…

WIN10 无密码自动登录

1、家里重装了一下WIN10系统&#xff0c;第一次登陆居然用了微软网站账号&#xff0c;结果密码忘记了&#xff0c;后面只能用PIN码登陆系统。 2、需要登录微软的网站修改密码&#xff1a; Microsoft account | Sign In or Create Your Account Today – Microsoft 3、在运行…

精品ssm的社区团购系统购物商城小程序

《[含文档PPT源码等]精品基于ssm的社区团购系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xff1a;HTML5,CSS3、Jav…

SpringBoot 整合WebService

文章目录 WebService1.简单介绍WebService1.1. 类型1.2. 架构1.3. 主要特点1.4. 使用场景1.5. Web服务标准和技术 2.案例-WebServiceDemo2.1.引入配置文件2.2.创建接口2.3.创建接口实现类2.4.创建WebService配置类2.5.测试 WebService Web服务&#xff08;Web Services&#xf…

kotlin安卓开发教程视频,2024年Android开发陷入饱和

Android基础 1、什么是ANR 如何避免它&#xff1f; 如果耗时操作需要让用户等待&#xff0c;那么可以在界面上显示进度条。 2、View的绘制流程&#xff1b;自定义View如何考虑机型适配&#xff1b;自定义View的事件 3、分发机制&#xff1b;View和ViewGroup分别有哪些事件分…

蓝桥杯备战刷题three(自用)

1.合法日期 #include <iostream> #include <map> #include <string> using namespace std; int main() {map<string,int>mp;int days[13]{0,31,28,31,30,31,30,31,31,30,31,30,31};for(int i1;i<12;i){for(int j1;j<days[i];j){string sto_strin…

在线ai写作,让你随时随地创作优质内容

如今的ai技术已经渗透到我们生活的方方面面。其中&#xff0c;AI写作成为了一个备受关注的领域。如今&#xff0c;我们可以利用在线ai写作在任何时间、任何地点创作出优质的内容。 传统的写作过程需要大量的时间和精力。从构思到写作再到修改&#xff0c;每一个环节都需要我们投…

C# 解决uploadify插件上传时造成session丢失问题

出现的问题&#xff1a; 在应用uploadify插件实现上传图片时&#xff0c;报了HTTP Error&#xff0c;经过在Network查看上传方法报错码是302&#xff0c;那这里就可以知道问题是什么了&#xff0c;HTTP 302是请求被重定向&#xff0c;如果你的uploadify处理上传方法有session验…

ABAP - OOALV 用户交互事件

当用户要根据ALV进行某些功能操作比如打印表单时&#xff0c;OOALV标准按钮无法满足用户需求的时候&#xff0c;就要用到自定义按钮来实现了。思路&#xff1a;在OOALV增加一个自定义按钮&#xff0c;类CL_GUI_ALV_GRID提供了内置事件toolbar来完成&#xff0c;通过自定义按钮的…

Apache Flink连载(三十五):Flink基于Kubernetes部署(5)-Kubernetes 集群搭建-1

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 ​编辑

HTTPS是什么,详解它的加密过程

目录 1.前言 2.两种加密解密方式 2.1对称加密 2.2非对称加密 3.HTTPS的加密过程 3.1针对明文的对称加密 3.2针对密钥的非对称加密 3.3证书的作用 1.前言 我们知道HTTP协议是超文本传输协议,它被广泛的应用在客户端服务器上,用来传输文字,图片,视频,js,html等.但是这种传…

MyBatis 学习(五)之 高级映射

目录 1 association 和 collection 介绍 2 案例分析 3 一对一关联和一对多关联 4 参考文档 1 association 和 collection 介绍 在之前的 SQL 映射文件中提及了 resultMap 元素的 association 和 collection 标签&#xff0c;这两个标签是用来关联查询的&#xff0c;它们的属…