Java HelloWorld

一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作:

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • 类:类是一个模板,它描述一类对象的行为和状态。
  • 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
  • 实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello World");
  }
}Copy to clipboardErrorCopied

基本语法

一个完整的 Java,源程序应该包括下列部分:

  • package 语句,该部分至多只有一句,必须放在源程序的第一句。由于 Java 编译器为每个类生成一个字节码文件,且文件名与类名相同因此同名的类有可能发生冲突。为了解决这一问题,Java 提供包来管理类名空间,包实 提供了一种命名机制和可见性限制机制。
  • import 语句,该部分可以有若干 import 语句或者没有,必须放在所有的类定义之前。
  • public classDefinition,公共类定义部分,至多只有一个公共类的定义,Java 语言规定该 Java 源程序的文件名必须与该公共类名完全一致。
  • classDefinition,类定义部分,可以有 0 个或者多个类定义。
  • interfaceDefinition,接口定义部分,可以有 0 个或者多个接口定义。

典型的例子如下所示:

package javawork.helloworld;
/*把编译生成的所有.class文件放到包javawork.helloworld中*/
import java awt.*;
//告诉编译器本程序中用到系统的AWT包
import javawork.newcentury;
/*告诉编译器本程序中用到用户自定义的包javawork.newcentury*/
 public class HelloWorldApp{...}
/*公共类HelloWorldApp的定义,名字与文件名相同*/
class TheFirstClass{...};
//第一个普通类TheFirstClass的定义
interface TheFirstInterface{......}
/*定义一个接口TheFirstInterface*/Copy to clipboardErrorCopied

Java 的主方法入口:所有的 Java 程序由 public static void main(String []args) 方法开始执行。值得注意的是,Java 是大小写敏感的,这就意味着标识符 Hello 与 hello 是不同的。对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如 MyFirstJavaClass。所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java。(如果文件名和类名不相同则会导致编译错误)。

源文件声明规则

当在一个源文件中定义多个类,并且还有 import 语句和 package 语句时,要特别注意这些规则。

  • 一个源文件中只能有一个 public 类
  • 一个源文件可以有多个非 public 类
  • 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为 Employee.java。
  • 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
  • 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
  • import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

类有若干种访问级别,并且类也分不同的类型:抽象类和 final 类等。除了上面提到的几种类型,Java 还有一些特殊的类,如:内部类、匿名类。这里还值得讨论的是,为什么 Java 文件中只允许包含一个 public 类呢?因为 Java 程序是从一个 public 类的 main 函数开始执行的,(其实是 main 线程),就像 C 程序 是从 main() 函数开始执行一样。只能有一个 public 类是为了给类装载器提供方便。一个 public 类只能定义在以它的类名为文件名的文件中。

每个编译单元(文件)都只有一个 public 类。因为每个编译单元都只能有一个公共接口,用 public 类来表现。该接口可以按照要求包含众多的支持包访问权限的类。如果有一个以上的 public 类,编译器就会报错。并且 public 类的名称必须与文件名相同(严格区分大小写)。当然一个编译单元内也可以没有 public 类。

标识符

Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。关于 Java 标识符,有以下几点需要注意:

  • 所有的标识符都应该以字母(A-Z 或者 a-z),美元符($)、或者下划线(_)开始
  • 首字符之后可以是字母(A-Z 或者 a-z),美元符($)、下划线(_)或数字的任何字符组合
  • 关键字不能用作标识符
  • 标识符是大小写敏感的
  • 合法标识符举例:age、\$salary、_value、__1_value
  • 非法标识符举例:123abc、-salary

修饰符

像其他语言一样,Java 可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:

  • 访问控制修饰符: default, public, protected, private
  • 非访问控制修饰符: final, abstract, static, synchronized

修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:

public class ClassName {
   // ...
}
private boolean myFlag;
static final double weeks = 9.5;
protected static final int BOXWIDTH = 42;
public static void main(String[] arguments) {
   // 方法体
}Copy to clipboardErrorCopied

关键字

