Java初级学者必学内容及学习指南

前言

Java 作为一门广泛应用的编程语言,其基础内容是深入学习和开发的基石。本文将系统梳理 Java 的基础知识点,帮助初学者快速入门,也为有一定经验的开发者提供复习参考。

一、Java 概述

1. Java 特点

Java 具有诸多显著特性。它是面向对象的语言,将数据和操作封装在对象中,提高了代码的可维护性与可扩展性。同时,Java 是健壮的,异常处理机制让程序在遇到错误时能妥善应对;垃圾自动收集功能减轻了开发者管理内存的负担;强类型机制则保证了数据的安全性和稳定性。

Java 的跨平台性是其一大优势,能在多种操作系统下运行,实现 “一次编写,到处运行”。此外,Java 是解释性语言,不能直接被机器执行,需要解释器将字节码转换为机器码。

2. Java 运行机制及运行过程:跨平台性

Java 程序的运行分两步:首先使用javac命令进行编译,将.java源文件编译成.class字节码文件;然后通过java命令运行字节码文件。Java 虚拟机(JVM)是 Java 跨平台的关键,针对不同的操作系统,有对应的 JVM 版本(如 JVM for Linux、JVM for Windows、JVM for Mac)。JVM 负责执行指令、管理数据、内存和寄存器,屏蔽了底层平台的差异。

3. JDK 和 JRE

JDK(Java Development Kit)即 Java 开发工具包,是提供给 Java 开发人员使用的工具集,它包含了 JRE 和一系列开发工具,如javajavacjavadocjavap等 。JRE(Java Runtime Environment)是 Java 运行环境,由 JVM 和 Java 核心类库(Java SE 标准类库)组成。如果只是运行开发好的 Java 程序(.class文件),安装 JRE 即可。

4. 注意事项

Java 源文件的扩展名为.java,类(class)是其基本组成部分。Java 应用程序的执行入口是main()方法,其标准格式为public static void main(String[] args){...} 。Java 对大小写非常敏感,编写代码时需严格区分。

Java 方法由语句构成,每个语句以分号 “;” 结尾,大括号必须成对出现。一个源文件中最多只能有一个public类,若有,源文件名必须与该public类名一致。main方法也可以写在非public类中,运行时指定该非public类即可。

5. 转义字符

转义字符用于表示一些特殊的字符。比如\t表示一个制表位,可实现文本对齐;\n是换行符;\\表示一个反斜杠;\"表示一个双引号;\'表示一个单引号;\r表示回车。

6. 注释

注释用于解释和说明代码,提高代码可读性,且不影响程序的编译和运行。Java 中有单行注释//,用于注释一行代码;多行注释/* */,可注释一段代码,但多行注释不允许嵌套;还有文档注释/** */,主要用于生成 API 文档。

7. DOS 命令(Disk Operating System 磁盘操作系统)

在开发过程中,常使用 DOS 命令操作文件和目录。相对路径从当前目录开始定位,绝对路径从顶级目录(如C:\D:\)开始定位。常用的 DOS 命令有:查看当前目录dir,如dir d:\abc\test;切换盘符cd /D,如cd /D c:;切换目录cd,如cd d:\abc\testcd..\..\abc\test;切换到上一级目录cd..;切换到根目录cd\;查看指定目录下所有子级目录tree;清屏cls;退出 DOSexit

二、变量

1. 基本概念

变量是程序的重要组成部分,它相当于内存中一个数据存储空间的标识。变量具有三要素:类型、名称和值。变量的类型决定了它能存储的数据种类和占用的内存空间大小,名称用于在程序中引用该变量,值则是存储在变量中的数据。

2. 变量使用(先声明,后使用)

使用变量前,需先声明变量的类型和名称,如int a; ,声明后可对其进行赋值,如a = 10; 。当+号左右两边都为数值型时,执行加法运算;当有一方为字符串时,进行拼接运算,例如"hello" + 100 + 3的结果为hello1003

3. 数据类型

Java 的数据类型分为基本数据类型和引用数据类型。基本数据类型类似化学中的基本元素,是构成复杂数据结构的基础;引用数据类型则用于处理更复杂的数据,如类、接口和数组。

