Java基础知识每日总结(13)---Java集合

1.Java集合框架
java.util包中提供了一些集合类,这些集合类又被称为容器。集合类与数组的不同之处是,数组的长度是固定的,集合的长度是可变的;数组用来存放基本数据类型和引用数据类型的数据,集合只能用来存放引用数据类型的数据。常用的集合有List集合、Set集合和Map集合。
在这里插入图片描述

2.Collection接口的常用功能
由于Collection接口不能进行实例化,所有以下的测试使用多态,即使用它的实现子类。以下的介绍中包含了泛型的一些知识。
注:Collection接口的实现类一般都重写了toString方法。
①添加功能
boolean add(E e)
boolean addAll(Collection<? extends E> c)

List l=new ArrayList();
System.out.println(l);   //[]
System.out.println(l.add("haha"));   //true
System.out.println(l);   //[haha]

注:add方法的源码显示此方法一直返回true,即添加功能永远成功。
addAll用来添加另一个集合中的所有元素。例,

List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
System.out.println(l2.addAll(l));   //true
System.out.println(l);   //[hello, world, java]
System.out.println(l2);   //[hello, hello, world, java]

②删除功能
void clear()
boolean remove(Object o)
removeAll(Collection<?> c)

clear方法删除集合中的所有元素,remove方法移除指定元素。

List l=new ArrayList();
System.out.println(l);
System.out.println(l.add("haha"));
System.out.println(l);
l.add("java");
System.out.println(l);   //[haha, java]
System.out.println(l.remove("haha"));   //true
System.out.println(l);   //[java]
l.clear();
System.out.println(l);   //[]

removeAll移除另一个集合中与本集合相同的元素,只要移除一个就返回true。例,

List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("haha");
System.out.println(l2.removeAll(l));   //true
System.out.println(l);    //[hello, world, java]
System.out.println(l2);    //[haha]

③判断功能
boolean contains(Object o)
boolean containsAll(Collection<?> c)
boolean isEmpty()

contains判断指定元素在集合中是否存在,底层是用equals方法来判断是否存在;isEmpty判断集合是否为空。

List l=new ArrayList();
l.add("haha");
l.add("java");
System.out.println(l);   //[haha, java]
System.out.println(l.contains("java"));   //true
System.out.println(l.contains("hello"));   //false
System.out.println(l.isEmpty());   //false
l.clear();
System.out.println(l.isEmpty());   //true

containsAll判断本集合中是否包含指定集合的所有元素,只有全部包含才返回true。例,

List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("haha");
System.out.println(l.containsAll(l2));   //false
l2.remove("haha");
System.out.println(l.containsAll(l2));   //true

④迭代器
Iterator< E> iterator()
iterator是集合的专属遍历方法,方法返回Iterator接口的实现类对象(内部类),这里是多态。
Iterator接口的方法:

  • E next():获取元素并移动到下一个元素的位置
  • boolean hasNext():判断是否有下一个元素,如果有就返回true
List<String> l=new ArrayList<>();
l.add("hello");
l.add("world");
l.add("java");
Iterator<String> it = l.iterator();
while(it.hasNext()) {
	System.out.print(it.next()+"  ");   //hello  world  java 
}

for(Iterator<String> it2 = l.iterator();it2.hasNext();) {
	System.out.print(it2.next()+"  ");   //hello  world  java
}

虽然while循环结构清晰简单,但是建议使用for循环。因为for循环的it2变量在循环结束后就销毁了,节省了内存,提高了效率。
注:不要在一条语句中多次使用next方法,因为每次使用next方法都会移动到下一个元素的位置,很容易出现NoSuchElementException异常。

⑤长度
int size()
size获取集合的长度。例,

List l=new ArrayList();
l.add("haha");
l.add("java");
System.out.println(l.size());   //2

⑥集合转数组
Object[] toArray()
toArray将集合转为了Object类型的数组。例,

List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
Object[] o = l.toArray();
String s=(String)o[0];
System.out.println(o[0]+" "+s.length());   //hello 5
String s2=(String)o[1];
System.out.println(o[1]+" "+s2.length());   //world 5
System.out.println(((String)o[0]).length());   //5

