我把Java基础编程及思维导图整理的超级详细,小白都能看懂

目录:

  • Java学习导图
  • 一、Java基本语法
  • 1.关键字与标识符 2.变量分类 3.运算符 4.流程控制
  • 二、数组
  • 1.数组概述 2.一维数组 3.二维数组 4.数组常见算法 5.Arrays工具类使用
  • 三、面向对象
  • Java类及其类成员三大特性(封装、继承、多态)关键字

Java学习思维导图

Java基本语法

关键字与标识符

定义变量格式变量类型 变量名 = 变量值;变量类型 变量名;变量名 = 变量值;

变量使用注意点

① 变量必须先声明,后使用② 变量都定义在其作用域内。在作用域内,它是有效的。换句话说,出了作用域,就失效了③ 同一个作用域内,不可以声明两个同名的变量

基本数据变量运算规则

  1. 自动类型转换:结论:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。byte 、char 、short –> int –> long –> float –> double特别的:当byte、char、short三种类型的变量做运算时,结果为int型

说明:此时的容量大小指的是,表示数的范围的大和小。比如:float容量要大于long的容量

  1. 强制类型转换:

1.需要使用强转符:()2.注意点:强制类型转换,可能导致精度损失。

算术运算符: + – + – * / % (前)++ (后)++ (前)– (后)– +1.(前)++ :先自增1,后运算(后)++ :先运算,后自增12.(前)– :先自减1,后运算(后)– :先运算,后自减13.连接符:+:只能使用在String与其他数据类型变量之间使用。

复制运算符:= += -= *= /= %=1.运算的结果不会改变变量本身的数据类型2.开发中,如果希望变量实现+1的操作,有几种方法?(前提:int num = 10;)//方式一:num = num + 1;//方式二:num += 1;//方式三:num++; (推荐)

比较运算符: == != > < >= <= instanceof1.比较运算符的结果是boolean类型2.> < >= <= :只能使用在数值类型的数据之间。3. == 和 !=: 不仅可以使用在数值类型数据之间,还可以使用在其他引用类型变量之间。

逻辑运算符:& && | || ! ^1.逻辑运算符操作的都是boolean类型的变量。而且结果也是boolean类型

2.区分& 与 &&相同点1:& 与 && 的运算结果相同相同点2:当符号左边是true时,二者都会执行符号右边的运算不同点:当符号左边是false时,&继续执行符号右边的运算。&&不再执行符号右边的运算。开发中,推荐使用&&

3.区分:| 与 ||相同点1:| 与 || 的运算结果相同相同点2:当符号左边是false时,二者都会执行符号右边的运算不同点3:当符号左边是true时,|继续执行符号右边的运算,而||不再执行符号右边的运算开发中,推荐使用||

位运算符:<< >> >>> & | ^ ~

  1. 位运算符操作的都是整型的数据
  2. << :在一定范围内,每向左移1位,相当于 * 2>>: 在一定范围内,每向右移1位,相当于/2

三元运算符:(条件表达式)? 表达式1 : 表达式2

说明① 条件表达式的结果为boolean类型② 根据条件表达式真或假,决定执行表达式1,还是表达式2.如果表达式为true,则执行表达式1。如果表达式为false,则执行表达式2。③ 表达式1 和表达式2要求是一致的。④ 三元运算符可以嵌套使用

凡是可以使用三元运算符的地方,都可以改写为if-else反之,不成立。

如果程序既可以使用三元运算符,又可以使用if-else结构,那么优先选择三元运算符。原因:简洁、执行效率高。

分支结构:1.if-else条件判断结构

结构一:if (条件表达式) {执行表达式}

结构二:二选一if (条件表达式) {执行表达式1}else{执行表达式2}

结构三:n选一if (条件表达式) {执行表达式1}else if (条件表达式) {执行表达式2}else if (条件表达式) {执行表达式3}…else{执行表达式n}

2.switch-case选择结构

switch (表达式) {case 常量1:执行语句1;break;case 常量2:执行语句2;break;…default:执行语句n;break;}

循环结构:

1.循环结构的四要素

① 初始化条件② 循环条件 —>是boolean类型③ 循环体④ 迭代条件说明:通常情况下,循环结束都是因为②中循环条件返回false了。

2.三种循环结构:

2.1 for循环结构

for(①;②;④){③}执行过程:① – ② – ③ – ④ – ② – ③ – ④ – … – ②

2.2 while循环结构

①while(②){③;④;}执行过程:① – ② – ③ – ④ – ② – ③ – ④ – … – ②说明:写while循环千万小心不要丢了迭代条件。一旦丢了,就可能导致死循环!

for和while循环总结:

开发中,基本上我们都会从for、while中进行选择,实现循环结构。for循环和while循环是可以相互转换的!区别:for循环和while循环的初始化条件部分的作用范围不同我们写程序,要避免出现死循环。

2.3 do-while循环结构

①do{③;④;}while(②);执行过程:① – ③ – ④ – ② – ③ – ④ – … – ②

说明:1.do-while循环至少会执行一次循环体!2.开发中,使用for和while更多一些。较少使用do-while

1.数组理解: 数组(Array),是多个相同类型数据一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。

2.数组相关的概念:

数组名元素角标、下标、索引数组的长度:元素的个数

3.数组的特点:

  1. 数组是序排列的
  2. 数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型
  3. 创建数组对象会在内存中开辟一整块连续的空间
  4. 数组的长度一旦确定,就不能修改。

4. 数组的分类:① 二维数:一维数组、二维数组、。。。② 照数组元素的类型:基本数据类型元素的数组、引用数据类型元素的数组

1.一维数组的声明与初始化

错误的方式:// int[] arr1 = new int[];// int[5] arr2 = new int[5];// int[] arr3 = new int[3]{1,2,3};

2.一维数组元素的引用:通过角标的方式调用。数组的角标(或索引从0开始的,到数组的长度-1结束)

3.数组的属性:lengthSystem.out.println(names.length);//5System.out.println(ids.length);

说明:数组一旦初始化,其长度就是确定的。arr.length数组长度一旦确定,就不可修改。

4.一维数组的遍历for(int i = 0;i < names.length;i++){System.out.println(names[i]);}

5.一维数组元素的默认初始化值> 数组元素是整型:0> 数组元素是浮点型:0.0> 数组元素是char型:0或’\\u0000’,而非’0’> 数组元素是boolean型:false> 数组元素是引用数据类型:null

1.如何理解二维数组?数组属于引用数据类型数组的元素也可以是引用数据类型一个一维数组A的元素如果还是一个一维数组类型的,则,此数组A称为二维数组。

2.二维数组的声明与初始化

错误的方式:// String[][] arr4 = new String[][4];// String[4][3] arr5 = new String[][];// int[][] arr6 = new int[4][3]{{1,2,3},{4,5},{6,7,8}};

3.如何调用二维数组元素:System.out.println(arr1[0][1]);//2System.out.println(arr2[1][1]);//null

4.遍历二维数组元素

1.数组的创建与元素赋值:杨辉三角(二维数组)、回形数(二维数组)、6个数,1-30之间随机生成且不重复。

2.针对于数值型的数组:最大值、最小值、总和、平均数等

3.数组的复制与复制int[] array1,array2;array1 = new int[]{1,2,3,4};3.1 赋值:

array2 = array1;如何理解:将array1保存的数组的地址值赋给了array2,使得array1和array2共同指向堆空间中的同一个数组实体。

3.2 复制:

array2 = new int[array1.length];for(int i = 0;i < array2.length;i++){array2[i] = array1[i];}如何理解:我们通过new的方式,给array2在堆空间中新开辟了数组的空间。将array1数组中的元素值一个一个的赋值到array2数组中。

1.理解:① 定义在java.util包下。② Arrays:提供了很多操作数组的方法。

2.使用:

1.类的设计中,两个重要结构之一:属性

对比:属性 vs 局部变量

1.相同点:1.1 定义变量的格式:数据类型 变量名 = 变量值1.2 先声明,后使用1.3 变量都其对应的作用域

