Java 智能体学习避坑指南:3 个常见误区,新手千万别踩,高效少走弯路

Java 智能体学习避坑指南:3 个常见误区,新手千万别踩,高效少走弯路
在这里插入图片描述

欢迎文末添加好友交流,共同进步!

“ 俺はモンキー・D・ルフィ。海贼王になる男だ!”

在这里插入图片描述

随着AI Agent技术的兴起,Java开发者也纷纷投身智能体开发。然而,许多新手在学习过程中容易陷入误区,导致学习效率低下甚至半途而废。本文将深入剖析3个最常见的误区,帮助你在Java智能体学习路上少走弯路。

前言

Java作为企业级应用的首选语言,在AI智能体开发领域也有其独特优势。然而,相比于Python在AI领域的统治地位,Java开发者学习智能体技术面临着更多的挑战和选择。本文将结合实际开发经验,为你揭示Java智能体学习中的常见陷阱,并提供科学的学习路径。


误区一:过度依赖框架,忽视底层原理

1.1 误区表现

很多新手在学习Java智能体时,直接上手使用LangChain4j、Spring AI等框架,却完全不理解Agent的工作原理。这就像学习开车直接上高速,连油门刹车都不认识。

1.2 问题诊断流程

否❌

是✅

Java智能体学习

是否先学底层原理?

直接使用框架

API调用熟练

遇到问题无法排查

不知道如何定制

学习陷入瓶颈

先理解核心概念

LLM调用原理

Prompt工程基础

记忆机制理解

工具调用原理

再使用框架

知其然知其所以然

灵活定制开发

高效解决问题

1.3 正确做法:从零构建理解