类别 关键字 说明
访问控制 private 私有的
protected 受保护的
public 公共的
类、方法和变量修饰符 abstract 声明抽象
class
extends 扩充,继承
final 最终值,不可改变的
implements 实现(接口)
interface 接口
native 本地,原生方法(非 Java 实现)
new 新,创建
static 静态
strictfp 严格,精准
synchronized 线程,同步
transient 短暂
volatile 易失
程序控制语句 break 跳出循环
case 定义一个值以供 switch 选择
continue 继续
default 默认
do 运行
else 否则
for 循环
if 如果
instanceof 实例
return 返回
switch 根据值选择执行
while 循环
错误处理 assert 断言表达式是否为真
catch 捕捉异常
finally 有没有异常都执行
throw 抛出一个异常对象
throws 声明一个异常可能被抛出
try 捕获异常
包相关 import 引入
package
基本类型 boolean 布尔型
byte 字节型
char 字符型
double 双精度浮点
float 单精度浮点
int 整型
long 长整型
short 短整型
变量引用 super 父类,超类
this 本类
void 无返回值
保留关键字 goto 是关键字,但不能使用
const 是关键字,但不能使用
null

我们可以通过以下的案例来大概看下核心的语法:

// To starts, run jshell --enable-preview which is a program able to interpret Java syntax
// then cut and paste the following lines to see how it works
// To exit jshell type /exit
// # A record is a user defined type
// here Light is defined as containing two components: a color (typed as a String) and
// an intensity (typed as a 64 bits floating number double)
record Light(String color, double intensity) {}
// In Java, there is strong division between primitive types like double that are written in lower case and
// objects like String or Light that have a name that starts with an uppercase letter.
// A primitive type is stored as value while an object is stored as
// a reference (the address of the object in memory)
// In Java, `var` create a new variable
var maxIntensity = 1.0;   // it's a value
var colorName = "black";  // it's a reference to String somewhere in memory
// you can also indicate the type instead of `var`
// if you are using var, you are asking the compiler to find the type for you
String colorName = "black";
// System.out.println()
// To print a value in Java we have a weird incantation `System.out.println()` that we will detail later
System.out.println(maxIntensity);
// Primitive types and objects can be printed using the same incantation
// We will see later its exact meaning
System.out.println(colorName);
// ## Concatenation with +
// If we want to print a text followed by a value, we use the operator `+`
System.out.println("the value of colorName is " + colorName);
// To create an object in memory, we use the operator `new` followed by the value of each record components
// the following instruction create a Light with "blue" as color and 1.0 as intensity
var blueLight = new Light("blue", 1.0);
// To interact with an object in Java, we use methods, that are functions attached to an object.
// to call a method, we use the operator `.` followed by the name of the method and its arguments
// A record automatically declares methods to access its components so Light declares two methods
// color() and intensity()
// By example to get the intensity of the object blueLight
var blueLightIntensity = blueLight.intensity();
System.out.println(blueLightIntensity);
// ## toString()
// By default a record knows how to transform itself into a String
// in Java, the method to transform an object to a String is named toString()
System.out.println(blueLight.toString());
// In fact, println() calls toString() if the argument is an object
// so when using println(), calling explicitly toString() is not necessary
System.out.println(blueLight);
// Let's create another Light
var redLight = new Light("red", 1.0);
// ## equals()
// In Java, you can ask if two objects are equals, using the method equals(Object)
// the return value is a boolean (a primitive type that is either true or false)
System.out.println(blueLight.equals(redLight));
// Let's create another red light
var anotherRedLight = new Light("red", 1.0);
System.out.println(redLight.equals(anotherRedLight));
// ## hashCode()
// You can also ask have an integer summary (a hash) of any object
// This is used to speed up data structures (hash table)
// Two objects that are equals() must have the same hashCode()
System.out.println(redLight.hashCode());
System.out.println(anotherRedLight.hashCode());
// # Summary
// A `record` has components that are the parameters used to create an object
// To create an object we use the operator `new` followed by the arguments of the
// record components in the same order
// To interact with an object, we are using methods that are functions that you
// call on an object using the operator `.`
// A Record defines methods to access the value of a component, and also
// `toString()` to get the textual representation of an object and
// `equals()` to test if two objects are equals.Copy to clipboardErrorCopied

命令行应用

选择执行类

通过反射的语法,我们能够动态地选择执行类:

public class MainApplication {
  /**
   * Calls main method of the class provided by the user.
   * @param args  Accepts the classname as the first parameter. The rest are passed as argument as args.
   */
  public static void main(String[] args) throws Exception {
    String[] arguments;
    if (args.length < 1) {
      throw new IllegalArgumentException("Requires at least one argument - name of the main class");
    } else {
      arguments = Arrays.copyOfRange(args, 1, args.length);
      Class mainClass = Class.forName(args[0]);
      Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
      Object[] methodArgs = new Object[1];
      methodArgs[0] = arguments;
      mainMethod.invoke(mainClass, methodArgs);
    }
  }
}Copy to clipboardErrorCopied

然后在运行 jar 的时候动态指定主类名即可:

$ java -jar target/target.jar io.dapr.examples.actors.DemoActorClient

Java 包

包主要用来对类和接口进行分类。当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。

import 语句

在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。package 的作用就是 c++ 的 namespace 的作用,防止名字相同的类产生冲突。Java 编译器在编译时,直接根据 package 指定的信息直接将生成的 class 文件生成到对应目录下。如 package aaa.bbb.ccc 编译器就将该 .java 文件下的各个类生成到 ./aaa/bbb/ccc/ 这个目录。

import 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。例如,下面的命令行将会命令编译器载入 java_installation/java/io 路径下的所有类:

import java.io.*;Copy to clipboardErrorCopied

import 是为了简化使用 package 之后的实例化的代码。假设 ./aaa/bbb/ccc/ 下的 A 类,假如没有 import,实例化 A 类为:new aaa.bbb.ccc.A(),使用 import aaa.bbb.ccc.A 后,就可以直接使用 new A() 了,也就是编译器匹配并扩展了 aaa.bbb.ccc. 这串字符串。

在该例子中,我们创建两个类:Employee 和 EmployeeTest。首先打开文本编辑器,把下面的代码粘贴进去。注意将文件保存为 Employee.java。Employee 类有四个成员变量:name、age、designation 和 salary。该类显式声明了一个构造方法,该方法只有一个参数。

import java.io.*;
public class Employee{
   String name;
   int age;
   String designation;
   double salary;
   // Employee 类的构造器
   public Employee(String name){
      this.name = name;
   }
   // 设置age的值
   public void empAge(int empAge){
      age =  empAge;
   }
   /* 设置designation的值*/
   public void empDesignation(String empDesig){
      designation = empDesig;
   }
   /* 设置salary的值*/
   public void empSalary(double empSalary){
      salary = empSalary;
   }
   /* 打印信息 */
   public void printEmployee(){
      System.out.println("名字:"+ name );
      System.out.println("年龄:" + age );
      System.out.println("职位:" + designation );
      System.out.println("薪水:" + salary);
   }
}Copy to clipboardErrorCopied

程序都是从 main 方法开始执行。为了能运行这个程序,必须包含 main 方法并且创建一个实例对象。下面给出 EmployeeTest 类,该类实例化 2 个 Employee 类的实例,并调用方法设置变量的值。将下面的代码保存在 EmployeeTest.java 文件中。

import java.io.*;
public class EmployeeTest{
   public static void main(String[] args){
      /* 使用构造器创建两个对象 */
      Employee empOne = new Employee("RUNOOB1");
      Employee empTwo = new Employee("RUNOOB2");
      // 调用这两个对象的成员方法
      empOne.empAge(26);
      empOne.empDesignation("高级程序员");
      empOne.empSalary(1000);
      empOne.printEmployee();
      empTwo.empAge(21);
      empTwo.empDesignation("菜鸟程序员");
      empTwo.empSalary(500);
      empTwo.printEmployee();
   }
}

Java 版本切换

jenv

jenv 为我们提供了便捷的 Java 版本切换的功能:

$ brew install jenv
$ jenv add /Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home/
$ jenv versions
* system
  1.8
  1.8.0.202-ea
  11.0
  11.0.2
  openjdk64-11.0.2
  oracle64-1.8.0.202-ea
$ jenv shell 1.8
$ java -version
java version "1.8.0_202-ea"
Java(TM) SE Runtime Environment (build 1.8.0_202-ea-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b03, mixed mode)