注:((String)o[0]).length()(String)o[0].length()是不同的,前者将o[0]转为String类型后调用length方法,后者将o[0]调用length方法后的结果转为String类型,但是o[0]是Object类型的,无法调用String类型的特有方法length,所有此时会报错。

⑦其他功能
boolean retainAll(Collection<?> c)
retainAll判断本集合与指定集合是否有交集(相同的元素),无论是否有都将结果保存在方法调用者即本集合中(有交集将交集放在本集合中,无交集则本集合为空),返回值为判断本集合是否发生过改变。例,

List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("haha");
System.out.println(l.retainAll(l2));   //true
System.out.println(l);   //[hello]
System.out.println(l2);   //[hello, haha]


List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("haha");
System.out.println(l.retainAll(l2));   //true
System.out.println(l);   //[] 没有交集,可以看做空集也是一种结果
System.out.println(l2);   //[haha]


List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("world");
l2.add("java");
System.out.println(l.retainAll(l2));   //false
System.out.println(l);   //[hello, world, java] 集合没有发生改变,返回false
System.out.println(l2);   //[hello, world, java]

3.List集合
①概述
List是一个接口,在它的实现类中常用的类有ArrayList、LinkedList、Vector(线程安全)。List接口及其子类是有顺序的(注意不是排序)允许重复的,可以通过索引查找元素。
数组和数组列表有一个重大的缺陷即在数组的中间位置插入或删除一个元素很麻烦,要插入位置之后的元素集体要向后移动一个位置,要删除位置之后的元素集体要向前移动一个位置。链表解决了这个问题。链表将每个对象存放在独立的结点中,每个结点还存放着序列中下一个结点的引用。在Java中,所有的链表都是双向链表,每个结点还存放着指向前驱结点的引用。

②特有功能
因为List接口是Collection接口的子接口,所以Collection的常用方法List及其子类都能使用。
void add(int index,E element)
add方法在集合中的index位置插入element元素。
注:index只能是已添加的所有元素的长度+1或长度内,超过会出现异常。

E get(int index)
get方法获取index位置的元素,注意index不要越界。

E remove(int index)
remove删除index位置的元素,注意index不要越界。

E set(int index,E element)
set方法将index位置的元素替换为element并返回被替换掉的元素。例,

List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
System.out.println(l.set(1,"haha"));   //world
System.out.println(l);   //[hello, haha, java]

ListIterator listIterator()
List集合的特有迭代器方法,返回一个ListIterator类型的对象,实际返回ListIterator接口实现类的对象,此处使用了多态。

③ListIterator特有方法
ListIterator继承了Iterator接口,所以Iterator的方法ListIterator及其实现类都可以使用。
E previous()
boolean hasPrevious()

previous方法获取上一个元素,hasPrevious和hasNext的使用相似。
注:使用previous前必须首先使用next方法,否则previous方法无意义(因为迭代器默认是从起始元素开始)。

④要点
不能在迭代器遍历元素时修改元素
如果修改会出现并发修改异常ConcurrentModificationException,因为迭代器是依赖于集合存在的,当集合修改元素时迭代器却不知道,所以会出现异常。解决方法:

  • 用迭代器去修改元素
  • 不用迭代器遍历元素,用集合遍历元素(for循环遍历)

第一种解决方法使用的是ListIterator的特有方法:void add(E e),此时元素在刚刚遍历的元素的后面。第二种解决方法添加的元素在集合的最后面添加。

ArrayList特点
底层数据结构是数组,查询快,增删慢,线程不安全所以效率高

Vector特点
底层数据结构是数组,查询快,增删慢,线程安全所以效率低

LinkedList特点
底层数据结构是链表,查询慢,增删快,线程不安全所以效率高
注:这三个常用类是List接口的实现类,所以Collection接口的方法和List接口的方法以上三个实现类都可以使用。

注意remove方法的使用
用remove方法清除一个List集合中的元素后,此元素后面的元素会向前进一位,在某些特殊算法中注意不要将进位的元素漏掉。

集合中的元素可以是集合

⑤LinkedList特有方法
添加:
public void addFirst(E e)
public void addLast(E e)

获取:
public E getFirst()
public E getLast()

删除:
public E removeFirst()
public E removeLast()