基本数据类型包括数值型、字符型char和布尔型boolean。数值型又分为整数类型和小数类型。

  • 整数类型:包括byte(1 字节,范围 -128 ~ 127)、short(2 字节,范围 -2^15 ~ 2^15 - 1)、int(4 字节,范围 -2^31 ~ 2^31 - 1)、long(8 字节,范围 -2^63 ~ 2^63 - 1) 。Java 整型常量默认为int类型,声明long型常量时需在值后加lL。计算机中最小存储单位是bit,基本存储单元是byte,1 byte = 8 bit。
  • 浮点数:有单精度float(4 字节,范围 -3.403E38 ~ 3.403E38)和双精度double(8 字节,范围 -1.798E308 ~ 1.798E308) 。浮点数在机器中的存放形式为浮点数 = 符号位 + 指数位 + 尾数位,由于尾数部分可能丢失,会造成精度损失,浮点数是不准确的。Java 中浮点数默认类型为double,声明float型时需加fF。还可以用科学计数法表示浮点数,如5.12e2表示5.12 * 10^2 ,5.12E - 2表示5.12 * 10^ - 2 。
  • 字符类型char用于表示单个字符,占用两个字节,能存放汉字,如char c1 = 'a'; 、char c3 = '韩'; 。字符常量用单引号(‘’)括起来,char本质上是整数,对应 Unicode 码,因此可以进行运算,输出时显示的是 Unicode 码对应的字符。
  • 布尔类型boolean类型只有truefalse两个取值,占用 1 个字节,常用于程序流程控制。

4. 字符编码表

字符编码表用于给字符分配唯一的数字编号。常见的字符编码有 ASCII 码,它规定了 128 个字符的编码,最高位为 0,后 7 位表示字符,适用于美国。ISO 8859 - 1(Latin - 1)的 0~127 位与 ASCII 相同,128~255 位中,128~159 表示一些控制字符,160~255 表示一些西欧字符。

Windows - 1252 与 ISO 8859 - 1 基本相同,区别在 128~159,它更全面,取代了 ISO 8859 - 1 编码。GB2312 是中文的第一个标准,针对简体中文常见字符,包含约 7000 个汉字和一些罕用词、繁体字。GBK 在 GB2312 基础上扩展,向下兼容,增加了 14000 个汉字,共约 21000 个汉字,包含繁体字。

GB18030 向下兼容 GBK,增加了 55000 多个字符,共 76000 多个字符,涵盖了很多少数民族字符及中日韩统一字符,采用变长编码。Big5 针对繁体中文,广泛用于台湾和香港等地。

Unicode 给世界上所有字符都分配了唯一编号,范围从 0x000000~0xFFFF,一般写成十六进制并在前面加 U + ,中文范围是 U + 4E00~U + 9FFF,兼容 ASCII 码,英文和汉字都占两个字节。UTF - 32 用四个字节表示字符编号的整数二进制形式;UTF - 16 是变长字节,U + 0000~U + FFFF 用两个字节表示,U + 10000~U + 10FFFF 用四个字节表示;UTF - 8 也是变长字节,1~6 个字节不等,字母占一个字节,汉字占 3 个字节,是使用最广泛的 Unicode 实现方式。

字符编码转换时,先根据原编码格式找到字符的 Unicode 编号,再通过该编号在目标编码的映射表中查找对应的编码格式。乱码通常是由于解析错误或编码转换错误导致的,可以使用 UltraEdit 多次尝试恢复,也可以利用 Java 处理字符串的类String来解决。

5. 数据类型转换

  • 自动类型转化:Java 在赋值或运算时,精度小的数据类型会自动转换为精度大的数据类型 ,顺序为byte < short < char < int < long < float < double 。表达式结果的类型会自动提升为操作数中最大的类型,byteshortchar在计算时会先转化为int类型,多种数据混合运算时,会先转化为容量最大的数据类型再进行计算。
  • 强制类型转换:这是自动类型转换的逆过程,将容量大的数据类型转化为容量小的数据类型,可能会导致精度降低或溢出,如int i = (int)1.9; 。强制类型转换只对最近的操作数有效,常使用小括号提升优先级。char可以保存int常量值,但不能保存int变量值,需要进行强转。
  • 基本数据类型和 String 类型:基本类型转String类型,只需将基本类型的值与空字符串相加,如String str = n + ""; 。String转基本类型,通过基本类型的包装类调用parseXX方法,如int n = Integer.parseInt(str); ,但要确保String能转化为有效的数据,否则会抛出异常导致程序终止。

三、运算符

1. 算术运算符

算术运算符用于基本的数学运算。包括正号+ 、负号- 、加法+ 、减法- 、乘法* 、除法/ 、取模(取余)% 、自增++和自减-- 。自增和自减运算符有前置和后置之分,前置(如++i)是先自增或自减后赋值,后置(如i++)是先赋值后自增或自减。整数之间做除法时,只保留整数部分,舍弃小数部分,例如int x = 10 / 3的结果是 3。取模运算a % b = a - a / b * b 。此外,+号还可用于字符串相加,实现字符串拼接。

2. 关系运算符(比较运算)