2.不同点:

2.1 在类中声明的位置的不同属性:直接定义在类的一对{}内局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量

2.2 关于权限修饰符的不同属性:可以在声明属性时,指明其权限,使用权限修饰符。常用的权限修饰符:private、public、缺省、protected —>封装性目前,大家声明属性时,都使用缺性就可以了。局部变量:不可以使用权限修饰符。

2.3 默认初始化值的情况:属性:类的属性,根据其类型,都默认初始化值。整型(byte、short、int、long:0)浮点型(float、double:0.0)字符型(char:0 (或’\\u0000’))布尔型(boolean:false)

2.4 在内存中加载的位置:属性:加载到堆空间中 (非static)局部变量:加载到栈空间

2.类的设计中,两个重要结构之二:方法方法:描述类应该具的功能。

  • public void eat(){}
  • public void sleep(int hour){}
  • public String getName(){}
  • public String getNation(String nation){}

2. 方法的声明:权限修饰符 返回值类型 方法名(形参列表){方法体}

3. 说明:3.1 关于权限修饰符:默认方法的权限修饰符先都使用publicJava规定的4种权限修饰符:private、public、缺省、protected –>封装性再细说

3.2 返回值类型: 返回值 vs 没返回值

3.2.1 如果方法返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用 return关键字来返回指类型的变量或常量:“return 数据”。如果方法没返回值,则方法声明时,使用void来表示。通常,没返回值的方法中,就不需要使用return.但是,如果使用的话,只能“return;”表示结束此方法的意思。

构造器(或构造方法):

  • 1.创建对象
  • 2.初始化对象的信息

使用说明:1.如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器2.定义构造器的格式:权限修饰符 类名(形参列表){}3.一个类中定义的多个构造器,彼此构成重载4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器5.一个类中,至少会有一个构造器。

面向对象的特征一:封装与隐藏

1.为什么要引入封装性?我们程序设计追求“高内聚,低耦合”。高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;低耦合 :仅对外暴露少量的方法用于使用。

隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

2.封装性思想具体的代码体现:体现一:将类的属性xxx私化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值private double radius;public void setRadius(double radius){this.radius = radius;}public double getRadius(){return radius;}体现二:不对外暴露的私有的方法体现三:单例模式(将构造器私有化)体现四:如果不希望类在包外被调用,可以将类设置为缺省的。

3.Java规定的四种权限修饰符

3.1 权限从小到大顺序为:private < 缺省 < protected < public3.2 具体的修饰范围:3.3 权限修饰符可用来修饰的结构说明:

4.种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类修饰类的话,只能使用:缺省、public

面向对象特征二:继承性

1.继承性的格式:class A extends B{}

  • A:子类、派生类、subclass
  • B:父类、超类、基类、superclass

2.子类继承父类以后有哪些不同?

2.1体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私的结构。只因为封装性的影响,使得人类不能直接调用父类的结构而已。

2.2 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。

  • 子类和父类的关系,不同于子集和集合的关系。
  • extends:延展、扩展

3.Java中继承性的说明

3.1.一个类可以被多个子类继承。

3.2.Java中类的单继承性:一个类只能有一个父类3.3.子父类是相对的概念。3.4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类3.5.子类继承父类以后,就获取了直接父类以及所间接父类中声明的属性和方法

面向对象的特性三:多态性

1.多态性的理解:可以理解为一个事物的多种形态。

2.何为多态性:对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)举例:Person p = new Man();Object obj = new Date();

3.多态性的使用:虚拟方法调用有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。总结:编译,看左边;运行,看右边。

4.多态性的使用前提:① 类的继承关系 ② 方法的重写

6.多态性使用的注意点:对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)

关键字:this1.可以调用的结构:属性、方法;构造器

2.this调用属性、方法:this理解为:当前对象 或 当前正在创建的对象

2.1 在类的方法中,我们可以使用\”this.属性\”或\”this.方法\”的方式,调用当前对象属性或方法。但是通常情况下,我们都择省略\”this.\”。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用\”this.变量\”的方式,表明此变量是属性,而非形参。

