java基础(4)
1.集合
概述:面向对象语言对事物的体现都是以对象的形式,所有为了方便对多个对象的操作,我们就必须把这多个对象进行存储,而想存储多个对象,就不能是一个基本的变量,而应该是一个容器类型的变量,数组可以满足这一要求,但是数组的长度是固定的,因此,java就为我们提供了集合类。
数组和集合的区别:
1.长度区别:数组的长度固定,集合长度可变
2.内容不同:数组存储的是同一种类型的元素,而集合可以存储不同类型的元素
3.元素的数据类型:数组可以存储基本数据类型,也可以存储引用数据类型,但是集合只能存储引用类型
刚说过集合是存储多元的,但是呢,存储多个元素我们也是有不同需求的,比如说,我要这多个元素中不能有相同的元素,再比如说,我要这多个元素按照某种规则排序一下,针对不同的需求,java就提供了不同的集合类,这多个集合类的数据结构不同,结构不同不重要的,重要的是你要能够存储东西,还要能够使用这些东西,比如说判断,获取等,既然这样,那么,这多个集合类是有共性的内容的,我们把这些集合类的共性内容不断的向上提取,最终就能形成集合的继承体系结构
集合的继承体系结构如下图:
2.接口Collection
概述:Collection是集合的顶层接口,它的体系有允许重复的,有唯一的,有有序的,有无序的
Collection的功能概述:
1.添加功能:
- boolean add(Object e):添加一个元素
- boolean addAll(Collection c):添加一个集合的元素
2.删除功能:
- void clear():移除所有元素
- boolean remove(Object e):移除一个元素
- boolean removeAll(Collection c):移除一个集合的元素,只要有一个元素被移除了就返回true
3.判断功能:
- boolean contains(Object e):判断集合中是否包含指定的元素
- boolean containsAll(Collection c):判断集合中是否包含指定的集合元素,只有包含所有的元素,才叫包含
- boolean isEmpty():判断集合是否为空
4.获取功能:
- Iterator
iterator():迭代器,集合的专用遍历方式,获取下一个元素的方法通过Iterator接口的子类对象的next()获取元素 - 迭代器,是一个遍历集合的一种方式,迭代器依赖于集合而存在,因为迭代器需要调用集合的iterator方法生成一个子类对象
- 迭代器的使用原理:迭代器是一个接口,而不是一个类,原因是因为java中提供了很多的集合类,而这些集合类的数据结构是不同的,所以,存储的方式和遍历应该是不同的,进而他们的遍历方式也应该不是一样的,因此,选择把迭代器做成一个接口,而真正的具体的实现类是以内部类Itr类的方式体现的。
5.长度功能:
- int size():元素的个数
6.交集功能:
- boolean retainAll(Collection c):两个集合都有的元素,都有的元素最终的结果会放在调用这个方法的集合里,而返回的布尔值表示的就是这个集合是否发生了改变,没有发生改变返回true,发生了改变返回false
7.把集合转换为数组
- Object[] toArray()
使用集合的四个步骤:
- 创建集合对象
- 创建要存储的对象
- 把要存储的对象添加到集合中
- 遍历集合
示例代码(把学生对象添加到集合中,并遍历他):
//Student.java
public class Student {
private String name;
private int age;
public Student(){};
public Student(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;
}
}
//Demo1.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo1 {
public static void main(String[] args) {
/*创建一个collection对象,由于collection是一个接口,
只能通过子类来new一个对象出来(ArrayList类就是其中一个子类)*/
Collection c = new ArrayList();
Student s1 = new Student("卢一", 18);
Student s2 = new Student("卢二", 19);
Student s3 = new Student("卢三", 20);
Student s4 = new Student("卢四", 21);
Student s5 = new Student("卢五", 22);
//把学生对象添加到集合中去
c.add(s1);
c.add(s2);
c.add(s3);
c.add(s4);
c.add(s5);
//把集合转成数组
Object[] objs = c.toArray();
//遍历这个数组
for(int i = 0; i < objs.length; i ++){
Student s = (Student)objs[i];
System.out.println(s.getName() + "-----" + s.getAge());
}
System.out.println("--------------");
//遍历集合专门的遍历方式,与上面通过数组的方法等价
Iterator it = c.iterator();
//hasNext():判断是否还有下一个元素,有为true,没有为false
while(it.hasNext()){
//获取下一个元素
Student s = (Student)it.next();
System.out.println(s.getName() + "-----" + s.getAge());
}
}
}
//注意:不要多次使用it.next()方法,因为每次使用都是访问员一个对象
3..接口List(继承自Collection接口)
概述:List是一个有序的(这里的有序指的是存储和取出的元素的顺序是一致),有索引的,可重复的集合
除了Collection集合的功能外,List集合的特有功能:
1.添加功能:void add(int index, Object element):在指定位置添加元素
2.获取功能:Object get(int index):获取指定位置的元素
3.列表迭代器:ListIterator listiterator():列表集合特有的迭代器
- ListInterator继承了Iterator迭代器,所有,就可以直接使用hasNext()和Next()方法
- 列表迭代器的特有功能:hasPrevious()方法和previous()方法来实现逆向遍历,但是必须先正向遍历才能逆向遍历,所有一般无意义,不使用
4.删除功能:Obejct remove(int index):根据索引删除元素,返回被删除的元素
5.修改功能:Object set(int index, Object element):根据索引修改元素,返回被修饰的元素
6.获取子集合:List
并发修改异常:
在讲并发修改异常之前,我们先来看一个案例:
//如果集合里有“python”,就添加“java”
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Demo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("python");
list.add("you are the best");
System.out.println(list);//[hello, python]
ListIterator lit = list.listIterator();
while(lit.hasNext()){
String s = (String) lit.next();
if("python".equals(s)){
list.add("java");//抛出异常ConcurrentModificationException
}
}
}
}
这是为什么呢?为什么会抛出异常呢?
这是因为迭代器是依赖于集合存在的,在判断成功后,集合中添加了新的元素,而迭代器并不知道,这个错就叫做并发修改异常
那么,如何解决呢?
有两种方式可以解决,第一种就是通过迭代器迭代元素,迭代器添加元素,但是添加的元素是跟在刚才迭代的元素后面的,还有一种方法就是集合遍历元素,集合修改元素(普通for循环),元素是在最后面添加的
根据我们的解决方案,对源代码进行修改后如下:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Demo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("python");
list.add("you are the best");
System.out.println(list);//[hello, python]
ListIterator lit = list.listIterator();
/*while(lit.hasNext()){
String s = (String) lit.next();
if("python".equals(s)){
list.add("java");//抛出异常ConcurrentModificationException
}
}*/
/* 第一种方法:
while(lit.hasNext()){
String s = (String)lit.next();
if("python".equals(s)){
lit.add("java");
}
}
System.out.println(list);//[hello, python, java, you are the best]
*/
//第二种方法:
for(int i = 0; i < list.size(); i ++){
if("python".equals(list.get(i))){
list.add("java");
}
}
System.out.println(list);//[hello, python, you are the best, java]
}
}
List的子类特点:
- ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率搞
- Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低
- LinkedList:底层数据结构是链表,查询慢,增删快,线程不安全,效率高
ArrayList(实现List接口)
没有什么特殊功能,但是这个比较常用
案例(去除集合中重复的自定义对象):
//Student.java
public class Student {
private String name;
private int age;
public Student(){};
public Student(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;
}
//重写equals方法
@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;
}
}
//Demo5.java
import java.util.ArrayList;
import java.util.Iterator;
public class Demo5 {
public static void main(String[] args) {
ArrayList array = new ArrayList();
ArrayList newArray = new ArrayList();
Student s1 = new Student("卢一", 19);
Student s2 = new Student("赵露思", 22);
Student s3 = new Student("丁禹兮", 23);
Student s4 = new Student("赵露思", 22);
Student s5 = new Student("赵露思", 23);
array.add(s1);
array.add(s2);
array.add(s3);
array.add(s4);
array.add(s5);
Iterator it = array.iterator();
while(it.hasNext()){
Student s = (Student)it.next();
/*
* 需要注意的是集合的contains方法底层走的时候equals方法,
* 而我们的学生类没有重写equals方法,这个时候,
* 默认使用的是他父亲Object的equals方法,比较的是地址值,
* 所以需要重写Student类的equals方法
*/
if(!newArray.contains(s)){
newArray.add(s);
}
}
for(int i = 0; i < newArray.size(); i ++){
Student s = (Student)newArray.get(i);
System.out.println(s.getName() + "-----" + s.getAge());
}
}
}
Vector(实现List接口)
Vector的特有功能:
1.添加功能:public void addElement(Object obj)
2.获取功能:
- public Object elementAt(int index):获取指定位置的元素
- public Enumerate elements():与Iterator相似
- boolean hasMoreElements()
- Object nextElement()
示例代码:
import java.util.Enumeration;
import java.util.Vector;
public class Demo3 {
public static void main(String[] args) {
Vector v = new Vector();
v.addElement("hello");
v.addElement("world");
System.out.println(v.get(1));//world
Enumeration en = v.elements();
while(en.hasMoreElements()){
String s = (String) en.nextElement();
System.out.println(s);//hello world
}
}
}
LinkedList(实现List接口)
LinkedList的特有功能:
1.添加功能:
- public void addFirst(Object e)
- public void addLast(Object e)
2.获取功能:
- public Object getFirst()
- public Object getLast()
3.删除功能:
- public Object removeFirst()
- public Object removeLast()
代码示例:
import java.util.LinkedList;
public class Demo4 {
public static void main(String[] args) {
LinkedList link = new LinkedList();
//添加元素
link.add("hello");
link.add("world");
link.add("java");
System.out.println(link);//[hello, world, java]
//LinkedList独有的添加方法
link.addFirst("python");
link.addLast("go");
System.out.println(link);//[python, hello, world, java, go]
//获取元素
System.out.println(link.get(0));//python
//删除元素
System.out.println(link.removeFirst());//python
System.out.println(link.removeLast());//go
System.out.println(link);//[hello, world, java]
}
}
4.泛型(java5之后有)
概述:早期的Object类型可以接收任意的对象类型,但是在实际的使用中,会有类型转换的问题,也就是存在这一隐患,所有java提供了泛型来解决这个安全问题。泛型是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型,也被称为参数化类型,把类型当做参数一样传递。泛型一般就是用于集合中
格式:<数据类型>,此处的类型只能是引用类型
好处:
1.把运行时期的问题提前到了编译期间
2.避免了强制类型转换
3.优化了程序设计,解决了黄色警告线
示例代码:
import java.util.ArrayList;
import java.util.Iterator;
public class Demo6 {
public static void main(String[] args) {
//定义了String类型的泛型
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("world");
array.add("java");
Iterator<String> it = array.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}
泛型类
把泛型定义在类上,格式为public class 类名<泛型类型...>
泛型类的使用示例:
class ObjectTool<T>{
//这里的泛型T就相当于一个形式参数一样,等待调用者传递类型过来
private T name;
public ObjectTool(){
};
public ObjectTool(T name){
this.name = name;
}
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
}
public class Demo7 {
public static void main(String[] args) {
//没有给定泛型,有黄色警告,编译不报错
/*ObjectTool obj = new ObjectTool();
obj.setName("卢一");
String s = (String) obj.getName();
System.out.println("名字为" + s);
obj.setName("18");
Integer i = (Integer)obj.getName();
System.out.println("名字为" + i);*///编译时不出错,运行时报错ClassCastException
System.out.println("-------------------");
//给定泛型,没有黄色警告,有红色警告
ObjectTool<String> obj = new ObjectTool<String>();
obj.setName("赵露思");
String s = (String)obj.getName();
System.out.println("名字为" + s);
obj.setName("100");
Integer i = (Integer)obj.getName();//直接红色警告,编译时报错
}
}
泛型方法:
把泛型定义在方法上,格式为public <泛型类型> 返回类型 方法名(泛型类型)
泛型方法使用的示例:
class ObjectTool1{
public<T> void show(T t){
//定义了泛型方法,这个方法就可以接收任意类型的参数
System.out.println(t);
}
}
public class Demo8 {
public static void main(String[] args) {
ObjectTool1 obj = new ObjectTool1();
obj.show("hello");//hello
obj.show(100);//100
obj.show(true);true
}
}
泛型接口:
把泛型定义在接口上,格式为public inteface 接口名<泛型类型1...>
interface Inter<T>{
//泛型接口
public abstract void show(T t);
}
class InterImp1<T> implements Inter<T>{
//泛型接口的实现类(泛型类)
@Override
public void show(T t) {
System.out.println(t);
}
}
class InterImp2 implements Inter<String>{
//泛型接口的实现类(不是泛型类)
@Override
public void show(String t) {
System.out.println(t);
}
}
public class Demo9 {
public static void main(String[] args) {
Inter<String> it = new InterImp1<String>();
it.show("hello");//hello
Inter<Integer> it2 = new InterImp1<Integer>();
it2.show(100);//100
System.out.println("---------------");
Inter<String> it3 = new InterImp2();
it3.show("world");//world
}
}
泛型高级之通配符
1.泛型通配符<?>:可以是任意类型,如果没有明确,那么就是Object以及任意的java类
2.?extends E:向下限定,只能是E类以及他的子类们
3.?super E:向上限定,只能是E类以及他们的父类们
5.增强for循环(java5之后有)
增强for循环是for循环的一种,是用来替代迭代器的,底层也是通过迭代器来实现的
格式:for(type 变量名:数组或集合变量名){...}
好处:简化了数组和集合的遍历
弊端:增强for的目标不能为null
如何解决这个弊端呢?对增强for的目标先进行不为null的判断,然后再使用
实例代码:
import java.util.ArrayList;
public class Demo1 {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4};
int sum = 0;
if(array != null){
for(int arg:array){
sum += arg;
}
}
System.out.println(sum);//10
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
if(arrayList != null){
for(String s:arrayList){
System.out.println(s);//hello world java
}
}
}
}
6.静态导入(java5之后才有)
格式:import static 包名....类名.方法名
好处:可以直接导入到方法级别,程序调用是就不用加类名前缀了
静态导入的注意事项:
1.方法必须是静态的
2.如果有多个同名的静态方法,容易不知道使用谁?这个时候要使用,就必须加前缀,这样一来,静态导入的意义就不大了,所有一般不使用静态导入
7.可变参数(java5之后才有)
当我们需要做2个,3个,4个不同参数个数的数量相加时,就需要重载多次方法,这样非常麻烦,所以,为了解决这个问题,java5就为我们提供了可变参数
格式:修饰符 返回值 方法名(数据类型... 变量名){}
注意事项:
1.这里的变量是其实是一个数组
2.如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个
示例代码:
public class Demo2 {
public static void main(String[] args) {
int sum = plus(1, 3, 5, 7, 9);
System.out.println(sum);
}
public static int plus(int... array){
int sum = 0;
for(int i : array){
sum += i;
}
return sum;
}
}
Arrays工具类的asList()方法的使用
作用:可以把数组转换为集合,虽然可以转换为集合,但是集合的长度不能被改变,也就是不能对转换过来的集合进行添加和删除操作
示例代码:
import java.util.Arrays;
import java.util.List;
public class Demo3 {
public static void main(String[] args) {
//asList方法把给定的数组转换为集合
String[] strArray = {"hello", "world", "java"};
List<String> list = Arrays.asList(strArray);
for(String s: list){
System.out.println(s);
}
//asList方法可以接收任意类型的可变参数
List<String> list2 = Arrays.asList("I", "am", "a", "boy");
for(String s: list2){
System.out.println(s);
}
}
}
8.Set接口(继承自Collection)
概述:一个不包含重复元素的collection
没有什么特殊方法,都是继承自Collection的方法
HashSet(实现Set接口)
概述:不保证set的迭代顺序,特别的是它不保证该顺序恒久不变
没有什么特殊的方法,但是我们需要来了解一下HashSet保证元素唯一性的原因
我们通过两个问题来介绍我们的HashSet方法存储东西时保证元素唯一性的原因
1.为什么HashSet存储字符串的时候,字符串内容相同的只存储了一个?
通过查看add方法的源码,我们知道这个方法底层依赖两个方法:hashCode()和equals(),当进行添加操作时,首先先看hashCode()值是否相同,如果hashCode()值相同,继续走equals方法,返回true,说明元素重复,就不添加,返回false,说明元素不重复,直接添加到集合中去;如果hashCode()值不相同,则直接把元素添加到元素中去;如果类没有重写这两个方法,默认使用的是Object的这两个方法,一般来说他们的哈希值是不会相同的,而String类重写了hashCode方法和equals方法,因此,它就可以把内容相同的字符串去掉,只留下一个
2.为什么我们使用HashSet存储自定义对象的使用,即使成员变量的值相同,也能存进集合中?
从前面的第一个问题我们也可以了解到,保证元素唯一性底层依赖的两个方法是hashCode()和equals(),而我们自定义的类如果没有重写这两个方法的话,他就会把我们的成员变量值相同的对象当做不同的对象存入进去,因此,HashSet存储我们的自定义对象时就保证不了元素的唯一性。而解决这个问题的方法当然就是重写hashCode()和equals方法,而Eclipse也为我们提供了自动生成重写hashCode()和equlas()方法的手段(右键-Source)
示例代码:
//Student.java
public class Student {
private String name;
private int age;
public Student(){};
public Student(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;
}
@Override
//重写hashCode()方法
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
//重写equals方法
@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;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
//Demo.java
import java.util.HashSet;
public class Demo{
public static void main(String[] args) {
HashSet<Student> h = new HashSet<Student>();
Student s1 = new Student("赵露思", 19);
Student s2 = new Student("卢一", 19);
Student s3 = new Student("赵丽颖", 22);
Student s4 = new Student("赵露思", 19);
Student s5 = new Student("赵露思", 21);
Student s6 = new Student("诸葛亮", 22);
h.add(s1);
h.add(s2);
h.add(s3);
h.add(s4);
h.add(s5);
h.add(s6);
for(Student s: h){
System.out.println(s);
}
}
}
LinkedHashSet(父类是HashSet):
概述:底层数据结构是由哈希表和链表组成,哈希表保证元素唯一性,链表保证元素有序(存储和取出顺序一致)
TreeSet(实现Set接口)
概述:TreeSet能够对元素按照某种规则进行排序
TreeSet的两个特点:排序和唯一
排序的方式有两种:根据TreeSet的构造方法决定走哪种排序方式
- 自然排序(元素具有比较性):让元素所属的类实现自然排序接口Comparable
- 比较器排序(集合具有比较性)让集合的构造方法接受一个比较器接口的子类对象Comparator
- 如果同时有自然排序和比较器排序,以比较器排序为主
TreeSet底层的数据结构是红黑树(自平衡的二叉树),他存储元素时,有以下特点:
- 第一个元素存储的时候,就直接作为根节点存储
- 从第二个元素开始,每个元素从根节点比较,大的话就作为右孩子,小的话就作为左孩子,如果相等,就添加到树里面去
这样存进去的数据再通过中序遍历(左,中,右顺序)取出来得到就是自然排序好的数据了
自然排序底层依赖的是Comparable接口的compareTo()方法,所以,如果你想重写该方法,就必须先实现Comparable接口
示例代码1(自然排序方法,存储自定义对象并按年龄排序):
//Student.java
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(){};
public Student(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;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Student s) {
/*制定比较的规则:
* 按年龄大小进行排序,如果年龄相同,则判断名字是否相同
* 如果名字和年龄都相同,则返回0,不添加到集合中
*/
int num1 = this.age - s.age;
int num2 = num1 == 0?this.name.compareTo(s.getName()):num1;
return num2;
}
}
//Demo.java
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>();
Student s1 = new Student("赵露思", 19);
Student s2 = new Student("卢一", 23);
Student s3 = new Student("赵丽颖", 24);
Student s4 = new Student("赵露思", 19);
Student s5 = new Student("赵露思", 21);
Student s6 = new Student("卢一", 22);
Student s7 = new Student("卢一", 23);
//如果Student类没有实现接口Comparable,则会报错,通过重写compareTo方法,制定比较的规则
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
for(Student s: ts){
System.out.println(s);
}
}
}
示例代码2(比较器方式,存储自定义对象并按年龄排序)
//Student.java
public class Student {
private String name;
private int age;
public Student(){};
public Student(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;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
//MyComparator
import java.util.Comparator;
public class MyComparator implements Comparator<Student>{
@Override
public int compare(Student s1, Student s2) {
int num1 = s1.getAge() - s2.getAge();
int num2 = num1 == 0 ? s1.getName().compareTo(s2.getName()) : num1;
return num2;
}
}
//Demo2.java
public class Demo2 {
public static void main(String[] args) {
//比较器排序,也可以通过创建一个匿名对象来实现,而不用单独创建一个类
TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());
Student s1 = new Student("赵露思", 19);
Student s2 = new Student("卢一", 23);
Student s3 = new Student("赵丽颖", 24);
Student s4 = new Student("赵露思", 19);
Student s5 = new Student("赵露思", 21);
Student s6 = new Student("卢一", 22);
Student s7 = new Student("卢一", 23);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
for(Student s: ts){
System.out.println(s);
}
}
}
9.Map集合
如果我们有这样一个需求,仅仅知道学号,就想知道学生姓名的情况,那么有什么好的存储方式呢?针对我们这样的需求,java就为我们提供了一种新的集合Map
概述:可以存储键值对的元素
特点:将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值上
Map集合和Collection集合的区别
1.Map集合存储元素是成对出现的,Map集合的键是唯一的,值是可以重复的,Map集合的数据结构只针对键有效,跟值无关
2.Collection集合存储元素是单独出现的,Collection的儿子Set是唯一的,List是可重复的,Collection集合的数据结构是针对元素有效的
Map集合的功能们
1.添加功能:
- V put(K key, V value):添加元素,如果键是第一次存储,就直接存储元素,返回null,如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值
2.删除功能:
- void clear():移除所有键值对元素
- V remove(Object key):根据键删除键值对,并把值返回
3.判断功能:
- boolean containsKey(Object key):判断集合是否包含指定的键
- boolean containsVlaue(Object value):判断集合是否包含指定的值
- boolean isEmpty:判断集合是否为空
4.获取功能:
- Set<Map.Entry<K, V>> entry():获取所有键值对对象的集合
- getKey():获取键
- getValue():获取值
- V get(Object key):根据键获取值
- Set
keySet():获取集合中所有键的集合 - Collection
values():获取集合中所有值的集合
5.长度功能:
- int size():返回集合中的键值对的对数
遍历Map集合代码示例:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Demo1 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
//添加元素
map.put("卢一", "黄伊");
map.put("邓超", "孙俪");
map.put("韩少君", "陈芊芊");
map.put("海王", "海后");
System.out.println("map:" + map);//map:{邓超=孙俪, 卢一=黄伊, 韩少君=陈芊芊, 海王=海后}
//获取所有键值对对象的集合
Set<Map.Entry<String, String>> set = map.entrySet();
for(Map.Entry<String, String> me: set){
String key = me.getKey();
String value = me.getValue();
System.out.println(key + "-----" + value);
}
/*等价于上面的通过获取所有键值对对象的方法来遍历集合的代码
* //获取所有的键
Set<String> set = map.keySet();
//根据键获取值
for(String key:set){
String value = map.get(key);
System.out.println(key + "-----" + value);
}*/
}
}
10.HashMap(实现Map接口)
概述:是基于哈希表的Map接口实现,哈希表的作用就是用来保证键的唯一性的
代码示例(用Student类做键,用String做值存储到集合中):
//Student.java
public class Student {
private String name;
private int age;
public Student(){
};
public Student(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;
}
@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;
}
}
//Demo.java
import java.util.HashMap;
import java.util.Set;
public class Demo {
public static void main(String[] args) {
HashMap<Student, String> hm = new HashMap<Student, String>();
Student s1 = new Student("卢一", 18);
Student s2 = new Student("黄伊", 17);
Student s3 = new Student("赵露思", 20);
Student s4 = new Student("赵露思", 20);
hm.put(s1, "1111");
hm.put(s2, "6666");
hm.put(s3, "8888");
hm.put(s4, "9999");
Set<Student> set = hm.keySet();
for(Student key:set){
String value = hm.get(key);
System.out.println(key.getName() + "-----" + key.getAge() + "-----" + value);
}
}
}
LinkedHashMap(是HashMap的子类)
概述:是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序,由哈希表保证键的唯一性,由链表保证键盘的有序(存储和取出的顺序一致)
11.TreeMap
概述:是基于红黑树的Map接口的实现,实现原理跟TreeSet相似
示例代码:
//Student.java
public class Student {
private String name;
private int age;
public Student(){
};
public Student(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;
}
@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;
}
}
//Demo2.java
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;
public class Demo3 {
public static void main(String[] args) {
TreeMap<Student, String> tm = new TreeMap<Student, String>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num1 = s1.getAge() - s2.getAge();
int num2 = (num1 == 0?s1.getName().compareTo(s2.getName()) : num1);
return num2;
}
});
Student s1 = new Student("卢一", 18);
Student s2 = new Student("黄伊", 17);
Student s3 = new Student("赵露思", 20);
Student s4 = new Student("赵露思", 20);
Student s5 = new Student("赵丽颖", 24);
tm.put(s1, "1111");
tm.put(s2, "6666");
tm.put(s3, "8888");
tm.put(s4, "9999");
tm.put(s5, "5555");
Set<Student> set = tm.keySet();
for(Student key:set){
String value = tm.get(key);
System.out.println(key.getName() + "-----" + key.getAge() + "-----" + value);
}
}
}
12.Collections类
概述:是针对集合进行操作的工具类,都是静态方法
静态方法:
- pubilc static
void sort(List list):排序 默认情况下是自然排序 - public static
void binarySearch(List<?> list, T key):二分查找 - public static
T max(Collection<?> coll):求最大值 - public static void reverse(List<?> list):反转
- public static void shuffle(List<?> list):随机置换(可以做扑克牌洗牌)
代码示例(模拟斗地主洗牌和发牌):
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;
public class Demo6 {
public static void main(String[] args) {
//创建一个HashMap集合来存放所有的纸牌,相当于一个扑克牌盒
HashMap<Integer, String> hm = new HashMap<Integer, String>();
//创建一个ArrayList集合,方便后面洗牌
ArrayList<Integer> array = new ArrayList<Integer>();
//创建扑克牌的花色和点数数组
String[] colors = {"♠","♥", "♣", "♦"};
String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
int index = 0;
//拼接得到一张张扑克牌,并把它放进HashMap纸盒里
for(String number: numbers){
for(String color: colors){
String poker = color.concat(number);
hm.put(index, poker);
array.add(index);
index ++;
}
}
hm.put(index, "小王");
array.add(index);
index ++;
hm.put(index, "大王");
array.add(index);
//洗牌功能
Collections.shuffle(array);
//发牌(发的也是编号,为了保证编号是排序的,就创建了TreeSet集合接受)
TreeSet<Integer> luYi = new TreeSet<Integer>();
TreeSet<Integer> luEr = new TreeSet<Integer>();
TreeSet<Integer> luSan = new TreeSet<Integer>();
TreeSet<Integer> diPai = new TreeSet<Integer>();
for(int i = 0; i < array.size(); i ++){
if(i >= array.size() - 3){
diPai.add(array.get(i));
}else if(i % 3 == 0){
luYi.add(array.get(i));
}else if(i % 3 == 1){
luEr.add(array.get(i));
}else if(i % 3 == 2){
luSan.add(array.get(i));
}
}
//看牌
lookPoker("卢一", luYi, hm);
lookPoker("卢二", luEr, hm);
lookPoker("卢三", luSan, hm);
lookPoker("底牌", diPai, hm);
}
//实现看牌的功能
public static void lookPoker(String name, TreeSet<Integer> ts, HashMap<Integer, String> hm){
System.out.println(name + "的牌是");
for(Integer key:ts){
String value = hm.get(key);
System.out.print(value + " ");
}
System.out.println();
}
}
13.面试题
1.HashMap和Hashtable的区别
答:Hashtable线程安全,效率低,不允许null做键和值,HashMap线程不安全,效率高,允许null做键和值
2.List,Set,Map等接口是否都继承自Map接口?
答:List,Set继承自Collection接口,而Map本身就是一个顶层接口
3.Collection和Collections的区别
答:Collection是单列集合顶层接口,而Collections是针对集合操作的工具,有对集合进行排序和二分查找的方法
14.集合内容总结:
1.Collection(单列集合)
- List(有序,可重复的)
- ArrayList:底层数据是数组,查询快,增删慢;线程不安全,效率高
- Vector: 底层数据是数组,查询快,增删慢;线程安全,效率低
- LinkedList:底层数据结构是链表,查询慢,增删快;线程不安全,效率高
- Set(无序,唯一)
- HashSet:底层数据结构是哈希表,哈希表主要依赖hashCode()和equals()方法
- LinkedHashSet:底层数据结构是由链表和哈希表组成,由链表保证元素有序,由哈希表保证元素唯一
- TreeSet(有序,唯一):底层数据结构是红黑树(是一种自平衡的二叉树),底层依赖的是compareTo()方法,有两种排序方法(比较排序和选择器排序)
- HashSet:底层数据结构是哈希表,哈希表主要依赖hashCode()和equals()方法
2.Map(双列集合):Map集合的数据结构仅仅针对键有效,与值无关;存储的是键值对形式的元素,键唯一,值可重复,线程不安全,效率高
- HashMap
- LinkedHashMap
- TreeMap
- Hashtable:使用方法跟Map一致,Map的出现替代了它,线程安全,效率低
案例
1.根据输入的字符串,把输入的字符串的各个字符作为键,把各个字符对应出现的个数作为值,输出格式为:字符(个数)字符(个数),如a(2)b(3)c(5)
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
public class Demo4 {
public static void main(String[] args) {
HashMap<Character, Integer> hp = new HashMap<Character, Integer>();
//键盘录入值
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String input = sc.nextLine();
//把得到的字符串转换为字符
char[] chs = new char[input.length()];
chs = input.toCharArray();
//遍历字符数组,得到每一个字符,并存储到集合中
for(int i = 0; i < chs.length; i ++){
Integer x = hp.get(chs[i]);
if(x == null){
hp.put(chs[i], 1);
}else{
x ++;
hp.put(chs[i], x);
}
}
//定义一个StringBuilder对象,方便后面的字符串拼接
StringBuilder sb = new StringBuilder();
//按需求拼接字符串
Set<Character> set = hp.keySet();
for(Character key : set){
Integer value = hp.get(key);
sb.append(key).append("(").append(value).append(")");
}
//把StringBuilder对象转换为字符串
String result = sb.toString();
System.out.println("result:" + result);
}
}
//结果:
请输入一个字符串:
aabbccddeeff
result:a(2)b(2)c(2)d(2)e(2)f(2)
2.集合嵌套
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
public class Demo5 {
public static void main(String[] args) {
HashMap<String, ArrayList<String>> hp = new HashMap<String, ArrayList<String>>();
ArrayList<String> array1 = new ArrayList<String>();
array1.add("吕布");
array1.add("周瑜");
hp.put("三国演义", array1);
ArrayList<String> array2 = new ArrayList<String>();
array2.add("令狐冲");
array2.add("林平之");
hp.put("笑傲江湖", array2);
ArrayList<String> array3 = new ArrayList<String>();
array3.add("郭靖");
array3.add("黄蓉");
hp.put("神雕侠侣", array3);
Set<String> set = hp.keySet();
for(String key:set){
System.out.println(key);
ArrayList<String> array = hp.get(key);
for(String s: array){
System.out.println("\t" + s);
}
}
}
}
3.登录注册案例
分析:
1.有哪些类:
- 用户类
- 用户基本描述类
- 用户操作类
- 测试类
2.每个类有哪些东西
-
用户基本描述类
- 成员变量:用户名,密码
- 构造方法:无参构造
- 成员方法:getXxx()/setXxx()
-
用户操作类:
- 登陆,注册
-
测试类:
- main方法
3.类与类之间的关系是什么
- 在测试类中创建用户操作类和用户基本描述类的对象,并使用其功能
4.选择一种分包的方式:
- 按功能划分
- 按模块划分
- 先按模块,再按功能划分
5.选择按功能划分
- 用户基本描述类包:cn.luyi.pojo
- 用户操作接口:cn.luyi.dao
- 用户操作类包:cn.luyi.dao.impl
- 用户测试类:cn.luyi.test
代码实现:
//User.java文件
package cn.luyi.pojo;
//定义一个User描述类
public class User {
private String username;
private String password;
public User(){};
public User(String username, String password){
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
//UserDao.java
package cn.luyi.dao;
//定义一个用户操作类接口
import cn.luyi.pojo.User;
public interface UserDao {
public abstract boolean islogin(String username, String password);
public abstract void register(User user);
}
//UserDaoImpl.java
package cn.luyi.dao.impl;
//定义了一个用户操作类的实现类
import java.util.ArrayList;
import cn.luyi.dao.UserDao;
import cn.luyi.pojo.User;
public class UserDaoImpl implements UserDao{
//这里用static修饰可供多个对象使用,而不只属于一个对象里面
private static ArrayList<User> list = new ArrayList<User>();
@Override
public boolean islogin(String name, String password) {
boolean flag = false;
for(int i = 0; i < list.size(); i ++){
User user = list.get(i);
if(name.equals(user.getUsername())&&password.equals(user.getPassword())){
return true;
}
}
return flag;
}
@Override
public void register(User user) {
list.add(user);
}
}
//UserText.java
package cn.luyi.text;
//测试类
import java.util.Scanner;
import cn.luyi.dao.UserDao;
import cn.luyi.dao.impl.UserDaoImpl;
import cn.luyi.pojo.User;
public class UserText {
public static void main(String[] args) {
while(true){
System.out.println("----------欢迎光临----------");
System.out.println("1 登录");
System.out.println("2 注册");
System.out.println("3 退出");
Scanner sc= new Scanner(System.in);
System.out.println("请输入你想要进行的操作:");
String choice = sc.nextLine();
UserDao user = new UserDaoImpl();
switch(choice){
case "1":
System.out.println("----------欢迎来到登录页面----------");
System.out.println("请输入登录用户名:");
String username = sc.nextLine();
System.out.println("请输入登录密码:");
String password = sc.nextLine();
if(user.islogin(username, password)){
System.out.println("登录成功");
System.exit(0);
}else{
System.out.println("用户名或密码输入错误");
}
break;
case "2":
System.out.println("----------欢迎来到注册页面----------");
System.out.println("请输入注册的用户名:");
String newUsername = sc.nextLine();
System.out.println("请输入注册的密码:");
String newPassword = sc.nextLine();
User newUser = new User(newUsername, newPassword);
user.register(newUser);
System.out.println("注册成功");
break;
case "3":
default:
System.out.println("谢谢使用,欢迎下次再来");
System.exit(0);
break;
}
}
}
}
作者:卢一
出处:http://www.cnblogs.com/luyi001/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。