关系运算符用于比较两个值,结果为boolean型,即truefalse。常见的关系运算符有==(相等于)、!=(不等于)、<(小于)、>(大于)、<=(小于等于)、>=(大于等于)以及instanceof(检查是否是类的对象)。关系表达式常用于if结构的条件判断或循环结构的条件控制中。

3. 逻辑运算符

逻辑运算符用于连接多个条件,最终结果也是boolean值。逻辑与(&)要求所有条件都为true时,结果才为true,否则为false,且所有条件都需判断完才得出结果。逻辑或(|)只要有一个条件为true,结果就为true,只有所有条件都为false时,结果才为false ,同样需要判断完所有条件。取反(!)对变量进行非运算,true变为falsefalse变为true。异或(^)表示两个条件相同为false,不同为true

短路与(&&)和短路或(||)是具有短路功能的逻辑运算符。短路与如果第一个条件为false,后续条件不再判断,结果直接为false,效率更高;短路或若第一个条件为true,后续条件也不再判断,结果为true 。

4. 赋值运算符

赋值运算符用于给变量赋值。基本赋值运算符是= ,复合赋值运算符有+= 、-= 、/= 、*= 、%=等。复合赋值运算符会进行类型转化,且运算顺序从右往左,赋值运算符左边只能是变量。例如,a += b;等价于a = a + b; 。

5. 三元运算符

三元运算符的基本语法是:条件表达式 ? 表达式 1 :表达式 2 。其运算规则为:当条件表达式为true时,运算结果为表达式 1;当条件表达式为false时,运算结果为表达式 2。

6. 运算符优先级

运算符优先级决定了表达式中不同运算符的运算顺序。优先级高的运算符先进行运算,优先级低的后运算。例如,后缀运算符() 、[] 、.优先级最高,赋值运算符= 、+=等优先级较低。具体优先级顺序可参考相关表格,同一优先级的运算符,按照规定的关联性(如左到右或从右到左)进行运算。

7. 标识符命名规则规范及关键字

  • 标识符命名规则:包名由多个单词组成,所有字母小写,如aaa.bbb.ccc;类名和接口名多单词组成时,所有单词首字母大写,采用大驼峰命名法,如XxxYyyZzz;变量名和方法名多单词组成时,第一个单词首字母小写,后续单词首字母大写,即小驼峰命名法,如xxxYyyZzz;常量名所有字母大写,多单词时用下划线连接,如XXX_YYY_ZZZ 。
  • 关键字:Java 中有许多关键字,用于定义数据类型(如classinterfaceenumbyte等)、定义数据类型值(truefalsenull)、控制程序流程(ifelseswitch等)、定义访问权限修饰符(privateprotectedpublic)、定义类和成员的修饰符(abstractfinalstatic等)、表示类之间的关系(extendsimplements)、创建和操作对象(newthissuperinstanceof)、处理异常(trycatchfinally等)以及用于包的管理(packageimport)等。此外,还有一些保留字,虽尚未使用但可能在未来使用,编程时应避免使用,如byValuecastfuture等。

四、进制

1. 概念

在 Java 中,常用的进制有二进制、十进制、八进制和十六进制。二进制由 0 和 1 组成,满 2 进 1,以0b0B开头;十进制是最常用的进制,由 0 - 9 组成,满 10 进 1;八进制由 0 - 7 组成,满 8 进 1,以数字 0 开头;十六进制由 0 - 9 及 A - F(不区分大小写)组成,满 16 进 1,以0x0X开头 。

2. 源码补码反码

在二进制中,最高位为符号位,0 表示正数,1 表示负数。正数的原码、补码和反码相同;负数的反码是原码符号位不变,其余位取反;负数的补码是反码加 1,反之,负数的反码是补码减 1 。0 的反码和补码都是 0。计算机运算以补码方式进行,但最终运算结果要转换回原码查看。

3. 位运算

位运算直接对二进制位进行操作。按位与(&)要求两位都为 1 时结果才为 1;按位或(|)只要有一位为 1,结果就为 1;按位取反(~)将 1 变为 0,0 变为 1;按位异或(^)两个位相异为 1,相同为 0。

左移(<<)是算数左移,向左移动时右边低位补 0,高位舍弃,左移 1 位相当于乘以 2。无符号右移(>>>)是逻辑右移,向右移动时右边舍弃,左边补 0。有符号右移(>>)是算术右移,右边舍弃,左边补什么取决于符号位,符号位为 1 则补 1,为 0 则补 0。

五、程序控制结构

1. 顺序控制

顺序控制是程序最基本的执行结构,程序按照代码书写的顺序逐行执行,中间没有判断和跳转。在 Java 中,定义变量时要遵循合法的向前引用原则,即变量在使用前必须先声明。例如:

public class SequentialControl {
    public static void main(String[] args) {
        int num1 = 5;
        int num2 = 10;
        int sum = num1 + num2;
        System.out.println("两数之和为:" + sum);
    }
}

在上述代码中,先声明并初始化了num1num2两个变量,然后计算它们的和并输出结果,完全按照代码的先后顺序依次执行。

2. 分支控制

1)单分支 if

基本语法:

if (条件表达式) {
    执行代码块;(可以有多条语句)
}

条件表达式的结果为true时,会执行执行代码块中的内容;若为false,则直接跳过该代码块。例如:

public class SingleBranchIf {
    public static void main(String[] args) {
        int score = 85;
        if (score >= 60) {
            System.out.println("考试通过!");
        }
    }
}
2)双分支 if - else
if (条件表达式) {
    执行代码块1;
} else {
    执行代码块2;
}

如果条件表达式true,执行执行代码块1;否则,执行执行代码块2。比如:

public class DoubleBranchIfElse {
    public static void main(String[] args) {
        int score = 55;
        if (score >= 60) {
            System.out.println("考试通过!");
        } else {
            System.out.println("考试未通过,需要努力!");
        }
    }
}
3)多分支 if - else if - … - else
if (条件表达式1) {
    执行代码块1;
} else if (条件表达式2) {
    // 这里可以有多个else if
} else {
    执行代码块n;
}

程序会依次判断各个条件表达式,当某个条件满足时,执行对应的执行代码块,然后跳出整个多分支结构。若所有条件都不满足,则执行else后的执行代码块n。示例如下:

public class MultipleBranchIfElseIf {
    public static void main(String[] args) {
        int score = 78;
        if (score >= 90) {
            System.out.println("成绩优秀!");
        } else if (score >= 80) {
            System.out.println("成绩良好!");
        } else if (score >= 60) {
            System.out.println("成绩中等!");
        } else {
            System.out.println("成绩较差!");
        }
    }
}
4)嵌套分支

一个分支中完整地嵌套另一个完整的分支结构。例如:

public class NestedBranch {
    public static void main(String[] args) {
        int score = 75;
        int bonus = 10;
        if (score >= 60) {
            if (score + bonus >= 90) {
                System.out.println("综合评价为优秀!");
            } else {
                System.out.println("成绩合格,但综合评价未达优秀。");
            }
        } else {
            System.out.println("成绩不合格。");
        }
    }
}
5)switch 分支结构

基本语法:

switch (表达式) {
    case 常量1:
        语句块1;
        break;
    case 常量2:
        语句块2;
        break;
    // 可以有多个case
    case 常量n:
        语句块n;
        break;
    default:
        default语句块;
        break;
}

switch根据表达式的值与各个case后的常量进行匹配,若匹配成功,则执行对应case后的语句块,直到遇到break语句跳出switch结构。如果所有case都不匹配,且有default分支,则执行default后的default语句块switch适用于判断具体数值且取值不多的情况,支持的数据类型有byteshortintcharenumString。对于区间判断或结果为boolean类型的判断,更适合使用if语句。例如:

public class SwitchBranch {
    public static void main(String[] args) {
        int day = 3;
        switch (day) {
            case 1:
                System.out.println("今天是星期一");
                break;
            case 2:
                System.out.println("今天是星期二");
                break;
            case 3:
                System.out.println("今天是星期三");
                break;
            default:
                System.out.println("未知的日期");
                break;
        }
    }
}

3. 循环控制

1)for 循环

基本语法:

for (循环变量初始化; 循环条件; 循环变量迭代) {
    循环操作;
}

for循环有四个要素:循环变量初始化、循环条件、循环操作、循环变量迭代。循环开始时,先执行循环变量初始化,然后判断循环条件,若条件为true,则执行循环操作,接着进行循环变量迭代,之后再次判断循环条件,如此反复,直到循环条件为false时结束循环。例如:

public class ForLoop {
    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            System.out.println("当前循环次数:" + i);
        }
    }
}
2)while 循环

基本语法:

循环变量初始化;
while (循环条件) {
    循环体;
    循环变量迭代;
}

while循环先进行循环变量初始化,然后判断循环条件,若条件为true,则执行循环体,执行完循环体后进行循环变量迭代,接着再次判断循环条件,直到条件为false时结束循环。需要注意的是,while循环是先判断再执行语句。例如:

public class WhileLoop {
    public static void main(String[] args) {
        int i = 1;
        while (i <= 5) {
            System.out.println("当前循环次数:" + i);
            i++;
        }
    }
}
3)do…while 循环

