一、汇合入门总结
《2020 最新 Java 根底精讲视频教程和学习路线!》
汇合框架:
Java 中的汇合框架大类可分为 Collection 和 Map;两者的区别:
1、Collection 是单列汇合;Map 是双列汇合
2、Collection 中只有 Set 系列要求元素惟一;Map 中键须要惟一,值能够反复
3、Collection 的数据结构是针对元素的;Map 的数据结构是针对键的。
泛型:
在说两大汇合体系之前先说说泛型,因为在前面的汇合中都会用到;所谓的泛型就是:类型的参数化
泛型是类型的一部分,类名 + 泛型是一个整体
如果有泛型,不应用时,参数的类型会主动晋升成 Object 类型,如果再取出来的话就须要向下强转,就可能产生类型转化异样 (ClassCaseException);不加泛型就不能在编译期限定向汇合中增加元素的类型,导致前期的解决麻烦。
上面就来比照加了泛型和不加泛型的区别:
package 好好学 java;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {public static void main(String[] args) {
// 不加泛型,增加和遍历
List list = new ArrayList<>();
list.add(1);
list.add("123");
list.add("hello");
Iterator it = list.iterator();
while(it.hasNext()){
// 没有增加泛型,这里只能应用 Object 接管
Object obj = it.next();
System.out.println(obj);
}
}
}
package 好好学 java;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {public static void main(String[] args) {
// 加泛型,增加和遍历
List<String> list = new ArrayList<String>();
list.add("123");
list.add("hello");
Iterator<String> it = list.iterator();
while(it.hasNext()){
// 因为增加了泛型,就阐明汇合中装的全部都是 String 类型的数据
// 所以这里用 String 类型接管,就不会产生异样,并且能够应用 String 的办法
String str = it.next();
System.out.println(str.length());
}
}
}
复制代码
自定义带泛型的类:
package 好好学 java;
public class Test {
// 自定义一个带有一个参数的泛型类, 能够向传入什么类型就传入什么类型
public static void main(String[] args) {
// 进行测试, 传入一个 String 对象
Person<String> perStr = new Person<String>();
perStr.setT("我是字符串");
String str = perStr.getT();
System.out.println(str);
// 进行测试,传入一个 Integer 对象
Person<Integer> perInt = new Person<Integer>();
perInt.setT(100);
Integer intVal = perInt.getT();
System.out.println(intVal);
}
}
// 自定义一个带有一个参数的泛型类
class Person<T>{
private T t;
void setT(T t){this.t = t;}
T getT(){return t;}
}
复制代码
实现带有泛型的接口类型:
实现接口的同时, 指定了接口中的泛型类型. (定义类时确定);
public class GenericImpl1 implements GenericInter<String> {}
复制代码
实现接口时, 没有指定接口中的泛型类型. 此时, 须要将该接口的实现类定义为泛型类. 接口的类型须要在创立实现类对象时能力真正确定其类型. (始终不确定类型, 直到创建对象时确定类型);
public class GenericImpl2<T> implements GenericInter<T> {}
复制代码
泛型的通配符(?):
下限限定:比方定义方法的时候呈现,public void getFunc(List<? extends Animal> an),
那么示意这里的参数能够传入 Animal,或者 Animal 的子类
上限限定: 比方定义方法的时候呈现,public void getFunc(Set<? super Animal> an),
那么示意这里的参数能够传入 Animal,或者 Animal 的父类
应用泛型的留神点:
1、泛型不反对根本数据类型
2、泛型不反对继承,必须放弃前后一致(比方这样是谬误的:List<Object> list = new ArrayList<String>();
Collection 体系:
ollection 包含两大体系,List 和 Set
List 的特点:
存取有序,有索引,能够依据索引来进行取值,元素能够反复
Set 的特点:
存取无序,元素不能够反复
List:
上面有ArrayList,LinkedList,Vector
(已过期)
汇合的的最大目标就是为了存取;List 汇合的特点就是存取有序,能够存储反复的元素,能够用下标进行元素的操作
ArrayList: 底层是应用数组实现,所以查问速度快,增删速度慢
package 好好学 java;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
// 应用 ArrayList 进行增加和遍历
public static void main(String[] args) {List<String> list = new ArrayList<String>();
list.add("接口 1");
list.add("接口 2");
list.add("接口 3");
// 第一种遍历形式, 应用迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){String next = it.next();
System.out.println(next);
}
System.out.println("-------------------");
// 第二种遍历形式,应用 foreach
for (String str : list){System.err.println(str);
}
}
}
复制代码
LinkedList: 是基于链表构造实现的,所以查问速度慢,增删速度快,提供了非凡的办法,对头尾的元素操作(进行增删查)。
应用 LinkedList 来实现栈和队列;栈是先进后出,而队列是先进先出
package com.xiaoshitou.classtest;
import java.util.LinkedList;
/**
* 利用 LinkedList 来模仿栈
* 栈的特点:先进后出
* @author Beck
*
*/
public class MyStack {private LinkedList<String> linkList = new LinkedList<String>();
// 压栈
public void push(String str){linkList.addFirst(str);
}
// 出栈
public String pop(){return linkList.removeFirst();
}
// 查看
public String peek(){return linkList.peek();
}
// 判断是否为空
public boolean isEmpty(){return linkList.isEmpty();
}
}
package 好好学 java;
public class Test {public static void main(String[] args) {
// 测试栈
StackTest stack = new StackTest();
stack.push("我是第 1 个进去的");
stack.push("我是第 2 个进去的");
stack.push("我是第 3 个进去的");
stack.push("我是第 4 个进去的");
stack.push("我是第 5 个进去的");
// 取出
while (!stack.isEmpty()){String pop = stack.pop();
System.out.println(pop);
}
// 打印后果
/* 我是第 5 个进去的
我是第 4 个进去的
我是第 3 个进去的
我是第 2 个进去的
我是第 1 个进去的 */
}
}
复制代码
LinkedList 实现 Queue:
package 好好学 java;
import java.util.LinkedList;
/**
* 利用 linkedList 来实现队列
* 队列: 先进先出
* @author Beck
*
*/
public class QueueTest {private LinkedList<String> link = new LinkedList<String>();
// 放入
public void put(String str){link.addFirst(str);
}
// 获取
public String get(){return link.removeLast();
}
// 判断是否为空
public boolean isEmpty(){return link.isEmpty();
}
}
package 好好学 java;
public class Test {public static void main(String[] args) {
// 测试队列
QueueTest queue = new QueueTest();
queue.put("我是第 1 个进入队列的");
queue.put("我是第 2 个进入队列的");
queue.put("我是第 3 个进入队列的");
queue.put("我是第 4 个进入队列的");
// 遍历队列
while (!queue.isEmpty()){String str = queue.get();
System.out.println(str);
}
// 打印后果
/* 我是第 1 个进入队列的
我是第 2 个进入队列的
我是第 3 个进入队列的
我是第 4 个进入队列的 */
}
}
复制代码
Vector: 因为曾经过期,被 ArrayList 取代了;它还有一种迭代器通过 vector.elements()获取,判断是否有元素和取元素的办法为:hasMoreElements(),nextElement()
。
package 好好学 java;
import java.util.Enumeration;
import java.util.Vector;
public class Test {public static void main(String[] args) {Vector<String> vector = new Vector<String>();
vector.add("搜寻");
vector.add("vector");
vector.add("list");
Enumeration<String> elements = vector.elements();
while (elements.hasMoreElements()){String nextElement = elements.nextElement();
System.out.println(nextElement);
}
}
}
复制代码
Set:
Set 汇合的特点:元素不反复,存取无序,无下标 Set 汇合上面有:HashSet,LinkedHashSet,TreeSet
HashSet 存储字符串:
package 好好学 java;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Test {public static void main(String[] args) {
// 利用 HashSet 来存取
Set<String> set = new HashSet<String>();
set.add("我的天");
set.add("我是反复的");
set.add("我是反复的");
set.add("welcome");
// 遍历 第一种形式 迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){String str = it.next();
System.out.println(str);
}
System.out.println("--------------");
for (String str : set){System.out.println(str);
}
// 打印后果,反复的曾经去掉了
/* 我的天
welcome
我是反复的
--------------
我的天
welcome
我是反复的 */
}
复制代码
那哈希表是怎么来保障元素的唯一性的呢,哈希表是通过 hashCode 和 equals 办法来独特保障的。
哈希表的存储数据过程(哈希表底层也保护了一个数组):
依据存储的元素计算出 hashCode 值,而后依据计算得出的 hashCode 值和数组的长度进行计算出存储的下标;如果下标的地位无元素,那么间接存储;如果有元素,那么应用要存入的元素和该元素进行 equals 办法,如果后果为真,则曾经有雷同的元素了,所以间接不存;如果后果假,那么进行存储,以链表的模式存储。
演示 HashSet 来存储自定义对象:
package 好好学 java;
public class Person {
// 属性
private String name;
private int age;
// 构造方法
public Person() {super();
}
public Person(String name, int age) {super();
this.name = name;
this.age = age;
}
// 要让哈希表存储不反复的元素,就必须重写 hasCode 和 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;
Person other = (Person) 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 "Person [name=" + name + ", age=" + age + "]";
}
// getter & setter
...
}
package 好好学 java;
import java.util.HashSet;
import java.util.Set;
public class Test {public static void main(String[] args) {
// 利用 HashSet 来存取自定义对象 Person
Set<Person> set = new HashSet<Person>();
set.add(new Person("张三", 12));
set.add(new Person("李四", 13));
set.add(new Person("王五", 22));
set.add(new Person("张三", 12));
// 遍历
for (Person p : set){System.out.println(p);
}
// 后果:向汇合中存储两个张三对象,然而汇合中就胜利存储了一个
/*Person [name= 王五, age=22]
Person [name= 李四, age=13]
Person [name= 张三, age=12]*/
}
}
复制代码
所以在向 HashSet 汇合中存储自定义对象时,为了保障 set 汇合的唯一性,那么必须重写 hashCode 和 equals 办法。
LinkedHashSet:
是基于链表和哈希表独特实现的,所以具备存取有序,元素惟一
package 好好学 java;
import java.util.LinkedHashSet;
public class Test {public static void main(String[] args) {
// 利用 LinkedHashSet 来存取自定义对象 Person
LinkedHashSet<Person> set = new LinkedHashSet<Person>();
set.add(new Person("张三", 12));
set.add(new Person("李四", 13));
set.add(new Person("王五", 22));
set.add(new Person("张三", 12));
// 遍历
for (Person p : set){System.out.println(p);
}
// 后果:向汇合中存储两个张三对象,然而汇合中就胜利存储了一个,
// 并且存进的程序,和取出来的程序是统一的
/*Person [name= 张三, age=12]
Person [name= 李四, age=13]
Person [name= 王五, age=22]*/
}
}
复制代码
TreeSet:
特点:存取无序,元素惟一,能够进行排序(排序是在增加的时候进行排序)。
TreeSet 是基于二叉树的数据结构,二叉树的:一个节点下不能多余两个节点。
二叉树的存储过程:
如果是第一个元素,那么间接存入,作为根节点,下一个元素进来是会跟节点比拟,如果大于节点放左边的,小于节点放右边;等于节点就不存储。前面的元素进来会顺次比拟,直到有地位存储为止
TreeSet 汇合存储 String 对象
package 好好学 java;
import java.util.TreeSet;
public class Test {public static void main(String[] args) {TreeSet<String> treeSet = new TreeSet<String>();
treeSet.add("abc");
treeSet.add("zbc");
treeSet.add("cbc");
treeSet.add("xbc");
for (String str : treeSet){System.out.println(str);
}
// 后果:取出来的后果是通过排序的
/*
abc
cbc
xbc
zbc*/
}
}
复制代码
TreeSet 保障元素的唯一性是有两种形式:
1、自定义对象实现 Comparable 接口,重写 comparaTo 办法,该办法返回 0 示意相等,小于 0 示意筹备存入的元素比被比拟的元素小,否则大于 0;
2、在创立 TreeSet 的时候向结构器中传入比拟器 Comparator 接口实现类对象,实现 Comparator 接口重写 compara 办法。
如果向 TreeSet 存入自定义对象时,自定义类没有实现 Comparable 接口,或者没有传入 Comparator 比拟器时,会呈现 ClassCastException 异样
上面就是演示用两种形式来存储自定义对象
package 好好学 java;
public class Person implements Comparable<Person>{
// 属性
private String name;
private int age;
// 构造方法
public Person() {super();
}
public Person(String name, int age) {super();
this.name = name;
this.age = age;
}
// 要让哈希表存储不反复的元素,就必须重写 hasCode 和 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;
Person other = (Person) 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 "Person [name=" + name + ", age=" + age + "]";
}
// getter & setter
...
@Override
public int compareTo(Person o) {
int result = this.age - o.age;
if (result == 0){return this.name.compareTo(o.name);
}
return result;
}
}
package 好好学 java;
import java.util.TreeSet;
public class Test {public static void main(String[] args) {
// 利用 TreeSet 来存储自定义类 Person 对象
TreeSet<Person> treeSet = new TreeSet<Person>();
// Person 类实现了 Comparable 接口,并且重写 comparaTo 办法
// 比拟规定是先依照 年龄排序,年龄相等的状况依照年龄排序
treeSet.add(new Person("张山 1", 20));
treeSet.add(new Person("张山 2", 16));
treeSet.add(new Person("张山 3", 13));
treeSet.add(new Person("张山 4", 17));
treeSet.add(new Person("张山 5", 20));
for (Person p : treeSet){System.out.println(p);
}
// 后果:依照 comparaTo 办法内的逻辑来排序的
/*
Person [name= 张山 3, age=13]
Person [name= 张山 2, age=16]
Person [name= 张山 4, age=17]
Person [name= 张山 1, age=20]
Person [name= 张山 5, age=20]
*/
}
}
复制代码
另一种形式:应用比拟器 Comparator
package 好好学 java;
public class Person{
// 属性
private String name;
private int age;
// 构造方法
public Person() {super();
}
public Person(String name, int age) {super();
this.name = name;
this.age = age;
}
// 要让哈希表存储不反复的元素,就必须重写 hasCode 和 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;
Person other = (Person) 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 "Person [name=" + name + ", age=" + age + "]";
}
// getter & setter
...
}
package 好好学 java;
import java.util.Comparator;
import java.util.TreeSet;
public class Test {public static void main(String[] args) {
// 利用 TreeSet 来存储自定义类 Person 对象
// 创立 TreeSet 对象的时候传入 Comparator 比拟器,应用匿名外部类的形式
// 比拟规定是先依照 年龄排序,年龄相等的状况依照年龄排序
TreeSet<Person> treeSet = new TreeSet<Person>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {if (o1 == o2){return 0;}
int result = o1.getAge() - o2.getAge();
if (result == 0){return o1.getName().compareTo(o2.getName());
}
return result;
}
});
treeSet.add(new Person("张山 1", 20));
treeSet.add(new Person("张山 2", 16));
treeSet.add(new Person("张山 3", 13));
treeSet.add(new Person("张山 4", 17));
treeSet.add(new Person("张山 5", 20));
for (Person p : treeSet){System.out.println(p);
}
// 后果:依照 compara 办法内的逻辑来排序的
/*
Person [name= 张山 3, age=13]
Person [name= 张山 2, age=16]
Person [name= 张山 4, age=17]
Person [name= 张山 1, age=20]
Person [name= 张山 5, age=20]
*/
}
}
复制代码
比拟器总结:
Collection 体系总结:
- List : “ 特点 :” 存取有序, 元素有索引, 元素能够反复.
- ArrayList : 数组构造, 查问快, 增删慢, 线程不平安, 因而效率高.
- Vector : 数组构造, 查问快, 增删慢, 线程平安, 因而效率低.
- LinkedList : 链表构造, 查问慢, 增删快, 线程不平安, 因而效率高.
addFirst() removeFirst() getFirst()
复制代码
- Set :” 特点 :” 存取无序, 元素无索引, 元素不能够反复.
- HashSet : 存储无序, 元素无索引, 元素不能够反复. 底层是哈希表.
请问 : 哈希表如何保障元素惟一呢 ? 底层是依赖 hashCode 和 equals 办法.
当存储元素的时候, 先依据 hashCode + 数组长度 计算出一个索引, 判断索引地位是否有元素.
如果没有元素, 间接存储, 如果有元素, 先判断 equals 办法, 比拟两个元素是否雷同, 不同则存储, 雷同则舍弃.
咱们自定义对象存储的元素肯定要实现 hashCode 和 equals.
- LinkedHashSet : 存储有序, 元素不能够反复.
- TreeSet : 存取无序, 然而能够排序 (天然排序), 元素不能够反复.
有两种排序形式 :
- 天然排序 :
咱们的元素必须实现 Comparable 接口. 可比拟的. 实现 CompareTo 办法.
- 比拟器排序 :
咱们须要自定义类, 实现 Comparetor 接口, 这个类就是比拟器实现 compare 办法.
而后在创立 TreeSet 的时候, 把比拟器对象作为参数传递给 TreeSet.
Map:
Map 是一个双列汇合,其中保留的是键值对,键要求放弃唯一性,值能够反复
键值是一一对应的,一个键只能对应一个值
Map 的特点:是存取无序,键不可反复
Map 在存储的时候,将键值传入 Entry,而后存储 Entry 对象
其中上面有HashMap,LinkedHashMap 和 TreeMap
HashMap:
是基于哈希表构造实现的,所以存储自定义对象作为键时,必须重写 hasCode 和 equals 办法。存取无序的
上面演示 HashMap 以自定义对象作为键:
package 好好学 java;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public class Test {public static void main(String[] args) {
// 利用 HashMap 存储,自定义对象 Person 作为键
// 为了保障键的唯一性,必须重写 hashCode 和 equals 办法
HashMap<Person,String> map = new HashMap<Person,String>();
map.put(new Person("张三", 12), "JAVA");
map.put(new Person("李四", 13), "IOS");
map.put(new Person("小花", 22), "JS");
map.put(new Person("小黑", 32), "PHP");
map.put(new Person("张三", 12), "C++");
Set<Entry<Person, String>> entrySet = map.entrySet();
Iterator<Entry<Person, String>> it = entrySet.iterator();
while (it.hasNext()){Entry<Person, String> entry = it.next();
System.out.println(entry.getKey() + "---" + entry.getValue());
}
// 后果:存入的时候增加了两个张三,如果 Map 中键雷同的时候,当前面的值会笼罩掉后面的值
/*
Person [name= 李四, age=13]---IOS
Person [name= 张三, age=12]---C++
Person [name= 小黑, age=32]---PHP
Person [name= 小花, age=22]---JS
*/
}
}
复制代码
LinkedHashMap:
用法跟 HashMap 基本一致,它是基于链表和哈希表构造的所以具备存取有序,键不反复的个性
上面演示利用 LinkedHashMap 存储,留神存的程序和遍历进去的程序是统一的:
package 好好学 java;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
public class Test {public static void main(String[] args) {
// 利用 LinkedHashMap 存储,自定义对象 Person 作为键
// 为了保障键的唯一性,必须重写 hashCode 和 equals 办法
LinkedHashMap<Person,String> map = new LinkedHashMap<Person,String>();
map.put(new Person("张三", 12), "JAVA");
map.put(new Person("李四", 13), "IOS");
map.put(new Person("小花", 22), "JS");
map.put(new Person("小黑", 32), "PHP");
map.put(new Person("张三", 12), "C++");
// foreach 遍历
for (Entry<Person,String> entry : map.entrySet()){System.out.println(entry.getKey()+"==="+entry.getValue());
}
// 后果:存入的时候增加了两个张三,如果 Map 中键雷同的时候,当前面的值会笼罩掉后面的值
// 留神:LinkedHashMap 的特点就是存取有序,取出来的程序就是和存入的程序保持一致
/*
Person [name= 张三, age=12]===C++
Person [name= 李四, age=13]===IOS
Person [name= 小花, age=22]===JS
Person [name= 小黑, age=32]===PHP
*/
}
}
复制代码
TreeMap:
给 TreeMap 汇合中保留自定义对象,自定义对象作为 TreeMap 汇合的 key 值。因为 TreeMap 底层应用的二叉树,其中寄存进去的所有数据都须要排序,要排序,就要求对象具备比拟性能。对象所属的类须要实现 Comparable 接口。或者给 TreeMap 汇合传递一个 Comparator 接口对象。
利用 TreeMap 存入自定义对象作为键:
package 好好学 java;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.TreeMap;
public class Test {public static void main(String[] args) {
// 利用 TreeMap 存储,自定义对象 Person 作为键
// 自定义对象实现 Comparable 接口或者传入 Comparator 比拟器
TreeMap<Person,String> map = new TreeMap<Person,String>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {if (o1 == o2){return 0;}
int result = o1.getAge() - o2.getAge();
if (result == 0){return o1.getName().compareTo(o2.getName());
}
return result;
}
});
map.put(new Person("张三", 12), "JAVA");
map.put(new Person("李四", 50), "IOS");
map.put(new Person("小花", 32), "JS");
map.put(new Person("小黑", 32), "PHP");
map.put(new Person("张三", 12), "C++");
// foreach 遍历
for (Entry<Person,String> entry : map.entrySet()){System.out.println(entry.getKey()+"==="+entry.getValue());
}
// 后果:存入的时候增加了两个张三,如果 Map 中键雷同的时候,当前面的值会笼罩掉后面的值
// 留神:TreeMap 取出来的程序是通过排序的,是依据 compara 办法排序的
/*
Person [name= 张三, age=12]===C++
Person [name= 小花, age=32]===JS
Person [name= 小黑, age=32]===PHP
Person [name= 李四, age=50]===IOS
*/
}
}
复制代码
二、汇合进阶总结
数组和第一类对象
无论应用的数组属于什么类型,数组标识符理论都是指向实在对象的一个句柄。那些对象自身是在内存“堆”里创立的。堆对象既可“隐式”创立(即默认产生),亦可“显式”创立(即明确指定,用一个 new 表达式)。堆对象的一部分(理论是咱们能拜访的惟一字段或办法)是只读的 length(长度)成员,它通知 咱们那个数组对象里最多能包容多少元素。对于数组对象,“[]”语法是咱们能采纳的惟一另类拜访办法。
对象数组和根本数据类型数组在应用办法上简直是完全一致的。惟一的差异在于对象数组包容的是句柄,而根本数据类型数组包容的是具体的数值
public class ArraySize {public static void main(String[] args) {
// Arrays of objects:
Weeble[] a; // Null handle
Weeble[] b = new Weeble[5]; // Null handles
Weeble[] c = new Weeble[4];
for (int i = 0; i < c.length; i++)
c[i] = new Weeble();
Weeble[] d = { new Weeble(), new Weeble(), new Weeble() };
// Compile error: variable a not initialized:
// !System.out.println("a.length=" + a.length);
System.out.println("b.length =" + b.length);
// The handles inside the array are
// automatically initialized to null:
for (int i = 0; i < b.length; i++)
System.out.println("b[" + i + "]=" + b[i]);
System.out.println("c.length =" + c.length);
System.out.println("d.length =" + d.length);
a = d;
System.out.println("a.length =" + a.length);
// Java 1.1 initialization syntax:
a = new Weeble[] { new Weeble(), new Weeble()};
System.out.println("a.length =" + a.length);
// Arrays of primitives:
int[] e; // Null handle
int[] f = new int[5];
int[] g = new int[4];
for (int i = 0; i < g.length; i++)
g[i] = i * i;
int[] h = { 11, 47, 93};
// Compile error: variable e not initialized:
// !System.out.println("e.length=" + e.length);
System.out.println("f.length =" + f.length);
// The primitives inside the array are
// automatically initialized to zero:
for (int i = 0; i < f.length; i++)
System.out.println("f[" + i + "]=" + f[i]);
System.out.println("g.length =" + g.length);
System.out.println("h.length =" + h.length);
e = h;
System.out.println("e.length =" + e.length);
// Java 1.1 initialization syntax:
e = new int[] { 1, 2};
System.out.println("e.length =" + e.length);
}
}
复制代码
输入如下:b.length = 5 b[0]=null b[1]=null b[2]=null b[3]=null b[4]=null c.length = 4 d.length = 3 a.length = 3 a.length = 2 f.length = 5 f[0]=0 f[1]=0 f[2]=0 f[3]=0 f[4]=0 g.length = 4 h.length = 3 e.length = 3 e.length = 2
其中,数组 a 只是初始化成一个 null 句柄。此时,编译器会禁止咱们对这个句柄作任何实际操作,除非已正 确地初始化了它。数组 b 被初始化成指向由 Weeble 句柄形成的一个数组,但那个数组里理论并未搁置任何 Weeble 对象。然而,咱们依然能够查问那个数组的大小,因为 b 指向的是一个非法对象。
换言之,咱们只晓得数组对象的大小或容量,不知其理论包容了多少个元素。
尽管如此,因为数组对象在创立之初会主动初始化成 null,所以可查看它是否为 null,判断一个特定的数组“空位”是否包容一个对象。相似地,由根本数据类型形成的数组会主动初始化成零(针对数值类型)、null(字符类型)或者 false(布尔类型)
数组 c 显示出咱们首先创立一个数组对象,再将 Weeble 对象赋给那个数组的所有“空位”。数组 d 揭示出“汇合初始化”语法,从而创立数组对象(用 new 命令明确进行,相似于数组 c),而后用 Weeble 对象进行 初始化,全副工作在一条语句里实现。上面这个表达式:
a = d;
复制代码
向咱们展现了如何获得同一个数组对象连接的句柄,而后将其赋给另一个数组对象,向咱们展现了如何获得同一个数组对象连接的句柄,而后将其赋给另一个数组对象
1. 根本数据类型汇合 汇合类只能包容对象句柄。但对一个数组,却既可令其间接包容根本类型的数据,亦可包容指向对象的句 柄。利用象 Integer、Double 之类的“封装器”类,可将根本数据类型的值置入一个汇合里。
无论将根本类型的数据置入数组,还是将其封装进入位于汇合的一个类内,都波及到执行效率的问题。显 然,若能创立和拜访一个根本数据类型数组,那么比起拜访一个封装数据的汇合,前者的效率会高出许多。
数组的返回
假设咱们当初想写一个办法,同时不心愿它仅仅返回一样货色,而是想返回一系列货色。此时,象 C 和 C++ 这样的语言会使问题复杂化,因为咱们不能返回一个数组,只能返回指向数组的一个指针。这样就十分麻烦,因为很难管制数组的“存在工夫”,它很容易造成内存“破绽”的呈现。
Java 采纳的是相似的办法,但咱们能“返回一个数组”。当然,此时返回的理论仍是指向数组的指针。但在 Java 里,咱们永远不用放心那个数组的是否可用—— 只有须要,它就会主动存在。而且垃圾收集器会在咱们实现后主动将其革除
public class IceCream {static String[] flav = { "Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin", "Praline Cream",
"Mud Pie" };
static String[] flavorSet(int n) {
// Force it to be positive & within bounds:
n = Math.abs(n) % (flav.length + 1);
String[] results = new String[n];
int[] picks = new int[n];
for(int i = 0; i < picks.length; i++)
picks[i] = -1;
for(int i = 0; i < picks.length; i++) {
retry:
while(true) {int t =(int)(Math.random() * flav.length);
for(int j = 0; j < i; j++)213
if(picks[j] == t) continue retry;
picks[i] = t;
results[i] = flav[t];
break;
}
}
return results;
}
public static void main(String[] args) {for (int i = 0; i < 20; i++) {System.out.println("flavorSet(" + i + ") =");
String[] fl = flavorSet(flav.length);
for (int j = 0; j < fl.length; j++)
System.out.println("t" + fl[j]);
}
}
}
复制代码
flavorSet()办法创立了一个名为 results 的 String 数组。该数组的大小为 n—— 具体数值取决于咱们传递给办法的自变量。随后,它从数组 flav 里随机筛选一些“香料”(Flavor),并将它们置入 results 里,并最终返回 results。返回数组与返回其余任何对象没什么区别—— 最终返回的都是一个句柄。
另一方面,留神当 flavorSet()随机筛选香料的时候,它须要保障以前呈现过的一次随机抉择不会再次出现。为达到这个目标,它应用了一个有限 while 循环,一直地作出随机抉择,直到发现未在 picks 数组里呈现过的一个元素为止(当然,也能够进行字串比拟,查看随机抉择是否在 results 数组里呈现过,但字串比拟的效率比拟低)。若胜利,就增加这个元素,并中断循环(break),再查找下一个(i 值会递增)。但假若 t 是一个已在 picks 里呈现过的数组,就用标签式的 continue 往回跳两级,强制抉择一个新 t。用一个调试程序能够很分明地看到这个过程。
汇合
为包容一组对象,最合适的抉择该当是数组。而且如果包容的是一系列根本数据类型,更是必须采纳数组。
毛病:类型未知
应用 Java 汇合的“毛病”是在将对象置入一个汇合时失落了类型信息。之所以会产生这种状况,是因为当初编写汇合时,那个汇合的程序员基本不晓得用户到底想把什么类型置入汇合。若批示某个汇合只容许特定的类型,会障碍它成为一个“惯例用处”的工具,为用户带来麻烦。为解决这个问题,汇合理论包容的是类型为 Object 的一些对象的句柄。
当然,也要留神汇合并不包含根本数据类型,因为它们并不是从“任何货色”继承来的。 Java 不容许人们滥用置入汇合的对象。如果将一条狗扔进一个猫的汇合,那么仍会将汇合内的所有货色都看作猫,所以在应用那条狗时会失去一个“违例”谬误。在同样的意义上,假若试图将一条狗的句柄“造型”到一只猫,那么运行期间仍会失去一个“违例”谬误
class Cat {
private int catNumber;
Cat(int i) {catNumber = i;}
void print() {System.out.println("Cat #" + catNumber);
}
}
class Dog {
private int dogNumber;
Dog(int i) {dogNumber = i;}
void print() {System.out.println("Dog #" + dogNumber);
}
}
public class CatsAndDogs {public static void main(String[] args) {Vector cats = new Vector();
for (int i = 0; i < 7; i++)
cats.addElement(new Cat(i));
// Not a problem to add a dog to cats:
cats.addElement(new Dog(7));
for (int i = 0; i < cats.size(); i++)
((Cat) cats.elementAt(i)).print();
// Dog is detected only at run-time
}
}
复制代码
- 谬误有时并不显露出来 在某些状况下,程序仿佛正确地工作,不造型回咱们原来的类型。第一种状况是相当非凡的:String 类从编译器取得了额定的帮忙,使其可能失常工作。只有编译器期待的是一个 String 对象,但它没有失去一个,就会主动调用在 Object 里定义、并且可能由任何 Java 类笼罩的 toString()办法。这个办法能生成满足要求的 String 对象,而后在咱们须要的时候应用。因而,为了让本人类的对象能显示进去,要做的全副事件就是笼罩 toString()办法。
class Mouse {
private int mouseNumber;
Mouse(int i) {mouseNumber = i;}
// Magic method:
public String toString() {return "This is Mouse #" + mouseNumber;}
void print(String msg) {if (msg != null)
System.out.println(msg);
System.out.println("Mouse number" + mouseNumber);
}
}
class MouseTrap {static void caughtYa(Object m) {Mouse mouse = (Mouse) m; // Cast from Object
mouse.print("Caught one!");
}
}
public class WorksAnyway {public static void main(String[] args) {Vector mice = new Vector();
for(int i = 0; i < 3; i++)
mice.addElement(new Mouse(i));
for(int i = 0; i < mice.size(); i++) {
// No cast necessary, automatic call
// to Object.toString():
System.out.println("Free mouse:" + mice.elementAt(i));
MouseTrap.caughtYa(mice.elementAt(i));
}
}
}
复制代码
可在 Mouse 里看到对 toString()的重定义代码。在 main()的第二个 for 循环中,可发现下述语句:
System.out.println("Free mouse:" +
mice.elementAt(i));
复制代码
在“+”后,编译器预期看到的是一个 String 对象。elementAt()生成了一个 Object,所以为取得心愿的 String,编译器会默认调用 toString()。但可怜的是,只有针对 String 能力失去象这样的后果;其余任何类型都不会进行这样的转换。
暗藏造型的第二种办法已在 Mousetrap 里失去了利用。caughtYa()办法接管的不是一个 Mouse,而是一个 Object。随后再将其造型为一个 Mouse。当然,这样做是十分冒失的,因为通过接管一个 Object,任何货色都能够传递给办法。然而,假若造型不正确—— 如果咱们传递了谬误的类型—— 就会在运行期间失去一个违例谬误。这当然没有在编译期进行查看好,但依然能避免问题的产生。留神在应用这个办法时毋需进行造型:MouseTrap.caughtYa(mice.elementAt(i));
- 生成能主动判断类型的 Vector 一个更“强壮”的计划是用 Vector 创立一个新类,使其只接管咱们指定的 类型,也只生成咱们心愿的类型。
class Gopher {
private int gopherNumber;
Gopher(int i) {gopherNumber = i;}
void print(String msg) {if (msg != null)
System.out.println(msg);
System.out.println("Gopher number" + gopherNumber);
}
}
class GopherTrap {static void caughtYa(Gopher g) {g.print("Caught one!");
}
}
class GopherVector {private Vector v = new Vector();
public void addElement(Gopher m) {v.addElement(m);
}
public Gopher elementAt(int index) {return (Gopher) v.elementAt(index);
}
public int size() {return v.size();
}
public static void main(String[] args) {GopherVector gophers = new GopherVector();
for (int i = 0; i < 3; i++)
gophers.addElement(new Gopher(i));
for (int i = 0; i < gophers.size(); i++)
GopherTrap.caughtYa(gophers.elementAt(i));
}
}
复制代码
新的 GopherVector 类有一个类型为 Vector 的 private 成员(从 Vector 继承有些麻烦,理由稍后便知),而且办法也和 Vector 相似。然而,它不会接管和产生一般 Object,只对 Gopher 对象 感兴趣。因为 GopherVector 只接管一个 Gopher(地鼠),所以如果咱们应用:gophers.addElement(new Pigeon()); 就会在编译期间取得一条出错音讯。采纳这种形式,只管从编码的角度看显得更令人爽朗,但能够立刻判断出是否应用了正确的类型。留神在应用 elementAt()时不用进行造型—— 它必定是一个 Gopher
枚举器
包容各种各样的对象正是汇合的首要任务。在 Vector 中,addElement()便是咱们插入对象采纳的办法,而 elementAt()是 提取对象的惟一办法。Vector 非常灵活,咱们可在任何时候抉择任何货色,并可应用不同的索引抉择多个元素。若从更高的角度看这个问题,就会发现它的一个缺点:须要当时晓得汇合的精确类型,否则无奈应用。乍看来,这一点仿佛没什么关系。但假若最开始决定应用 Vector,起初在程序中又决定(思考执行效率的起因)扭转成一个 List(属于 Java1.2 汇合库的一部分),这时又该如何做呢?咱们通常认为重复器是一种“轻量级”对象;也就是说,创立它只需付出极少的代价。但也正是因为这个起因,咱们常发现重复器存在一些仿佛很奇怪的限度。例如,有些重复器只能朝一个方向挪动。Java 的 Enumeration(枚举,正文②)便是具备这些限度的一个重复器的例子。除上面这些外,不可再用它 做其余任何事件:(1) 用一个名为 elements()的办法要求汇合为咱们提供一个 Enumeration。咱们首次调用它的 nextElement() 时,这个 Enumeration 会返回序列中的第一个元素。(2) 用 nextElement() 取得下一个对象。(3) 用 hasMoreElements()查看序列中是否还有更多的对象
class Hamster {
private int hamsterNumber;
Hamster(int i) {hamsterNumber = i;}
public String toString() {return "This is Hamster #" + hamsterNumber;}
}
class Printer {static void printAll(Enumeration e) {while (e.hasMoreElements())
System.out.println(e.nextElement().toString());
}
}
public class HamsterMaze {public static void main(String[] args) {Vector v = new Vector();
for (int i = 0; i < 3; i++)
v.addElement(new Hamster(i));
Printer.printAll(v.elements());
}
}
复制代码
认真钻研一下打印办法:
static void printAll(Enumeration e) {while(e.hasMoreElements())
System.out.println(e.nextElement().toString());
}
复制代码
留神其中没有与序列类型无关的信息。咱们领有的全副货色便是 Enumeration。为理解无关序列的状况,一个 Enumeration 便足够了:可获得下一个对象,亦可晓得是否已到达了开端。获得一系列对象,而后在其中遍历,从而执行一个特定的操作—— 这是一个颇有价值的编程概念
汇合的类型
V e c t o r
解体 Java Java 规范汇合里蕴含了 toString()办法,所以它们能生成本人的 String 表达方式,包含它们包容的对象。例如在 Vector 中,toString()会在 Vector 的各个元素中步进和遍历,并为每个元素调用 toString()。假设咱们当初想打印出本人类的地址。看起来仿佛简略地援用 this 即可(特地是 C++ 程序员有这样做的偏向):
public class CrashJava {public String toString() {return "CrashJava address:" + this + "n";}
public static void main(String[] args) {Vector v = new Vector();
for (int i = 0; i < 10; i++)
v.addElement(new CrashJava());
System.out.println(v);
}
}
复制代码
此时产生的是字串的主动类型转换。当咱们应用下述语句时:“CrashJava address:”+ this 编译器就在一个字串前面发现了一个“+”以及好象并非字串的其余货色,所以它会试图将 this 转换成一个字串。转换时调用的是 toString(),后者会产生一个递归调用。若在一个 Vector 内呈现这种事件,看起来堆栈就会溢出,同时违例管制机制基本没有机会作出响应。若的确想在这种状况下打印出对象的地址,解决方案就是调用 Object 的 toString 办法。此时就不用退出 this,只需应用 super.toString()。当然,采取这种做法也有一个前提:咱们必须从 Object 间接继承,或者没有一个父类笼罩了 toString 办法。
B i t S e t
BitSet 理论是由“二进制位”形成的一个 Vector。如果心愿高效率地保留大量“开-关”信息,就应应用 BitSet。它只有从尺寸的角度看才有意义;如果心愿的高效率的拜访,那么它的速度会比应用一些固有类型的数组慢一些。BitSet 的最小长度是一个长整数(Long)的长度:64 位。这意味着如果咱们筹备保留比这更小的数据,如 8 位数据,那么 BitSet 就显得节约了。所以最好创立本人的类,用它包容本人的标记位。
S t a c k
Stack 有时也能够称为“后入先出”(LIFO)汇合。换言之,咱们在堆栈里最初“压入”的货色将是当前第 一个“弹出”的。和其余所有 Java 汇合一样,咱们压入和弹出的都是“对象”,所以必须对本人弹出的货色 进行“造型”。上面是一个简略的堆栈示例,它能读入数组的每一行,同时将其作为字串压入堆栈。
public class Stacks {static String[] months = { "January", "February", "March", "April", "May",
"June", "July", "August", "September", "October", "November",
"December" };
public static void main(String[] args) {Stack stk = new Stack();
for (int i = 0; i < months.length; i++)
stk.push(months[i] + " ");
System.out.println("stk =" + stk);
// Treating a stack as a Vector:
stk.addElement("The last line");
System.out.println("element 5 =" + stk.elementAt(5));
System.out.println("popping elements:");
while (!stk.empty())
System.out.println(stk.pop());
}
}
复制代码
months 数组的每一行都通过 push()继承进入堆栈,稍后用 pop()从堆栈的顶部将其取出。要申明的一点是,Vector 操作亦可针对 Stack 对象进行。这可能是由继承的特质决定的—— Stack“属于”一种 Vector。因而,能对 Vector 进行的操作亦可针对 Stack 进行,例如 elementAt()办法
H a s h t a b l e
Vector 容许咱们用一个数字从一系列对象中作出抉择,所以它理论是将数字同对象关联起来了。但如果咱们想依据其余规范抉择一系列对象呢?堆栈就是这样的一个例子:它的抉择规范是“最初压入堆栈的货色”。这种“从一系列对象中抉择”的概念亦可叫作一个“映射”、“字典”或者“关联数组”。从概念上讲,它看起来象一个 Vector,但却不是通过数字来查找对象,而是用另一个对象来查找它们!这通常都属于一个程序中的重要过程。在 Java 中,这个概念具体反映到抽象类 Dictionary 身上。该类的接口是十分直观的 size()通知咱们其中蕴含了多少元素;isEmpty()判断是否蕴含了元素(是则为 true);put(Object key, Object value)增加一个值(咱们心愿的货色),并将其同一个键关联起来(想用于搜寻它的货色);get(Object key)取得与某个键对应的值;而 remove(Object Key)用于从列表中删除“键-值”对。还能够应用枚举技术:keys()产生对键的一个枚举(Enumeration);而 elements()产生对所有值的一个枚举。这便是一个 Dict ionary(字典)的全副。
public class AssocArray extends Dictionary {private Vector keys = new Vector();
private Vector values = new Vector();
public int size() {return keys.size();
}
public boolean isEmpty() {return keys.isEmpty();
}
public Object put(Object key, Object value) {keys.addElement(key);
values.addElement(value);
return key;
}
public Object get(Object key) {int index = keys.indexOf(key);
// indexOf() Returns -1 if key not found:
if (index == -1)
return null;
return values.elementAt(index);
}
public Object remove(Object key) {int index = keys.indexOf(key);
if (index == -1)
return null;
keys.removeElementAt(index);
Object returnval = values.elementAt(index);
values.removeElementAt(index);
return returnval;
}
public Enumeration keys() {return keys.elements();
}
public Enumeration elements() {return values.elements();
}
// Test it:
public static void main(String[] args) {AssocArray aa = new AssocArray();
for (char c = 'a'; c <= 'z'; c++)
aa.put(String.valueOf(c), String.valueOf(c).toUpperCase());
char[] ca = { 'a', 'e', 'i', 'o', 'u'};
for (int i = 0; i < ca.length; i++)
System.out.println("Uppercase:" + aa.get(String.valueOf(ca[i])));
}
}
复制代码
在对 AssocArray 的定义中,咱们留神到的第一个问题是它“扩大”了字典。这意味着 AssocArray 属于 Dictionary 的一种类型,所以可对其收回与 Dictionary 一样的申请。如果想生成本人的 Dictionary,而且就在这里进行,那么要做的全副事件只是填充位于 Dictionary 内的所有办法(而且必须笼罩所有办法,因为 它们—— 除构建器外—— 都是形象的)。规范 Java 库只蕴含 Dictionary 的一个变种,名为 Hashtable(散列表,正文③)。Java 的散列表具备与 AssocArray 雷同的接口(因为两者都是从 Dictionary 继承来的)。但有一个方面却反映出了差异:执行效率。若认真想想必须为一个 get()做的事件,就会发现在一个 Vector 里搜寻键的速度要慢得多。但此时用散列表却能够放慢不少速度。不用用简短的线性搜寻技术来查找一个键,而是用一个非凡的值,名为“散列码”。散列码能够获取对象中的信息,而后将其转换成那个对象“绝对惟一”的整数(int)。所有对象都有一个散列码,而 hashCode()是根类 Object 的一个办法。Hashtable 获取对象的 hashCode(),而后用它疾速查找键。
class Counter {
int i = 1;
public String toString() {return Integer.toString(i);
}
}
class Statistics {public static void main(String[] args) {Hashtable ht = new Hashtable();
for (int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
Integer r = new Integer((int) (Math.random() * 20));
if (ht.containsKey(r))
((Counter) ht.get(r)).i++;
else
ht.put(r, new Counter());
}
System.out.println(ht);
}
}
复制代码
- 创立“要害”类 但在应用散列表的时候,一旦咱们创立本人的类作为键使 用,就会遇到一个很常见的问题。例如,假如一套天气预报零碎将 Groundhog(土拔鼠)对象匹配成 Prediction(预报)。这看起来十分直观:咱们创立两个类,而后将 Groundhog 作为键应用,而将 Prediction 作为值应用。如下所示:
class Groundhog {
int ghNumber;
Groundhog(int n) {ghNumber = n;}
}
class Prediction {boolean shadow = Math.random() > 0.5;
public String toString() {if (shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
}
public class SpringDetector {public static void main(String[] args) {Hashtable ht = new Hashtable();
for (int i = 0; i < 10; i++)
ht.put(new Groundhog(i), new Prediction());
System.out.println("ht =" + ht + "n");
System.out.println("Looking up prediction for groundhog #3:");
Groundhog gh = new Groundhog(3);
if (ht.containsKey(gh))
System.out.println((Prediction) ht.get(gh));
}
}
复制代码
问题在于 Groundhog 是从通用的 Object 根类继承的(若当初未指 定根底类,则所有类最终都是从 Object 继承的)。事实上是用 Object 的 hashCode()办法生成每个对象的散列码,而且默认状况下只应用它的对象的地址。所以,Groundhog(3)的第一个实例并不会产生与 Groundhog(3)第二个实例相等的散列码,而咱们用第二个实例进行检索 或者认为此时要做的全副事件就是正确地笼罩 hashCode()。但这样做仍然行不能,除非再做另一件事件:笼罩也属于 Object 一部分的 equals()。当散列表试图判断咱们的键是否等于表内的某个键时,就会用到这个办法。同样地,默认的 Object.equals()只是简略地比拟对象地址,所以一个 Groundhog(3)并不等于 另一个 Groundhog(3)。因而,为了在散列表中将本人的类作为键应用,必须同时笼罩 hashCode()和 equals(),就象上面展现的那样:
class Groundhog {
int ghNumber;
Groundhog(int n) {ghNumber = n;}
}
class Prediction {boolean shadow = Math.random() > 0.5;
public String toString() {if (shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
}
public class SpringDetector {public static void main(String[] args) {Hashtable ht = new Hashtable();
for (int i = 0; i < 10; i++)
ht.put(new Groundhog(i), new Prediction());
System.out.println("ht =" + ht + "n");
System.out.println("Looking up prediction for groundhog #3:");
Groundhog gh = new Groundhog(3);
if (ht.containsKey(gh))
System.out.println((Prediction) ht.get(gh));
}
}
复制代码
Groundhog2.hashCode()将土拔鼠号码作为一个标识符返回(在这个例子中,程序员须要保障没有两个土拔鼠用同样的 ID 号码并存)。为了返回一个举世无双的标识符,并不需要 hashCode(),equals()办法必须可能严格判断两个对象是否相等。equals()办法要进行两种查看:查看对象是否为 null;若不为 null,则持续查看是否为 Groundhog2 的一个实例(要用到 instanceof 关键字)。即便为了继续执行 equals(),它也应该是一个 Groundhog2。正如大家看到的那样,这种比拟建设在理论 ghNumber 的根底上。这一次一旦咱们运行程序,就会看到它终于产生了正确的输入(许多 Java 库的类都笼罩了 hashcode() 和 equals()办法,以便与本人提供的内容适应)。
再论枚举器
将穿梭一个序列的操作与那个序列的根底构造分隔开。在上面的例子里,PrintData 类用一个 Enumeration 在一个序列中挪动,并为每个对象都调用 toString()办法。此时创立了两个不同类型的汇合:一个 Vector 和一个 Hashtable。并且在它们外面别离填 充 Mouse 和 Hamster 对象,因为 Enumeration 暗藏了基层汇合的构造,所以 PrintData 不晓得或者不关怀 Enumeration 来自于什么类型的汇合:
class PrintData {static void print(Enumeration e) {while (e.hasMoreElements())
System.out.println(e.nextElement().toString());
}
}
class Enumerators2 {public static void main(String[] args) {Vector v = new Vector();
for (int i = 0; i < 5; i++)
v.addElement(new Mouse(i));
Hashtable h = new Hashtable();
for (int i = 0; i < 5; i++)
h.put(new Integer(i), new Hamster(i));
System.out.println("Vector");
PrintData.print(v.elements());
System.out.println("Hashtable");
PrintData.print(h.elements());
}
}
复制代码
留神 PrintData.print()利用了这些汇合中的对象属于 Object 类这一事实,所以它调用了 toString()。但在 解决本人的理论问题时,常常都要保障本人的 Enumeration 穿梭某种特定类型的汇合。例如,可能要求汇合 中的所有元素都是一个 Shape(几何形态),并含有 draw()办法。若呈现这种状况,必须从 Enumeration.nextElement()返回的 Object 进行下溯造型,以便产生一个 Shape。
排序
编写通用的排序代码时,面临的一个问题是必须依据对象的理论类型来执行比拟运算,从而实现正确的排序。当然,一个方法是为每种不同的类型都写一个不同的排序办法。然而,应意识到假若这样做,当前减少新类型时便不易实现代码的反复利用。程序设计一个次要的指标就是“将发生变化的货色同放弃不变的货色分隔开”。在这里,放弃不变的代码是通用的排序算法,而每次应用时都要变动的是对象的理论比拟办法。因而,咱们不可将比拟代码“硬编码”到多个不同的排序例程内,而是采纳“回调”技术。 利用回调,常常发生变化的那局部代码会封装到它本人的类内,而总是放弃雷同的代码则“回调”发生变化的代码。这样一来,不同的对象就能够表白不同的比拟形式,同时向它们传递雷同的排序代码。上面这个“接口”(Interface)展现了如何比拟两个对象,它将那些“要发生变化的货色”封装在内:
interface Compare {boolean lessThan(Object lhs, Object rhs);
boolean lessThanOrEqual(Object lhs, Object rhs);
}
复制代码
对这两种办法来说,lhs 代表本次比拟中的“左手”对象,而 rhs 代表“右手”对象。可创立 Vector 的一个子类,通过 Compare 实现“疾速排序”。对于这种算法,包含它的速度以及原理等等
public class SortVector extends Vector {
private Compare compare; // To hold the callback
public SortVector(Compare comp) {compare = comp;}
public void sort() {quickSort(0, size() - 1);
}
private void quickSort(int left, int right) {if (right > left) {Object o1 = elementAt(right);
int i = left - 1;
int j = right;
while (true) {while (compare.lessThan(elementAt(++i), o1))
;
while (j > 0)
if (compare.lessThanOrEqual(elementAt(--j), o1))
break; // out of while
if (i >= j)
break;
swap(i, j);
}
swap(i, right);
quickSort(left, i - 1);
quickSort(i + 1, right);
}
}
private void swap(int loc1, int loc2) {Object tmp = elementAt(loc1);
setElementAt(elementAt(loc2), loc1);
setElementAt(tmp, loc2);
}
}
复制代码
为应用 SortVector,必须创立一个类,令其为咱们筹备排序的对象实现 Compare。此时外部类并不显得特地重要,但对于代码的组织却是无益的。上面是针对 String 对象的一个例子
public class StringSortTest {
static class StringCompare implements Compare {public boolean lessThan(Object l, Object r) {return ((String) l).toLowerCase().compareTo(((String) r).toLowerCase()) < 0;
}
public boolean lessThanOrEqual(Object l, Object r) {return ((String) l).toLowerCase().compareTo(((String) r).toLowerCase()) <= 0;
}
}
public static void main(String[] args) {SortVector sv = new SortVector(new StringCompare());
sv.addElement("d");
sv.addElement("A");
sv.addElement("C");
sv.addElement("c");
sv.addElement("b");
sv.addElement("B");
sv.addElement("D");
sv.addElement("a");
sv.sort();
Enumeration e = sv.elements();
while (e.hasMoreElements())
System.out.println(e.nextElement());
}
}
复制代码
一旦设置好框架,就能够十分不便地重复使用象这样的一个设计—— 只需简略地写一个类,将“须要发生变化”的货色封装进去,而后将一个对象传给 SortVector 即可 继承(extends)在这儿用于创立一种新类型的 Vector—— 也就是说,SortVector 属于一种 Vector,并带有一些附加的性能。继承在这里可施展很大的作用,但了带来了问题。它使一些办法具备了 final 属性,所以不能笼罩它们。如果想创立一个排好序的 Vector,令其只接管和生成 String 对象,就会遇到麻烦。因为 addElement()和 elementAt()都具备 final 属性,而且它们都是咱们必须笼罩的办法,否则便无奈实现只能接管和产生 String 对象。但在另一方面,请思考采纳“合成”办法:将一个对象置入一个新类的外部。此时,不是改写上述代码来达到这个目标,而是在新类里简略地应用一个 SortVector。在这种状况下,用于实现 Compare 接口的外部类就能够“匿名”地创立
import java.util.*;
public class StrSortVector {
private SortVector v = new SortVector(
// Anonymous inner class:
new Compare() {public boolean lessThan(Object l, Object r) {return ((String) l).toLowerCase().compareTo(((String) r).toLowerCase()) < 0;
}
public boolean lessThanOrEqual(Object l, Object r) {return ((String) l).toLowerCase().compareTo(((String) r).toLowerCase()) <= 0;
}
});
private boolean sorted = false;
public void addElement(String s) {v.addElement(s);
sorted = false;
}
public String elementAt(int index) {if(!sorted) {v.sort();232
sorted = true;
}
return (String)v.elementAt(index);
}
public Enumeration elements() {if (!sorted) {v.sort();
sorted = true;
}
return v.elements();}
// Test it:
public static void main(String[] args) {StrSortVector sv = new StrSortVector();
sv.addElement("d");
sv.addElement("A");
sv.addElement("C");
sv.addElement("c");
sv.addElement("b");
sv.addElement("B");
sv.addElement("D");
sv.addElement("a");
Enumeration e = sv.elements();
while (e.hasMoreElements())
System.out.println(e.nextElement());
}
}
复制代码
新汇合
这张图刚开始的时候可能让人有点儿摸不着头脑,置信大家会真正了解它理论只有三个汇合组件:Map,List 和 Set。而且每个组件理论只有两、三种实现形式 虚线框代表“接口”,点线框代表“形象”类,而实线框代表一般(理论)类。点线箭头示意一个特定的类筹备实现一个接口(在抽象类的状况下,则是“局部”实现一个接口)。双线箭头示意一个类可生成箭头指向的那个类的对象。致力于包容对象的接口是 Collection,List,Set 和 Map。在传统状况下,咱们须要写大量代码能力同这些接口打交道。而且为了指定本人想应用的精确类型,必须在创立之初进行设置。所以可能创立上面这样的一 个 List:
List x = new LinkedList();
复制代码
当然,也能够决定将 x 作为一个 LinkedList 应用(而不是一个一般的 List),并用 x 负载精确的类型信息。应用接口的益处就是一旦决定扭转本人的施行细节,要做的全副事件就是在创立的时候扭转它,就象上面这样:
List x = new ArrayList();
复制代码
在类的分级构造中,可看到大量以“Abstract”(形象)结尾的类,这刚开始可能会使人感觉蛊惑。它们实际上是一些工具,用于“局部”实现一个特定的接口。举个例子来说,如果想生成本人的 Set,就不是从 Set 接口开始,而后自行实现所有办法。相同,咱们能够从 AbstractSet 继承,只需极少的工作即可失去本人的新类。尽管如此,新汇合库依然蕴含了足够的性能,可满足咱们的简直所有需要。所以思考到咱们的目标,可疏忽所有以“Abstract”结尾的类。因而,在观看这张示意图时,真正须要关怀的只有位于最顶部的“接口”以及一般(理论)类—— 均用实线方框突围。通常须要生成理论类的一个对象,将其上溯造型为对应的接口。当前即可在代码的任何中央应用那个接口。上面是一个简略的例子,它用 String 对象填充一个汇合,而后打印出汇合内的每一个元素:
public class SimpleCollection {public static void main(String[] args) {Collection c = new ArrayList();
for (int i = 0; i < 10; i++)
c.add(Integer.toString(i));
Iterator it = c.iterator();
while (it.hasNext())
System.out.println(it.next());
}
}
复制代码
main()的第一行创立了一个 ArrayList 对象,而后将其上溯造型成为一个汇合。因为这个例子只应用了 Collection 办法,所以从 Collection 继承的一个类的任何对象都能够失常工作。但 ArrayList 是一个典型的 Collection,它代替了 Vector 的地位。add()办法的作用是将一个新元素置入汇合里。然而,用户文档审慎地指出 add()“保障这个汇合蕴含了指定的元素”。这一点是为 Set 作铺垫的,后者只有在元素不存在的前提下才会真的退出那个元素。对于 ArrayList 以及其余任何模式的 List,add()必定意味着“间接退出”。利用 iterator()办法,所有汇合都能生成一个“重复器”(Iterator)。重复器其实就象一个“枚举”(Enumeration),是后者的一个替代物,只是:(1) 它采纳了一个历史上默认、而且早在 OOP 中失去宽泛驳回的名字(重复器)。(2) 采纳了比 Enumeration 更短的名字:hasNext()代替了 hasMoreElement(),而 next()代替了 nextElement()。(3) 增加了一个名为 remove()的新办法,可删除由 Iterator 生成的上一个元素。所以每次调用 next()的时候,只需调用 remove()一次
应用 C o l l e c t i o n s
上面这张表格总结了用一个汇合能做的所有事件(亦可对 Set 和 List 做同样的事件,只管 List 还提供了一 些额定的性能)。Map 不是从 Collection 继承的,所以要独自看待
boolean add(Object) *保障汇合内蕴含了自变量。如果它没有增加自变量,就返回 false(假)boolean addAll(Collection) *增加自变量内的所有元素。如果没有增加元素,则返回 true(真)void clear() *删除汇合内的所有元素 boolean contains(Object) 若汇合蕴含自变量,就返回“真”boolean containsAll(Collection) 若汇合蕴含了自变量内的所有元素,就返回“真”boolean isEmpty() 若汇合内没有元素,就返回“真”Iterator iterator() 返回一个重复器,以用它遍历汇合的各元素 boolean remove(Object) *如自变量在汇合里,就删除那个元素的一个实例。如果已进行了删除,就返回“真”boolean removeAll(Collection) *删除自变量里的所有元素。如果已进行了任何删除,就返回“真”boolean retainAll(Collection) *只保留蕴含在一个自变量里的元素(一个实践的“交加”)。如果已进 行了任何扭转,就返回“真”int size() 返回汇合内的元素数量 Object[] toArray() 返回蕴含了汇合内所有元素的一个数组 *这是一个“可选的”办法,有的汇合可能并未实现它。若的确如此,该办法就会遇到一个 UnsupportedOperatiionException,即一个“操作不反对”违例。
上面这个例子向大家演示了所有办法。同样地,它们只对从汇合继承的货色无效,一个 ArrayList 作为一种“不罕用的分母”应用
public class Collection1 {
// Fill with 'size' elements, start
// counting at 'start':
public static Collection fill(Collection c, int start, int size) {for (int i = start; i < start + size; i++)
c.add(Integer.toString(i));
return c;
}
// Default to a "start" of 0:
public static Collection fill(Collection c, int size) {return fill(c, 0, size);
}
// Default to 10 elements:
public static Collection fill(Collection c) {return fill(c, 0, 10);
}
// Create & upcast to Collection:
public static Collection newCollection() {return fill(new ArrayList());
// ArrayList is used for simplicity, but it's
// only seen as a generic Collection
// everywhere else in the program.
}
// Fill a Collection with a range of values:
public static Collection newCollection(int start, int size) {return fill(new ArrayList(), start, size);
}
// Moving through a List with an iterator:
public static void print(Collection c) {for (Iterator x = c.iterator(); x.hasNext();)
System.out.print(x.next() + " ");
System.out.println();}
public static void main(String[] args) {Collection c = newCollection();
c.add("ten");
c.add("eleven");
print(c);
// Make an array from the List:
Object[] array = c.toArray();
// Make a String array from the List:
String[] str = (String[]) c.toArray(new String[1]);
// Find max and min elements; this means
// different things depending on the way
// the Comparable interface is implemented:
System.out.println("Collections.max(c) =" + Collections.max(c));
System.out.println("Collections.min(c) =" + Collections.min(c));
// Add a Collection to another Collection
c.addAll(newCollection());
print(c);
c.remove("3"); // Removes the first one
print(c);
c.remove("3"); // Removes the second one
print(c);
// Remove all components that are in the
// argument collection:
c.removeAll(newCollection());
print(c);
c.addAll(newCollection());
print(c);
// Is an element in this Collection?
System.out.println("c.contains("4") =" + c.contains("4"));
// Is a Collection in this Collection?
System.out.println("c.containsAll(newCollection()) ="
+ c.containsAll(newCollection()));
Collection c2 = newCollection(5, 3);
// Keep all the elements that are in both
// c and c2 (an intersection of sets):
c.retainAll(c2);
print(c);
// Throw away all the elements in c that
// also appear in c2:
c.removeAll(c2);
System.out.println("c.isEmpty() =" + c.isEmpty());
c = newCollection();
print(c);
c.clear(); // Remove all elements
System.out.println("after c.clear():");
print(c);
}
} 复制代码
newCollection()的两个版本都创立了 ArrayList,用于蕴含不同的数据集,并将它们作为汇合对象返回。所以很显著,除了 Collection 接口之外,不会再用到其余什么。
应用 L i s t s
List(接口)程序是 List 最重要的个性;它可保障元素依照规定的顺序排列。 List 为 Collection 增加了大量办法,以便咱们在 List 中部插入和删除元素(只举荐对 LinkedList 这样做)。List 也会生成一个 ListIterator(列表重复器),利用它可在一个列表里朝两个方向遍历,同时插入和删除位于列表中部的元素(同样地,只倡议对 LinkedList 这样做)ArrayList 由一个数组后推失去的 List。作为一个惯例用处的对象容器应用,用于替换原先的 Vector。容许咱们快速访问元素,但在从列表中部插入和删除元素时,速度却嫌稍慢。个别只应该用 ListIterator 对一个 ArrayList 进行向前和向后遍历,不要用它删除和插入元素;与 LinkedList 相比,它的效率要低许多 LinkedList 提供优化的程序拜访性能,同时能够高效率地在列表中部进行插入和删除操作。但在进行随机拜访时,速度却相当慢,此时应换用 ArrayList。 也提供了 addFirst(),addLast(),getFirst(),getLast(),removeFirst() 以及 removeLast()
(未在任何接口或根底类中定义),以便将其作为一个规格、队列以及一个双向队列应用
public class List1 {// Wrap Collection1.fill() for convenience:
public static List fill(List a) {return (List) Collection1.fill(a);
}
// You can use an Iterator, just as with a
// Collection, but you can also use random
// access with get():
public static void print(List a) {for (int i = 0; i < a.size(); i++)
System.out.print(a.get(i) + " ");
System.out.println();}
static boolean b;
static Object o;
static int i;
static Iterator it;
static ListIterator lit;
public static void basicTest(List a) {a.add(1, "x"); // Add at location 1
a.add("x"); // Add at end
// Add a collection:
a.addAll(fill(new ArrayList()));
// Add a collection starting at location 3:
a.addAll(3, fill(new ArrayList()));
b = a.contains("1"); // Is it in there?
// Is the entire collection in there?
b = a.containsAll(fill(new ArrayList()));
// Lists allow random access, which is cheap
// for ArrayList, expensive for LinkedList:
o = a.get(1); // Get object at location 1
i = a.indexOf("1"); // Tell index of object
// indexOf, starting search at location 2:
i = a.indexOf("1", 2);
b = a.isEmpty(); // Any elements inside?
it = a.iterator(); // Ordinary Iterator
lit = a.listIterator(); // ListIterator
lit = a.listIterator(3); // Start at loc 3
i = a.lastIndexOf("1"); // Last match
i = a.lastIndexOf("1", 2); // ...after loc 2
a.remove(1); // Remove location 1
a.remove("3"); // Remove this object
a.set(1, "y"); // Set location 1 to "y"
// Keep everything that's in the argument
// (the intersection of the two sets):
a.retainAll(fill(new ArrayList()));
// Remove elements in this range:
a.removeRange(0, 2);
// Remove everything that's in the argument:
a.removeAll(fill(new ArrayList()));
i = a.size(); // How big is it?
a.clear(); // Remove all elements}
public static void iterMotion(List a) {ListIterator it = a.listIterator();
b = it.hasNext();
b = it.hasPrevious();
o = it.next();
i = it.nextIndex();
o = it.previous();
i = it.previousIndex();}
public static void iterManipulation(List a) {ListIterator it = a.listIterator();
it.add("47");
// Must move to an element after add():
it.next();
// Remove the element that was just produced:
it.remove();
// Must move to an element after remove():
it.next();
// Change the element that was just produced:
it.set("47");
}
public static void testVisual(List a) {print(a);
List b = new ArrayList();
fill(b);
System.out.print("b =");
print(b);
a.addAll(b);
a.addAll(fill(new ArrayList()));
print(a);
// Shrink the list by removing all the
// elements beyond the first 1/2 of the list
System.out.println(a.size());
System.out.println(a.size() / 2);
a.removeRange(a.size() / 2, a.size() / 2 + 2);
print(a);
// Insert, remove, and replace elements
// using a ListIterator:
ListIterator x = a.listIterator(a.size() / 2);
x.add("one");
print(a);
System.out.println(x.next());
x.remove();
System.out.println(x.next());
x.set("47");
print(a);
// Traverse the list backwards:
x = a.listIterator(a.size());
while (x.hasPrevious())
System.out.print(x.previous() + " ");
System.out.println();
System.out.println("testVisual finished");
}
// There are some things that only
// LinkedLists can do:
public static void testLinkedList() {LinkedList ll = new LinkedList();
Collection1.fill(ll, 5);
print(ll);
// Treat it like a stack, pushing:
ll.addFirst("one");
ll.addFirst("two");
print(ll);
// Like "peeking" at the top of a stack:
System.out.println(ll.getFirst());
// Like popping a stack:
System.out.println(ll.removeFirst());
System.out.println(ll.removeFirst());
// Treat it like a queue, pulling elements
// off the tail end:
System.out.println(ll.removeLast());
// With the above operations, it's a dequeue!
print(ll);
}
public static void main(String args[]) {
// Make and fill a new list each time:
basicTest(fill(new LinkedList()));
basicTest(fill(new ArrayList()));
iterMotion(fill(new LinkedList()));
iterMotion(fill(new ArrayList()));
iterManipulation(fill(new LinkedList()));
iterManipulation(fill(new ArrayList()));
testVisual(fill(new LinkedList()));
testLinkedList();}
} 复制代码
在 basicTest()和 iterMotiion() 中,只是简略地收回调用,以便揭示出正确的语法。而且只管捕捉了返回 值,然而并未应用它。在某些状况下,之所以不捕捉返回值,是因为它们没有什么特地的用途。在正式应用 它们前,应认真钻研一下本人的联机文档,把握这些办法残缺、正确的用法。
ArrayList 应用实例
import java.awt.List;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author sihai
* @time 2018/4/19
* ArrayList 用法示例阐明
*
*/
public class Main {public static void main(String[] args) {
//ArrayList 用法示例
ArrayList<String> m_ArrayList=new ArrayList<String>();
m_ArrayList.add("Evankaka");
m_ArrayList.add("sihai");
m_ArrayList.add("德德");
m_ArrayList.add("Evankaka");
m_ArrayList.add("小红");
m_ArrayList.set(2,"sihai2");// 将索引地位为 2 的对象批改
m_ArrayList.add(3,"好好学 java");// 将对象增加到索引地位为 3 的地位
//ArrayList 遍历办法 1
Iterator<String> it_ArrayList = m_ArrayList.iterator();
System.out.println("ArrayList 遍历办法 1");
while (it_ArrayList.hasNext()) {System.out.println(it_ArrayList.next());
}
//ArrayList 遍历办法 2
System.out.println("ArrayList 遍历办法 2");
for(Object o:m_ArrayList){System.out.println(o);
}
//ArrayList 遍历办法 2
System.out.println("ArrayList 遍历办法 3");
for(int i = 0; i<m_ArrayList.size(); i++){System.out.println(m_ArrayList.get(i));
}
// 删除元素
m_ArrayList.remove("Evankaka");
it_ArrayList = m_ArrayList.iterator();
System.out.println("ArrayList 删除元素后的遍历");
while (it_ArrayList.hasNext()) {String m_String=it_ArrayList.next();
if(m_String.equals("好好学 java")){it_ArrayList.remove();
}else{System.out.println(m_String);
}
}
}
}
复制代码
输入后果:
ArrayList 遍历办法 1 Evankaka sihai sihai2 好好学 java Evankaka 小红 ArrayList 遍历办法 2 Evankaka sihai sihai2 好好学 java Evankaka 小红 ArrayList 遍历办法 3 Evankaka sihai sihai2 好好学 java Evankaka 小红 ArrayList 删除元素后的遍历 sihai sihai2 Evankaka 小红
ArrayList 留神
(1)应用 Iterator 迭代汇合过程中,不可批改汇合元素,否则会引发异样。并且 Iterator 只能向后迭代(2)如果你想在循环过程中去掉某个元素, 只能调用 it.remove 办法, 不能应用 list.remove 办法, 否则肯定出并发拜访的谬误.
应用 S e t s
Set 齐全就是一个 Collection,只是具备不同的行为(这是实例和多形性最现实的利用:用于表白不同的行为)。在这里,一个 Set 只容许每个对象存在一个实例(正如大家当前会看到的那样,一个对象的“值”的形成是相当简单的) Set(接口)增加到 Set 的每个元素都必须是举世无双的;否则 Set 就不会增加反复的元素。增加到 Set 里的对象必须定义 equals(),从而建设对象的唯一性。Set 领有与 Collection 完全相同的接口。一个 Set 不能保障本人可按任何特定的程序维持本人的元素 HashSet 用于除十分小的以外的所有 Set。对象也必须定义 hashCode() ArraySet 由一个数组后推失去的 Set。面向十分小的 Set 设计,特地是那些须要频繁创立和删除的。对于小 Set,与 HashSet 相比,ArraySet 创立和重复所需付出的代价都要小得多。但随着 Set 的增大,它的性能也 会大打折扣。不须要 HashCode() TreeSet 由一个“红黑树”后推失去的程序 Set(正文⑦)。这样一来,咱们就能够从一个 Set 里提到一个 程序汇合
public class Set1 {public static void testVisual(Set a) {Collection1.fill(a);
Collection1.fill(a);
Collection1.fill(a);
Collection1.print(a); // No duplicates!
// Add another set to this one:
a.addAll(a);
a.add("one");
a.add("one");
a.add("one");
Collection1.print(a);
// Look something up:
System.out.println("a.contains("one"):" + a.contains("one"));
}
public static void main(String[] args) {testVisual(new HashSet());
testVisual(new TreeSet());
}
}
复制代码
反复的值被增加到 Set,但在打印的时候,咱们会发现 Set 只承受每个值的一个实例。运行这个程序时,会留神到由 HashSet 维持的程序与 ArraySet 是不同的。这是因为它们采纳了不同的办法来保留元素,以便它们当前的定位。ArraySet 放弃着它们的程序状态,而 HashSet 应用一个散列函数,这是特地为疾速检索设计的)。
class MyType implements Comparable {
private int i;
public MyType(int n) {i = n;}
public boolean equals(Object o) {return (o instanceof MyType) && (i == ((MyType) o).i);
}
public int hashCode() {return i;}
public String toString() {return i + " ";}
public int compareTo(Object o) {int i2 = ((MyType) o).i;
return (i2 < i ? -1 : (i2 == i ? 0 : 1));
}
}
public class Set2 {public static Set fill(Set a, int size) {for (int i = 0; i < size; i++)
a.add(new MyType(i));
return a;
}
public static Set fill(Set a) {return fill(a, 10);
}
public static void test(Set a) {fill(a);
fill(a); // Try to add duplicates
fill(a);
a.addAll(fill(new TreeSet()));
System.out.println(a);
}
public static void main(String[] args) {test(new HashSet());
test(new TreeSet());
}
}
复制代码
但只有要把类置入一个 HashSet 的前提下,才有必要应用 hashCode()—— 这种状况是齐全有可能的,因为通常应先抉择作为一个 Set 实现。
应用 M a p s
Map(接口)维持“键-值”对应关系(对),以便通过一个键查找相应的值 HashMap 基于一个散列表实现(用它代替 Hashtable)。针对“键-值”对的插入和检索,这种模式具备最稳固的性能。可通过构建器对这一性能进行调整,以便设置散列表的“能力”和“装载因子”ArrayMap 由一个 ArrayList 后推失去的 Map。对重复的程序提供了准确的管制。面向十分小的 Map 设计,特地是那些须要常常创立和删除的。对于十分小的 Map,创立和重复所付出的代价要比 HashMap 低得多。但在 Map 变大当前,性能也会相应地大幅度降低 TreeMap 在一个“红-黑”树的根底上实现。查看键或者“键-值”对时,它们会按固定的顺序排列(取决于 Comparable 或 Comparator,稍后即会讲到)。TreeMap 最大的益处就是咱们失去的是已排好序的后果。TreeMap 是含有 subMap()办法的惟一一种 Map,利用它能够返回树的一部分
public class Map1 {public final static String[][] testData1 = {{ "Happy", "Cheerful disposition"},
{"Sleepy", "Prefers dark, quiet places"},
{"Grumpy", "Needs to work on attitude"},
{"Doc", "Fantasizes about advanced degree"},
{"Dopey", "'A' for effort"},
{"Sneezy", "Struggles with allergies"},
{"Bashful", "Needs self-esteem workshop"}, };
public final static String[][] testData2 = {{ "Belligerent", "Disruptive influence"},
{"Lazy", "Motivational problems"},
{"Comatose", "Excellent behavior"} };
public static Map fill(Map m, Object[][] o) {for (int i = 0; i < o.length; i++)
m.put(o[i][0], o[i][1]);
return m;
}
// Producing a Set of the keys:
public static void printKeys(Map m) {System.out.print("Size =" + m.size() + ",");
System.out.print("Keys:");
Collection1.print(m.keySet());
}
// Producing a Collection of the values:
public static void printValues(Map m) {System.out.print("Values:");
Collection1.print(m.values());
}
// Iterating through Map.Entry objects (pairs):
public static void print(Map m) {Collection entries = m.entries();
Iterator it = entries.iterator();
while (it.hasNext()) {Map.Entry e = (Map.Entry) it.next();
System.out.println("Key =" + e.getKey() + ", Value ="
+ e.getValue());
}
}
public static void test(Map m) {fill(m, testData1);
// Map has 'Set' behavior for keys:
fill(m, testData1);
printKeys(m);
printValues(m);
print(m);
String key = testData1[4][0];
String value = testData1[4][1];
System.out.println("m.containsKey("" + key + ""):"
+ m.containsKey(key));
System.out.println("m.get("" + key + ""):" + m.get(key));
System.out.println("m.containsValue("" + value + ""):"
+ m.containsValue(value));
Map m2 = fill(new TreeMap(), testData2);
m.putAll(m2);
printKeys(m);
m.remove(testData2[0][0]);
printKeys(m);
m.clear();
System.out.println("m.isEmpty():" + m.isEmpty());
fill(m, testData1);
// Operations on the Set change the Map:
m.keySet().removeAll(m.keySet());
System.out.println("m.isEmpty():" + m.isEmpty());
}
public static void main(String args[]) {System.out.println("Testing HashMap");
test(new HashMap());
System.out.println("Testing TreeMap");
test(new TreeMap());
}
}
复制代码
遍历 map 实例
package com.test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Test {public static void main(String[] args) {Map<String, String> map = new HashMap<String, String>();
map.put("first", "linlin");
map.put("second", "好好学 java");
map.put("third", "sihai");
map.put("first", "sihai2");
// 第一种:通过 Map.keySet 遍历 key 和 value
System.out.println("=================== 通过 Map.keySet 遍历 key 和 value:===================");
for (String key : map.keySet()) {System.out.println("key=" + key + "and value=" + map.get(key));
}
// 第二种:通过 Map.entrySet 应用 iterator 遍历 key 和 value
System.out.println("=================== 通过 Map.entrySet 应用 iterator 遍历 key 和 value:===================");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {Map.Entry<String, String> entry = it.next();
System.out.println("key=" + entry.getKey() + "and value="
+ entry.getValue());
}
// 第三种:通过 Map.entrySet 遍历 key 和 value
System.out.println("=================== 通过 Map.entrySet 遍历 key 和 value:===================");
for (Map.Entry<String, String> entry : map.entrySet()) {System.out.println("key=" + entry.getKey() + "and value="
+ entry.getValue());
}
// 第四种:通过 Map.values()遍历所有的 value,然而不能遍历键 key
System.out.println("=================== 通过 Map.values()遍历所有的 value:===================");
for (String v : map.values()) {System.out.println("value=" + v);
}
}
}
复制代码
输入后果如下:
=================== 通过 Map.keySet 遍历 key 和 value:=================== key= third and value= sihai key= first and value= sihai2 key= second and value= 好好学 java =================== 通过 Map.entrySet 应用 iterator 遍历 key 和 value:=================== key= third and value= sihai key= first and value= sihai2 key= second and value= 好好学 java =================== 通过 Map.entrySet 遍历 key 和 value:=================== key= third and value= sihai key= first and value= sihai2 key= second and value= 好好学 java =================== 通过 Map.values()遍历所有的 value:=================== value= sihai value= sihai2 value= 好好学 java
决定应用哪种汇合
ArrayList,LinkedList 以及 Vector(大抵等价于 ArrayList)都实现了 List 接口,所以无论选用哪一个,咱们的程序都会失去相似的后果。然而,ArrayList(以及 Vector)是由一个数组后推失去的;而 LinkedList 是依据惯例的双重链接列表形式实现的,因为每个独自的对象都蕴含了数据以及指向列表内前后元素的句柄。正是因为这个起因,如果想在一个列表中部进行大量插入和删除操作,那么 LinkedList 无疑是最失当的抉择(LinkedList 还有一些额定的性能,建设于 AbstractSequentialList 中)。若非如此,就愿意抉择 ArrayList,它的速度可能要快一些。作为另一个例子,Set 既可作为一个 ArraySet 实现,亦可作为 HashSet 实现。ArraySet 是由一个 ArrayList 后推失去的,设计成只反对大量元素,特地适宜要求创立和删除大量 Set 对象的场合应用。然而,一旦须要在本人的 Set 中包容大量元素,ArraySet 的性能就会大打折扣。写一个须要 Set 的程序时,应默认抉择 HashSet。而且只有在某些非凡状况下(对性能的晋升有迫切的需要),才应切换到 ArraySet。
- 决定应用何种 List
为领会各种 List 实施方案间的差别,最简便的办法就是进行一次性能测验。
public class ListPerformance {
private static final int REPS = 100;
private abstract static class Tester {
String name;
int size; // Test quantity
Tester(String name, int size) {
this.name = name;
this.size = size;
}
abstract void test(List a);
}
private static Tester[] tests = { new Tester("get", 300) {void test(List a) {for (int i = 0; i < REPS; i++) {for (int j = 0; j < a.size(); j++)
a.get(j);
}
}
}, new Tester("iteration", 300) {void test(List a) {for (int i = 0; i < REPS; i++) {Iterator it = a.iterator();
while (it.hasNext())
it.next();}
}
}, new Tester("insert", 1000) {void test(List a) {int half = a.size() / 2;
String s = "test";
ListIterator it = a.listIterator(half);
for (int i = 0; i < size * 10; i++)
it.add(s);
}
}, new Tester("remove", 5000) {void test(List a) {ListIterator it = a.listIterator(3);
while (it.hasNext()) {it.next();
it.remove();}
}
}, };
public static void test(List a) {
// A trick to print out the class name:
System.out.println("Testing" + a.getClass().getName());
for (int i = 0; i < tests.length; i++) {Collection1.fill(a, tests[i].size);
System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(a);
long t2 = System.currentTimeMillis();
System.out.println(":" + (t2 - t1));
}
}
public static void main(String[] args) {test(new ArrayList());
test(new LinkedList());
}
}
复制代码
外部类 Tester 是一个抽象类,用于为特定的测试提供一个根底类。它蕴含了一个要在测试开始时打印的字串、一个用于计算测试次数或元素数量的 size 参数、用于初始化字段的一个构建器以及一个形象办法 test()。test()做的是最理论的测试工作。各种类型的测试都集中到一个中央:tests 数组。咱们用继承于 Tester 的不同匿名外部类来初始化该数组。为增加或删除一个测试项目,只需在数组里简略地增加或移去一个外部类定义即可,其余所有工作都是主动进行的。
Type Get Iteration Insert Remove
A r r a y L i s t 110 490 3790 8730
LinkedList 1980 220 110 110
复制代码
在 ArrayList 中进行随机拜访(即 get())以及循环反复是最划得来的;但对于 LinkedList 却是一个不小的开销。但另一方面,在列表中部进行插入和删除操作对于 LinkedList 来说却比 ArrayList 划算得多。咱们最好的做法兴许是先抉择一个 ArrayList 作为本人的默认终点。当前若发现因为大量的插入和删除造成了性能的升高,再思考换成 LinkedList 不迟。
- 决定应用何种 Set
可在 ArraySet 以及 HashSet 间作出抉择,具体取决于 Set 的大小(如果须要从一个 Set 中取得一个程序列表,请用 TreeSet;)
public class SetPerformance {
private static final int REPS = 200;
private abstract static class Tester {
String name;
Tester(String name) {this.name = name;}
abstract void test(Set s, int size);
}
private static Tester[] tests = { new Tester("add") {void test(Set s, int size) {for (int i = 0; i < REPS; i++) {s.clear();
Collection1.fill(s, size);
}
}
}, new Tester("contains") {void test(Set s, int size) {for (int i = 0; i < REPS; i++)
for (int j = 0; j < size; j++)
s.contains(Integer.toString(j));
}
}, new Tester("iteration") {void test(Set s, int size) {for (int i = 0; i < REPS * 10; i++) {Iterator it = s.iterator();
while (it.hasNext())
it.next();}
}
}, };
public static void test(Set s, int size) {
// A trick to print out the class name:
System.out.println("Testing" + s.getClass().getName() + "size"
+ size);
Collection1.fill(s, size);
for (int i = 0; i < tests.length; i++) {System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(s, size);
long t2 = System.currentTimeMillis();
System.out.println(":" + ((double) (t2 - t1) / (double) size));
}
}
public static void main(String[] args) {
// Small:
test(new TreeSet(), 10);
test(new HashSet(), 10);
// Medium:
test(new TreeSet(), 100);
test(new HashSet(), 100);
// Large:
test(new HashSet(), 1000);
test(new TreeSet(), 1000);
}
}
复制代码
进行 add()以及 contains()操作时,HashSet 显然要比 ArraySet 杰出得多,而且性能显著与元素的多寡关系不大。个别编写程序的时候,简直永远用不着应用 ArraySet
3. 决定应用何种 Map
抉择不同的 Map 实施方案时,留神 Map 的大小对于性能的影响是最大的,上面这个测试程序分明地阐示了这 一点:
public class MapPerformance {
private static final int REPS = 200;
public static Map fill(Map m, int size) {for (int i = 0; i < size; i++) {String x = Integer.toString(i);
m.put(x, x);
}
return m;
}
private abstract static class Tester {
String name;
Tester(String name) {this.name = name;}
abstract void test(Map m, int size);
}
private static Tester[] tests = { new Tester("put") {void test(Map m, int size) {for (int i = 0; i < REPS; i++) {m.clear();
fill(m, size);
}
}
}, new Tester("get") {void test(Map m, int size) {for (int i = 0; i < REPS; i++)
for (int j = 0; j < size; j++)
m.get(Integer.toString(j));
}
}, new Tester("iteration") {void test(Map m, int size) {for (int i = 0; i < REPS * 10; i++) {Iterator it = m.entries().iterator();
while (it.hasNext())
it.next();}
}
}, };
public static void test(Map m, int size) {
// A trick to print out the class name:
System.out.println("Testing" + m.getClass().getName() + "size"
+ size);
fill(m, size);
for (int i = 0; i < tests.length; i++) {System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(m, size);
long t2 = System.currentTimeMillis();
System.out.println(":" + ((double) (t2 - t1) / (double) size));
}
}
public static void main(String[] args) {
// Small:
test(new Hashtable(), 10);
test(new HashMap(), 10);
test(new TreeMap(), 10);
// Medium:
test(new Hashtable(), 100);
test(new HashMap(), 100);
test(new TreeMap(), 100);
// Large:
test(new HashMap(), 1000);
test(new Hashtable(), 1000);
test(new TreeMap(), 1000);
}
}
复制代码
因为 Map 的大小是最重大的问题,所以程序的计时测试按 Map 的大小(或容量)来宰割工夫,以便失去令人 服气的测试后果。上面列出一系列后果(在你的机器上可能不同):即便大小为 10,ArrayMap 的性能也要比 HashMap 差—— 除重复循环时以外。而在应用 Map 时,重复的作用通常并不重要(get()通常是咱们工夫花得最多的中央)。TreeMap 提供了杰出的 put()以及重复工夫,但 get()的性能并不佳。然而,咱们为什么依然须要应用 TreeMap 呢?这样一来,咱们能够不把它作为 Map 应用,而作为创立程序列表的一种路径。一旦填充了一个 TreeMap,就能够调用 keySet()来取得键的一个 Set“现象”。而后用 toArray()产生蕴含了那些键的一个数组。随后,可用 static 办法 Array.binarySearch()疾速查找排好序的数组中的内容。当然,兴许只有在 HashMap 的行为不可承受的时候,才须要采纳这种做法。因为 HashMap 的设计主旨就是进行疾速的检索操作。最初,当咱们应用 Map 时,首要的抉择应该是 HashMap。只有在极少数状况下才须要思考其余办法
public class MapCreation {public static void main(String[] args) {
final long REPS = 100000;
long t1 = System.currentTimeMillis();
System.out.print("Hashtable");
for (long i = 0; i < REPS; i++)
new Hashtable();
long t2 = System.currentTimeMillis();
System.out.println(":" + (t2 - t1));
t1 = System.currentTimeMillis();
System.out.print("TreeMap");
for (long i = 0; i < REPS; i++)
new TreeMap();
t2 = System.currentTimeMillis();
System.out.println(":" + (t2 - t1));
t1 = System.currentTimeMillis();
System.out.print("HashMap");
for (long i = 0; i < REPS; i++)
new HashMap();
t2 = System.currentTimeMillis();
System.out.println(":" + (t2 - t1));
}
}
复制代码
TreeMap 的创立速度比其余两种类型显著快得多(但你应亲自尝试一下,因为据说新版本可能会改善 ArrayMap 的性能)。思考到这方面的起因,同时因为前述 TreeMap 杰出的 put()性能,所以如 果须要创立大量 Map,而且只有在当前才须要波及大量检索操作,那么最佳的策略就是:创立和填充 TreeMap;当前检索量增大的时候,再将重要的 TreeMap 转换成 HashMap—— 应用 HashMap(Map)构建器。
未反对的操作
利用 static(动态)数组 Arrays.toList(),兴许能将一个数组转换成 List
public class Unsupported {private static String[] s = { "one", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten", };
static List a = Arrays.toList(s);
static List a2 = Arrays.toList(new String[] {s[3], s[4], s[5] });
public static void main(String[] args) {Collection1.print(a); // Iteration
System.out.println("a.contains(" + s[0] + ") =" + a.contains(s[0]));
System.out.println("a.containsAll(a2) =" + a.containsAll(a2));
System.out.println("a.isEmpty() =" + a.isEmpty());
System.out.println("a.indexOf(" + s[5] + ") =" + a.indexOf(s[5]));
// Traverse backwards:
ListIterator lit = a.listIterator(a.size());
while (lit.hasPrevious())
System.out.print(lit.previous());
System.out.println();
// Set the elements to different values:
for (int i = 0; i < a.size(); i++)
a.set(i, "47");
Collection1.print(a);
// Compiles, but won't run:
lit.add("X"); // Unsupported operation
a.clear(); // Unsupported
a.add("eleven"); // Unsupported
a.addAll(a2); // Unsupported
a.retainAll(a2); // Unsupported
a.remove(s[0]); // Unsupported
a.removeAll(a2); // Unsupported
}
} 复制代码
从中能够看出,理论只实现了 Collection 和 List 接口的一部分。残余的办法导致了不受欢迎的一种状况,名为 UnsupportedOperationException。在实现那些接口的汇合类中,或者提供、或者没有提供对那些办法的反对。若调用一个未获反对的办法,就会导致一个 UnsupportedOperationException(操作未反对违例),这表明呈现了一个编程谬误。Arrays.toList()产生了一个 List(列表),该列表是由一个固定长度的数组后推出来的。因而惟一可能反对的就是那些不扭转数组长度的操作。在另一方面,若申请一个新接口表白不同品种的行为(可能叫作“FixedSizeList”—— 固定长度列表),就有遭逢更大的复杂程度的危险。这样一来,当前试图应用库的时候,很快就会发现自己不知从何处下手。对那些采纳 Collection,List,Set 或者 Map 作为参数的办法,它们的文档该当指出哪些可选的办法是必须实现的。举个例子来说,排序要求实现 set()和 Iterator.set()办法,但不包含 add()和 remove()。
排序和搜寻
数组
Arrays 类为所有根本数据类型的数组提供了一个过载的 sort()和 binarySearch()
,它们亦可用于 String 和 Object。
public class Array1 {static Random r = new Random();
static String ssource = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
static char[] src = ssource.toCharArray();
// Create a random String
public static String randString(int length) {char[] buf = new char[length];
int rnd;
for (int i = 0; i < length; i++) {rnd = Math.abs(r.nextInt()) % src.length;
buf[i] = src[rnd];
}
return new String(buf);
}
// Create a random array of Strings:
public static String[] randStrings(int length, int size) {String[] s = new String[size];
for (int i = 0; i < size; i++)
s[i] = randString(length);
return s;
}
public static void print(byte[] b) {for (int i = 0; i < b.length; i++)
System.out.print(b[i] + " ");
System.out.println();}
public static void print(String[] s) {for (int i = 0; i < s.length; i++)
System.out.print(s[i] + " ");
System.out.println();}
public static void main(String[] args) {byte[] b = new byte[15];
r.nextBytes(b); // Fill with random bytes
print(b);
Arrays.sort(b);
print(b);
int loc = Arrays.binarySearch(b, b[10]);
System.out.println("Location of" + b[10] + "=" + loc);
// Test String sort & search:
String[] s = randStrings(4, 10);
print(s);
Arrays.sort(s);
print(s);
loc = Arrays.binarySearch(s, s[4]);
System.out.println("Location of" + s[4] + "=" + loc);
}
}
复制代码
在 main()中,Random.nextBytes() 用随机抉择的字节填充数组自变量(没有对应的 Random 办法用于创立其余根本数据类型的数组)。取得一个数组后,便可发现为了执行 sort()或者 binarySearch(),只需收回一次办法调用即可。与 binarySearch()无关的还有一个重要的正告:若在执行一次 binarySearch()之前不调用 sort(),便会产生不可预测的行为,其中甚至包含有限循环。 对 String 的排序以及搜寻是类似的,但在运行程序的时候,咱们会留神到一个乏味的景象:排序恪守的是字典程序,亦即大写字母在字符集中位于小写字母的后面。因而,所有大写字母都位于列表的最后面,前面再跟上小写字母—— Z 竟然位于 a 的后面。仿佛连电话簿也是这样排序的。
- 可比拟与比拟器 若想对一个 Object 数组进行排序,那么必须解决一个问题。依据什么来断定两个 Object 的程序呢?可怜的是,最后的 Java 设计者并不认为这是一个重要的问题,否则就曾经在根类 Object 里定义它了。这样造成的一个结果便是:必须从内部进行 Object 的排序,而且新的汇合库提供了实现这一操作的规范形式(最现实的是在 Object 里定义它)。针对 Object 数组(以及 String,它当然属于 Object 的一种),可应用一个 sort(),并令其接收另一个参数:实现了 Comparator 接口(即“比拟器”接口,新汇合库的一部分)的一个对象,并用它的单个 compare()办法进行比拟。这个办法将两个筹备比拟的对象作为本人的参数应用—— 若第一个参数小于第二个,返回一个负整数;若相等,返回零;若第一个参数大于第二个,则返回正整数。基于这一规定,上述例子的 String 局部便可从新写过,令其进行真正按字母程序的排序:通过造型为 String,compare()办法会进行“暗示”性的测试,保障本人操作的只能是 String 对象—— 运期零碎会捕捉任何过错。将两个字串都强制换成小写模式后,String.compareTo()办法会产生预期的后果若用本人的 Comparator 来进行一次 sort(),那么在应用 binarySearch()时必须应用那个雷同的 Comparator。Arrays 类提供了另一个 sort()办法,它会采纳单个自变量:一个 Object 数组,但没有 Comparator。这个 sort()办法也必须用同样的形式来比拟两个 Object。通过实现 Comparable 接口,它采纳了赋予一个类的“天然比拟办法”。这个接口含有独自一个办法—— compareTo(),能别离依据它小于、等于或者大于自变量而返回正数、零或者负数,从而实现对象的比拟。
public class CompClass implements Comparable {
private int i;
public CompClass(int ii) {i = ii;}
public int compareTo(Object o) {
// Implicitly tests for correct type:258
int argi = ((CompClass) o).i;
if (i == argi)
return 0;
if (i < argi)
return -1;
return 1;
}
public static void print(Object[] a) {for (int i = 0; i < a.length; i++)
System.out.print(a[i] + " ");
System.out.println();}
public String toString() {return i + "";}
public static void main(String[] args) {CompClass[] a = new CompClass[20];
for (int i = 0; i < a.length; i++)
a[i] = new CompClass((int) (Math.random() * 100));
print(a);
Arrays.sort(a);
print(a);
int loc = Arrays.binarySearch(a, a[3]);
System.out.println("Location of" + a[3] + "=" + loc);
}
}
复制代码
- 列表 可用与数组雷同的模式排序和搜寻一个列表(List)。用于排序和搜寻列表的静态方法蕴含在类 Collections 中,但它们领有与 Arrays 中差不多的签名:sort(List)用于对一个实现了 Comparable 的对象列表进行排序;binarySearch(List,Object)用于查找列表中的某个对象;sort(List,Comparator)利用一个“比拟器”对一个列表进行排序;而 binarySearch(List,Object,Comparator)则用于查找那个列表中的一个对象
public class ListSort {public static void main(String[] args) {
final int SZ = 20;
// Using "natural comparison method":
List a = new ArrayList();
for(int i = 0; i < SZ; i++)
a.add(new CompClass((int)(Math.random() *100)));
Collection1.print(a);
Collections.sort(a);
Collection1.print(a);
Object find = a.get(SZ/2);259
int loc = Collections.binarySearch(a, find);
System.out.println("Location of" + find +
"=" + loc);
// Using a Comparator:
List b = new ArrayList();
for(int i = 0; i < SZ; i++)
b.add(Array1.randString(4));
Collection1.print(b);
AlphaComp ac = new AlphaComp();
Collections.sort(b, ac);
Collection1.print(b);
find = b.get(SZ/2);
// Must use the Comparator to search, also:
loc = Collections.binarySearch(b, find, ac);
System.out.println("Location of" + find +
"=" + loc);
}
}
复制代码
这些办法的用法与在 Arrays 中的用法是完全一致的,只是用一个列表代替了数组。TreeMap 也必须依据 Comparable 或者 Comparator 对本人的对象进行排序 Collections 类中的实用工具:enumeration(Collection) 为自变量产生原始格调的 Enumeration(枚举)max(Collection),min(Collection) 在自变量中用汇合内对象的天然比拟办法产生最大或最小元素 max(Collection,Comparator),min(Collection,Comparator) 在汇合内用比拟器产生最大或最小元素 nCopies(int n, Object o) 返回长度为 n 的一个不可变列表,它的所有句柄均指向 o subList(List,int min,int max) 返回由指定参数列表后推失去的一个新列表。可将这个列表设想成一个“窗口”,它自索引为 min 的中央开始,正好完结于 max 的后面 留神 min()和 max()都是伴随 Collection 对象工作的,而非伴随 List,所以不用放心 Collection 是否须要排序(就象新近指出的那样,在执行一次 binarySearch()—— 即二进制搜寻—— 之前,必须对一个 List 或者一个数组执行 sort())
- 使 Collection 或 Map 不可批改
通常,创立 Collection 或 Map 的一个“只读”版本显得更无利一些。Collections 类容许咱们达到这个指标,办法是将原始容器传递进入一个办法,并令其传回一个只读版本。这个办法共有四种变动模式,别离用于 Collection(如果不想把汇合当作一种更非凡的类型看待)、List、Set 以及 Map。
public class ReadOnly {public static void main(String[] args) {Collection c = new ArrayList();
Collection1.fill(c); // Insert useful data
c = Collections.unmodifiableCollection(c);
Collection1.print(c); // Reading is OK
// ! c.add("one"); // Can't change it
List a = new ArrayList();
Collection1.fill(a);
a = Collections.unmodifiableList(a);
ListIterator lit = a.listIterator();
System.out.println(lit.next()); // Reading OK
// ! lit.add("one"); // Can't change it
Set s = new HashSet();
Collection1.fill(s);
s = Collections.unmodifiableSet(s);
Collection1.print(s); // Reading OK
// ! s.add("one"); // Can't change it
Map m = new HashMap();
Map1.fill(m, Map1.testData1);
m = Collections.unmodifiableMap(m);
Map1.print(m); // Reading OK
// ! m.put("Ralph", "Howdy!");
}
} 复制代码
对于每种状况,在将其正式变为只读以前,都必须用有无效的数据填充容器。一旦载入胜利,最佳的做法就是用“不可批改”调用产生的句柄替换现有的句柄。这样做可无效防止将其变成不可批改后不慎扭转其中的内容。 在另一方面,该工具也容许咱们在一个类中将可能批改的容器放弃为 private 状态,并可从一个办法调用中返回指向那个容器的一个只读句柄。这样一来,尽管咱们可在类里批改它,但其余任何人都只能读。为特定类型调用“不可批改”的办法不会造成编译期间的查看,但一旦产生任何变动,对批改特定容器的办法的调用便会产生一个 UnsupportedOperationException 违例。
2.Collection 或 Map 的同步
在这儿,大家只需注意到 Collections 类提供了对整个容器进行主动同步的一种路径。它的语法与“不可批改”的办法是相似的:
public class Synchronization {public static void main(String[] args) {Collection c = Collections.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
}
}
复制代码
总结
(1) 数组蕴含了对象的数字化索引。它包容的是一种已知类型的对象,所以在查找一个对象时,不用对后果进行造型解决。数组能够是多维的,而且可能包容根本数据类型。然而,一旦把它创立好当前,大小便不能变动了。(2) Vector(矢量)也蕴含了对象的数字索引—— 可将数组和 Vector 设想成随机拜访汇合。当咱们退出更多的元素时,Vector 可能主动扭转本身的大小。但 Vector 只能包容对象的句柄,所以它不可蕴含根本数据类型;而且将一个对象句柄从汇合中取出来的时候,必须对后果进行造型解决。(3) Hashtable(散列表)属于 Dictionary(字典)的一种类型,是一种将对象(而不是数字)同其余对象关联到一起的形式。散列表也反对对对象的随机拜访,事实上,它的整个设计方案都在突出拜访的“高速度”。(4) Stack(堆栈)是一种“后入先出”(LIFO)的队列 对于 Hashtable,可将任何货色置入其中,并以十分快的速度检索;对于 Enumeration(枚举),可遍历一个序列,并对其中的每个元素都采取一个特定的操作。那是一种性能足够强劲的工具。但 Hashtable 没有“程序”的概念。Vector 和数组为咱们提供了一种线性程序,但若要把一个元素插入它们任何一个的中部,个别都要付出“惨重”的代价。除此以外,队列、拆散队列、优先级队列以及树都波及到元素的“排序”—— 并非仅仅将它们置入,以便当前能按线性程序查找或挪动它们。
三、各汇合类比照总结
集(Set):集里的对象不按任何特定的形式排列,按索引值来操作数据,不能有反复的元素 列表(List):序列中的对象以线性形式存储,按索引值来操作数据,能够有反复的元素 映射(Map):映射的每一项为“名称—数值”对,名称不能够反复,值能够反复,一个名称对应一个惟一的值
迭代器 Iterator
迭代器是按秩序一个一个地获取汇合中所有的对象,是拜访汇合中每个元素的规范机制。迭代器的创立:Collection 接口的 iterator()办法返回一个 Iterator Iterator it=test.iterator(); // 将 test 汇合对象转为迭代器 迭代器的罕用办法:
hasNext() // 判断迭代器中是否有下一个元素 next() // 返回迭代的下一个元素 Remove() // 将迭代器新返回的元素删除
public interface Iterator {boolean hasNext();
Object next();
void remove(); // Optional}
复制代码
在调用 remove()办法的时候, 必须调用一次 next()办法. remove()办法实际上是删除上一个返回的元素.
List 罕用办法
void add(int index, Object element):增加对象 element 到地位 index 上 boolean addAll(int index, Collection collection):在 index 地位后增加容器 collection 中所有的元素 Object get(int index):取出下标为 index 的地位的元素 int indexOf(Object element):查找对象 element 在 List 中第一次呈现的地位 int lastIndexOf(Object element):查找对象 element 在 List 中最初呈现的地位 Object remove(int index):删除 index 地位上的元素 ListIterator listIterator(int startIndex):返回一个 ListIterator 跌代器,开始地位为 startIndex List subList(int fromIndex, int toIndex):返回一个子列表 List , 元素寄存为从 fromIndex 到 toIndex 之前的一个元素
ArrayList
能够将其看作是可能主动增长容量的数组。利用 ArrayList 的 toArray()返回一个数组。Arrays.asList()返回一个列表。迭代器 (Iterator) 给咱们提供了一种通用的形式来拜访汇合中的元素。ArrayList 能够主动扩大容量 ArrayList.ensureCapacity(int minCapacity) 首先失去以后 elementData 属性的长度 oldCapacity。而后通过判断 oldCapacity 和 minCapacity 参数谁大来决定是否须要扩容, 如果 minCapacity 大于 oldCapacity,那么咱们就对以后的 List 对象进行扩容。 扩容的的策略为:取(oldCapacity * 3)/2 + 1 和 minCapacity 之间更大的那个。而后应用数组拷 贝的办法,把以前寄存的数据转移到新的数组对象中如果 minCapacity 不大于 oldCapacity 那么就不进行扩容。
LinkedList
LinkedList 是采纳双向循环链表实现的。利用 LinkedList 能够实现栈 (stack)、队列(queue)、双向队列(double-ended queue)。它具备办法 addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast() 等。
ArrayList 和 LinkedList 的比拟
1.ArrayList 是实现了基于动静数组的数据结构,LinkedList 基于链表的数据结构。2. 对于随机拜访 get 和 set,ArrayList 感觉优于 LinkedList,因为 LinkedList 要挪动指针。3. 对于新增和删除操作 add 和 remove,LinedList 比拟占优势,因为 ArrayList 要挪动数据。尽量避免同时遍历和删除汇合。因为这会扭转汇合的大小;
for(Iterator<ComType> iter = ComList.iterator(); iter.hasNext();){ComType com = iter.next();
if (!com.getName().contains("abc")){ComList.remove(com);}
}
复制代码
举荐:
for(Iterator<ComType> iter = ComList.iterator(); iter.hasNext();){ComType com = iter.next();
if (!com.getName().contains("abc")){iter.remove(com); }
复制代码
无限度的在 lst 中 add element,势必会造成 lst 占用内存过高
Map 罕用办法
罕用办法:
Object put(Object key,Object value):用来寄存一个键 - 值对 Map 中 Object remove(Object key):依据 key(键),移除键 - 值对,并将值返回 void putAll(Map mapping):将另外一个 Map 中的元素存入以后的 Map 中 void clear():清空以后 Map 中的元素 Object get(Object key):依据 key(键)获得对应的值 boolean containsKey(Object key):判断 Map 中是否存在某键(key)boolean containsValue(Object value): 判断 Map 中是否存在某值(value) public Set keySet():返回所有的键(key),并应用 Set 容器寄存 public Collection values():返回所有的值(Value),并应用 Collection 寄存 public Set entrySet():返回一个实现 Map.Entry 接口的元素 Set
HashMap
Map 次要用于存储键 (key) 值(value)对,依据键失去值,因而键不容许反复, 但允许值反复。HashMap 是一个最罕用的 Map, 它依据键的 HashCode 值存储数据, 依据键能够间接获取它的值,具备很快的访问速度。HashMap 最多只容许一条记录的键为 Null; 容许多条记录的值为 Null; HashMap 不反对线程的同步,即任一时刻能够有多个线程同时写 HashMap; 可能会导致数据的不统一。如果须要同步,能够用 Collections 的 synchronizedMap 办法使 HashMap 具备同步的能力,或者应用 ConcurrentHashMap 应用 HashMap,当一个对象被当作键值须要对 equals()和 hashCode()同时覆写
LinkedHashMap 和 HashMap,TreeMap 比照
Hashtable 与 HashMap 相似, 它继承自 Dictionary 类,不同的是: 它不容许记录的键或者值为空; 它反对线程的同步,即任一时刻只有一个线程能写 Hashtable, 因而也导致了 Hashtable 在写入时会比较慢。Hashmap 是一个最罕用的 Map, 它依据键的 HashCode 值存储数据, 依据键能够间接获取它的值,具备很快的访问速度,遍历时,获得数据的程序是齐全随机的。 LinkedHashMap 保留了记录的插入程序,在用 Iterator 遍历 LinkedHashMap 时,先失去的记录必定是先插入的. 也能够在结构时用带参数,依照利用次数排序。在遍历的时候会比 HashMap 慢,不过有种状况例外,当 HashMap 容量很大,理论数据较少时,遍历起来可能会比 LinkedHashMap 慢,因为 LinkedHashMap 的遍历速度只和理论数据无关,和容量无关,而 HashMap 的遍历速度和他的容量无关。 TreeMap 实现 SortMap 接口,可能把它保留的记录依据键排序, 默认是按键值的升序排序,也能够指定排序的比拟器,当用 Iterator 遍历 TreeMap 时,失去的记录是排过序的。 咱们用的最多的是 HashMap,HashMap 外面存入的键值对在取出的时候是随机的, 在 Map 中插入、删除和定位元素,HashMap 是最好的抉择。TreeMap 取出来的是排序后的键值对。但如果您要按 天然程序或自定义程序遍历键,那么 TreeMap 会更好。LinkedHashMap 是 HashMap 的一个子类,如果须要输入的程序和输出的雷同, 那么用 LinkedHashMap 能够实现, 它还能够按读取程序来排列,像连接池中能够利用。
Set 的应用
不容许反复元素 对 add()、equals() 和 hashCode() 办法增加了限度 HashSet 和 TreeSet 是 Set 的实现 Set—》HashSet LinkedHashSet SortedSet —》TreeSet
HashSet
public boolean contains(Object o):如果 set 蕴含指定元素,返回 true public Iterator iterator()返回 set 中元素的迭代器 public Object[] toArray():返回蕴含 set 中所有元素的数组 public Object[] toArray(Object[] a):返回蕴含 set 中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型 public boolean add(Object o):如果 set 中不存在指定元素,则向 set 退出 public boolean remove(Object o):如果 set 中存在指定元素,则从 set 中删除 public boolean removeAll(Collection c):如果 set 蕴含指定汇合,则从 set 中删除指定汇合的所有元素 public boolean containsAll(Collection c):如果 set 蕴含指定汇合的所有元素,返回 true。如果指定汇合也是一个 set,只有是以后 set 的子集时,办法返回 true
实现 Set 接口的 HashSet,依附 HashMap 来实现的。咱们应该为要寄存到散列表的各个对象定义 hashCode()和 equals()。HashSet 如何过滤反复元素 调用元素 HashCode 取得哈希码–》判断哈希码是否相等,不相等则录入—》相等则判断 equals()后是否相等,不相等在进行 hashcode 录入,相等不录入
TreeSet
TreeSet 是依附 TreeMap 来实现的。TreeSet 是一个有序汇合,TreeSet 中元素将依照升序排列,缺省是依照天然程序进行排列,意味着 TreeSet 中元素要实现 Comparable 接口,咱们能够在结构 TreeSet 对象时,传递实现了 Comparator 接口的比拟器对象。
HashSet 与 TreeSet 与 LinkedHashSet 比照
HashSet 不能保障元素的排列程序,程序有可能发生变化,不是同步的,汇合元素能够是 null, 但只能放入一个 null TreeSet 是 SortedSet 接口的惟一实现类,TreeSet 能够确保汇合元素处于排序状态。TreeSet 反对两种排序形式,天然排序 和定制排序,其中天然排序为默认的排序形式。向 TreeSet 中退出的应该是同一个类的对象。TreeSet 判断两个对象不相等的形式是两个对象通过 equals 办法返回 false,或者通过 CompareTo 办法比拟没有返回 0 天然排序 天然排序应用要排序元素的 CompareTo(Object obj)办法来比拟元素之间大小关系,而后将元素依照升序排列。 定制排序 天然排序是依据汇合元素的大小,以升序排列,如果要定制排序,应该应用 Comparator 接口,实现 int compare(To1,To2) 办法 LinkedHashSet 汇合同样是依据元素的 hashCode 值来决定元素的存储地位,然而它同时应用链表保护元素的秩序。这样使得元素看起 来像是以插入顺 序保留的,也就是说,当遍历该汇合时候,LinkedHashSet 将会以元素的增加程序拜访汇合的元素。LinkedHashSet 在迭代拜访 Set 中的全副元素时,性能比 HashSet 好,然而插入时性能略微逊色于 HashSet。