添加方法为在开头和结尾添加元素,获取方法为获取开头和结尾的元素,删除方法为删除开头和结尾的元素。例,

LinkedList l=new LinkedList();
l.add("hello");
l.add("world");
l.add("java");
l.addFirst("qiong");
l.addLast("qiong");
System.out.println(l);   //[qiong, hello, world, java, qiong]
System.out.println(l.getFirst());   //qiong
System.out.println(l.getLast());    //qiong
System.out.println(l);   //[qiong, hello, world, java, qiong]
l.removeFirst();
l.removeLast();
System.out.println(l);   //[hello, world, java]

注:使用LinkedList的特有方法时不能将变量声明为List类型的,即List l=new LinkedList();,因为此时的特有方法不满足多态的条件。

4.增强for
①格式

for(元素的数据类型  变量名:数组或Collection集合) {
	对该变量的使用(如,输出),此变量就是数组或集合中遍历的一个个元素;
}

②使用

List<String> s=new ArrayList<String>();
s.add("hello");
s.add("world");
s.add("java");
s.add("haha");
s.add("SilverBullet");
for(String s2:s) {
	System.out.print(s2+"  ");   //hello  world  java  haha  SilverBullet  
}

③缺点
因为增强for在底层使用时会调用数组或集合的方法对变量进行判断,所以增强for中的数组或Collection集合不能为null,一旦为null,会出现空指针异常NullPointerException。例,

List<String> s=null;
for(String s2:s) {
	System.out.println(s2);   //Exception in thread "main" java.lang.NullPointerException
}

解决办法:对增强for的目标数组或集合先进行非null判断。
增强for的本质是Iterator,所以与Iterator一样不要在增强for遍历元素时修改元素,一旦修改会出现ConcurrentModificationException并发修改异常。

5.Set集合
①概述
Set是一个接口,它的父接口是Collection,在它的实现类中常用的类有HashSet、TreeSet。Set接口及其子类是无序(即存储顺序和取出顺序不一致)不允许重复的。

②HashSet的add方法
在使用Set接口的实现类的add方法添加元素时,有一些相同的元素并没有添加进集合,此时判断相同的元素的依据是:

  • 将要添加的元素的哈希值与集合中的每个元素的哈希值比较,无相同添加元素,有相同进行下一步判断
  • 用equals方法判断是否相等,有相等不添加,无相等添加

重写equals方法的三步骤:

  • 比较两个对象的地址值是否相同
  • 比较两个对象是否属于同一个类
  • 将两个对象转为同一个类的对象,比较想要比较的内容

③特殊Set实现类
LinkedHashSet是一个有序不允许重复的类,它是哈希表和链表的结合,它所有的方法都是HashSet类或父接口的方法。

④TreeSet
TreeSet的底层是由TreeMap实现的。它能够对元素以某种规则进行排序,排序主要有两种:自然排序和比较器排序。自然排序即按照从小到大的顺序进行排序,比较器排序即根据创建Set时提供的Comparator进行排序,具体取决于使用的构造方法。

6.TreeSet
①构造方法
public TreeSet()
public TreeSet(Comparator<? super E> comparator)

第一个构造方法是自然排序,它的底层使用了Comparable接口,它允许添加的实现了Comparable接口的元素进行自然排序,这调用了添加元素的int compareTo(T o)方法,Comparable是java.lang包下的接口。第二个构造方法是传入一个实现了Comparator接口的类来比较要添加的元素和已添加的元素,此时元素本身的类不需要实现Comparator接口。TreeSet保证元素的唯一性是根据比较的两种方法来实现的,当两个元素相等时比较值为0,元素不添加。

②无参构造使用

class Student implements Comparable<Student> {
	private String name;
	private int 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 Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}

	@Override
	public int compareTo(Student o) {
		int t = this.getAge() - o.getAge(); // 以年龄从小到大排序
		int t2 = t == 0 ? this.name.compareTo(o.name) : t; // 年龄相同时比较姓名
		return t2;
	}

}

public class Test {

