Java: 基本语法

Java基本语法

包管理

包的声明

  • Java包的功能类似C++的命名空间,减少类名冲突;但它并不是随意声明和引用的:
    • 包名必须是当前文件/目录的父目录相对于项目根目录的路径,用.运算符表明父子关系 例如,若.为根目录,当前文件的路径为./test/math,则声明如下:

      1
      package test.math;
    • 一个文件除非直接在项目文件夹下,否则必须声明包,且必须在文件开头(在import前)

  • 一个.java文件只能有一个public类或接口,且类名必须和文件名相同
  • 不同.java文件在同一个包体内,指的是它们处于同一个目录下,声明的package路径相同
  • 命名规范:所有包名均应小写

外部包的引用

  • 引用外部包的类或接口需要使用import关键字,用于减少包名冗余导致的可读性下降

    1
    2
    import pkg1.pkg2.MyClass;     // 可快捷使用MyClass
    import pkg1.*; // 可快捷使用pkg1下的所有公有类或接口, 但不包含子包下的公有类或接口
  • 引用外部包的类的静态成员或静态方法时需要使用import static

    1
    import static java.lang.Math.*;
  • 实践时,引入外部包类等情形不推荐使用通配符*,引用静态成员等可酌情使用,推荐按以下顺序引入:

    1
    2
    3
    4
    5
    6
    7
    import java.util.?;               // 先引入java标准库

    import org.springframework.?; // 再引入java扩展的第三方库, 按照依赖顺序, 每个不同的库之间隔一空行

    import jakarta.persistence.?; // 第三方库

    import dlut.?; // 最后引入自己开发的模块
  • 实践时,推荐使用格式化插件

javac命令

  • javac用于编译.java文件,生成可被JVM执行的.class文件

  • 最常用的编译命令如下(多个文件使用:分隔,Windows下是;):

    1
    2
    # -d(destination)参数表示保留文件的包路径信息,并生成到目标目录下
    javac -d 目标目录 待编译的.java文件
  • 其它参数:-encoding,指定文件编码

  • 实践中,推荐使用IDE插件提供的脚本服务自动编译

java命令

  • java用于运行.class文件

  • 最常用的命令如下:

    1
    2
    3
    4
    # -cp(classpath)参数标明`JVM`应从哪些路径下查找
    # 主类名必须包含`main`,链接由JVM在运行时进行,被链接的类同样是在指定的目录下查找
    # 注意主类名不是文件名,不能包含`.class`
    java -cp 目录 主类名
  • javamain函数模板:

    1
    2
    3
    public class MainClass {
    public static void main(String[] args) {}
    }

jar命令

  • jar命令与tar命令极其相似,用于归档

  • 常用参数如下:

    • -vverbose,显示归档/解压详情
    • -ccreate,创建归档文件
    • -ffile,指明归档文件名
    • -uupdate,更新归档文件
    • -xextract,解压归档文件
  • 常用语句如下:

    1
    2
    3
    jar -cf a.jar 类列表
    jar -uf a.jar 新加入类列表
    jar -xf a.jar 解压a.jar
  • 归档后,可以通过java -cp xxx.jar MainClass运行主类MainClass,所有需要链接的类都在归档文件中找到

关键字及声明型语句

访问控制修饰符

  • 本节不讨论内部类

  • java有四个访问控制权限,分别对应public、protected、default、private四个修饰符

  • public:可以修饰类、接口、方法、属性,表示所有包可访问,是最常用的修饰符

  • protected:可以修饰方法、属性,表示同包内均可访问,外部包中仅子孙类可通过this(自己或子孙类的对象)访问,例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package a;
    import b.A; // 假设含有protected void methodA() {}方法
    class B extends A {
    void func() {
    methodA(); // 正确
    A a = new A();
    a.methodA(); // 错误
    }
    }
  • default:实际上不存在这个关键字,当类或方法或属性没有显式地声明访问控制修饰符时,含有default权限,常称为包级私有,仅包内所有类可访问

  • private:可以修饰方法、属性,仅类内可访问

