首页 > 编程语言 >24 | JAVA集合之List(实际为接口)

24 | JAVA集合之List(实际为接口)

时间:2022-09-02 08:45:26浏览次数:51  
标签:24 JAVA Iterator list List equals null public

使用 List

在集合类中,List是最基础的一种集合:它是一种有序列表。

List 下的两种具体实现 ArrayListLinkedList ,本别对应数组和链表。

List 的接口

  • List<E>接口
    • 在末尾添加一个元素: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允许添加重复元素,允许添加null(这在 C++中 确实没有,不过将引用理解成指针也可以有
public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();  //每次都 new??
        list.add("apple"); // size=1
        list.add(null); // size=2
        list.add("pear"); // size=3
        String second = list.get(1); // null
        System.out.println(second);
    }
}
  • 通过List接口提供的of()方法,根据给定元素快速创建List ,new 进行了封装
List<Integer> list = List.of(1, 2, 5);

但是List.of()方法不接受null值,如果传入null,会抛出NullPointerException异常。

List 的遍历

  • 遍历 List
    • 利用下标和get方法,不推荐
    • 使用迭代器Iterator来访问ListIterator本身也是一个对象,但它是由List的实例调用iterator()方法的时候创建的。Iterator对象知道如何遍历一个List,并且不同的List类型,返回的Iterator对象实现也是不同的,但总是具有最高的访问效率。
import java.util.Iterator;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.println(s);
        }
    }
}

通过Iterator遍历List永远是最高效的方式。并且,由于Iterator遍历是如此常用,所以,Java的for each循环本身就可以帮我们使用Iterator遍历。把上面的代码再改写如下:

import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (String s : list) {
            System.out.println(s);
        }
    }
}

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

List 和 Array 的互相转换

  • List和Array转换

    • 第一种是调用toArray()方法直接返回一个Object[]数组
    import java.util.List;
    public class Main {
        public static void main(String[] args) {
            List<String> list = List.of("apple", "pear", "banana");
            Object[] array = list.toArray();
            for (Object s : array) {
                System.out.println(s);
            }
        }
    }
    
    • 第二种方式是给toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array
    import java.util.List;
    public class Main {
        public static void main(String[] args) {
            List<Integer> list = List.of(12, 34, 56);
            Integer[] array = list.toArray(new Integer[3]);
            for (Integer n : array) {
                System.out.println(n);
            }
        }
    }
    

    注意到这个toArray(T[])方法的泛型参数<T>并不是List接口定义的泛型参数<E>,所以,我们实际上可以传入其他类型的数组,例如我们传入Number类型的数组,返回的仍然是Number类型:

    import java.util.List;
    public class Main {
        public static void main(String[] args) {
            List<Integer> list = List.of(12, 34, 56);
            Number[] array = list.toArray(new Number[3]);
            for (Number n : array) {
                System.out.println(n);
            }
        }
    }
    

    如果类型不匹配那么就抛出异常!

    如果传入的数组大小不一致,不够大就给你创建一个够大的,如果过大就填null。

    实际上,最常用的是传入一个“恰好”大小的数组:

    Integer[] array = list.toArray(new Integer[list.size()]);
    
  • Array 转换成 List,通过List.of(T...)方法最简单

    • Integer[] array = { 1, 2, 3 };
      List<Integer> list = List.of(array);
      

      返回的List不一定就是ArrayList或者LinkedList,因为List只是一个接口,如果我们调用List.of(),它返回的是一个只读List.对只读List调用add()remove()方法会抛出UnsupportedOperationException

      import java.util.List;
      public class Main {
          public static void main(String[] args) {
              List<Integer> list = List.of(12, 34, 56);
              list.add(999); // UnsupportedOperationException
          }
      }
      

List 方法 contains() 和 indexOf() 的依赖函数 equals 方法

List内部并不是通过==判断两个元素是否相等,而是使用equals()方法判断两个元素是否相等,例如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

