Java学习笔记之集合(上)

2021/4/12 20:27:26

本文主要是介绍Java学习笔记之集合(上),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录
  • 集合概述
    • 为什么需要集合
    • Java中的集合结构
  • Conection接口
    • List 接口
      • List接口介绍
        • List接口的实现类
          • ArrayList
          • Vector
          • LinkedList
    • Set接口
      • HashSet
      • TreeSet
        • 关于重复元素的说明

集合概述

为什么需要集合

  • 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象进行操作,就需要对对象进行存储
  • 虽然我们可以使用 Array 存储对象,但这种存储对象的方式也有一些弊端。
    1. 数组初始化以后,长度就不可变了,不便于扩展
    2. 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高
    3. 同时无法直接获取存储元素的个数
    4. 数组存储的数据是有序的、可以重复的。即存储数据的特点单一

Java中的集合结构

image

Conection接口

Collection 接口用于存储单列数据的最大操作接口,定义了存取一组对象的方法的集合。
该接口中定义了以下方法:

No. 方法名称 描述
1 public boolean add(E e) 向集合中插入一个元素
2 public boolean addAll(Collection c) 向集合中插入一组元素
3 public void clear() 清空集合中的元素
4 public boolean contains(Object o) 查找一个元素是否存在
5 public boolean containsAll(Collection c) 查找一组元素是否存在
6 public boolean isEmpty() 判断集合是否为空
7 public Iterator iterator() 实例化该集合对应的迭代器
8 public boolean remove(Object o) 从集合中删除一个对象
9 boolean removeAll(Collection c) 从集合中删除一组对象
10 boolean retainAll(Collection c) 判断是否没有指定的集合
11 public int size() 求出集合中元素的个数
12 public Object[] toArray() 以对象数组的形式返回集合中的全部内容
13 T[] toArray(T[] a) 指定操作的泛型类型,并把内容返回
14 public boolean equals(Object o) 从 Object 类中覆写而来
15 public int hashCode() 从 Object 类中覆写而来

注意:

JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如: Set和List)实现
在 Java5 之前, Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理; 从 JDK 5.0 增加了泛型以后, Java 集合可以记住容器中对象的数据类型

List 接口

了解Collection接口之后,我们先来看看Collection接口中的子类List

List接口介绍

List 见名知意,即列表。那什么是列表呢?列表就相当于以前学习的数组,和数组不同的是列表被封装成了一个对象,增删改查操作依赖于其中的方法实现,同时列表对象内部自带动态增容,使我们无需关心其容量。
其中在Collection接口的基础上新增和重载了以下方法:

No. 方法名称 描述
1 public void add(int index,E element) 在指定位置处增加元素
2 boolean addAll(int index,Collection c) 在指定位置处增加一组元素
3 public E get(int index) 根据索引位置取出每一个元素
4 public int indexOf(Object o) 根据对象查找指定的位置,找不到返回-1
5 public int lastIndexOf(Object o) 从后面向前查找位置,找不到返回-1
6 public ListIterator listIterator() 返回该List对象对应的迭代器
7 public ListIterator listIterator(int index) 返回从指定位置的List对象对应的迭代器
8 public E remove(int index) 删除指定位置的内容
9 public E set(int index,E element) 修改指定位置的内容
10 List subList(int fromIndex,int toIndex) 返回子集合

List接口的实现类

ArrayList

ArrayList是对象引用的一个”变长”数组
基本操作示例:

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>(); // 实例化List对象,并指定泛型类型
		list.add("hello"); // 增加内容,此方法从Collection接口继承而来
		list.add(0, "world");// 增加内容,此方法是List接口单独定义的
		System.out.println("集合中的内容是:" + list);// 打印list对象调用toString()方法
		list.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
		list.remove("world");// 删除指定的对象
		System.out.print("集合中删除后的内容是:" + list);
		for (int x = 0; x < list.size(); x++) { // size()方法从Collection接口继承而来
			System.out.print(list.get(x) + "、"); // 此方法是List接口单独定义的
		}
	}
}