2.2 在类的构造器中,我们可以使用\”this.属性\”或\”this.方法\”的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都择省略\”this.\”。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用\”this.变量\”的方式,表明此变量是属性,而非形参。

3.this调用构造器:

① 我们在类的构造器中,可以显式的使用\”this(形参列表)\”方式,调用本类中指定的其他构造器② 构造器中不能通过\”this(形参列表)“方式调用自己③ 如果一个类中有n个构造器,则最多有 n – 1构造器中使用了\”this(形参列表)”④ 规定:\”this(形参列表)“必须声明在当前构造器的首行⑤ 构造器内部,最多只能声明一个\”this(形参列表)”,用来调用其他的构造器

关键字:abstractabstract: 抽象的1.可以用来修饰:类、方法2.具体的:abstract修饰类:抽象类

abstract修饰方法:抽象方法

3.注意点:

1.abstract不能用来修饰:属性、构造器等结构2.abstract不能用来修饰私方法、静态方法、final的方法、final的类

不讲道理,这本神作就是零废话教你用Java干活

新手学习 Java 的第一步,总是找高手们推荐的经典好书开啃。豆瓣评分 9.3 的《Java 核心技术》就是这样一套神作,位列“Java 编程四大名著”之一,从基础入门到高级知识,系统化地讲解了 Java 语言的各个方面。

但当书拿到手的时候,新手们心里却有些犯怵,两卷本加起来足有 1300 多页的篇幅,难道非要把里面的知识都学完才能上手干活吗?

还真不是!Java 虽然包含了众多的概念、特性和最佳实践,但实际上只需要掌握好几个核心领域就能干活了,包括基础语法、面向对象编程、集合框架、多线程编程、常用类库、基本 Web 开发知识等。

《Java 核心技术》的作者霍斯特曼大神,就为 Java 程序员们准备了一条学习的捷径——《Java核心技术速学版(第3版)》,从书名就能看出来,本书是对《Java 核心技术》的高度凝炼,提取出供开发者开箱即用的法宝。

本书的篇幅也足以让读者放心“速学”,16 开本总共 276 页,不讲道理,没有废话,全是干货。

但可能有人又会提出疑问,篇幅倒是大大缩短了,但知识确定够用吗?我们就来对这个问题一探究竟。

先回答上面提出的问题,《Java核心技术速学版(第3版)》篇幅缩短,但内容绝对没有缩水,该有的全都有。

本书紧扣 Java 17 的新特性,涵盖了Java 的基础知识,如基本程序结构、面向对象编程、容器、流、I/O 处理、接口和 Lambda 表达式,还深入探讨了高级主题,包括泛型编程、并发编程、注解、日期和时间 API、国际化、编译和脚本,以及 Java 平台模块系统。

《Java核心技术》与之相比在内容上则大而全,针对知识点的讨论往往透彻而深入,会对不同编程范式的优劣展开仔细的论证。这种技术的思辨十分有利于读者吃透原理,修炼内功。

《Java核心技术速学版(第3版)》则略去了那些讲道理的论述过程,直接给出结论,告诉读者在工作中的最优实践是什么,实际工作中的问题该如何解决。这本书就是帮助 Java 程序员,以最快的速度成为一名称职的开发。

所以本书真的是“零废话”,直接切入主题,以最简洁的方式传递信息,确保读者能够快速吸收和应用。工作已经足够忙碌,程序员们不想浪费时间 ,给他们 就好。

大神凯·S.·霍斯特曼(Cay S. Horstmann)既然已经写出了《Java核心技术》这样一部巨著,为何还要写一本配套的“速学版”呢?

这是因为霍斯特曼教授看到 Java 语言使用广泛,语言特性也在不断推陈出新,但许多初学者在扑面而来的大量知识中容易迷失,所学不知如何用到工作中。所以大神编写“速学版”就是旨在提供一种更加紧凑和集中的学习体验,专注于 Java 最关键、最核心的应用。