如何正确编写equals()方法?equals()方法要求我们必须满足以下条件:

  • 自反性(Reflexive):对于非nullx来说,x.equals(x)必须返回true
  • 对称性(Symmetric):对于非nullxy来说,如果x.equals(y)true,则y.equals(x)也必须为true
  • 传递性(Transitive):对于非nullxyz来说,如果x.equals(y)truey.equals(z)也为true,那么x.equals(z)也必须为true
  • 一致性(Consistent):对于非nullxy来说,只要xy状态不变,则x.equals(y)总是一致地返回true或者false
  • null的比较:即x.equals(null)永远返回false

上述规则看上去似乎非常复杂,但其实代码实现equals()方法是很简单的。比如

public class Person {
    public String name;
    public int age;
    public boolean equals(Object o) {
    if (o instanceof Person) {
        Person p = (Person) o;
        return this.name.equals(p.name) && this.age == p.age;
    }
    return false;
}
}

基本功:对于引用字段比较,我们使用equals(),对于基本类型字段的比较,我们使用==

如果this.namenull,那么equals()方法会报错,因此,需要继续改写如下:

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;
}

如果Person有好几个引用类型的字段,上面的写法就太复杂了。要简化引用类型的比较,我们使用Objects.equals()静态方法:

public boolean equals(Object o) {
    if (o instanceof Person) {
        Person p = (Person) o;
        return Objects.equals(this.name, p.name) && this.age == p.age;
    }
    return false;
}

因此,我们总结一下equals()方法的正确编写方法:

  1. 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
  2. instanceof判断传入的待比较的Object是不是当前类型,如果是,继续比较,否则,返回false
  3. 对引用类型用Objects.equals()比较,对基本类型直接用==比较。

使用Objects.equals()比较两个引用类型是否相等的目的是省去了判断null的麻烦。两个引用类型都是null时它们也是相等的。

如果不调用Listcontains()indexOf()这些方法,那么放入的元素就不需要实现equals()方法。

标签:24,JAVA,Iterator,list,List,equals,null,public
From: https://www.cnblogs.com/mmxingye/p/16648525.html

相关文章

  • 25 | JAVA集合Map(实际为接口)
    使用MapMap这种键值(key-value)映射表的数据结构,作用就是能高效通过key快速查找value(元素)。Map也是一个接口,最常用的实现类是HashMap。containsKey(Kkey)方法put()ge......
  • 26 | JAVA集合EnumMap(Map的针对枚举类的一种特殊实现,接口仍为Map)
    EnumMap如果作为key的对象是enum类型,那么,还可以使用Java集合库提供的一种EnumMap,它在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,......
  • 17 | JAVA反射之调用方法
    反射调用方法获得Method对象我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method:Method......
  • Java开发学习(二十九)----Maven依赖传递、可选依赖、排除依赖解析
    现在的项目一般是拆分成一个个独立的模块,当在其他项目中想要使用独立出来的这些模块,只需要在其pom.xml使用<dependency>标签来进行jar包的引入即可。<dependency>其实就是......
  • 19 | JAVA反射之获取继承关系
    反射获取继承关系获取父类的Class有了Class实例,我们还可以获取它的父类的Class://reflectionpublicclassMain{publicstaticvoidmain(String[]args)thro......
  • 18 | JAVA反射之调用构造方法
    反射调用构造方法调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调......
  • 01 | JAVA入门基础
    基本数据类型基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:整数类型:byte,short,int,long浮点数类型:float,double字符类型:char布尔类型:bool......
  • 02 | JAVA内部类
    java内部类Java的内部类分为好几种,通常情况用得不多,但也需要了解它们是如何使用的。1.InnerClass如果一个类定义在另一个类的内部,这个类就是InnerClass:它与普通类有......
  • 04 | JAVA模块
    模块jar只是用于存放class的容器,它并不关心class之间的依赖。从Java9开始引入的模块,主要是为了解决“依赖”这个问题。如果a.jar必须依赖另一个b.jar才能运行,那我们应该......
  • 05 | JAVA字符串
    字符串Strings1="Hello!";实际上字符串在String内部是通过一个char[]数组表示的,因此,按下面的写法也是可以的:Strings2=newString(newchar[]{'H','e','l','l......