# 语法
# 语法特点
- Java 源文件的扩展名为 .java 。需要先编译成扩展名为 .class 的类文件,然后交给 JVM 运行。
- 每个语句的末尾以分号 ; 作为分隔符。
- 用
//
声明单行注释,用/*
、*/
声明多行注释。 - 支持面向对象编程,但不支持定义函数。
- Java 的语法与 C++ 相似,但是做出了许多优化,比如:
- 丢弃了 C++ 中一些难用的功能,比如操作符重载、多继承。
- 用引用取代了指针。
- 自动回收垃圾内存。
# 程序示例
编写一个源文件 Dog.java :
public class Dog{ // 定义类
String name; // 定义成员变量
int age = 0;
public Dog(String name){ // 定义构造方法
this.name = name; // 通过 this 指针调用该对象的成员
}
public Dog(int age){
this.age = age;
}
public void setAge(int age){ // 定义成员方法
this.age = age;
}
public int getAge(){
return age;
}
public static void main(String[] args){ // 定义主方法
Dog dog1 = new Dog("Tony");
dog1.setAge(2);
System.out.println("小狗的名字为:" + dog1.name);
System.out.println("小狗的年龄为:" + dog1.getAge());
}
}
- 一个源文件中可以定义多个类,但最多只能定义一个 public 类,并且该类必须与源文件同名(区分大小写),否则编译时会报错。
- 用 Java 编程时,需要定义有且仅有一个主方法,作为程序运行的入口。
- 当 JVM 执行一个 Java 程序时,会寻找包含主方法的类(称为 Main 类),执行其主方法。
- 如果不存在 Main 类,则不能执行该 Java 程序,报错:
Main method not found in class Main
- 一个 Java 程序中可以存在多个 Main 类,但通常只定义一个,以简化运行逻辑。
- 如果不存在 Main 类,则不能执行该 Java 程序,报错:
- 只有声明为
public static void main(String[] args)
的方法才会被视作主方法。- 其中形参 args 的名称可以自定义。
- 当 JVM 执行一个 Java 程序时,会寻找包含主方法的类(称为 Main 类),执行其主方法。
System.out.println()
用于输出一个值到终端。其中,System 是系统类,out 是标准输出对象,println() 是其中的一个方法。
# 数据类型
# 基本类型
boolean x = false; // 存储空间占 1 bit ,取值为 true 或 false
byte x = 0; // 占 8 bit ,取值范围为 -128~127
short x = 0; // 占 2 字节
int x = 0; // 占 4 字节
long x = 0L; // 占 8 字节,赋值时要加后缀 L 或 l
float x = 0.0f; // 占 4 字节,赋值时要加后缀 F 或 f
double x = 0.0d; // 占 8 字节,赋值时的后缀为 F 或 f 。但可以不加后缀,因为浮点数默认为 double 类型
char x = 'A'; // 占 2 字节,用于存储单个 Unicode 字符,用单引号作为定界符。取值范围为 \u0000~\uffff
# 常量
- :在初始化之后不能改变取值。
- 用关键字 final 声明常量。如下:
final double PI = 3.1415927;
# 字符串
- :String ,用双引号作为定界符。
- 字符串支持通过 + 与其它类型的值自动拼接,如下:
System.out.println("Hello" + 0.0); // 显示为 Hello0.0
# 引用
- 与 C++ 相比,Java 用引用取代了指针,用于存储对象、数组的地址。
- 例:
Dog dog1; // 创建一个引用类型的变量,可用于指向 Dog 类型的对象 dog1 = new Dog(1); // 用 new 创建一个对象,并将引用指向它 dog1 = new Dog(2); // 可以改变引用指向的对象 Dog dog2 = dog1; // 多个引用可以指向同一个对象
# 数组
- Java 中用关键字 new 创建数组,实际上是创建一个对象并返回引用。
- 例:创建指定大小的数组,然后赋值
int[] array = new int[3]; array[0] = 0; System.out.println(array[0]);
- C++ 中声名数组的格式为
int array[]
,而 Java 中为了避免歧义,建议采用int[] array
的格式。
- C++ 中声名数组的格式为
- 例:在创建数组的同时初始化
int[] array = {0, 1, 2};
# 泛型
- 用
< >
可以声明泛型(generics)。- 可以将数据类型参数化,允许用多种类型的值赋值。
- 主要用途:代码中的强制类型转换可能转换成错误的类型。如果声明为泛型,在编译时会自动进行强制类型转换,从泛型转换成具体的数据类型,如果不安全则报错。
- 例:
import java.util.*; List<String> arrayList = new ArrayList<String>(); // 使用 String 作为类型参数 arrayList.add("Hello"); arrayList.add(100); // 编译时会报错:incompatible types: int cannot be converted to String
# 流程控制
与 C++ 相似,Java 支持 if、switch 条件语句,和 for、while、do-while 循环语句。如下:
- 嵌套的 if 语句:
int x = 1; if( x > 1 ){ System.out.println("A"); }else if( x == 0 ){ System.out.println("B"); }else { System.out.println("C"); }
- for 循环:
for(int x = 0; x < 10; x++) { System.out.println("x = " + x ); }
- 可以通过增强型 for 循环遍历一个数组:
int [] array = {0, 1, 2, 3}; for(int x : array ){ System.out.println( x ); }
# 类
- Java 支持在类中、方法中定义嵌套的类,称为内部类。
- 内部类可以直接访问外部类的私有成员。
- 但是不支持在方法中定义嵌套的方法。
# 方法
- 与类名同名,且没有声明返回值类型的方法会被编译器视作构造方法。
- 每个类都默认有一个空的构造方法,其形参列表为空、内容为空。如下:
public Dog(){}
- 可以定义多个构造方法,但它们的形参列表必须不同。
- 实例化一个类时,编译器会自动调用与实参列表匹配的那个构造方法,初始化该对象。
- 构造方法只能在实例化类时被编译器自动调用,不能用代码直接调用。
- 每个类都默认有一个空的构造方法,其形参列表为空、内容为空。如下:
- Java 不支持给方法的形参声明默认值。如下:
public int sum(int x, int y=0){ // 这样会导致编译时解析形参列表出错 return x + y; }
# 对象
- 用关键字 new 可以实例化一个类,返回一个对象。如下:
Dog dog1 = new Dog(1);
# 继承
- 定义类时,可以用关键字 extends 继承另一个类。
- Java 只支持单继承,不支持多继承(一个子类同时继承多个父类)。
- Object 类是所有类的父类。
- 例:
class Cat extends Dog{ public Cat(){ // 子类不会继承父类的构造方法,需要自己定义 super(); // 调用父类的构造方法 } public Cat(String name, int age){ super(name, age); } void sleep(){ } void show(){ super.getAge(); this.sleep(); } public static void main(String[] args){ Dog cat1 = new Cat("Pussy"); System.out.println("小猫的年龄为:" + cat1.getAge()); } }
- 通过关键字 this 可以调用当前对象的成员,通过关键字 super 可以调用父类对象的成员(直系父类)。
- 定义构造方法时,可以通过 this、super 调用其它构造方法,但必须是方法内的第一条可执行语句。
- 如果没有通过 super 调用父类的构造方法,编译器默认会隐式地执行 super() ,调用父类的无参数构造方法。
- 当子类的方法名或变量名与父类重名时,编译器会优先执行子类的。
- 定义构造方法时,可以通过 this、super 调用其它构造方法,但必须是方法内的第一条可执行语句。
- Java 主要有两种方式实现多态性:
- 方法重写(override)
- :在一个类及其子类中,如果定义多个方法,它们的名称、形参列表都相同,则只会保留最后一个定义的方法,之前的同名方法会被重写、覆盖。
- 例如父类的一个方法,执行了某个操作。子类可以重写该方法,执行不同的操作。
- 方法重载(overload)
- :在一个类及其子类中,如果定义多个方法,它们的名称相同、形参列表不同,则这些方法都会保留。
- 用户调用该名称的方法时,实参列表与哪个方法的形参列表匹配,就会调用哪个方法。
- 方法重写(override)
# 修饰符
- Java 提供了多种修饰符,用于修饰某个标识符。
- 使用时,通常将修饰符,写在被修饰对象之前。
- 主要分为两类:
- 访问控制符:用于修饰类,或者类中的成员,控制它们的访问权限。
- 非访问控制符:用于修饰变量、函数、类。
# 访问控制符
default
- 修饰的目标,可以从同一个包内访问。
- 一个类或者成员,如果未使用访问控制符,则默认会当作 default 类型处理。
- 类(非内部类)只能被 default 或 public 修饰。
private
- 修饰的目标,只能从当前类中访问。
protected
- 修饰的目标,可以从子类,或者同一个包中访问。
public
- 修饰的目标,可以从所有位置访问。
# 非访问控制符
static
- 修饰的变量称为静态变量。
- 不管一个类创建多少个实例,只会存储一份静态变量。
- 只能修饰类的成员变量,不能修饰局部变量。
- 修饰的方法称为静态方法。
- 静态方法只能访问静态成员、局部成员,不能访问非静态成员。
- 静态方法可以通过
对象名.方法名
的形式调用,也可以通过类名.方法名
的形式调用。
- 不被 static 修饰的成员方法、成员变量称为非静态成员。
- 修饰的变量称为静态变量。
final
- 修饰的类不能被继承。
- 修饰的方法不能被重载。
- 修饰的变量不能修改取值(即常量)。
- 比如可以用
static final int id = 1;
的形式声明一个类常量。
- 比如可以用
abstract
- 修饰的类称为抽象类。
- 抽象类不能再被 final 修饰。
- 抽象类不能直接实例化,只能被继承。
- 修饰的方法称为抽象方法。
- 抽象方法不能再被 final 或 static 修饰。
- 一个抽象类中,可以包含多个抽象方法,和非抽象方法。非抽象类中,不允许包含抽象方法。
- 当前类继承一个抽象类时,需要 override 其所有抽象方法,除非当前类也是抽象类。
- 例:
public abstract class SuperClass{ abstract void sum(); // 定义抽象方法时,不能定义方法体,而且要以分号 ; 结尾 } class SubClass extends SuperClass{ void sum(){ // 实现抽象方法 return 0; } }
- Java 的抽象方法类似于 C++ 中的 virtual 虚函数。
- 修饰的类称为抽象类。
# interface
- 接口:一种抽象类型,定义的语法像类,用关键字 interface 声明。
- 接口没有构造方法,只包含一些抽象方法。
- 接口中的每个方法都会被编译器隐式地用 public abstract 声明,不能再用其它修饰符。
- 接口中的每个成员变量都会被编译器隐式地用 public static final 声明,不能再用其它修饰符。
- 接口可以继承其它接口,支持多继承。
- 例:
interface Pet extends Animal{ public void eat(); public void sleep(); }
- 一个类可以通过关键字 implements 继承一个接口。
- 支持多继承。
- 类必须实现接口的所有抽象方法,除非该类是抽象类。
- 例:
class Dog implements Pet{ public void eat(){ System.out.println("eating"); } public void sleep(){ System.out.println("sleeping"); } }
# annotation
Java 支持用 @ 给类、方法、变量、包等对象添加注解(Annotation),实现比注释更多的功能。
- 所有注解都继承于
java.lang.annotation.Annotation
接口。
- 所有注解都继承于
例:添加注解
@MyAnnotation1("Hello") public class Hello { @@MyAnnotation2(length=32) String name; @Override public String toString() { return "Hello"; } }
例:定义注解
@Documented // 将该注解加入到 javadoc 中 @Target(ElementType.TYPE) // 声明该注解的类型。ElementType.TYPE 表示可以修饰类、接口(包括注解类型)、枚举 @Retention(RetentionPolicy.RUNTIME) // 声明该注解的保留策略。RetentionPolicy.RUNTIME 表示保留到类文件中,支持 JVM 在运行时读取 public @interface MyAnnotation1 { // 用 @interface 定义一个注解,名为 MyAnnotation1 String value() default ""; }
- 元注解:用于修饰注解的注解。
- 定义注解时,上方可以添加一些元注解来修饰它。
- 可以在注解中定义任意个参数。
- 如果参数名为 value ,则传参时可以省略参数名。
- 可以用 default 关键字给参数声明默认值。
- 元注解:用于修饰注解的注解。
Java 内置的注解举例:
@Override # 只能修饰方法,表示该方法重写了父类的方法 @Deprecated # 表示该对象被弃用。如果调用了该对象,则编译时会产生警告 @SuppressWarnings # 编译时,屏蔽该对象产生的警告
# package
用关键字 package 可以将一个源文件的内容加入到一个包(package)中。
- package 语句必须是源文件中的第一条可执行语句。
- 例:
package test.animals; public class Dog{ ... }
- 包名通常全部小写。
- 包名必须与源文件的存储目录名一致,否则 JVM 会找不到该包对应的源文件。例如上例中源文件的存储路径应该是
test/animals/Dog.java
。 - 一些公司通常以域名作为所有包的根目录,例如
com.sun.javadoc.ClassDoc
、com.sun.javadoc.MethodDoc
。
- 每个包有自己独立的命名空间,类似于 C++ 中的 namespace 。
- 例如:同一个包中,类名不能重复。不同包中,类名可以重复。
用关键字 import 可以导入一个包的内容。
- import 语句必须放在 package 语句之后、类声明之前。
- 例:
import test.animals.Dog; // 导入一个包中的指定类 import test.animals.*; // 可以使用通配符 * 导入包中的所有成员
- Java 程序默认会隐式地导入 java.lang 包。
# 异常处理
- 异常(Exception)和错误(Error)严格来说是两种概念。
- 错误
- :会导致程序停止运行。
- 程序可以捕捉并处理自己的异常,但处理不了错误。
- 检查性异常
- :在编译时就能检查出来,会导致编译不能通过。
- 通过继承 Exception 类,可以自定义一个检查性异常类。
- 所有异常类都是 java.lang.Exception 类的子类。
- 运行时异常
- :编译可以通过,在程序运行时才能发现。
- 通过继承 RuntimeException 类,可以自定义一个运行时异常类。
- 错误
- 可以用关键字 throw 主动抛出一个异常,用关键字 throws 声明某个方法可能抛出某些异常。
- 例:
void test() throws Exception, RuntimeException{ throw new Exception("测试异常"); }
- 例:
- 可以用关键字 try 和 catch 捕捉异常。
- 当 try 语句块出现一个异常时,会抛出该异常,依次与 catch 语句尝试匹配。
- 如果该异常类型与某个 catch 语句声明的异常类型匹配,该异常就会被捕获,像给函数传递参数一样传递给该 catch 语句,执行其语句块。
- 如果该异常不与任何 catch 语句匹配,则会抛出,供外层代码捕获。
- 可以在 catch 语句之后定义一个 finally 语句块,无论是否出现异常都会执行它。
- 即使 try、catch 中有执行 return 语句,系统也会先执行 finally 语句块再执行 return 语句。
- 如果该异常始终没有被捕获,在 main 方法中抛出,就会导致程序终止,报错:
unreported exception Exception; must be caught or declared to be thrown
- 例:
try{ ... }catch(IOException i){ // IOException 表示异常类型,i 表示将异常实例化并传递给一个变量 ... }catch(FileNotFoundException f) System.exit(1); // 让程序以指定的返回码退出 }finally{ ... }
- 当 try 语句块出现一个异常时,会抛出该异常,依次与 catch 语句尝试匹配。