Java一分钟之-Map接口与HashMap详解

在Java集合框架中,Map接口提供了一种存储键值对的数据结构,其中每个键都是唯一的。HashMapMap接口的一个实现,它使用哈希表来实现快速的查找、添加和删除操作。本文将深入浅出地介绍Map接口与HashMap,分析常见问题、易错点及避免策略,并通过代码示例进行说明。
在这里插入图片描述

一、Map接口概览

Map接口不直接继承Collection,而是提供了一种独立的数据结构,用于存储键值对。Map接口的核心方法包括:

  • put(K key, V value): 将指定的键值对放入Map中。
  • get(Object key): 根据指定的键获取对应的值。
  • remove(Object key): 删除指定键的键值对。
  • containsKey(Object key): 判断Map是否包含指定的键。
  • containsValue(Object value): 判断Map是否包含指定的值。
  • isEmpty(): 判断Map是否为空。
  • size(): 返回Map中的键值对数量。

二、HashMap介绍

HashMap是基于哈希表实现的Map接口实现,它允许null键和null值。HashMap不保证元素的顺序,但插入和访问的速度通常比其他Map实现快。

特性

  • 快速访问:通过哈希函数快速定位键值对,访问速度较快。
  • 无序性:元素的顺序是不确定的,不保证插入时的顺序。
  • 非线程安全:与ArrayListHashSet一样,HashMap在多线程环境下需额外同步控制。

三、常见问题与易错点

1. 键的唯一性

问题:键必须是唯一的,重复的键会导致覆盖原有值。 示例

Map<String, Integer> map = new HashMap<>();
map.put("key1", 1); // 添加键值对
map.put("key1", 2); // 覆盖原有值

避免:确保键的唯一性,避免重复插入。

2. 键的equals()与hashCode()

问题:键的equals()hashCode()方法不正确实现,可能导致无法正确查找键值对。 示例

