JAVA07-Object类、Date类、Calendar类、System类、包装类、Collection、泛型、List、Set、数据结构、Collections
1.概述
java.lang.Object类是java语言中的跟类,即所有类中的父类·它类 Object
是类层次结构的根类。每个类都使用 Object
作为超类。所有对象(包括数组)都实现这个类的方法。它包含11个方法。
1 package cn.itcast.demo01.demo01; 2 3 public class Demo01ToString { 4 public static void main(String[] args) { 5 // Person类默认继承了Object类,可以使用toString方法 6 // 创建Person对象 7 Person person = new Person("张三",19); 8 String s = person.toString(); 9 // 直接打印对象的名字,其实是调用对象的toStirng方法 person = person.toString 10 System.out.println(s);//cn.itcast.demo01.demo01.Person@75412c2f 11 System.out.println(person);//cn.itcast.demo01.demo01.Person@75412c2f 12 } 13 } 14 15
1 package cn.itcast.demo01.demo01; 2 3 public class Person { 4 private String name; //私有成员变量 5 private int age;// 私有成员变量 6 //添加对应的构造方法 7 8 9 public Person() { 10 } 11 // 直接打印对象的地址值没有意义,需要重写toString方法 12 13 14 @Override 15 public String toString() { 16 // 默认调用return super.toString();我们重写 17 // return "abc";// 方法返回值是String 18 return "Person{ name = " + name + " , age = " + age + "}"; 19 } 20 21 public Person(String name, int age) { 22 this.name = name; 23 this.age = age; 24 } 25 26 // 添加成员变量的对应set/get方法 27 public void setName(String name) { 28 this.name = name; 29 } 30 31 public void setAge(int age) { 32 this.age = age; 33 } 34 35 public String getName() { 36 return name; 37 } 38 39 public int getAge() { 40 return age; 41 } 42 } 43
实际过程中,可以直接用alt + insert 选择tostring(),自动帮忙重写。
1.1. 判断类是否重写了toString()方法
看一个类是否重写了toString()方法,直接打印改类的对象的名字即可,如果是地址值就是没有重写,如果重写了,那就按照重写的方式打印
在Java中每个类都默认继承Object类,除非声明继承于某个类。 而toString 方法来自于Object 类,作用是: 返回一个字符串,是该Java对象的内存地址经过哈希算法得出的int类型的值在转换成十六进制。。 换句话说,该方法返回一个字符串,它的值大概就是等于:
getClass().getName()+’@’+Integer.toHexString(hashCode()) 这是一个内存地址经过哈希算法得出的int类型的值在转换成十六进制的东西。等同的看作Java对象在堆中的内存地址。
1.2 equals方法
Home 光标快速定位至行首 End 光标快速定位至行末
equals(Object obj)
指示其他某个对象是否与此对象“相等”。
格式:
boolean 变量名 = 对象名1.equals(对象名2);
当然这是比较两个对象的地址值,大部分情况下是无意义的,也需要重写。
可以考虑重写修改为比较两个对象的属性
1 @Override 2 public boolean equals(Object obj) { 3 // return super.equals(obj); 4 // 使用向下转型,把object类型转为Person 5 Person p = (Person) obj; 6 // 比较两个对象的属性,一个是调用方法this(p1),一个是p(obj) 7 boolean b = this.name.equals(p.name)&& this.age == p.age; 8 return b;
1.3 Objects类-equals方法
2.Date类-瞬间的时间
2.1 毫秒值的概念
毫秒值,有两个概念
- 时间原点:1970年1月1日 0:0:0(英国格林威治),我国会时间加8小时(东八区GMT)。
- 可以将日期转换为毫秒计算,结果将毫秒转为日期
将当期日期到时间原点之家一共经历了多少毫秒。
构造方法:主要学习 Date() 和
Date(long time)
Date()构造方法
分配 Date
对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
date的构造函数中,无参构造方法-Date()获取的是当前系统的日期与时间
1 package cn.itcast.demo01.demo01.demo02; 2 3 import java.util.Date; 4 5 public class Demo02Date { 6 public static void main(String[] args) { 7 demo01(); 8 } 9 public static void demo01(){ 10 Date date = new Date(); 11 System.out.println(date); 12 } 13 }
Date(long date)
分配 Date
对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。参数是毫秒值。
成员方法
getTime()
返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。也就是当前时间转换为毫秒数值
1 package cn.itcast.demo01.demo01.demo02; 2 import java.util.Date; 3 public class Demo02Date { 4 public static void main(String[] args) { 5 demo01(); 6 System.out.println("===="); 7 demo02(); 8 System.out.println("===="); 9 demo03(); 10 } 11 12 private static void demo03() { 13 Date date3 = new Date(); 14 long time = date3.getTime(); 15 System.out.println(time); 16 }
如果想返回中文,可以使用DateFormat类方法;
DateFormat类,返回的是一个文本。`java.text.DateFormat` 是日期/时间格式化子类的抽象类(public abstract class DateFormat extends Format
),我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。
、
format(Date date)
将一个 Date 格式化为日期/时间字符串。
作用 格式化(日期→文本),解析(文本 → 日期)
2.2. 成员方法
String format(Date date)按照指定的模式,把date日期,格式化为符合模式的字符串
Date parse(String source)按符合模式的字符串,解析为date日期。
由于DateFormat类是一个抽象类,无法直接创建对象使用,可以使用Dateformat的子类,而使用子类,必然是构造方法的对象,该处子类是 SimpleDateFormat--java.text.SimpleDateFormat 继承了DateFormat
SimpleDateFormat(String pattern)
用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat
。 其中 参数:pattern 就是我们传递的模式,模式如下图:
写对应的模式
“yyyy-MM-dd HH:mm:ss z” 或“yyyy年MM月dd天 HH时mm分ss秒 z”
注意:模式中大小写已固定,需区分,但连接线可中文或-
2.3. 日历类 Calendar类,是Date 类之后的,因此它取消了很多Date类的方法。
Calendar
类是一个抽象类,它为特定瞬间与一组诸如 YEAR
、MONTH
、DAY_OF_MONTH
、HOUR
等 日历字段
之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。
该类还为实现包范围外的具体日历系统提供了其他字段和方法。这些字段和方法被定义为 protected
。
与其他语言环境敏感类一样,Calendar
提供了一个类方法 getInstance
,以获得此类型的一个通用的对象。Calendar
的 getInstance
方法返回一个 Calendar
对象,其日历字段已由当前日期和时间初始化:
Calendar rightNow = Calendar.getInstance();
3. System类
包含一些有用的类字段和方法。它不能被实例化。
system类提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在system类的api文档中,常用的房房有:
static long
currentTimeMillis()
返回以毫秒为单位的当前时间。比方说测试效率
1 package cn.itcast.demo01.demo01.demo02; 2 import java.util.Calendar; 3 import java.util.Date; 4 public class Demo02Date { 5 public static void main(String[] args) { 6 demo01(); 7 } 8 // 日历类 9 private static void demo01() { 10 long s = System.currentTimeMillis();// 程序执行前,获取一次毫秒值 11 for (int i = 0; i < 9999; i++) { 12 System.out.println(i); 13 } 14 long e = System.currentTimeMillis();// 程序执行后,获取一次毫秒值 15 System.out.println("程序共耗时"+ (e - s)+"毫秒"); 16 } 17 18 }
static void
arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束
src
- 源数组。
srcPos
- 源数组中的起始位置。
dest
- 目标数组。
destPos
- 目标数据中的起始位置。
length
- 要复制的数组元素的数量。
IndexOutOfBoundsException
- 如果复制会导致对数组范围以外的数据的访问。
ArrayStoreException
- 如果因为类型不匹配而使得无法将 src
数组中的元素存储到 dest
数组中。
NullPointerException
- 如果 src
或 dest
为 null
。1 package cn.itcast.demo01.demo01.demo02; 2 import java.util.Arrays; 3 import java.util.Calendar; 4 import java.util.Date; 5 public class Demo02Date { 6 public static void main(String[] args) { 7 demo02(); 8 } 9 private static void demo02() { 10 // 定义源数组 11 int[] src = {1, 2, 3, 4, 5}; 12 // 定义目标数组 13 int[] dest = {6, 7, 8, 9, 10}; 14 System.out.println("复制前" + Arrays.toString(dest)); 15 // 使用system的方法 16 System.arraycopy(src,0,dest,0,3); 17 System.out.println("复制后" +Arrays.toString(dest)); 18 19 } 20 21 }
4.StringBuilder类 也称之为 字符串缓冲区
String
类代表字符串,底层是数组。Java 程序中的所有字符串字面值(如 "abc"
)都作为此类的实例实现。 string类底层是被final修饰的、字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。例如:
String str = "abc";
等效于:
char data[] = {'a', 'b', 'c'}; String str = new String(data);
因此在StringBuilder类可以提供字符串的效率,在进行字符串的相加,内存中存在多个字符串,占用空间多,效率低下。而StringBuilder类底层是一个数组,但没有被final修饰,可以改变长度,默认长度为16 byte【】 value = new byte【16】;如果超过默认容量,会自动扩容。
string本身是不可改变的,它只能赋值一次,每一次内容发生改变,都会生成一个新的对象,然后原有的对象引用新的对象,而每一次生成新对象都会对系统性能产生影响,这会降低.NET编译器的工作效率。string操作示意图如图1所示。
、
StringBuilder类则不同,每次操作都是对自身对象进行操作,而不是生成新的对象,其所占空间会随着内容的增加而扩充,这样,在做大量的修改操作时,不会因生成大量匿名对象而影响系统性能。StringBuilder操作示意图如下图所示。
构造方法常用的是2个
StringBuilder()
构造一个不带任何字符的字符串生成器,其初始容量为 16 个字符。
StringBuilder(String str)
构造一个字符串生成器,并初始化为指定的字符串内容。
StringBuilder常用的方法有2个:
- `public StringBuilder append(...)`:添加任意类型数据的字符串形式,并返回当前对象自身(因为append返回值是this,自己的自身。以后使用可以不用返回值)。
1 package cn.itcast.demo01.demo01.demo02; 2 import java.util.Arrays; 3 import java.util.Calendar; 4 import java.util.Date; 5 public class Demo02Date { 6 public static void main(String[] args) { 7 // 创建一个StringBuilder对象 8 StringBuilder bu1 = new StringBuilder(); 9 // 使用append方法 10 StringBuilder bu2 = bu1.append("abc"); 11 // 直接出值,而不是地址值,说明该方法重写了toString方法 12 System.out.println(bu1); 13 System.out.println(bu2); 14 // 打印ture,说明两个对象是同一个对象,也就是说append方法返回的是this 15 System.out.println(bu1 == bu2); 16 System.out.println(bu1.append(1)); 17 } 18 }
链式编程可以使得代码可读性高,链式编程的原理就是返回一个this对象,就是返回本身,达到链式效果。
- public String reverse()`:反转,依然是自身
如:sb.reverse();
- `public String toString()`:将当前StringBuilder对象转换为String对象。
String→StringBuilder 可以使用StringBuilder的构造方法 StringBuilder(String str)
StringBuilder → String可以使用StringBuilder的方法:public String toString()`:
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.util.Arrays; 5 import java.util.Scanner; 6 7 public class Demo05StaticField { 8 public static void main(String[] args) { 9 String atr = "hello"; 10 System.out.println("atr:" + atr); 11 System.out.println("以上是字符串====="); 12 StringBuilder strBu = new StringBuilder(atr); 13 System.out.println("strBu:"+ strBu); 14 System.out.println("以上是StringBuilder====="); 15 // 往strBu添加数据 16 strBu.append("world"); 17 System.out.println("strBu:" + strBu); 18 System.out.println("以上是StringBuilder添加数据====="); 19 String dtr2 = strBu.toString(); 20 System.out.println(dtr2); 21 } 22 }
5.包装类
Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:
| 基本类型 | 对应的包装类(位于java.lang包中) |
| ------- | --------------------- |
| byte | Byte |
| short | Short |
| int | **Integer** |
| long | Long |
| float | Float |
| double | Double |
| char | **Character** |
| boolean | Boolean |
5.1.装箱
装箱就是把基本类型的数据,包装到包装类中(基本数据类型→包装类)
构造方法:
Integer(int value) 构造一个新分配的 Integer 对象,它表示指定的 int 值。
Integer(String s) 构造一个新分配的 Integer 对象,它表示 String 参数所指示的 int 值。
静态方法:
static Integer valueOf(int i) 返回一个表示指定的 int 值的 Integer 实例。
static Integer valueOf(String s) 返回保存指定的 String 的值的 Integer 对象。
5.2.拆箱
拆箱就是把包装类中取出基本类型的数据(包装类→基本数据类型)
成员方法:
int intValue() 以 int 类型返回该 Integer 的值。
long longValue() 以 long 类型返回该 Integer 的值。
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.util.Arrays; 5 import java.util.Scanner; 6 7 public class Demo05StaticField { 8 public static void main(String[] args) { 9 // 装箱 10 // 构造方法 11 int a = 11; 12 Integer in1 = new Integer(1);// 方法上有横线,说明过时了 13 System.out.println(in1);// 打印不是地址值,说明重写了toString方法 14 System.out.println("======"); 15 Integer in2 = new Integer("22");// 给一个a数值字符串,也可以 16 Character in3 = new Character('a');// 给一个字母字符串,得变化类型否则报错NumberFormatException: For input string: 17 System.out.println(in3); 18 //静态方法 19 Integer in4 = Integer.valueOf(1);// valueOf是静态方法,不需要生成对象调用 20 System.out.println(in4); 21 // 拆箱 22 int i = in1.intValue(); 23 System.out.println(i); 24 25 } 26 }
5.3.自动装箱与自动拆箱
由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:
Integer i = 1;//自动装箱。相当于Integer i = Integer.valueOf(1);
i = i + 4;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 4;
//加法运算完成后,再次装箱(i = new Integer(5)),把基本数值转成对象。
```
早期学习的ArrayList集合无法存储整数,但可以存储Inreger包装类
5.4.基本数据类型和字符串的转换
- 基本数据类型to字符串类型(包含char类型)
(1)toString
①基本数据类型的都有一个静态方法toString(),转换时可以采用 "封装类型.toString(对应的基本数据类型字面值)" 方法来转换成字符串。
例:将int类型的20转成字符串,String s = Int.toString(20)。
②将基本数据类型封装成相应的封装类型对象,采用 "基本数据类型对象.toString()"方法转换成字符串。
例:将double类型的425.0转成字符串,Double d = new Double(425.0); String s =d.toString();
(2)+"",将基本数据类型字面值与空字符串""通过"+"连接
例:将long类型的200转成字符串,String s = 200 + "";
(3)valueOf,利用String类的静态方法valueOf()。
例:将char类型的'b'转成字符串,String.valueOf('b');
- 字符串to基本数据类型(不包含char类型)
(1)利用基本数据类型包装类的parseXxx方法
例:将字符串"123"转成int类型,int i = Integer.parseInt("123");
(需注意字符串转成基本数据类型时字符串内容必须是基本数据类型的字面值,否则编译虽然能通过,但运行时出现NumberFormatException)
(2)用字符串构造基本类型的封装对象,再调用封装对象的xxxValue方法
例:将字符串"20.5"转成double类型,Double d1 = new Double("20.5"); double d2 = d1.doubleValue();
6.Collection集合
Collection 层次结构 中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
集合和数组既然都是容器,它们有啥区别呢?
* 数组的长度是固定的。集合的长度是可变的。
* 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
1 Collection<String> coll = new ArrayList<>();//创建集合对象 2 System.out.println(coll);// 打印为空,说明重写了toString方法
6.1.单列集合的体系
List集合分为ArrayList LinkedList Voctor三种集合
其中List集合 有索引,可以存储重复元素,可以保证存储顺序
ArrayList 底层是数组实现的,查询快,增删慢
LinkedList 底层是链表实现的,查询慢,增删快;
Set集合 无索引,不可以存储重复的元素,存取无序
HashSet 底层是哈希表+(红黑树)实现的,无索引,不可以存储重复元素,存取无序
LinkedHashSet 底层是哈希表+链表实现的,无索引,不可以存储重复怨怒是,可以保证存取顺序
TreeSet 底层是二叉树实现,一般用于排序。
6.2.Collection集合常用的方法,这些方法定义在最顶层collection方法中。
boolean add(E e); 向集合中添加元素
1 Collection<String> coll = new ArrayList<>();//创建集合对象 2 3 Boolean id=coll.add("hello");// 返回true 4 coll.add("world"); 5 coll.add("heima"); 6 coll.add("java"); 7 System.out.println(coll); 8 System.out.println(id);// 返回true
boolean remove(E e);删除集合中某个元素
1 boolean result = coll.remove("hello");// 返回true 2 System.out.println(result); 3 System.out.println(coll);
void clear(); 清空集合中所有的元素
1 coll.clear(); 2 System.out.println(coll);
boolean contains(E e); 判断集合中是否包含某个元素
1 Boolean r = coll.contains("java");// 返回true 则是包含 2 System.out.println(r);
int size(); 判断集合元素个数
1 2 int a = coll.size();// 返回0,即为空 3 System.out.println(a); 4
Object[] toArray(); 将集合转为数组,返回Object数组,变成数组就可以遍历数组
1 Object[] arr = coll.toArray(); 2 for (int i = 0; i < coll.size() ; i++) { 3 System.out.print(arr[i] + " "); 4 5 }
boolean isEmpty();判断集合是否为空
1 2 1 Boolean l= coll.isEmpty();// 返回true 则是空 3 2 System.out.println(l); 4
6.3.Itertor也称之为迭代器 对集合遍历
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口`java.util.Iterator`。`Iterator`接口也是Java集合中的一员,但它与`Collection`、`Map`接口有所不同,`Collection`接口与`Map`接口主要用于存储元素,而`Iterator`主要用于迭代访问(即遍历)`Collection`中的元素,因此`Iterator`对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
* `public Iterator iterator()`: 获取集合对应的迭代器,用来遍历集合中的元素的。
下面介绍一下迭代的概念:先判断后取出
* **迭代**:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
6.3.1.Iterator接口的常用方法如下:
无法直接使用,需要Itertor接口的实现类对象,获取实现类方法比较特殊,在Collection集合中有一个iterator方法返回集合的迭代器实现类对象。然后用下面两个方法。
* `public E next()`:返回迭代的下一个元素。
* `public boolean hasNext()`:如果仍有元素可以迭代,则返回 true。
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.util.*; 5 6 public class Demo05StaticField { 7 public static void main(String[] args) { 8 Collection<String> coll = new ArrayList<>();//创建集合对象 9 coll.add("姚明");// 10 coll.add("科比"); 11 coll.add("麦迪"); 12 coll.add("詹姆斯"); 13 System.out.println(coll); 14 /* 15 使用集合类的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态) 16 注意: Iterator<E>接口也有范型,迭代器的泛型跟着结合走,集合是什么泛型,迭代器就是什么泛型 17 */ 18 // 多态 接口 实现类对象 19 Iterator<String> st = coll.iterator(); 20 System.out.println(st);// 返回是地址,未重写toString()方法 21 boolean b = st.hasNext();// 判断有无下一个元素 22 System.out.println(b);// 返回true 23 // 未知循环次数,使用while循环 24 while(st.hasNext()){ 25 String str = st.next(); 26 System.out.print(str + " "); 27 } 28 } 29 } 30 31
for循环格式比较繁琐:
1 for (Iterator<String> st2 = coll.iterator(); st2.hasNext();){// hasNext()自动向下 2 String str = st.next(); 3 System.out.print(str + " "); 4 }
迭代器实现的原理:先判断 有就取
tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
6.3.2增强for循环
是jdk1.5以后出来的一个高级for循环,用来专门遍历数组和集合的,它的内部原理其实是Iterator迭代器,遍历的过程中,不能对集合的元素进行增删操作。
for (元素的数据类型 遍历 : Collection集合或数组){
代码块
}
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
#### 练习1:遍历数组
~~~java
public class NBForDemo1 {
public static void main(String[] args) {
int[] arr = {3,5,6,87};
//使用增强for遍历数组
for(int a : arr){//a代表数组中的每个元素
System.out.println(a);
}
}
}
~~~
#### 练习2:遍历集合
~~~java
public class NBFor {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("小河神");
coll.add("老河神");
coll.add("神婆");
//使用增强for遍历
for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
System.out.println(s);
}
}
}
~~~
> tips: 新for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。
7. 泛型:泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型,泛型也可以看成一个变量,用来接收数据类型,例如集合中的对象,也可以称之为泛型。此前见到的两个泛型。:
E e Element
T t Type
7.3.1 泛型概述
在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。
大家观察下面代码:
~~~java
public class GenericDemo {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("abc");
coll.add("itcast");
coll.add(5);//由于集合没有做任何限定,任何类型都可以给其中存放
Iterator it = coll.iterator();
while(it.hasNext()){
//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
String str = (String) it.next();
System.out.println(str.length());
}
}
}
~~~
程序在运行时发生了问题**java.lang.ClassCastException**。 为什么会发生类型转换异常呢? 我们来分析下:由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastExcepti 怎么来解决这个问题 Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象。例如都是存储字符串对象。因此在JDK5之后,新增了**泛型**(**Generic**)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.util.*; 5 6 public class Demo05StaticField { 7 public static void main(String[] args) { 8 // 创建Stydent对象,泛型使用Integer 9 Student<Integer> st = new Student<>(); 10 st.setName(1); 11 Integer name = st.getName(); 12 System.out.println(name); 13 } 14 } 15 16
1 package cn.itcast.demo01.demo01.demo05; 2 3 public class Student< E > {// 加上泛型 <E> 4 private E name;// 姓名 5 public E getName() { 6 return name; 7 } 8 public void setName(E name) { 9 this.name = name; 10 } 11 } 12
使用格式:**调用方法时,确定泛型的类型**
public class Student< E > {// 加上泛型 <E>private E name;// 姓名
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
7.3.2 定义泛型的方法
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
public <M> void method(M m){
方法体;
}
含有泛型的方法,在调用方法的时候,确定泛型的数据类型,传递什么类型的参数,泛型就是什么类型。
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.util.*; 5 6 public class Demo05StaticField { 7 public static void main(String[] args) { 8 Student stu = new Student(); 9 stu.method01(1); 10 stu.method01("abc"); 11 Student.method02("dw");//调用静态泛型方法 12 } 13 } 14 15 package cn.itcast.demo01.demo01.demo05; 16 17 public class Student {// 定义泛型方法 18 public <M> void method01(M m){ 19 System.out.println(m); 20 } 21 public static <M> void method02(M m){// 静态的泛型方法 22 System.out.println(m); 23 } 24 } 25 26
~~~
7.3.3 含有泛型的接口
定义格式
修饰符 interface接口名 <代表泛型的变量>{
抽象方法;
}
使用格式:
**1、定义类时确定泛型的类型**
例如
~~~java
public class MyImp1 implements MyGenericInterface<String> {
@Override
public void add(String e) {
// 省略...
}
@Override
public String getE() {
return null;
}
}
~~~
此时,泛型E的值就是String类型。
**2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型**
例如
~~~java
public class MyImp2<E> implements MyGenericInterface<E> {
@Override
public void add(E e) {
// 省略...
}
@Override
public E getE() {
return null;
}
}
~~~
确定泛型:
~~~java
/*
* 使用
*/
public class GenericInterface {
public static void main(String[] args) {
MyImp2<String> my = new MyImp2<String>();
my.add("aa");
}
}
~~~
7.3.4.泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
#### 通配符基本使用
泛型的通配符:**不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。**
此时只能接受数据,不能往该集合中存储数据。
举个例子大家理解使用即可:
~~~java
public static void main(String[] args) {
Collection<Intger> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
~~~
> tips:泛型不存在继承关系 Collection<Object> list = new ArrayList<String>();这种是错误的。且创建对象时不能指定?通配符 ArrayList<?> list3 = new ArrayList<>();
#### 通配符高级使用----受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的**上限**和**下限**。
**泛型的上限**:
* **格式**: `类型名称 <? extends 类 > 对象名称`
* **意义**: `只能接收该类型及其子类`
**泛型的下限**:
- **格式**: `类型名称 <? super 类 > 对象名称`
- **意义**: `只能接收该类型及其父类型`
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类,Number是Object的子类,
~~~java
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
~~~
8.数据结构:我们本次学习和集合相关的数据结构,栈结构/队列结构/数组/链表/红黑树
当你用着java里面的容器类很爽的时候,你有没有想过,怎么ArrayList就像一个无限扩充的数组,也好像链表之类的。好用吗?好用,这就是数据结构的用处,只不过你在不知不觉中使用了。
现实世界的存储,我们使用的工具和建模。每种数据结构有自己的优点和缺点,想想如果Google的数据用的是数组的存储,我们还能方便地查询到所需要的数据吗?而算法,在这么多的数据中如何做到最快的插入,查找,删除,也是在追求更快。
我们java是面向对象的语言,就好似自动档轿车,C语言好似手动档吉普。数据结构呢?是变速箱的工作原理。你完全可以不知道变速箱怎样工作,就把自动档的车子从 A点 开到 B点,而且未必就比懂得的人慢。写程序这件事,和开车一样,经验可以起到很大作用,但如果你不知道底层是怎么工作的,就永远只能开车,既不会修车,也不能造车。当然了,数据结构内容比较多,细细的学起来也是相对费功夫的,不可能达到一蹴而就。我们将常见的数据结构:堆栈、队列、数组、链表和红黑树 这几种给大家介绍一下,作为数据结构的入门,了解一下它们的特点即可。
8.1.栈结构
- * **栈**:**stack**,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
简单的说:采用该结构的集合,对元素的存取有如下的特点 - * 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
- * 栈的入口、出口的都是栈的顶端位置。
这里两个名词需要注意:
* **压栈**:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
* **弹栈**:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
8.2. #### 队列 先进先出
* **队列**:**queue**,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
* 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
* 队列的入口、出口各占一侧。
8.3. 数组 有序序列,连续的内存空间,查询快,增删慢(数组长度一定,增删将新增数组)
数组**:**Array**,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
* 查找元素快:通过索引,可以快速访问指定位置的元素
* 增删元素慢
* **指定索引位置增加元素**:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图![](img/数组添加.png)
* **指定索引位置删除元素:**需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图
8.4. 链表 内存地址不连续 分数据域和两个指针域(自己地址和下个元素地址)组成部分
链表**:**linked list**,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是**单向链表**。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
* 多个结点之间,通过地址进行连接。但存储数据和取出数据的顺序不保证相同,例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
* 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
* 增删元素快:
* 增加元素:只需要修改连接下个元素的地址即可。
* 删除元素:只需要修改连接下个元素的地址即可。
双向链表就是 链表中有两个链子,有一条链子专门记录元素的顺序,是一个有序集合
8.5. 红黑树
* **二叉树**:**binary tree** ,是每个结点不超过2的有序**树(tree)** 。
简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。我们要说的是二叉树的一种比较有意思的叫做**红黑树**,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
1. 节点可以是红色的或者黑色的
2. 根节点是黑色的
3. 叶子节点(特指空节点)是黑色的
4. 每个红色节点的子节点都是黑色的
5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
9.List集合之实现类
掌握Collection接口使用后,再来看看Collection接口中的字类。三大特点
List也就是有序的 collection(也称为序列)。
以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。难免有人希望通过在用户尝试插入重复元素时抛出运行时异常的方法来禁止重复的列表,但我们希望这种用法越少越好
public interface List<E>extends Collection<E> 继承了Collection方法
主要的方法:
public void add(int index, E element)`: 将指定的元素,添加到该集合中的指定位置上。原有位置的数据自动往后移。
public E get(int index)`:返回集合中指定位置的元素。
public E remove(int index)`: 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element)`:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
举例:
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.util.*; 5 // 使用泛型的第一种方法,定义接口的实现类。实现接口,指定接口的泛型 6 //public final class Scanner implements Iterator<String> 实现了接口,并指定接口的泛型为String 7 8 public class Demo05StaticField {// Student 接口的实现类 9 10 public static void main(String[] args) { 11 // 创建List集合对象 12 List<String> list = new ArrayList<String>(); 13 // 往 尾部添加 指定元素 14 list.add("图图"); 15 list.add("小美"); 16 list.add("不高兴"); 17 System.out.println(list); 18 // add(int index,String s) 往指定位置添加 19 list.add(1, "没头脑"); 20 System.out.println(list); 21 // String remove(int index) 删除指定位置元素 返回被删除元素 22 // 删除索引位置为2的元素 23 System.out.println("删除索引位置为2的元素"); 24 System.out.println(list.remove(2)); 25 System.out.println(list); 26 // String set(int index,String s) 27 // 在指定位置 进行 元素替代(改) 28 // 修改指定位置元素 29 list.set(0, "三毛"); 30 System.out.println(list); 31 // String get(int index) 获取指定位置元素 32 // 跟size() 方法一起用 来 遍历的 33 for (int i = 0; i < list.size(); i++) { 34 System.out.println(list.get(i)); 35 } 36 //还可以使用增强for 37 for (String string : list) { 38 System.out.println(string); 39 } 40 } 41 }
10. LinkedList集合
List的链表实现,也就具备链表特点:即 查询慢,增删快
实现所有可选的列表操作,并且允许所有元素(包括 null)。
包含了大量操作首尾的操作方法
LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈(也就是有方法可以将加入数据放在开头),队列的结构使用。(了解即可)
注意: 使用LinkedList特有的方法时不能使用多态(多态不能看到字类方法)。
10.1.特有的方法
* `public void addFirst(E e)`:将指定元素插入此列表的开头。
* `public void addLast(E e)`:将指定元素添加到此列表的结尾。
* `public E getFirst()`:返回此列表的第一个元素。不能是空,否则报错
* `public E getLast()`:返回此列表的最后一个元素。不能是空,否则报错
* `public E removeFirst()`:移除并返回此列表的第一个元素。
* `public E removeLast()`:移除并返回此列表的最后一个元素。
* `public E pop()`:从此列表所表示的堆栈处弹出一个元素。
* `public void push(E e)`:将元素推入此列表所表示的堆栈。
* `public boolean isEmpty()`:如果列表不包含元素,则返回true。
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.util.*; 5 6 7 public class Demo05StaticField { 8 9 public static void main(String[] args) { 10 show(); 11 12 13 } 14 15 private static void show() { 16 LinkedList<String> linked = new LinkedList<>(); 17 linked.add("a"); 18 linked.add("b"); 19 linked.add("c"); 20 System.out.println(linked); 21 //* `public void addFirst(E e)`:将指定元素插入此列表的开头。 22 linked.addFirst("2"); 23 System.out.println(linked); 24 //* `public void addLast(E e)`:将指定元素添加到此列表的结尾。 25 linked.addLast("2"); 26 System.out.println(linked); 27 //* `public E getFirst()`:返回此列表的第一个元素。 28 String first = linked.getFirst(); 29 System.out.println(first); 30 //* `public E getLast()`:返回此列表的最后一个元素。 31 String last = linked.getLast(); 32 System.out.println(last); 33 //* `public E removeFirst()`:移除并返回此列表的第一个元素。 34 String s = linked.removeFirst(); 35 System.out.println(s); 36 //* `public E removeLast()`:移除并返回此列表的最后一个元素。 37 String s1 = linked.removeLast(); 38 System.out.println(s1); 39 //* `public E pop()`:从此列表所表示的堆栈处弹出一个元素。 40 String pop = linked.pop(); 41 System.out.println(pop); 42 //* `public void push(E e)`:将元素推入此列表所表示的堆栈。 43 linked.push("3"); 44 System.out.println(linked); 45 //* `public boolean isEmpty()`:如果列表不包含元素,则返回true。 46 boolean empty = linked.isEmpty(); 47 System.out.println(empty); 48 } 49 }
11.list集合之vector集合,vector集合是所有单列集合的祖宗,单列集合会慢
Vector
类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector
的大小可以根据需要增大或缩小,以适应创建 Vector
后进行添加或移除项的操作。
从 Java 2 平台 v1.2 开始,此类改进为可以实现 List
接口,使它成为 Java Collections Framework 的成员。与新 collection 实现不同,Vector
是同步的。
12.Set集合,不允许重复,没有索引(和Collection),也不能使用普通的for循环
此类允许使用null
`java.util.Set`接口和`java.util.List`接口一样,同样继承自`Collection`接口,它与`Collection`接口中的方法基本一致,并没有对`Collection`接口进行功能上的扩充,只是比`Collection`接口更加严格了。与`List`接口不同的是,`Set`接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。底层是哈希表结构(查询的速度非常快)
`Set`集合有多个子类,这里我们介绍其中的`java.util.HashSet`、`java.util.LinkedHashSet`这两个集合。
> tips:Set集合取出元素的方式可以采用:迭代器、增强for。
本次主要学习它两个实现类,HashSet<E>
public class HashSet<E>extends AbstractSet<E>implements Set<E>
1 package cn.itcast.demo01.demo01.demo05; 2 import javax.print.DocFlavor; 3 import java.util.*; 4 public class Demo05StaticField { 5 public static void main(String[] args) { 6 show(); 7 } 8 private static void show() { 9 HashSet<Integer> set = new HashSet<>(); 10 set.add(1); 11 set.add(2); 12 set.add(3); 13 set.add(1); 14 //使用迭代器遍历集合set 15 Iterator<Integer> it = set.iterator(); 16 while(it.hasNext()){ 17 Integer n = it.next(); 18 System.out.println(n); 19 } 20 } 21 } 22 23 24 25
哈希值,一个十进制的整数,由系统随机提供(就是对象的地址值,但是逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址),在Object类有一个方法,可以获取对象的hash值。 Object 方法的源码 public native int hashcode();
native代表该方法调用的是本地操作系统的方法。
切记 toString'方法返回的也是地址值,是hashcode的十六进制值。
String类的哈希值也重写了Object类的HashCode方法。
12.HashSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
创建自定义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 boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
~~~
~~~java
public class HashSetDemo2 {
public static void main(String[] args) {
//创建集合对象 该集合中存储 Student类型对象
HashSet<Student> stuSet = new HashSet<Student>();
//存储
Student stu = new Student("于谦", 43);
stuSet.add(stu);
stuSet.add(new Student("郭德纲", 44));
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("郭麒麟", 23));
stuSet.add(stu);
for (Student stu2 : stuSet) {
System.out.println(stu2);
}
}
}
执行结果:
Student [name=郭德纲, age=44]
Student [name=于谦, age=43]
Student [name=郭麒麟, age=23]
~~~
13. LinkedHsahSet集合
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢? 在HashSet下面有一个子类`java.util.LinkedHashSet`,它是链表和哈希表组合的一个数据存储结构。 它和HashSet集合区别是LinkedHsahSet是有序的,且不可重复,而HsahSet是无序的且不可重复。
特点:
LinkedHashSet底层是一个哈希表(数组+链表/红黑树)+链表,多了一个链表(记录元素的存储顺序),保证元素有序。
~~~java
public class LinkedHashSetDemo {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<String>();
set.add("bbb");
set.add("aaa");
set.add("abc");
set.add("bbc");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
结果:
bbb
aaa
abc
bbc
~~~
14.可变参数
在**JDK1.5**之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:
使用前提:
当方法的参数列表的数据类型已经确定,但是参数的个数不确定,就可以使用可变参数。
使用格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
```
其实这个书写完全等价与
```
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
可变参数原理
1 private static int add(int...arr) { 2 System.out.println(arr);// 打印传递的地址值,是数组地址值 3 System.out.println(arr.length);// 用数组的length方法 4 return 9; 5
可变参数的注意事项
一个方法的参数列表,只能有一个可变参数
如果方法的参数有多个,那么可变参数必须写在参数列表的末尾。
public static void method(String a,double b,int…arr){};可变参数的终极写法:public static void method( Object… obj){}
15.Collections集合的工具类用法,他们是静态方法,可以直接使用
* `java.utils.Collections`是集合工具类,用来对集合进行操作。部分方法如下:
- `public static <T> boolean addAll(Collection<T> c, T... elements) `:往集合中添加一些元素。
- `public static void shuffle(List<?> list) 打乱顺序`:打乱集合顺序。
- `public static <T> void sort(List<T> list)`:将集合中元素按照默认规则排序。
注意:使用sort(List<T> list)前提,被排序的集合里面存储的元素,必须实现Comparable,重写接口中的方法 compareTo定义排序的规则。
1 @Override 2 public int compareTo(Person o) { 3 return this.getAge() - o.getAge(); // 自己 this 减去 参数o 就是升序,反之降序 4 }
- `public static <T> void sort(List<T> list,Comparator<? super T> )`:将集合中元素按照指定规则排序。与上面的Comparable相比较区别如下:
compareble 自己(this)与别人(参数)比较,自己需要实现compareble接口,重写比较规则compareTo方法
comparetor 相当于找一个第三方裁判,比较两个。
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.sql.Connection; 5 import java.util.*; 6 7 public class Demo05StaticField { 8 public static void main(String[] args) { 9 ArrayList<Integer> list01 = new ArrayList<>(); 10 list01.add(1); 11 list01.add(2); 12 list01.add(3); 13 System.out.println(list01); 14 Collections.sort(list01, new Comparator<Integer>() { 15 @Override 16 public int compare(Integer o1, Integer o2) { 17 return o1 - o2;// 前面-后面 就是升序 18 } 19 }); 20 21 } 22 }
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.sql.Connection; 5 import java.util.*; 6 7 public class Demo05StaticField { 8 public static void main(String[] args) { 9 10 ArrayList<Person> per = new ArrayList<>(); 11 per.add(new Person("new",22)); 12 per.add(new Person("new2",28)); 13 per.add(new Person("new3",22)); 14 System.out.println(per); 15 Collections.sort(per, new Comparator<Person>() { 16 @Override 17 public int compare(Person o1, Person o2) { 18 return o1.getAge() - o2.getAge(); 19 } 20 }); 21 System.out.println(per); 22 23 } 24 } 25
Collections.sort(per, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int result = o1.getAge() - o2.getAge();// 如果年龄相同,看姓名首字母
if (result == 0){
result = o1.getName().charAt(0)- o2.getName().charAt(0);
}
return result;
}
16 Map集合<K v>将键映射到值。
现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即`java.util.Map`接口。一个元素包含2个值。
* `Collection`中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
* `Map`中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
* `Collection`中的集合称为单列集合,`Map`中的集合称为双列集合。
* 需要注意的是,`Map`中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
Map集合特点:
- Map集合是一个双列集合,一个元素包含两个值(key value)
- Map集合中的元素,Key与value数据类型可以相同,也可以不同
- Map集合中的元素,Key是不允许重复的,value是可以重复的
- Map集合中的元素,Key和value是一一对象的。
16.1.实现类 HashMap<K,V>
HashMap<K,V>集合的特点:
- HashMap<K,V>底层是哈希表,查询的速度非常快,JDK1.8以前,是数组+单向链表,之后是数组+单向链表/红黑树(链表的长度超过8),提供查询的速度
- HashMap<K,V>集合是一个无序的集合,存储元素和取出元素的顺序可能不一致。
基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 不同步意思就是多线程(多线程也就意味着快)。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap
方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问。
16.1.1.LinkedHashMap<K,V>
LinkedHashMap<K,V>特点:
- LinkedHashMap<K,V>底层是哈希表+链表(可以保证元素的顺序)
- LinkedHashMap<K,V>集合是一个有序的集合,存储和取出元素的顺序是一致的。保证迭代的顺序。
HashMap<K,V>子类 LinkedHashMap<K,V>Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。注意,如果在映射中重新插入 键,则插入顺序不受影响。(如果在调用 m.put(k, v) 前 m.containsKey(k) 返回了 true,则调用时会将键 k 重新插入到映射 m 中。)
16.1.2 Map集合的方法:
* `public V put(K key, V value)`: 把指定的键与指定的值添加到Map集合中。返回值是value,不包含key。
存储键值对时,Key不重复,返回值v是null;
存储键值对时,key重复,则会使新的value替换Map集合中的值,返回新的value
1 package cn.itcast.demo01.demo01.demo05; 2 3 import javax.print.DocFlavor; 4 import java.sql.Connection; 5 import java.util.*; 6 7 public class Demo05StaticField { 8 public static void main(String[] args) { 9 show01(); 10 } 11 private static void show01() { 12 //创建Map集合,使用多态 13 Map<String,String> strMap = new HashMap<>(); 14 String put1 = strMap.put("李晨", "范冰冰"); 15 System.out.println(put1);// 没有重复就返回null 16 System.out.println(strMap); 17 System.out.println("+++++++++"); 18 String put2 = strMap.put("李晨", "范冰冰1"); 19 System.out.println(put2);// 重复,则替换value值,且返回新value值。 20 System.out.println(strMap); 24 } 25 }
* `public V remove(Object key)`: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。返回值是value,不包含key。
* `public V get(Object key)` 根据指定的键,在Map集合中获取对应的值。返回值是value,不包含key。
* `boolean containsKey(Object key) ` 判断集合中是否包含指定的键。
* `public Set<K> keySet()`: 获取Map集合中所有的键,存储到Set集合中。可以用于遍历
* `public Set<Map.Entry<K,V>> entrySet()`: 获取到Map集合中所有的键值对对象的集合(Set集合)。可用于遍历