	public static void main(String[] args) {
		TreeSet<Student> hs = new TreeSet<>();
		hs.add(new Student("hello", 21));
		hs.add(new Student("world", 23));
		hs.add(new Student("java", 21));
		hs.add(new Student("hello", 21));
		hs.add(new Student("haha", 23));
		System.out.println(hs);
	}
}

当要添加的元素对象没有实现Comparable接口,运行时会出现java.lang.ClassCastException类型转换异常:Student cannot be cast to Comparable。

③有参构造使用

class Student  {
	private String name;
	private int 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 Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}

}


public class Test {

	public static void main(String[] args) {
		TreeSet<Student> hs = new TreeSet<>(new Comparator<Student>() {   //此处使用了匿名内部类

			@Override
			public int compare(Student o1, Student o2) {  //o1是要添加的元素,o2是集合已有的元素
				int t = o1.getAge() - o2.getAge(); // 以年龄从小到大排序
				int t2 = t == 0 ? o1.getName().compareTo(o2.getName()) : t; // 年龄相同时比较姓名
				return t2;
			}
		});
		hs.add(new Student("hello", 21));
		hs.add(new Student("world", 23));
		hs.add(new Student("java", 21));
		hs.add(new Student("hello", 21));
		hs.add(new Student("haha", 23));
		System.out.println(hs);
	}
}

7.Map集合
①概述
Map集合是一个特殊集合,它的父类是Object,与Collection一样它本身就是一个顶层接口,它可以存储键值对元素,即可以通过一个键映射到一个值,一个键只能有一个值,但一个值可以有多个不同的键映射它,即键是唯一的。Map的实现类中常用的类有HashMap、TreeMap、Hashtable(线程安全)。
public interface Map<K,V>

②添加功能
V put(K key, V value)
将键值对添加到Map集合中,并返回此键上一次对应的值,如果此键上一次(即第一次存储)不存在返回null。例,

Map<String,Integer> m=new HashMap<>();   //泛型
System.out.println(m);   //{},此结果说明Map已重写toString方法
System.out.println(m.put("小明", 62));   //null
System.out.println(m.put("小明", 65));   //62
m.put("小红", 75);
System.out.println(m);   //{小明=65, 小红=75}

③删除功能
V remove(Object key)
void clear()

第一个方法根据指定的键删除键值对并返回此键对应的值,如果没有这个键则返回null。第二个方法是清除所有的键值对。例,

Map<String,Integer> m=new HashMap<>();
m.put("小明", 65);
m.put("小红", 75);
System.out.println(m.remove("小明"));   //65
System.out.println(m.remove("小李"));   //null
System.out.println(m);   //{小红=75}
m.put("小蓝", 85);
System.out.println(m);   //{小蓝=85, 小红=75}
m.clear();
System.out.println(m);   //{}

④判断功能
boolean containsKey(Object key)
boolean containsValue(Object value)

判断Map集合中是否存在键key或value,存在返回true,不存在返回false。
boolean isEmpty()
判断集合是否为空

⑤长度
int size()
返回Map集合中Key值的数量

⑥获取功能
V get(Object key)
Set < K> keySet()
Collection< V> values()

第一个方法是根据键获取集合中对应的值;第二个方法是获取集合中所有的键,因为键是唯一的所以用Set集合接收;第三个方法是获取集合中所有的值,用Collection集合接收。例,

Map<String,Integer> m=new HashMap<>();
m.put("小明", 65);
m.put("小红", 75);
System.out.println(m.get("小明"));   //65
Set<String> s = m.keySet();
for(String ss:s) {
	System.out.print(ss+"  ");   //小明  小红  
}
System.out.println();
Collection<Integer> cl = m.values();
for(Integer i:cl) {
	System.out.print(i+" ");   //65 75 
}

Set<Map.Entry<K,V>> entrySet()
此方法返回一个包含键值对对象的Set集合,Map.Entry<K,V>是一个接口,此处使用了多态,此接口中有获取键值对的方法。例,

Map<String,Integer> m=new HashMap<>();
m.put("小明", 65);
m.put("小红", 75);
Set<Entry<String, Integer>> se = m.entrySet();
for(Entry<String, Integer> e:se) {   //e是键值对对象,此处是多态
	System.out.println(e.getKey()+"  "+e.getValue());   
	//小明  65
	//小红  75
}

