Skip to main content

第二课:基本语法

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

  • 对象:对象是类的一个实例,有状态和行为。例如,黑白的的小猫咪咪是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • :类是一个模板,它描述一类对象的行为和状态。如所有的量产黑白小猫都是一个类
  • 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
  • 实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。

(一)Hello World

    public class HelloWorld {
        public static void main(String[] args) {
            System.out.println("Hello World"); // 输出 Hello World
        }
    }

把文件名保存为:HelloWorld.java ( 源文件名必须和类名相同,大小写也一样!文件名的后缀为 .java

在当前路径输入命令 编译代码

 javac HelloWorld.java
或者
javac -encoding UTF-8 HelloWorld.java

我们会看到在当前目录下生成了一个 class 文件:HelloWorld.class

运行程序

java HelloWorld

你将会在窗口看到 Hello World

这段代码的运行流程是这样的:

  1. public static void main(String[] args) 是 Java 程序的 入口 方法,JVM 从这里开始执行。

  2. Java 是 先编译再执行 的语言, javac 编译器 将其转换为 字节码(Bytecode),生成 字节码文件 HelloWorld.class(这个文件是 JVM 可执行的格式)

  3. 运行时,JVM 读取 HelloWorld.class,并加载到 方法区(Method Area)

  4. JVM 确保 .class 文件没有安全漏洞(防止恶意代码)

  5. JVM 解释并执行 .class 文件中的 字节码。 可能会把热点代码转换为 机器码(提升性能)。

  6. JVM 解释执行 Java 字节码,将其转换为操作系统能理解的指令。

  7. Java 内部调用 PrintStream 对象的 println() 方法,将 "Hello World" 输出到控制台。

  8. main() 方法执行结束,JVM 关闭,程序退出。

注意:

Java 代码是 跨平台 的,因为它不直接运行在 CPU 上,而是运行在 JVM 内部

Windows / Mac / Linux 上的 JVM 都能运行 相同的 .class 文件

Java 代码比 C 代码多了一个 JVM 转换步骤

  • C 语言:直接编译成机器码 → CPU 执行
  • Java:编译成字节码 → JVM 翻译成机器码 → CPU 执行

Java 源程序与编译型运行区别

image-20250306092909082

(二)类和对象

Java 是一门基于类的语言,几乎所有的代码都是围绕类和对象组织的。

类(Class)可以看成是创建 对象(Object)的模板。

1. 类和对象的定义

在 Java 中,类的定义遵循以下格式:

class 类名 {    //注意类后面没有括号!
    // 属性(成员变量)
    数据类型 变量名;
    
    // 方法(成员方法)
    返回类型 方法名(参数列表) {
        // 方法体
    }
}

示例:定义一个简单的类

class Car {
    // 属性(成员变量)
    String brand;   // 品牌
    int speed;      // 速度

    // 方法(成员方法)
    void accelerate() {
        speed += 10;
        System.out.println(brand + " 加速了,现在速度是 " + speed + " km/h");
    }
}

**对象(Object)**是类的一个实例(Instance)。换句话说,类是抽象的,而对象是具体的

在 Java 中,使用 new 关键字来创建对象:

类名 对象名 = new 类名();

示例:创建对象并使用它

public class Main {
    public static void main(String[] args) {
        // 创建对象
        Car myCar = new Car();   //创建了 `Car` 类的一个实例 `myCar`。
        
        // 给对象的属性赋值
        myCar.brand = "Toyota";
        myCar.speed = 50;

        // 调用对象的方法
        myCar.accelerate();  // 输出: Toyota 加速了,现在速度是 60 km/h
    }
}

在这里我们可以看到定义类 和 变量的 语法

image-20250306092853359

2. 类的四大特性

  • 继承(Inheritance)

    • 允许一个类继承另一个类的属性和方法,增强代码复用性。

    • 关键字:extends

    • 示例

      class Animal {
          void makeSound() {
              System.out.println("动物发出声音");
          }
      }
      
      class Dog extends Animal {
          void bark() {
              System.out.println("汪汪汪");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Dog myDog = new Dog();
              myDog.makeSound();  // 继承父类方法
              myDog.bark();
          }
      }
      
  • 封装(Encapsulation)

    • 通过 private 修饰变量,提供 public 方法访问,提高安全性。

    • 示例

      class Person{
          private String name;
      
          public void setName(String newName){
              name = newName;
          }
          public String getName(){
              return name;
          }
      }
      
      public class Main {
          public static void main(String[] args){
              Person person = new Person();
              System.out.println("改名字之前:" + person.getName());  // 输出 null
              person.setName("Heihe");
              // System.out.println(person.name);    //直接访问 person 的 name 属性会报错
              System.out.println("改名字之后:" + person.getName());
          }
      }
      
  • 多态(Polymorphism)

    • 方法重载(Overloading): 在同一个类中,同名方法可以有不同的参数列表。

    • 方法重写(Overriding): 子类可以重写父类的方法,实现不同的行为。

    • 示例

      class Animal {
          void makeSound() {
              System.out.println("动物发出声音");
          }
      }
      
      class Cat extends Animal {
          @Override
          void makeSound() {
              System.out.println("喵喵喵");
          }
      }
      
  • 抽象(Abstraction)

    • 通过 抽象类(abstract class)接口(interface) 实现。

    • 示例

      abstract class Shape {
          abstract void draw();  // 抽象方法
      }
      
      class Circle extends Shape {
          void draw() {
              System.out.println("画一个圆");
          }
      }
      

3. 构造方法(Constructor)

构造方法 用于在创建对象时初始化对象的属性。

构造方法的特点

  • 方法名必须与类名相同
  • 没有返回值(甚至不能写 void
  • 可以有参数,用来在创建对象时赋值。
  • 如果你不定义构造方法,Java 会自动创建一个无参构造
  • 每次使用 new 创建对象时,构造方法会被自动调用。
  • 构造方法不能像普通方法那样被直接调用,只能通过 new 来触发。
  • 一个类可以定义多个构造方法(构造方法重载),只要参数列表不同即可(详见“方法的特性”)
  • 构造方法不能被子类继承,但可以通过 super() 调用父类的构造方法。

示例:定义构造方法

class Cat {
    String name;
    int weight;

    Cat(String name, int weight ){
        this.name=name;
        this.weight=weight;
    }
}
public class Main {
    public static void main(String[] args){
        Cat cat= new Cat("mimi",56); 
        System.out.println(cat.name);
        System.out.println(cat.weight);
    }  
}

这里有个地方需要注意,为什么在写构造函数的时候要用 this. ?不用会怎么样?

如果不用,会没法成功赋值,输出的分别是 null 和 0 ,也就是默认值。

这是因为 左边的 sizewight 是局部变量(构造方法的参数),而不是类的成员变量

换句话说,java不会把等号左边的 sizewight 当成是类的属性,而当成新建的局部变量

而加上 this. 之后,实际上是类的属性

可以使用 this() 调用当前类的其他构造方法

调用 this() 时,必须放在构造方法中的第一行

示例:

public class Person {
    String name;
    int age;

    // 构造方法调用
    public Person(String name) {
        this(name, 0); // 调用双参数的构造方法
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

调用实例

Person p = new Person("Alice"); // 调用了单参数构造方法,进而调用双参数构造方法
System.out.println(p.name); // 输出:Alice
System.out.println(p.age);  // 输出:0

super() 调用父类构造方法

  • 子类的构造方法可通过 super() 调用父类的构造方法(必须是第一行)。
  • 如果未显式调用父类的构造方法,系统会自动插入对父类无参构造方法的调用。

4. 访问修饰符

Java 提供了四种访问权限控制:

修饰符作用范围说明
public任何地方可访问最高权限
private仅限当前类内访问只能在本类使用
protected当前类、同包内的类和子类可访问适用于继承
(默认,无修饰符)仅限当前包内访问不能被外部包访问

示例

class Car {
    private String brand;  // 只能在 Car 类内部访问
    public int speed;  // 任何地方都可以访问

    // 构造方法
    Car(String b, int s) {
        this.brand = b;
        this.speed = s;
    }

    // 通过方法访问私有变量
    public String getBrand() {
        return brand;
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car("BMW", 100);
        
        // 访问 public 变量
        System.out.println("速度: " + myCar.speed);  // 可以访问

        // 访问 private 变量(必须通过方法)
        System.out.println("品牌: " + myCar.getBrand());  // 不能直接访问 brand
    }
}

在这里:

  • brandprivate 修饰,不能在 Main 类中直接访问,只能通过 getBrand() 获取。
  • speedpublic 修饰,可以直接访问。

5. 变量类型

前面一点讲到了变量的访问权限控制,其实变量本身有三种分类:

变量类型存储位置作用域默认值
局部变量方法内部无(必须手动初始化)
实例变量属于对象0falsenull
静态变量方法区属于类0falsenull
  • 局部变量(Local Variable):定义在方法、构造方法或代码块内部,生命周期仅限于该方法内。
  • 成员变量(Instance Variable):定义在类中但在方法外,是当前对象的实例变量
  • 类变量(Static Variable):使用 static 声明,是属于 整个类 的,而不是某个特定的对象,所有对象共享一份数据。 适用于存储所有对象共享的信息,比如统计创建了多少个实例 等

示例

public class Puppy {
    // 实例变量(每个对象独立)
    private int age;
    private String name;

    // 类变量(所有对象共享)
    private static int count = 0;  // 统计创建的 Puppy 对象个数
    private static String species = "Dog A";  // 共享种类信息

    // 构造方法
    public Puppy(String n) {
        this.name = n;  // n 是局部变量
        count++;  // 每创建一个 Puppy 对象,count 自增
    }

    // Setter 方法
    public void setAge(int a) {
        this.age = a;   // a 是局部变量
        //由于变量名不同,这里可以直接写:age = a;
    }

    // Getter 方法
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }

    // 类变量的 setter 方法
    public static void setSpecies(String newSpecies) {
        species = newSpecies;
    }

    // 类变量的 getter 方法
    public static int getCount() {
        return count;
    }
    public static String getSpecies() {
        return species;
    }



    // 主程序
    public static void main(String[] args) {
        Puppy puppy1 = new Puppy("Tommy");
        puppy1.setAge(2);

        Puppy puppy2 = new Puppy("Charlie");
        puppy2.setAge(3);

        System.out.println("小狗1的名字是: " + puppy1.getName());
        System.out.println("小狗1的年龄是: " + puppy1.getAge());

        System.out.println("小狗2的名字是: " + puppy2.getName());
        System.out.println("小狗2的年龄是: " + puppy2.getAge());

        // 访问类变量
        System.out.println("Puppy 的种类是: " + Puppy.getSpecies());
        System.out.println("当前创建的小狗总数: " + Puppy.getCount());

        // 修改类变量(影响所有对象)
        Puppy.setSpecies("Dog B");
        System.out.println("更新后的 Puppy 种类是: " + Puppy.getSpecies());
    }
}

关于 this. 的进一步解释:

Java 优先查找局部变量,因此对于同名的 局部变量成员变量,会优先认为是 局部变量

在 setter 方法中:由于 a 和 age 变量名不同,理论上 this 可以省略。

​ 但如果参数名和实例变量名相同,就必须使用 this 来区分

在 getter 方法中: age 和 name 没有同名的局部变量,所以默认查找的就是 成员变量

6. 包(Package)

Java 提供**包(Package)**来管理类:

  • 默认包(Unnamed Package):如果不声明 package,Java 会自动使用默认包。

  • 自定义包:

    • 声明包package mypackage;
    • 导入包import mypackage.MyClass;
    • 使用类MyClass obj = new MyClass();

示例:

package mypackage;

public class MyClass {
    public void showMessage() {
        System.out.println("这是我的包");
    }
}

使用这个包

import mypackage.MyClass;

public class Test {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.showMessage();
    }
}

7. Java 源文件声明规则

在 Java 开发中,源文件有以下规则:

  1. 一个源文件只能有一个 public,类名必须与文件名相同。
  2. 可以有多个非 public,但它们不能与文件名相同。
  3. package 语句必须放在文件的第一行(如果有)
  4. import 语句必须放在 package 之后,类定义之前

示例:

package mypackage;  // 必须放在首行

import java.util.*;

public class MyClass {
    public void display() {
        System.out.println("Hello, Java!");
    }
}

(三)方法的特性

在上面的 类和对象 中,我们已经看到了 类中的方法是如何定义的。

方法还具有以下特性

1. 参数按值传递

基本规则:

  • Java 方法总是按值传递(通过参数的副本传递值)。
  • 实参的值在方法调用中无法被改变。

示例

public class TestPassByValue {
    public static void main(String[] args) {
        int num1 = 1;
        int num2 = 2;
        swap(num1, num2);
        System.out.println("num1: " + num1 + ", num2: " + num2);
    }

    public static void swap(int n1, int n2) {
        int temp = n1;
        n1 = n2;
        n2 = temp;
    }
}

输出结果

num1: 1, num2: 2

2. 方法的重载

定义:

  • 同一个类中,可以定义多个名字相同但参数列表不同的方法。
  • Java 编译器通过方法签名(方法名 + 参数列表)区分方法。

示例:

public static int max(int num1, int num2) {
    return (num1 > num2) ? num1 : num2;
}

public static double max(double num1, double num2) {
    return (num1 > num2) ? num1 : num2;
}

调用:

System.out.println(max(10.5, 15.0)); // 调用 double 类型版本
System.out.println(max(20, 30));    // 调用 int 类型版本

3. 可变参数(Varargs)

语法:

  • JDK 1.5 起支持在方法中声明可变参数:

    public static void methodName(typeName... parameterName) {...}
    
  • 一个方法只能有一个可变参数,且必须是最后一个。

示例:

public static void printMax(double... numbers) {
    if (numbers.length == 0) return;
    double max = numbers[0];
    for (double num : numbers) {
        if (num > max) max = num;
    }
    System.out.println("The max value is " + max);
}

调用:

printMax(1, 2, 3, 4, 5);
printMax(new double[]{10, 20, 30});

输出

The max value is 5
The max value is 30

4. 变量作用域

  • 局部变量:声明于方法或代码块中,只在方法/块内可见。
  • 参数变量:方法参数在整个方法中可见。
  • 块内变量:变量可在声明的块内访问。

image-20250306092932632

5. 命令行参数

main(String[] args) 中的 args 是用来给 命令行传递的参数数组。

示例:

public class CommandLine {
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            System.out.println("args[" + i + "]: " + args[i]);
        }
    }
}

