Java编程笔记16:深入容器
图源:PHP中文网
填充容器
填充容器会有Java编程笔记15:数组 - 魔芋红茶’s blog (icexmoon.cn)种提到的填充数组同样的问题。
和数组类似,标准库提供一个方法Collections.fill
用于向容器中填充元素:
package ch16.fill;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
for (int i = 0; i < 10; i++) {
nums.add(i);
}
System.out.println(nums);
Collections.fill(nums, Integer.valueOf(99));
System.out.println(nums);
}
}
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// [99, 99, 99, 99, 99, 99, 99, 99, 99, 99]
但这个方法存在局限性:
- 只能向
List
类型的容器填充数据。 - 所谓的填充是用指定元素覆盖已有元素,而非添加若干个指定元素。
同样的,这种方式填充的元素同样是同一个对象的引用:
package ch16.fill2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import util.Fmt;
class AddrStr {
private String str;
public AddrStr(String str) {
this.str = str;
}
@Override
public String toString() {
String addr = super.toString();
return Fmt.sprintf("AddrStr(%s:%s)", addr, str);
}
}
public class Main {
public static void main(String[] args) {
List<AddrStr> strings = new ArrayList<>();
String[] words = "abc".split("");
for (String w : words) {
strings.add(new AddrStr(w));
}
System.out.println(strings);
Collections.fill(strings, new AddrStr("z"));
System.out.println(strings);
}
}
// [AddrStr(ch16.fill2.AddrStr@24d46ca6:a),
// AddrStr(ch16.fill2.AddrStr@6d311334:b),
// AddrStr(ch16.fill2.AddrStr@682a0b20:c)]
// [AddrStr(ch16.fill2.AddrStr@3d075dc0:z),
// AddrStr(ch16.fill2.AddrStr@3d075dc0:z),
// AddrStr(ch16.fill2.AddrStr@3d075dc0:z)]
使用Generator
很容易地,就会想到像在Java编程笔记15:数组 - 魔芋红茶’s blog (icexmoon.cn)中做过的那样,利用Generator
接口实现将测试数据填充进容器:
package ch16.generator;
import java.util.ArrayList;
import java.util.List;
import ch15.test2.CommonGenerator;
import ch15.test2.Generator;
public class ListFiller {
public static <T> List<T> fill(List<T> list, Generator<T> gen, int num) {
list.addAll(getList(gen, num));
return list;
}
public static <T> List<T> getList(Generator<T> gen, int num) {
List<T> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(gen.next());
}
return list;
}
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
fill(nums, new CommonGenerator.IntGenerator(), 5);
System.out.println(nums);
List<Character> chars = new ArrayList<>(getList(new CommonGenerator.CharGenerator(), 5));
System.out.println(chars);
List<String> strings = new ArrayList<>();
strings.addAll(getList(new CommonGenerator.StringGenerator(), 5));
System.out.println(strings);
}
}
// [0, 1, 2, 3, 4]
// [a, b, c, d, e]
// [abcde, fghij, klmno, pqrst, uvwxy]
fill
方法可以直接用指定的Generator
对象填充List
,而geList
方法可以获取一个用来填充List
的List
,相对而言后者更灵活一些,可以用于在容器的构造器或者addAll
方法中,main
中的示例代码说明了这一点。
同样的,可以创建一个用来填充Map
的工具类,只不过稍有些不同:
package ch16.generator;
import java.util.LinkedHashMap;
import java.util.Map;
import ch15.test2.CommonGenerator;
import ch15.test2.Generator;
public class MapFiller {
public static class Entry<K, V> {
public final K key;
public final V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
public static <K, V> Map<K, V> fill(Map<K, V> map, Generator<Entry<K, V>> gen, int num) {
for (int i = 0; i < num; i++) {
Entry<K, V> entry = gen.next();
map.put(entry.key, entry.value);
}
return map;
}
public static <K, V> Map<K, V> fill(Map<K, V> map, Generator<K> keyGen, Generator<V> valueGen, int num) {
for (int i = 0; i < num; i++) {
map.put(keyGen.next(), valueGen.next());
}
return map;
}
public static <K, V> Map<K, V> getMap(Generator<Entry<K, V>> gen, int num) {
Map<K, V> map = new LinkedHashMap<>();
fill(map, gen, num);
return map;
}
public static <K, V> Map<K, V> getMap(Generator<K> kGen, Generator<V> vGen, int num) {
Map<K, V> map = new LinkedHashMap<>();
fill(map, kGen, vGen, num);
return map;
}
public static void main(String[] args) {
Map<Integer, Character> map = getMap(new CommonGenerator.IntGenerator(), new CommonGenerator.CharGenerator(),
5);
System.out.println(map);
}
}
// {0=a, 1=b, 2=c, 3=d, 4=e}
静态内部类Entry
是为了方便某些以键值对方式产生元素的Generator
使用的,可以用一个单独的例子说明:
package ch16.generator;
import java.util.Map;
import ch15.test2.CommonGenerator;
import ch15.test2.Generator;
import ch16.generator.MapFiller.Entry;
class IntCharGenerator implements Generator<MapFiller.Entry<Integer, Character>> {
Generator<Integer> kGen = new CommonGenerator.IntGenerator();
Generator<Character> vGen = new CommonGenerator.CharGenerator();
@Override
public Entry<Integer, Character> next() {
Integer key = kGen.next();
Character value = vGen.next();
Entry<Integer, Character> entry = new Entry<>(key, value);
return entry;
}
}
public class Main {
public static void main(String[] args) {
Map<Integer, Character> map = MapFiller.getMap(new IntCharGenerator(), 5);
System.out.println(map);
}
}
// {0=a, 1=b, 2=c, 3=d, 4=e}
这里的IntCharGenerator
只是简单用于举例,事实上可以通过策略模式来编写一个更通用的KeyValueGenerator<K,V>
类作为产生Entry
元素的生成器,可以分别将产生键和值的生成器作为策略进行绑定。
这里使用的
Generator
以及CommonGenerator
等组件都是在Java编程笔记15:数组 - 魔芋红茶’s blog (icexmoon.cn)中定义的,没印象的可以翻翻之前的笔记。
Abs类
标准库中的容器组件包含一些抽象类,比如AbstractMap
等,可以通过继承这些类来实现自定义容器。
当然一般来说我们不会有类似的需求,标准库已经提供大量丰富可靠的容器,但就产生测试数据这个问题来说,完全可以利用容器的抽象类,来产生一些专门用于测试数据填充和展示的容器:
package ch16.abs;
import java.util.AbstractList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ch15.test2.CommonGenerator;
import ch15.test2.Generator;
class SampleList<E> extends AbstractList<E> {
private final int size;
private Generator<E> gen;
private Map<Integer, E> items = new HashMap<>();
public SampleList(Generator<E> gen) {
this(gen, 10);
}
public SampleList(Generator<E> gen, int size) {
this.gen = gen;
if (size < 0) {
size = 0;
}
this.size = size;
}
@Override
public E get(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException();
}
if (!items.containsKey(index)) {
E item = gen.next();
items.put(index, item);
}
return items.get(index);
}
@Override
public int size() {
return size;
}
}
public class Main {
public static void main(String[] args) {
List<Integer> sl = new SampleList<>(new CommonGenerator.IntGenerator());
System.out.println(sl);
}
}
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
类似的,可以编写适用于Map
的版本:
package ch16.abs2;
import java.util.AbstractMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import ch15.test2.CommonGenerator;
import ch15.test2.Generator;
class SampleMap<K, V> extends AbstractMap<K, V> {
private int size;
private Generator<K> kGen;
private Generator<V> vGen;
private Set<Entry<K, V>> entries = new HashSet<>();
public SampleMap(Generator<K> kGen, Generator<V> vGen, int size) {
this.kGen = kGen;
this.vGen = vGen;
if (size < 0) {
size = 0;
}
this.size = size;
for (int i = 0; i < size; i++) {
entries.add(newEntry());
}
}
public SampleMap(Generator<K> kGen, Generator<V> vGen) {
this(kGen, vGen, 10);
}
@Override
public Set<Entry<K, V>> entrySet() {
return entries;
}
private Entry<K, V> newEntry() {
return new Entry<K, V>() {
@Override
public K getKey() {
return kGen.next();
}
@Override
public V getValue() {
return vGen.next();
}
@Override
public V setValue(V value) {
throw new UnsupportedOperationException();
}
};
}
}
public class Main {
public static void main(String[] args) {
Map<Integer, Character> map = new SampleMap<Integer, Character>(new CommonGenerator.IntGenerator(),
new CommonGenerator.CharGenerator());
System.out.println(map);
}
}
// {0=a, 1=b, 2=c, 3=d, 4=e, 5=f, 6=g, 7=h, 8=i, 9=j}
需要说明的是,上边的两个示例为了更通用,使用了泛型+Generator
接口的实现方式,但实际上你可以通过继承容器的抽象类编写任意实现的容器,甚至是内部不包含任何存储单元,只用游标或者别的什么来临时产生元素:
package ch16.abs3;
import java.util.AbstractList;
import java.util.List;
class SampleList2 extends AbstractList<Integer> {
private final int size;
public SampleList2(int size) {
if (size < 0) {
size = 0;
}
this.size = size;
}
public SampleList2() {
this(10);
}
@Override
public Integer get(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException();
}
return Integer.valueOf(index);
}
@Override
public int size() {
return size;
}
}
public class Main {
public static void main(String[] args) {
List<Integer> list = new SampleList2();
System.out.println(list);
}
}
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
最后介绍一个稍微复杂的例子:
package ch16.abs4;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util