static、abstract、final、record

  • static:声明静态对象或方法或内部类,和cpp类似

  • abstract:声明抽象方法或抽象类,和cppvirtual定义类似

  • final

    • 修饰类时:表明该类不能被继承
    • 修饰方法时:表明该方法不能被重写
    • 修饰变量时:声明常变量,注意只是限制在栈中的值不能改变
  • abstract不能和finalprivatestatic同时出现,因为没有意义

  • final不能修饰接口,因为没有意义

  • 修饰符的顺序对程序没有影响,但规范是先是访问控制修饰符,然后是其它修饰符

  • final修饰引用对象时只能限制该变量不能改变指向,但没有限制用户通过该变量改变其指向的实例

    因为final修饰方法只代表该方法不能被重写,所以做不到cpp那样用一个类型声明可变或不可变对象

    java中,一个类要么是可变类,要么是不可变类,不可变类的所有属性都被final修饰且不提供setter方法,例如String就是典型的不可变类,不要把实例方法和引用赋值搞混:

    1
    2
    3
    4
    String s = "";    // 引用赋值
    s = s.trim(); // 实际上是改变了s的指向使其指向s.trim()的返回值,而没有改变原来指向堆中的实例
    StringBuilder sb = new StringBuilder(""); // 引用赋值
    sb.append("a"); // StringBuilder是一个可变类, append()修改了sb指向的实例的属性
  • 重写后的方法的访问控制权不能比父类的该方法低

  • record关键字可用于修饰类,表示一个类是不可变类,自动添加final等修饰符、自动实现equals(), hashCode()方法、自动实现getter方法

synchronized、transient、volatile、sealed、permits

  • synchronized用于同步

  • transient修饰表示无法被序列化

  • volatile修饰表示每次读写都对主存读写,但仍不能保证原子性

  • sealedpermitsJDK 17及以上提供的继承修饰符,与final相区分,sealed修饰的类只能被其permits的类继承

    1
    2
    public sealed SuperClass permits SubClass {}
    class SubClass extends SuperClass {} // 允许

标识符声明

  • 声明变量:仅支持用=初始化新变量,且需要指明类型

    1
    String obj = "";
  • 声明类:java没有函数,所有函数体都在类内,抽象类不能被实例化

    1
    modifiers class ClassName {ClassBody}  // 不需要';'
  • 声明方法:抽象方法所属类必须为抽象类

    1
    modifiers returnType methodName(paramList) {methodBody}
  • 声明接口:java提供所谓接口,是一系列抽象方法的集合,是其它模块访问你编写的模块的入口,编译时会自动在接口的所有方法前加上public修饰符

    1
    2
    3
    4
    public interface InterfaceName {
    // 接口内的方法默认为public,且没有实现
    returnType methodName(paramList);
    }
  • 声明一个实现了某个接口的类,一个类可以实现多个接口:

    1
    2
    3
    public class ClassName implements InterfaceName {
    // 必须重写所有该接口的方法
    }
  • 声明一个继承了某个类的类,或一个继承了某个接口的接口,只能单继承:

    1
    2
    public class ClassName extends SuperClass {}
    public interface InterfaceName extends SuperInterface {}
  • extends应该在implements之前

标识符规范

  • 包名规范:应全小写
  • 变量名和方法名规范:构成标识符的所有单词,首单词全小写,其余单词仅首字母大写
  • 类名和接口名规范:构成标识符的所有单词首字母均大写
  • 常量名规范:应全大写

protected权限的解释

  • 在引入类(链接在.class文件中的字节码)时,JVM会加载类/接口的元数据进方法区,并构建方法表来区分不同的访问控制域
  • 在创建对象时,JVM会申请一段内存,并创建对象的方法表,用于单独存储所有非静态成员 类、对象的方法表是不同的
  • 每个类都会维护一个引用,指向允许访问的类或对象的方法表
    • 这个引用能指向所有同一包的类及其对象、所有引入的外包类的方法表
    • 能指向,同时符合访问修饰符的范围,才能正常调用
  • 因此protected是极其特殊的:
    • 包外子类不能通过基类的对象访问保护型方法,因为包外的子类不能指向对象的方法表
    • 包外子类可以通过基类访问保护型静态方法,因为包外子类能指向类的方法表,同时符合protected的范围
    • 包内子类可以通过基类及其对象访问保护型方法,因为包内类能指向类及其对象的方法表,同时符合protected的范围

java的语法糖

  • java不支持运算符重载,仅有部分特殊的类含有类似的语法糖(String支持+拼接,但没有[]运算符)

  • java不支持默认参数

  • java支持函数重载,重载要求方法名相同但参数列表不同

  • java支持变长参数,java变长参数的语法如下:

    1
    2
    // paramList可以是零或多个String参数
    void method(String... paramList) {}
  • java不支持多继承

  • java不支持常方法,cpp支持用const修复一个方法保证不能被非常量调用

  • javacpp均支持类型自动推断,在cpp中是auto而在java中是var

  • javaType[]整体表示一个数组类型,元素的类型为指定的Type

结构型语句

  • if-else if-else
  • where-continue-break
  • for(;;)-continue-break
  • for(type e : c)语法糖
  • try-catch-finally
  • try-with-resource
  • switch-case-break
  • switch-case -> {yield}