❌ 错误示范:直接使用框架
// 错误:直接使用LangChain4j,不知其所以然@ServicepublicclassBadAgentService{@InjectChatLanguageModel model;publicStringchat(String message){// 只会调用API,不理解背后的原理return model.generate(message);// 问题:Prompt怎么优化?失败怎么办?成本如何控制?}}
✅ 正确示范:先理解底层,再用框架
importcom.fasterxml.jackson.databind.ObjectMapper;importokhttp3.*;importjava.io.IOException;importjava.util.*;/** * LLM客户端基础实现 * 理解LLM调用的核心原理后再使用框架 */publicclassLLMClient{privatestaticfinalStringAPI_URL="https://api.openai.com/v1/chat/completions";privatefinalString apiKey;privatefinalOkHttpClient httpClient;privatefinalObjectMapper objectMapper;publicLLMClient(String apiKey){this.apiKey = apiKey;this.httpClient =newOkHttpClient();this.objectMapper =newObjectMapper();}/** * 基础聊天完成请求 * 理解参数含义:temperature、max_tokens等 */publicStringchat(String userMessage,String systemPrompt)throwsIOException{// 构建请求体 - 理解消息格式Map<String,Object> requestBody =newHashMap<>(); requestBody.put("model","gpt-3.5-turbo");// 理解角色系统:system/user/assistantList<Map<String,String>> messages =newArrayList<>(); messages.add(Map.of("role","system","content", systemPrompt)); messages.add(Map.of("role","user","content", userMessage)); requestBody.put("messages", messages);// 理解参数作用 requestBody.put("temperature",0.7);// 控制随机性 requestBody.put("max_tokens",2000);// 控制输出长度 requestBody.put("top_p",1.0);// 核采样// 发送请求 - 理解HTTP通信Request request =newRequest.Builder().url(API_URL).addHeader("Authorization","Bearer "+ apiKey).addHeader("Content-Type","application/json").post(RequestBody.create( objectMapper.writeValueAsString(requestBody),MediaType.parse("application/json"))).build();try(Response response = httpClient.newCall(request).execute()){if(!response.isSuccessful()){thrownewIOException("API调用失败: "+ response.code());}String responseBody = response.body().string();returnparseResponse(responseBody);}}/** * 流式响应 - 理解Server-Sent Events */publicvoidchatStream(String userMessage,StreamCallback callback){// 流式请求实现// 理解SSE协议和流式处理}privateStringparseResponse(String responseBody)throwsIOException{// 解析响应 - 理解返回格式Map<String,Object> response = objectMapper.readValue(responseBody,Map.class);List<Map<String,Object>> choices =(List<Map<String,Object>>) response.get("choices");Map<String,Object> message =(Map<String,Object>) choices.get(0).get("message");return(String) message.get("content");}@FunctionalInterfacepublicinterfaceStreamCallback{voidonChunk(String chunk);}}
importjava.util.*;/** * 记忆管理基础实现 * 理解Agent的记忆机制 */publicclassMemoryManager{// 对话历史privatefinalList<Map<String,String>> conversationHistory =newArrayList<>();// 长期记忆存储privatefinalMap<String,Object> longTermMemory =newHashMap<>();// 记忆重要性评估privatefinalint maxHistorySize =50;/** * 添加消息到历史 * 理解Token限制和上下文窗口管理 */publicvoidaddMessage(String role,String content){Map<String,String> message =Map.of("role", role,"content", content); conversationHistory.add(message);// 管理历史长度 - 滑动窗口策略if(conversationHistory.size()> maxHistorySize){// 保留最近的N条消息int removeCount = conversationHistory.size()- maxHistorySize;for(int i =0; i < removeCount; i++){ conversationHistory.remove(0);}}}/** * 构建上下文 - 理解提示词工程 */publicList<Map<String,String>>buildContext(String systemPrompt){List<Map<String,String>> context =newArrayList<>();// 系统提示词 context.add(Map.of("role","system","content", systemPrompt));// 添加长期记忆中的关键信息String memoryContext =buildMemoryContext();if(!memoryContext.isEmpty()){ context.add(Map.of("role","system","content","重要背景信息:"+ memoryContext));}// 对话历史 context.addAll(conversationHistory);return context;}/** * 记忆检索 - 理解向量检索原理 */publicList<String>retrieveRelevantMemory(String query,int topK){// 简化版:基于关键词匹配// 实际应该使用向量相似度检索List<String> relevant =newArrayList<>();// TODO: 实现向量检索return relevant;}privateStringbuildMemoryContext(){// 构建记忆摘要StringBuilder sb =newStringBuilder(); longTermMemory.forEach((key, value)->{ sb.append(key).append(": ").append(value).append("; ");});return sb.toString();}publicvoidsaveToLongTermMemory(String key,Object value){ longTermMemory.put(key, value);}}

1.4 学习路径对比

错误路径 ❌第1周直接学LangChain4j框架第2周调用各种API接口第3周遇到问题无法解决第4周尝试深入但理解有限第5-8周陷入瓶颈,进展缓慢正确路径 ✅第1周LLM基础概念 &API调用第2周Prompt工程原理第3周记忆机制实现第4周工具调用原理第5-6周使用框架开发第7-8周定制化开发 & 优化正确 vs 错误学习路径对比


误区二:忽视Java特性,照搬Python方案

2.1 误区表现

很多教程和示例都是Python写的,Java开发者容易直接照搬,忽略了Java的语言特性和生态差异。

2.2 常见错误对比

否❌

是✅

Python方案

是否考虑Java特性?

直接翻译

动态类型问题

异步处理不当

性能问题

❌ 失败

适配Java特性

强类型系统

响应式编程

JVM优化

✅ 成功

2.3 典型错误案例

❌ 错误1:字符串拼接JSON
// 错误:像Python一样直接拼接字符串publicclassBadJsonHandler{publicStringbuildPrompt(String name,int age){// Python风格的字符串格式化return"你好 "+ name +",你今年 "+ age +" 岁了";// 问题:没有类型安全,容易出错}publicStringparseResponse(String jsonStr){// 手动解析JSONint start = jsonStr.indexOf("\"content\": \"")+11;int end = jsonStr.indexOf("\"", start);return jsonStr.substring(start, end);// 问题:脆弱、易错、难以维护}}
✅ 正确1:使用Java类型系统
importcom.fasterxml.jackson.annotation.JsonProperty;importcom.fasterxml.jackson.core.JsonProcessingException;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.Builder;importlombok.Data;importlombok.extern.slf4j.Slf4j;/** * Java风格的类型安全实现 */@Slf4jpublicclassGoodJsonHandler{privatefinalObjectMapper objectMapper =newObjectMapper();/** * 使用强类型对象 */@Data@BuilderpublicstaticclassChatRequest{@JsonProperty("model")privateString model;@JsonProperty("messages")privateList<Message> messages;@JsonProperty("temperature")privateDouble temperature;@JsonProperty("max_tokens")privateInteger maxTokens;}@Data@BuilderpublicstaticclassMessage{@JsonProperty("role")privateString role;@JsonProperty("content")privateString content;}@DatapublicstaticclassChatResponse{@JsonProperty("id")privateString id;@JsonProperty("choices")privateList<Choice> choices;@JsonProperty("usage")privateUsage usage;@DatapublicstaticclassChoice{@JsonProperty("index")privateInteger index;@JsonProperty("message")privateMessage message;@JsonProperty("finish_reason")privateString finishReason;}@DatapublicstaticclassUsage{@JsonProperty("prompt_tokens")privateInteger promptTokens;@JsonProperty("completion_tokens")privateInteger completionTokens;@JsonProperty("total_tokens")privateInteger totalTokens;}}/** * 使用Record模式(Java 16+) */publicrecordUserInfo(String name,int age){}/** * 类型安全的Prompt构建 */publicStringbuildPrompt(UserInfo user){returnString.format("你好 %s,你今年 %d 岁了", user.name(), user.age());}/** * 类型安全的JSON序列化 */publicStringserializeRequest(ChatRequest request){try{return objectMapper.writeValueAsString(request);}catch(JsonProcessingException e){ log.error("JSON序列化失败", e);thrownewRuntimeException("请求构建失败", e);}}/** * 类型安全的JSON反序列化 */publicChatResponseparseResponse(String jsonStr){try{return objectMapper.readValue(jsonStr,ChatResponse.class);}catch(JsonProcessingException e){ log.error("JSON反序列化失败: {}", jsonStr, e);thrownewRuntimeException("响应解析失败", e);}}/** * 使用Java的Optional处理可能为空的值 */publicStringsafeExtractContent(ChatResponse response){returnOptional.ofNullable(response).map(ChatResponse::getChoices).filter(choices ->!choices.isEmpty()).map(choices -> choices.get(0)).map(Choice::getMessage).map(Message::getContent).orElse("无法获取响应内容");}}
❌ 错误2:同步阻塞调用
// 错误:像Python一样同步调用publicclassBadAsyncHandler{publicvoidhandleMultipleRequests(List<String> prompts){for(String prompt : prompts){// 同步调用,阻塞等待String response =callLLM(prompt);System.out.println(response);}// 问题:性能差,无法利用Java并发优势}privateStringcallLLM(String prompt){// 同步HTTP调用return"response";}}
✅ 正确2:使用Java响应式编程
importreactor.core.publisher.Flux;importreactor.core.publisher.Mono;importreactor.core.scheduler.Schedulers;importlombok.extern.slf4j.Slf4j;importjava.util.List;/** * Java风格的响应式异步处理 */@Slf4jpublicclassGoodAsyncHandler{privatefinalLLMClient llmClient;publicGoodAsyncHandler(LLMClient llmClient){this.llmClient = llmClient;}/** * 使用Project Reactor处理并发请求 */publicFlux<String>handleMultipleRequestsReactive(List<String> prompts){returnFlux.fromIterable(prompts).flatMap(prompt ->Mono.fromCallable(()-> llmClient.chat(prompt,"你是一个助手")).subscribeOn(Schedulers.boundedElastic()).doOnError(e -> log.error("处理失败: {}", prompt, e)).onErrorReturn("处理失败")).doOnNext(response -> log.info("收到响应"));}/** * 使用Virtual Thread(Java 21+) */publicvoidhandleMultipleRequestsVirtualThreads(List<String> prompts){try(var executor =Executors.newVirtualThreadPerTaskExecutor()){List<Future<String>> futures = prompts.stream().map(prompt -> executor.submit(()-> llmClient.chat(prompt,"你是一个助手"))).toList();for(Future<String> future : futures){try{String response = future.get(); log.info("响应: {}", response);}catch(Exception e){ log.error("获取响应失败", e);}}}}/** * 使用CompletableFuture(Java 8+) */publicCompletableFuture<List<String>>handleMultipleRequestsAsync(List<String> prompts){List<CompletableFuture<String>> futures = prompts.stream().map(prompt ->CompletableFuture.supplyAsync(()-> llmClient.chat(prompt,"你是一个助手"),Executors.newVirtualThreadPerTaskExecutor()).exceptionally(e ->{ log.error("请求失败: {}", prompt, e);return"默认响应";})).toList();returnCompletableFuture.allOf(futures.toArray(newCompletableFuture[0])).thenApply(v -> futures.stream().map(CompletableFuture::join).toList());}/** * 带限流的并发控制 */publicFlux<String>handleWithRateLimit(List<String> prompts,int ratePerSecond){returnFlux.fromIterable(prompts).delayElements(Duration.ofMillis(1000/ ratePerSecond)).flatMap(prompt ->Mono.fromCallable(()-> llmClient.chat(prompt,"助手")).timeout(Duration.ofSeconds(30)).retry(2).onErrorReturn("超时"));}}

2.4 Java vs Python Agent开发对比

特性PythonJava
类型系统动态类型,灵活但易错静态类型,安全但冗长
异步处理asyncioReactor/RxJava/Virtual Thread
生态丰富度AI库非常丰富相对较少,但企业级强
性能解释执行,较慢JVM优化,性能更好
部署简单稍复杂但更稳定
适用场景快速原型、研究生产环境、企业应用

误区三:重功能轻工程,缺乏生产思维

3.1 误区表现

很多开发者只关注Agent"能不能用",忽略了生产环境必需的稳定性、可观测性、安全性等工程问题。

3.2 生产级Agent要求

生产级Agent

核心功能

对话能力

工具调用

记忆管理

任务规划

可观测性

日志记录

指标监控

链路追踪

错误分析

稳定性

重试机制

熔断降级

超时控制

异常处理

安全性

API密钥管理

敏感信息过滤

访问控制

审计日志

性能优化

响应缓存

连接池

批处理

成本控制

3.3 生产级Agent实现

importio.micrometer.core.instrument.*;importio.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;importio.micrometer.prometheus.PrometheusConfig;importio.micrometer.prometheus.PrometheusMeterRegistry;importlombok.extern.slf4j.Slf4j;importorg.springframework.retry.annotation.Backoff;importorg.springframework.retry.annotation.Retryable;importorg.springframework.stereotype.Component;importreactor.core.publisher.Mono;importjava.time.Duration;importjava.util.concurrent.*;/** * 生产级Agent实现 * 包含监控、重试、限流、缓存等生产特性 */@Slf4j@ComponentpublicclassProductionAgent{// 监控指标privatefinalMeterRegistry meterRegistry;privatefinalCounter requestCounter;privatefinalCounter errorCounter;privatefinalTimer responseTimer;privatefinalGauge cacheHitRate;// 限流器privatefinalRateLimiter rateLimiter;// 缓存privatefinalCache<String,String> responseCache;// 断路器privatefinalCircuitBreaker circuitBreaker;privatefinalLLMClient llmClient;publicProductionAgent(LLMClient llmClient){this.llmClient = llmClient;// 初始化监控this.meterRegistry =newPrometheusMeterRegistry(PrometheusConfig.DEFAULT);this.requestCounter =Counter.builder("agent.requests.total").description("总请求数").register(meterRegistry);this.errorCounter =Counter.builder("agent.errors.total").description("错误数").register(meterRegistry);this.responseTimer =Timer.builder("agent.response.time").description("响应时间").publishPercentiles(0.5,0.95,0.99).register(meterRegistry);// 初始化限流this.rateLimiter =RateLimiter.create(10.0);// 每秒10个请求// 初始化缓存this.responseCache =Caffeine.newBuilder().maximumSize(1000).expireAfterWrite(Duration.ofMinutes(10)).recordStats().build();this.cacheHitRate =Gauge.builder("agent.cache.hit.rate", responseCache, cache ->{var stats = cache.stats();return stats.hitCount()/(double)(stats.hitCount()+ stats.missCount());}).register(meterRegistry);// 初始化断路器this.circuitBreaker =CircuitBreaker.ofDefaults("llm-service"); circuitBreaker.getEventPublisher().onStateTransition(event -> log.info("断路器状态变更: {}", event));}/** * 生产级聊天方法 * 包含完整的监控、限流、重试、缓存 */@Retryable( value ={LLMException.class}, maxAttempts =3, backoff =@Backoff(delay =1000, multiplier =2))publicMono<AgentResponse>chat(AgentRequest request){ requestCounter.increment();returnMono.fromCallable(()->{// 检查断路器if(!circuitBreaker.tryAcquirePermission()){thrownewLLMException("服务暂时不可用,请稍后重试");}// 限流检查if(!rateLimiter.tryAcquire(Duration.ofSeconds(5))){thrownewLLMException("请求过多,请稍后重试");}// 检查缓存String cacheKey =buildCacheKey(request);String cachedResponse = responseCache.getIfPresent(cacheKey);if(cachedResponse !=null){ log.debug("缓存命中: {}", cacheKey);returnAgentResponse.builder().content(cachedResponse).cached(true).build();}// 记录开始时间long startTime =System.nanoTime();Timer.Sample sample =Timer.start(meterRegistry);try{// 调用LLMString response = llmClient.chat( request.getMessage(), request.getSystemPrompt());// 成功时更新断路器 circuitBreaker.onSuccess(0,TimeUnit.NANOSECONDS);// 缓存响应if(request.isCacheable()){ responseCache.put(cacheKey, response);}// 记录指标 sample.stop(responseTimer); log.info("请求成功,耗时: {}ms",TimeUnit.NANOSECONDS.toMillis(System.nanoTime()- startTime));returnAgentResponse.builder().content(response).cached(false).tokens(estimateTokens(request.getMessage(), response)).build();}catch(Exception e){// 失败时记录断路器 circuitBreaker.onError(0,TimeUnit.NANOSECONDS, e); errorCounter.increment(); log.error("LLM调用失败", e);thrownewLLMException("LLM调用失败", e);}}).subscribeOn(Schedulers.boundedElastic());}/** * 批量处理优化 */publicFlux<AgentResponse>chatBatch(List<AgentRequest> requests){returnFlux.fromIterable(requests).flatMap(request ->chat(request).timeout(Duration.ofSeconds(30)).onErrorResume(e ->Mono.just(AgentResponse.builder().content("处理超时或失败").error(e.getMessage()).build())));}/** * 流式响应 */publicFlux<String>chatStream(AgentRequest request){ requestCounter.increment();returnFlux.create(sink ->{ llmClient.chatStream(request.getMessage(), chunk ->{ sink.next(chunk);}, sink::error, sink::complete);});}/** * 安全检查 - 过滤敏感信息 */privatevoidsanitizeInput(AgentRequest request){String message = request.getMessage();// 检测敏感信息if(containsSensitiveInfo(message)){ log.warn("检测到敏感信息,已过滤"); request.setMessage(filterSensitiveInfo(message));}// 检测注入攻击if(detectPromptInjection(message)){ log.warn("检测到提示词注入尝试");thrownewSecurityException("检测到异常输入");}}privateStringbuildCacheKey(AgentRequest request){return request.getSystemPrompt()+":"+ request.getMessage();}privatebooleancontainsSensitiveInfo(String text){// 简化的敏感信息检测return text.matches(".*\\d{15,19}.*")||// 可能是身份证 text.matches(".*\\d{11}.*");// 可能是手机号}privateStringfilterSensitiveInfo(String text){return text.replaceAll("\\d{15,19}","***").replaceAll("(\\d{3})\\d{4}(\\d{4})","$1****$2");}privatebooleandetectPromptInjection(String text){// 检测常见的提示词注入模式String[] injectionPatterns ={"忽略以上指令","ignore previous instructions","forget everything","新的指令"};String lowerText = text.toLowerCase();for(String pattern : injectionPatterns){if(lowerText.contains(pattern.toLowerCase())){returntrue;}}returnfalse;}privateintestimateTokens(String input,String output){// 简单估算:约4字符=1tokenreturn(input.length()+ output.length())/4;}/** * 获取监控指标 */publicStringgetMetrics(){return((PrometheusMeterRegistry) meterRegistry).scrape();}}
importlombok.Builder;importlombok.Data;/** * Agent请求数据结构 */@Data@BuilderpublicclassAgentRequest{privateString message;privateString systemPrompt;@Builder.Defaultprivateboolean cacheable =true;privateString userId;privateString sessionId;privateMap<String,Object> metadata;}
importlombok.Builder;importlombok.Data;/** * Agent响应数据结构 */@Data@BuilderpublicclassAgentResponse{privateString content;privateboolean cached;privateInteger tokens;privateString error;privateMap<String,Object> metadata;}

3.4 配置管理

importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.annotation.Configuration;importlombok.Data;/** * Agent配置管理 */@Data@Configuration@ConfigurationProperties(prefix ="agent")publicclassAgentConfig{/** * LLM配置 */privateLLMConfig llm =newLLMConfig();/** * 缓存配置 */privateCacheConfig cache =newCacheConfig();/** * 限流配置 */privateRateLimitConfig rateLimit =newRateLimitConfig();/** * 重试配置 */privateRetryConfig retry =newRetryConfig();@DatapublicstaticclassLLMConfig{privateString apiKey;privateString baseUrl ="https://api.openai.com/v1";privateString model ="gpt-3.5-turbo";privateDouble temperature =0.7;privateInteger maxTokens =2000;privateDuration timeout =Duration.ofSeconds(30);}@DatapublicstaticclassCacheConfig{privateInteger maxSize =1000;privateDuration expireAfterWrite =Duration.ofMinutes(10);privateBoolean enabled =true;}@DatapublicstaticclassRateLimitConfig{privateDouble permitsPerSecond =10.0;privateBoolean enabled =true;}@DatapublicstaticclassRetryConfig{privateInteger maxAttempts =3;privateLong delay =1000L;privateDouble multiplier =2.0;}}
# application.yml 配置示例agent:llm:api-key: ${LLM_API_KEY}base-url: https://api.openai.com/v1 model: gpt-3.5-turbo temperature:0.7max-tokens:2000timeout: 30s cache:max-size:1000expire-after-write: 10m enabled:truerate-limit:permits-per-second:10enabled:trueretry:max-attempts:3delay:1000multiplier:2.0# 监控配置management:endpoints:web:exposure:include: health,metrics,prometheus metrics:export:prometheus:enabled:true

总结:正确的Java智能体学习路径

4.1 学习路线图

第1阶段:基础夯实(2-3周)第1周LLM基础概念 &API调用原理Prompt工程基础第2周记忆机制实现工具调用原理第3周Java特性运用响应式编程入门第2阶段:框架学习(2-3周)第4周LangChain4j框架Spring AI框架第5周向量数据库集成RAG模式实现第6周Agent框架对比选型与定制第3阶段:工程实践(3-4周)第7周监控与可观测性错误处理与重试第8周性能优化缓存与限流第9周安全性加固测试与部署第4阶段:项目实战(4周+)第10-13周构建完整应用持续优化迭代Java智能体正确学习路径

4.2 核心要点总结

Java智能体学习

误区一

✅ 先学原理再用框架

✅ 理解LLM工作机制

✅ 掌握Prompt工程

✅ 实现基础功能

误区二

✅ 发挥Java类型优势

✅ 使用响应式编程

✅ 重视并发性能

✅ 适配Java生态

误区三

✅ 关注可观测性

✅ 实现容错机制

✅ 加强安全防护

✅ 优化成本控制

4.3 推荐学习资源

/** * 学习资源清单 */publicclassLearningResources{publicstaticclassFrameworks{// Java Agent框架String langChain4j ="https://docs.langchain4j.dev/";String springAI ="https://spring.io/projects/spring-ai";String dashscope ="https://github.com/aliyun/dashscope-java-sdk";}publicstaticclassTools{// 开发工具String idea ="IntelliJ IDEA + GitHub Copilot";String postman ="Postman - API调试";String wireshark ="Wireshark - 网络抓包";}publicstaticclassPractice{// 实践平台String openai ="OpenAI API文档";String huggingface ="Hugging Face模型库";String kaggle ="Kaggle竞赛平台";}publicstaticclassReading{// 推荐阅读String[] books ={"《Building Agents with LLMs》","《Prompt Engineering Guide》","《Reactive Programming in Java》"};}}

结语

Java智能体开发是一项融合AI技术和Java工程能力的综合性工作。避免这三大误区,按照科学的学习路径循序渐进,你一定能在Java + AI的交叉领域找到自己的位置。

记住:先理解原理,再使用工具;先关注工程,再追求功能;先稳定可靠,再性能优化。

💡 互动话题:你在学习Java智能体时遇到过哪些坑?欢迎在评论区分享!

✍️ 坚持用清晰易懂的图解+可落地的代码,让每个知识点都简单直观!💡 座右铭:“道路是曲折的,前途是光明的!”

Read more

debian 12安裝openjdk 17步骤及tomcat 10.1.26配置指定jdk的步骤记录

背景 上一篇文章里,我说需要使用tomcat 10.1.26,但是不支持jdk 1.8.0_281的版本,想来想去,重新安装jdk是最方便的做法。 原本 环境里是tomcat9,还需要用jdk 1.8,所以后续启动tomcat 10.1.26前只需要指定jdk启动应该就可以了。 理论是这样的,实践看看。 这里先记录一下debian 12里安装OpenJDK 17以及tomcat 10.1.26配置指定jdk的步骤。 1、更新源 sudoapt update 2、指定安装openjdk 17 sudoaptinstall -y openjdk-17-jdk 等待安装完毕: 至此安装完毕,根据输出信息可以知道openjdk 17的安装目录在: /usr/lib/jvm/java-17-openjdk-amd64/ 3、确认openjdk

By Ne0inhk

【JavaScript】不用flat怎么扁平化数组

你想在不使用数组的flat()方法的前提下实现数组扁平化,核心是通过递归或迭代的方式,逐层遍历数组中的元素,把嵌套的子数组 “拆解” 成一维数组。我会给你几种实用且易懂的实现方式,覆盖不同场景需求。 方法 1:递归实现(最易理解,适合新手) 递归的核心思路是:遍历数组的每一个元素,如果元素是数组,就继续递归处理这个子数组;如果不是数组,就直接加入结果数组。 // 递归实现数组扁平化(可指定扁平化深度,默认完全扁平化) function flattenArray(arr, depth = Infinity) { // 存储最终扁平化结果 const result = []; // 遍历原数组的每一个元素 arr.forEach(item => { // 判断:如果当前元素是数组,且还没达到指定扁平化深度 if (Array.isArray(item) && depth > 0) { // 递归处理子数组,深度减1

By Ne0inhk
Java初中级工程师面试指南:从理论到实战的完美回答

Java初中级工程师面试指南:从理论到实战的完美回答

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[[email protected]] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? * 专栏导航: 码农阿豪系列专栏导航 面试专栏:收集了java相关高频面试题,面试实战总结🍻🎉🖥️ Spring5系列专栏:整理了Spring5重要知识点与实战演练,有案例可直接使用🚀🔧💻 Redis专栏:Redis从零到一学习分享,经验总结,案例实战💐📝💡 全栈系列专栏:海纳百川有容乃大,可能你想要的东西里面都有🤸🌱🚀 目录 * Java初中级工程师面试指南:从理论到实战的完美回答 * 引言 * 一、Java基础 * 1. Java集合框架:ArrayList vs LinkedList * 2. 多线程:Thread

By Ne0inhk