-
前言:JavaScript是一种广泛应用于网页开发的脚本语言,它能够使网页变得更加动态和交互性。作为一种客户端脚本语言,JavaScript可以被嵌入到HTML中,并且可以被所有现代的网页浏览器支持。 JavaScript的强大之处在于它能够让开发者们通过编写一些简单的代码,就能够实现许多复杂的功能。无论是实现页面元素的动态变化,响应用户的交互操作,还是处理表单提交数据,JavaScript都能够胜任。 本文旨在介绍和学习JavaScript的基础知识。通过本文的阅读,读者将能够了解JavaScript在网页开发中的重要性和作用,掌握其基础语法和概念。不论是初学者还是有一定经验的开发者,都可以通过本文来加深对JavaScript的理解,并提升自己的开发能力。希望读者能够从中获得有益的知识,为日后的学习和实践打下坚实基础。愿读者在学习JavaScript的过程中不断成长,探索更多可能性 作者建议:学习知识在于深度理解,多动手、多动脑,总能更快地领悟。不要仅仅停留在阅读代码的层面,亲自动手敲打一遍,会带来更丰富的收获。通过实践,我们能够更深入地理解知识,掌握技能,并且在解决问题时更加得心应手。相信自己的能力,坚持不懈地实践,你将会取得更大的进步和成就。让学习成为一种习惯,让动手实践成为你提升的捷径,加油!你是最棒的! JavaScript 是什么?JavaScript 简介JavaScript(简称Js)是当前最流行,应用最广泛的客户端(网页)脚本语言,用来在网页中添加动态效果与交互功能,在web开发中拥有举足轻重的地位. JavaScript,HTML,CSS共同构成网页 HTML:用来定义网页内容,例如:标题,正文,图像等(HTML是网页制作的基础语言---->跳转学习:HTML);CSS:用来修饰网页组件得外观,例如:颜色,大小,位置,背景等等(CSS是网页制作的修饰语言--->跳转学习:CSS)JavaScript:用来实时更新网页内容,例如:从服务器中获取数据并更新到网页中,修改某些标签的样式内容,可以让网页变得生动.1.JavaScript历史JavaScript原名:LiveScript 是由美国网景(Netscape Communications Corporation)开发的一种用于对网页操作的脚本语言,LiveScript也是面向对象的.后来sun公司与网景合作更名为JavaScript. 脚本语言:无需编译,可以由某种解释器直接执行 (sql python html css JavaScript) 直接由某种解释器(引擎)解释执行,逐行从上到下解释执行. JavaScript和java完全是二种不同的语言 区别 JavaScript是嵌入在网页中,对网页进行各种操作的,是一种脚本语言java是一种后端的高级语言,是需要编译的2.JavaScript 具有以下特点1) 解释型脚本语言JavaScript 是一种解释型脚本语言,与 C、C++ 等语言需要先编译再运行不同,使用 JavaScript 编写的代码不需要编译,可以直接运行。2) 面向对象JavaScript 是一种面向对象语言,使用 JavaScript 不仅可以创建对象,也能操作使用已有的对象。3) 弱类型JavaScript 是一种弱类型的编程语言,对使用的数据类型没有严格的要求,例如您可以将一个变量初始化为任意类型,也可以随时改变这个变量的类型。4) 动态性JavaScript 是一种采用事件驱动的脚本语言,它不需要借助 Web 服务器就可以对用户的输入做出响应,例如我们在访问一个网页时,通过鼠标在网页中进行点击或滚动窗口时,通过 JavaScript 可以直接对这些事件做出响应。5) 跨平台JavaScript 不依赖操作系统,在浏览器中就可以运行。因此一个 JavaScript 脚本在编写完成后可以在任意系统上运行,只需要系统上的浏览器支持 JavaScript 即可。 第一个JavaScript程序JavaScript程序不能独立运行,只能在宿主环境中执行.一般情况下可以吧JavaScript代码放在网页中,借助浏览器环境来运行. 在HTML文档嵌入JavaScript代码 在HTML页面中嵌入JavaScript脚本需要使用<script>标签,我们可以在<script>标签中编写JavaScript代码,具体步骤如下: 新建HTML文档(一般都直接使用编译工具直接生成HTML文档--->推荐使用:HBuilderX文档)在<head>标签或者<body>标签中插入<script>标签在<script>标签中写JavaScript代码示例: <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>// JavaScript代码写的地方alert("你好 JavaScript")</script></head><body></body></html>运行本项目html 注:alert(‘welcome!’); 提示对话框 效果图: 1.在脚本文件中编写JavaScript代码JavaScript代码不仅可以放在HTML文档中也可以放在JavaScript脚本文件中.JavaScript脚本文件,扩展名是.js,使用任何文本编辑器都可以编辑(本博客以HBuilderX文档为示例) 具体步骤如下: 1.新建.js文本. 2.打开文本编写JavaScript文本 alert("你好"); 3.保存JavaScript文件,并连接HTML文档 <script type="text/javascript" src="test.js"></script>运行本项目html4.运行HTML文档 注:定义 src 属性的<script><script> 标签不应再包含 JavaScript 代码。如果嵌入了代码,则只会下载并执行外部 JavaScript 文件,嵌入代码将被忽略。 2.JavaScript代码执行顺序JavaScript代码可以写在HTML文档<head>标签与<body>标签中,写在不同标签中的执行顺序不同 示例 <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>alert("写在<head>标签中");</script></head><body><script>alert("写在<body>标签中");</script></body></html>运行本项目html 效果图 由效果图我们就可以看出,在HTML文档中先执行在<head>标签中的JavaScript代码后执行<body>标签中的代码 基本语法1.变量声明变量用var关键字 var name; 声明变量的同时对其赋值 var name = "小明"; 2.数据类型数值型(number):其中包括整型数和浮点型数.布尔型(boolean):逻辑值,true或false字符串型:有单个或者多个字符组成的.字符串使用单引号或者双引号来说明的.undefined类型:变量没用赋值的情况下默认类型,值为undefined.Object类型3.算术运算符算数运算符用来执行常见的数学运算,例如加法、减法、乘法、除法等,下表中列举了 JavaScript 中支持的算术运算符: var x = 10;var y = 4;console.log("x + y =", x + y); // 输出:x + y = 14console.log("x - y =", x - y); // 输出:x - y = 6console.log("x * y =", x * y); // 输出:x * y = 40console.log("x / y =", x / y); // 输出:x / y = 2.5console.log("x % y =", x % y); // 输出:x % y = 2 运行本项目html注:在JavaScript中的不同 var a = "10";var b = 5;var c = 10;var d = "a"; // + 字符串连接 加法运算 // alert(c-b); 减法// alert(a-b); // 5 "字符串(数字)" - 数值 = 数值 会把表达式中的数字 字符串尝试类型转换// alert(d-c); // NaN 字符串不能转换为数值返回NaN // alert(a==c); ture 只比较值是否相等// alert(a===c); false 全等 比较值和类型运行本项目html 4.赋值运算赋值运算符用来为变量赋值,下表中列举了 JavaScript 中支持的赋值运算符: 代码示例: var x = 10;x += 20;console.log(x); // 输出:30var x = 12, y = 7;x -= y;console.log(x); // 输出:5x = 5;x *= 25;console.log(x); // 输出:125x = 50;x /= 10;console.log(x); // 输出:5x = 100;x %= 15;console.log(x); // 输出:10运行本项目html 5.字符串运算符JavaScript 中的 + 和 += 运算符除了可以进行数学运算外,还可以用来拼接字符串,其中: +运算符表示将运算符左右两侧的字符串拼接到一起;+=运算符表示将字符串进行拼接,并重写赋值var x = "Hello ";var y = "World!";var z = x + y;console.log(z); // 输出:Hello World!x += y;console.log(x); // 输出:Hello World!运行本项目html6.自增,自减运算符自增、自减运算符用来对变量的值进行自增(+1)、自减(-1)操作 代码示例: var x;x = 10;console.log(++x); // 输出:11console.log(x); // 输出:11x = 10;console.log(x++); // 输出:10console.log(x); // 输出:11x = 10;console.log(--x); // 输出:9console.log(x); // 输出:9x = 10;console.log(x--); // 输出:10console.log(x); // 输出:9 运行本项目html 7.比较运算符比较运算符会比较左右两侧的数据,最后返回一个布尔值(true或者false) 代码示例 var x = 25;var y = 35;var z = "25";console.log(x == z); // 输出: trueconsole.log(x === z); // 输出: falseconsole.log(x != y); // 输出: trueconsole.log(x !== z); // 输出: trueconsole.log(x < y); // 输出: trueconsole.log(x > y); // 输出: falseconsole.log(x <= y); // 输出: trueconsole.log(x >= y); // 输出: false 运行本项目html 8.逻辑运算符逻辑运算符通常来组合多个表达式,逻辑运算符的运算是一个布尔值(true/false) 代码示例: var year = 2021;// 闰年可以被 400 整除,也可以被 4 整除,但不能被 100 整除if((year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0))){ console.log(year + " 年是闰年。");} else{ console.log(year + " 年是平年。");}运行本项目 9.条件运算符JavaScript 还包含了基于某些条件对变量进行赋值的条件运算符 语法: var result = (条件表达式)?结果1:结果2 当条件成立返回?后的内容,否则返回:后的内容 代码示例: var c = 1;var b = 2;var a = c > b ? c : b; // a = b;运行本项目10.控制语句选择结构1.单一选择结构(if) 2.二路选择结构(if/else) 3.多路选择结构(switch) 程序控制结构是循环结构1.由计数器控制的循环(for) 2.在循环的开头测试表达式(while) 3.在循环的末尾测试表达式(do/while) 4.break continue JavaScript中的选择结构和其他语言是相通的也是一样的. 如果你是第一次学习编程语言:学习推荐路径 函数1.定义函数函数定义的基本语法: function functionName([arguments]){ javascript statements; [return expression] } function: 表示函数定义的关键字; functionName:表示函数名; arguments:表示传递给函数的参数列表,各个参数之间用逗号隔开,可以为空; statements: 表示实现函数功能的函数体; return expression:表示函数将返回expression的值,同样是可选的的语句。 代码示例: sayHello---->函数名 name--->参数 alert("Hello " + name);--->函数体 return 0;---->返回值 function sayHello(name){ alert("Hello " + name); return 0;}运行本项目html2.函数调用函数调用: 1.在<script>标签中直接调用 2.在其他函数中调用 3.通过组件 代码演示: <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>function test(text){alert(text);}test("函数在<script>标签中调用");function a(){test("函数在其他函数中调用");}a();</script></head><body><input type="button" value="测试" onclick="test('组件调用函数')"/></body></html>运行本项目html 效果演示: 3.全局函数1.parseInt(arg) 把括号内的内容转换成整数之后的值。如果括号内是字符串, 则字符串开头的数字分被转换成整数,如果以字母开头,则返回 “NaN” 2.parseFloat(arg) 把括号内的字符串转换成浮点数之后的值,字符串开头的数字部分被转换成浮点数,如果以字母开头,则返回“NaN” 3.typeof (arg)返回arg值的数据类型 4.eval(arg) 可运算某个字符串 5.alert(String) 可以在浏览器中弹出一个提示框,里面的内容是String 6.console.log(String)在控制面板上打印String 事件JS 事件(event)是当用户与网页进行交互时发生的事情,例如单击某个链接或按钮,在文本框中输入文本、按下键盘上的某个按键、移动鼠标等等。当事件发生时,您可以使用 JavaScript 中的事件处理程序(也可称为事件监听器)来检测并执行某些特定的程序 常用事件: onclick()鼠标点击时 onblur()标签失去焦点 onfocus()标签获得焦点 onmouseover()鼠标被移到某标签之上 onmouseout鼠标从某标签移开 onload()是在网页加载完毕后触发相应的的事件处理程序 onchange()是指当前标签失去焦点并且标签的内容发生改变时触发事件处理程序 代码: <!DOCTYPE html><html><head><meta charset="utf-8"><title></title></head><body><div id="div" style=""><!-- 点击事件 -->onclick:<input type="button" value="onclick" onclick="test('pink')" /><br /><!-- onblur()标签失去焦点 -->onblur:<input type="text" onblur="test('red')" /><br /><!-- onfocus()标签获得焦点-->onfocus:<input type="text" onfocus="test('blue')"/><br /><!-- onmouseover()鼠标被移到某标签之上 -->onmouseover:<input type="text" onmouseover = "test('black')"/><br /><!-- onmouseout鼠标从某标签移开-->onmouseout:<input type="text" onmouseout = "test('aqua')"/><br /><!-- onchange()是指当前标签失去焦点并且标签的内容发生改变时触发事件处理程序 -->onchange:<input type="text" onchange = "test('yellow')"/></div> <script>var obj = document.getElementById("div");function test(color) {obj.style.backgroundColor = color;}</script></body></html>运行本项目html 效果显示: 内置对象1.String字符串属性 length 用法:返回该字符串的长度. 方法 charAt(n):返回该字符串位于第n位的单个字符.indexOf(char):返回指定char首次出现的位置.lastIndexOf(char) :跟 indexOf() 相似,不过是从后边开始找.substring(start,end) :返回原字符串的子字符串,该字符串是原字符串从start位 置到end位置的前一位置的一段.substr(start,length) :返回原字符串的子字符串,该字符串是原字符串从start位 置开始,长度为length的一段.split(分隔符字符) :返回一个数组,该数组是从字符串对象中分离开来的,决定了分离的地方,它本身不会包含在所返回的数组中。var string = new String("String字符串r2323");console.log(string.charAt(2)); // 打印结果是:econsole.log(string.indexOf('r'));//打印结果是:3console.log(string.lastIndexOf('r')); //打印结果是:9console.log(string.substring(2,5)); // 打印结果是:rinconsole.log(string.substr(2,3));//打印结果是:rinconsole.log(string.split('r'));//打印结果是:一个数组:'st' 'ing字符串' '2323'运行本项目html2.Array数组数组是值的有序集合,数组中的每个值称为一个元素,每个元素在数组中都有一个数字位置,称为索引,索引从 0 开始,依次递增。在 JavaScript 中,您可以使用 Array 对象定义数组,此外,Array 对象中还提供了各种有关数组的属性和方法。 创建 Array 对象的语法格式如下: var = new Array(); 这样就定义了一个空数组。以后要添加数组元素,就用: [下标] = 值; 如果想在定义数组的时候直接初始化数据,请用: var = new Array(, , ...); 还可以 var = [, , ...]; var a = new Array(); a[0] = 1; a[1] = "aaa"; a[5] = true; var b = new Array(1,2,3,4,5); var a = [1,2,3,4,5]; a[5] = 3;运行本项目html属性 length :数组的长度,即数组里有多少个元素. 方法 join() :返回一个字符串,该字符串把数组中的各个元素串起来,用置于元素与元素之间。 reverse() 使数组中的元素顺序反过来。如果对数组[1, 2, 3]使用这个方法,它将使数组变成:[3, 2, 1]. sort() :使数组中的元素按照一定的顺序排列.如果不指定,则按字母顺序排列. 对数字排序需要调用排序函数。 function sortNumber(a,b){ return a - b; } 代码演示: var arr = [1, 11, 12, 55, 66, 8, 7, 9];console.log(arr.join()); //打印结果:1,5,8,10,2,6var str = "Hello World";var reversedStr = str.split("").reverse().join("");console.log(reversedStr); // Output: "dlroW olleH"console.log(arr.sort()); // Output: [1,11,12,55,66,7,8,9] 这里是根据字符计算的arr.sort(function(a, b) {return a - b;});console.log(arr); // Output: [1, 7, 8, 9, 11, 12, 55, 66] //反序排列arr.sort(function(a, b) {return b - a;});console.log(arr); // Output: [66, 55, 12, 11, 9, 8, 7, 1]运行本项目html 3.Date获取日期 new Date() 返回当日的日期和时间 getFullYear() 返回四位数字年份 getDate() 返回一个月中的某一天 (1 ~ 31) getMonth() 返回月份 (0 ~ 11) getDay() 返回一周中的某一天 (0 ~ 6) getHours() 返回 Date 对象的小时 (0 ~ 23) getMinutes() 返回 Date 对象的分钟 (0 ~ 59) getSeconds() 返回 Date 对象的秒数 (0 ~ 59)) 代码演示:下代码是一个简单时间表的制作 <!DOCTYPE html><html><head> <title></title> <script> function addTextToDiv() { var myDiv = document.getElementById('myDiv'); var currentDate = new Date(); var year = currentDate.getFullYear(); var month = currentDate.getMonth() + 1; var day = currentDate.getDate(); var hour = currentDate.getHours(); var minutes = currentDate.getMinutes(); var seconds = currentDate.getSeconds(); myDiv.innerHTML = year + "年" + month + "月" + day + "日 " + hour + ":" + minutes + ":" + seconds; }setInterval("addTextToDiv()",1000); </script></head><body onload="addTextToDiv()"> <div id="myDiv"></div></body></html>运行本项目html 4.Math Math 对象,提供对数据的数学计算。 属性 PI 返回π(3.1415926535...)。 方法 Math.abs(x) 绝对值计算; Math.pow(x,y) 数的幂; x的y次幂 Math.sqrt(x) 计算平方根; Math.ceil(x) 对一个数进行上舍入 Math.floor(x) 对一个数进行下舍入 Math.round(x) 把一个数四舍五入为最接近的整数 Math.random() 返回 0 ~ 1 之间的随机数 Math.max(x,y) 返回 x 和 y 中的最大值 Math.min(x,y) 返回 x 和 y 中的最小值 Html DOM● DOM是Document Object Model文档对象(网页中的标签)模型的缩写. ● 通过html dom,可用javaScript操作html文档的所有标签 1.查找元素如果我们想对标签进行操作,我们就得找到他们,那我们怎么找到他们呢? 有四种方法可以找到他们: 通过 id 找到 HTML 标签 document.getElementById(“id"); 通过标签名找到 HTML 标签 document.getElementsByTagName("p"); 通过类名找到 HTML 标签 document.getElementsByClassName("p"); 通过name找到 HTML 标签 document.getElementsByName(“name"); 2.改变HTMLHtml dom允许javaScript 改变html标签的内容.改变 HTML 标签的属性 document.getElementById(“username").value=“new value"; 代码演示: 演示代码就让通过js修改按钮的value值,让按钮的value值从测试变到按钮 <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>function test(){document.getElementById("button").value = "按钮";}</script></head><body><input type="button" value="测试" id="button" onclick="test()"/></body></html>运行本项目html 代码演示:修改图片 document.getElementById("image").src=“new.jpg"; <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>function test(){document.getElementById("img").src = "img/3.jpg";}</script></head><body><input type="button" value="测试" id="button" onclick="test()"/><img src="img/1.jpg" id="img"/></body></html>运行本项目html 效果演示: 修改 HTML 内容的最简单的方法时使用 innerHTML 属性 document.getElementById(“div”).innerHTML=new HTML 代码演示: <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>function test(){document.getElementById("div").innerHTML = "加油胜利就在前方";}</script></head><body><input type="button" value="测试" id="button" onclick="test()"/><div style="font-size: 50px;" id="div">你好,登山者</div></body></html>运行本项目html 效果演示: 这里只是举例说明了这几个,我们还有更多的操作,自己动动手,去发现更多吧,加油登山者 3.改变 CSShtml dom允许 javaScript改变html标签的样式 这里演示上面的文案修改颜色 代码演示: <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>function test(){document.getElementById("div").innerHTML = "加油胜利就在前方";} function color(){document.getElementById("div").style.color = "pink";}</script></head><body><input type="button" value="测试" id="button" onclick="test()"/><div style="font-size: 50px;" id="div">你好,登山者</div><input type="button" value="修改颜色" onclick="color()"/></body></html>运行本项目html 效果演示: 这里用一个实例:使一个div的背景颜色修改并且循环 效果: 代码: <!DOCTYPE html><html><head><meta charset="utf-8"><title></title><script>var color = ['red','green','blue'];var i = 0;function test(){var obj1 = document.getElementById("text");var obj2 = document.getElementById("button");obj1.style.backgroundColor = color[i];i++;if(i==color.length){i=0;}obj2.value = color[i];}</script></head><body><div style="width: 300px; height: 300px;" id="text"></div><input type="button" value="red" onclick="test()" id="button"/></body></html>运行本项目html 计时通过使用 JavaScript,我们有能力做到在一个设定的时间间隔之后来执 行代码,而不是在函数被调用后立即执行。我们称之为计时事件。 方法: setTimeout(“函数”,”时间”)未来的某时执行代码 <!DOCTYPE html><html><head> <meta charset="utf-8"> <title>setTimeout 示例</title> <script> function showMessage() { alert("这是一个 setTimeout 示例!"); } // 调用 showMessage 函数,在 3 秒后显示消息 setTimeout("showMessage()", 3000); </script></head><body> <h1>setTimeout 示例</h1> <p>页面加载后将在 3 秒后显示一条消息。</p></body></html>运行本项目html 效果: clearTimeout()取消setTimeout() 效果: 代码演示: <!DOCTYPE html><html><head> <meta charset="utf-8"> <title>setTimeout 和 clearTimeout 示例</title> <script> var timeoutID; function showMessage() { alert("这是一个 setTimeout 示例!"); } function setupTimeout() { timeoutID = setTimeout(showMessage, 3000); } function cancelTimeout() { clearTimeout(timeoutID); alert("定时器已取消!"); } </script></head><body> <h1>setTimeout 和 clearTimeout 示例</h1> <p>页面加载后将在 3 秒后显示一条消息。</p> <button onclick="setupTimeout()">设置定时器</button> <button onclick="cancelTimeout()">取消定时器</button></body></html>运行本项目html setInterval(“函数”,”时间”)每隔指定时间重复调用 clearInterval()取消setInterval() clearInterval()------是清除了一个计时器 效果: 代码: <!DOCTYPE html><html><head><meta charset="utf-8"><title></title></head><body><input type="text" id="text"/><br /><br /><input type="button" value="开始" onclick="startTime()"/><input type="button" value="停止" onclick="stopTime()"/><input type="button" value="复位" onclick="fuwei()"/><script>var num = 0;var textobj = document.getElementById("text");textobj.value = 0;var index;function jishi(){num++;textobj.value = num;}function startTime(){index = setInterval("jishi()",1000);} function stopTime(){clearInterval(index);} function fuwei(){num = 0;textobj.value = 0;}</script></body></html>————————————————原文链接:https://blog.csdn.net/Dreamkidya/article/details/139717728
-
前言:如果你是 Java 开发者,或许曾有过这样的疑问:“为什么同样的代码,在不同环境下运行速度差异明显?”“明明用了最新的 Java 语法,换个工具却编译报错?” 这些问题的答案,往往藏在 “编译器” 这个关键环节里。作为连接 Java 源代码与可执行字节码的核心工具,编译器直接决定了代码的兼容性、运行效率和开发体验。今天,我们就来盘点 Java 生态中的主流编译器,帮你搞懂它们的特性、适用场景,从此选对工具少走弯路。目录一、Java 编译器的 “基石”:javac(Oracle JDK/OpenJDK 内置)核心特性:适用场景:小技巧:二、追求 “极致性能” 的编译器:GraalVM Native Image核心特性:适用场景:注意点:三、专注 “Android 开发” 的编译器:Jack & Jill(已淘汰)与 D8/R8D8 编译器:Android 的 “专属 javac”R8 工具:编译 + 混淆 “二合一”小提示:四、其他值得关注的 Java 编译器1. Eclipse ECJ(Eclipse Compiler for Java)2. AJC(AspectJ Compiler)五、如何选择适合自己的 Java 编译器?一、Java 编译器的 “基石”:javac(Oracle JDK/OpenJDK 内置)提到 Java 编译器,javac 绝对是绕不开的 “元老”。它是 Oracle JDK 和 OpenJDK 中默认集成的编译器,从 Java 诞生之初就伴随开发者,也是绝大多数 Java 项目的 “默认编译工具”—— 我们在命令行里敲下 javac HelloWorld.java 时,调用的就是它。核心特性:兼容性拉满:作为 Java 语言的 “官方标配”,javac 对 Java 语法标准的支持是 “标杆级” 的。从 Java 1.0 到最新的 Java 21,所有官方定义的语法特性(比如 Lambda 表达式、Record 类、虚拟线程),javac 都会第一时间稳定支持,几乎不会出现 “语法兼容问题”。轻量无依赖:不需要额外安装,只要装了 JDK/JRE,就能直接在命令行调用。无论是 Windows 的 cmd、Linux 的终端还是 macOS 的终端,输入命令就能编译代码,适合快速验证简单程序。稳定可靠:经过二十多年的迭代,javac 的稳定性已经过海量项目验证。企业级应用、开源框架(比如 Spring、MyBatis)在编译时,优先选择 javac,就是因为它 “不容易出幺蛾子”,能保证代码编译后的一致性。适用场景:绝大多数 Java 基础开发场景:比如学生作业、小型工具开发、企业后端接口开发;依赖官方语法特性的项目:比如使用 Java 21 虚拟线程、Java 17 Sealed 类的新项目;需要兼容多环境的项目:比如跨平台运行的桌面应用、分布式服务,javac 编译的字节码能在所有支持 JVM 的环境中运行。小技巧:如果想查看编译细节,可以加 -verbose 参数(比如 javac -verbose HelloWorld.java),能看到编译器加载类、生成字节码的全过程;如果想指定编译后的 Java 版本(比如用 JDK 17 编译出兼容 Java 8 的字节码),可以用 -source 和 -target 参数(javac -source 8 -target 8 HelloWorld.java)。二、追求 “极致性能” 的编译器:GraalVM Native Image如果你觉得 “Java 程序启动慢、内存占用高”,那一定要试试 GraalVM 的 Native Image—— 它不是传统意义上的 “字节码编译器”,而是能将 Java 代码直接编译成原生可执行文件(比如 Windows 的 .exe、Linux 的 ELF 文件),从根本上解决 Java 程序的 “启动痛点”。核心特性:启动速度极快:传统 Java 程序启动时,需要先启动 JVM,再加载类、初始化环境,而 Native Image 编译的原生程序,直接跳过 JVM 启动步骤,双击就能运行。比如一个简单的 Spring Boot 接口,用 javac 编译后启动要 3-5 秒,用 Native Image 编译后启动只要 0.1-0.3 秒。内存占用低:原生程序不需要 JVM 运行时环境,内存占用能减少 50% 以上。比如同样的 Java 工具,javac 编译后运行需要 200MB 内存,Native Image 版本可能只需要 80MB。跨平台但需 “针对性编译”:支持 Windows、Linux、macOS,但要注意 —— 在 Windows 上编译的原生程序,不能直接在 Linux 上运行,需要在对应平台上重新编译(这点和 C/C++ 类似)。适用场景:对启动速度敏感的场景:比如云原生应用(Docker 容器、K8s 服务)、命令行工具(CLI)、Serverless 函数(比如 AWS Lambda);资源受限的环境:比如嵌入式设备、边缘计算节点,原生程序的低内存占用更适合;追求 “Java 原生性能” 的项目:比如高性能网关、实时数据处理程序,原生程序能减少 JVM 垃圾回收(GC)的开销。注意点:Native Image 编译时会做 “静态分析”,如果代码里有反射、动态代理(比如 Spring 依赖注入),需要提前配置 “反射白名单”(通过 reflect-config.json),否则编译后的程序会报错;另外,它目前对部分 Java 特性支持有限(比如 JVM Attach API),使用前建议先查看官方兼容性文档。三、专注 “Android 开发” 的编译器:Jack & Jill(已淘汰)与 D8/R8如果你是 Android 开发者,对 “编译器” 的感知可能更强烈 ——Android 早期用的是 javac 编译 Java 代码,再用 dx 工具转换成 Dalvik 字节码,但随着 Android 生态的发展,谷歌推出了专门的编译器工具链,其中最核心的就是 D8 和 R8。先插一句:很多老开发者可能记得 “Jack & Jill” 编译器,它是谷歌早年试图替代 javac 的工具,但因为稳定性和兼容性问题,在 Android Studio 3.2 之后就被淘汰了,现在 Android 开发的主流是 D8(编译器)和 R8(混淆压缩工具)。D8 编译器:Android 的 “专属 javac”核心作用:将 Java 源代码(或 javac 编译的字节码)转换成 Android 虚拟机(ART)能识别的 DEX 格式文件(.dex)。优势:相比早期的 dx 工具,D8 编译速度更快,生成的 DEX 文件体积更小,而且对 Java 8+ 特性(比如 Lambda、Stream API)的支持更好 —— 不需要额外引入 retrolambda 这类兼容库,直接编译就能在低版本 Android 系统上运行。R8 工具:编译 + 混淆 “二合一”核心作用:在 D8 编译的基础上,增加了 “代码混淆”“无用代码删除”“资源压缩” 功能。比如项目中引用了庞大的第三方库,但只用到其中 20% 的代码,R8 会自动删除未使用的 80% 代码,让最终的 APK/APP Bundle 体积减少 30%-50%。适用场景:所有 Android 应用开发,尤其是需要上架应用商店的项目 —— 代码混淆能保护源码不被反编译,减少被破解的风险。小提示:在 Android Studio 中,D8 和 R8 是默认启用的(从 Android Gradle Plugin 3.0 开始),不需要手动配置;如果想关闭混淆(比如调试时),可以在 build.gradle 文件中设置 minifyEnabled false。四、其他值得关注的 Java 编译器除了上面三款主流工具,还有一些编译器在特定场景下很有用,适合有特殊需求的开发者:1. Eclipse ECJ(Eclipse Compiler for Java)它是 Eclipse IDE 内置的 Java 编译器,和 javac 相比,最大的优势是 “增量编译”—— 当你修改了项目中的一个文件,ECJ 只会重新编译这个文件以及依赖它的文件,而不是整个项目。对于大型 Java 项目(比如有上千个类的企业应用),ECJ 的增量编译能把编译时间从几分钟缩短到几秒,极大提升开发效率。现在很多 IDE(比如 IntelliJ IDEA、NetBeans)也支持配置 ECJ 作为编译器,如果你经常在 IDE 中频繁修改代码,试试 ECJ 会有惊喜。2. AJC(AspectJ Compiler)如果你用 AspectJ 做 “面向切面编程”(AOP),比如实现日志记录、事务管理、性能监控,就需要用到 AJC 编译器。它是 javac 的扩展,能在编译时将 AspectJ 语法(比如 @Aspect、@Before 注解)织入到 Java 代码中,生成支持 AOP 功能的字节码。AJC 支持两种编译方式:一种是直接编译 .java 和 .aj(AspectJ 源文件),另一种是对 javac 编译后的字节码做 “后织入”,灵活性很高,是 Java 企业级应用实现 AOP 的核心工具。五、如何选择适合自己的 Java 编译器?其实没有 “最好” 的编译器,只有 “最适合” 的 —— 记住这几个判断维度,就能快速做出选择:看开发场景:普通 Java 后端 / 桌面开发选 javac,Android 开发选 D8/R8,云原生 / 高性能场景选 GraalVM Native Image,Eclipse 开发选 ECJ,AOP 开发选 AJC;看核心需求:追求兼容性和稳定性选 javac,追求启动速度和低内存选 GraalVM,追求 Android 体积和混淆选 D8/R8,追求 IDE 增量编译选 ECJ;看项目依赖:如果项目用了 Spring Boot 3.x,优先试试 GraalVM(Spring 官方对 Native Image 有很好的支持);如果项目是 Android 应用,直接用 D8/R8 即可,不用纠结其他编译器。结语:ava 编译器的迭代,本质上是为了适配不同场景的需求 —— 从 javac 保障兼容性,到 GraalVM 突破性能瓶颈,再到 D8/R8 优化移动端体验,每一款编译器都在解决特定的问题。建议大家根据自己的项目需求多尝试,比如用 GraalVM 把自己写的小工具改成原生程序,感受一下 “秒启动” 的快乐;或者在 Android 项目中看看 R8 压缩后的体积变化,或许能发现新的优化思路。————————————————原文链接:https://blog.csdn.net/2503_91389547/article/details/150951709
-
引言 在Java编程中,尤其是在使用匿名内部类时,许多开发者都会遇到这样一个限制:从匿名内部类中访问的外部变量必须声明为final或是"等效final"。这个看似简单的语法规则背后,其实蕴含着Java语言设计的深层考量。本文将深入探讨这一限制的原因、实现机制以及在实际开发中的应用。 一、什么是匿名内部类? 在深入讨论之前,我们先简单回顾一下匿名内部类的概念。匿名内部类是没有显式名称的内部类,通常用于创建只使用一次的类实例。button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Button clicked!"); }});AI运行代码java二、final限制的历史与现状1、Java 8之前的严格final要求在Java 8之前,语言规范强制要求:任何被匿名内部类访问的外部方法参数或局部变量都必须明确声明为final// Java 7及之前版本public void process(String message) { final String finalMessage = message; // 必须声明为final new Thread(new Runnable() { @Override public void run() { System.out.println(finalMessage); // 访问外部变量 } }).start();}AI运行代码java2、Java 8的等效final(effectively final)Java 8引入了一个重要改进:等效final的概念如果一个变量在初始化后没有被重新赋值,即使没有明确声明为final,编译器也会将其视为final,这就是"等效final"// Java 8及之后版本public void process(String message) { // message是等效final的,因为它没有被重新赋值 new Thread(new Runnable() { @Override public void run() { System.out.println(message); // 可以直接访问 } }).start(); // 如果取消下面的注释,会导致编译错误 // message = "modified"; // 这会使message不再是等效final的}AI运行代码java三、为什么需要final或等效final限制?1、变量捕获与生命周期差异核心问题:方法参数和局部变量的生命周期与匿名内部类实例的生命周期不一致方法参数和局部变量存在于栈帧中,方法执行完毕后就会被销毁匿名内部类对象可能存在于堆中,生命周期可能远超方法执行时间解决方案:Java通过值捕获而不是引用捕获来解决这个生命周期不匹配问题public void example() { int value = 10; // 局部变量 Runnable r = new Runnable() { @Override public void run() { // 这里访问的是value的副本,不是原始变量(引用地址不一样) System.out.println(value); } }; // value变量可能在此处已经销毁,但匿名内部类仍然存在 new Thread(r).start();}AI运行代码java2、数据一致性保证(不限制出现的问题)如果允许修改捕获的变量,会导致令人困惑的行为// 假设Java允许这样做(实际上不允许)public void problematicExample() { int counter = 0; Runnable r = new Runnable() { @Override public void run() { // 如果允许访问非final变量,这里应该看到什么值? System.out.println(counter); } }; counter = 5; // 修改原始变量(实际开发,如果这里修改变量就会导致匿名内部类访问外部类编译报错) r.run(); // 输出应该是什么?0还是5?}// 通过final限制,Java确保了捕获的值在内部类中始终保持一致,避免了这种不确定性AI运行代码java而且匿名内部类可能在另一个线程中执行,而原始变量可能在原始线程中被修改。final限制避免了线程安全问题四、底层实现机制Java编译器通过以下方式实现这一特性:值拷贝:编译器将final变量的值拷贝到匿名内部类中合成字段:在匿名内部类中创建一个合成字段来存储捕获的值构造函数传递:通过构造函数将捕获的值传递给匿名内部类实例可以通过反编译匿名内部类来观察这一机制:// 源代码public class Outer { public void method(int param) { Runnable r = new Runnable() { @Override public void run() { System.out.println(param); } }; }}AI运行代码java反编译后的内部类和内部类大致如下:(参数自动添加final,内部类通过构造方法引入变量)// 反编译的外部类 public class Outer { public void method(final int var1) { Runnable var10000 = new Runnable() { public void run() { System.out.println(var1); } }; }}// 反编译后的匿名内部类class Outer$1 implements Runnable { Outer$1(Outer var1, int var2) { this.this$0 = var1; this.val$param = var2; } public void run() { System.out.println(this.val$param); }}AI运行代码java五、解决方案如果确实需要“共享可变状态”,可以使用一个单元素数组、或者一个Atomicxxx类(如 AtomicInteger),或者将变量封装到一个对象中final int[] holder = new int[]{42};Runnable r = () -> { System.out.println(holder[0]); // 可以读取 holder[0] = 100; // 可以修改数组内容,但不修改数组引用本身};AI运行代码java注意:这里你修改的是数组的内容,而不是变量 holder的引用,所以不违反规则六、常见问题与误区1、为什么实例变量没有这个限制?实例变量存储在堆中,与对象生命周期一致,因此内部类可以通过持有外部类引用直接访问它们,不需要值拷贝public class Outer { private int instanceVar = 10; // 实例变量 public void method() { new Thread(new Runnable() { @Override public void run() { instanceVar++; // 可以直接修改实例变量 } }).start(); }}AI运行代码java2、等效final的实际含义等效final意味着变量虽然没有明确声明为final,但符合final的条件:只赋值一次且不再修改public void effectivelyFinalExample() { int normalVar = 10; // 等效final final int explicitFinal = 20; // 明确声明为final // 两者都可以在匿名内部类中使用 Runnable r = () -> { System.out.println(normalVar + explicitFinal); }; // 如果这里修改变量,同样会编译报错 // normalVar = 5;}AI运行代码java————————————————原文链接:https://blog.csdn.net/qq_35512802/article/details/151362542
-
1.单例模式1.1 概述单例模式(Singleton Pattern):是一种常用的设计模式,主要用于确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点 核心作用 1.控制资源访问:常用于管理共享资源,可以避免多线程竞争或重复创建资源导致的性能问题2.全局状态管理:当某些对象需要被多个模块或组件共享时,单例模式提供统一的访问入口3.保证数据一致性:避免多个实例导致的数据不一致问题常见实现方式 饿汉模式:在类加载时就创建实例,避免了线程安全问题,但可能会造成资源浪费,尤其是当实例初始化过程复杂或占用较多资源时懒汉模式:一种延迟初始化的单例实现方式。实例在第一次被使用时才创建,而非在类加载时就创建。这种方式可以节省资源,但需要考虑线程安全问题1.2 饿汉模式public class Singleton {//类加载时进行实例化 private static final Singleton hungry = new Singleton(); //全局唯一获取实例的接口 public static Singleton getInstance(){ return hungry; } //构造方法私有化 private Singleton(){}}AI运行代码java 特点: 类加载时进行实例化不存在运行时实例化过程,所以不存在线程安全问题缺点: 即使后面的场景中没有使用到该实例,也会将该实例创建出来,可能会造成不必要的资源浪费1.3 懒汉模式class Singleton{//volatile:禁止指令重排序 private static volatile Singleton lazy = null; //创建锁对象 private static final Object object = new Object(); //全局唯一获取实例的接口 public static Singleton getInstance(){ //外层if判断:优化,提高性能 if (lazy == null) { //避免多线程时实例化多个对象 synchronized (object) { if (lazy == null) { lazy = new Singleton(); } } } return lazy; } //构造方法私有化 private Singleton(){}}AI运行代码java 实现细节: 1.通过synchronized加锁解决线程安全问题2.外层if判断,减少锁的开销3.volatile防止指令重排序(避免半初始化对象)特点: 只有在真正需要时才创建实例,减少系统启动时的资源占用,资源利用率高缺点: 若未正确使用同步机制(如synchronized或volatile),可能导致多线程环境下实例被多次创建,线程安全实现复杂1.4 懒汉模式半初始化Java对象初始化流程 1.内存分配阶段:当使用new关键字创建对象时,JVM会在堆内存中为该对象分配空间2.对象初始化阶段:对象内存分配完成后,开始执行初始化3.引用赋值阶段:内存分配完成后,JVM将分配的内存地址赋值给引用变量Java对象初始化流程(指令重排序后) 1.内存分配阶段:当使用new关键字创建对象时,JVM会在堆内存中为该对象分配空间2.引用赋值阶段:内存分配完成后,JVM将分配的内存地址赋值给引用变量3.对象初始化阶段:对象内存分配完成后,开始执行初始化 1.5 懒汉/饿汉优缺点对比特性 懒汉模式 饿汉模式实例化时机 第一次使用时 类加载时资源消耗 节省资源 可能浪费资源线程安全 需要额外同步机制 天然线程安全实现复杂度 较复杂 简单适用场景 实例化开销大,延迟加载 实例化开销小,不需要延迟加载2.生产者/消费者模式2.1 概述生产者/消费者模式(Producer/consumer model):用于协调多个线程或进程之间的任务分配与数据处理。生产者负责生成数据或任务,消费者负责处理这些数据或任务,二者通过共享的缓冲区(队列)进行解耦,避免直接依赖 核心作用 1.解耦生产与消费逻辑:生产者仅负责生成数据并放入缓冲区,消费者仅从缓冲区获取数据并处理。两者无需直接交互,降低代码复杂度,提高模块化程度2.平衡处理速率差异:生产者与消费者通常以不同速度运行。缓冲区作为中间层,允许生产者持续写入数据,消费者按自身能力消费,避免互相阻塞3.削峰填谷:通过缓冲队列平滑流量波动,避免系统因瞬时高负载崩溃。当生产者突然产生大量请求时,缓冲区暂时存储这些请求,消费者按照自身处理能力逐步消费;当生产者速度降低时,缓冲区逐步释放积压的请求,保持消费者稳定工作2.2 实现阻塞队列class MyBlockingQueue{ private int head = 0; private int tail = 0; private int useSize = 0; private final String[] array; public MyBlockingQueue(int capacity){ array = new String[capacity]; } //添加 public synchronized void put(String string) throws InterruptedException { if (isFull()){ //队列满了,等待消费者消耗元素 this.wait(); } array[tail] = string; tail++; tail = (tail + 1) % array.length; useSize++; this.notify(); } //删除 public String take() throws InterruptedException { String ret; synchronized (this) { if (useSize <= 0) { //队列空了,等待生产者添加元素. this.wait(); } ret = array[head]; head++; head = (head + 1) % array.length; useSize--; this.notify(); } return ret; } //判断是否满了 public boolean isFull(){ return useSize >= array.length; }}AI运行代码java 2.3 实现生产者/消费者模式public class Producer_Consumer_Blog { public static void main(String[] args) { MyBlockingQueue queue = new MyBlockingQueue(1000); Thread thread1 = new Thread(()->{ int n = 1; while (true){ try { queue.put(n + ""); System.out.println("生产元素n = " + n); n++; } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread thread2 = new Thread(()->{ while (true){ try { System.out.println("消费元素n = " + queue.take()); Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); thread1.start(); thread2.start(); }}AI运行代码java 3.定时器3.1 概述定时器(Timer):用于在特定时间间隔或指定时间点执行任务的编程模式,广泛应用于定时任务调度、延迟操作、周期性任务等场景。核心思想是将任务的执行逻辑与时间控制解耦,通过统一的定时器管理多个任务 核心作用/特点 1.管理异步任务调度:Timer允许你安排一个任务在未来的某个时间点执行,或者以固定的间隔重复执行2.后台执行:Timer可以使用一个后台线程来执行任务,这意味着调度和执行任务不会阻塞主线程(主线程结束后后台线程跟着结束)3.简单易用:Timer提供了一个相对简单的方式来处理定时任务,适合用于不需要复杂调度的场景标准库Timer构造方法 //1.默认构造方法//创建一个Timer对象,是一个后台线程,并使用线程的默认名字public Timer() { this("Timer-" + serialNumber());}//2.指定线程名字的构造方法//创建一个Timer对象,是一个后台线程,并使用指定的线程名字public Timer(String name) { thread.setName(name); thread.start();}//3.指定是否为后台线程的构造方法//传入true,是后台线程;传入false,是前台线程public Timer(boolean isDaemon) { this("Timer-" + serialNumber(), isDaemon);}//4.指定线程名字和是否为后台线程的构造方法public Timer(String name, boolean isDaemon) { thread.setName(name); thread.setDaemon(isDaemon); thread.start();}AI运行代码java 标准库Timer的schedule方法 1.schedule(TimerTask task, Date time):安排任务在指定的时间执行一次public static void main(String[] args) { Timer timer = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("延迟三秒执行"); } }; //使用Date对象来指定具体的执行时间 //new Date(System.currentTimeMillis()+1000表示当前时间等待1000ms timer.schedule(timerTask,new Date(System.currentTimeMillis()+1000));}AI运行代码java 2.schedule(TimerTask task, Date firstTime, long period):安排任务在指定的时间首次执行,然后每隔一段时间重复执行public static void main(String[] args) { Timer timer = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("延迟三秒执行"); } }; //当前时间等待1000ms后第一次执行任务 //此后每间隔1000ms就执行一次任务 timer.schedule(timerTask,new Date(System.currentTimeMillis()+1000),1000);}AI运行代码java 3.schedule(TimerTask task, long delay):安排任务在指定的延迟时间后执行一次(相对于当前时间)public static void main(String[] args) { Timer timer = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("延迟三秒执行"); } }; //当前时间延迟3000ms后执行 timer.schedule(timerTask,3000);}AI运行代码java 4.schedule(TimerTask task, long delay, long period):安排任务在指定的延迟时间后首次执行,然后每隔一段时间重复执行public static void main(String[] args) { Timer timer = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("延迟三秒执行"); } }; //当前时间延迟3000ms后执行 //此后每间隔3000ms就执行一次任务 timer.schedule(timerTask,3000,3000);}AI运行代码java 3.2模拟实现定时器class MyTask implements Comparable<MyTask>{ private final Runnable runnable; private final long time; public MyTask(Runnable runnable,long delay){ this.runnable = runnable; this.time = System.currentTimeMillis() + delay; } public long getTime(){ return this.time; } public void run(){ runnable.run(); } @Override public int compareTo(MyTask o) { return (int)(this.time - o.time); }}class MyTime{ private final PriorityQueue<MyTask> queue = new PriorityQueue<>(); public void schedule(Runnable runnable,long delay){ synchronized (this) { MyTask myTask = new MyTask(runnable, delay); queue.offer(myTask); this.notify(); } } public MyTime(){ Thread thread = new Thread(() -> { while (true) { try { synchronized (this) { while (queue.isEmpty()) { this.wait(); } MyTask myTask = queue.peek(); long curTime = System.currentTimeMillis(); if (curTime >= myTask.getTime()) { myTask.run(); queue.poll(); } else { this.wait(myTask.getTime() - curTime); } } }catch (InterruptedException e){ throw new RuntimeException(e); } } }); thread.setDaemon(true); thread.start(); }}AI运行代码java 4.线程池4.1 概述线程池:线程池是一种管理和复用线程的编程模式。它预先创建一定数量的线程,在执行任务需要时,将任务分配给这些线程,从而提高运行效率 核心作用:优化多线程任务的执行效率与管理资源 特点 线程复用:当线程执行完一个任务时,不会立即销毁,而是等待下一个任务的到来(当然这种等待是有时间限制的),这样避免了频繁的创建和销毁线程动态调整:根据实际环境需要动态调整线程数量,以达到最佳性能任务队列:线程池会维护一个任务队列,用于存放待执行的任务,当线程空闲时,从队列中取出任务并执行标准库线程池构造方法 1.int corePoolSize:核心线程数2.int maximumPoolSize:最大线程数3.long keepAliveTime:非核心线程的空闲时的最大存活时间4.TimeUnit unit:时间单位5.BlockingQueue< Runnable > workQueue:任务队列6.ThreadFactory threadFactory:线程工厂,用于创建新线程的工厂7.RejectedExecutionHandler handler:拒绝策略4.3线程池的执行流程假设现在有一个线程池:核心线程数2,最大线程数4,等待队列2 任务数量<=2(A,B)时,由核心线程执行任务2<任务数量<=4(A,B,C,D)时,核心线程无法同时处理所有任务,未被执行的任务(C,D)将会进入等待队列中等待核心线程执行4<任务数量<=6(A,B,C,D,E,F),此时等待队列也满了,线程池就会就会开放非核心线程来执行任务,C和D任务继续在等待队列中等待,新添加的E和F任务由非核心线程来执行任务数量>6,核心线程,等待队列,非核心线程都被任务所占用,仍然无法满足需求,此时就会触发线程池的拒绝策略4.4 拒绝策略 1.AbortPolicy:直接抛异常2.CallerRunsPolicy:由提交该任务的线程来执行3.DiscardPolicy:丢弃新任务4.DiscardOldestPolicy:丢弃最老的任务4.5 模拟实现线程池public class MyThreadPoolExecutor { private final int capacity = 1000; //阻塞队列 private final MyBlockingQueue queue = new MyBlockingQueue(capacity); private final List<Thread> list = new ArrayList<>(); //创建线程 public MyThreadPoolExecutor(int n){ for (int i = 0; i < n; i++) { Thread thread = new Thread(()->{ while (true) { try { queue.take().run(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); thread.start(); list.add(thread); } } //添加任务 public void submit(Runnable runnable) throws InterruptedException { queue.put(runnable); } public int getCapacity(){ return capacity; } //获取线程 public List<Thread> getList() { return list; }}AI运行代码java ————————————————原文链接:https://blog.csdn.net/2401_89167985/article/details/150641639
-
语言即工具,选对方向比埋头苦学更重要你好,编程世界的新朋友!当你第一次踏入代码的宇宙,面对形形色色的编程语言,是否感到眼花缭乱?今天我们就来聊聊最主流的三种编程语言——C语言、Java 和 Python——它们各自是谁,适合做什么,以及未来十年谁能带你走得更远。一、编程世界的三把钥匙:角色定位如果把编程比作建造房屋,那么:C语言是钢筋骨架:诞生于1972年,它直接与计算机硬件“对话”,负责构建最基础的支撑结构。Java是精装套房:1995年问世,以“一次编写,到处运行”闻名,擅长打造稳定、可复用的功能模块。Python是智能管家:1991年出生却在近十年大放异彩,像一位高效助手,用最少的指令完成复杂任务13。二、核心差异对比:从底层到应用1. 语言类型与设计哲学C语言:属于面向过程的编译型语言。代码在执行前需全部翻译成机器指令,运行效率极高,但需要开发者手动管理内存(类似自己打扫房间)15。Java:面向对象的半编译语言。代码先转为字节码,再通过Java虚拟机(JVM)运行。牺牲少许效率换来跨平台能力——Windows、Linux、Mac 都能执行同一份代码39。Python:多范式的解释型语言。代码边翻译边执行,开发便捷但速度较慢。支持面向对象、函数式编程,语法如英语般直白78。翻译2. 语法与学习曲线# Python 打印10次"Hello" for i in range(10): print("Hello") // Java 实现相同功能public class Main { public static void main(String[] args) { for(int i=0; i<10; i++){ System.out.println("Hello"); } }} /* C语言版本 */#include <stdio.h>int main() { for(int i=0; i<10; i++){ printf("Hello\n"); } return 0;}运行本项目Python 接近自然语言,新手1天就能写出实用脚本5Java 需理解类、对象等概念,1-2个月可入门9C语言 需掌握指针、内存分配,门槛最高13. 性能特点语言 执行速度 内存管理 典型场景C语言 ⚡⚡⚡⚡⚡ 手动管理 实时系统、高频交易Java ⚡⚡⚡⚡ 自动回收 企业后台服务Python ⚡⚡ 自动回收 数据分析、原型开发C语言直接操作硬件,速度可比Python快50倍以上;Java居中;Python虽慢但可通过C扩展提速210。4. 应用领域C语言:操作系统(Linux内核)、嵌入式设备(空调芯片)、游戏引擎(Unity底层)27Java: - 安卓APP(微信、支付宝) - 银行交易系统(高可靠性必须) - 大型网站后端(淘宝、京东)28Python: - 人工智能(ChatGPT的基石语言) - 数据分析(处理百万行Excel只需几行代码) - 自动化脚本(批量处理文件/网页)185. 生态系统支持Python:拥有28万个第三方库,如NumPy(科学计算)、TensorFlow(AI)2Java:Spring框架统治企业开发,Android SDK构建移动应用2C语言:标准库较小,但Linux/Windows API均以其为核心7三、未来十年:谁主沉浮?1. AI战场:Python 正面临 Java 的挑战Python目前占据90%的AI项目,但2025年可能成为转折点。Java凭借企业级性能正加速渗透: - Spring AI项目获阿里等巨头支持 - 直接调用GPU提升计算效率(Project Babylon) - 大厂倾向将AI集成到现有Java系统中46Python 仍靠易用性守住数据科学家阵地,但需解决性能瓶颈10。2. 新兴领域卡位战边缘计算(IoT设备):C语言因极致效率成为传感器、工控设备首选10云原生服务:Java和Go语言(非本文主角)主导容器化微服务8Web3与区块链:Java的强安全性被蚂蚁链等采用23. 就业市场真相Java:国内70%企业系统基于Java,岗位需求最稳定68Python:AI工程师平均薪资比Java高18%,但竞争加剧8C语言:嵌入式开发缺口大,入行门槛高但职业生涯长9四、给新手的终极建议学习路径规划:零基础入门:选 Python → 快速建立成就感,两周做出小工具求职导向:学 Java → 进入金融/电信等行业的核心系统硬件/高薪偏好:攻 C语言 → 深耕芯片、自动驾驶等高端领域关键决策原则:graph LRA[你的目标] --> B{选择语言}B -->|做AI/数据分析| C(Python)B -->|开发企业软件/安卓APP| D(Java)B -->|写操作系统/驱动/引擎| E(C语言)运行本项目专家提醒:2025年之后,掌握“双语言能力”更吃香:Python + C:用Python开发AI原型,C语言加速核心模块Java + Python:Java构建系统,Python集成智能组件五、技术架构深度拆解1. C语言:系统级开发的基石内存操作直接通过malloc()/free()管理内存,程序员可精确控制每一字节:int *arr = (int*)malloc(10 * sizeof(int)); // 申请40字节内存free(arr); // 必须手动释放,否则内存泄漏运行本项目指针的威力与风险指针直接访问物理地址,可实现高效数据传递:void swap(int *a, int *b) { // 通过指针交换变量 int temp = *a; *a = *b; *b = temp;}运行本项目典型事故:缓冲区溢出(如strcpy未检查长度导致系统崩溃)应用场景扩展领域 代表项目 关键技术点操作系统 Linux内核 进程调度、文件系统实现嵌入式系统 无人机飞控 实时响应(<1ms延迟)高频交易 证券交易所系统 微秒级订单处理图形渲染 OpenGL底层 GPU指令优化2. Java:企业级生态的王者JVM虚拟机机制Java源码 → 字节码 → JIT编译 → 机器码跨平台原理:同一份.class文件可在Windows/Linux/Mac的JVM上运行垃圾回收(GC)奥秘分代收集策略:graph LRA[新对象] --> B[年轻代-Eden区]B -->|Minor GC| C[Survivor区]C -->|年龄阈值| D[老年代]D -->|Full GC| E[回收]运行本项目调优关键:-Xmx设置堆大小,G1GC减少停顿时间企业级框架矩阵框架 作用 代表应用Spring Boot 快速构建微服务 阿里双11后台Hibernate 对象-数据库映射 银行客户管理系统Apache Kafka 高吞吐量消息队列 美团订单分发系统Netty 高性能网络通信 微信消息推送3. Python:科学计算的终极武器动态类型双刃剑graph TD A[数据获取] --> B(Pandas处理) B --> C{建模选择} C --> D[机器学习-scikit-learn] C --> E[深度学习-TensorFlow/PyTorch] D --> F[模型部署-Flask] E --> F F --> G[Web服务]运行本项目六、行业应用全景图1. C语言:硬科技核心载体航天控制火星探测器着陆程序:实时计算轨道参数(C代码执行速度比Python快400倍)火箭燃料控制系统:直接操作传感器寄存器汽车电子特斯拉Autopilot底层:毫米波雷达信号处理发动机ECU(电子控制单元):微控制器(MCU)仅支持C工业自动化PLC编程:三菱FX系列用C编写逻辑控制数控机床:实时位置控制精度达0.001mm2. Java:商业系统支柱金融科技支付清算:Visa每秒处理6.5万笔交易(Java+Oracle)风控系统:实时反欺诈检测(Apache Flink流计算)电信领域5G核心网:爱立信Cloud RAN基于Java微服务计费系统:中国移动月账单生成(处理PB级数据)电子商务淘宝商品搜索:Elasticsearch集群(Java开发)京东库存管理:Spring Cloud微服务架构3. Python:数据智能引擎生物医药基因序列分析:Biopython处理FASTA文件药物分子模拟:RDKit库计算3D结构金融分析量化交易:pandas清洗行情数据,TA-Lib技术指标计算风险建模:Monte Carlo模拟预测股价波动AIGC革命Stable Diffusion:PyTorch实现文生图大模型训练:Hugging Face Transformers库七、性能优化实战对比1. 计算圆周率(1亿次迭代)// C语言版:0.8秒#include <stdio.h>int main() { double pi = 0; for (int k = 0; k < 100000000; k++) { pi += (k % 2 ? -1.0 : 1.0) / (2*k + 1); } printf("%f", pi * 4);}运行本项目// Java版:1.2秒public class Pi { public static void main(String[] args) { double pi = 0; for (int k = 0; k < 100000000; k++) { pi += (k % 2 == 0 ? 1.0 : -1.0) / (2*k + 1); } System.out.println(pi * 4); }}运行本项目# Python版:12.7秒 → 用Numpy优化后:1.5秒import numpy as npk = np.arange(100000000)pi = np.sum((-1)**k / (2*k + 1)) * 4print(pi)运行本项目2. 内存消耗对比(处理1GB数据)语言 峰值内存 关键影响因素C 1.1GB 手动分配精确控制Java 2.3GB JVM堆内存开销Python 5.8GB 对象模型额外开销八、未来十年技术演进预测1. C语言:拥抱现代安全特性新标准演进:C23引入#elifdef简化宏,nullptr替代NULL安全强化:边界检查函数(如strcpy_s())静态分析工具(Clang Analyzer)2. Java:云原生时代进化GraalVM革命:将Java字节码直接编译为本地机器码(启动速度提升50倍)Project Loom:虚拟线程支持百万级并发(颠覆传统线程模型)3. Python:性能突围计划Pyston v3:JIT编译器使速度提升30%Mojo语言:兼容Python语法的超集,速度达C级别(专为AI设计)九、开发者能力矩阵建议能力维度 C语言工程师 Java架构师 Python数据科学家核心技能 指针/内存管理 Spring Cloud生态 Pandas/NumPy汇编接口调用 JVM调优 Scikit-Learn实时系统设计 分布式事务 TensorFlow辅助工具 GDB调试器 Arthas诊断工具 Jupyter NotebookValgrind内存检测 Prometheus监控 MLflow实验管理薪资范围 3-5年经验:30-50万 5-8年经验:50-80万 AI方向:60-100万+结语:三角平衡的编程生态C语言守护数字世界的物理边界——没有它,芯片无法启动,火箭不能升空Java构筑商业文明的数字基石——支撑全球70%的企业交易系统Python点燃智能时代的创新引擎——驱动90%的AI研究论文————————————————原文链接:https://blog.csdn.net/2302_77626561/article/details/151645868
-
大家好,8月份的技术汇总贴它虽迟但到。本次带来了关于SpringBoot,C++,Java,python,GO语言等等多方面知识,量大管饱,希望可以帮助到大家。1、Java报错:org.springframework.beans.factory.BeanCreationException的五种解决方法【转载】cid:link_02、C#控制台程序同步调用WebApi实现方式【转载】cid:link_73、java -jar example.jar 产生的日志输出到指定文件的方法【转载】cid:link_84、 SpringBoot项目自定义静态资源映射规则的实现代码【转载】cid:link_15、 SpringBoot中9个内置过滤器用法的完整指南【转载】cid:link_26、C++中的list与forward_list介绍与使用【转载】cid:link_37、Java使用Redis实现消息订阅/发布的几种方式【转载】cid:link_98、Spring Boot中使用@Scheduled和Quartz实现定时任务的详细过程【转载】cid:link_109、 Python中的sort()和sorted()用法示例解析【转载】https://bbs.huaweicloud.com/forum/thread-02102190373187063025-1-1.html10、 python中update()函数的用法和一些例子【转载】cid:link_411、基于Python编写新手向的简易翻译工具 【转载】cid:link_1112、使用Python创建PowerPoint各种图表的详细教程【转载】cid:link_1213、python basicConfig()简介及用法举例【转载】cid:link_514、 Python 存根文件(.pyi)简介与实战案例及类型提示的高级指南【转载】cid:link_615、Go轻松构建WebSocket服务器的实现方案【转载】https://bbs.huaweicloud.com/forum/thread-0294190373041865026-1-1.html
-
JUC(Java Util Concurrent)即 Java 并发工具包,是java.util.concurrent包及其子包的简称,自 Java 5 引入,为并发编程提供了高效、安全、可靠的工具类,极大简化了多线程编程的复杂度。JUC 主要包含以下几类组件:线程池框架(Executor Framework)并发集合(Concurrent Collections)同步工具(Synchronizers)原子操作类(Atomic Classes)锁机制(Locks)并发工具类(如 CountDownLatch、CyclicBarrier 等)线程池框架线程池通过重用线程来减少线程创建和销毁的开销,提高系统性能。核心接口与类Executor:最基本的线程池接口,定义了执行任务的方法ExecutorService:扩展了 Executor,提供了更丰富的线程池操作ThreadPoolExecutor:线程池的核心实现类Executors:线程池的工具类,提供了常用线程池的创建方法线程池示例import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit; public class ThreadPoolExample { public static void main(String[] args) { // 创建固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(3); // 提交任务 for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { try { System.out.println("任务 " + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行"); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // 关闭线程池 executor.shutdown(); try { // 等待所有任务完成 if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { // 超时后强制关闭 executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); } }}AI生成项目ThreadPoolExecutor 核心参数手动创建线程池时,ThreadPoolExecutor的构造函数提供了最灵活的配置:public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)AI生成项目corePoolSize:核心线程数maximumPoolSize:最大线程数keepAliveTime:非核心线程的空闲超时时间workQueue:任务等待队列threadFactory:线程工厂handler:拒绝策略import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit; public class CustomThreadPool { public static void main(String[] args) { // 自定义线程池配置 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心线程数 5, // 最大线程数 30, // 空闲时间 TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), // 有界队列 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); // 提交任务 for (int i = 0; i < 20; i++) { final int taskId = i; executor.execute(() -> { try { System.out.println("任务 " + taskId + " 由线程 " + Thread.currentThread().getName() + " 执行"); TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); }}AI生成项目并发集合JUC 提供了一系列线程安全的集合类,相比传统的同步集合,通常具有更好的性能。常用并发集合ConcurrentHashMap:线程安全的 HashMap 替代者CopyOnWriteArrayList:读多写少场景下的线程安全 ListCopyOnWriteArraySet:基于 CopyOnWriteArrayList 实现的 SetConcurrentLinkedQueue:高效的并发队列LinkedBlockingQueue:可阻塞的链表队列ArrayBlockingQueue:有界的数组队列PriorityBlockingQueue:支持优先级的阻塞队列ConcurrentHashMap 示例import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit; public class ConcurrentHashMapExample { public static void main(String[] args) throws InterruptedException { Map<String, Integer> concurrentMap = new ConcurrentHashMap<>(); ExecutorService executor = Executors.newFixedThreadPool(4); // 并发写入 for (int i = 0; i < 1000; i++) { final int num = i; executor.submit(() -> { String key = "key" + (num % 10); // 原子操作:计算并替换 concurrentMap.compute(key, (k, v) -> v == null ? 1 : v + 1); }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); // 输出结果 concurrentMap.forEach((k, v) -> System.out.println(k + ": " + v)); }}AI生成项目同步工具类JUC 提供了多种同步工具,用于协调多个线程之间的协作。CountDownLatch允许一个或多个线程等待其他线程完成操作。import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { // 计数器为3 CountDownLatch latch = new CountDownLatch(3); ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { final int taskId = i; executor.submit(() -> { try { System.out.println("任务 " + taskId + " 开始执行"); Thread.sleep(1000 + taskId * 500); System.out.println("任务 " + taskId + " 执行完成"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 计数器减1 latch.countDown(); } }); } System.out.println("等待所有任务完成..."); // 等待计数器变为0 latch.await(); System.out.println("所有任务已完成,继续执行主线程"); executor.shutdown(); }}AI生成项目CyclicBarrier让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障,所有被阻塞的线程才会继续执行。import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors; public class CyclicBarrierExample { public static void main(String[] args) { // 3个线程到达屏障后,执行Runnable任务 CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程已到达屏障,开始下一步操作")); ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { final int threadId = i; executor.submit(() -> { try { System.out.println("线程 " + threadId + " 正在执行任务"); Thread.sleep(1000 + threadId * 500); System.out.println("线程 " + threadId + " 到达屏障"); // 等待其他线程到达 barrier.await(); System.out.println("线程 " + threadId + " 继续执行"); } catch (InterruptedException | BrokenBarrierException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); }}AI生成项目Semaphore信号量,用于控制同时访问特定资源的线程数量。import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit; public class SemaphoreExample { public static void main(String[] args) { // 允许3个线程同时访问 Semaphore semaphore = new Semaphore(3); ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { try { // 获取许可 semaphore.acquire(); System.out.println("任务 " + taskId + " 获得许可,开始执行"); TimeUnit.SECONDS.sleep(2); System.out.println("任务 " + taskId + " 执行完成,释放许可"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 释放许可 semaphore.release(); } }); } executor.shutdown(); }}AI生成项目原子操作类JUC 提供了一系列原子操作类,用于在不使用锁的情况下实现线程安全的原子操作。主要原子类包括:基本类型:AtomicInteger、AtomicLong、AtomicBoolean数组类型:AtomicIntegerArray、AtomicLongArray等引用类型:AtomicReference、AtomicStampedReference等字段更新器:AtomicIntegerFieldUpdater等import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private static AtomicInteger counter = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(10); // 10个线程,每个线程自增1000次 for (int i = 0; i < 10; i++) { executor.submit(() -> { for (int j = 0; j < 1000; j++) { // 原子自增操作 counter.incrementAndGet(); } }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); // 结果应该是10000 System.out.println("最终计数: " + counter.get()); }}AI生成项目锁机制JUC 的java.util.concurrent.locks包提供了比synchronized更灵活的锁机制。Lock 接口Lock接口是所有锁的父接口,主要实现类有:ReentrantLock:可重入锁ReentrantReadWriteLock:可重入读写锁import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private static int count = 0; // 创建可重入锁 private static Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 1000; i++) { executor.submit(() -> { // 获取锁 lock.lock(); try { count++; } finally { // 确保锁被释放 lock.unlock(); } }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); System.out.println("最终计数: " + count); }}AI生成项目读写锁ReentrantReadWriteLock提供了读锁和写锁分离,适合读多写少的场景:import java.util.HashMap;import java.util.Map;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private Map<String, String> data = new HashMap<>(); private ReadWriteLock lock = new ReentrantReadWriteLock(); // 读操作使用读锁 public String get(String key) { lock.readLock().lock(); try { System.out.println("读取 key: " + key + ",线程: " + Thread.currentThread().getName()); return data.get(key); } finally { lock.readLock().unlock(); } } // 写操作使用写锁 public void put(String key, String value) { lock.writeLock().lock(); try { System.out.println("写入 key: " + key + ",线程: " + Thread.currentThread().getName()); data.put(key, value); } finally { lock.writeLock().unlock(); } } public static void main(String[] args) throws InterruptedException { ReadWriteLockExample example = new ReadWriteLockExample(); ExecutorService executor = Executors.newFixedThreadPool(5); // 添加写操作 executor.submit(() -> example.put("name", "Java")); // 添加多个读操作 for (int i = 0; i < 4; i++) { executor.submit(() -> { for (int j = 0; j < 3; j++) { example.get("name"); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); }}AI生成项目实战案例:生产者消费者模型使用 JUC 的阻塞队列实现经典的生产者消费者模型:import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit; public class ProducerConsumerExample { // 容量为10的阻塞队列 private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); private static final int MAX_ITEMS = 20; // 生产者 static class Producer implements Runnable { private int id; public Producer(int id) { this.id = id; } @Override public void run() { try { for (int i = 0; i < MAX_ITEMS; i++) { int item = id * 100 + i; queue.put(item); // 放入队列,如果满了会阻塞 System.out.println("生产者 " + id + " 生产了: " + item + ",队列大小: " + queue.size()); TimeUnit.MILLISECONDS.sleep(100); // 模拟生产耗时 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } // 消费者 static class Consumer implements Runnable { private int id; public Consumer(int id) { this.id = id; } @Override public void run() { try { for (int i = 0; i < MAX_ITEMS; i++) { int item = queue.take(); // 从队列取,如果空了会阻塞 System.out.println("消费者 " + id + " 消费了: " + item + ",队列大小: " + queue.size()); TimeUnit.MILLISECONDS.sleep(150); // 模拟消费耗时 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(4); // 创建2个生产者 executor.submit(new Producer(1)); executor.submit(new Producer(2)); // 创建2个消费者 executor.submit(new Consumer(1)); executor.submit(new Consumer(2)); executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); }}AI生成项目总结JUC 为 Java 并发编程提供了强大的工具支持,大大简化了多线程程序的开发难度。掌握 JUC 的使用,能够帮助开发者编写高效、安全的并发程序,应对多线程环境下的各种挑战。在实际开发中,应根据具体场景选择合适的并发工具,同时注意线程安全和性能之间的平衡。通过不断实践和深入理解这些工具的原理,您将能够构建出更健壮、更高效的并发应用程序。————————————————原文链接:https://blog.csdn.net/m0_57836225/article/details/149613910
-
从诺基亚塞班到阿里双11,从安卓应用到华尔街交易,Java用一行System.out.println()征服了数字世界1998年,诺基亚在塞班系统上首次采用Java ME技术,让手机具备了运行应用程序的能力,开启了移动互联网的序幕。当时的Java开发者们可能不会想到,这个简单的System.out.println()打印语句,会成为改变世界的代码。2009年,阿里首次在双11购物节中使用Java构建的分布式系统,成功应对了每秒数万笔交易的挑战。在2019年双11期间,阿里云更是创下单日处理54.4万笔/秒的世界纪录,这背后是数百万行Java代码的完美配合。在移动端,Android系统基于Java语言构建的应用生态已经覆盖全球超过25亿台设备。从简单的计算器应用到复杂的3D游戏,Java的跨平台特性让同一个应用能在不同设备上稳定运行。在金融领域,华尔街90%以上的高频交易系统使用Java开发。高盛、摩根士丹利等投行依靠Java的稳定性和高性能特性,在纳秒级的时间窗口内完成数以亿计的交易。一个简单的System.out.println()调试语句,可能就关系着数百万美元的交易决策。一、设计哲学:一次编写,到处运行的虚拟王国核心三支柱:graph LR A[Java语言] --> B[字节码] B --> C[JVM虚拟机] C --> D[操作系统] AI生成项目跨平台本质:字节码作为通用货币,JVM担任央行(Windows/Mac/Linux分别实现本地化)内存安全革命:自动垃圾回收(GC)终结手动内存管理时代对象王国宪法:万物皆对象(除基本类型)单继承多接口(规避C++菱形继承问题)强类型检查(编译期拦截90%类型错误)版本进化里程碑:版本 代号 革命性特性 商业影响JDK 1.2 Playground 集合框架/内部类 企业级开发奠基Java 5 Tiger 泛型/注解/枚举 企业注解驱动开发爆发Java 8 Spider Lambda/Stream API 函数式编程普及Java 17 LTS 密封类/模式匹配 云原生时代标准基石二、JVM虚拟机:万亿级商业系统的动力引擎1. 字节码执行全流程public class Main { public static void main(String[] args) { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } } } AI生成项目编译后字节码关键指令:0: iconst_0 // 压入常数0 1: istore_1 // 存储到变量1 2: iconst_1 // 压入1 3: istore_2 // 存储到循环变量i 4: iload_2 // 加载i 5: bipush 100 // 压入100 7: if_icmpgt 20 // 比较i>100则跳转 AI生成项目2. JIT即时编译黑科技分层编译策略:层级 编译方式 适用场景Level 0 解释执行 冷门代码Level 3 C1简单编译 短期存活方法Level 4 C2深度优化 热点方法(>万次)逃逸分析优化:// 未优化前:在堆分配100万对象 void process() { for(int i=0; i<1_000_000; i++){ User user = new User(); // 对象分配 } } AI生成项目JIT优化后:拆解User字段为局部变量,彻底消除对象分配翻译3. GC垃圾回收王朝更迭收集器 工作方式 适用场景 暂停时间Serial GC 单线程复制 客户端小程序 数百msParallel GC 多线程标记整理 吞吐优先系统 几十msCMS 并发标记清除 响应敏感系统 10ms以下G1 GC 分区域并发收集 大内存应用 10ms级可控ZGC 染色指针+并发转移 10TB级内存 <1ms阿里双11实战配置:-XX:+UseG1GC -Xmx100g -XX:MaxGCPauseMillis=200 三、技术生态:四大疆域的统治版图1. 企业级开发王国(Java EE / Jakarta EE)Spring帝国架构:graph TD A[Spring Boot] --> B[自动配置] A --> C[嵌入式容器] B --> D[Spring Data] B --> E[Spring Security] C --> F[Tomcat/Netty] AI生成项目微服务黄金组合:注册中心:Nacos/Zookeeper服务调用:OpenFeign熔断降级:Sentinel配置中心:Apollo高并发架构案例(12306系统):@RestController public class TicketController { @Autowired private RedisTemplate<String, Ticket> redisTemplate; @GetMapping("/grab") public String grabTicket(@RequestParam String trainId) { // Redis分布式锁确保原子性 Boolean locked = redisTemplate.opsForValue() .setIfAbsent("lock_"+trainId, "1", 10, TimeUnit.SECONDS); if(locked) { Ticket ticket = redisTemplate.opsForList().rightPop(trainId); if(ticket != null) return "抢票成功"; } return "票已售罄"; } } AI生成项目2. 移动端王国(Android)Android架构演进:架构 代表技术 解决痛点MVC Activity全能控制 逻辑视图耦合MVP Presenter中介 单元测试困难MVVM LiveData+DataBinding 数据驱动视图MVI 单向数据流 状态管理混乱Jetpack组件矩阵:graph LR A[Lifecycle] --> B[ViewModel] B --> C[LiveData] C --> D[Room] A --> E[WorkManager] D --> F[Paging] AI生成项目3. 大数据王国Hadoop生态链:组件 Java类占比 核心功能HDFS 98% 分布式文件存储MapReduce 100% 批处理计算框架HBase 85% 列式数据库Spark 30% 内存计算(Scala主导)Flink流处理Java示例:DataStream<String> data = env.socketTextStream("localhost", 9999); data.flatMap((String line, Collector<WordCount> out) -> { for (String word : line.split(" ")) { out.collect(new WordCount(word, 1)); } }) .keyBy(WordCount::getWord) .sum("count") .print(); // 实时词频统计 AI生成项目4. 云原生新边疆Quarkus:云原生Java革命@Path("/hello") public class GreetingResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "启动时间: " + (System.currentTimeMillis() - StartupTimer.start); } } AI生成项目性能对比:指标 传统Tomcat Quarkus启动时间 4.5秒 0.038秒内存占用 285MB 45MB请求延迟 15ms 3ms四、开发工具链:帝国工程师的武器库1. 构建工具进化史工具 配置文件 依赖管理机制 构建速度Ant build.xml 手动下载jar 慢Maven pom.xml 中央仓库自动解析 中等Gradle build.gradle 增量编译+缓存 快(快30%)Gradle多模块配置:// settings.gradle include 'user-service', 'order-service', 'gateway' // build.gradle subprojects { apply plugin: 'java' dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' } } AI生成项目2. 诊断调优神器JFR飞行记录仪:java -XX:StartFlightRecording=duration=60s,filename=recording.jfr MyApp Arthas在线诊断:watch com.example.service.UserService queryUser '{params, returnObj}' -x 3 GC日志分析:java -Xlog:gc*=debug:file=gc.log -jar app.jar 五、未来战场:危机与变革1. 云原生时代的挑战者语言 优势领域 Java应对策略Go 高并发微服务 Quarkus/GraalVMRust 系统编程 Panama FFI接口Kotlin Android开发 Jetpack Compose整合2. 颠覆性技术突破GraalVM原生编译:native-image --no-fallback -jar myapp.jar 将Spring Boot应用转为独立可执行文件(启动<50ms)Loom虚拟线程:try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for(int i=0; i<10_000; i++) { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; }); // 万级并发无压力 } } AI生成项目Valhalla值类型:__value class Point { int x; int y; } // 栈分配替代对象,性能提升5倍 AI生成项目六、开发者进阶路线图1. 职业赛道选择方向 技术栈 薪资范围(3-5年)企业级开发 Spring Cloud + Alibaba 30-50万Android开发 Jetpack Compose + KMM 25-40万大数据开发 Flink + Hadoop 35-60万云原生架构 Quarkus + Kubernetes 50-80万2. 知识体系图谱graph LR A[Java基础] --> B[JVM原理] A --> C[并发编程] B --> D[性能调优] C --> E[分布式系统] D --> F[云原生架构] E --> G[领域驱动设计] AI生成项目结语:永不落幕的帝国当Oracle的律师团为版权奔走时,当Rustaceans高呼内存安全时,当Go开发者炫耀协程效率时——Java依然运行在:全球45亿台Android设备华尔街78% 的交易系统阿里云上百万台服务器Java的终极竞争力:用严谨的类型系统构建数字世界的秩序用虚拟机的智慧平衡效率与跨平台用二十年的生态沉淀驾驭技术变革浪潮正如James Gosling在Java诞生时的预言:“我们不是在创造语言,而是在构建数字文明的基石。” 从智能卡到航天器,从物联网到元宇宙,Java帝国仍在拓展它的疆域。————————————————原文链接:https://blog.csdn.net/2302_77626561/article/details/149091813
-
一.查找算法(1)基本查找基本查找就是一个一个遍历数组,直到找到所需元素,效率最低(2)二分/折半查找前提条件:数组中的数据是有序的核心逻辑:每次去除一半的查找范围优点:效率比基本查找要高实现:1.max是数组最大索引2.min是数组最小索引3.mid是二者的整除所取的整数,拿它来跟目标值作比较4.如果mid索引的整数比目标值大,那么min=mid+15.如果比目标值小,那么max=mid-1如果目标值不在数组中,返回索引-1,并且这时候min会大于max(因为当要找的数据大于所有数据的时候,min最后会比max大;如果要找的数据小于所有数据,max最后比min小;如果在数据之中找不到数据,min最后也会比max大;但是一般要查找的的数据都在数组里面)public class BinarySearch { public static void main(String[] args) { int[] arr = {1, 22, 33, 44, 55, 6666, 77777, 88888, 99999, 100000}; System.out.println(binarySearch(arr, 22)); } public static int binarySearch(int[] arr, int target) { int min=0; int max=arr.length-1; while (true){ int mid=(min+max)/2; if (min>max){ return -1; } //mid索引在目标左边 if (arr[mid]<target){ min=mid+1; } //mid索引在目标右边 if (arr[mid]>target){ max=mid-1; } //mid索引在目标上 if (arr[mid]==target){ return mid; } } }}AI生成项目java运行(3)分块查找用于每个数据之间没有顺序,但是每个区域间有顺序的情况:例如有个数组:{1,3,2,4,5,66,88,77,999,100,101}(会发现每个数据之间确实是没有顺序可言,但是可以把这几个数据分区域,第一个区域的最大值为5,第二个区域最大值为88,第三个区域最大值为101,而且第二个区域的所有数据都大于第一个区域,第三个区域的数据也大于第二个区域的最大数值,那么就可以利用分块查找了。)1.既然要分区,那我们可以先创建一个区域类:Blockclass Block{int maxIndex;int minIndex;int maxNumber;还有构造方法和get,set方法等等}2.创建对象,把每个区域信息填进去,一般创建数据个数的平方根个对象3.创建存放Block对象的数组4.创建方法去把目标值的所在区域找到5.创建方法在区域中找目标值,并返回索引实例:public class BlockSearch { public static void main(String[] args) { int[] arr = {1, 22, 34, 33, 55, 66, 78, 68, 9999, 10000}; Block b1= new Block(3, 0, 34); Block b2= new Block(7, 4, 78); Block b3= new Block(9, 8, 10000); Block[] blockArr = {b1, b2, b3}; int target = 33; int index = finalSearch(blockArr, target, arr); System.out.println("index = " + index); } //先找区域 public static int blockSearch(Block[] arr,int target){ for(int i=0;i<arr.length;i++){ if(arr[i].maxNumber>=target){ return i; } } return -1; } //再找位置 public static int finalSearch(Block[] arr1,int target,int[] arr2){ int index = blockSearch(arr1,target); if (index==-1){ return -1; } for(int i=arr1[index].minIndex;i<=arr1[index].maxIndex;i++){ if (arr2[i]==target){ return i; } } return -1; } }//Blockclass Block{ int maxIndex; int minIndex; int maxNumber; public Block(int maxIndex, int minIndex, int maxNumber) { this.maxIndex = maxIndex; this.minIndex = minIndex; this.maxNumber = maxNumber; } }AI生成项目java运行结果: 二.排序算法(1)冒泡排序相邻的元素两两比较,先按照要求把最大或者最小的先放到右边:——外循环是看它要执行的次数,也就是数据数量-1次——内循环的-1是为了不让i+1(最后一个数据)超出索引——内循环的-i是为了效率,因为每次执行完,就可以减少最后一个数据,也就是减少一次public class BubbleSort { public static void main(String[] args) { int[] arr={2,5,8,9,1,5,6,4,7,8}; for (int i=0;i<arr.length-1;i++){ for (int j=0;j<arr.length-i-1;j++){ if(arr[j]>arr[j+1]){ int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } System.out.println(Arrays.toString(arr)); }}AI生成项目java运行结果: (2)选择排序 拿第一个元素跟后面的元素比,符合要求的放到一个,继续循环,让第二个元素再依次跟后面比,让符合规则的放到第二个,然后一个一个把最大的挑出来(从大到小排序)。以此类推,跟冒泡不同的是,把确定的元素放到左边——i是用来做比较的下标——j是比i大1,然后逐渐递增的下标的元素下面是排序从大到小:public class SelectionSort { public static void main(String[] args) { int[] arr={2,5,8,9,1,5,6,4,7,8}; //i是要拿来做比较的元素下标 for (int i=0;i<arr.length-1;i++){ for(int j=i+1;j<arr.length;j++){ if(arr[j]>arr[i]){ int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } } } System.out.println(Arrays.toString(arr));}}AI生成项目java运行结果: (3) 插入排序将0索引到n索引的数据看作是有序的,把索引为n+1一直到最后一个索引当作是无序的,遍历无序的数组,将遍历到的数据插入到有序序列中的合适位置,如遇到相同数据,排在后面,这个方法就是插入排序public class InsertSort { public static void main(String[] args) { //将下面的数组按照从大到小排列 int[] arr={5,8,3,2,17,19,15,16,90}; //找无序的开始索引 int startIndex=-1; for(int i=0;i<arr.length;i++){ if(arr[i+1]>arr[i]){ startIndex=i+1; break; } } //从无序开始索引开始插入排序,反向遍历,一个一个对比插入 for(int i=startIndex;i<arr.length;i++){ int j=i; while(j>0&&arr[j]>arr[j-1]){ int temp=arr[j]; arr[j]=arr[j-1]; arr[j-1]=temp; j--; } } //看结果 System.out.println(Arrays.toString(arr)); }}AI生成项目java运行结果: 三.递归算法 递归指的是方法中调用方法自己本身的现象。递归的注意点:一定要有出口,否则会内存溢出。所谓出口就是调用到第几次就不再调用自己了。递归的作用:大事化小,小事化了书写递归的两个核心:1.出口2.找规则如何把大问题转换成规模较小的小问题3.再次调用方法的时候,要更靠近出口实例:求1——100的和public class Recursion { public static void main(String[] args) { System.out.println(getSum(100)); } public static int getSum(int number){ if(number == 1){ return 1; } return number + getSum(number - 1); }}AI生成项目java运行//其实就是把1+100转换成:100+1—99的和99+1—98的和98+1—97的和。。。2+1的和1就是出口————————————————原文链接:https://blog.csdn.net/2403_86850076/article/details/146273968
-
Java 对接第三方接口的三种实现方式详解在 Java 开发中,调用第三方接口是常见的需求。无论是与外部系统进行数据交互,还是集成第三方服务(如支付、地图、短信等),都需要通过 HTTP 请求与目标接口进行通信。本文将详细介绍三种主流的实现方式:HttpURLConnection、Apache HttpClient 和 Spring RestTemplate,并提供代码示例和注意事项,帮助开发者选择最适合的方案。一、使用 HttpURLConnection(Java 原生方式)1.1 特点与适用场景HttpURLConnection 是 Java 自带的 HTTP 客户端工具,无需引入额外依赖,适合简单的 HTTP 请求场景。其优势在于轻量级和无需配置,但功能相对基础,复杂请求(如设置请求头、处理 JSON 数据)需要手动实现。1.2 示例代码以下是一个发送 POST 请求并处理响应的示例:import java.io.*;import java.net.HttpURLConnection;import java.net.URL;public class HttpURLConnectionExample { public static void main(String[] args) { try { // 创建URL对象 URL url = new URL("https://api.example.com/data"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // 设置请求方法为POST conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json; utf-8"); conn.setRequestProperty("Accept", "application/json"); conn.setDoOutput(true); // 允许输出流(写入请求体) // 构建JSON请求体 String jsonInputString = "{\"name\": \"John\", \"age\": 30}"; // 写入请求体 try (OutputStream os = conn.getOutputStream()) { byte[] input = jsonInputString.getBytes("utf-8"); os.write(input, 0, input.length); } // 读取响应 try (BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8"))) { StringBuilder response = new StringBuilder(); String responseLine; while ((responseLine = br.readLine()) != null) { response.append(responseLine.trim()); } System.out.println("响应结果: " + response.toString()); } } catch (Exception e) { e.printStackTrace(); } }}AI生成项目java运行1.3 优点与缺点优点:无需依赖,简单易用,适合小型项目或快速原型开发。缺点:功能有限,手动处理请求头和响应较繁琐,不支持异步请求。1.4 注意事项需要手动管理连接超时和读取超时:conn.setConnectTimeout(5000); // 连接超时时间conn.setReadTimeout(5000); // 读取超时时间AI生成项目java运行对于需要认证的接口,需手动设置请求头:String auth = "Basic " + Base64.getEncoder().encodeToString("username:password".getBytes());conn.setRequestProperty("Authorization", auth);AI生成项目java运行二、使用 Apache HttpClient(功能强大,灵活可控)2.1 特点与适用场景Apache HttpClient 是一个功能强大的 HTTP 客户端库,支持更复杂的请求场景(如文件上传、身份验证、代理配置等)。它比 HttpURLConnection 更灵活,但需要引入额外依赖。2.2 示例代码以下是一个使用 Apache HttpClient 发送 GET 请求的示例:import org.apache.http.HttpEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;public class HttpClientExample { public static void main(String[] args) { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { // 创建GET请求 HttpGet httpGet = new HttpGet("https://api.example.com/data"); // 设置请求头 httpGet.setHeader("Content-Type", "application/json"); httpGet.setHeader("Accept", "application/json"); // 执行请求 CloseableHttpResponse response = httpClient.execute(httpGet); // 获取响应实体 HttpEntity entity = response.getEntity(); if (entity != null) { String result = EntityUtils.toString(entity); System.out.println("响应结果: " + result); } } catch (Exception e) { e.printStackTrace(); } }}AI生成项目java运行2.3 优点与缺点优点:功能全面,支持高级特性(如连接池、重试机制、超时配置)。缺点:需要引入依赖(如 httpclient 和 httpcore),代码略显冗长。2.4 注意事项依赖配置(Maven):<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version></dependency>AI生成项目xml自定义配置(如超时时间):RequestConfig config = RequestConfig.custom() .setConnectTimeout(5000) .setSocketTimeout(5000) .build();CloseableHttpClient httpClient = HttpClients.custom() .setDefaultRequestConfig(config) .build();AI生成项目java运行三、使用 Spring RestTemplate(Spring 生态首选)3.1 特点与适用场景RestTemplate 是 Spring 框架提供的 HTTP 客户端工具,专为 RESTful 接口设计。它简化了请求和响应的处理,支持自动序列化/反序列化(如 JSON、XML),是 Spring 项目中的首选方案。3.2 示例代码以下是一个使用 RestTemplate 发送 GET 和 POST 请求的示例:3.2.1 发送 GET 请求import org.springframework.web.client.RestTemplate;public class RestTemplateExample { public static void main(String[] args) { RestTemplate restTemplate = new RestTemplate(); String url = "https://api.example.com/data"; // 发送GET请求并获取响应 String response = restTemplate.getForObject(url, String.class); System.out.println("GET响应结果: " + response); }}AI生成项目java运行3.2.2 发送 POST 请求import org.springframework.http.HttpEntity;import org.springframework.http.HttpHeaders;import org.springframework.http.MediaType;import org.springframework.web.client.RestTemplate;public class RestTemplatePostExample { public static void main(String[] args) { RestTemplate restTemplate = new RestTemplate(); String url = "https://api.example.com/data"; // 构建请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // 构建请求体 String requestBody = "{\"name\": \"John\", \"age\": 30}"; HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers); // 发送POST请求并获取响应 String response = restTemplate.postForObject(url, requestEntity, String.class); System.out.println("POST响应结果: " + response); }}AI生成项目java运行3.3 优点与缺点优点:与 Spring 框架无缝集成,支持自动序列化/反序列化,代码简洁。缺点:需要依赖 Spring 框架,不适合非 Spring 项目。3.4 注意事项异常处理:RestTemplate 抛出的 RestClientException 需要捕获处理:try { String response = restTemplate.getForObject(url, String.class);} catch (RestClientException e) { e.printStackTrace();}AI生成项目java运行配置超时时间:RestTemplate restTemplate = new RestTemplate();restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());((HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory()) .setConnectTimeout(5000);AI生成项目java运行四、三种方式的对比与选择建议实现方式 适用场景 优点 缺点HttpURLConnection 简单的小型项目或快速原型开发 无需依赖,轻量级 功能有限,手动处理较繁琐Apache HttpClient 复杂请求(如认证、文件上传等) 功能全面,灵活可控 需要引入依赖,代码冗长Spring RestTemplate Spring 项目中的 RESTful 接口交互 与 Spring 无缝集成,代码简洁 依赖 Spring 框架,不适合非 Spring 项目选择建议简单需求:优先使用 HttpURLConnection。复杂需求:选择 Apache HttpClient,尤其是需要高级功能(如连接池、重试)时。Spring 项目:推荐使用 RestTemplate,充分利用 Spring 的生态优势。五、最佳实践统一封装工具类:无论选择哪种方式,建议将 HTTP 请求逻辑封装为工具类,便于复用和维护。异常处理:所有 HTTP 请求都应捕获异常(如网络超时、认证失败),并记录日志。性能优化:使用连接池(如 Apache HttpClient 的 PoolingHttpClientConnectionManager)。启用 GZIP 压缩(Accept-Encoding: gzip)。安全性:避免硬编码敏感信息(如 API Key),使用环境变量或配置中心。验证响应数据的合法性(如 JSON 格式校验)。六、总结Java 对接第三方接口的三种主流方式各有特点:HttpURLConnection 适合轻量级需求,但功能有限;Apache HttpClient 提供了更强大的功能,适合复杂场景;Spring RestTemplate 在 Spring 项目中表现优异,代码简洁高效。开发者可根据项目需求和框架选择合适的实现方式,并结合最佳实践优化性能和可维护性。随着 Spring 6 和 Java 11+ 的普及,未来可能会更多采用 HttpClient(Java 11 新特性)或 WebClient(Spring WebFlux),但目前这三种方法仍是主流选择。————————————————原文链接:https://blog.csdn.net/huayula/article/details/148243340
-
排序稳定的排序:排序之前和排序之后它们俩的相对顺序没有发生变化内部排序:在内存上的排序外部排序:需要借助磁盘等外部空间进行的排序插入排序思想:假设这组数据的第一个元素是有序的,从第二个元素和前面的元素进行比较,找到合适的位置进行插入 // 插入排序 public static void insertSort(int[] array){ for(int i = 1;i < array.length;i++){ int j = i - 1; int tmp = array[i]; for(;j >= 0;j--){ if(array[j] > tmp){ array[j+1] = array[j]; }else{ break; } } array[j + 1] = tmp; } }AI生成项目java运行 分析时间复杂度:O(N^2)最坏情况:O(N^2)最好情况:有序的数组,O(N)数据越有序,排序越快适用于待排序数组基本上趋于有序了,时间复杂度趋于O(N)空间复杂度:O(1)稳定性:是一个稳定的排序例子:5 5 7稳定的排序可以改成不稳定的排序,但是不稳定的排序不能改成稳定的排序希尔排序对直接插入排序进行了优化,如果是 5 4 3 2 1 会退化为O(N^2)分组:分完组后,每组都采用直接插入排序中间隔一些元素进行分组的好处:比较大的元素都往后走了,比较小的元素都往前走了缩小增量到最后会把整体看成是一组,5 3 1 组,前面的5 3 都是预排序,真正的排序是最后的一组缩小增量的目的:为了让排序更接近O(N),为了让排序更快 // 希尔排序 public static void shellSort(int[] array){ int gap = array.length; while(gap > 1){ gap /= 2; shell(array,gap); } } // 对每组进行插入排序 public static void shell(int[] array,int gap){ for(int i = gap;i < array.length;i++){ int j = i - gap; int tmp = array[i]; for(;j >= 0;j -= gap){ if(array[j] > tmp){ array[j+gap] = array[j]; }else{ break; } } array[j + gap] = tmp; } }AI生成项目java运行 分析时间复杂度:O(N^1.3 - N ^ 1.5),时间复杂度不好计算空间复杂度:O(1)稳定性:不稳定的排序检测排序速度: public static void testInsert(int[] array){ long startTime = System.currentTimeMillis(); int[] tmp = Arrays.copyOf(array,array.length); Sort.insertSort(tmp); long endTime = System.currentTimeMillis(); System.out.println(" " + (endTime - startTime)); } public static void testShell(int[] array){ long startTime = System.currentTimeMillis(); int[] tmp = Arrays.copyOf(array,array.length); Sort.shellSort(tmp); long endTime = System.currentTimeMillis(); System.out.println(" " + (endTime - startTime)); } // 逆序初始化 public static void initOrder(int[] array){ for(int i = 0;i < array.length;i++){ array[i] = array.length - i; } } public static void main(String[] args) { int[] arr = new int[10000]; initOrder(arr); testInsert(arr); testShell(arr); }AI生成项目java运行 选择排序在当前i下标对应的值的后面,找到后面最小的值和i下标对应的值交换 // 交换 public static void swap(int i, int j, int[] array){ int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } // 选择排序 public static void selectSort(int[] array){ // 在i下标的后面找到比i下标对应的值的最小值,然后交换 int n = array.length; for(int i = 0;i < n;i++){ int minIndex = i; for(int j = i + 1;j < n;j++){ if(array[j] < array[i]){ if(array[j] < array[minIndex]){ minIndex = j; } } } swap(i,minIndex,array); } }AI生成项目java运行 双向选择排序时间复杂度还是O(N^2)左边找最大的,右边找最小的,和第一个数和最后一个数交换 存在缺陷,maxIndex下标可能是在0下标是最大的,0下标会和最小值小标的值交换,那么0下标就不是最大值下标,应该更新为maxIndex = minIndex public static void selectSort2(int[] array){ // 在i下标的后面找到比i下标对应的值的最小值,然后交换 int left = 0; int right = array.length - 1; while(left < right){ int minIndex = left; int maxIndex = left; for(int i = left + 1;i <= right;i++){ if(array[i] < array[minIndex]){ minIndex = i; } if(array[i] > array[maxIndex]){ maxIndex = i; } } swap(left,minIndex,array); // 第一个数是最大的数,防止最小的下标和第一个数换了,最大值就在minIndex的位置了 if(maxIndex == left){ maxIndex = minIndex; } swap(right,maxIndex,array); left++; right--; } }AI生成项目java运行 分析时间复杂度:O(N^2)空间复杂度:O(1)稳定性:不稳定的排序堆排序// 堆排序 private static void shifDown(int[] array,int parent,int len){ int child = 2 * parent + 1; while(child < len){ if(child + 1 < len && array[child] < array[child + 1]){ child++; } if(array[child] > array[parent]){ swap(child,parent,array); parent = child; child = 2 * parent + 1; }else{ break; } } } private static void createHeap(int[] array){ // 建立大根堆 for(int parent = (array.length - 1 - 1) / 2;parent >= 0;parent--) { shifDown(array, parent, array.length); } } public static void heapSort(int[] array){ createHeap(array); int end = array.length - 1; while(end > 0){ swap(end,0,array); shifDown(array,0,end); end--; } }AI生成项目java运行 分析时间复杂度:O(N * logN)空间复杂度:O(1)稳定性:不稳定的排序冒泡排序// 冒泡排序 public static void bubbleSort(int[] array){ // i是趟数 for(int i = 0;i < array.length;i++){ // j是比较的大小的 boolean flag = true; for(int j = 0;j < array.length - i - 1;j++){ if(array[j] > array[j + 1]){ swap(j,j + 1,array); flag = false; } } if(flag) { break; } } }AI生成项目java运行 分析时间复杂度:O(N ^ 2)空间复杂度:O(1)稳定性:稳定的排序快速排序霍尔法根据二叉树进行递归划分 // 快速排序 public static void quickSort(int[] array){ quick(array,0,array.length - 1); } private static void quick(int[] array,int start,int end){ if(start >= end){ return; } int prvot = partitionHoare(array,start,end); quick(array,start,prvot - 1); quick(array,prvot + 1,end); } private static int partitionHoare(int[] array,int left,int right){ // 基准元素 int tmp = array[left]; // 记录第一个基准下标 int i = left; while(left < right) { // 必须先找先右再左 // 找小 while (left < right && array[right] >= tmp) { right--; } // 找大 while (left < right && array[left] <= tmp) { left++; } swap(left, right, array); } swap(i,left,array); return left; }AI生成项目java运行 为什么有等于号?没有等于号,会死循环,比如两端都是6 为什么先从右边开始,不先从左边开始?先走左边的话,先遇到大的停下来,如果相遇的话,那么相遇位置的值就是大于基准元素的,这时候交换的话,6的左边有一个数比6大 分析时间复杂度:最好情况下:O(N * logN)每层都有N个节点,高度为logN,需要每个节点都遍历到,N * logN次遍历最坏情况下:O(N^2),有序/逆序,单分支的树空间复杂度:最好情况下:O(logN)最坏情况下:O(N),有序/逆序,单分支的树递归左边再递归右边,递归右边左边没有释放稳定性:不稳定的排序挖坑法找基准先走右边再走左边,以第一个元素为基准,并且拿出基准元素,基准元素的位置就是坑,如果右边找到比基准小的,把它放入坑中,左边找到比基准元素大的放到坑中,最后两者相遇,把基准元素放入其中// 挖坑法 private static int partitionHole(int[] array,int left,int right){ // 基准元素 int tmp = array[left]; while(left < right) { // 必须先找先右再左 // 找小 while (left < right && array[right] >= tmp) { right--; } array[left] = array[right]; // 找大 while (left < right && array[left] <= tmp) { left++; } array[right] = array[left]; } array[left] = tmp; return left; }AI生成项目java运行 前后指针法如果cur比基准元素小并且cur下标的值和prev下标的值不相等, // 前后指针法 public static int partition(int[] array,int left,int right){ int prev = left; int cur = left + 1; while(cur <= right){ while(array[cur] < array[left] && array[++prev] != array[cur]){ swap(cur,prev,array); } cur++; } swap(prev,left,array); return prev; }AI生成项目java运行 题目优先试挖坑法,其次是Hoare,最后是前后指针法A 快排的优化均匀的分割数组让递归的次数变少三数取中法三数取中法:left,right,mid三个下标中的中间值和第一个数交换位置,然后右边找比基准元素小的值,左边找比基准元素大的值规定array[mid] <= array[left] <= array[right] // 三数取中法,求中位数的下标 private static int middleNum(int[] array,int left,int right){ int mid = (left + right) / 2; if(array[left] < array[right]){ // 1. x < 3 < 9 // 2. 3 < 9 < x // 3. 3 < x < 9 if(array[mid] < array[left]){ return left; } else if(array[mid] > array[right]){ return right; } else{ return mid; } }else{ // 9 > 3 == left > right // 1. x > left > right if(array[mid] > array[left]){ return left; }else if(array[right] > array[mid]){ // 2. left > right > x return right; }else{ // 3. left > x > right return mid; } } } private static void quick(int[] array,int start,int end){ if(start >= end){ return; } // 1 2 3 4 5 6 7 // 中间值的下标和第一个数交换,作为新的基准元素 int index = middleNum(array,start,end); swap(index,start,array); // 4 2 3 1 5 6 7 // 为了让左右分布更均匀,降低树的高度,减少递归的次数 int prvot = partition(array,start,end); quick(array,start,prvot - 1); quick(array,prvot + 1,end); }AI生成项目java运行 只剩最后几层时,使用插入排序进行优化,降低递归次数,可以使用插入排序是因为前面递归成有序的序列了 public static void insertSort(int[] array,int left,int right){ for(int i = left + 1;i <= right;i++){ int j = i - 1; int tmp = array[i]; for(;j >= left;j--){ if(array[j] > tmp){ array[j+1] = array[j]; }else{ break; } } array[j + 1] = tmp; } } private static void quick(int[] array,int start,int end){ if(start >= end){ return; } if(end - start + 1 <= 15){ // 减少递归的次数 // 因为最后几层节点数最多,递归次数也多 insertSort(array,start,end); return; } // 1 2 3 4 5 6 7 // 中间值的下标和第一个数交换,作为新的基准元素 int index = middleNum(array,start,end); swap(index,start,array); // 4 2 3 1 5 6 7 // 为了让左右分布更均匀,降低树的高度,减少递归的次数 int prvot = partition(array,start,end); quick(array,start,prvot - 1); quick(array,prvot + 1,end); }AI生成项目java运行 非递归实现快排找基准里面会进行交换元素,进行排序,外面就进行找到左右下标位置 // 非递归实现快速排序 public static void quickSortNor(int[] array){ int start = 0; int end = array.length - 1; Stack<Integer> stack = new Stack<>(); int pivot = partitionHoare(array,start,end); if(pivot - 1 > start){ // 左边有两个元素 stack.push(start); stack.push(pivot - 1); } else if(pivot + 1 < end){ // 右边至少有两个元素 stack.push(pivot + 1); stack.push(end); } // 找基准里面会互换元素进行排序 while(!stack.empty()){ end = stack.pop(); start = stack.pop(); pivot = partitionHoare(array,start,end); if(pivot - 1 > start){ // 左边有两个元素 stack.push(start); stack.push(pivot - 1); } else if(pivot + 1 < end){ // 右边至少有两个元素 stack.push(pivot + 1); stack.push(end); } }AI生成项目java运行 归并排序分解:根据mid进行分解 合并:第一个有序段和第二个有序段,比较大小放入一个新的数组中 // 归并排序 public static void mergeSort(int[] array){ merge(array,0,array.length - 1); } private static void merge(int[] array,int start,int end){ if(start >= end){ return; } int mid = (start + end) / 2; // 分解 merge(array,start,mid); merge(array,mid + 1,end); // 合并 mergeHeB(array,start,mid,end); } private static void mergeHeB(int[] array, int left, int mid, int right) { int s1 = left; int e1 = mid; int s2 = mid + 1; int e2 = right; // 新数组的下标 int k = 0; int[] tmpArray = new int[right - left + 1]; while(s1 <= e1 && s2 <= e2){ if(array[s1] < array[s2]){ tmpArray[k++] = array[s1++]; }else{ tmpArray[k++] = array[s2++]; } } while(s1 <= e1){ tmpArray[k++] = array[s1++]; } while(s2 <= e2){ tmpArray[k++] = array[s2++]; } // left是因为有左边还有右边的数组 // tmpArray是临时数组,会销毁的,需要拷贝回原来的数组 for(int i = 0;i < tmpArray.length;i++){ array[i + left] = tmpArray[i]; } }AI生成项目java运行 分析时间复杂度:O(N * logN)空间复杂度:O(N),因为每次合并数组的时候要开O(N)大小的额外空间稳定性:稳定的排序非递归实现归并排序先一个一个有序,两个两个有序,四个四个有序,八个八个有序…gap = 1left = imid = left + gap - 1right = mid + gapgap *= 2每组合并完,最终就有序了 // 非递归实现归并排序 public static void mergeSortNor(int[] array){ // gap表示每组的数据个数 int gap = 1; while(gap < array.length){ for(int i = 0;i < array.length;i = i + 2 * gap){ int left = i; // mid和right可能会越界 // 比如只有5个元素 // 分解右边一半的时候 // l = i mid = 5 r = 7 int mid = left + gap - 1; int right = mid + gap; if(mid >= array.length){ mid = array.length - 1; } if(right >= array.length){ right = array.length - 1; } mergeHeB(array,left,mid,right); } gap *= 2; } }AI生成项目java运行 海量数据的排序使用归并排序进行外部排序,如果内存中不够空间的话 非比较的排序计数排序通过统计每次数字出现的次数,最后按照顺序从小到大输出这些数字适用的场景:指定范围内的数据 // 计数排序,指定范围内的数据进行排序// O(Max(N,范围)) public static void countSort(int[] array){ int minVal = array[0]; int maxVal = array[0]; // O(N) for(int i = 0;i < array.length;i++){ if(minVal > array[i]){ minVal = array[i]; } if(maxVal < array[i]){ maxVal = array[i]; } } int len = maxVal - minVal + 1; int[] count = new int[len]; // O(N) for(int i = 0;i < array.length;i++){ count[array[i] - minVal]++; } // 写回array数组中 // O(范围) // 因为count数组集中于一个数字,那么也是O(范围) // 如果count数组不集中于一个数子,也是N倍的范围 // 也是O(范围) int k = 0; for(int i = 0;i < count.length;i++){ while(count[i] > 0){ array[k++] = i + minVal; count[i]--; } } }AI生成项目java运行 分析时间复杂度:O(Max(N,范围))空间复杂度:O(范围)稳定性:稳定的排序基数排序开10个大小的空间,分别依次比较个位大小,十位大小和百位大小,最终达到有序,每个空间都是一个队列 桶排序先把数字放入一个范围的区间内进行排序,排好序依次拿出来,最终就有序了———————————————— 原文链接:https://blog.csdn.net/2301_79722622/article/details/149584998
-
一、Scanner 类概述Scanner 类是 Java 中用于获取用户输入的一个实用类,它位于 java.util 包下。通过 Scanner 类,可以方便地从多种输入源(比如标准输入流,也就是键盘输入,或者文件等)读取不同类型的数据,例如整数、小数、字符串等,大大简化了输入操作相关的编程工作。二、Scanner 类的创建在使用 Scanner 类之前,需要先创建它的对象。如果要从标准输入(键盘)读取数据,创建示例代码如下:import java.util.Scanner; public class ScannerExample { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 后续可使用该scanner对象进行输入读取操作 }}AI生成项目这里通过 new Scanner(System.in) 创建了一个 Scanner 对象,System.in 表示标准输入流,意味着后续操作将从键盘获取输入内容。三、常用方法及读取不同类型数据1.读取整数使用 nextInt() 方法可以读取用户输入的整数,示例代码如下:import java.util.Scanner; public class ReadInt { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入一个整数:"); int num = scanner.nextInt();//程序执行到这里就会停下来,等待键盘的输入。 //键盘如果没有输入,这里就会一直卡着 //直到用户输入了内容之后,敲回车,这行代码就执行结束了 //这样就完成了数据从控制台到内存 System.out.println("你输入的整数是: " + num); scanner.close(); }}AI生成项目这里提示用户输入整数后,调用 nextInt() 方法获取输入并赋值给 int 类型的变量 num,最后输出展示读取到的整数内容。需要注意的是,在读取完成后,如果不再需要使用该 Scanner 对象,最好调用 scanner.close() 方法关闭它,以释放相关资源。注意:针对nextInt()方法来说,只能接收整数数字。输入其他的字符串会报错。2.读取浮点数(小数)若要读取浮点数,可以使用 nextFloat() 方法(读取单精度浮点数) 或者 nextDouble() 方法(读取双精度浮点数),示例代码如下:import java.util.Scanner; public class ReadFloat { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入一个单精度浮点数:"); float fNum = scanner.nextFloat(); System.out.println("你输入的单精度浮点数是: " + fNum); System.out.println("请输入一个双精度浮点数:"); double dNum = scanner.nextDouble(); System.out.println("你输入的双精度浮点数是: " + dNum); scanner.close(); }}AI生成项目上述代码分别演示了读取单精度和双精度浮点数的过程,按照提示输入相应类型的小数,就能通过对应方法获取并输出展示。3.读取字符串读取字符串有两种常用方式,一种是 next() 方法,一种是 nextLine() 方法。(1).next() 方法: 它读取输入的下一个单词(以空格、制表符等空白字符作为分隔符)细节:从键盘上接收一个字符串,但是接收的是第一个空格之前的内容示例代码1:import java.util.Scanner; public class ReadStringNext { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入一些单词(以空格分隔):"); String word = scanner.next(); System.out.println("你输入的单词是: " + word); scanner.close(); }}AI生成项目输入多个单词时,它只会获取第一个单词并返回。示例代码2:import java.util.Scanner;public class scannerTest { public static void main(String[] args) { Scanner s=new Scanner(System.in); String username=s.next(); System.out.println(username); }}AI生成项目(2).nextLine() 方法:该方法读取输入的一整行内容,示例代码:import java.util.Scanner; public class ReadStringNextLine { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入一行文字:"); String line = scanner.nextLine(); System.out.println("你输入的文字内容是: " + line); scanner.close(); }}AI生成项目它会获取从当前位置到行尾的所有字符,更适合读取完整的语句等情况。注意:从键盘上接收一个字符串,但是接收的是第一个换行符\n之前的内容可能遇到的问题及注意事项1.输入不匹配异常如果用户输入的数据类型和期望读取的数据类型不一致,例如:期望读取整数,但用户输入了字母等非数字内容,会抛出 InputMismatchException 异常。所以在实际应用中,可能需要添加异常处理代码来让程序更健壮,示例如下:import java.util.InputMismatchException;import java.util.Scanner; public class ExceptionHandle { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try { System.out.println("请输入一个整数:"); int num = scanner.nextInt(); System.out.println("你输入的整数是: " + num); } catch (InputMismatchException e) { System.out.println("输入的数据类型不正确,请重新输入整数!"); } finally { scanner.close(); } }}AI生成项目这里使用 try-catch 语句块捕获可能出现的输入类型不匹配异常,并在 catch 块中给出相应提示,无论是否出现异常,最终都会在 finally 块中关闭 Scanner 对象。2.nextLine() 方法使用的坑由于nextInt()只读取整数,不读取后续的换行符,这会导致nextLine()在下一次调用时直接读取空字符串。解决方法是使用nextLine()获取整数后的换行符,避免空字符导致的跳过输入现象。import java.util.Scanner;public class scannerTest { public static void main(String[] args) { Scanner s=new Scanner(System.in); String username=s.next(); //无论是next(),还是nextInt(),nextDouble()这几个方法接收的是第一个空格之前的内容 //而对于 son依旧留在缓存中,其在缓存的格式为 son\r(回车符) System.out.println(username); String position=s.nextLine();//执行到这一行时,nextLine回去缓存读内容,而读到\r时就结束了 //即读取了 son //所以这一行的运行,用户无法输入东西 System.out.println(position); String name =s.nextLine(); System.out.println(name); }}AI生成项目运行结果: 当在调用 nextInt() 或者其他读取基本类型的方法(如 nextFloat() 等)后紧接着调用 nextLine() 方法时,可能会出现问题。因为 nextInt() 等方法读取数据后,留下的换行符(回车键对应的字符)会被 nextLine() 当作输入内容读取,导致 nextLine() 似乎 “跳过” 了用户的输入。解决办法通常是在调用 nextLine() 之前,先额外调用一次 nextLine() 来消耗掉前面留下的换行符,示例如下:import java.util.Scanner; public class NextLineIssue { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入一个整数:"); int num = scanner.nextInt(); scanner.nextLine(); // 消耗掉换行符 System.out.println("请输入一行文字:"); String line = scanner.nextLine(); System.out.println("你输入的文字内容是: " + line); scanner.close(); }}AI生成项目总之,Scanner 类在 Java 中是很常用的用于处理输入的类,掌握好它的使用方法以及注意相关问题,能更好地实现具有交互性的 Java 程序。————————————————原文链接:https://blog.csdn.net/m0_73941339/article/details/144803973
-
泛型、反射与注解是 JavaSE 中支撑 “灵活编程” 与 “框架设计” 的核心技术。泛型解决 “类型安全” 问题,反射实现 “运行时动态操作类”,注解提供 “代码标记与元数据” 能力 —— 三者结合构成了 Java 框架(如 Spring、MyBatis)的底层基础。本章节将系统讲解这三项技术的核心原理与实际应用。一、泛型(Generic):编译时的类型安全保障在泛型出现之前,集合(如List)默认存储Object类型,取出元素时需强制转换,容易出现ClassCastException(类型转换异常)。泛型通过 “编译时类型指定”,让集合只能存储特定类型元素,从源头避免类型错误。1.1 泛型的核心作用编译时类型检查:限制集合(或泛型类)只能存储指定类型元素,编译阶段就报错,而非运行时崩溃。避免强制转换:取出元素时无需手动转换(编译器自动确认类型)。代码复用:一套逻辑支持多种类型(如List<String>、List<Integer>共用List的实现)。1.2 泛型的基本用法1.2.1 泛型类与泛型接口泛型类 / 接口在定义时声明 “类型参数”(如<T>),使用时指定具体类型(如String)。泛型类示例:// 定义泛型类:声明类型参数T(Type的缩写,可自定义名称)class GenericBox<T> { // 使用T作为类型(类似变量,代表一种类型) private T value; // T作为方法参数和返回值 public void setValue(T value) { this.value = value; } public T getValue() { return value; }} public class GenericClassDemo { public static void main(String[] args) { // 使用时指定类型为String(只能存String) GenericBox<String> stringBox = new GenericBox<>(); stringBox.setValue("Hello"); // 正确:存入String // stringBox.setValue(123); // 编译错误:不能存Integer // 取出时无需转换(自动为String类型) String str = stringBox.getValue(); System.out.println(str); // 输出:Hello // 指定类型为Integer GenericBox<Integer> intBox = new GenericBox<>(); intBox.setValue(100); Integer num = intBox.getValue(); // 无需转换 System.out.println(num); // 输出:100 }}AI生成项目泛型接口示例:// 定义泛型接口(支持多种类型的“生产者”)interface Producer<T> { T produce();} // 实现泛型接口时指定具体类型(如String)class StringProducer implements Producer<String> { @Override public String produce() { return "生产的字符串"; }} // 实现时保留泛型(让子类也成为泛型类)class NumberProducer<T extends Number> implements Producer<T> { private T value; public NumberProducer(T value) { this.value = value; } @Override public T produce() { return value; }} public class GenericInterfaceDemo { public static void main(String[] args) { Producer<String> strProducer = new StringProducer(); String str = strProducer.produce(); System.out.println(str); // 输出:生产的字符串 Producer<Integer> intProducer = new NumberProducer<>(100); Integer num = intProducer.produce(); System.out.println(num); // 输出:100 }}AI生成项目1.2.2 泛型方法泛型方法在方法声明时独立声明类型参数(与类是否泛型无关),适用于 “单个方法需要支持多种类型” 的场景。class GenericMethodDemo { // 定义泛型方法:<E>是方法的类型参数,声明在返回值前 public <E> void printArray(E[] array) { for (E element : array) { System.out.print(element + " "); } System.out.println(); } // 泛型方法带返回值 public <E> E getFirstElement(E[] array) { if (array != null && array.length > 0) { return array[0]; } return null; }} public class TestGenericMethod { public static void main(String[] args) { GenericMethodDemo demo = new GenericMethodDemo(); // 调用时自动推断类型(无需显式指定) String[] strArray = {"A", "B", "C"}; demo.printArray(strArray); // 输出:A B C Integer[] intArray = {1, 2, 3}; demo.printArray(intArray); // 输出:1 2 3 // 获取第一个元素(自动返回对应类型) String firstStr = demo.getFirstElement(strArray); Integer firstInt = demo.getFirstElement(intArray); System.out.println("字符串数组第一个元素:" + firstStr); // 输出:A System.out.println("整数数组第一个元素:" + firstInt); // 输出:1 }}AI生成项目泛型方法特点:类型参数声明在方法返回值前(如<E>),与类的泛型参数无关。调用时编译器自动推断类型(无需手动指定,如传入String[]则E自动为String)。1.2.3 类型通配符(Wildcard)当需要处理 “未知类型的泛型” 时(如方法参数需要接收任意泛型List),使用通配符?及限定符(extends、super)控制类型范围。通配符形式 含义 适用场景<?> 任意类型(无限制) 仅读取元素,不修改(如打印任意 List)<? extends T> 上限:只能是 T 或 T 的子类 读取(可获取 T 类型),不能添加(除 null)<? super T> 下限:只能是 T 或 T 的父类 添加(可添加 T 或子类),读取只能到 Object通配符示例:import java.util.ArrayList;import java.util.List; public class WildcardDemo { // 1. 无限制通配符<?>:接收任意List,只能读,不能写(除null) public static void printList(List<?> list) { for (Object obj : list) { // 只能用Object接收 System.out.print(obj + " "); } System.out.println(); // list.add("A"); // 编译错误:无法确定类型,不能添加非null元素 } // 2. 上限通配符<? extends Number>:只能接收Number或其子类(如Integer、Double) public static double sum(List<? extends Number> list) { double total = 0; for (Number num : list) { // 可安全转为Number total += num.doubleValue(); // 调用Number的方法 } return total; } // 3. 下限通配符<? super Integer>:只能接收Integer或其父类(如Number、Object) public static void addIntegers(List<? super Integer> list) { list.add(10); // 可添加Integer(或其子类,如Integer本身) list.add(20); // Integer num = list.get(0); // 编译错误:只能用Object接收 } public static void main(String[] args) { // 测试<?> List<String> strList = List.of("A", "B"); List<Integer> intList = List.of(1, 2); printList(strList); // 输出:A B printList(intList); // 输出:1 2 // 测试<? extends Number> List<Integer> integerList = List.of(1, 2, 3); List<Double> doubleList = List.of(1.5, 2.5); System.out.println("整数和:" + sum(integerList)); // 输出:6.0 System.out.println("小数和:" + sum(doubleList)); // 输出:4.0 // 测试<? super Integer> List<Number> numberList = new ArrayList<>(); addIntegers(numberList); // 向NumberList添加Integer System.out.println("添加后:" + numberList); // 输出:[10, 20] }}AI生成项目1.3 泛型的局限不能用基本类型:泛型参数只能是引用类型(如List<int>错误,需用List<Integer>)。运行时擦除:泛型信息在编译后被擦除(如List<String>和List<Integer>运行时都是List),无法通过instanceof判断泛型类型。静态方法不能用类的泛型参数:静态方法属于类,而泛型参数随对象变化,若需泛型需定义为泛型方法。二、反射(Reflection):运行时的类信息操作反射允许程序在运行时获取类的信息(如类名、属性、方法、构造器),并动态操作这些成分(如调用私有方法、修改私有属性)。这打破了 “编译时确定代码逻辑” 的限制,让程序更灵活(也是框架实现 “自动装配”“依赖注入” 的核心)。2.1 反射的核心作用运行时获取类信息:无需提前知道类名,就能获取类的属性、方法等元数据。动态创建对象:通过类信息动态实例化对象(如Class.newInstance())。动态调用方法:包括私有方法(通过反射可绕过访问权限)。动态操作属性:包括私有属性(可修改值)。2.2 反射的核心类反射的所有操作都基于java.lang.Class类(类对象),它是反射的 “入口”。核心类 / 接口 作用Class 类的元数据对象,代表一个类的信息Constructor 类的构造器对象,用于创建实例Method 类的方法对象,用于调用方法Field 类的属性对象,用于访问 / 修改属性值2.3 反射的基本操作2.3.1 获取 Class 对象(反射入口)获取Class对象有 3 种方式,根据场景选择:class Student { private String name; private int age; public Student() {} public Student(String name, int age) { this.name = name; this.age = age; } public void study() { System.out.println(name + "正在学习"); } private String getInfo() { return "姓名:" + name + ",年龄:" + age; }} public class GetClassDemo { public static void main(String[] args) throws ClassNotFoundException { // 方式1:通过对象.getClass()(已知对象) Student student = new Student(); Class<?> clazz1 = student.getClass(); System.out.println("方式1:" + clazz1.getName()); // 输出:Student // 方式2:通过类名.class(已知类名,编译时确定) Class<?> clazz2 = Student.class; System.out.println("方式2:" + clazz2.getName()); // 输出:Student // 方式3:通过Class.forName("全类名")(仅知类名,运行时动态获取,最常用) Class<?> clazz3 = Class.forName("Student"); // 全类名:包名+类名(此处默认无包) System.out.println("方式3:" + clazz3.getName()); // 输出:Student // 验证:同一个类的Class对象唯一 System.out.println(clazz1 == clazz2); // 输出:true System.out.println(clazz1 == clazz3); // 输出:true }}AI生成项目说明:一个类的Class对象在 JVM 中唯一,是类加载的产物(类加载时 JVM 自动创建Class对象)。2.3.2 反射创建对象(通过构造器)通过Class对象获取Constructor,再调用newInstance()创建实例(支持无参和有参构造)。import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException; public class ReflectNewInstance { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { // 1. 获取Class对象 Class<?> clazz = Class.forName("Student"); // 2. 方式1:调用无参构造(若类无无参构造,会抛异常) Student student1 = (Student) clazz.newInstance(); // 已过时,推荐用Constructor System.out.println("无参构造创建:" + student1); // 3. 方式2:调用有参构造(更灵活,推荐) // 获取有参构造器(参数为String和int) Constructor<?> constructor = clazz.getConstructor(String.class, int.class); // 传入参数创建实例 Student student2 = (Student) constructor.newInstance("张三", 18); System.out.println("有参构造创建:" + student2); }}AI生成项目2.3.3 反射调用方法(包括私有方法)通过Method对象调用方法,支持公有和私有方法(私有方法需先设置setAccessible(true)取消访问检查)。import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method; public class ReflectInvokeMethod { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Class<?> clazz = Class.forName("Student"); Student student = (Student) clazz.getConstructor(String.class, int.class).newInstance("张三", 18); // 1. 调用公有方法(study()) // 获取方法:参数1为方法名,参数2为参数类型(无参则不写) Method studyMethod = clazz.getMethod("study"); // 调用方法:参数1为实例对象,参数2为方法参数(无参则不写) studyMethod.invoke(student); // 输出:张三正在学习 // 2. 调用私有方法(getInfo()) // 获取私有方法需用getDeclaredMethod(getMethod只能获取公有) Method getInfoMethod = clazz.getDeclaredMethod("getInfo"); // 取消访问检查(关键:私有方法必须设置,否则抛异常) getInfoMethod.setAccessible(true); // 调用私有方法 String info = (String) getInfoMethod.invoke(student); System.out.println("私有方法返回:" + info); // 输出:姓名:张三,年龄:18 }}AI生成项目2.3.4 反射操作属性(包括私有属性)通过Field对象访问或修改属性,私有属性同样需要setAccessible(true)。import java.lang.reflect.Field; public class ReflectOperateField { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class<?> clazz = Class.forName("Student"); Student student = (Student) clazz.getConstructor().newInstance(); // 无参构造创建 // 1. 获取并修改私有属性name Field nameField = clazz.getDeclaredField("name"); // 获取私有属性 nameField.setAccessible(true); // 取消访问检查 nameField.set(student, "李四"); // 设置属性值(参数1为实例,参数2为值) // 2. 获取并修改私有属性age Field ageField = clazz.getDeclaredField("age"); ageField.setAccessible(true); ageField.set(student, 20); // 验证修改结果(调用之前的私有方法getInfo()) Method getInfoMethod = clazz.getDeclaredMethod("getInfo"); getInfoMethod.setAccessible(true); String info = (String) getInfoMethod.invoke(student); System.out.println("修改后信息:" + info); // 输出:姓名:李四,年龄:20 }}AI生成项目2.4 反射的应用场景框架底层:Spring 的 IOC 容器通过反射创建对象并注入依赖;MyBatis 通过反射将数据库结果集映射为 Java 对象。注解解析:自定义注解需配合反射获取注解标记的类 / 方法,执行对应逻辑(如权限校验)。动态代理:AOP 的动态代理(如 JDK 代理)基于反射实现方法增强。工具类:如 JSON 序列化工具(Jackson、FastJSON)通过反射获取对象属性并转为 JSON。2.5 反射的优缺点优点:灵活性高,支持运行时动态操作,是框架的核心技术。缺点:性能损耗:反射操作绕开编译优化,性能比直接调用低(但框架中影响可接受)。破坏封装:可直接访问私有成员,可能导致代码逻辑混乱。可读性差:反射代码较繁琐,不如直接调用直观。三、注解(Annotation):代码的标记与元数据注解(Annotation)是 Java 5 引入的特性,本质是 “代码的标记”,可在类、方法、属性等元素上添加,用于携带 “元数据”(描述数据的数据)。注解本身不直接影响代码逻辑,但可通过反射解析注解,执行对应操作。3.1 注解的核心作用标记代码:如@Override标记方法重写,编译器会校验是否符合重写规则。携带元数据:如@Test标记测试方法,测试框架会自动执行标记的方法。简化配置:替代 XML 配置(如 Spring 的@Controller标记控制器类)。3.2 常用内置注解Java 内置了 3 个基本注解(定义在java.lang包),编译器会识别并处理:注解名称 作用 使用位置@Override 标记方法为重写父类的方法,编译器校验 方法@Deprecated 标记元素(类、方法等)已过时,编译器警告 类、方法、属性等@SuppressWarnings 抑制编译器警告(如未使用变量警告) 类、方法等内置注解示例:public class BuiltInAnnotationDemo { // @Deprecated:标记方法已过时 @Deprecated public void oldMethod() { System.out.println("这是过时的方法"); } // @Override:标记方法重写(若父类无此方法,编译报错) @Override public String toString() { return "BuiltInAnnotationDemo对象"; } // @SuppressWarnings:抑制“未使用变量”警告 @SuppressWarnings("unused") public void test() { int unusedVar = 10; // 若没有@SuppressWarnings,编译器会警告“变量未使用” // 调用过时方法(编译器会警告,但可执行) oldMethod(); } public static void main(String[] args) { new BuiltInAnnotationDemo().test(); }}AI生成项目3.3 元注解:定义注解的注解自定义注解时,需要用 “元注解”(注解的注解)指定注解的 “作用范围”“保留策略” 等。Java 提供 4 个元注解(定义在java.lang.annotation包):元注解名称 作用 常用值@Target 指定注解可使用的位置(如方法、类) ElementType.METHOD(方法)、ElementType.TYPE(类)等@Retention 指定注解的保留策略(生命周期) RetentionPolicy.RUNTIME(运行时保留,可反射获取)@Documented 标记注解会被 javadoc 文档记录 无参数@Inherited 标记注解可被子类继承 无参数核心元注解:@Target和@Retention是自定义注解必须的 ——@Target限制使用位置,@Retention(RetentionPolicy.RUNTIME)确保注解在运行时存在(才能被反射解析)。3.4 自定义注解及解析自定义注解需配合反射使用:先定义注解,再在代码中标记,最后通过反射解析注解并执行逻辑。示例:自定义权限校验注解import java.lang.annotation.*;import java.lang.reflect.Method; // 1. 定义自定义注解@Target(ElementType.METHOD) // 注解只能用在方法上@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可反射获取@interface RequirePermission { // 注解属性(类似方法,可指定默认值) String value(); // 权限名称(如"admin")} // 2. 使用注解标记方法class UserService { // 标记需要"admin"权限才能执行 @RequirePermission("admin") public void deleteUser() { System.out.println("执行删除用户操作"); } // 标记需要"user"权限才能执行 @RequirePermission("user") public void queryUser() { System.out.println("执行查询用户操作"); }} // 3. 通过反射解析注解,实现权限校验class PermissionChecker { // 模拟当前用户拥有的权限 private String currentPermission = "admin"; // 执行方法前校验权限 public void executeWithCheck(Object obj, String methodName) throws Exception { // 获取方法对象 Method method = obj.getClass().getMethod(methodName); // 判断方法是否有@RequirePermission注解 if (method.isAnnotationPresent(RequirePermission.class)) { // 获取注解对象 RequirePermission annotation = method.getAnnotation(RequirePermission.class); // 获取注解的权限值 String requiredPerm = annotation.value(); // 校验权限 if (currentPermission.equals(requiredPerm)) { method.invoke(obj); // 权限通过,执行方法 } else { throw new RuntimeException("权限不足,需要:" + requiredPerm); } } else { // 无注解,直接执行 method.invoke(obj); } }} // 测试public class CustomAnnotationDemo { public static void main(String[] args) throws Exception { UserService userService = new UserService(); PermissionChecker checker = new PermissionChecker(); checker.executeWithCheck(userService, "deleteUser"); // 输出:执行删除用户操作(权限足够) checker.executeWithCheck(userService, "queryUser"); // 输出:执行查询用户操作(权限足够) }}AI生成项目解析流程:定义注解:用@Target和@Retention指定使用位置和生命周期。使用注解:在目标方法上添加注解,设置属性值。反射解析:通过method.isAnnotationPresent()判断是否有注解,method.getAnnotation()获取注解对象,进而获取属性值并执行逻辑。四、三者关系与总结泛型:编译时保障类型安全,避免强制转换,是编写健壮代码的基础。反射:运行时动态操作类信息,突破编译时限制,是框架灵活性的核心。注解:通过标记携带元数据,配合反射实现 “标记 - 解析 - 执行” 的逻辑,是简化配置的关键。三者结合构成了 Java 高级编程的基础 —— 泛型保证类型安全,反射提供动态能力,注解简化元数据携带,共同支撑了 Spring、MyBatis 等主流框架的设计。————————————————原文链接:https://blog.csdn.net/m0_64198455/article/details/149465961
-
Java 源代码文件,通常以 .java 扩展名结尾,包含了用 Java 编程语言编写的类定义和方法实现。为了将这些源代码转换成 JVM 可以理解和执行的格式,需要使用 Java 编译器( javac )进行编译。编译完成后,生成的 .class 文件包含了 JVM 可以理解的字节码。这些字节码文件可以在任何安装了 JVM 的系统上执行,这是 Java 语言“一次编写,到处运行”(Write Once, Run Anywhere,WORA)特性的基础。编译 javac运行java.java.class 字节码文件JVM数据类型数据类型基本数据类型引用数据类型整数浮点类型字符类型布尔类型数组 类 接口 字符串......数据类型关键字 内存占用 (字节) 范围byte 1 -128 到 127short 2 -32,768 到 32,767int 4 -2,147,483,648 到 2,147,483,647long 8 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807float 4 单精度 32 位 IEEE 754double 8 双精度 64 位 IEEE 754char 2 U+0000 到 U+FFFFboolean 1 true 或 false包装类只是针对基本的数据类型 对应的类类型类型转换的要点 在 Java 编程语言中,当进行算术运算时,较小的数据类型(如short和byte)会被隐式提升(upcast)到较大的数据类型(如int)以避免数据丢失。这种隐式类型转换被称为类型提升(type promotion)。然而,在将计算结果赋值回较小的数据类型变量时,需要进行显式类型转换(explicit type casting),以确保数据类型的兼容性和防止潜在的数据丢失。隐式类型提升(Type Promotion)在 Java 中,当不同类型的操作数参与算术运算时,较小的数据类型会自动转换为较大的数据类型。例如,当byte或short类型的变量与int类型的变量进行运算时,byte或short类型的变量会被提升为int类型。这种提升是自动的,不需要程序员显式进行类型转换。显式类型转换(Type Casting)在将运算结果赋值回较小的数据类型变量时,必须进行显式类型转换。这是因为,如果没有显式转换,较大的数据类型(如int)可能包含较小的数据类型(如byte或short)无法表示的值。直接赋值会导致编译器报错,因为这种操作可能会导致数据丢失。例如,如果一个int类型的变量存储的值超出了byte类型变量的表示范围,直接赋值将导致编译错误。为了解决这个问题,可以使用显式类型转换,如下所示:byte b = 10;int i = 200;i = i + b; // 自动类型提升:b 被提升为 int 类型b = (byte) i; // 显式类型转换:将 i 转换为 byte 类型AI生成项目java运行在这个例子中,b和i相加时,b被隐式提升为int类型。但是,当尝试将结果赋值回b时,必须显式地将i转换为byte类型,以确保结果在byte类型的范围内。数据丢失和类型安全(Data Loss and Type Safety)在进行显式类型转换时,必须小心处理可能的数据丢失。如果转换后的值超出了目标类型的表示范围,可能会导致数据丢失或不可预测的行为。因此,程序员有责任确保类型转换是安全的,并且不会导致数据丢失。编译器错误(Compiler Errors)如果不进行适当的类型转换,编译器会报错,因为 Java 是一种静态类型语言(statically-typed language),它要求在编译时就确定所有变量和表达式的类型。这种类型检查机制有助于在编译时捕获潜在的错误,从而提高程序的可靠性和稳定性。位运算和逻辑运算在Java中,位运算和逻辑运算是两种不同的运算类型,它们在底层对数据进行操作。以下是Java中支持的位运算符和逻辑运算符,以及如何使用它们的方法:逻辑运算符逻辑运算符用于执行逻辑比较,并返回布尔值(true或false)。• &&(逻辑与):当且仅当两个操作数都为true时,结果才为true。• ||(逻辑或):如果两个操作数中至少有一个为true,则结果为true。• !(逻辑非):反转操作数的布尔值。示例方法:位运算符位运算符对整数的二进制位进行操作。(黄色为简易口诀)• &(位与):对两个操作数的每一位执行逻辑与操作。有0则0,同1为1• |(位或):对两个操作数的每一位执行逻辑或操作。有1则1,同0为0• ^(位异或):对两个操作数的每一位执行逻辑异或操作。相同为0,不同为1• ~(位非):反转操作数的所有位。• <<(左移):将操作数的位向左移动指定的位数。• >>(右移):将操作数的位向右移动指定的位数。• >>>(无符号右移):将操作数的位向右移动指定的位数,左边用0填充。示例方法:public class BitwiseOperations { public static int bitwiseAnd(int a, int b) { return a & b; } public static int bitwiseOr(int a, int b) { return a | b; } public static int bitwiseXor(int a, int b) { return a ^ b; } public static int bitwiseNot(int a) { return ~a; } public static int leftShift(int a, int shift) { return a << shift; } public static int rightShift(int a, int shift) { return a >> shift; } public static int unsignedRightShift(int a, int shift) { return a >>> shift; } public static void main(String[] args) { System.out.println(bitwiseAnd(10, 7)); // 输出 2 System.out.println(bitwiseOr(10, 7)); // 输出 15 System.out.println(bitwiseXor(10, 7)); // 输出 13 System.out.println(bitwiseNot(10)); // 输出 -11 System.out.println(leftShift(10, 2)); // 输出 40 System.out.println(rightShift(10, 2)); // 输出 2 System.out.println(unsignedRightShift(-10, 2)); // 输出 1073741822 }}AI生成项目java运行在这些示例方法中,我们定义了各种位运算和逻辑运算,并在main方法中提供了测试用例。您可以根据需要调用这些方法来执行相应的运算。请注意,位运算符只能用于整数类型(byte、short、int、long),而逻辑运算符只能用于布尔类型(boolean)。没有 <<< (无符号符号左移[不存在]) 因为有无符号都是用右边用0来补充,<<<会增加复杂度,带来不必要的麻烦,故不存在。最后两个注意点最后要注意的两个点是三目运算符和String字符的拼接:1.三目运算符所产生的值需要接收,否则编译器会报错2.String拼接的时候的顺序是从左到右,32+12 +"HelloWorld" //结果是"44HelloWorld"AI生成项目1"HelloWorld"+32+12 //结果是"HelloWorld44"————————————————原文链接:https://blog.csdn.net/2501_92963994/article/details/150390125
-
一、Java概述 这些笔者认为是快速上手java的核心知识,是java和c++的核心区别。有了这些知识能很快理解java的各种设计。1、JDK使用java首先要下载的就是jdk(Java Development Kit),用于构建和运行Java应用程序的完整开发环境。jdk里面包括:JVM:虚拟机,java程序实际上的运行环境,也是java“一次编译,处处运行”的关键。核心类库:Java官方提供的标准库,包含了大量预定义的类和接口,供开发者调用。开发工具:javac(将Java源程序.java编译成字节码文件.class)、java(Java解释器,用于在JVM里运行编译后的Java程序。)“一次编译,处处运行” Java的一个核心特性,也是Java使用广泛的原因。由于java程序在JVM上运行,所以java程序可以在任何安装了有合适的JVM的平台上运行。2、Java是一门纯粹的面向对象的语言。2.1 mian()方法写在类中 与C++不同的是,c++程序的入口main函数可以作为全局的函数,不写在类中的,但是Java中所有函数(java里面称为“方法”)包括main方法都是写在一个类中的。使用intellJ IDEA运行某个类的时候,实际就是运行这个类里面定义的main方法;如果这个类没有定义main方法,则不能将这个类作为程序的入口点,这个类只能被其他代码调用。2.2 保存类信息的Class类 对于用户自定义的各种类和java官方提供的各种类,这些类的信息都是保存在一个Class类的实例对象中的。类的信息包括但不限于 类名、成员变量、构造器(即C++中的构造函数)、成员方法…… 也就是说,java官方提供了一个类,类名就是Class。程序执行的时候,对于用户自定义的每一个类,JVM将会Class实例,将这些类的信息保存到这个Class的实例中。 类似的,对于类里面的构造器,java官方也提供了一个Constructor类,对于每个构造器,会使用一个Constructor实例来保存这个构造器的信息(如这个构造器是否私有)。3、Java的数据类型java中所有类型分为两大类:基本数据类型(类型后面的数字代表该类型几字节)整型(byte 1、short 2、int 4、long 8)、浮点型(float 4、double 8)字符型(char 2)布尔型(boolean :true / false)注意:java中的char是无符号的;布尔类型和整型之间不能互相显示/隐式转换。关于boolean的大小,Java规范并没有明确规定它占用多少字节,通常认为它是1位(bit),但实际上在内存中可能是1字节或更多,具体取决于JVM实现。引用数据类型其余任何类型。包括但不限于类、字符串String(String的本质也是一个类)、数组当一个变量存储的是一个引用类型数据的时候,该变量保存的实则是该数据在内存中的“地址”而非其本身。所有引用类型都是Object类的后代。 注意:Java没有指针的概念4、数据的在内存中的存储每个运行的java程序的内存分为三大块:栈(局部变量)堆(引用数据类型存储的位置:eg类的对象实例、数组)方法区(已被虚拟机加载的类信息、常量、静态变量) 一个栗子public class A { int value; void fun() {...} static void sfun() {...}} public class Main { public static void main(String[] args) { A a = new A(); // step 1:创建A的实例a a.fun(); // step 2:调用实例方法fun() A.sfun(); // step 3:调用静态方法sfun() }}AI生成项目java运行当Main.main方法开始执行时,会在栈中创建一个新的栈帧,用于存储局部变量a。a是一个引用类型的变量,它存储的是指向堆中A实例的引用(即对象的内存地址)。new A() 创建了一个A的实例,并将其存储在堆中 。这个实例包含了成员变量value、A类在方法区中的位置。类A的信息(包括类名、成员变量、构造器、成员方法等)存储在方法区中。调用a.fun()的过程首先在栈中找到局部变量a,获取其值(即指向堆中A实例的引用)。根据a的值找到堆中对应的A实例。在堆中找到A实例对应的类信息(位于方法区),从而找到fun()方法的相关信息。将fun()方法的相关信息加载到栈上,并执行该方法。调用a.sfun()的过程直接在方法区中找到类A的静态方法sfun()。加载sfun()方法的相关信息到栈上,并执行该方法。二、C++到Java快速上手语法1、Java的项目结构初学者主要了解以下项目结构。初学者写的Hello,World就是新建一个class文件写的。projectmodel(一般开发一个单独的功能,比如微信的朋友圈模块)package(用于组织类文件,对于不同包,在后面类的访问性修饰符有影响)class(如果一个类文件里面只有一个类,这个文件名需要和类名保持完全一致,包括大小写)2、注释 相同点: // 单行注释 /* 多行注释 */ 不同点:文档注释(笔者认为对于初学者来说不是重点,不会影响后续学习。笔者对此的了解也甚是浅薄) Java 提供了一种专门用于生成文档的注释格式。有了这种注释格式,可以自动为编写的代码生成文档,这些文档通常以html文件的形式呈现。非常适合一些大型项目或者多人合作项目,方便查阅类和方法的作用。 文档注释以 /** 开始,并以 */ 结束。与普通注释不同,文档注释通常放置在类、接口、方法和字段声明之前。为了更好地描述代码,文档注释中可以使用一系列预定义的标签(tags),如“@author”。下面我将以一个栗子简单说明文档注释的用法:假如我有以下文件Example.java。//有“树树:”代表这是笔者写的文档注释,并没有实际含义 /** * 树树:这是一个简单的类,用于演示文档注释。 * * @author 张三 * @version 1.0 * @since 1.0 */public class Example { /** * 树树:一个简单的实例变量。 */ private int value; /** * 树树:构造函数,初始化实例变量。 * * @param initialValue 初始值 * @throws IllegalArgumentException 如果初始值小于0 */ public Example(int initialValue) throws IllegalArgumentException { if (initialValue < 0) { throw new IllegalArgumentException("Initial value cannot be negative"); } this.value = initialValue; } /** * 树树:获取实例变量的值。 * * @return 实例变量的值 * @see #setValue(int) */ public int getValue() { return value; } /** * 树树:设置实例变量的值。 * * @param newValue 新值 * @deprecated 使用 {@link #setValue(int)} 代替 */ @Deprecated public void setValue(int newValue) { this.value = newValue; } /** * 树树:设置实例变量的新值。 * * @param newValue 新值 */ public void setValueNew(int newValue) { this.value = newValue; }}AI生成项目java运行 使用命令为这个文件的文档注释生成文档(实则命令可以更复杂,为文档指定生成位置等,具体方法可以ai一下(。◕‿◕。)javadoc Example.javaAI生成项目bash 然后就能在对应文档生成位置找到文档。其实生成的文档是静态html网页资源 打开allclasses-index.html可以看见所有类的信息;点开index-all.html可以看见所有文档注释的内容。网页上方还提供了一个搜索框以便快速定位信息。 3、运算符大部分Java运算符的用法和功能都和C++一致,这里只说不同点。>>>C++没有该运算符。在 Java 中,>>> 是一个无符号右移运算符。它用于将一个整数的所有位向右移动指定的位数,并且在左侧填充0,无论该整数的最高位是0还是1。public class Main { public static void main(String[] args) { int signed = -8; // 11111111 11111111 11111111 11111000 int unsignedRightShift = signed >>> 2; // 00111111 11111111 11111111 11111110 System.out.println(unsignedRightShift); // 输出 1073741822 int signedRightShift = signed >> 2; // 11111111 11111111 11111111 11111110 System.out.println(signedRightShift); // 输出 -2 }}AI生成项目java运行Java不支持运算符重载4、分支结构switch1、Java支持的数据类型:byte、short、int、char、枚举(JDK5开始)、String(JDK7开始)2、case的值只能是字面量或者是编译时常量,而非表达式3、增强switch(JDK12开始),允许一个case匹配多个标签;引入符号“->”,使用该符号,每个case后面的代码块不用“break”也不会fall-through。int dayOfWeek = 2;switch (dayOfWeek) { case 1, 2, 3 -> System.out.println("Weekday"); case 6, 7 -> System.out.println("Weekend"); default -> System.out.println("Invalid day");}AI生成项目java运行try-catch-finall 和c++相比,java多了一个finally块。不论try块里面的程序有没有成功执行,finally块里面的代码都最总会执行。(当然不要finally块,只要try-catch也是可以的。)try { // 可能抛出异常的代码} catch (Exception e) { // 处理异常} finally { // 总是执行的代码}AI生成项目java运行5、数组一维数组 数组声明:如果只是声明数组变量,但是没有初始化,那此时这个数组变量的初始值是null,因为它没有指向任何对象。此时在内存中并没有为这个数组分配空间。int[] intArray; // 声明一个整数数组,此时intArray为nullAI生成项目java运行 数组初始化:必须指定数组内每个元素的值或者显示分配空间(new)。int[] arr={1,2,3};//初始化方式一:直接指定数组内每个元素的值int[] arr=new int[5]; //初始化方式二:先不指定数组内每个元素的值,但是分配了内存AI生成项目java运行 边界检查:Java中如果越界访问元素会抛出异常 length属性:数组本质上也是一个类,继承自Object。初始化一个数组就是创建了一个数组实例;声明一个数组就是创建一个“指向在堆中的数组实例”的引用变量。因此每个数组对象将会有Java提供的方法和属性。其中最常用的是length属性,用于获取数组的长度。int[] intArray = {1, 2, 3, 4, 5};System.out.println("Length of intArray: " + intArray.length); // 输出: 5AI生成项目java运行二维数组 普通二维数组的声明、使用和C++类似。//二维数组声明int[][] arrayName1; //方法一int arrayName2[][]; //方法二 //二维数组初始化//静态初始化int[][] arrayName3 = { {1, 2, ..., n}, {1, 2, ..., n}, // ...};//动态初始化int[][] arrayName4 = new int[3][4]; //使用int[0][0]=1;AI生成项目java运行 锯齿数组 Java中的二维数组本质是数组的数组,而每个数组都是一个引用对象。也就是说,二维数组本质上是一个一维数组,只不过这个数组里面的每个元素都是一个一维数组的地址(即一维数组的引用对象)。这意味着,和C++不同的是,Java中的二维数组不必每一行的元素都一样多。栗子//方法一:int[][] jaggedArray = new int[3][]; // 只指定行数jaggedArray[0] = new int[]{1, 2, 3}; // 第一行有3个元素jaggedArray[1] = new int[]{4, 5}; // 第二行有2个元素jaggedArray[2] = new int[]{6, 7, 8, 9}; // 第三行有4个元素 System.out.println("Length of first row: " + jaggedArray[0].length); // 输出: 3System.out.println("Length of second row: " + jaggedArray[1].length); // 输出: 2System.out.println("Length of third row: " + jaggedArray[2].length); // 输出: 4 //方法二:int[][] jaggedArray2 = { {1, 2}, {1, 2, 3,4}};AI生成项目java运行三、Java的面对对象类构造器 和C++一样,Java中每个类都有一个构造器(C++里称为构造函数)用于创建该类的实例。如果在自定义的类里面没有声明构造器,Java编译器将会自动生成一个无参构造器;如果有声明其他构造器(不论是有参还是无参),Java编译器将不会自动生成编译器。这意味着如果你同时需要无参构造器和有参构造器,你需要手动实现这两种构造器。 C++有但是Java不支持的:复制构造函数 在进行引用变量之间的相互赋值的时候,并不会创建新的实例,它们只是进行引用赋值析构函数 Java 没有析构函数的概念。Java 使用垃圾回收机制自动管理内存,当对象不再被引用时,垃圾回收器会在适当的时候回收其占用的内存。初始化列表 this关键字 C++中的this是一个指针,Java中this是一个引用变量,指向对象本身,它们的作用相似。可以使用this访问当前对象的属性或者方法,或者通过this()访问当前对象的构造器。栗子public class Rectangle { private double width; private double height; //使用this访问属性 public Rectangle(double width, double height) { this.width = width; this.height = height; } // 使用this()访问构造器: public Rectangle() { this(1.0, 1.0); }}AI生成项目java运行构造函数委托上面那个栗子中,在一个构造器中调用同一个类的另一个构造器,被称为构造函数委托。其中要注意的是this()调用其他构造器的代码需要放在第一行。static关键字修饰成员变量:与C++类似。该变量属于类而不是对象,所有对象共享同一个变量。可以通过类名或者对象名来访问该变量。修饰方法:与C++类似。该方法属于类而不是对象,所有对象共享。可以通过类名或者对象名来访问该方法。但是不能在静态方法中访问非静态成员。静态方法不能被override(重写),只能被隐藏。tip:工具类中的方法常常被声明为静态的,以便于使用工具类提供的功能。工具类的构造器通常被设计为私有的,这意味着理论上无法创建对应实例,因为工具类通常只是提供一个功能,创建实例对于工具类来说通常是不必要的。如Java官方提供的Math工具类提供各种数学运算方法:double result = Math.pow(2, 3); // 8.0AI生成项目java运行静态成员变量/方法 VS 非静态的成员变量/方法:访问前者只需要类名即可访问,而后者需要创建实例才能访问。静态代码块Java中的静态初始化块是C++中没有的,它在类加载时(注意不是创建对象时)执行,且只会执行一次。静态块通常用于复杂的静态变量初始化。public class MyClass { static int counter; static { // 静态初始化块 counter = 5; System.out.println("Static block executed"); }}AI生成项目java运行类的五大成分成员变量、构造器、方法、代码块、内部类 前三成分C++中也有我们较为熟悉,再次只着重说明后两部分。代码块代码块分为静态代码块和实例代码块。静态代码块就是上文提到的,会在类加载的时候执行一次钱。实例代码块 不含static的代码块,包括在 {} 中,在创建对象的时候执行一次,且先于构造器执行。public class MyClass { //实例代码块 { System.out.println("Instance block executed"); } public MyClass() { System.out.println("Constructor executed"); } public static void main(String[] args) { new MyClass(); // 输出: Instance block executed, Constructor executed new MyClass(); // 输出: Instance block executed, Constructor executed (每次创建对象时都会执行实例代码块) }}AI生成项目java运行内部类定义在类内部的类。分为四种:成员内部类、静态内部类、局部内部类、匿名内部类成员内部类定义在外部类内的非静态类,属于外部类的对象所持有。它可以直接访问外部类的所有成员(包括私有成员),并且可以通过外部类的对象来创建实例。public class OuterClass { private int outerField = 10; // 成员内部类 public class InnerClass { public void display() { System.out.println("Outer field value: " + outerField); // 可以直接访问外部类的成员 } } public static void main(String[] args) { OuterClass outer = new OuterClass(); OuterClass.InnerClass inner = outer.new InnerClass(); // 需要通过外部类对象创建内部类实例 inner.display(); // 输出: Outer field value: 10 }}AI生成项目java运行静态内部类也叫静态嵌套类,是使用 static 关键字修饰的内部类。它可以看作是外部类的一个静态成员,因此不能直接访问外部类的非静态成员(但可以访问静态成员);但可以通过类名来创建实例public class OuterClass { private static int staticOuterField = 20; private int nonStaticOuterField = 30; // 静态嵌套类 public static class StaticNestedClass { public void display() { System.out.println("Static outer field value: " + staticOuterField); // 可以访问静态成员 // System.out.println(nonStaticOuterField); // 错误:无法访问非静态成员 } } public static void main(String[] args) { // 创建静态嵌套类的实例 OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass(); nested.display(); // 输出: Static outer field value: 20 }}AI生成项目java运行局部内部类定义在方法或代码块中的类。它只能在该方法或代码块的作用域内使用,并且可以访问外部类的所有成员以及方法中的局部变量(前提是这些局部变量必须是 final 或者实际上是不可变的)。public class OuterClass { private int outerField = 40; public void someMethod() { final int localVar = 50; // 局部变量必须是 final 或者实际上是不可变的 // 局部内部类 class LocalInnerClass { public void display() { System.out.println("Outer field value: " + outerField); System.out.println("Local variable value: " + localVar); } } LocalInnerClass localInner = new LocalInnerClass(); localInner.display(); // 输出: Outer field value: 40, Local variable value: 50 } public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.someMethod(); }}AI生成项目java运行匿名内部类匿名内部类是没有名字的内部类,通常用于实现接口或扩展抽象类。它们在定义时立即实例化,并且通常只使用一次。// 定义一个接口interface Printable { void print();} public class OuterClass { public static void main(String[] args) { // 使用匿名内部类实现 Printable 接口 Printable printable = new Printable() { @Override public void print() { System.out.println("Printing from anonymous inner class"); } }; printable.print(); // 输出: Printing from anonymous inner class }}AI生成项目java运行继承语法通过extends关键字实现继承java中的继承都是公有的不支持多重继承//继承public class Father{ private int value; public void fun(){...}} public class Son extends Father{ //...}AI生成项目java运行通过关键字abstract来定义抽象类、抽象方法:和C++一样,Java中的类中如果有抽象方法(类似于C++的虚函数),该类就是抽象类。就算类中没有抽象方法,也可以使用abstract关键字将类声明为抽象类。抽象类不能被实例化,通常需要一个子类继承该抽象类并实现了所有抽象方法,该子类才能被实例化。public abstract class Animal { // 抽象方法 public abstract void makeSound(); // 普通方法 public void sleep() { System.out.println("The animal is sleeping."); }}AI生成项目java运行权限修饰符修饰符 同一包内的类 子孙类(包括其他包) 所有类 说明private default 默认情况下没有显式的修饰符。protected public Java中需要为每一个成员变量或者方法显示声明权限修饰符(除非使用默认权限default)方法重写@override 注解:写在方法的上一行,告诉编译器这是一个重写方法,编译器在编译阶段将会检查父类中是否有同名、同参的方法。子类重写父类方法时,重写的方法的访问权限应该≥父类对应方法;该方法的返回值的类型应该≤父类对应方法返回值。(返回值类型≤意思是该类型应该是父类方法对应返回值的类型或者其子孙类型)私有方法、静态方法不能被重写。重写toString()使用语句System.out.print(data)可以将变量输出到工作台上。对于基本数据类型和String,直接将变量打印到工作台上。对于引用数据类型,默认输出为类名@哈希码。其底层是调用Object的toString(),输出的是toString()方法的返回值。如果想自定义类的输出,应该重写类的toString()。栗子public class Person { private String name; private int age; @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } ...}AI生成项目java运行子类构造器调用父类构造器子类构造器在执行之前会先调用父类构造器。因此如果父类只有一个构造器,且这个构造器私有的话会报错,通常认为这个父类并不能被继承子类构造器在默认情况下会在代码第一行自动调用super()。但是如果父类没有无参构造器,子类构造器内的第一行要手动super(参数列表)class Parent { public Parent(int value) { // ... }} class Child extends Parent { public Child() { super(10); // 必须显式调用父类的有参构造器 //... }}AI生成项目java运行多态概述java中的多态强调对象多态、行为多态,但不强调成员变量多态。java中常常使用多态,即声明一个父类的引用类型数据,但是其指向的具体实现是子类。多态的好处:右边的真实对象和左边声明的引用类型是解耦合的。定义父类类型的形参,可以接受一切其子孙类型作为实参。使得代码的拓展性更强 class Animal { public void makeSound() { System.out.println("Animal makes a sound"); }} class Dog extends Animal { @Override public void makeSound() { System.out.println("Dog barks"); }} class Cat : public Animal {public: void makeSound() const override { std::cout << "Cat meows" << std::endl; }}; public class Main { public static void main(String[] args) { //在这个例子中,myAnimal 是 Animal 类型的引用,但它实际上指向的是 Dog 类型的对象。 //即使 Dog 类的具体实现发生变化,只要 makeSound() 方法的签名保持不变, //main 方法中的代码不需要修改。 Animal myAnimal = new Dog(); // 父类引用指向子类对象 myAnimal.makeSound(); // 输出: Dog barks //在这个例子中,makeAnimalSound 函数接受一个 Animal* 类型的参数, //它可以接受任何 Animal 的派生类对象。 makeAnimalSound(new Dog()); // 输出: Dog barks makeAnimalSound(new Cat()); // 输出: Cat meows }}AI生成项目java运行注意:多态情况下声明的引用类型,不能调用对应子类实例独有的功能,只能调用声明类型有的功能。原因涉及多态的底层实现逻辑↓多态实现的底层逻辑方法表:JVM加载类的时候,为每一个类创建一个方法表,该表记录了该类每个方法的入口。子类会继承父类的方法表,如果子类Override了父类的某个方法,其方法表上对应的方法的入口也会重载。编译时类型检查:java源码在编译的时候会进行类型检查。可以认为,实现多态的时候,编译器只认一个引用变量声明时的类型而不在乎其具体引用对象的类型。它会检查这个引用变量调用的方法是否属于父类的方法,如果调用子类的独有方法编译器会认为类型不匹配而报错。动态绑定:程序运行的时候,在调用一个引用类型的方法的时候,实际上查找的时实例对应的类型的方法表。以此实现多态。多态下的类型转换向上转换(自动) 向上转型是安全的,因为子类对象一定是父类类型。Animal myAnimal = new Dog(); // 向上转型,Dog 对象被当作 Animal 类型AI生成项目java运行向下转换(手动 / 显示转换) 只有引用类型指向的对象确实是转换之后的类型才能转换,否则会抛出类型转换异常Animal myAnimal = new Dog();Dog myDog = (Dog) myAnimal; // 向下转型,将 Animal 引用转换为 Dog 类型//如果myAnimal指向的类型是Cat会在运行时抛出类型转换的异常。AI生成项目java运行final关键字该关键字可以理解为最后一次被定义。修饰类,该类不能被继承(工具类可以考虑使用final修饰)修饰方法,该方法不能被重写修饰变量,该变量仅能在声明时被赋值一次。如果修饰的是引用类型变量,该变量指向别的实例对象,但是指向的实例对象本身的内容时可以改变的。(类似C++的指针,指针指向的位置不变,但是指针指向位置上的内容是可以改变的)常量static finalJava中称被static final修饰的变量为常量。常量的标识符一般使用大写加下划线。(eg. MAX_VALUE)Java中,编译时能确定值的常量将会在.class文件中被替换字面量(即内联)。因此将常用的配置信息定义成一个常量包,即便于管理又不会影响性能。抽象(abstract关键字)被abstract修饰的类是抽象类,不能被实例化,只有其非抽象的子孙类能实例化;被abstract修饰的方法是抽象方法,无需具体实现。抽象类中不一定有抽象方法;有抽象方法的类一定是抽象类。// 定义一个抽象类abstract class Animal { // 抽象方法,没有方法体 abstract void makeSound(); // 普通方法 void sleep() { System.out.println("This animal is sleeping."); }}// 定义一个子类,继承抽象类class Dog extends Animal { // 实现抽象方法 @Override void makeSound() { System.out.println("Woof! Woof!"); }}// 测试类public class Main { public static void main(String[] args) { // 创建子类对象 Dog myDog = new Dog(); // 调用实现的方法 myDog.makeSound(); // 输出: Woof! Woof! // 调用继承的普通方法 myDog.sleep(); // 输出: This animal is sleeping. // 以下代码会报错,因为抽象类不能被实例化 // Animal myAnimal = new Animal(); }}AI生成项目java运行*接口(interface)接口可以被类继承,且一个类可以继承多个接口,这弥补了Java中类与类之间不能多继承的缺陷。接口不能被实例化如果一个类继承了某个接口并且实现了该接口里定义的所有抽象方法,则称这个类为该接口的实现类;否则该类需要被设计成抽象类JDK8前的传统接口只能有常量成员变量和抽象方法。public interface Animal { // 抽象方法:方法默认为公有抽象的;即自动加上关键字public abstract void makeSound(); // 常量字段:成员变量默认为公有常量,且只能是常量 //即自动加上关键字 public final static String KINGDOM = "Animalia";}AI生成项目java运行JDK8后的接口JDK8后的接口(新增三种方法)默认方法(需要加关键字 default):即普通方法,可以被实现类继承、重写。但是不能直接通过接口名调用;只能通过实现类或者其对象调用。私有方法(需要加关键字private):只能由接口中的其他实例方法(有方法体的方法)调用。静态方法(需要关键字static):默认为public的,属于接口的方法,可以直接通过接口名调用。实现类可以通过隐藏(Hide)的方式重载方法(类似C++的重载,需要参数列表不同)public interface Animal { // 抽象方法: void makeSound(); // 默认方法:带有默认实现的方法,可以被实现类继承或重写 default void sleep() { System.out.println("Animal is sleeping"); } // 私有方法:只能在接口内部调用,用于辅助其他方法 private void performAction(String action) { System.out.println("Performing: " + action); } // 另一个默认方法,使用私有方法 default void eat() { performAction("eating"); } // 静态方法:属于接口本身,可以通过接口名直接调用 static void displayKingdom() { System.out.println("Kingdom: Animalia"); }}AI生成项目java运行接口的使用继承接口的类称为该接口的实现类(使用关键字 implement),实现类必须要实现该接口的所有抽象方法;否则需要将该类定义为抽象类。实现类public class Dog implements Animal { @Override public void makeSound() { System.out.println("Dog barks"); } // 重写默认方法 @Override public void sleep() { System.out.println("Dog is sleeping"); }}AI生成项目java运行一个类实现多个接口,需要实现该接口定义的所有抽象方法(除非把这个类定义为抽象类)public interface Swimmable { void swim();} public interface Flyable { void fly();} public class Duck implements Swimmable, Flyable { @Override public void swim() { System.out.println("Duck is swimming"); } @Override public void fly() { System.out.println("Duck is flying"); }}AI生成项目java运行使用接口的实现类public class Main { public static void main(String[] args) { // 创建 Dog 对象并通过 Animal 接口引用 Animal myDog = new Dog(); myDog.makeSound(); // 输出: Dog barks myDog.sleep(); // 输出: Dog is sleeping (重写了默认方法) myDog.eat(); // 输出: Performing: eating (使用了私有方法) // 创建 Cat 对象并通过 Animal 接口引用 Animal myCat = new Cat(); myCat.makeSound(); // 输出: Cat meows myCat.sleep(); // 输出: Animal is sleeping (使用默认方法) myCat.eat(); // 输出: Performing: eating (使用了私有方法) // 调用静态方法 Animal.displayKingdom(); // 输出: Kingdom: Animalia }}AI生成项目java运行该例子中使用Animal的引用类型来承接该接口的实现类实例,这种写法在Java中常用,尤其在使用Java提供的API时经常使用。使用接口的好处多态性:eg. myDog 和 myCat 都是 Animal 类型的引用,但它们分别指向了 Dog 和 Cat 的实例。在一个方法中接受 Animal 类型的参数,而无需关心传入的具体实现类是什么。public void animalSound(Animal animal) { animal.makeSound();} // 调用animalSound(new Dog()); // 输出: Dog barksanimalSound(new Cat()); // 输出: Cat meowsAI生成项目java运行解耦合增强可扩展性强制实现规范提高代码复用性其他注意事项一个类继承了父类和接口,如果父类和接口中有同名方法(包括参数列表相同),优先使用父类。如果一个类继承了多个接口,这些接口中存在冲突的同名默认方法,需要重写该方法。四、注解这部分内容涉及反射机制。也就是说,注解的底层实现借助保存类信息的特殊的class类(前文提到)关于反射机制这篇文章没有提到,因为反射和注解都属于java高级的内容。初学者暂时跳过并无大碍。但是注解是java中十分重要的内容,专业的java开发人员必须掌握这部分内容。java中的特殊标记,如常见注解@Test、@Override。其作用是让其他程序根据注解信息来决定如何执行该程序。注解可以用在类上、构造器上、方法上、成员变量上、参数上等位置。 自定义注解注意:属性名后面要带括号!因为虽然它被称为属性,但在实现上,注解是官方接口Annotation的实现类。每次使用接口的时候都会创建该注解的类的一个实例。public @interface 注解名{ public 属性类型 属性名() default 默认值;}AI生成项目java运行自定义注解的使用方法下面通过一个例子来说明注解的使用方法:假如定义了以下注解://MyBook.javapublic @interface MyBook { String name(); int age() default 18; String[] address();}AI生成项目java运行则该注解的使用为//BookDemo.java@MyBook(name = "小王子",address = {"B612星球", "地球"})public class BookDemo { //...}AI生成项目java运行属性age有默认值,因此使用该注解的时候可以不给age赋值。特殊属性value在 Java 注解里,value 是一个特殊的预定义属性名。如果注解里仅有一个value属性,那么在使用注解的时候可以不标明属性名。【例子】假如有以下注解public @interface Test { String value(); // 唯一属性}AI生成项目java运行在使用该注解的时候可以省略属性名@Test("测试用例") // 最简洁的语法public void myTestMethod() {}AI生成项目java运行如果某个注解里除了value属性之外,其他属性独有默认值,在使用该注解的时候也可以使用简略语法。【例子】public @interface MyAnnotation { String value(); // 无默认值,必须显式赋值 int id() default 0; // 有默认值 String[] tags() default {}; // 有默认值} // 正确使用:@MyAnnotation("简化语法") // 等价于 @MyAnnotation(value = "简化语法")public class MyClass {} // 也可以显式指定其他属性:@MyAnnotation(value = "完整语法", id = 123)public class MyClass2 {}AI生成项目java运行元注解用来注解注解的注解。官方提供了5中内置的元注解。以下两种是常用的元注解@Target:声明被修饰的注解只能在哪些地方使用@Retention:声明注解的保留周期【例子】下面是@Target的使用//MyAnnotation1.java@Target(ElementType.METHOD) // 限制注解仅能用于方法public @interface MyAnnotation1 { // 注解定义} //MyAnnotation2.java//@Target注解接受的是一个列表,可以选择多个枚举类型@Target({ElementType.METHOD,ElementType.PARAMETER}) // 限制注解仅能用于方法和参数public @interface MyAnnotation2 { // 注解定义}AI生成项目java运行注解的解析简单来说注解的解析就是分析一个类,或者类里面的一个方法,或者……上面有没有标注注解,并把注解里的信息拿出来分析的过程。既然要分析类的信息,势必要借助反射机制来实现。下面用一个例子说明注解的解析假设有如下自定义注解和自定义的类//MyBook.java//假如有以下自定义注解public @interface MyBook { String name(); int age() default 18; String[] address();} //BookInfo.java// 使用注解@MyBook(name = "小王子", address = {"B612星球", "地球"})class BookInfo { // ...}AI生成项目java运行解析注解 ps:不了解反射机制的话,只需要简单理解成每一个类可以通过Class类(一个Java官方定义的类)获取这个类Class类的实例保存的就是自定义类的类信息。每一个自定义方法都可以通过一个Method类来获取这个方法的信息,每一个Method类的实例保存的就是这个自定义方法的信息。// AnnotationParser.javaclass AnnotationParser { public static void main(String[] args) { //通过class类获取BookInfo类的信息 Class<BookInfo> bookInfoClass = BookInfo.class; //如果BookInfo类被MyBook注解 if (bookInfoClass.isAnnotationPresent(MyBook.class)) { //将注解内容取出,做后续操作 MyBook myBookAnnotation = bookInfoClass.getAnnotation(MyBook.class); System.out.println("书名: " + myBookAnnotation.name()); System.out.println("年龄: " + myBookAnnotation.age()); System.out.print("地址: "); for (String address : myBookAnnotation.address()) { System.out.print(address + " "); } } }}AI生成项目java运行五 java常用apiCollection 集合集合体系:MapCollectionList 接口(元素有序、可重复、有索引)ArrayListLinkedListSet 接口(元素无序、不重复、无索引)TreeSetHashSetLinkedHashSet(有序不重复、无索引) 集合的遍历方法一:迭代器方法二:增强for方法三 :Lambdaaction是匿名内部类对象,forEach()将会对每个元素,调用action内唯一的抽象方法accept()。遍历时引发的并发删除异常增强for和Lambda内部都是使用迭代器实现。如果在遍历的时候删除会引发异常。解决方法:for下标遍历:正序遍历,删除之后i--倒序遍历迭代器遍历删除的时候用iterator.remove()迭代器remove()的特殊性迭代器的remove()方法是专为遍历中修改设计的,它会: 确保删除操作与迭代器的状态同步(更新expectedModCount);处理索引偏移问题(如删除元素后,后续元素的索引会前移,迭代器会正确跳过)。ListArrayList VS LinkedList由于LinkedList首尾操作极快,因此很适合用于设计queue或者stack。Set 集合HashSet扩容机制:当 元素个数>数组长度*加载因子 时,数组双倍扩容 java重写equal的时候也会重写hashcode为什么?如果两个对象通过equals()比较相等,那么它们的hashCode()必须返回相同的值。如果两个对象的hashCode()不同,它们一定不相等(但hashCode()相同不保证对象相等,可能存在哈希冲突)。LinkedHashSetTreeSet自定义排序规则 方法二:例子 Map 集合常用方法 Map 遍历方式一:键找值例子:map是上文定义好的一个HashMap集合。 Map 遍历方法二:键值对entrySet()方法,底层遍历每个键值对,并把每个键值对包装成一个entry对象,最后将这些对象放进一个set里面。Map 遍历方式三:LambdaBiConsumer是匿名内部类对象,forEach()将会对每个元素,调用BiConsumer内唯一的抽象方法accept()。forEach内部通过方法二遍历。不用Lambda表达式也可以这样写: HashMap 底层原理并发编程线程创建方法一:这种方式需要创建一个类继承Thread类,并且重写其run()方法。run()方法中的代码就是线程要执行的任务。之后创建该类的实例并调用start()方法,线程便会启动。注意必须是调用start()才是开启子线程,如果调用run()相当于还是单线程。class MyThread extends Thread { // 重写run方法,定义线程执行的任务 @Override public void run() { System.out.println("继承Thread类创建的线程正在执行"); }} public class ThreadCreationExample { public static void main(String[] args) { // 创建线程实例 MyThread thread = new MyThread(); // 启动线程 thread.start(); }}AI生成项目java运行方法二:实现Runnable接口。class MyRunnable implements Runnable { // 实现run方法,定义线程执行的任务 @Override public void run() { System.out.println("实现Runnable接口创建的线程正在执行"); }} public class RunnableExample { public static void main(String[] args) { // 创建Runnable实现类的实例 MyRunnable runnable = new MyRunnable(); // 将runnable实例传递给Thread类的构造函数 Thread thread = new Thread(runnable); // 启动线程 thread.start(); }}AI生成项目java运行该方式可以使用 匿名内部类 / 函数式编程 来简化语句。public class AnonymousRunnableExample { public static void main(String[] args) { // 创建匿名内部类实现Runnable接口,并直接传递给Thread构造函数 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("使用匿名内部类创建的线程正在执行"); } }); //使用函数式编程 Thread thread = new Thread(()->{ System.out.println("使用函数式编程创建的线程正在执行"); }); // 启动线程 thread.start(); }}AI生成项目java运行方式三:实现callable接口并使用futureTask该接口是一个泛型接口,泛型的类型要和需要返回的返回值类型相同。import java.util.concurrent.*; class MyCallable implements Callable<String> { // 实现call方法,定义线程执行的任务,有返回值 @Override public String call() throws Exception { return "实现Callable接口创建的线程执行完成"; }} public class CallableExample { public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建Callable实现类的实例 MyCallable callable = new MyCallable(); // 使用FutureTask包装Callable对象 FutureTask<String> futureTask = new FutureTask<>(callable); // 将futureTask传递给Thread类的构造函数 Thread thread = new Thread(futureTask); // 启动线程 thread.start(); // 获取线程执行的返回结果 String result = futureTask.get(); System.out.println(result); }}AI生成项目java运行仅仅方法三有返回值,因为前两种重写的run()方法固定没有返回值。使用futureTask的get()方法获取返回值的时候,如果此时线程还没有执行完成,主线程会让出CPU直到线程执行完毕。Thread 的常用方法线程安全的方式同步代码块:synchronized{}把访问共享资源的核心代码上锁。synchronized(锁对象) { // 需要同步的核心代码(访问共享资源的代码)}AI生成项目java运行锁对象:可以是任意 Java 对象(通常使用 this 或类的静态实例),用于标识哪个线程可以执行该代码块。如果两个线程获取的锁对象是相同的,这两个线程只有一个能够进入该同步代码块;如果两个线程获取的锁对象不同,这两个线程能够同时进入该同步代码块。作用:当一个线程进入同步代码块时,会先获取锁对象的锁,执行完代码后释放锁。其他线程必须等待锁被释放后才能获取锁并执行代码块。同步方法Lock 锁:ReentrantLock使用具体的锁对象来实现上锁解锁。 public class LockDemo { private final ReentrantLock lock = new ReentrantLock(); private int sharedResource = 0; public void fun() { // 执行不涉及共享资源的功能(无需同步) lock.lock(); try { // 只对访问共享资源的代码加锁 sharedResource++; } catch (Exception e) { // 处理异常(例如记录日志) e.printStackTrace(); } finally { lock.unlock(); } // 执行其他不涉及共享资源的功能(无需同步) }}AI生成项目java运行线程池重复利用线程的技术。工作原理 方式一:通过ThreadPoolExecutor 创建runnable对象任务:import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { // 1. 配置参数 int corePoolSize = 3; // 正式工3人 int maxPoolSize = 5; // 最多招5人(3正式+2临时) long keepAliveTime = 60; // 临时工空闲60秒就裁 TimeUnit unit = TimeUnit.SECONDS; // 时间单位:秒 // 任务队列:最多存10个任务(超出则触发拒绝策略) BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10); // 线程工厂:自定义线程名(方便调试) ThreadFactory threadFactory = r -> new Thread(r, "MyPool-Thread-"); // 拒绝策略:任务满了,让调用者自己执行(同步执行) RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); // 2. 创建线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler ); // 3. 提交任务(模拟业务) for (int i = 0; i < 20; i++) { int taskId = i; executor.execute(() -> { System.out.println( Thread.currentThread().getName() + " 处理任务:" + taskId ); // 模拟任务耗时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }); } // 4. 关闭线程池(业务结束后调用) executor.shutdown(); }}AI生成项目java运行创建callable对象任务:public class CallableExample { public static void main(String[] args) { // 创建固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(3); // 创建并提交多个Callable任务 Future<Integer> future1 = executor.submit(new FactorialTask(5)); Future<Integer> future2 = executor.submit(new FactorialTask(7)); Future<Integer> future3 = executor.submit(new FactorialTask(10)); // 获取任务结果 try { System.out.println("5! = " + future1.get()); // 输出: 120 System.out.println("7! = " + future2.get()); // 输出: 5040 System.out.println("10! = " + future3.get()); // 输出: 3628800 } catch (InterruptedException | ExecutionException e) { System.err.println("任务执行异常: " + e.getMessage()); } finally { // 关闭线程池 executor.shutdown(); } }} // 实现Callable接口的任务类(计算阶乘)class FactorialTask implements Callable<Integer> { private final int number; public FactorialTask(int number) { this.number = number; } @Override public Integer call() throws Exception { /.... return result; }}AI生成项目java运行 方式二:通过Executors工具类 其他lombok库(为类自动生成getter方法)这个库的主要功能是提供了一些很有用的注解。@Getter / @Setter:自动生成字段的 Getter 和 Setter 方法。@ToString:自动生成 toString() 方法。@EqualsAndHashCode:自动生成 equals() 和 hashCode() 方法。@Data:组合注解,包含 @Getter、@Setter、@ToString、@EqualsAndHashCode 等功能。@NoArgsConstructor:生成无参构造函数。@AllArgsConstructor:生成全参构造函数。@RequiredArgsConstructor:生成包含 final 字段或 @NonNull 字段的构造函数。Getter / Setter 方法在实际的开发项目中,通常需要为类中的每个成员变量提供共有的get方法(用于获取该成员变量的值)和公有的set方法(用于修改该成员变量的值),然后将这些成员变量设为私有的。这么做是为了隐藏实现细节:类的内部实现可以随时更改,而不会影响使用该类的代码。控制访问权限:通过 Getter 和 Setter 方法,可以控制字段的读写权限。数据验证:在 Setter 方法中可以添加逻辑,确保数据的有效性。虽然 Getter 和 Setter 方法很重要,但手动编写它们会增加代码量。Lombok 库通过注解自动生成这些方法,从而简化代码。栗子源文件:使用@Getter @Setter注解import lombok.Getter;import lombok.Setter; @Getter@Setterpublic class User { private String name; private int age;}AI生成项目java运行编译成的.class文件:会自动生成每个成员变量的get方法和set方法public class User { private String name; private int age; // Getter for name public String getName() { return this.name; } // Setter for name public void setName(String name) { this.name = name; } // Getter for age public int getAge() { return this.age; } // Setter for age public void setAge(int age) { this.age = age; }————————————————原文链接:https://blog.csdn.net/2301_76391858/article/details/146043230
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签