执行命令:

$ java CommandLine this is a test

输出

args[0]: this
args[1]: is
args[2]: a
args[3]: test

(三)基本数据类型

Java 提供了两大数据类型:内置数据类型(基本数据类型)和 引用数据类型

数据类型说明
基本数据类型(Primitive Types)直接存储值,效率高
引用数据类型(Reference Types)存储对象的引用(如 String、数组、类等)

1. 基本数据类型

其中基本数据类型可分为 4 类:

  1. 整数类型byteshortintlong
  2. 浮点类型floatdouble
  3. 字符类型char
  4. 布尔类型boolean
数据类型位数取值范围默认值示例
byte8 位-128 ~ 1270byte b = 100;
short16 位-32,768 ~ 32,7670short s = 1000;
int32 位-2^31 ~ 2^31-10int i = 100000;
long64 位-2^63 ~ 2^63-10Llong l = 100000L;
float32 位约 ±3.4E380.0ffloat f = 3.14f;
double64 位约 ±1.8E3080.0ddouble d = 3.14;
char16 位\u0000(0)到 \uffff(65535)(Unicode)\u0000char c = 'A';
boolean1 位true / falsefalseboolean b = true;

2. 类型转换

  • 小范围类型可以自动提升到大范围类 (隐式转换)

    int i = 100;
    double d = i; // 自动转换为 double
    
  • 大范围类型转换到小范围时,需要强制转换 (显式转换)

    double d = 99.99;
    int i = (int) d; // 手动转换
    