霍斯特曼大神的著作曾获软件业“奥斯卡奖”之称的 Jolt 大奖,他是一位著名的计算机科学作家、教授,也是一名 Java Champion。他在计算机编程领域,尤其是 Java 编程语言方面,有着深厚的知识和丰富的经验。

跟着大神学 Java,学会了这些就赶紧开工吧。

对于一名想要在工作中得心应手的 Java 程序员来说,需要掌握以下核心知识和方法。

  • 基本编程结构:了解 Java 的基本语法和编程结构。
  • 面向对象编程:深入 Java 的面向对象特性,学会设计和实现灵活、可重用的代码。
  • 接口和 Lambda 表达式:探索接口的高级用法和 Lambda 表达式用法。
  • 继承与反射:通过继承和 Java 的反射 API,提高代码的复用性和灵活性。
  • 异常、断言和日志:学会有效地处理异常、使用断言和日志记录,确保代码的健壮性。
  • 泛型编程:掌握泛型,编写类型安全和灵活的代码。
  • 容器:深入了解 Java 集合框架,高效管理数据集合。
  • :学习使用 Stream API 处理数据流,实现高效的数据处理。
  • 输入和输出处理:掌握 Java 的 I/O 机制,进行文件和数据流的操作。
  • 并发编程:了解并发编程的基本概念和工具,编写高效并行的程序。
  • 注解:学习使用注解来提供元数据,并增强代码的表达能力。
  • 日期和时间 API:掌握 Java 8 引入的 java.time API,处理日期和时间。
  • 国际化:了解如何让应用程序支持多语言和文化。
  • 编译和脚本:学习 Java 编译和脚本的相关知识,提高开发效率。
  • Java平台模块系统:掌握 Java 模块系统,构建大型和复杂的应用程序。

可以看到,本书内容高度聚焦于 Java 语言本身,并没有去讨论一个单链表该如何实现,也没有实现一个简单的 Web 应用。但读者们会发现,无论开发的应用是什么形态,都是上述知识点的组合。

跟随本书学好以上内容,读者可以编写出结构清晰、易于维护的 Java 代码,并利用面向对象的原则设计复杂的系统。对于异常和错误也能进行处理,编写出健壮的应用程序。

《Java核心技术速学版(第3版)》是一本“零废话”的 Java 程序员职场生存手册,本书以其简明直接的技术讲解和强大的实用性,帮助初学者快速成长为一名合格的 Java 开发工程师。

本书内容提取自《Java 核心技术》,但仍然做到了全面而精炼,覆盖了Java 编程的各个方面,从基础语法到高级特性,还提供了丰富的代码示例和配套的学习资源。

本书有以下特点:

  • 技术点简明直接:省略冗长的讨论,直接切入 Java 编程的核心,让读者能够迅速抓住每个知识点的要点。
  • 实用性强:书中的示例和讲解都注重实际应用,帮助读者理解如何在真实项目中运用 Java 技术解决问题。
  • 技术覆盖面广:从面向对象编程的基本原则到 Java 17 的新特性,接口与 Lamda 表达式,泛型编程与并发编程等。
  • 代码示例丰富:提供了大量的实用代码示例,使读者能够通过实例学习,更好地理解和掌握 Java 编程。
  • 配套资源齐全:包括全书示例源代码、实战项目源码 + 教程,随书附赠 Java 学习效率手册,为读者提供全方位的学习支持。

本书适合小白入门并成长为 Java 开发者,Java 老手也可以放在手边作为参考书,快速回顾 Java 知识点,温故而知新。

《Java核心技术速学版(第3版)》就是这样一本书,不讲道理,不废话,只为 Java 程序员而写!

小任务大能量:Java 在小型编程场景中的崛起

在编程的广阔世界里,Java 一直以来都以其适用于大型、长期项目而闻名遐迩。然而,你可能想不到,Java 在小型任务中的表现同样出色,甚至让人眼前一亮。今天,就让我们一同走进 Java 在小型任务中的奇妙天地,探寻它为何能在这个领域崭露头角。

