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的部分关键字:

  1. assert
  2. default
  3. transient
  4. strictfp
  5. volatile
  6. native
  7. exports
  8. opens
  9. requires
  10. yield
  11. module
  12. permites
  13. sealed
  14. var
  15. provides
  16. to
  17. 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的保护协议,以便达成最终的一致性。

另外记住几个原文列出的重点:

  1. volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如booleanflag;或者作为触发器,实现轻量级同步。
  2. volatile属性的读写操作都是无锁的,它不能替代synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁_上,所以说它是低成本的。
  3. volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。
  4. volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见,volatile属性不会被线程缓存,始终从主 存中读取。
  5. volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作。
  6. volatile可以使得long和double的赋值是原子的。
  7. 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 关键字是用于在并发编程中,特别是在实现自定义的 RunnableThread 类时,指示当前线程愿意放弃对处理器的使用。

它主要用在 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)
  • 看一些源码,也许完整意思不要懂,但是关键字,语法要先看懂。否则非常容易出现吃力情况