控制台输出如下:
image

Vector

Vector 是一个古老的集合, JDK1.0就有了,大多数操作与ArrayList相同。

  • 示例:将ArrayList示例中的ArrayList改为Vector即可
  • ArrayList的区别如下:
    • Vector是线程安全的,性能较低;ArrayList是非线程安全的,性能较高
    • ArrayList支持 Iterator、ListIterator 输出;Vector除了支持 Iterator、ListIterator 输出,还支持Enumeration 输出
LinkedList

ArrayList大部分操作相同,但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:

No. 方法名称 描述
1 public boolean add(E e) 增加元素,如果有容量限制,并且已满,则抛出异常
2 public E element() 取得元素,但是不删除当前元素,如果队列为空则抛出异常
3 boolean offer(E e) 添加,如果有容量限制,并且已满,只是无法添加,但不抛出异常,返回 false
4 E peek() 取得头元素,但是不删除,如果队列为空,则返回null
5 E poll() 取得头元素,但是删除, 如果队列为空,则返回 null
6 E remove() 删除当前元素, 如果对列为空则抛出异常
  • 继承List示例将ArrayList示例中的ArrayList改为LinkedList即可
  • 继承队列Queue示例:
public class LinkedListDemo {
	public static void main(String[] args) {
		Queue<String> queue = new LinkedList<String>();
        	queue.add("A");
        	queue.add("B");
        	queue.add("C");
        	String element = queue.element();
        	System.out.println("element:" + element);
        	String peek = queue.peek();
        	System.out.println("peek:" + peek);
		//把queue的大小先取出来,否则每循环一次,移除一个元素,就少一个元素,那么queue.size()在变小,就不能循环queue.size()次了。
        	int len=queue.size();
        	for (int x = 0; x <len; x++) {
            		System.out.println(queue.poll());
        	}
        	System.out.println(queue);
	}
}

控制台输出:
image

  • ArrayList的区别如下:
    1. ArrayList底层是使用 Array 数组实现,LinkedList底层是使用链表实现
    2. 由于LinkedList底层使用链表实现,对于频繁的插入或删除元素的操作,效率较高

在这三个List实现类中,使用占比为:ArrayList(95%)、Vector(4%)、LinkedList(1%)
尽管 ArrayList 是最常用的,但我们应该根据业务场景作出选择
在各种list中,在不要求同步的情况下,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList; Vector总是比ArrayList慢,所以尽量避免使用

Set接口

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。
Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义的 get(int index)方法,所以无法使用循环进行输出。
该接口中有两个常用的子类:HashSet、TreeSet。

HashSet

HashSet 属于散列的存放类集,里面的内容是无序存放的。

public class HashSetDemo {
	public static void main(String[] args) {
		Set<String> all = new HashSet<String>(); // 实例化Set接口对象
		all.add("A");
		all.add("A");//重复元素
		all.add("A");//重复元素
		all.add("B");
		all.add("P");
		all.add("D");
		all.add("E");
		System.out.println(all);
		//将其转换为Object数组,不推荐,因为在操作的时候已经指定了操作的泛型类型
		Object[] objects = all.toArray();
		//将其转换为指定的泛型类型数组
		String[] array = all.toArray(new String[]{});
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + ", ");
		}
	}
}

控制台输出如下:
image
以上字符串“A”设置了很多次,因为 Set 接口中是不能有任何的重复元素的,所以其最终结果只能有一个“A”。

TreeSet

  • 与 HashSet 不同的是,TreeSet 属于可以子类。
  • 存储基础数据类型示例:
public class TreeSetDemo {
	public static void main(String[] args) {
		Set<String> all = new TreeSet<>(); // 实例化Set接口对象
		all.add("P");
		all.add("E");
		all.add("D");
		System.out.println(all);
	}
}