基本语法:

循环变量初始化;
do {
    循环体(语句);
    循环变量迭代;
} while (循环条件);

do…while循环先执行循环体,然后进行循环变量迭代,最后判断循环条件。无论循环条件是否满足,do…while循环至少会执行一次循环体。例如:

public class DoWhileLoop {
    public static void main(String[] args) {
        int i = 1;
        do {
            System.out.println("当前循环次数:" + i);
            i++;
        } while (i <= 5);
    }
}
4)多重循环控制

将一个循环放在另一个循环体内,形成嵌套循环。三种循环(forwhiledo…while)都可以作为外层和内层循环。嵌套循环中,内层循环会被当作外层循环的循环体,只有内层循环条件为false时,才会完全跳出内层循环,结束外层的当次循环,开始下一次外层循环。假设外层循环执行m次,内层循环执行n次,那么内层循环总共会执行m * n次。例如:

public class MultipleLoops {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 2; j++) {
                System.out.println("i = " + i + ", j = " + j);
            }
        }
    }
}
5)continue 和 break

continue语句用于结束本次循环,跳过本次循环内后续的操作,直接进入下一次循环。在多层嵌套的循环体中,可以通过标签指明要跳过的是哪层循环。例如:

public class ContinueExample {
    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            if (i == 3) {
                continue;
            }
            System.out.println(i);
        }
    }
}

break语句用于提前结束循环。比如在数组中查找元素时,如果已经找到目标元素,就可以使用break提前结束循环。例如:

public class BreakExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};
        int target = 3;
        for (int i = 0; i < numbers.length; i++) {
            if (numbers[i] == target) {
                System.out.println("找到了目标元素,位置为:" + i);
                break;
            }
        }
    }
}

return语句用于跳出所在方法,使程序返回到调用该方法的位置继续执行。

6)foreach 语句

基本语法:

int[] arr = {1, 2, 3, 4};
for (int element : arr) {
    System.out.println(element);
}

foreach语句使用冒号: ,冒号前是循环中的每个元素(包括数据类型和变量名称),冒号后是要遍历的数组或集合。每次循环时,element会自动更新为数组或集合中的下一个元素。在仅需要简单遍历数组或集合的情况下,foreach语法更加简洁。

例如,有一个存储整数的数组,我们想要打印出数组中的每一个元素:

public class ForeachExample {
    public static void main(String[] args) {
        int[] numbers = {10, 20, 30, 40, 50};
        for (int number : numbers) {
            System.out.println(number);
        }
    }
}

上述代码中,int number表示从numbers数组中依次取出的每一个元素,在每次循环时,number会自动获取数组中的下一个值,然后执行循环体中的System.out.println(number);语句,将元素打印出来。

当涉及到对象类型的数组或集合时,foreach语句同样适用且能简化代码。假设有一个自定义的Student类:

class Student {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

现在有一个Student类型的数组,我们想要遍历并输出每个学生的信息:

public class StudentForeachExample {
    public static void main(String[] args) {
        Student[] students = {
            new Student("Alice", 20),
            new Student("Bob", 21),
            new Student("Charlie", 22)
        };

        for (Student student : students) {
            System.out.println("Name: " + student.getName() + ", Age: " + student.getAge());
        }
    }
}

在这个例子中,Student student表示从students数组中取出的每个Student对象,通过student可以方便地访问对象的属性和方法。

需要注意的是,foreach语句虽然简洁,但它也有一定的局限性。如果在遍历过程中需要对数组或集合进行删除、添加元素等操作,使用foreach语句可能会导致错误。因为foreach是基于迭代器的遍历方式,在遍历过程中修改集合结构可能会引发ConcurrentModificationException异常。这种情况下,使用传统的for循环会更加合适,因为可以通过索引来精确控制遍历和修改操作。例如:

import java.util.ArrayList;
import java.util.List;

public class ListModifyExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        // 使用普通for循环删除元素
        for (int i = 0; i < numbers.size(); i++) {
            if (numbers.get(i) == 2) {
                numbers.remove(i);
                // 注意:删除元素后,索引需要减1,避免跳过下一个元素
                i--; 
            }
        }

        System.out.println(numbers); 
    }
}

而如果使用foreach语句来尝试删除元素:

import java.util.ArrayList;
import java.util.List;

public class ListModifyWithForeachExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        // 尝试使用foreach语句删除元素,会抛出ConcurrentModificationException异常
        for (Integer number : numbers) {
            if (number == 2) {
                numbers.remove(number); 
            }
        }

        System.out.println(numbers); 
    }
}

运行上述代码会抛出异常,这体现了foreach语句在遍历和修改集合时的限制。所以在实际编程中,需要根据具体需求选择合适的遍历方式。