遍历Map的5种方法

使用 for 循环遍历

Map<String, String> myMap = new HashMap<>();
myMap.put("key1", "value1");
myMap.put("key2", "value2");

for(Map.Entry<String, String> entry : myMap.entrySet()){
  String key = entry.getKey();
  String value = entry.getValue();
    System.out.println(key + ": " + value);
}

上面这个示例通过entrySet()方法获取Map中所有键值对的集合,然后使用for循环遍历该集合。在循环内部,使用getKey()和getValue()方法分别获取键和值。

使用 keySet、values 分别遍历键和值


Map<String, String> myMap = new HashMap<>();
myMap.put("key1", "value1");
myMap.put("key2", "value2");

// 遍历Map的键
for(String key : myMap.keySet()){
  System.out.println(key);
}

// 遍历Map的值
for(String value : myMap.values()){
  System.out.println(value);
}

第一个遍历,通过keySet()方法获取Map中所有键的集合,然后使用for循环遍历该集合。
第二个遍历,通过values()方法获取Map中所有值的集合,然后使用for循环遍历该集合。
需要注意的是,在Java中,Map的键和值也可以是任意类型,但是在使用for-each循环或迭代器遍历时,需要使用泛型指定键值对的类型,并使用Map.Entry类来表示键值对。

使用迭代器 Iterator 遍历

Map<String, String> myMap = new HashMap<>();
myMap.put("key1", "value1");
myMap.put("key2", "value2");

Iterator<Map.Entry<String, String>> iterator = myMap.entrySet().iterator();
while(iterator.hasNext()){
  Map.Entry<String, String> entry = iterator.next();
  String key = entry.getKey();
  String value = entry.getValue();
  System.out.println(key + ": " + value);
}

首先通过entrySet()方法获取Map中所有键值对的集合,然后使用iterator()方法返回一个迭代器对象。

使用 Lambda 表达式的 forEach

Map<String, String> myMap = new HashMap<>();
myMap.put("key1", "value1");
myMap.put("key2", "value2");

// 遍历Map的键值对
myMap.forEach((key, value) -> {
  System.out.println(key + ": " + value);
});

注:以上遍历Map的示例仅适用于HashMap、TreeMap等实现了Map接口的类。对于ConcurrentHashMap、LinkedHashMap等其他实现,可能有不同的遍历方法或限制。

使用 Stream API遍历

// 遍历Map的键值对
myMap.entrySet().stream()
  .forEach(entry -> {
    System.out.println(entry.getKey() + ": " + entry.getValue());
  });

// 遍历Map的键myMap.keySet().stream()
myMap.keySet().forEach(key -> {
    System.out.println(key);
  });

// 遍历Map的值myMap.values().stream()
myMap.values().forEach(value -> {
    System.out.println(value);
  });

Map遍历注意点

在Java中,执行Map的删除操作时,需要注意不能使用for-each循环遍历并删除元素。这是因为,在使用for-each循环遍历Map时,实际上是通过Map.entrySet()返回的Set集合来进行遍历的,而在使用Set集合的迭代器进行遍历时,如果修改了集合中的元素(例如:删除),则会抛出ConcurrentModificationException异常。

因此,在Java中执行Map的删除操作,一般使用迭代器或者直接指定key进行删除。


Map<String, String> myMap = new HashMap<>();
myMap.put("key1", "value1");
myMap.put("key2", "value2");

Iterator<Map.Entry<String, String>> iterator = myMap.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, String> entry = iterator.next();
    if (entry.getKey().equals("key1")) {
        iterator.remove();
    }
}

注:使用remove方法指定key进行删除时,如果key不存在,remove方法会返回null并不会抛出异常。

⑦HashMap
HashMap集合的数据结构是哈希表,它保证键唯一的原因:依靠键元素的hashCode方法和equals方法。
与链表有关的Map类是LinkedHashMap类。

⑧TreeMap
TreeMap的键的数据结构是红黑树,它与TreeSet的保证元素唯一和排序的方法一样。

⑨要点
HashMap和Hashtable的区别
HashMap不是线程安全的类,但是效率高,且允许有null值和null键;Hashtable是线程安全的类,但是效率低,且不允许有null值和null键。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值