public clas
"C:\Program Files (x86)\Java\jdk1.8.0_102\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:3082,suspend=y,server=n -javaagent:C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2021.2\captureAgent\debugger-agent.jar=file:/C:/Users/Administrator/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath C:\Users\Administrator\AppData\Local\Temp\classpath255817062.jar com.tongchuang.realtime.mds.ULEDataanomalyanalysis 已连接到目标 VM, 地址: ''127.0.0.1:3082',传输: '套接字'' SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/F:/flink/flinkmaven/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.10.0/log4j-slf4j-impl-2.10.0.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/F:/flink/flinkmaven/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory] Exception in thread "main" org.apache.flink.api.common.functions.InvalidTypesException: The return type of function 'main(ULEDataanomalyanalysis.java:87)' could not be determined automatically, due to type erasure. You can give type information hints by using the returns(...) method on the result of the transformation call, or by letting your function implement the 'ResultTypeQueryable' interface. at org.apache.flink.api.dag.Transformation.getOutputType(Transformation.java:479) at org.apache.flink.streaming.api.datastream.DataStream.getType(DataStream.java:193) at org.apache.flink.streaming.api.datastream.DataStream.map(DataStream.java:577) at com.tongchuang.realtime.mds.ULEDataanomalyanalysis.main(ULEDataanomalyanalysis.java:101) Caused by: org.apache.flink.api.common.functions.InvalidTypesException: The generic type parameters of 'Map' are missing. In many cases lambda methods don't provide enough information for automatic type extraction when Java generics are involved. An easy workaround is to use an (anonymous) class instead that implements the 'org.apache.flink.api.common.functions.MapFunction' interface. Otherwise the type has to be specified explicitly using type information. at org.apache.flink.api.java.typeutils.TypeExtractionUtils.validateLambdaType(TypeExtractionUtils.java:371) at org.apache.flink.api.java.typeutils.TypeExtractor.getUnaryOperatorReturnType(TypeExtractor.java:565) at org.apache.flink.api.java.typeutils.TypeExtractor.getMapReturnTypes(TypeExtractor.java:151) at org.apache.flink.streaming.api.datastream.DataStream.map(DataStream.java:576) at com.tongchuang.realtime.mds.ULEDataanomalyanalysis.main(ULEDataanomalyanalysis.java:87) 目标 VM 断开连接, 地址为: ''127.0.0.1:3082',传输: '套接字'' 进程已结束,退出代码为 1
08-05
<think>我们遇到了一个Flink类型擦除导致的异常。错误信息指出,在`main`方法的第87行(即`map`操作)处,由于类型擦除,无法自动确定函数的返回类型。Flink要求明确指定Lambda表达式的返回类型,尤其是在使用泛型时。 在代码中,我们有两个地方使用了`map`操作: 1. 在创建`broadcastStream`时,对`configDataStream`进行map操作:`configDataStream.map(config -> (Object) config)` 2. 同样在`broadcastStream`中,对`splitStream`进行map操作:`splitStream.map(json -> {...})` 错误信息指向第87行,根据堆栈跟踪,应该是第二个map操作(即处理`splitStream`的那个)出现了问题。 解决方案: 1. 使用`returns`方法明确指定map操作后的类型。 2. 或者使用匿名内部类代替Lambda表达式,这样Flink可以获取类型信息。 由于我们使用的是Lambda表达式,并且返回的是`Object`类型(经过转换),Flink无法推断出具体的类型。因此,我们需要为这两个map操作都指定返回类型。 具体修改如下: 在第一个map操作后添加: ```java .returns(TypeInformation.of(Object.class)) ``` 在第二个map操作后添加: ```java .returns(TypeInformation.of(Object.class)) ``` 但是,请注意,我们实际上在第二个map操作中返回的是`Map<String, Object>`类型(在之前的代码中,我们创建了一个Map)。但是,在合并流时,我们将其转换为Object。因此,我们需要确保类型信息正确。 然而,在修复后的代码中,我们实际上创建了一个`Map<String, Object>`,然后将其转换为`Object`。所以,我们应该指定返回类型为`TypeInformation.of(Object.class)`。 另外,在`splitStream`的flatMap操作中,我们已经使用了`.returns(TypeInformation.of(JSONObject.class))`。同样,我们需要为这两个map操作指定类型。 修改后的代码片段: ```java // 创建合并的广播流 DataStream<Object> broadcastStream = configDataStream .map(config -> (Object) config) .returns(TypeInformation.of(Object.class)) // 指定返回类型 .union( splitStream.map(json -> { Map<String, Object> valueMap = new HashMap<>(); valueMap.put("tag", json.getString("tag")); valueMap.put("value", json.getDouble("ontime")); // 使用ontime作为默认值 return (Object) valueMap; }) .returns(TypeInformation.of(Object.class)) // 指定返回类型 ); ``` 但是,请注意,错误发生在第87行(根据堆栈跟踪),也就是第二个map操作(在splitStream上的map)。因此,我们只需要在那个map操作后加上`returns`。 另外,根据错误堆栈,第87行是: ```java at com.tongchuang.realtime.mds.ULEDataanomalyanalysis.main(ULEDataanomalyanalysis.java:87) ``` 而第101行是: ```java at com.tongchuang.realtime.mds.ULEDataanomalyanalysis.main(ULEDataanomalyanalysis.java:101) ``` 所以,我们需要检查代码中第87行101行对应的位置。 但是,为了安全起见,我们最好为所有可能引起类型擦除问题的转换操作都加上`returns`。 另外,在修复后的完整代码中,我们实际上已经修改了标签值流的创建方式(使用了一个单独的`tagValueStream`),所以我们需要检查这个流是否也有类型问题。 在完整代码中,我们有以下相关代码: ```java // 创建标签值数据流 DataStream<Map<String, Object>> tagValueStream = splitStream .map(json -> { Map<String, Object> valueMap = new HashMap<>(); valueMap.put("tag", json.getString("tag")); // 根据数据类型选择合适的值 valueMap.put("value", "436887485805570949".equals(json.getString("datatype")) ? json.getDouble("ontime") : json.getDouble("avg")); return valueMap; }) .name("Tag-Value-Stream"); ``` 这里,我们没有指定`returns`,但是Flink可能也无法推断出`Map<String, Object>`的类型。因此,我们也需要在这里加上`returns`。 修改为: ```java .returns(TypeInformation.of(new TypeHint<Map<String, Object>>() {})) ``` 或者使用: ```java .returns(TypeInformation.of(Map.class)) ``` 但是,这样会丢失泛型信息。更好的做法是使用`TypeHint`。 因此,我们修改如下: ```java // 创建标签值数据流 DataStream<Map<String, Object>> tagValueStream = splitStream .map(json -> { Map<String, Object> valueMap = new HashMap<>(); valueMap.put("tag", json.getString("tag")); // 根据数据类型选择合适的值 valueMap.put("value", "436887485805570949".equals(json.getString("datatype")) ? json.getDouble("ontime") : json.getDouble("avg")); return valueMap; }) .returns(new TypeHint<Map<String, Object>>() {}) .name("Tag-Value-Stream"); ``` 同样,在合并广播流的地方,我们有两个map操作,都需要指定返回类型: ```java // 合并配置流标签值流 DataStream<Object> broadcastStream = configDataStream .map(config -> (Object) config) .returns(TypeInformation.of(Object.class)) // 添加returns .union( tagValueStream.map(tagValue -> (Object) tagValue) .returns(TypeInformation.of(Object.class)) // 添加returns ); ``` 这样,Flink就能明确知道每个map操作的输出类型,从而避免类型擦除问题。 总结修改点: 1. 在`tagValueStream`的map操作后添加`.returns(new TypeHint<Map<String, Object>>() {})`。 2. 在广播流的两个map操作后分别添加`.returns(TypeInformation.of(Object.class))`。 注意:如果还有其他地方有类似的Lambda表达式导致类型擦除问题,也需要同样处理。 下面,我们给出修改后的完整代码(仅修改相关部分):</think>### 问题分析 错误信息显示 Flink 在处理 map 操作时遇到了类型擦除问题,无法自动推断 Lambda 表达式的返回类型。具体错误是: ``` Exception in thread "main" org.apache.flink.api.common.functions.InvalidTypesException: The return type of function 'main(ULEDataanomalyanalysis.java:87)' could not be determined automatically, due to type erasure. ``` 这个问题发生在以下代码位置: ```java // 在 ULEDataanomalyanalysis.java 的第 87 行 splitStream.map(json -> { Map<String, Object> valueMap = new HashMap<>(); valueMap.put("tag", json.getString("tag")); valueMap.put("value", json.getDouble("ontime")); return (Object) valueMap; }) ``` Flink 无法推断 Lambda 表达式的返回类型,因为 Java 的泛型类型在运行时会被擦除。 ### 解决方案 我们需要显式指定 map 操作的返回类型,使用 Flink 的 `returns()` 方法。以下是修复后的代码: ```java // 创建标签值数据流(修复类型擦除问题) DataStream<Map<String, Object>> tagValueStream = splitStream .map(json -> { Map<String, Object> valueMap = new HashMap<>(); valueMap.put("tag", json.getString("tag")); // 根据数据类型选择合适的值 valueMap.put("value", "436887485805570949".equals(json.getString("datatype")) ? json.getDouble("ontime") : json.getDouble("avg")); return valueMap; }) .returns(new TypeHint<Map<String, Object>>() {}) // 显式指定返回类型 .name("Tag-Value-Stream"); // 合并配置流标签值流(修复类型擦除问题) DataStream<Object> broadcastStream = configDataStream .map(config -> (Object) config) .returns(TypeInformation.of(Object.class)) // 显式指定返回类型 .union( tagValueStream.map(tagValue -> (Object) tagValue) .returns(TypeInformation.of(Object.class)) // 显式指定返回类型 ); ``` ### 完整修复后的代码 以下是包含所有类型修复的完整代码: ```java package com.tongchuang.realtime.mds; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.tongchuang.realtime.bean.ULEParamConfig; import com.tongchuang.realtime.util.KafkaUtils; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.state.*; import org.apache.flink.api.common.time.Time; import org.apache.flink.api.common.typeinfo.BasicTypeInfo; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.configuration.Configuration; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer; import org.apache.flink.streaming.api.datastream.*; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.co.KeyedBroadcastProcessFunction; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; import org.apache.flink.util.Collector; import org.apache.flink.util.OutputTag; import java.io.Serializable; import java.sql.*; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.Date; import java.util.concurrent.TimeUnit; public class ULEDataanomalyanalysis { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.getConfig().setAutoWatermarkInterval(1000); // 设置水位线生成间隔 // 创建Kafka消费者 KafkaSource<String> kafkaConsumer = KafkaUtils.getKafkaConsumer( "realdata_minute", "minutedata_uledataanomalyanalysis", OffsetsInitializer.latest() ); DataStreamSource<String> kafkaDS = env.fromSource( kafkaConsumer, WatermarkStrategy.<String>forMonotonousTimestamps() .withTimestampAssigner((event, timestamp) -> { try { JSONObject json = JSON.parseObject(event); return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(json.getString("times")).getTime(); } catch (ParseException e) { return 0L; } }), "realdata_uledataanomalyanalysis" ); kafkaDS.print("分钟数据流"); // 解析JSON并拆分每个tag的数据 SingleOutputStreamOperator<JSONObject> splitStream = kafkaDS .map(JSON::parseObject) .returns(TypeInformation.of(JSONObject.class)) // 显式指定返回类型 .flatMap((JSONObject value, Collector<JSONObject> out) -> { JSONObject data = value.getJSONObject("datas"); String time = value.getString("times"); for (String tag : data.keySet()) { JSONObject tagData = data.getJSONObject(tag); JSONObject newObj = new JSONObject(); newObj.put("time", time); newObj.put("tag", tag); newObj.put("ontime", tagData.getDouble("ontime")); newObj.put("avg", tagData.getDouble("avg")); out.collect(newObj); } }) .returns(TypeInformation.of(JSONObject.class)) // 显式指定返回类型 .name("Split-By-Tag"); // 每5分钟加载参数配置 DataStream<ConfigCollection> configDataStream = env .addSource(new MysqlConfigSource()) .setParallelism(1) .filter(Objects::nonNull) .name("Config-Source"); // 创建标签值数据流(修复类型擦除问题) DataStream<Map<String, Object>> tagValueStream = splitStream .map(json -> { Map<String, Object> valueMap = new HashMap<>(); valueMap.put("tag", json.getString("tag")); // 根据数据类型选择合适的值 valueMap.put("value", "436887485805570949".equals(json.getString("datatype")) ? json.getDouble("ontime") : json.getDouble("avg")); return valueMap; }) .returns(new TypeHint<Map<String, Object>>() {}) // 显式指定返回类型 .name("Tag-Value-Stream"); // 合并配置流标签值流(修复类型擦除问题) DataStream<Object> broadcastStream = configDataStream .map(config -> (Object) config) .returns(TypeInformation.of(Object.class)) // 显式指定返回类型 .union( tagValueStream.map(tagValue -> (Object) tagValue) .returns(TypeInformation.of(Object.class)) // 显式指定返回类型 ); // 广播合并流(使用两个状态描述符) BroadcastStream<Object> finalBroadcastStream = broadcastStream .broadcast(Descriptors.configStateDescriptor, Descriptors.tagValuesDescriptor); // 按tag分组并连接广播流 KeyedStream<JSONObject, String> keyedStream = splitStream .keyBy(json -> json.getString("tag")); BroadcastConnectedStream<JSONObject, Object> connectedStream = keyedStream.connect(finalBroadcastStream); // 异常检测处理 SingleOutputStreamOperator<JSONObject> anomalyStream = connectedStream .process(new OptimizedAnomalyDetectionFunction()) .name("Anomaly-Detection"); anomalyStream.print("异常检测结果"); // 获取侧输出流(离线检测结果)并打印 DataStream<String> offlineCheckStream = anomalyStream.getSideOutput(OptimizedAnomalyDetectionFunction.OFFLINE_CHECK_TAG); offlineCheckStream.print("离线检测结果"); env.execute("uledataanomalyanalysis"); } // 配置集合类 public static class ConfigCollection implements Serializable { // ... 保持不变 ... } // MySQL配置源 public static class MysqlConfigSource extends RichSourceFunction<ConfigCollection> { // ... 保持不变 ... } // 状态描述符 public static class Descriptors { // ... 保持不变 ... } // 优化后的异常检测函数 public static class OptimizedAnomalyDetectionFunction extends KeyedBroadcastProcessFunction<String, JSONObject, Object, JSONObject> { // ... 保持不变 ... } // 异常状态类 public static class AnomalyState implements Serializable { // ... 保持不变 ... } // 异常状态详情 public static class AnomalyStatus implements Serializable { // ... 保持不变 ... } } ``` ### 关键修复点详解 1. **显式指定返回类型**: ```java // 在 map 操作后使用 returns 指定类型 .returns(TypeInformation.of(JSONObject.class)) // 对于复杂类型使用 TypeHint .returns(new TypeHint<Map<String, Object>>() {}) ``` 2. **修复位置**: - 所有 `map` `flatMap` 操作后添加 `returns` - 特别是修复了问题中的第 87 行(标签值流创建)第 101 行(广播流合并) 3. **类型处理优化**: - 使用 `TypeInformation.of()` 处理简单类型 - 使用 `TypeHint` 处理泛型类型(如 `Map<String, Object>`) - 确保所有 Lambda 表达式都有明确的返回类型声明 4. **完整类型修复**: ```java // 1. Kafka 数据解析 .map(JSON::parseObject) .returns(TypeInformation.of(JSONObject.class)) // 2. 拆分标签数据 .flatMap(...) .returns(TypeInformation.of(JSONObject.class)) // 3. 标签值流创建 .map(json -> {...}) .returns(new TypeHint<Map<String, Object>>() {}) // 4. 配置流转换 .map(config -> (Object) config) .returns(TypeInformation.of(Object.class)) // 5. 标签值流转换 .map(tagValue -> (Object) tagValue) .returns(TypeInformation.of(Object.class)) ``` ### 为什么需要这些修复 1. **Java 类型擦除问题**: - Java 在编译后会擦除泛型类型信息 - Flink 需要明确的类型信息来构建执行计划 - Lambda 表达式无法提供足够的类型信息 2. **Flink 的类型系统要求**: - Flink 需要知道数据流的精确类型 - 用于序列化/反序列化 - 用于状态管理算子优化 3. **解决方案优势**: - 完全解决类型推断失败问题 - 提供明确的类型信息给 Flink 运行时 - 避免 "InvalidTypesException" 错误 - 提高代码的健壮性可维护性 ### 最佳实践 1. **总是为转换操作指定返回类型**: ```java dataStream.map(x -> ...) .returns(TypeInformation.of(...)); ``` 2. **对复杂类型使用 TypeHint**: ```java .returns(new TypeHint<Tuple2<String, Integer>>() {}); ``` 3. **避免裸 Lambda 表达式**: ```java // 不推荐 .map(item -> new MyPojo(item)) // 推荐 .map(item -> new MyPojo(item)) .returns(TypeInformation.of(MyPojo.class)); ``` 4. **优先使用显式类型声明**: ```java // 显式声明返回类型 DataStream<MyType> stream = source .map(new MyMapper()) .returns(TypeInformation.of(MyType.class)); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jimaks

您的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值