前言: 你好,陌生人。如果你点开了这篇文章,说明你或许正站在Java学习的起点,甚至可能感到一丝迷茫。没关系,我也是从头开始重新梳理Java体系。这篇文章是我学习路上的笔记,也是我想和所有初学者、迷茫者一起进步的见证。 本系列将分阶段更新,这是第一篇:Java基础语法、数据类型、流程控制、数组与方法。每一个知识点我都配备了代码和解释,希望能帮你少走弯路。我们顶峰相见!


阶段一:Java基础语法入门

1. 常见配置与HelloWorld

学习Java前,我们需要配置 JDK(Java开发工具包)和 IDE(如IntelliJ IDEA)。JDK包含了编译和运行Java程序的工具。


// 这是一个Java程序的基本骨架
public class HelloWorld {
    // main方法是程序的入口,JVM会从这里开始执行
    public static void main(String[] args) {
        System.out.println("Hello, World! 开启Java之旅!");
    }
}

2. 注释、关键字与保留字

  • 注释:解释代码,不参与编译。
  • 关键字:Java赋予特殊含义的单词(如 classpublicstatic),全部小写。
  • 保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使用(如 gotoconst)。

/**
 * 文档注释:可用于生成API文档 (多行)
 */
public class CommentDemo {
    public static void main(String[] args) {
        // 单行注释:解释单行代码
        int age = 18; 
        
        /* 
         * 多行注释:解释多行代码 
         * 注意:多行注释不能嵌套
         */
        System.out.println(age);
    }
}