此外,在 Java 8 及之后的版本中,结合流(Stream)API,foreach的功能得到了进一步拓展。通过流操作,可以更灵活地对集合进行遍历、过滤、映射等操作。例如,对上述Student数组,想要筛选出年龄大于 20 岁的学生并打印其姓名:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Student {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

public class StudentStreamForeachExample {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
                new Student("Alice", 20),
                new Student("Bob", 21),
                new Student("Charlie", 22)
        );

        students.stream()
              .filter(student -> student.getAge() > 20)
              .map(Student::getName)
              .forEach(System.out::println);
    }
}

在这段代码中,students.stream()将集合转换为流,filter方法用于筛选出年龄大于 20 岁的学生,map方法将筛选后的学生对象映射为其姓名,最后通过forEach遍历并打印这些姓名。这种方式借助流 API 的链式调用,使代码更加简洁和易读,同时也提高了代码的功能性和表达力。

在嵌套集合的场景下,foreach语句同样可以发挥作用。比如有一个二维列表(List<List<Integer>>),想要打印出其中的所有元素:

import java.util.ArrayList;
import java.util.List;

public class NestedListForeachExample {
    public static void main(String[] args) {
        List<List<Integer>> nestedList = new ArrayList<>();
        List<Integer> innerList1 = new ArrayList<>();
        innerList1.add(1);
        innerList1.add(2);
        List<Integer> innerList2 = new ArrayList<>();
        innerList2.add(3);
        innerList2.add(4);
        nestedList.add(innerList1);
        nestedList.add(innerList2);

        for (List<Integer> innerList : nestedList) {
            for (Integer number : innerList) {
                System.out.print(number + " ");
            }
            System.out.println();
        }
    }
}

这里通过两层foreach循环,外层循环遍历二维列表中的每一个内层列表,内层循环遍历每个内层列表中的元素,实现了对嵌套集合的遍历和打印。

foreach语句在遍历集合时还可以结合 Lambda 表达式实现更复杂的操作。比如对一个包含整数的列表,想要计算所有偶数的平方和:

import java.util.Arrays;
import java.util.List;

public class LambdaForeachExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        int sumOfSquares = 0;
        numbers.forEach(number -> {
            if (number % 2 == 0) {
                sumOfSquares += number * number;
            }
        });
        System.out.println("所有偶数的平方和为:" + sumOfSquares);
    }
}

在这个示例中,通过foreach结合 Lambda 表达式,在遍历列表元素时进行条件判断和计算,简洁地完成了复杂的业务逻辑。这展示了foreach语句在 Java 编程中的强大灵活性和实用性,无论是简单的遍历操作还是复杂的业务处理,都能在合适的场景下发挥重要作用。

在使用foreach遍历集合时,还能借助方法引用来简化代码。例如,有一个字符串列表,想要将每个字符串转换为大写并打印:

import java.util.Arrays;
import java.util.List;

public class MethodReferenceForeachExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");
        words.forEach(String::toUpperCase);
        words.forEach(System.out::println);
    }
}

这里,String::toUpperCase是一个方法引用,它告诉foreach对每个字符串元素调用toUpperCase方法进行转换。之后再次使用foreach结合System.out::println,将转换后的字符串打印出来,使代码更加紧凑和易读。

当遍历的集合元素是自定义类,且该类实现了特定接口时,foreach可以与接口方法配合使用。假设定义一个Shape接口和实现该接口的Circle类、Rectangle类:

interface Shape {
    void draw();
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}

现在有一个Shape类型的列表,使用foreach遍历并调用每个形状的draw方法:

import java.util.ArrayList;
import java.util.List;

public class InterfaceForeachExample {
    public static void main(String[] args) {
        List<Shape> shapes = new ArrayList<>();
        shapes.add(new Circle());
        shapes.add(new Rectangle());
        shapes.forEach(Shape::draw);
    }
}

在上述代码中,shapes.forEach(Shape::draw)使得列表中的每个形状对象都调用自身的draw方法,实现了对不同形状绘制操作的统一遍历调用,充分体现了面向对象编程中多态的特性与foreach语句的结合优势。

在处理集合元素间的依赖关系时,foreach也能派上用场。例如,有一个表示任务依赖关系的列表,每个任务可能依赖于其他任务,任务类定义如下:

import java.util.ArrayList;
import java.util.List;

class Task {
    private String name;
    private List<Task> dependencies;

    public Task(String name) {
        this.name = name;
        this.dependencies = new ArrayList<>();
    }

    public void addDependency(Task task) {
        dependencies.add(task);
    }

