JAVA基础之十-不常用但是又希望能看懂的关键字/保留字
对于绝大部分JAVA工程师而言,大部分的关键字也是能够看懂的,但还是相当一部分比较不常见的关键字,妨碍了代码阅读。
本文力图收集一些个人认为在CRUD机械工作中可能比较少见的一些关键字/保留字。
此类关键字主要用于修饰方法和类。
收集过程会持续一段时间,现在暂时没有时间也没有必要找出个人为人需要整理的,只会遇到就更新一下。
一、概述
需要注意的是,不同的版本中,关键字包含的内容不是一样的。
大体而言:
- 随着版本的发展,关键字越来越多
- 增长的幅度不定,有的版本还是相当克制的,甚至可能有些版本没有出现新的关键字;有的版本会新增许多
- 大部分的关键字,在java1.0的时候已经确定下来
如果想知道具体有哪些,最好阅读官方的文档,地址是:https://docs.oracle.com/javase/specs/index.html
当前有从6~23有关版本的文档。
我们看下几个关键版本的关键字差别
JAVA8
50个。
JAVA17
67个,注意下划线也算,比J8多了很多
JAVA23
68个,比J17多了一个when
可以看到,不同的版本,有差异。
本文主要讨论截止JAVA23的部分关键字:
- assert
- default
- transient
- strictfp
- volatile
- native
- exports
- opens
- requires
- yield
- module
- permites
- sealed
- var
- provides
- to
- when
除了这些语言中的关键字(保留字),我们还会在javaDoc中看到许多专业词语,虽然使用翻译软件能够解决一些问题,但毫无疑问
如果能够直接阅读无疑能提高效率,并避免翻译过程中的曲解问题。
二、关键字细节
2.1、assert
释义:断言;生效;维护自己的权利(或权威);坚持自己的主张;明确肯定;表现坚定
说明:这个东西主要是为了辅助开发调试用的-如果条件不成立那么会终端运行。
用法:
a. assert 布尔表达式:条件不成立后的警告
b.需要在运行时候设置vm选项 -ea
示例:
assert 1!=1:"1等于1";
这个东西有助于开发调试。
2.2、default
释义:默认
说明:
a.default关键字用于接口,从J9开始出现的,允许定义一个默认的实现
b.在注解中为属性提供默认值
示例:
例一、接口默认函数
/** * 接口特性示例,包含了j7~j17 */ public interface ITool {/** * @apiNote 收起来 * 这是默认方法 * 实现类可以覆盖,也可以不覆盖。 * 可以调用其它四种类型的方法 * 如果每个子类实现的都是一样的,就可以用这个。这样可以节约代码,也好维护 * @since J8 */ default void putAway() { //调用 private static //decay(); //调用 private //flash(); //调用 public abstract //repair(); //调用 public static //show(); } default void putAway(String msg) { } }
例二、注解
@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = {ValidMessage.class}) @Documented public @interface ValidIsPositive { String message() default ""; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
2.3、transient
释义:短暂的;临时的;转瞬即逝的;暂住的;过往的;倏忽;临时工;过往旅客;暂住某地的人;
说明:申明某个字段/属性不能被序列化(指的是java自身,非JSON系列化)
对象序列化的时候,部分属性不需要/无法序列化,通过这个标记告诉序列化操作,不要处理。
和JACKSON等序列化比较,java的默认序列化主要用于内部交流和网络传输,重点在于内部。
而后者强调展示和跨程序交流。
示例:
package study.base.serialize; import java.io.*; public class SerializeTest implements Serializable { private static final long serialVersionUID = 1L; private String name; // 这个字段不会被序列化 private transient String password; public SerializeTest(String name, String password) { this.name = name; this.password = password; } @Override public String toString() { return "{" + "name='" + name + '\'' + ", password='" + (password != null ? "******" : "null") + '\'' + '}'; } public static void main(String[] args) { SerializeTest test = new SerializeTest("中国❤🔫", "secretPassword"); String fileName="D:\\temp\\learn\\user.dat"; // 序列化对象 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) { oos.writeObject(test); } catch (IOException e) { e.printStackTrace(); } // 反序列化对象 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) { SerializeTest dTest = (SerializeTest) ois.readObject(); System.out.println(dTest); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
输出:{name='中国❤🔫', password='null'}
2.4、strictfp
释义:严格的浮点计算要求标识
说明:
用于确保浮点运算的精确性和可移植性。它主要用在类、接口或方法声明中,以指示编译器和运行时环境必须严格遵守 IEEE 754 浮点算术标准
IEEE 754标准的具体内容略。
注意:Java17之后,这个关键字已经没有什么意义,至少eclipse是这么提示的:
Floating-point expressions are always strictly evaluated from source level 17. Keyword 'strictfp' is not required.
示例:
/** * strictfp 测试 */ public class Teststrictfp { strictfp double add(double a, double b) { return a + b; } double add2(double a, double b) { return a + b; } public static void main(String[] args) { Teststrictfp calc = new Teststrictfp(); double result = calc.add(0.1, 0.2); System.out.println(result); // 输出将严格遵循IEEE 754标准 double resultNostrict =calc.add2(0.1, 0.2); System.out.println(resultNostrict); } }
2.5、volatile
释义:英[ˈvɒlətaɪl]
不稳定的;<计>易失的;易挥发的,易发散的;爆发性的,爆炸性的;易变的,无定性的,无常性的;短暂的..
说明:
参考资料巨多,只摘选一些。
https://mp.weixin.qq.com/s/Oa3tcfAFO9IgsbE22C5TEg
这篇文章说了个大概,也算清楚,只不过尚有些微瑕疵。
说了造成不一致的原因:cpu存储和主存同步导致的,这是因为线程需要把变量先复制到cpu高速存储中,再找个时机同步回来。
说了解决方案:cpu(intel)有mesi协议;JAVA 则是volatile通知各个编译器禁止指令重排(
读:在读后插入两个指令,形如
v读
LoadLoad
LoadStore
写:在写先后插入两个指令形如
StoreStore
v写
StoreStore
)
指令的作用,就是除非cpu的保护协议,以便达成最终的一致性。
另外记住几个原文列出的重点:
- volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如booleanflag;或者作为触发器,实现轻量级同步。
- volatile属性的读写操作都是无锁的,它不能替代synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁_上,所以说它是低成本的。
- volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。
- volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见,volatile属性不会被线程缓存,始终从主 存中读取。
- volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作。
- volatile可以使得long和double的赋值是原子的。
- volatile可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性
另外一个地方说得也挺详细的:https://zhuanlan.zhihu.com/p/652920156,这篇虽然没有什么图,但说的内容更多一些。
还有https://www.cnblogs.com/dayanjing/p/9954562.html
在java的文档 java language specification 标准版本8的8.3.1.4章节中有很简单的需求描述(规范就是这样的)
volatile属于java多线程,相对难于理解的概念,因为涉及到CPU,JMM,一致性,可见性、指令重排等几个概念。
示例:
public class CorrectExample { private static volatile boolean flag = false; public static void main(String[] args) throws InterruptedException { Thread writer = new Thread(() -> { try { // 模拟一些工作 Thread.sleep(1000); flag = true; System.out.println("Flag is set to true"); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread reader = new Thread(() -> { while (!flag) { //其它代码 } System.out.println("Flag is true, exiting reader thread"); }); reader.start(); writer.start(); writer.join(); reader.join(); } }
以上一段代码,如果flag不加volatile,那么预计是陷入死循环的,加了之后就不会。测试的确如此。
需要特别注意的是:
1.如果上文代码的flag没有添加关键字volatile也可以不陷入死循环。所以并不是说不加这个就一定会陷入死循环.
注意,不是指”//其它代码“中加入一些人工跳出的,例如break,throw之类的。
2.线程的一些操作会导致volatile加不加都一样(特定条件下)
编写多线程的时候,需要特别注意,务必需要多加测试。 很多时候测试环境不当,会得到错误的结果。
2.6、native
释义:英[ˈneɪtɪv].本地的;土著的;当地的;土著人的;出生地的;天赋的;原产于某地的;儿时居住地的;
说明:
native关键字用于标识方法,表示当前方法是由操作系统来实现。
主要用途是调用外部的dll/so等动态链接库,主要目的是提升性能(我们都知道java很慢的)或者复用或者增加反编译的难度
在JVM中,有个Native method stack(本地方法栈)用于支持这个。
有个博友写了一个很好的例子,链接:https://www.cnblogs.com/ysocean/p/8476933.html
示例:
略。
2.7、exports与requires、module、opens、uses、provides
释义:
三者和模块有关,exports,requires,module分别表示导出,引入、模块
opens,uses,provides则用于定义模块的使用方式。
说明:
各个语言看起来都差不多,用于需要暴露给其它模块的内容。例如js(export)。意思都差不多。
java的module是J9之后才有的东西,用的起来比JS的复杂。,它主要解决几个问题:
- 强封装 -- 避免public 类可以任意被应用,同时减轻使用包的人的负担
- 可靠的配置 -- 对使用者而言,能看到的少,就能够减少错误,会变得更加可靠
- 可扩展的平台
当我们引用一个jar包的时候,需要考虑这个问题。
初初看了下,不怎么好下结论。
虽然没有module前,大家也活得好好的,以后也能够活得很好。但不能在这里下一个关于module好坏的结论,
以及我们现在编码应该要普及module。
示例:
module是新的东西,毕竟现在还由大把人的用J8,无论是固执还是其它原因,本人用了J17不久,暂时没有使用module的经验。
暂无.
模块的概念比较复杂,具体可以参见个人示例: java基础之十二、module
2.8、yield
释义:英[jiːld]
n. 产量;产出;利润;
v. 产生(收益、效益等);屈服;提供;(受压)活动,变形,弯曲,折断;让步;放弃;出产(作物);给(大路上的车辆)让路;缴出;
说明:
a.用于线程
在Java中,yield
关键字是用于在并发编程中,特别是在实现自定义的 Runnable
或 Thread
类时,指示当前线程愿意放弃对处理器的使用。
它主要用在 Thread
类的 run
方法或者实现了 Runnable
接口的 run
方法中。当线程调用 Thread.yield()
方法时,它告诉调度器当前线程愿意让出CPU资源,让其他具有相同优先级的线程运行。
不过,调度器可以忽略这个提示,继续运行当前线程。yield
是一种提示,而不是一种强制命令。
根据这种特性,可以把yield放在一些不是特别紧要的线程中,至于是否紧要,则完全由工程师自行判断。
和sleep类似,可以参见 https://www.cnblogs.com/xiaoyh/p/17092288.html
b.用于switch语句
表示返回一个值,且具有break的效果;
public void pirntDayNameYield(Day day) { // yield关键字可以在返回的时候,不要再输入break关键字。 int numLetters = switch (day) { case MONDAY,FRIDAY,SUNDAY: yield 60; case TUESDAY: yield 7; case THURSDAY,SATURDAY: yield 8; case WEDNESDAY: yield 9; default: throw new IllegalStateException("Invalid day: " + day); }; System.out.println(numLetters); }
示例:
要加就是一个语句,但效果不容易体现出来
略。
2.9、permits、sealed
释义:permit,seal分别表示允许和密封的意思
说明:
此二关键字用于java的密封类,这是java15提出,java17完善的一个新特性
可以参考 https://www.cnblogs.com/lzfhope/p/18369699/java_oop 关于密封类的说明。
sealed表示某个类是密封类,而permits指定了可以集成的子类。
只能说,这是在某个比较罕见的场景下,才会考虑的东西。
在大部分场景中,工程师不会去考虑某个类不能被谁继承,只有工程师比较有空的时候,才会考虑这个。
示例:
public sealed class SealedMan permits SonOfSealedMan { public void think3TimesOneDay() { } }
2.10、var
释义:变量
说明:
一种在方法体内使用的,定义变量的方式,但又一些限制(至少j17是这样的):
1.不需要指定类型,但又需要赋予初始值,不能是Null
2.不允许改变一个var变量的类型-- 这是因为java在没有大概的情况下,本就不支持重复定义一个变量。
示例:
public class VarTest { public static void main(String[] args) { var orgs=new ArrayList<Organisms>(); Human oa=new Human("a"); orgs.add(oa); Human ob=new Human("b"); orgs.add(ob); for(Organisms item:orgs) { System.out.println(item); } } }
个人不是太习惯,至少在很长的一段时间内不会考虑用这个,这个时间大概可以3年5年。关键不确定这样会不会产生一些不必要的麻烦。
个人不太理解jcp的想法。从目前来看,jcp在java中引入了许多类似js的语法,这些很有一些出于工程考量。但是我不认为适合java这样相对严谨的语言。
当然除了java,也有不少语言也有类似的。
例如rust允许利用类型推定来确定变量的实际类型,当然它在推送类型后也不能修改类型,但rust允许重新定义类型。
let cat="小白"; println!("{}",cat); let cat=90; println!("{}",cat);
python则和js差不多,可以随意折腾同一个名称的变量,而且都是需要重新定义,只需要重复赋值即可。
gg="1000"; print(gg); gg=99; print(gg);
2.11、to
释义: 到
说明:
暂无找到有关资料
示例:
2.12、when
释义:当什么时候
说明:
暂无找到有关资料
示例:
三、小结
- java的关键字总体还是比较少,至少官方收录的比较少
- 有许多网上的资料可以参考。从w3cschool学习java也是不错的注意,Java volatile Keyword (w3schools.com)
- 看一些源码,也许完整意思不要懂,但是关键字,语法要先看懂。否则非常容易出现吃力情况