3. 字面量、变量与命名规范

  • 字面量:代码中直接写出的值(如 10"abc"'a')。
  • 变量:内存中存储数据的基本单元。
  • 命名规范
    • 变量/方法名:小驼峰(myAgegetUserName
    • 类名:大驼峰(UserServiceHelloWorld
    • 常量:全大写,下划线分隔(MAX_VALUE

public class VariableDemo {
    public static void main(String[] args) {
        // 数据类型 变量名 = 字面量;
        int myAge = 20;          // 整数字面量
        double price = 99.9;     // 小数字面量
        char gender = '男';       // 字符字面量
        String name = "张三";     // 字符串字面量 (注意String是引用类型)
        boolean isStudent = true;// 布尔字面量
        
        System.out.println(name + "的年龄是:" + myAge);
    }
}

阶段二:数据类型与底层存储原理

1. 整数与字符在计算机中的存储原理

整数存储(原码、反码、补码): 计算机底层只存储和运算补码

  • 正数:原码 = 反码 = 补码。
  • 负数:原码 -> 符号位不变,其余取反得反码 -> 反码+1得补码。 为什么用补码? 因为补码可以将加法和减法统一,且解决了 +0 和 -0 的问题。

字符存储(ASCII、Unicode): 字符在底层也是数字。Java使用 Unicode 编码(占用2个字节),兼容 ASCII。


public class StorageDemo {
    public static void main(String[] args) {
        // 字符底层是数字,可以直接参与运算
        char c1 = 'A'; // ASCII中 'A' 是 65
        char c2 = 'a'; // ASCII中 'a' 是 97
        System.out.println(c1 + 1); // 输出 66
        System.out.println(c2 - 32); // 输出 65 ('A')
    }
}

2. 进制转换与Java书写格式

  • 二进制转十进制:按权展开求和(如 101 -> 1×22+0×21+1×20=5 )。
  • 二转八/十六:二进制每3位转一位八进制,每4位转一位十六进制。

public class RadixDemo {
    public static void main(String[] args) {
        int num1 = 10;      // 默认十进制
        int num2 = 0b1010;  // 0b开头,二进制 (值为10)
        int num3 = 012;     // 0开头,八进制 (值为10)
        int num4 = 0xA;     // 0x开头,十六进制 (值为10)
        
        // 输出十进制结果
        System.out.println(num1); // 10
        System.out.println(num2); // 10
        
        // 将十进制转为其他进制字符串输出
        System.out.println(Integer.toBinaryString(10));  // 1010
        System.out.println(Integer.toHexString(10));     // a
    }
}

3. 八大基本数据类型与类型转换

Java有8大基本类型:byte(1), short(2), int(4), long(8), float(4), double(8), char(2), boolean(1)。

自动类型转换:小范围自动转大范围(byte -> short -> int -> long -> float -> double)。 强制类型转换:大范围转小范围,可能精度丢失溢出


public class TypeCastDemo {
    public static void main(String[] args) {
        // 1. 自动类型转换
        int i = 100;
        double d = i; // int自动转为double
        System.out.println(d); // 100.0

        // 2. int边界与溢出
        int max = Integer.MAX_VALUE; // 2147483647
        System.out.println(max + 1); // 溢出,变成 -2147483648

        // 3. 强制类型转换 (格式:(目标类型) 变量)
        double pi = 3.14;
        int piInt = (int) pi; // 精度丢失,小数部分被截断
        System.out.println(piInt); // 3
        
        // 注意:long类型赋值需要加L,float需要加F
        long bigNum = 10000000000L; 
        float f = 3.14F;
    }
}

阶段三:运算符与键盘输入

1. 算术、自增自减与赋值运算符

  • 复合赋值运算符(如 +=)隐含了强制类型转换。
  • ++ / --:前置(先自增后使用),后置(先使用后自增)。

public class OperatorDemo1 {
    public static void main(String[] args) {
        int a = 5;
        int b = a++; // 后置:先把a的值(5)给b,a再自增变6
        int c = ++a; // 前置:a先自增变7,再把值(7)给c
        System.out.println("a=" + a + ", b=" + b + ", c=" + c); // 7, 5, 7

        byte b1 = 10;
        // b1 = b1 + 1; // 报错!1是int,b1+1结果是int,不能自动赋给byte
        b1 += 1;       // 正确!+= 底层自带强制转换 (byte)(b1 + 1)
    }
}

2. 比较、逻辑与三元运算符

  • 逻辑运算符&& 和 || 具有短路效果(左边能确定结果,右边不执行)。
  • 三元运算符条件 ? 表达式1 : 表达式2

public class OperatorDemo2 {
    public static void main(String[] args) {
        int x = 5, y = 10;
        
        // 比较运算符:结果是boolean
        System.out.println(x == y); // false

        // 逻辑短路测试
        boolean flag = false;
        // 因为flag是false,&&短路,后面的 x++ 不会执行
        if (flag && (x++ > 0)) {} 
        System.out.println(x); // 依然是 5

        // 三元运算符:获取最大值
        int max = (x > y) ? x : y;
        System.out.println("最大值:" + max); // 10
    }
}

3. Scanner 键盘输入

用于接收用户在控制台输入的数据。


import java.util.Scanner; // 必须导包

public class ScannerDemo {
    public static void main(String[] args) {
        // 1. 创建Scanner对象,关联系统标准输入流
        Scanner sc = new Scanner(System.in);
        
        System.out.println("请输入您的年龄:");
        // 2. 接收整数
        int age = sc.nextInt(); 
        
        System.out.println("请输入您的名字:");
        // 3. 接收字符串
        String name = sc.next(); // 遇到空格/回车结束
        // String line = sc.nextLine(); // 接收整行,遇到回车结束
        
        System.out.println("欢迎您," + name + ",年龄:" + age);
        sc.close(); // 关闭扫描器
    }
}

阶段四:流程控制

1. 分支结构:if 与 switch

  • switch 注意事项:支持的类型(byte, short, int, char, String, 枚举)。case穿透问题(如果没有 break,会继续执行下一个case)。

public class BranchDemo {
    public static void main(String[] args) {
        int day = 2;
        
        // if 分支
        if (day == 1) {
            System.out.println("星期一");
        } else if (day == 2) {
            System.out.println("星期二");
        } else {
            System.out.println("其他");
        }

        // switch 分支 (演示 case 穿透)
        switch (day) {
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
                System.out.println("工作日"); // 1-5都会执行这里
                break; // 跳出switch,防止穿透
            case 6:
            case 7:
                System.out.println("周末");
                break;
            default:
                System.out.println("输入错误");
        }
    }
}

2. 循环结构:for、while、do-while

  • 选择原则:已知次数用 for,未知次数用 while,至少执行一次用 do-while

import java.util.Random;

public class LoopDemo {
    public static void main(String[] args) {
        // 1. for 循环:打印1-5
        for (int i = 1; i <= 5; i++) {
            System.out.print(i + " ");
        }
        System.out.println();

        // 2. while 循环
        int count = 0;
        while (count < 3) {
            System.out.println("while执行: " + count);
            count++;
        }

        // 3. do-while:至少执行一次
        int num = 10;
        do {
            System.out.println("do-while执行了!");
        } while (num < 5); // 条件不满足,但已经执行了一次

        // 4. 死循环与 break/continue
        for (int i = 1; i <= 10; i++) {
            if (i == 4) continue; // 跳过本次循环(不打印4)
            if (i == 8) break;    // 跳出整个循环(8及以后不打印)
            System.out.print(i + " "); // 输出 1 2 3 5 6 7
        }
        
        // 5. 随机数生成
        Random r = new Random();
        int randomNum = r.nextInt(10); // 生成 0-9 的随机数
        System.out.println("\n随机数: " + randomNum);
    }
}

阶段五:数组与内存管理

1. 数组基础与初始化

数组是存储同一种数据类型的连续内存空间。下标从 0 开始,长度一旦初始化不可改变


public class ArrayDemo {
    public static void main(String[] args) {
        // 1. 静态初始化:指定元素
        int[] arr1 = {10, 20, 30}; 
        // 2. 动态初始化:指定长度,系统赋默认值 (int默认0,引用类型默认null)
        int[] arr2 = new int[5]; 

        // 3. 索引访问与遍历
        arr2[0] = 100;
        for (int i = 0; i < arr2.length; i++) {
            System.out.print(arr2[i] + " "); // 100 0 0 0 0
        }
        System.out.println();

        // 4. 增强for循环 (foreach) 遍历
        for (int num : arr1) {
            System.out.print(num + " "); // 10 20 30
        }
        
        // 5. 数组越界异常
        // System.out.println(arr1[3]); // 报错:ArrayIndexOutOfBoundsException
    }
}

2. 内存存储与引用机制(核心难点)

  • 栈内存:存储局部变量(包括基本类型的值和引用类型的地址)。方法执行完自动释放。
  • 堆内存:存储 new 出来的对象和数组实体。由垃圾回收器(GC)管理。

public class MemoryDemo {
    public static void main(String[] args) {
        // arr1 是一个引用变量,存在栈中;它指向堆中的一块内存(实体)
        int[] arr1 = new int[]{1, 2, 3}; 
        
        // 将 arr1 的地址赋值给 arr2,此时两个变量指向堆中同一个数组实体
        int[] arr2 = arr1; 
        
        // 通过 arr2 修改数据
        arr2[0] = 99;
        
        // 通过 arr1 访问,发现数据也被修改了!这就是引用机制
        System.out.println(arr1[0]); // 输出 99
        
        // 如果 arr1 = null; 则 arr1 不再指向堆内存,如果 arr2 也 = null,该数组实体将被GC回收
    }
}

图解记忆arr1 和 arr2 就像两把钥匙(栈),它们开的是同一扇门(堆里的数组)。


阶段六:方法(函数)与参数传递

1. 方法的定义与调用

方法用于封装一段可复用的代码。注意:Java中方法不能嵌套定义(不能在方法里面写方法)


public class MethodDemo {
    public static void main(String[] args) {
        // 调用方法
        int result = add(5, 10);
        System.out.println("和为:" + result);
        
        // 调用重载方法
        double result2 = add(5.5, 10.2);
        System.out.println("和为:" + result2);
    }

    // 定义方法:修饰符 返回值类型 方法名(参数列表) { 方法体 }
    public static int add(int a, int b) {
        return a + b; // return 返回结果并结束方法
    }

    // 方法重载:方法名相同,参数列表不同(类型、个数、顺序)
    public static double add(double a, double b) {
        return a + b;
    }
    
    // void 方法不需要返回值,但可以用 return; 提前终止方法
    public static void printInfo(int age) {
        if (age < 0) return; // 提前结束
        System.out.println("年龄:" + age);
    }
}

2. 方法执行原理与值传递机制(面试必考)

  • 栈调用原理:方法被调用时,会在栈内存中压入一个“方法栈帧”,方法执行完毕后栈帧弹出。
  • 值传递机制
    • 基本数据类型:传递的是数据值的副本。修改形参不影响实参。
    • 引用数据类型:传递的是地址值的副本。修改形参指向的实体内容,会影响实参。

public class PassValueDemo {
    public static void main(String[] args) {
        // 1. 基本类型传递
        int x = 10;
        changeInt(x);
        System.out.println("main中的x: " + x); // 依然是 10,互不影响

        // 2. 引用类型传递
        int[] arr = {1, 2, 3};
        changeArray(arr);
        System.out.println("main中的arr[0]: " + arr[0]); // 变成了 99,因为修改了堆中的同一个实体
    }

    public static void changeInt(int num) {
        num = 100; // 只是修改了栈中局部变量 num 的值
    }

    public static void changeArray(int[] array) {
        array[0] = 99; // array 和 main 中的 arr 指向同一个堆内存,修改生效
    }
}

方法核心规则与参数传递避坑指南(深度总结)

💡 学习提示:方法和参数传递是基础阶段最核心、也是面试中最容易被问到的难点。很多初学者会卡壳,搞不清“为什么传进去的数组被修改了,而整数却没变”。下面为你系统总结,帮你彻底扫清障碍!

① 方法的三大核心规则
  1. 方法不能嵌套定义:Java中方法内部绝对不能定义另一个方法(与Python/JS不同),所有方法必须平级定义在类中。
  2. return 的双重作用
    • 有返回值的方法:结束方法并将结果返回给调用者。
    • void 无返回值的方法:写 return; 用于提前终止方法,后面代码不再执行。
  3. 方法重载(Overload)的判定
    • 发生在同一个类中,方法名相同,参数列表不同(个数、类型、顺序不同)。
    • ⚠️ 注意:仅返回值类型不同、修饰符不同,不能构成重载,会编译报错!
② 参数传递机制(核心重难点!)

结论先行:Java中只有“值传递”,没有“引用传递”! 所谓“值传递”,就是传递的是实参的副本(拷贝)。根据数据类型不同,表现分为两种:

情况A:基本数据类型(传递“数据值”的副本) 传递给方法的是具体的数值。在方法内部修改这个值,完全不会影响外部的原始变量。


public static void main(String[] args) {
    int num = 10;
    change(num);
    System.out.println("main中的num: " + num); // 输出 10,没有改变!
}
public static void change(int x) {
    x = 100; // 修改的只是 x 这个副本,不影响外面的 num
}

内存解释main 栈帧里 num=10,调用 change 时,在 change 栈帧里创建了 x,把 10 复制给 xx 变成 100 只是 change 栈帧里的事,main 里的 num 毫发无损。

情况B:引用数据类型(传递“地址值”的副本) 传递的是内存地址的副本。

  • 修改地址指向的内容 ➡️ 会影响外部原对象。
  • 给形参重新赋值(改变地址) ➡️ 不会影响外部原对象。

public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    
    // 1. 修改内容:会影响外部
    changeContent(arr);
    System.out.println("修改内容后 arr[0]: " + arr[0]); // 输出 99

    // 2. 重新赋值:不会影响外部
    changeReference(arr);
    System.out.println("重新赋值后 arr[0]: " + arr[0]); // 输出 99,没变!
}

public static void changeContent(int[] array) {
    array[0] = 99; // 顺着地址找到堆内存中的数组,修改了元素
}

public static void changeReference(int[] array) {
    array = new int[]{100, 200}; // array 指向了新地址,原 arr 依然指向老数组
}
③ 方法执行的内存原理(栈调用)

理解方法必须结合栈内存(Stack)。JVM为每个线程分配了一个栈,方法的调用就是栈帧(Stack Frame) 的压栈和弹栈:

  1. 压栈(Push):调用方法时,JVM在栈顶分配一块内存(栈帧),存储局部变量和形参。
  2. 执行:方法在自己的栈帧内执行。
  3. 弹栈(Pop):方法执行完毕(遇到 return 或到末尾),栈帧销毁,局部变量瞬间消失。

⚠️ 致命风险:如果方法自己调用自己(递归)且没有终止条件,栈帧会无限压栈,最终撑爆栈内存,抛出 StackOverflowError

④ 常见易错点与避坑指南
易错点 / 坑 详细说明与避坑建议
返回值类型不匹配 方法声明返回 int,但 returndouble 会报错。必须保证类型一致或能自动转换。
void 方法带返回值 void 方法只能写 return; 提前结束,绝对不能写 return 值;
形参和实参搞混 实参:调用时实际传入的值(如 add(5, 10))。
形参:定义时声明的变量(如 add(int a, int b))。
数组参数空指针 传入的数组如果是 null,方法内直接 arr.length 会抛 NullPointerException操作前务必判空!

💡 终极避坑代码示例(养成好习惯):


public static void printArray(int[] arr) {
    // 核心好习惯:操作引用类型前,先判断是否为null
    if (arr == null) {
        System.out.println("数组为空,无法打印!");
        return; // 提前终止,防止空指针异常
    }
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
    }
}

结语

恭喜你看到了这里!这篇文章涵盖了Java基础最核心的语法、底层存储原理、流程控制以及数组和方法的内存机制。这些内容是后续学习面向对象、集合框架、并发编程的基石

学习建议

  1. 不要只看不练:把上面的代码复制到IDEA里,自己改改参数,看看报错信息,这是进步最快的方式。
  2. 理解内存模型:遇到想不通的问题,画一画栈和堆的内存图,90%的问题都能迎刃而解。

下期预告: 在下一篇文章中,我们将进入Java的灵魂——面向对象编程(OOP)。我们会深入探讨类与对象、封装、继承、多态,以及抽象类和接口的实战应用。

如果你在学习中也感到迷茫,或者对文章中的某个知识点有疑问,欢迎在评论区留言,我们一起讨论,一起进步!点个赞和收藏,我们下一篇见!

Logo

CANN开发者社区旨在汇聚广大开发者,围绕CANN架构重构、算子开发、部署应用优化等核心方向,展开深度交流与思想碰撞,携手共同促进CANN开放生态突破!

更多推荐