    public void execute() {
        System.out.println("执行任务:" + name);
    }
}

假设有多个任务并构建了依赖关系,现在要按顺序执行这些任务(先执行依赖的任务):

public class DependencyForeachExample {
    public static void main(String[] args) {
        Task task1 = new Task("任务1");
        Task task2 = new Task("任务2");
        Task task3 = new Task("任务3");
        task2.addDependency(task1);
        task3.addDependency(task2);

        List<Task> tasks = new ArrayList<>();
        tasks.add(task3);

        tasks.forEach(task -> {
            task.dependencies.forEach(Task::execute);
            task.execute();
        });
    }
}

在这段代码中,tasks.forEach遍历任务列表,对于每个任务,先通过内部的task.dependencies.forEach(Task::execute)执行其所有依赖任务,然后再执行该任务本身,利用foreach简洁地处理了任务依赖关系的执行逻辑。

在处理复杂业务逻辑时,foreach语句结合自定义方法可以让代码逻辑更加清晰。例如,在一个电商系统中,有一个订单列表,订单类包含订单编号、客户信息、订单明细等。现在要对每个订单进行复杂的业务处理,如检查订单状态、计算订单总价、更新库存等。

import java.util.ArrayList;
import java.util.List;

class OrderItem {
    private String productName;
    private int quantity;
    private double price;

    public OrderItem(String productName, int quantity, double price) {
        this.productName = productName;
        this.quantity = quantity;
        this.price = price;
    }

    public double getTotalPrice() {
        return quantity * price;
    }
}

class Order {
    private String orderId;
    private String customerName;
    private List<OrderItem> orderItems;

    public Order(String orderId, String customerName) {
        this.orderId = orderId;
        this.customerName = customerName;
        this.orderItems = new ArrayList<>();
    }

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
    }

    public double calculateTotalPrice() {
        return orderItems.stream()
               .mapToDouble(OrderItem::getTotalPrice)
               .sum();
    }
}

public class EcommerceForeachExample {
    public static void main(String[] args) {
        Order order1 = new Order("1001", "Alice");
        order1.addOrderItem(new OrderItem("Book", 2, 20.0));
        order1.addOrderItem(new OrderItem("Pen", 5, 5.0));

        Order order2 = new Order("1002", "Bob");
        order2.addOrderItem(new OrderItem("Notebook", 3, 15.0));

        List<Order> orders = new ArrayList<>();
        orders.add(order1);
        orders.add(order2);

        orders.forEach(EcommerceForeachExample::processOrder);
    }

    private static void processOrder(Order order) {
        System.out.println("处理订单:" + order.orderId);
        double totalPrice = order.calculateTotalPrice();
        System.out.println("订单总价:" + totalPrice);
        // 模拟检查订单状态、更新库存等其他业务逻辑
        System.out.println("订单状态检查通过,库存更新完成");
        System.out.println();
    }
}

在上述代码中,processOrder方法封装了对单个订单的复杂业务处理逻辑。通过orders.forEach(EcommerceForeachExample::processOrder),对订单列表中的每个订单进行统一处理,使得代码结构清晰,易于维护和扩展。

在 Java 的集合框架中,foreach还可以用于遍历不同类型的集合,如SetMap。对于Set集合,它不允许包含重复元素,遍历方式与List类似,但顺序可能是无序的(取决于具体的Set实现类,如HashSet是无序的,TreeSet是有序的)。例如:

import java.util.HashSet;
import java.util.Set;

public class SetForeachExample {
    public static void main(String[] args) {
        Set<String> fruits = new HashSet<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        fruits.forEach(System.out::println);
    }
}

在这个例子中,使用HashSet存储水果名称,通过foreach遍历并打印出每个水果名称。

对于Map集合,它存储的是键值对(key - value pairs)。foreach遍历Map时,可以使用entrySet()方法获取键值对集合,然后分别访问键和值。例如:

import java.util.HashMap;
import java.util.Map;

public class MapForeachExample {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        scores.put("Bob", 85);
        scores.put("Charlie", 95);

        scores.forEach((key, value) -> System.out.println(key + "的分数是:" + value));
    }
}

在上述代码中,scores.forEach((key, value) -> System.out.println(key + "的分数是:" + value))使用foreach遍历scores这个Map集合,key代表学生姓名,value代表学生分数,通过 Lambda 表达式打印出每个学生的姓名和分数。

此外,foreach在处理并行流(Parallel Stream)时也有独特的表现。并行流可以利用多核处理器的优势,提高遍历和处理集合元素的效率。例如,有一个包含大量整数的列表,要对每个整数进行平方操作并求和:

import java.util.Arrays;
import java.util.List;

