从一段 Java 代码说起

很多人对 Java 的第一印象是「啰嗦」:写一个能打印文字的程序还要好几行。但如果把这几行拆开看,它其实把 Java 的核心思想都摆在明面上了——一切代码都要待在类里面,程序从 main 方法开始执行。理解了这两点,后面所有语法都是在这套骨架上的填充。

Java 是一门静态类型、编译型与解释型结合的面向对象语言。源码先由 javac 编译成字节码,再由 JVM 在各平台上执行,这就是「一次编写,到处运行」的来源。对初学者来说,前期不必深究虚拟机细节,先把语法写顺、把面向对象的思维方式建立起来,收益最大。

学 Java 的顺序很关键:先能写出一段完整可运行的小程序,再回头理解类型系统与对象模型。上来就背概念,很容易陷入「每个字都认识、连起来不会用」的状态。
本文核心知识点
  • 基本类型八种内置类型各占多少位、默认值是什么,什么时候该用包装类。
  • 流程控制if / switch、for / while 与作用域,决定代码的执行路径。
  • 类与对象类描述结构与行为,对象是它的具体实例,靠构造方法初始化。
  • 三大特性封装保护内部状态,继承复用结构,多态让同一调用产生不同行为。
Java 基础语法示意:层叠的半透明方板与立方体代表类与对象,右侧蓝图面板向下分叉出连线与方块节点
面向对象的骨架:类是模板,对象是实例,继承与多态把一个个孤立的类连成可替换的体系。

变量、基本类型与运算符

Java 是强类型语言,每个变量在声明时就要确定类型,之后不能随意改变。基本类型共八种,它们直接存值、不涉及对象头,所以访问速度快。写代码时先在脑子里建立这张表,遇到「为什么整数除法丢小数」「为什么 long 要加 L」这类问题,答案基本都在类型宽度和取值范围里。

类型位数典型取值范围常见用途
byte8-128 ~ 127文件、网络字节流
short16-32768 ~ 32767较少使用
int32约 ±21 亿计数、索引的默认选择
long64约 ±9.2×10¹⁸时间戳、大整数
float32约 ±3.4×10³⁸精度要求低的浮点
double64约 ±1.8×10³⁰⁸浮点运算的默认选择
char160 ~ 65535单个 UTF-16 字符
boolean1true / false条件判断

对应的包装类(Integer、Long、Double……)把基本类型包成对象,方便放进集合、支持泛型与空值判断。二者之间会自动装箱与拆箱,但要注意:Integer 对 -128 到 127 做了缓存,用 == 比较小数值可能「碰巧相等」,比较对象值务必用 equals。

// HelloJava.java —— 第一个 Java 程序
public class HelloJava {
    public static void main(String[] args) {
        String name = "吉极云码";
        int age = 3;
        double score = 96.5;
        boolean active = true;
        long timestamp = 1781000000000L;   // long 字面量要加 L

        System.out.println("你好," + name);
        System.out.println("年龄:" + age + ",评分:" + score + ",在线:" + active);
        System.out.println("时间戳:" + timestamp);
    }
}

运算符方面,整数相除会截断小数部分,需要精确结果先把任意一端转成浮点;++ 与 -- 的前后缀语义、&& 与 || 的短路特性,都是排查逻辑错误时的高频考点。类型转换分自动与强制两种,把大范围类型塞进小范围类型要显式强转,并小心溢出。

流程控制:分支、循环与作用域

程序默认从上往下顺序执行,流程控制语句用来改变这条直线。分支决定走哪条路,循环决定重复多少次,两者配合就构成了业务逻辑的基本形状。

  • if / else if / else:按条件真假选择分支,条件必须是 boolean,不能用整数代替。
  • switch:适合对同一个变量做多值匹配;现代 Java 的 switch 支持箭头语法与 yield,写法更紧凑。
  • for:已知循环次数时首选,三段式(初始化、条件、步进)一目了然。
  • while / do-while:条件驱动,do-while 至少执行一次。
  • for-each:遍历数组与集合最简洁,但不适合需要在遍历中改动索引的场景。

作用域是初学者最容易踩的隐坑:一对花括号就是一层作用域,变量出了这层就不可见。循环里声明的变量不会泄露到循环外,方法里的局部变量不会影响别的同名变量——这条规则同时保证了代码的可读性与内存的可回收性。