控制台输出:
image
可以看到,TreeSet集合对基本数据类型的数据自动进行了排序

  • 如果存储自定义类类型,该自定义类必须实现Comparable接口,如果未实现该接口,则会抛出ClassCastException异常,示例:
    Person类:
public class Person implements Comparable<Person> {
    private String name;
    private int age;
    public int compareTo(Person per) {
        if (this.age > per.age) {
            return 1;
        } else if (this.age < per.age) {
            return -1;
        } else {
            return 0;
        }
    }
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String toString() {
        return "姓名:" + this.name + ",年龄:" + this.age;
    }
}

TreeSetDemo类

public class TreeSetDemo {
	public static void main(String[] args) {
		Set<Person> all = new TreeSet<>(); // 实例化Set接口对象
		all.add(new Person("张三", 10));
		all.add(new Person("李四", 12));
		all.add(new Person("王五", 14));
		all.add(new Person("赵六", 10));
		all.add(new Person("孙七", 13));
		System.out.println(all);
	}
}

控制台输出如下:
image
从以上的结果可以看到我们已经实现了年龄排序,但是我们发现赵六没有了。因为赵六的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改 Person 类,如果假设年龄相等的话,按名字进行排序。
修改的compareTo方法如下:

public int compareTo(Person per) {
	if (this.age > per.age) {
		return 1;
	} else if (this.age < per.age) {
		return -1;
	} else {
		return this.name.compareTo(per.name);
	}
}
  • 我们也可以通过TreeSet()构造方法传入一个Comparator的实现类来实现自定义类型比较,示例:
public class TreeSetDemo {
	public static void main(String[] args) {
		Set<Person> all = new TreeSet<>(new Comparator<Person>() {
			@Override
			public int compare(Person p1, Person p2) {
				if (p1.getAge() > p2.getAge()) {
					return 1;
				} else if (p1.getAge() < p2.getAge()) {
					return -1;
				} else {
					return p1.getName().compareTo(p2.getName());
				}
			}
		}); // 实例化Set接口对象,传入Comparator对象
		all.add(new Person("张三", 10));
		all.add(new Person("李四", 12));
		all.add(new Person("王五", 14));
		all.add(new Person("赵六", 10));
		all.add(new Person("孙七", 13));
		System.out.println(all);
	}
}

控制台输出如下:
image
可以看出两者得到的结果相同,但这种传入Comparator接口只能使用一次,而且每次都需要传入Comparator对象。

关于重复元素的说明

之前使用 Comparable 完成的对于重复元素的判断,那么 Set 接口定义的时候本身就是不允许重复元素的,那么证明
如果现在真的是有重复元素的话,使用 HashSet 也同样可以进行区分。

public class HashSetPersonDemo {
	public static void main(String[] args) {
		Set<Person> all = new HashSet<Person>();
		all.add(new Person("张三", 10));
		all.add(new Person("李四", 10));
		all.add(new Person("李四", 10));
		all.add(new Person("王五", 11));
		all.add(new Person("赵六", 12));
		all.add(new Person("孙七", 13));
		System.out.println(all);
	}
}

image
此时我们发现姓名:李四,年龄:10的对象出现了两次,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable 接口间接完成的。
因此,如果我们要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。
从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:

  • 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码
  • 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。
    所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。
public boolean equals(Object obj) {
	if (this == obj) {
		return true;
	}
	if (!(obj instanceof Person)) {
		return false;
	}
	Person per = (Person) obj;
	if (per.name.equals(this.name) && per.age == this.age) {
		return true;
	} else {
		return false;
	}
}
public int hashCode() {
	return this.name.hashCode() * this.age;
}

我们发现,此时已经不存在重复元素了,所以如果要想去掉重复元素是需要依靠hashCode()和 equals()方法共同完成的。



这篇关于Java学习笔记之集合(上)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程