一、 前传
如果需要存放一组数据,用 数组 。
比如存放一组 int 类型的数据,可以使用:
int[] array = new int[]{ 2 , 4 , 6 , 8 };
array[ 0 ] = 1 ;
如果存在一个 Student 类,比如:
class Student {
private Integer id ;
private String name ;
private char gender ;
private Date birthdate ;
public Student( Integer id , String name , char gender , Date birthdate ) {
this.id = id ;
this.name = name ;
this.gender = gender ;
this.birthdate = birthdate ;
}
}
存放一组 Student 数据:
Student[] students = new Student[ 5 ];
students[ 0 ] = new Student( 1001 , "张三丰" , '男' , new Date() );
如果需要再 students 数组中存放 第 6 个学生的信息,怎么办?
students[ 5 ] = new Student( 1001 , "张三丰" , '男' , new Date() ); // ArrayIndexOutOfBoundsException
用数组存放数据的缺陷:
1、 容量有限制
2、 存取数据时需要使用数组索引,需要手动控制索引的范围
3、 不能实现数据的"删除"操作
4、 不能实现数据的"插入"操作
5、 检查元素存在时,需要手动遍历数组去查找
为了避免 ArrayIndexOutOfBoundsException 我们需要为数组 “扩容” ( 但是数组一经创建其长度绝对不能更改 )
创建一个新的数组,新数组长度是 老数组长度的 2 倍:
Student[] aNewArray = new Student[ students.length * 2 ];
将老数组中的原有的元素复制到新数组中
System.arraycopy( students , 0 , aNewArray , 0 , students.length );
将 aNewArray 变量存储的地址 赋值给 students 变量:
students = aNewArray ;
students[ 5 ] = new Student( 1001 , "张三丰" , '男' , new Date() );
二、 开发一个拥有自主知识产权的"容器"
- 创建 Container 类
创建一个表示容器的类,将来用来该类型的实例存放数据
public class Container {
}
-
确定真正用来批量存放数据的"场所"
在 Container 类内部声明一个私有的数组,用来存放向容器中添加的数据public class Container { private Object[ ] data ; }
-
声明用来确定数据存放位置、并统计数据个数的变量
在 Container 类内部声明一个私有的变量 count :public class Container { private Object[] data ; private int count ; }
这个 count 变量作用有三:
a) 将来向容器中添加数据时,确定当前被添加的数据在数组中的存放位置
b) 本次添加成功后,count自增1,以便于确定下一次所添加数据的位置
c) 通过 count 变量的值,确定容器中已添加数据的个数*
-
声明加载因子
参照 Java 集合框架中大多数类的设计,为 Container 类增加一个 loadFactor 变量:private float loadFactor ;
这个变量用来确定对数组进行"扩容"的时机,其取值范围为 ( 0 , 1 ) ,含义是容器中 数据个数 达到 容器容量 的某个比例后,为数组"扩容"。
-
构造方法
a) 添加一个带有两个参数的构造方法:public Container( int initialCapacity , float loadFactor) { if( initialCapacity < 0 ) { throw new RuntimeException( "你居然敢给我传负数,想上天吗?" ); } this.data = new Object[ initialCapacity ]; // 使用给定的初始容量创建数组实例 if( loadFactor <= 0 || loadFactor >= 1 ) { throw new RuntimeException( "加载因子必须是 0 到 1 之间的 小数" ); } this.loadFactor = loadFactor; // 保存加载因子 }
第一个参数表示 容器的初始容量 ( initialCapacity )
第二个参数表示 加载因子 ( loadFactor )
b) 为 Container 类添加无参构造方法,并在构造方法中对 data 数组 和 loadFactory 进行初始化
public Container() {
this( 10 , 0.75F ) ;
}
这里,我们直接给定了 数组的容量为 10 ,
此时 数组容量被称作 data 所引用的数组的 初始容量。默认的加载因子为 0.75 。
- 实现最基本的几个方法
a) 实现添加数据的方法
参照 Java 中 Collection 接口中的 add 方法,为 Container 类增加 add 方法,用来实现添加数据操作:
public boolean add( Object o ) {
data[ count++ ] = o ;
return true ;
}
每次向容器中添加数据时,都将当前被添加的数据添加到 data 数组的 count 位置,添加成功后,将 count 变量自增 1 作为 下次添加数据时的存方位置。
b) 添加一个用来保证容器容量的方法(私有的)
private void ensureCapacity() {
// 判断 容器中已经存放的数据个数 是否 已经 达到了 容器容量的 指定比例 ( 加载因子 loadFactor )
if( count >= data.length * loadFactor ) {
// 如果 容器个数 已经达到 预定比例,就需要 对数组进行 "扩容"
Object[] temp = new Object[ data.length * 3 / 2 + 1 ] ; // 扩容第一步: 创建新数组
System.arraycopy( data , 0 , temp , 0 , count ); // 扩容第二步: 把 老数组 中 已经添加的数据 复制 到新数组中
data = temp ;// 扩容第三步: 将新数组的地址赋值给 data 变量
}
}
c) 获取容器中已添加的有效数据的个数
参照 Java 中 Collection 接口中的 size 方法,为 Container 类增加 size 方法,用以返回容器中元素的个数:
public int size() {
return this.count ;
}
通过参看 add 方法的实现,可以知道,每次添加一个数据后 ,count 都会自增 1 ,自增后的 count 表示下一次添加数据时,数据的存放位置,同时也刚好是容器中已添加的数据个数。
d) 获取容器容量
声明一个用来获得容器容量的方法(实际是返回内部的数组的长度):
public int capacity() {
return data.length ;
}
e) 判断容器是否为空
参照 Java 中 Collection 接口中的 isEmpty 方法,为 Container 类增加 isEmpty 方法,用来判断容器是否为”空”:
public boolean isEmpty() {
return this.count == 0 ;
}
当容器中添加任何数据时,count 变量的取值为 0 ,表示该容器是 “空” 的 ( 不是 null )
以后需要 “清空” 容器时,只需要保证 count 为 0 ,即可视 容器为 “空” 的
f) 重写 toString 方法
我们已经知道,当直接输出一个引用变量时,最终输出的是 该变量对应的对象的 toString 方法的返回值,因此为了在输出 Container 类型的引用变量时,能够输出 容器中的已存放数据,我们需要为 Container 类重写 toString 方法。
为了能够便捷地连接字符串,我们需要声明一个 StringBuffer 或 StringBuilder 类型的实例变量:
private StringBuffer buffer = new StringBuffer();
随后,我们借助于 buffer 变量即可重写 toString 方法:
@Override
public String toString() {
buffer.setLength( 0 );
buffer.append( "Container [ " );
for( int i = 0 ; i < this.count ; i++ ) {
buffer.append( data[ i ] ) ;
buffer.append( " ," );
}
int index = buffer.lastIndexOf( "," );
if( index != - 1 ) {
buffer.deleteCharAt( index );
}
buffer.append( "]" );
String s = buffer.toString();
return s ;
}
三、 类型参数( JDK 1.5 老特性 泛型 )
从 JDK 1.5 开始,允许在类中使用 类型参数 ,这种新特性被称作 “泛型” ( generic type ) 。
- 定义类型参数
在定义类时,可以在类名之后使用 的形式来声明 类型参数:
public class Receptacle<E> {
}
对于 Receptacle 来说,E就是类型参数,在这里也是形式上的参数( 参数名称就是 E )。
类型参数的作用范围是整个类(即在整个类内部都可以使用该 类型参数 )。
如果有多个类型参数需要声明,可以在它们之间使用 逗号 进行分隔,比如:
public interface Map<K,V> {
}
这里的 K 和 V 都是 类型参数 ( 两个参数的名称分别是 K 和 V )。
-
在类内部使用类型参数
在 Receptacle 类内部,可以在方法中使用类型参数,比如:public boolean add( E o ) { // 此处省略方法的实现过程 }
此时,add 方法的 参数类型 由 使用时设定的 具体类型 所决定。
-
使用类型参数创建 Receptacle 实例
在使用 Receptacle 类时,可以使用 < > 为 Receptacle 类传递实际的类型参数:Receptacle<String> re = new Receptacle<String>(); re.add( "向晚意不适" ); re.add( "驱车登古原" ); re.add( "夕阳无限好" ); re.add( "只是近黄昏" );
此时,对于 容器( Receptacle ) 的实例来说,将只能添加( add ) String类型的数据,不能接受其它类型的数据。
从JDK 1.7 开始,允许将 new 之后的 类名 和 () 之间的 <> 内部的类型省略( 自动"感应”前面声明的类型 ):
Receptacle<Integer> re = new Receptacle<>();
re.add( 9527 );
re.add( 1234 );
re.add( 5668 );
同时,在使用 Receptacle 时,如果没有指定实际的类型参数,则默认认为 容器 中存放的是 Object 类型,此时跟之前所开发的 Container 类型一样,添加数据是没有类型限制:
Receptacle re = new Receptacle();
re.add( "离离原上草" );
re.add( 9527 );
re.add( '生' );
四、 Iterable 接口
- Iterable
java.util.Collection 接口及其所有子接口 都继承了 java.lang.Iterable 接口。
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach( Consumer<? super T> action ) { // JDK 1.8 开始提供
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() { // JDK 1.8 开始提供
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
实现这个接口( Iterable ) 允许其实现类的对象成为 “foreach” 语句的目标
-
“foreach”
从 JDK 1.5 引入了 迭代器 后,开始支持一种特殊格式的循环,这种循环被称作 foreach 循环for( 类型 变量 : Iterable实例或数组 ) { System.out.println( name ); }
这里的 变量 用来 “存储” 从 Iterable 实例 或 数组 取出的 单个元素
这里的 变量 的类型 必须跟 Iterable 实例 或 数组 内部存放的元素类型一致
比如使用 foreach 循环 迭代 List 集合:
List<String> list = new ArrayList<>();
list.add("白云");
list.add("黑土");
list.add("村长");
list.add("老毕");
for( String name : list ) {
System.out.println( name );
}
比如使用 foreach 遍历 数组:
int[] array = { 2 , 4 , 6 , 8 , 10 };
for( int e : array ) {
System.out.println( e );
}
-
迭代器( Iterator )
public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
Iterator 是用来对集合(Collection)进行迭代的迭代器。
迭代器取代了 Java Collections Framework 中的 Enumeration。
迭代器与枚举有两点不同:
- 迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。
- 方法名称得到了改进。
注意:
只要能够返回 Iterator 类型的对象,就表示 被调用的方法 所在的类已经完全实现了 Iterator 接口。
五、 Collection 接口
- Collection接口概述
Collection 接口是 collection 层次结构 中的 根 接口,其所处层次如下图所示:
Collection 表示一组对象,这些对象也称为 collection 的元素。
一些 collection 允许有重复的元素( 比如 List ),而另一些则不允许 ( 比如 Set )。
一些 collection 是有序的 ( 比如 List ),而另一些则是无序的( 比如 Set )。
package java.util;
// Iterable 来自 java.lang 包,因此不需要 import
public interface Collection<E> extends Iterable<E> {
// 这里省略了 内部的所有 抽象方法 和 default 方法
}
-
Collection中的抽象方法
(1) 在 collection 中添加一个新的元素boolean add( E e );
(2) 在 collection 中添加一批新的元素
boolean addAll(Collection<? extends E> c);
(3) 清空 collection 集合
void clear();
(4) 判断当前 collection 中是否包含指定的元素
boolean contains( Object o );
(5) 判断当前 collection 中是否包含 给定 collection 中的所有元素
boolean containsAll( Collection<?> c );
(6) 判断当前 collection 对象是否跟另外一个 对象 “相等”
boolean equals( Object o );
(7) 获取当前 collection 对象的 哈希码值
int hashCode();
(8) 判断当前 collection 是否为 空集
boolean isEmpty();
(9) 返回迭代器
Iterator<E> iterator();
(10) 从当前 collection 移除给定的元素
boolean remove( Object o );
(11) 从当前 collection 中移除 给定 collection 中所包含的元素
boolean removeAll( Collection<?> c );
(12) 仅保留当前 collection 中同时存在于给定 collection 集合中的那些元素
boolean retainAll( Collection<?> c );
(13) 返回当前 collection 中的元素个数
int size();
(14) 以数组形式返回当前 collection 中的所有元素
Object[] toArray();
(15) 以数组形式返回当前 collection 中的所有元素
<T> T[] toArray( T[] a );
- Collection中的 default 方法 ( 这部分内容 暂时不做要求 )
从 JDK 1.8 开始,Collection 接口中增加了几个 default 修饰的方法:
(1) 新增 removeIf 方法
default boolean removeIf( Predicate<? super E> filter ) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while ( each.hasNext() ) {
if ( filter.test( each.next() ) ) {
each.remove();
removed = true;
}
}
return removed;
}
(2) 新增 stream 方法
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
(3) 新增 parallelStream 方法
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
(4) 重写了从 Iterable 接口继承的 spliterator 方法:
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
六、 List 接口
- 特点
a) 元素可以重复
b) 有序: 元素在集合内部的默认存放顺序跟添加顺序一致
c) 可排序: Collections.sort( list )
d) 元素可以是null ( 这个主要还得看实现类是否支持 )
- 方法
- List 接口继承了 Collection 接口,因此拥有 Collection 接口中声明的所有方法。
- 另外,List接口增加了许多 借助于索引 访问集合的方法:
(1) 在指定索引处插入指定的元素:
void add( int index , E element );
(2) 在指定索引处插入给定 collection 中包含的一批元素
boolean addAll( int index , Collection<? extends E> c );
(3) 使用指定的元素替换指定索引处的元素:
E set( int index , E element );
(4) 获取指定索引处的元素
E get( int index );
(5) 删除指定索引处的元素
E remove( int index );
(6) 获取指定元素在列表中首次出现的位置
int indexOf( Object o )
(7) 获取指定元素在列表中最后一次出现的位置
int lastIndexOf( Object o )
(8) 截取子列表
List<E> subList( int fromIndex , int toIndex )
- 实现类
(1) ArrayList类
a、 特点
- ArrayList 内部借助于数组实现元素的存储。 ArrayList 是线程不安全的,相对于线程安全的Vector来说可能效率较高。
- ArrayList 内部基于数组实现数据存储,因此随机访问效率较高。( 可以使用索引直接访问数组中的元素 ) ArrayList
- 内部基于数组实现数据存储,因此增删元素速度较慢。( 增删元素可能需要挪动数组中大量元素 )
b、构造方法
- ArrayList() 构造一个初始容量为 10 的空列表。
- ArrayList( int initialCapacity ) 构造一个具有指定初始容量的空列表。
- ArrayList( Collection<? extends E> c ) 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
(2) Vector类
a、 特点:
- Vector 内部借助于数组实现元素的存储。
- Vector是线程安全的,相对于线程不安全的ArrayList来说可能效率较低。
- Vector内部基于数组实现数据存储,因此随机访问效率较高。( 可以使用索引直接访问数组中的元素 )
- Vector内部基于数组实现数据存储,因此增删元素速度较慢。( 增删元素可能需要挪动数组中大量元素 )
b、 构造方法:
- Vector() 构造一个空Vector实例,使其内部数据数组的大小为 10,其标准容量增量为零。
- VECTOR( INT INITIALCAPACITY ) 使用指定的初始容量和等于零的容量增量构造一个空VECTOR实例。
- Vector( int initialCapacity, int capacityIncrement ) 使用指定的初始容量和容量增量构造一个空的Vector实例。
- Vector(Collection<? extends E> c) 构造一个包含指定 collection 中的元素的向量,这些元素按其 collection 的迭代器返回元素的顺序排列。
Vector实例的容量不足以容纳新的元素时也会扩容,每次容量都增加 capacityIncrement ;
如果 capacityIncrement <= 0 则直接在 原来的容量 基础上 翻一倍。
(3) Stack类
Stack 类是 Vector 类的子类。
- push 将元素压入栈顶
- peek 检查栈顶元素(不弹出元素)
- pop 弹出栈顶元素
- search 从栈顶开始搜索元素( 基数从 1 开始 )
- empty 判断当前栈是否为空
(4) LinkedList类
特点
- LinkedList 内部基于链表实现元素的存储。
- LinkedList是线程不安全的。
- LinkedList内部基于链表实现数据存储,因此随机访问效率较低。( 需要逐个遍历链表中的结点 )
- LinkedList内部基于链表实现数据存储,因此增删元素速度较快。( 增删元素只需要修改相邻两个节点的指针 )
七 排序
1. 自然顺序
在 Java 语言中,所谓的自然顺序是指根据一组对象的自然比较方法的比较结果来实现排序操作。
java.lang.Comparable 接口提供了对 自然排序 的支持,Comparable接口强行对实现它的每个类的对象进行整体排序,这种排序被称为类的自然排序,相应类的 compareTo 方法被称为它的自然比较方法。
public interface Comparable<T> {
public int compareTo( T o );
}
注意,这里的 中的 T 表示形式上的类型参数 ( 形参 ),T 就是参数名称。
所谓支持自然排序,就是某个类实现了Comparable接口并实现了其 compareTo 方法。
比如存在一个 Panda 类:
public class Panda implements Comparable<Panda> {
private Integer id;
private String name;
private double weight;
public Panda() {
super();
}
public Panda(Integer id, String name, double weight) {
super();
this.id = id;
this.name = name;
this.weight = weight;
}
@Override
public int compareTo( Panda another ) {
if( this.weight > another.weight ) {
return 1 ;
} else if ( this.weight == another.weight ) {
return 0 ;
} else {
return -1 ;
}
}
@Override
public String toString() {
return "(" + id + ", " + name + ", " + weight + ")";
}
}
对 Panda 数组实现 自然排序
Panda[] pandas = new Panda[ 3 ];
pandas[ 0 ] = new Panda( 1001 , "团团" , 56.5 );
pandas[ 1 ] = new Panda( 2001 , "媛媛" , 46 );
pandas[ 2 ] = new Panda( 3001 , "和和" , 50 );
System.out.println( Arrays.toString( pandas ) );
Arrays.sort( pandas ); // Arrays.sort( Object[] array )
System.out.println( Arrays.toString( pandas ) );
对存放 Panda 实例的 List 集合实现自然排序
List<Panda> pandas = new ArrayList<>();
Panda p1 = new Panda( 1001 , "团团" , 56.5 );
pandas.add( p1 );
pandas.add( new Panda( 2001 , "媛媛" , 46 ) );
pandas.add( new Panda( 3001 , "和和" , 50 ) );
System.out.println( pandas );
Collections.sort( pandas );
System.out.println( pandas );
- 使用比较器排序
专门用来对某种类型的两个对象进行比较的工具,就是比较器。
public interface Comparator<T> {
int compare( T first , T second );
boolean equals( Object o );
... ...
}
在 Comparator 接口中,compare 方法是用来 比较两个对象 的方法:
如果 first “大于” second 返回 正整数
如果 first “等于” second 返回 零
如果 first “小于” second 返回 负整数
在 Comparator 接口中,equals 方法用来比较当前的 比较器 跟 另外一个比较器 是否 “相等”
List<Panda> pandas = new ArrayList<>();
pandas.add( new Panda( 1001 , "团团" , 56.5 ));
pandas.add( new Panda( 2001 , "媛媛" , 46 ) );
pandas.add( new Panda( 3001 , "和和" , 50 ) );
System.out.println( "排序前: " + pandas );
Collections.sort( pandas );
System.out.println( "自然顺序: "+pandas );
// 使用匿名内部类实现 Comparator 接口 并创建该类的实例
Comparator<Panda> comparator = new Comparator<Panda>() {
@Override
public int compare( Panda first , Panda second ) {
if( first.getId() > second.getId() ) {
return 1 ;
} else if( first.getId().equals( second.getId() ) ) {
return 0 ;
} else {
return -1 ;
}
}
} ;
Collections.sort( pandas , comparator ); // 使用比较器进行排序
System.out.println( "使用比较器顺序: "+pandas );
- 探究 Arrays.sort 和 Collections.sort 的实现
在 Arrays.sort 和 Collections.sort 两个方法内部,不论采用了哪种排序方法,最终必定会借助于 被排序元素的自然顺序 或 借助于比较器实现排序操作。
八、 Map接口
- 特点
- Map 集合中存放"键-值对" ( “key-value pair” ),键值对也被称作映射项。
- Map 集合中不能包含重复的键 ,但可以包含重复的值。
- Map 集合中一个键只能对应一个值。( 即一个键只能映射到一个值,不能映射到多个值 )
理解:
A 钥匙 可以打开 X 锁 “A-X”
B 钥匙 可以打开 Y 锁 “B-Y”
C 钥匙 可以打开 X 锁 “C-X”
C 钥匙 可以打开 Y 锁 “C-Y” ( 错误,原因是: 一个 键 只能 对应 一个 值 )
Map接口的核心:
public interface Map<K, V> {
// 省略 Map 接口中的其它方法
interface Entry<K, V> { // 映射项 ( 内部包含了 键 和 值 )
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
// 省略 Entry 接口中的其它方法
}
// 省略 Map 接口中的其它方法
}
Map接口内部内部声明的 Entry 接口表示 映射项 ( item ) ,即前文所说的 键-值对 对应的对象的类型。
在 HashMap 类内部,提供了 Map.Entry 接口的一个实现类:
static class Node<K,V> implements Map.Entry<K,V> { // Node 类是 HashMap 类的静态内部类
final int hash;
final K key;
V value;
Node<K,V> next;
Node( int hash , K key , V value , Node<K,V> next ) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
- 抽象方法
(1) 将指定的值与此映射中的指定键关联
V put( K key, V value );
如果在 Map 集合中 不存在指定的 key ,则使用 直接将 key-value 对放入 Map 集合,返回 null 。
如果在 Map 集合中已经存在指定的 key ,则使用 新的 value 替换 旧的 value 并返回 旧的 value 。
(2) 返回指定键所映射的值 ( 如果此映射不包含该键的映射关系,则返回 null )
V get( Object key );
根据 key 从 Map 集合中获取相应 key ,如果未找到相应 key 就返回 null 。
(3) 判断 map 集合是否为空
boolean isEmpty();
(4) 获取 map 集合中 键-值对 的个数 ( 映射项的个数 )
int size();
(5) 获取 map 集合本身的 哈希码值 ( Map实例本身的哈希码值 )
int hashCode();
(6) 指示当前的 map 对象是否跟另外一个 map 对象 “相等” ( 由实现类提供支持 )
boolean equals( Object o );
(7) 从指定映射(Map)中将所有映射关系复制到此映射(Map)中
void putAll( Map<? extends K, ? extends V> m );
将参数对应的 Map 集合中的所有的 映射关系 ( 键值对 ) 全部添加到 当前的 Map 集合中。
(8) 如果存在一个键( key )的映射关系(键值对),则将其从此映射(Map)中移除
V remove( Object key );
根据 指定的 key 从当前 Map 实例中移除相应的 键值对,并返回该键值对(映射项)的 值。
如果 指定的 key 在当前 Map 不存在,则返回 null ( 不删除任何已有的键值对 )。
(9) 从此映射中移除所有映射关系 ( 清空一个 Map 集合 )
void clear();
(10) 如果此映射包含指定键( key ) 的映射关系(键值对),则返回 true
boolean containsKey( Object key );
判断指定的 key 在当前 Map 实例中是否存在 ( 以 key 的形式存在),存在则返回 true ,否则返回 false 。
在内部通过 key 对应的类型的 equals 方法来比较,从而确定 指定的 key 在当前Map中是否存在的判断。
因此,作为 Map 的key使用的哪些类型,需要重写 equals 方法。
(11) 如果此映射(当前Map)将一个或多个键映射到指定值 ( value ),则返回 true
boolean containsValue(Object value);
注意: Map 集合中不能包含重复的键 ,但可以包含重复的值
(12) 返回此映射中包含的键的 Set 视图
Set<K> keySet();
获取当前的Map集合中,所有的 key 组成的集合( Set 类型 )。
因为 Map 集合中不能包含重复的键,所以所有的 key 组成的 Set 集合中没有重复的元素。
(13) 返回此映射(当前Map)中包含的值的 Collection 视图
Collection<V> values();
(14) 返回此映射(当前的Map)中包含的映射关系(键值对)的 Set 视图
Set< Map.Entry<K, V> > entrySet();
获取当前的 Map 集合中所有的 映射项(键值对) 对应的 Set 集合。
注意:
这个 Set 集合中的元素类型是 Map.Entry 类型。
一个元素就是一个 Entry 类型的实例。
一个 Entry 实例中 包含了 key 和 value 。
- 实现类
(1) HashMap 类
a、 特点
- HashMap内部基于哈希表(数组)存储键值对(映射项)。
- HashMap是线程不安全的。
- HashMap根据键的哈希码值重新计算键值对(映射项)的在哈希表中的位置。
- HashMap的key可以为null ,value 也可以为 null 。
- HashMap的父类是 AbstractMap 。
b、构造
public HashMap()
public HashMap( Map<? extends K, ? extends V> m )
public HashMap( int initialCapacity )
public HashMap( int initialCapacity , float loadFactor )
(2) Hashtable 类
a、 特点
- Hashtable内部基于哈希表(数组)存储键值对(映射项)。
- Hashtable是线程安全的 ( 所有的方法都被 synchronized 关键词所修饰 )。
- Hashtable直接根据键的哈希码值确定键值对(映射项)的在哈希表中的位置。
- Hashtable的key和value 都不能为null 。
- Hashtable的父类是 Dictionary 类。
b、 构造
public Hashtable()
public Hashtable( Map<? extends K, ? extends V> t )
public Hashtable( int initialCapacity )
public Hashtable( int initialCapacity , float loadFactor )
**
九、 Set接口
**
- 特点
- 元素不可以重复
- 无序: 元素在集合内部的默认存放顺序跟添加顺序不一致
- 元素可以是null ( 这个主要还得看实现类是否支持,比如 HashSet就支持,TreeSet就不支持 )
- 方法
参见 Collection中的抽象方法 。
- 实现类
(1) HashSet
1 特点:
- 元素不可以重复
- HashSet内部借助于哈希表(数组)存放元素(内部借助于HashMap来存储数据)
- 无序: 在HashSet中存放的元素根据元素的 hashCode 决定存放位置,所以跟添加顺序无关
- 不支持排序: 在HashSet中存放的元素根据元素的 hashCode 决定存放位置
元素可以是null
2、 构造
public HashSet()
public HashSet( Collection<? extends E> c )
public HashSet( int initialCapacity )
public HashSet( int initialCapacity , float loadFactor )
方法实现:
private transient HashMap< E , Object > map ; // HashSet内部借助于HashMap来存储数据
private static final Object PRESENT = new Object(); // Dummy value to associate with an Object in the backing Map
public boolean add( E e ) {
return map.put( e , PRESENT ) == null ; // 在 Map 内部的 Entry 实例 的 key 中存储 被添加的 元素
}
十、 Queue接口
- 常见数据结构
(1) 串 ( string )
java.lang.String 就是一种 串 结构。
(2) 栈 ( stack )
特点: 后进先出( Last In First Out , LIFO )
用来模拟栈操作的类: java.util.Stack 。
(3) 队列 ( queue )
特点: 先进先出( First In First Out , FIFO )
用来模拟队列操作的类: java.util.LinkedList 。
- Queue的特点
先进先出( First In First Out , FIFO )
-
Queue
-
Deque
-
实现类
LinkedList 类
特点:
-
LinkedList内部借助于链表存储数据。
-
LinkedList是线程不安全的。
-
LinkedList中的元素可以重复
-
LinkedList中的元素也是有序的 ( 添加顺序跟存放顺序一致 )
-
LinkedList支持排序操作( 因为实现了 List 接口 )
-
LinkedList中可以有null元素
-
LinkedList 可以当作 单端队列 、双端队列 、栈 、List集合 来使用。
-
LinkedList内部基于链表实现数据存储,因此随机访问效率较低。( 需要逐个遍历链表中的结点 )
-
LinkedList内部基于链表实现数据存储,因此增删元素速度较快。( 增删元素只需要修改相邻两个节点的指针 )