【Java】Map的笔记
的意思
-
- Javaのコレクションフレームワークの1つである Map について、整理してみます。
- 各クラスやメソッドの詳細な説明はしません。
主要的课程
- java.util.HashMap被称为哈希映射或哈希表,键对象需要正确实现hashCode()和equals()方法。
java.util.LinkedHashMap基本上与HashMap相同,返回的顺序在迭代器中被保证(默认为插入顺序)。除了插入顺序外,可以使用”访问顺序”。还可以用它来创建LRU缓存。参考:LRU缓存的实现。
java.util.TreeMap是带有树结构的映射。键对象需要实现java.lang.Comparable接口或明确指定java.util.Comparator。并且结果要与equals()方法一致。迭代器返回的顺序是保证的(排序顺序)。
java.util.EnumMap适用于键只有一种枚举类型的情况。内存效率很高。迭代器返回的顺序是保证的(按序数顺序)。
java.util.Hashtable是旧的集合框架,基本上不使用。
java.util.Properties也是旧的集合框架,是一个负面遗产。由于在许多Library中使用到,所以不能被完全舍弃。
其他类有java.util.concurrent.ConcurrentHashMap、java.util.IdentityHashMap和java.util.WeakHashMap等等,还有很多其他类。
方便的实用工具
基本上集中在java.util.Collections中。
一张空白的地图
- java.util.Collections#emptyMap()
如果需要一个空的地图,最好不要浪费用new HashMap()实例化一个对象,而是使用不变的对象进行重复利用。请注意,它是不可变的。
- java.util.Map#of()
从Java 9开始,我们可以轻松生成不可更改的空映射。这个选项更简洁。
只有一个要素的地图
- java.util.Collections#singletonMap()
注意,无法更改。
- java.util.Map#of(K, V)
从Java9开始,可以轻松地生成不可更改的1个元素的映射。这个更简洁。
无法更改为地图化
java.util.Collections#unmodifiableMap()
java.util.Collections#unmodifiableSortedMap()
java.util.Map#of(K, V, K, V, …)
从Java 9开始,可以直接生成不可更改的映射。
但是,由于需要交替指定两种类型的参数,所以它不是可变参数方法。
有提供了10对初始值的方法进行重载定义。
将同期转化为地图
-
- java.util.Collections#synchronizedMap()
- java.util.Collections#synchronizedSortedMap()
也许考虑使用java.util.concurrent.ConcurrentHashMap比同步化更好,意思稍有不同。
动态担保
- java.util.Collections#checkedMap()
使用LinkedHashMap而不是HashMap
在HashMap中,无法保证在使用迭代器等方式获取全部元素时的顺序。
因此,在单元测试验证或故障排除时,可能会遇到一些困难。
使用有序的LinkedHashMap比较多时会更加方便,因为它可以明确规定获取数据的顺序。
地图的初始化
匿名类和初始化器。
在初始化Map时,有时会使用以下技巧,结合匿名类和初始化块。
// 使い方
Map<String, String> map = new HashMap<String, String>() {
{
put("one", "1st");
put("two", "2nd");
put("three", "3rd");
}
};
我认为这种方法存在一些大小各种问题。
-
- 初期化ごとに匿名クラスが作られる(影響:小)
-
- ダイヤモンドオペレータが使用できない(影響:極小)
-
- HashMap などはシリアライズ可能だが、serialVersionUID が定義されていない(影響:小)
- インスタンスメソッド内などで生成した場合、アウタークラスのインスタンスが暗黙のうちに保持される(影響:大)
在特别是持有最后外部类的实例时,可能会出现以下问题(如果在类的静态范围内生成,则可以解决问题)。
-
- メモリリーク(必要なのはMapだけなのに、アウタークラスのインスタンスもずっと保持してしまう)
-
- シリアライズ失敗(Mapをシリアライズしたいが、アウタークラスがシリアライズ不可能の場合)
- シリアライズサイズが無駄に大きくなる(アウタークラスもシリアライズ可能な場合)
假设在实例范围内生成如下的Map。
import java.util.HashMap;
import java.util.Map;
public class Test {
public void test() {
Map<String, String> map = new HashMap<String, String>() {
{
put("one", "1st");
put("two", "2nd");
put("three", "3rd");
}
};
}
}
以下是匿名类的反编译结果。我们可以看到它持有外部类(在这里是 Test 类)的实例。
import java.util.HashMap;
class Test$1 extends HashMap
{
final Test this$0;
Test$1()
{
this$0 = Test.this;
super();
put("one", "1st");
put("two", "2nd");
put("three", "3rd");
}
}
建造者模式
使用以下的生成器类别也是非常常见的方法。
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Mapビルダー.<br>
* 各メソッドはメソッドチェーンで呼び出すことを想定.<br>
*/
public class MapBuilder<K, V> {
/** 生成したMap */
private final Map<K, V> map;
/** Mapビルダーを生成 */
public MapBuilder() {
map = new LinkedHashMap<>();
}
/** Mapに値を登録 */
public MapBuilder<K, V> put(K key, V value) {
map.put(key, value);
return this;
}
/** 編集可能なMapを返す */
public Map<K, V> toMap() {
return map;
}
/** 編集不可なMapを返す */
public Map<K, V> toConst() {
return Collections.unmodifiableMap(map);
}
}
// 使い方
Map<String, String> map = new MapBuilder<String, String>()
.put("one", "1st")
.put("two", "2nd")
.put("three", "3rd")
.toConst();
优点是可以创建可编辑和不可编辑的地图。
缺点是无法使用钻石操作员。
匿名函数
如果您确实不想写两次泛型类型的话,似乎也有使用Lambda表达式的方法。
public static <K, V> Map<K, V> toMap(Consumer<Map<K, V>> initializer) {
Map<K, V> map = new LinkedHashMap<>();
initializer.accept(map);
return map;
}
// 使い方
Map<String, String> map = toMap(m -> {
m.put("one", "1st");
m.put("two", "2nd");
m.put("three", "3rd");
});
Java 8u60以后的情况下,对于Lambda和Reflection。
据说在Java8u60或更高版本中,可以通过反射获取Lambda表达式的参数名称,这样就可以实现一种有趣的初始化方式。
在Java 8u60中,关于Map的初始化等方面变得非常简便。
我只选择了与相关部分,然后执行了一下。
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/** ラムダ式の変数名を取得してマップを定義するテスト */
public class LambdaNameMap {
/** テスト実行 */
public static void main(String[] args) throws Exception {
System.out.println(System.getProperty("java.version"));
// こんな感じでマップが作れる
Map<String, String> map = toConstMap(
one -> "1st",
two -> "2nd",
three -> "3rd");
System.out.println(map);
}
/** 名前付きの値のためのインタフェース */
public interface NamedValue<T> extends Function<String, T>, Serializable {
default T value() {
return apply(name());
}
default String name() {
return getName(this);
}
}
/** 変更不能マップを生成 */
@SafeVarargs
public static <T> Map<String, T> toConstMap(NamedValue<T>... namedValues) {
Map<String, T> map = Arrays.stream(namedValues)
.collect(Collectors.toMap(namedValue -> namedValue.name(), namedValue->namedValue.value()));
return Collections.unmodifiableMap(map);
}
/** ラムダ式から変数の名前を取得 */
private static <T> String getName(Function<String, T> func) {
SerializedLambda lambdaInfo = toSerializedLambda(func);
Method method = getMethod(lambdaInfo, String.class);
Parameter parameter = method.getParameters()[0];
if (!parameter.isNamePresent()) {
throw new IllegalStateException("no name");
}
return parameter.getName();
}
/** SerializedLambda から指定のシグネチャのメソッドを取得 */
private static Method getMethod(SerializedLambda lambdaInfo, Class<?>... argClasses) {
try {
// 実装クラス名
String implClassName = lambdaInfo.getImplClass().replace('/', '.');
Class<?> implClass = Class.forName(implClassName);
// 実装メソッド名
String implMethodName = lambdaInfo.getImplMethodName();
Method implMethod = implClass.getDeclaredMethod(implMethodName, argClasses);
return implMethod;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** ラムダ式から SerializedLambda を取得 */
private static SerializedLambda toSerializedLambda(Object lambda) {
try {
Method replaceMethod = lambda.getClass().getDeclaredMethod("writeReplace");
replaceMethod.setAccessible(true);
return (SerializedLambda) replaceMethod.invoke(lambda);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在编译时,需要在javac命令后加上-parameters选项。
如果是在Eclipse中的情况下,请在偏好设置的Java/编译器中的”存储有关方法参数的信息(可通过反射使用)”选项上进行勾选,而不是使用-parameters参数。(或者在项目属性的Java编译器中找到相同的选项)
执行结果如下。
1.8.0_65
{one=1st, three=3rd, two=2nd}
这非常有趣,但也可能存在缺点。
-
- キーは変数名に使えるものだけ
-
- 現時点では動作環境が少ない(Java9などに切り替わった後なら安心して使えるかもね)
- コンパイルオプションを変更しないと使えない
一般的启动
通常情况下,我认为应该直接而不使用奇怪的技巧进行初始化。
在类静态变量的情况下,可以使用static初始化器。
private static final Map<String, String> CONST_MAP;
static {
Map<String, string> map = new LinkedHashMap<>();
map.put("one", "1st");
map.put("two", "2nd");
map.put("three", "3rd");
CONST_MAP = Collections.unmodifiableMap(map);
}
如果是实例变量,则使用初始化器或构造函数。
private final Map<String, String> map;
{
map = new LinkedHashMap<>();
map.put("one", "1st");
map.put("two", "2nd");
map.put("three", "3rd");
}
如果是本地变量,则在生成后按顺序进行。
Map<String, string> map = new LinkedHashMap<>();
map.put("one", "1st");
map.put("two", "2nd");
map.put("three", "3rd");
或者,我认为人们经常会准备一个生成地图的方法。
private static final Map<String, String> CONST_MAP = createMap();
private static Map<String, String> createMap() {
Map<String, string> map = new LinkedHashMap<>();
map.put("one", "1st");
map.put("two", "2nd");
map.put("three", "3rd");
return Collections.unmodifiableMap(map);
}
Java 9之后的初始化(不可更改列表)
如前所述,Java 9 现在可以轻松生成不可更改的列表。
然而,由于参数的类型有两种情况,因此它并不是可变参数,而是通过参数的初始值来实现重载,范围为0至10。
private static final Map<String, String> CONST_MAP = Map.of(
"one", "1st",
"two", "2nd",
"three", "3rd");
还有一种使用Map#ofEntries的生成方法。这是因为它将Map.Entry作为变长参数,所以没有数量限制。
import java.util.Map;
import static java.util.Map.entry;
// ...
private static final Map<String, String> CONST_MAP = Map.ofEntries(
entry("one", "1st"),
entry("two", "2nd"),
entry("three", "3rd"));
Hashtable 的 contains() 方法
Map接口有containsKey()和containsValue()方法。
然而,旧的Hashtable和其子类Properties还有contains()方法。从功能上讲,它与containsValue()是相同的。
注意不要把它与containsKey()混淆。