低 → 高
byteshortintlongfloatdouble

3. 声明常量

  • final 关键字声明,值不可变

    final double PI = 3.1415927;
    
  • 进制表示

    int decimal = 100;   // 十进制
    int octal = 0144;    // 八进制
    int hexa = 0x64;     // 十六进制
    

4. 引用数据类型

引用数据类型 是指 存储对象的地址,而不是直接存储数据本身

数据类型说明
对象 (new)存储对象地址(如 StringScanner
数组 (new[])存储相同类型的数据集合
接口 (interface)定义行为规范
  • 所有引用类型默认值是 null, 表示未指向任何对象。

    public class Main {
        public static void main(String[] args) {
            // String 类型的引用变量,默认值为 null
            String str = null;
    
            // 尝试访问 str 会报错
            // System.out.println(str.length()); // 会抛出 NullPointerException
    
            // 先赋值再访问
            str = "Hello, Java!";
            System.out.println(str.length()); // 输出: 12
        }
    }
    
    
  • 对象是类的实例,存储对象的引用(地址)

    class Person {
        String name;
        int age;
    
        // 构造方法
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        // 显示信息的方法
        public void display() {
            System.out.println("姓名: " + name + ", 年龄: " + age);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 创建对象
            // p1 和 p2 存储的是对象的地址,而不是实际值。
            Person p1 = new Person("Alice", 25);
            Person p2 = new Person("Bob", 30);
    
            // 访问对象的成员变量和方法
            p1.display();
            p2.display();
    
            // 对象的引用存储的是地址
            System.out.println("p1 地址: " + p1);  // p1 地址: Person@4ec6a292
            System.out.println("p2 地址: " + p2);  // p2 地址: Person@ea4a92b
        }
    }
    
    
  • 数组 是存储相同数据类型的集合,本质上也是引用类型。

    public class Main {
        public static void main(String[] args) {
            // 创建整数数组
            //numbers 变量存储的是数组的地址,而不是具体的整数值。
            int[] numbers = new int[3];
    
            // 赋值
            numbers[0] = 10;
            numbers[1] = 20;
            numbers[2] = 30;
    
            // 遍历数组
            for (int num : numbers) {
                System.out.println(num);
            }
        }
    }
    
    
  • 字符串数组 存储的是 String 对象的引用,而不是具体字符串。

    public class Main {
        public static void main(String[] args) {
            // 创建字符串数组
            //字符串数组存储的是 String 对象的引用,而不是具体字符串。
            String[] fruits = {"Apple", "Banana", "Cherry"};
    
            // 遍历数组
            for (String fruit : fruits) {
                System.out.println(fruit);
            }
        }
    }
    
    
  • 对象数组(一个储存了多个 引用对象 的数组)

    class Car {
        String brand;
        int speed;
    
        public Car(String brand, int speed) {
            this.brand = brand;
            this.speed = speed;
        }
    
        public void show() {
            System.out.println("品牌: " + brand + ", 速度: " + speed + " km/h");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 创建 Car 类型的对象数组
            // cars 数组存储的是 对象的地址,每个元素指向一个 Car 对象。
            Car[] cars = new Car[2];
    
            // 创建对象并赋值
            cars[0] = new Car("Toyota", 180);
            cars[1] = new Car("BMW", 200);
    
            // 遍历数组
            for (Car car : cars) {
                car.show();
            }
        }
    }
    
    
  • 接口 是引用类型,定义行为规范,让不同类可以实现相同的方法。

    // 定义接口
    // Animal 是接口,不能创建实例,但可以存储 Dog 和 Cat 的对象地址
    interface Animal {
        void makeSound(); // 抽象方法
    }
    
    // 具体实现类 1
    class Dog implements Animal {
        public void makeSound() {
            System.out.println("Dog: 汪汪!");
        }
    }
    
    // 具体实现类 2
    class Cat implements Animal {
        public void makeSound() {
            System.out.println("Cat: 喵喵!");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 接口类型的引用
            // a1 和 a2 存储的是 Dog 和 Cat 对象的地址,实现了 多态(Polymorphism)。
            Animal a1 = new Dog();
            Animal a2 = new Cat();
    
            // 调用方法
            a1.makeSound();
            a2.makeSound();
        }
    }
    
    

(四) 修饰符

之前我们已经零零碎碎讲了一些修饰符,比如 访问修饰符,声明常量 等等。

这里再做统一的整理

Java 修饰符用于控制 类、方法、变量访问权限行为特性,主要分为 访问修饰符非访问修饰符

1. 访问修饰符

Java 提供 4 种访问修饰符,用于控制类、变量、方法的可见范围:

修饰符当前类同包内子类(不同包)其他包
public✅ 可访问✅ 可访问✅ 可访问✅ 可访问
protected✅ 可访问✅ 可访问✅ 可访问❌ 不能直接访问
(默认)✅ 可访问✅ 可访问❌ 不能访问❌ 不能访问
private✅ 可访问❌ 不能访问❌ 不能访问❌ 不能访问

private 为例

public class PrivateExample {
    private int secret = 100;  // 私有变量

    private void showSecret() {  // 私有方法
        System.out.println("Secret number: " + secret);
    }

    public void accessPrivate() {
        showSecret(); // ✅ 内部方法可以访问 private 成员
    }
}
  • 访问方式
PrivateExample obj = new PrivateExample();
// System.out.println(obj.secret);  ❌ 报错(私有变量不可访问)
// obj.showSecret();  ❌ 报错(私有方法不可访问)
obj.accessPrivate();  // ✅ 可以通过 public 方法间接访问 private 方法

2. 非访问修饰符

Java 还提供了一些非访问修饰符,用于控制类、方法、变量的特殊行为。

修饰符作用
static静态变量或方法,共享数据
final常量、不可继承的类、不可重写的方法
abstract抽象类或抽象方法
synchronized线程同步
volatile线程可见性
transient序列化时忽略字段
  • static 指定 变量或方法 属于,而不是特定对象。可以直接用 类名.方法名() 访问。
public class StaticExample {
    static int count = 0;  // 静态变量

    static void show() {  // 静态方法
        System.out.println("Static method called.");
    }
}

System.out.println(StaticExample.count); // ✅ 直接访问
StaticExample.show();  // ✅ 直接访问
  • final 指定不可修改/重写的 变量、方法 和 不能被继承的
final class FinalClass {}

// 不能继承 FinalClass
// class SubClass extends FinalClass {} ❌ 报错

class Parent {
    final void show() {
        System.out.println("This method cannot be overridden.");
    }
}

class Child extends Parent {
    // void show() {}  ❌ 报错,不能重写 final 方法
}
  • abstract 用于定义抽象类 或 抽象方法
    • 抽象类: 不能实例化,必须由子类实现。
    • 抽象方法: 没有方法体,子类必须实现。
abstract class Animal {
    abstract void makeSound(); // 抽象方法
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("汪汪!");
    }
}
  • synchronized 保证方法同一时间只能被一个线程访问
public class SyncExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }
}
  • volatile 保证变量对所有线程立即可见。
public class VolatileExample {
    private volatile boolean flag = true;

    public void stop() {
        flag = false;
    }
}
  • transient 修饰的 变量不会被序列化。
import java.io.*;

class TransientExample implements Serializable {
    private static final long serialVersionUID = 1L;
    transient int password;
}