作为一名出身于二本院校的程序员,我的Java语法基础主要来源于大学时期的教材。然而,近期我在阅读一些前沿书籍时,意外发现了许多新颖的语法技巧和应用。为了记录这一学习过程,并与同行分享我的心得,特此撰写此篇。
提起运算符,大多人的脑海里第一联想到的是加减乘除,有逻辑电路基础的,会想起||,&&等符号。笔者在算法的学习中,看到了一些神奇的用法。
位运算,常见的就是与(&)和或(|)。Java 语言中,他们用来做多种条件(结果为boolean值)的组合判断。如果两边的类型是int类型,就会产生一些奇妙的效果。比如用下面的方式判断奇偶:
n & 1 // 偶数:0。奇数:1
在现代计算机中,位运算与加减运算相当,但对比乘除与取模,还是要快上一些。
排序时想要交换两个数字,又不申请额外空间?试试如下操作:
int a = 34;
int b = 67;
a = a ^ b; // a = 97
b = a ^ b; // b = 34
a = a ^ b; // a = 67
当然,位运算的成员还有很多,异或(^)左移(<<)右移(>>)等。你会在HashMap#hash 方法中见到妙用。
while 是一个很常用的循环语句,工作中常常用break,continue来处理一些中断。Java 中还提供了另一种,见下例:
outer:
while (true) {
// doSomething
while(true) {
// doSomething
if (2 > 1) break outer;
}
}
代码第 1 行定义了一个outer
的label,第二层循环的break后面跟这这个label。代码运行到这里,不仅不会报错,命中条件后,还会回到定义的位置继续执行。
第一次遇到以为是什么新特性,在官网查了半天,原来在初始版本就拥有该特性。至于为什么被人遗忘,一是场景较少,使用有局限。二是多层循环很难理解。
JDK23 的JEP 455,JDK21的JEP 441,以及之前的n多预览版,都可以看到switch的身影。Java核心技术卷的描述也变了。先看一段示例:
在Java中,JEP是Java Enhancement Proposal(Java增强提案)的缩写。JEP是一种用于提议改进Java平台的新特性和功能的文档。这些提案由Java平台的开发者、用户和社区成员提出,并经过Java平台的维护者和领导者进行审查和批准。
String key = "a";
String realKey = switch(key) {
case "a" -> "a+";
case "b" -> "b+";
case "c" -> "c+";
case "d" -> "d+";
default -> "S";
};
根据官方描述(JEP325)这种语法,是模仿C/C++的,叫fall-through semantics (直通语义)。而经常使用的switch现在被叫做fall-through variant(直通式变体),这下成“守旧派”了。
结合后面提到的Record类,又有了新用法。
record Person(String name, int age) {}
class SwitchExample {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
switch (person) {
case Person p when p.age() < 18:
System.out.println("未成年人");
break;
case Person p when p.age() >= 18 && p.age() < 60:
System.out.println("成年人");
break;
case Person p when p.age() >= 60:
System.out.println("老年人");
break;
default:
System.out.println("未知");
}
}
}
还有针对对象类型的JEP406,和上面类似,这里就不展开介绍。值得注意的是,代码还是需要手动判断是否为null,否者会抛出NPE。
在写AI应用时,会遇到多行文本的情况,在以前的情况下,会采用手动加入换行符来实现。而在Java 13的JEP355中,可以这样书写:
// HTML代码
String html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
// SQL
String query = """
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
WHERE `CITY` = 'INDIANAPOLIS'
ORDER BY `EMP_ID`, `LAST_NAME`;
""";
如果你只是书写上换行,而不是真正的换行,结尾可以添加\
。不过还存在一个问题,如果想动态的拼接参数,还需要自己实现替换。官方在这个特性中对比了其他语言:
Java 集众家特性得到如下的语法:
String name = "name";
String phone = "1111";
String address = "address";
String json = STR."""
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
""";
System.out.println(json);
更详细的用法可以查看java.lang.StringTemplate
提出是在JDK14,正式引入是在16,以下是对正式版的解读。 一个自带get方法的类,这听起来就像是像是引入了lombok。除了上述的方法,它还自带:
正如这个词的意思一样,记录,非常适合作为DTO来使用。我在Spring AI的Ollama包下,也发现了它
org.springframework.ai.ollama.api.OllamaApi#Message
/**
* Chat message object.
*
* @param role The role of the message of type {@link Role}.
* @param content The content of the message.
* @param images The list of base64-encoded images to send with the message.
* Requires multimodal models such as llava or bakllava.
*/
@JsonInclude(Include.NON_NULL)
public record Message(
@JsonProperty("role") Role role,
@JsonProperty("content") String content,
@JsonProperty("images") List<String> images,
@JsonProperty("tool_calls") List<ToolCall> toolCalls) {
这些配置类希望一开始就设置好属性,不允许再更改,Record也非常适合。
除了不可变之外,相较于普通类还有些特性:
还有一点,取得属性时,比如name,请使用name(),而不是getName()
如果Spring项目尝试过从JDK 8 升级到JDK 17,可能会遇到module-info.java的相关报错,一般是升级相关依赖就好,那么怎样理解这个类呢。参考JDK 9的JEP261与Java 核心技术卷。
Spring Boot 3支持的最低版本是JDK 17。
众所周知,封装,继承,多态是面向对象的三大特性。在Java类与类中,封装体现在访问修饰符上,比如Public,Private等。这些是为了单个工程间的隔离,如果在多模块(module)下,自带的限制,比如public,可能不想那么”publicer“。于是就有了这么一个模块化配置。
在JDK 9以后的更新中,官方又根据此特性,将以前的宽松封装替换为了强封装。比如下方的图例中,明显的jre没了(实际上放到了lib下)
具体module-info里包含了什么呢?以java.sql举例:
// 定义一个名为java.sql的模块,该模块包含Java数据库连接(JDBC)的核心API。
module java.sql {
// 需要java.logging模块,并且是传递性的,意味着任何依赖java.sql的模块也需要java.logging。
requires transitive java.logging;
// 需要java.transaction.xa模块,并且是传递性的,用于支持XA事务。
requires transitive java.transaction.xa;
// 需要java.xml模块,并且是传递性的,用于处理XML数据。
requires transitive java.xml;
// 导出java.sql包,使得其他模块可以使用这个包中的公共类和接口。
exports java.sql;
// 导出javax.sql包,提供额外的与数据库交互的接口和类。
exports javax.sql;
// 声明java.sql.Driver为服务提供者接口,允许第三方实现自己的数据库驱动。
uses java.sql.Driver;
}
JDK 21刚问世的时候,做过一次解读,那时以为向量是为数学运算服务。现在看来,对也不对。
面对LLM的幻觉问题,RAG技术诞生了。如果想要用得好,向量数据库作为存储服务,起到了很大的作用。向量的作用不仅如此,这里不过多展开。
向量的关键字为Vector,这和原链表Vector同名。向量API包路径为jdk.incubator.vector
具体用法见下代码:
import jdk.incubator.vector.VectorSpecies;
import jdk.incubator.vector.FloatVector;
public class MethodDemo {
// 定义一个静态常量SPECIES,表示浮点数向量的种类
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
/**
* 对输入的三个浮点数数组进行向量计算。
*
* @param a 第一个浮点数数组
* @param b 第二个浮点数数组
* @param c 结果数组,存储计算后的值
*/
void vectorComputation(float[] a, float[] b, float[] c) {
int i = 0;
// 计算循环的上限,确保不会超出数组a的长度
int upperBound = SPECIES.loopBound(a.length);
// 使用SIMD指令集并行处理数组a和b的前部分
for (; i < upperBound; i += SPECIES.length()) {
// 从数组a和b中创建FloatVector对象va和vb
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
// 计算va的平方加上vb的平方,然后取负值,结果存储在vc中
var vc = va.mul(va)
.add(vb.mul(vb))
.neg();
// 将vc的值存入数组c的相应位置
vc.intoArray(c, i);
}
// 处理剩余的数组元素,这些元素无法使用SIMD指令集并行处理
for (; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
}
代码主要来自官方文档,补全了报包引入和注释。旧版本的Eclipse无法自动引入。
笔者Eclipse版本为2023-12,JDK21版本是2023年9月19正式发布。
可以借助AI,比如和元宝说
解读 【link】link为你要阅读的网页
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。