public class ParallelStreamForeachExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        long sumOfSquares = 0;

        sumOfSquares = numbers.parallelStream()
               .mapToLong(n -> n * n)
               .sum();

        System.out.println("平方和为:" + sumOfSquares);
    }
}

在这段代码中,numbers.parallelStream()将列表转换为并行流,mapToLong(n -> n * n)对每个元素进行平方操作,sum()计算平方和。使用并行流可以在处理大规模数据时显著提高计算速度,但需要注意线程安全问题,因为并行处理可能会导致多个线程同时访问和修改共享资源。

六、数组、排序、查找

1. 基本概念

数组是一种引用类型的数据类型,可用于存放多个同一类型的数据。其定义方式有多种,例如:

// 声明并创建一个能存放5个整数的数组
int[] arr1 = new int[5];
// 先声明,后创建
int[] arr2;
arr2 = new int[10];
// 直接初始化数组
int[] arr3 = {1, 2, 3};
int[] arr4 = new int[]{1, 2, 3};

需要注意,数组不能在给定初始值的同时给定长度,像int[] arr = new int[3]{1, 2, 3};这种写法是错误的。数组中的元素可以是任何数据类型,但不能混用。数组创建后若未赋值,会有默认值,如intshortbytelong类型的默认值是0floatdouble类型的默认值是0.0char类型的默认值是\u0000boolean类型的默认值是falseString类型的默认值是null 。数组属于引用类型,数组型数据是对象(object),在默认情况下是引用传递,赋值时传递的是地址,在内存中,栈中存放数据地址,堆中存放数据内容。

2. 排序

排序分为内部排序和外部排序。内部排序是将所有需要处理的数据都加载到内部存储器中进行排序,常见的内部排序算法包括交换式排序法(如冒泡排序)、选择式排序法和插入式排序法。外部排序则是在数据量过大,无法全部加载到内存中时,借助外部存储进行排序,例如合并排序法和直接合并排序法 。

以冒泡排序法为例,它通过对排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部。以下是冒泡排序的代码实现:

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换arr[j]和arr[j + 1]
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
3. 查找

常见的查找算法有顺序查找和二分查找。顺序查找是从数组的第一个元素开始,逐个与目标元素进行比较,直到找到目标元素或遍历完整个数组。二分查找则要求数组是有序的,它通过不断将数组分成两部分,比较中间元素与目标元素的大小,来缩小查找范围,从而提高查找效率。

以下是顺序查找的代码示例:

public class SequentialSearch {
    public static int sequentialSearch(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1; // 表示未找到
    }

    public static void main(String[] args) {
        int[] arr = {10, 20, 30, 40, 50};
        int target = 30;
        int result = sequentialSearch(arr, target);
        if (result != -1) {
            System.out.println("目标元素在数组中的索引为:" + result);
        } else {
            System.out.println("未找到目标元素");
        }
    }
}

二分查找的代码示例:

public class BinarySearch {
    public static int binarySearch(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1; // 表示未找到
    }

    public static void main(String[] args) {
        int[] arr = {10, 20, 30, 40, 50};
        int target = 30;
        int result = binarySearch(arr, target);
        if (result != -1) {
            System.out.println("目标元素在数组中的索引为:" + result);
        } else {
            System.out.println("未找到目标元素");
        }
    }
}
4. 二维数组

二维数组可以看作是数组的数组,其语法如下:

// 方式一
int[][] a = new int[2][3];
// 方式二
int[][] b = new int[2][];
// 方式三
int[][] c = {{1, 1, 1}, {2, 2}, {3}};

二维数组的声明方式有int[][] arr;int[] arr[];int arr[][];这几种。在使用二维数组时,可以通过两个索引来访问其中的元素,例如a[0][1]表示访问a数组中第一行第二列的元素 。二维数组在处理矩阵运算、表格数据等场景中非常有用。例如,计算一个二维数组(矩阵)的转置:

public class TransposeMatrix {
    public static void main(String[] args) {
        int[][] matrix = {
                {1, 2, 3},
                {4, 5, 6},
                {7, 8, 9}
        };
        int rows = matrix.length;
        int cols = matrix[0].length;
        int[][] transpose = new int[cols][rows];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                transpose[j][i] = matrix[i][j];
            }
        }
        for (int i = 0; i < cols; i++) {
            for (int j = 0; j < rows; j++) {
                System.out.print(transpose[i][j] + " ");
            }
            System.out.println();
        }
    }
}

在上述代码中,首先定义了一个二维数组matrix,然后创建了一个转置后的二维数组transpose,通过两层循环将原矩阵的行和列进行交换,实现矩阵的转置,并打印出转置后的矩阵。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值