在日常工作中,我们常常会遇到各种各样繁琐且重复的小任务,比如文件的整理、内容的转换等。最初,我们可能会像大多数人一样,选择使用 shell 脚本来解决这些问题。毕竟,脚本编写起来简单快捷,对于一些基本的操作似乎游刃有余。然而,随着任务的深入,特殊情况层出不穷,原本简洁的脚本逐渐变得臃肿不堪,如同乱麻一般难以理清。就像一个原本整洁的房间,随着物品的不断增加,变得杂乱无章,让人无从下手。

这时,Python 或许会成为很多人的下一个尝试对象。它确实拥有简洁的语法和丰富的库,看起来是个不错的选择。但实际使用中,Python 的 API 并非完美无缺,其动态类型特性虽然带来了灵活性,却也埋下了隐患。在处理复杂逻辑时,动态类型可能导致类型错误在运行时才暴露出来,这使得调试过程变得漫长而痛苦,就像在黑暗中摸索,找不到问题的根源。

而 Java 则不同,它的静态类型系统就像是一位严谨的守护者,在编译阶段就能发现许多潜在的错误。这就好比在出发前就检查好装备,避免了在旅途中因为装备问题而陷入困境。对于熟悉 Java 的人来说,其强大的 API 更是一大助力。无论是处理文件、操作集合还是运用正则表达式,Java 的 API 都提供了丰富且高效的功能。就像拥有一个装满工具的工具箱,总能找到合适的工具来解决问题。

以往,使用 Java 开发项目往往需要创建复杂的项目结构,如单独的 POM 文件和规范的 <beginning of the code>src/main/java<end of the code> 层次结构。这对于小型任务来说,无疑是一种沉重的负担,就像为了喝一杯水而要准备一整套复杂的茶具一样繁琐。但幸运的是,现代 Java 和相关工具已经不再强制要求这样做。如今,我们可以更加自由地编写 Java 代码来处理小型任务,无需被繁琐的配置束缚手脚。

以一个验证备份是否有效的任务为例,我们需要每天从目录树中随机选取十个文件。使用 Java 来实现这个脚本时,JEP 330 和 JEP 458 这两个特性发挥了巨大作用。现在,我们只需将代码放在 .java 文件中,然后直接使用命令 java RandomFiles.java 10 /home/cay/data 就能运行。每次运行脚本时,文件会自动即时编译,这在开发和后续修改过程中为我们提供了极大的便利。在开发阶段,我们可以随时修改代码并立即看到效果,就像在画布上作画,可以随时调整笔触和颜色。而且在日常使用中,即时编译的速度也完全可以接受,并不会让人感到明显的延迟。

(二)实例主方法与隐式类:简洁代码新方式

JEP 477 为编写小型 Java 程序带来了新的变革。在过去,编写一个简单的 Java 程序,总是需要重复地编写 public static void main 方法,这对于初学者来说可能是一个小小的困扰,就像在学习走路时,总是被一根小绳子绊倒。而现在,有了这个特性,我们可以摆脱这种繁琐。例如:

在这样的代码中,不再需要显式地定义类和使用 static 关键字。从技术上讲,任何带有顶级 main 方法的 Java 文件都会自动成为一个隐式类,其中的实例变量和方法就是文件中的顶级变量和方法。并且,我们还可以在隐式类中定义其他类、接口、枚举或记录,它们会成为嵌套类型。这使得代码结构更加清晰简洁,就像整理好的书架,每一本书都在它应该在的位置。

同时,java.base 模块会自动导入,这为我们节省了大量的导入语句。截至 Java 23,java.io.IO 中的三个方法 println、print 和 readln 也会自动导入。这对于编写脚本来说非常方便,虽然从教学角度看可能需要记住更多的知识点,但在实际编写脚本时,确实减少了很多工作量。

(三)记录与枚举:增强代码可读性

在处理数据聚合时,Python 程序员常常会使用临时字典(即映射)。而在 Java 中,我们有更优雅的解决方案 —— 记录(Records)。例如:

记录不仅使代码更易于阅读,还为我们提供了一个自然的地方来添加方法。比如:

同样,Java 的枚举(Enums)也比 Python 中的更加简洁明了。例如:

相比之下,Python 的枚举显得有些笨拙。

  1. 灵活使用 var:在复杂的程序中,对于 var 的使用可能需要谨慎,只有在类型非常明显的情况下才会使用,例如 var builder = new StringBuilder();。但在脚本中,我们可以更加自由地使用 var,它的语法比 Python 更好,因为可以清晰地区分声明和赋值。这就像在写作中,根据不同的情境选择不同的表达方式,使代码更加简洁流畅。
  2. 积极运用静态导入:通过静态导入,我们可以直接使用类中的静态成员,而无需每次都写出完整的类名。例如:
  3. import static java.lang.Math.*;diagonal = sqrt(pow(width, 2) + pow(height, 2));
  4. 巧用文本块:文本块在处理与代码相关的数据时非常有用,它类似于脚本中的 “here documents”。虽然目前还希望能尽快支持插值功能,但在这之前,我们可以使用 String.formatted 来处理可变文本部分。这就像在信件中插入个性化的内容,使代码和数据的结合更加紧密。

Java 的标准库在处理字符串、正则表达式、集合和日期 / 时间等方面表现出色,其文档也非常详尽。例如,读取文件内容可以简单地使用 Files.readString(Path.of(filename));,这比其他语言(如 Python、JavaScript 或 Bash)中的等效操作更加方便和高效。

在运行外部进程时,也有相应的辅助方法:

并且,自从 JEP 400 之后,我们可以默认使用 UTF – 8 编码,无需担心编码问题。对于 HTTP 操作,Java 提供了 HTTPClient(JEP 321)和简单的 Web 服务器(JEP 408),方便我们进行网络通信。虽然 XML 支持的 API 有些陈旧和繁琐,但至少能够稳定地工作。

然而,Java 标准库也存在一些不足之处,比如缺少 JSON 处理和命令行处理的功能。对于大型 Java 项目来说,这并不是什么大问题,只需添加相应的第三方库(如 Jackson 或 PicoCLI)到 POM 文件中即可。但对于编写脚本来说,手动下载并添加这些库的依赖关系就比较麻烦。一个解决办法是使用简单的单文件库,如 Essential JSON 和 JArgs,只需将文件与脚本放在同一目录即可。

在脚本中,当出现错误时,有时直接终止并显示堆栈跟踪是可以接受的。但按照 Java 的规则,我们仍然需要声明或捕获检查异常。在大型程序中,这是合理的做法,但在脚本中可能会感觉有些繁琐。最简单的解决方法是在可能抛出检查异常的每个方法(包括 main 方法)中添加 throws Exception。

不过,在使用 lambda 表达式处理文件流时,检查异常会带来一些问题。例如,当我们想要使用 streamOfPaths.map(Files::readString) 时,由于 readString 方法可能抛出 IOException,所以无法直接这样使用。正确的做法是处理这个异常,比如返回空字符串、记录异常或者将其转换为 UncheckedIOException。当然,在脚本中,如果不太在意这些异常,也可以使用一些 “sneaky throw” 库(如 Sneaky Fun)来简化处理。这些库利用了 Java 类型系统的一个漏洞,通过巧妙的泛型使用,将带有 throws 声明的方法转换为没有声明的方法。但需要注意的是,这种方法不适合大型和严肃的程序,在脚本规模较小时可以使用,一旦脚本变得复杂,就需要采用更规范的异常处理方式。

编写脚本时,使用简单的文本编辑器显然不是一个好选择。Java 的优势在于其静态类型系统,而 IDE 可以充分利用这一点,为我们提供代码自动完成、实时错误提示等功能,就像一位贴心的助手,时刻提醒我们避免犯错。

在开始编写小型 Java 程序时,我通常会选择一款中等重量的编辑器,如 Visual Studio Code 或带有 LSP 模式的 Emacs。这些编辑器可以提供基本的 Java 集成功能,无需为每个脚本单独创建项目。我们只需打开 Java 文件就可以开始编辑,非常方便快捷。