控制循环还需要 break 和 continue:前者直接跳出整个循环,后者跳过本次进入下一次。它们常与带标签的循环配合使用,但过度嵌套会让可读性急剧下降,通常更推荐把内层逻辑抽成独立方法。

类与对象:把数据和行为装在一起

有了变量和流程控制,你已经能写出「能做事的程序」;但要写出「能长大的程序」,必须学会组织代码的方式,这就是面向对象。一句话概括:类是模板,描述有哪些属性和方法;对象是模板造出来的具体实例。比如「账户」是类,「张三的账户」是对象。

对象通过构造方法初始化。构造方法名与类名相同、没有返回值,可以重载多个版本,用来保证「对象一旦创建出来,内部状态就是合法的」。下面这段代码把余额设为私有字段,只允许通过方法读写,越界的输入在入口处就被拦下:

public class Account {
    private double balance;          // 私有字段,外部不能直接改

    public Account(double initial) {
        if (initial < 0) {
            throw new IllegalArgumentException("初始余额不能为负");
        }
        this.balance = initial;
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("存款金额必须为正");
        }
        balance += amount;
    }

    public static void main(String[] args) {
        Account acc = new Account(1000);
        acc.deposit(500);
        System.out.println("余额:" + acc.getBalance());   // 余额:1500.0
    }
}

这里出现了几个关键细节:private 表示字段只在本类可见;this 指向当前对象,用来区分同名字段与参数;static 表示属于类而不属于某个对象,所以 main 方法能直接通过类名调用。把「数据」和「操作数据的方法」放在一起,正是面向对象区别于面向过程的核心。

封装、继承与多态

面向对象有三大特性,它们解决的是三个不同的问题:封装负责保护内部状态不被外部随意破坏;继承负责复用共性、避免重复代码;多态负责让程序在运行时按对象的真实类型选择行为,从而支持可替换、可扩展的结构。

特性要解决的问题Java 中的抓手
封装内部状态被外部改坏private 字段 + public 方法
继承共性代码被重复书写extends、super
多态新增类型要改调用方父类引用 + 方法重写

继承用 extends 表达「是一种」的关系。子类自动获得父类的非私有成员,并可以重写(override)父类方法。注意 Java 不支持类的多重继承,一个类只能有一个直接父类,但可以实现多个接口。下面这段代码用一个循环处理不同类型的动物,调用方完全不需要 instanceof 判断:

// 继承与多态:父类引用指向子类对象
class Animal {
    protected String name;
    Animal(String name) { this.name = name; }
    String speak() { return name + " 发出了声音"; }
}

class Dog extends Animal {
    Dog(String name) { super(name); }
    @Override
    String speak() { return name + " 汪汪叫"; }
}

class Cat extends Animal {
    Cat(String name) { super(name); }
    @Override
    String speak() { return name + " 喵喵叫"; }
}

public class PolyDemo {
    public static void main(String[] args) {
        Animal[] zoo = { new Dog("旺财"), new Cat("咪咪") };
        for (Animal a : zoo) {
            System.out.println(a.speak());   // 运行时决定调用哪个版本
        }
    }
}

如果把 @Override 去掉,方法名拼错就会变成「新增一个方法」而不是重写,这类 bug 很难发现,所以注解一定要写。多态的价值在于:以后新增一种动物,只要它继承 Animal 并重写 speak,上面的循环一行都不用改。

接口与抽象类:约定优于实现

当多个毫不相关的类需要遵循同一套能力约定时,抽象类就不够用了,这时候用接口。接口只描述「能做什么」,不关心「怎么做到」,是实现解耦与依赖倒置的基础设施。

  • 抽象类用 abstract 声明,可以有字段、构造方法和已实现的方法,适合表达一组关系紧密、共享状态的类型。
  • 接口用 interface 声明,方法默认是公开抽象的;Java 8 起支持 default 与 static 方法,Java 9 起支持 private 方法。
  • 选择标准:子类之间存在「是一种」的天然层级用抽象类;只是需要具备某种能力、可能出现在完全不同的继承体系中,用接口。

一个类可以 implements 多个接口,这弥补了单继承的局限。接口最常见的用法是定义服务契约:上层代码只依赖接口,具体实现可以在运行时注入,测试时替换成替身。这套思路正是后面 Spring Boot 依赖注入的语义基础。