集合
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,我们就需要对这多个对象进行存储。而目前为止我们学习过的可以存储多个元素的东西是数组,但是呢,数组长度固定,不能适应变化的需求,所以,Java就提供了集合类供我们使用。
集合和数组的区别:
数组:
1.长度固定
2.可以存储基本类型,也可以存储引用类型
3.存储元素类型一致
集合:
1.长度可变
2.只可以存储引用类型
3.可以存储多种类型
集合体系图:
首先先介绍一下Collection这个接口
是集合的顶层结构,定义了集合的共性功能。
它有一些成员方法是所有集合所共有的:
成员方法:(看到E我们先把它理解为Object即可)
A:添加功能
boolean add(Object obj):往集合中添加一个元素
boolean addAll(Collection c):往集合中添加多个元素
B:删除功能
void clear():清空所有元素
boolean remove(Object o):从集合中删除一个元素
boolean removeAll(Collection c):从集合中删除另一个集合的元素
C:判断功能
boolean contains(Object o):判断集合中是否包含指定的元素
boolean containsAll(Collection c):判断集合中是否包含另一个集合的元素
boolean isEmpty():判断集合是否为空。
D:交集功能
boolean retainAll(Collection c)
E:迭代器(集合特有的遍历方式)
Iterator iterator()
F:长度功能
int size():返回集合中元素的个数
G:集合转数组
Object[]toArray():把集合转成数组,然后遍历数组,其实就相当于遍历了集合。
public class CollectionDemo {
public static void main(String[] args) {
// A:添加功能
// boolean add(Object obj):往集合中添加一个元素
// boolean addAll(Collection c):往集合中添加多个元素
Collection<String> c1=new ArrayList<String>();//创建Collection集合
//向集合中添加元素
c1.add("人机交互");
c1.add("图像处理");
c1.add("微机原理");
//再创建一个集合对象
Collection<String> c2=new ArrayList<String>();
c2.add("c++");
c2.add("java");
//把集合c2的元素添加到c1中
c1.addAll(c2);
/*B:删除功能
* void clear():清空所有元素
* boolean remove(Object o):从集合中删除一个元素
* boolean removeAll(Collection c):从集合中删除另一个集合的元素*/
c1.remove("c++");
//c1.removeAll(c2);
//c1.clear();
/*C:判断功能
* boolean contains(Object o):判断集合中是否包含指定的元素
* boolean containsAll(Collection c):判断集合中是否包含另一个集合的元素
* boolean isEmpty():判断集合是否为空。*/
//boolean contains = c1.contains("c++");
boolean contains = c1.containsAll(c2);
System.out.println(contains);
/*D:交集功能
* boolean retainAll(Collection c)*/
boolean retainAll = c1.retainAll(c2);//求出交集元素,并将没有交集的元素删除
System.out.println(retainAll);
//遍历集合c1
Object[] arr = c1.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//遍历集合c2
Object[] arr1 = c2.toArray();
for (int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);
}
}
}
迭代器: Iterator iterator(),用来遍历集合中的元素
成员方法:
Object next():返回迭代的下一个元素,并移动指向的位置
boolean hasNext():判断是否有元素
迭代器是依赖于集合而存在的。所以,要想得到迭代器对象,必须先有集合对象。
迭代步骤:
A:通过集合对象获取到迭代器对象
B:通过迭代器对象的hasNext()方法判断是否有元素
C:通过迭代器对象的next()方法获取元素
练习:创建狗对象(带参数),存储到集合,用迭代器进行遍历并打印对象的属性数据
分析:1。先写一个Dog类
2.然后在创建一个空的集合
3.创建Dog对象
4.向集合中添加Dog对象元素
5.使用while循环加迭代器进行遍历
Dog类:
public class Dog {
private String name;
private int age;
private String color;
public Dog(String name, int age, String color) {
super();
this.name = name;
this.age = age;
this.color = color;
}
public Dog() {
super();
// TODO Auto-generated constructor stub
}
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 getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
然后写一个测试类:
public class CollectionDemo {
public static void main(String[] args) {
//创建狗对象
Dog d1 = new Dog("大黄",2,"黄色");
Dog d2 = new Dog("小黑",2,"黑色");
Dog d3 = new Dog("大花",2,"花色");
//创建集合对象
Collection c1=new ArrayList();
//集合中添加狗对象元素
c1.add(d1);
c1.add(d2);
c1.add(d3);
//创建迭代器对象
Iterator it = c1.iterator();
//遍历
while(it.hasNext()){
Dog d=(Dog) it.next();
System.out.println(d.getName()+" "+d.getAge()+" "+d.getAge());
}
}
}
List接口:
(1)List集合的元素有序(存储和取出顺序一致),元素可重复
(2)List的特有功能:
A:添加功能
void add(int index,Object obj):在指定的位置添加元素
B:删除功能
Object remove(int index):通过指定的索引删除元素,并把删除的元素返回
C:获取功能
get(int index) 返回列表中指定位置的元素。
D:替换功能
Object set(int index,Object obj)
下面写一些简单的代码对这些功能进行测试:
public class ListDemo {
public static void main(String[] args) {
//创建List集合对象
List<String> list = new ArrayList<String>();
//给集合添加元素
list.add("李白");
list.add("杜甫");
list.add("王维");
//遍历集合
print(list);
System.out.println("----------------------------");
//void add(int index,Object obj):在指定的位置添加元素
list.add(1, "白居易");
print(list);
System.out.println("----------------------------");
//Object remove(int index):通过指定的索引删除元素,并把删除的元素返回
String remove = list.remove(1);
print(list);
System.out.println("--------------------------");
System.out.println(remove);
System.out.println("-----------------------------");
//get(int index) 返回列表中指定位置的元素。
String str = list.get(0);
System.out.println(str);
System.out.println("----------------------");
//Object set(int index,Object obj)
list.set(0, "苏轼");
print(list);
}
public static void print(List<String> p){
Iterator<String> it = p.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
集合中还有一个集合工具类Collections类,可以实现List的倒序与洗牌与排序
List中元素顺序可以被洗牌Collections.shuffle(list)
List中元素顺序可以被倒序Collections.reverse(list)
Collections.sort(list)对List元素排序
public class CollectionsDemo {
public static void main(String[] args) {
//创建List集合
List list = new ArrayList();
//给集合中添加元素
list.add(2);
list.add(8);
list.add(1);
list.add(3);
//List中元素顺序可以被洗牌Collections.shuffle(list)
//Collections.shuffle(list);
//List中元素顺序可以被倒序Collections.reverse(list)
//Collections.reverse(list);
//Collections.sort(list)对List元素排序(字母数字分别进行测试)
//如果是按字母是按照a-z的顺序进行排序
//Collections.sort(list);
//如果是按照数字进行排序,按照从小到大的顺序进行排列
Collections.sort(list);
//遍历集合
for (Object object : list) {
System.out.println(object);
}
}
}
LinkedList:list集合接口的实现类
LinkedList特有功能:
public void addFirst(E e)//向第一个位置添加元素
public void addLast(E e)//向最后一个位置添加元素
public E getFirst()//获取第一个元素
public E getLast()//获取最后一个元素
public E removeFirst()//移除第一个元素
public E removeLast()//移除最后一个元素
简单的进行测试:
public class LinkedListDemo {
public static void main(String[] args) {
//创建一个LinkedList的集合
LinkedList list = new LinkedList();
list.add("hello");
list.add("world");
list.add("java");
// public void addFirst(E e)
// public void addLast(E e)
//需求:在hello前面加上一个元素“c++”
list.addFirst("c++");
list.addLast("js");
// public E getFirst()
// public E getLast()
// Object first = list.getFirst();
// Object last = list.getLast();
// System.out.println(first);
// System.out.println(last);
// public E removeFirst()
// public E removeLast()
Object removeFirst = list.removeFirst();
Object removeLast = list.removeLast();
System.out.println(removeFirst);
System.out.println(removeLast);
System.out.println("---------------");
//遍历集合
for(Object obj:list){
System.out.println(obj);
}
}
}
ArrayList与LinkedList的相同点与不同点
相同点:有顺序的,元素可以重复
不同点:
(1)ArrayList特点:
底层数据结构是数组,查询快,增删慢
线程不安全,效率高
(2)LinkedList特点:
底层数据结构是链表,查询慢,增删快
线程不安全,效率高
集合的遍历方式:
1.for(普通for)(list集合专有,因为有get()方法)
2.Iterator(迭代器)
3.foreach(增强for)
foreach的格式:
for(集合中的元素类型 变量名:集合名){
}
需求:用List集合存储3个汽车对象,然后遍历。(用三种方式进行遍历)
分析:
1.创建汽车集合
2.创建汽车对象
3.创建list集合对象
4.向集合中添加对象元素
5.遍历
Car类:
public class Car {
private String brand;
private int price;
private String color;
public Car() {
super();
// TODO Auto-generated constructor stub
}
public Car(String brand, int price, String color) {
super();
this.brand = brand;
this.price = price;
this.color = color;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class ListDemo {
public static void main(String[] args) {
//创建汽车对象
Car c1 = new Car("大众",20000,"黑色");
Car c2 = new Car("奥迪",40000,"红色");
Car c3 = new Car("奔驰",60000,"白色");
//创建集合对象
List list =new ArrayList();
//集合中添加元素
list.add(c1);
list.add(c2);
list.add(c3);
//使用迭代器遍历
Iterator it = list.iterator();
while(it.hasNext()){
Car c=(Car) it.next();
System.out.println(c.getBrand()+" "+c.getPrice()+" "+c.getColor());
}
System.out.println("---------------------------");
//使用for循环遍历
for (int i = 0; i <list.size(); i++) {
Car c=(Car) list.get(i);
System.out.println(c.getBrand()+" "+c.getPrice()+" "+c.getColor());
}
System.out.println("---------------------------");
//增强for循环
for(Object obj:list){
Car c=(Car) obj;
System.out.println(c.getBrand()+" "+c.getPrice()+" "+c.getColor());
}
}
}
结果:
set集合中的hashset
HashSet 元素顺序:元素唯一,但是无序(它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变)
如何保证元素的唯一性的呢?
通过分析源码,我们知道HashSet集合保证元素的唯一性和add()方法相关。
如何我们想深入的了解,就必须看add()方法的源码,看它的底层依赖什么内容?
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {...}
左边:e.hash == hash
比较对象的哈希值。
右边:((k = e.key) == key || key.equals(k))
左边:(k = e.key) == key
比较对象的地址值。
右边:key.equals(k)
比较的是对象的内容是否相同。默认情况下比较的是地址值
结论:
底层数据结构是哈希表。
哈希表依赖两个方法:hashCode()和equals()
执行流程:
首先判断哈希值是否相同,如果不同,就直接添加到集合。
如果相同,继续执行equals(),看其返回值,
如果是false,就直接添加到集合。
如果是true,说明元素重复不添加。
所以我们在使用hashset集合时想要保住元素的唯一性,就必须重写hashCode()和equals()这两个方法。至于如何重写,eclipse提供了自动生成的方法
练习:创建一个HashSet集合,添加元素(学生对象元素),保证其唯一性。
Student类:在这个类中重写hashCode()和equals()这两个方法
public class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
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;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
//重写hashCode(),equals()方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
测试类:
public class HashSetDemo {
public static void main(String[] args) {
//创建集合
HashSet<Student> hs = new HashSet<Student>();
//创建学生对象
Student s1 = new Student("郭德纲", 50);
Student s2 = new Student("刘德华", 55);
Student s3 = new Student("张学友", 56);
Student s4 = new Student("黎明", 57);
Student s5 = new Student("郭德纲", 50);
//给集合中添加元素
hs.add(s1);
hs.add(s2);
hs.add(s3);
hs.add(s4);
hs.add(s5);
//遍历集合
for (Student student : hs) {
System.out.println(student);
}
}
}
通过分析发现:郭德纲这个对象只出现了一次,保证了元素的唯一性
TreeSet
首先看一下它的两个构造方法:
public TreeSet()
//构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
TreeSet(Comparator<? super E> comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序
元素顺序:使用元素的自然顺序对元素进行排序,或者根据创建 set时提供的 Comparator进行排序,具体取决于使用的构造方法。
底层算法:二叉树
元素要求, 加入自定义JavaBean
对java中已有数据类型的存入:
1.如果是Integer类型的数据,按大小顺序存入
2.如果是String类型的数据,按首字母a-z的顺序存入
练习:创建集合存储Integer类型的元素(20,18,23,22,17,24,19,18,24)
public class TreeSetDemo {
public static void main(String[] args) {
//创建一个TreeSet集合
TreeSet<Integer> ts = new TreeSet<Integer>();
//给集合中存储元素
//(20,18,23,22,17,24,19,18,24)
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(22);
ts.add(17);
ts.add(24);
ts.add(19);
ts.add(18);
ts.add(24);
//遍历集合
for (Integer integer : ts) {
System.out.print(integer+" ");
}
}
}
//结果:17 18 19 20 22 23 24
分析:TreeSet集合特点:
1.元素唯一
2.元素有序
那么在存入自定义对象元素时如何保证元素有序呢?分析Integer类和String类之后发现这两个类实现了一个Comparable接口,并重写comparaTo()方法,所以我们在存入自定义对象元素是也需要重写这个方法,就可以保证元素有序.当然还有另外一种方法,我们首先看一下第二种构造方法,它需要一个比较器实现类对象,那么我们可以用匿名类部类重写比较器中的Compare方法进行比较来保证元素有序。
练习:创建集合存储Student类型的元素,并保证其有序性
方式一:
1.创建Student类并实现Comparable接口,并重写comparaTo()方法
2.创建学生对象
3.使用无参构造创建空TreeSet集合
4.添加学生元素
5.遍历
学生类中实现的compareto方法
public int compareTo(Student s) {
//就是写的是元素的比较规则,由你自己去动手写出
//按照学生的年龄进行排序
/**
* 两个对象进行比较:
* s
* this
*/
int num = this.age - s.age;
//判断年龄是否相同,如果相同比较姓名
/**
* 写这个比较规则的时候注意两点:
* 1.他有主要条件,先按照主要条件进行排序
* 2.如果主要条件相同,就需要你自己分析出来他的次要条件,再去按照次要条件进行比较
*/
int num2 = num==0?this.name.compareTo(s.name):num;
return num2;
}
测试类:
public class TreeSetDemo {
public static void main(String[] args) {
//创建TreeSet集合,存储自定义对象
TreeSet<Student> ts = new TreeSet<Student>();
//给集合中添加Student对象
Student s = new Student("guodegang", 50);
Student s6 = new Student("liuyifei", 50);
Student s2 = new Student("zhangxueyou", 55);
Student s3 = new Student("amu", 45);
Student s4 = new Student("tf-boys", 18);
Student s5 = new Student("wangfeng", 49);
ts.add(s);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student student : ts) {
System.out.println(student);
}
}
}
方法二
1.创建Student类
2.创建学生对象
3.使用使用带选择器的有参构造创建空TreeSet集合
4.匿名类部类重写比较器中的Compare方法进行比较来保证元素有序。
5.添加学生元素
6.遍历
public class TreeSetDemo {
public static void main(String[] args) {
//使用匿名内部类来进行改进
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge();
int num2 = num==0?s1.getName().compareTo(s2.getName()):num;
return num2;
}
});
//创建对象存入集合
Student s = new Student("guodegang", 50);
Student s6 = new Student("liuyifei", 50);
Student s2 = new Student("zhangxueyou", 55);
Student s3 = new Student("amu", 45);
Student s4 = new Student("tf-boys", 18);
Student s5 = new Student("wangfeng", 49);
ts.add(s);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student student : ts) {
System.out.println(student);
}
}
}
两者结果完全相同
HashSet与TreeSet的相同点与不同点
相同点:
单列集合,元素不可重复
不同点:
1. 底层存储的数据结构不同
HashSet底层用的是HashMap哈希表结构存储,而TreeSet底层用的是TreeMap树结构存储
2.存储时保证数据唯一性依据不同
HashSet是通过复写hashCode()方法和equals()方法来保证的,而TreeSet通过Compareable接口的compareTo()方法来保证的
3.有序性不一样
HashSet无序,TreeSet有序
Map:将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
Map接口中的方法概述(创建集合测试方法):
A:删除功能
void clear():移除集合中的所有键值对元素
V remove(Object key):根据键移除键值对元素,并返回值
B:判断功能
boolean containsKey(Object key):判断集合中是否包含指定的键
boolean containsValue(Object value):判断集合中是否包含指定的值
boolean isEmpty():判断集合是否为空
C:获取功能
Set<Map.Entry<K,V>> entrySet():获取键值对对象的集合,遍历键值对对象,
利用getKey(),getValue()取出键和值(理解即可)
V get(Object key):根据键获取值
Set<K> keySet():获取所有的键
Collection<V> values():获取所有的值
D:添加功能
V put(K key,V value):集合添加键值对
E:长度功能
int size():键值对对数。
进行功能测试:
public class MapDemo {
public static void main(String[] args) {
//创建一个Map集合
//键是学号--值是姓名
Map<Integer,String> map = new HashMap<Integer,String>();
//V put(K key,V value):集合添加键值对
map.put(1, "周杰伦");
map.put(2, "郭德纲");
map.put(3, "刘德华");
map.put(4, "张学友");
//void clear():移除集合中的所有键值对元素
//map.clear();
//V remove(Object key):根据键移除键值对元素,并返回值
//String remove = map.remove(1);
//System.out.println(remove);
//boolean containsKey(Object key):判断集合中是否包含指定的键
//boolean containsKey = map.containsKey(2);
//System.out.println(containsKey);
//boolean containsValue(Object value):判断集合中是否包含指定的值
//boolean containsValue = map.containsValue("周杰伦");
//System.out.println(containsValue);
//boolean isEmpty():判断集合是否为空
//System.out.println(map.isEmpty());
//int size():键值对对数。
//System.out.println(map.size());
//Collection<V> values():获取所有的值
/*
Collection<String> values = map.values();
Iterator<String> it = values.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}*/
//Set<Map.Entry<K,V>> entrySet():获取键值对对象的集合,遍历键值对对象,
//利用getKey(),getValue()取出键和值(理解即可)
Set<Entry<Integer,String>> entrySet = map.entrySet();
for (Entry<Integer, String> entry : entrySet) {
System.out.println(entry.getKey()+" "+entry.getValue());
}
System.out.println("--------------------");
//Set<K> keySet():获取所有的键
Set<Integer> keys = map.keySet();
for (Integer key : keys) {
//V get(Object key):根据键获取值
System.out.println(key+" "+map.get(key));
}
}
}
HashMap
元素顺序:元素顺序不可预测
底层算法:哈希算法
对键没有要求(仅仅相对于TreeMap来说)
public class HashMapDemo {
public static void main(String[] args) {
// 1.存入(String,String)主要讲解遍历方式,键:丈夫 值:妻子
HashMap<String, String> hm = new HashMap<String,String>();
//给键值对集合存入元素
hm.put("文章", "马伊琍");
hm.put("黄晓明", "baby");
hm.put("汪老师", "章子怡");
hm.put("周杰伦", "昆凌");
//hm.put("文章", "姚笛");,当后面存入的元素和前面的键的值相同的时候,前面的元素的值会被后面的元素的值代替
//遍历,通过建找值
Set<String> keys = hm.keySet();
for (String key : keys) {
System.out.println(key+" "+hm.get(key));
}
System.out.println("-------------");
//通过找到结婚证,来分别找到丈夫和妻子
Set<Entry<String,String>> entrySet = hm.entrySet();
for (Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey()+" "+entry.getValue());
}
}
}
Treemap
元素顺序:元素顺序与键的排序规则有关
底层算法:Tree算法
.HashMap与TreeMap的相同点与不同点
相同点:主要用于存储键(key)值(value)对,根据键得到值,因此键不允许键重复,但允许值重复。
不同点:
1.HashMap里面存入的键值对在取出的时候是随机的,也是我们最常用的一个Map.根据键可以直接获取它的值,
具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。
2.TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。