当脚本逐渐变得复杂,需要更强大的调试功能时,我们可能会希望切换到完整的 IDE。但传统的 IDE 项目结构要求可能会让我们望而却步,因为不想为每个小脚本都创建一个新的 <beginning of the code>src/main/java<end of the code> 目录结构。实际上,我们可以通过一些方法让重量级 IDE 使用项目基目录作为源目录。例如,在 Eclipse 中,可以右键单击项目名称,选择 “Properties” 和 “Java Build Path”,然后在 “Source” 选项卡中进行设置;在 IntelliJ 中,可以通过 “Menu → Project structure… → Modules”,移除 “content root”,并将项目基目录添加为新的 “content root” 并标记为 “Sources”。虽然这个过程听起来有些奇怪,但确实可行。

在 Java 脚本编程中,使用第三方库一直是一个痛点。由于 Java 语言标准本身与 Maven 生态系统没有直接关联,所以单文件的 Java 启动器无法直接导入 Maven 中的库。这就像两个不同世界的人,无法直接交流和合作。

不过,JBang 为我们提供了一个很好的解决方案。它的一个强大功能是可以在源文件中直接添加 Maven 依赖项,例如:

然后,我们就可以使用 jbang MailMerge.java 来运行程序。在 Linux 和 Mac OS 系统中,还可以通过添加 “shebang” 行(///usr/bin/env jbang \”$0\” \”$@\” ; exit $?)将文件转换为可执行脚本。需要注意的是,// 用于隐藏 shebang 以免被 Java 识别,exit $? 用于在脚本执行后正确退出。

除了这些,JBang 还提供了许多其他实用功能,如可以在加载文件及其依赖项的情况下启动 JShell,或者在临时的 <beginning of the code>src/main/java<end of the code> 目录中创建指向源文件的符号链接来启动 IDE。如果您打算认真使用 Java 进行脚本编程并且可以使用第三方工具,那么 JBang 绝对值得一试。

(三)笔记本编程:探索数据的新方式

到目前为止,我们主要讨论了用于定期运行的脚本程序。在小型编程的另一个领域 —— 探索性编程中,编写代码的目的是从数据集中获取结果,通常只运行一次或几次。数据科学家们通常更喜欢使用笔记本(notebooks)来完成这类工作。

笔记本由代码单元和文本单元组成,每个代码单元的运行结果可以以文本、表格、图像甚至音频或视频剪辑的形式显示。这使得我们可以采用试错法来探索数据,就像在一个充满宝藏的迷宫中,每次尝试都可能发现新的线索。一旦得到满意的结果,还可以通过文本单元对计算过程进行注释,方便后续查看和分享。

在 Python 中,最常用的笔记本是 “Jupyter”,它可以本地运行(通常通过 Web 界面),也可以使用托管服务(如 Google Colab)。实际上,Jupyter 的核心技术是与编程语言无关的,我们可以为不同的编程语言安装相应的内核。然而,Java 内核的安装过程可能比较繁琐,而且不同的 Java 内核(如 IJava、JJava、Ganymede 和 Rapaio 等)在安装 Maven 依赖项、显示非文本结果等方面各有不同。虽然 Jupyter Java Anywhere 提供了一种简单的机制(使用 JBang)来安装 Java 内核,但目前仍然存在一些依赖项解析等方面的问题。希望未来 Oracle 或其他主要厂商能够加强这方面的支持,提供一个类似于 Colab 的 Java 笔记本服务,这将极大地提升 Java 在探索性编程领域的应用体验。

与 Python 相比,Java 在探索性编程方面目前还不够普及,相关的支持库也相对较少。不过,tablesaw 可能是一个与 NumPy 相当的合理选择,它还提供了对知名的 Plot.ly JavaScript 绘图包的包装。此外,Sven Reimers 正在开发的 JTaccuino 笔记本也值得关注,它是一个基于 JavaFX 的实现,具有比基于 Web 的 Jupyter 笔记本更友好的用户界面,并且在底层使用了 JShell。虽然该项目仍处于早期阶段,但已经展现出了很大的潜力。

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。