Java学习笔记:基础篇
序言
说起来,自从学完了C语言,很久都没有成系统地学完一门编程语言了。
Python和PHP比较简单,基本都是边用边查学完的;Kotlin则是最近学Android开发顺便学的;C++倒是系统看过一遍书,不过最近基本没咋写过,忘的也差不多了;C#好久之前学的了,也就是写UWP的时候学了一点;JS?好像会,又好像不会(
快寒假了,也该摸点鱼了。梳理一下:Android学了大半,ML/DL没碰,Unity还在新建文件夹,算法就看了一点点。仔细想了想,还是先学点Java吧,一来下学期要学,二来和Android开发联系也紧密。最重要的是,这次得认真学学面向对象了。虽然在Python和PHP里都在用,但是终归还是系统学习一遍为上。
我用的是《Java核心技术》卷一/二。很多人在推荐,试读了一下,感觉不错,不像黑皮系列那么难读,废话也比较少。
Java简介
官方白皮书给出了如下关键字:简单性(接近C++)、面向对象(支持多重继承)、分布式、健壮性(优秀的指针模型)、安全性(复杂的安全模型)、体系结构中立(Java虚拟机)、可移植性(众多平台独立的Java库)、解释型(轻量的编译过程)、高性能(即时编译器)、多线程、动态性。
虽然关键字里有“解释型”,然而Java是真正的编译型语言。
虽然说Java是纯粹的面向对象语言,不过它并没有做到完全的面向对象:int,double等基本数据类型仍然不是对象。不过同样基于JVM(Java虚拟机)的Kotlin做到了完全的面向对象。
Java环境配置
在Windows下,访问Oracle官网,下载JDK(Java Development Kit)后安装,并将
jdk/bin/目录加入环境变量(详情百度)即可。Linux使用相应的包管理器(apt,yum等)直接安装即可。
安装完成后,可以安装库源文件:找到jdk/lib目录,将src.zip解压到jdk/javasrc/目录下即可。同时也可下载官方文档,在官网上能找到。
JDK基本使用
打开记事本(或其他文本编辑器),写入以下内容:
1 | public class Hello |
另存为Hello.java并打开cmd并切换到当前目录。在cmd中输入以下内容以编译并运行:
1 | javac Hello.java |
这是我们的第一个程序。掌握基础操作后,你可以使用你喜欢的IDE进行开发。我偏向使用VSCode(理论上Android Studio应该也可以)。
Java基本内容
程序结构
Java程序是以类为单位的,类则是一种自定义数据结构(类似于C中的结构体struct)。上面的程序包含了一个public类型的class(类),Hello是这个类的类名,这名称需和文件名同名。和C语言一样,Java也是大小写敏感的。习惯上将类名的每个单词首字母大写。
这个类中包含了一个main方法(也就是函数),作为这个Java程序的运行起点。这个方法中包含了该程序的所有逻辑,和C非常相似。
注释
Java注释和C/C++基本一样,支持//和/*、*/,同时也支持另一种:
1 | /** |
后两种都不能嵌套。这和C/C++一致。
数据类型
Java是强类型语言。它共有8种基本数据类型:
- 整型:int(4字节)、short(2字节)、long(8字节)、byte(1字节)
- 浮点类型:float(4字节)、double(8字节)
- char类型
- boolean(布尔)类型
这些都是关键字,用于声明对应类型的变量。
关于整型,和C差不多,有几点要注意:Java没有无符号类型整数。在整数后加L或l表示long类型整数,前缀0X或0x表示16进制整数,前缀0B或0b表示2进制整数,前缀0表示8进制数。为了可读性,还可以用下划线_分割整数:例如0b1111_0100这样的形式都是合法的。
关于浮点类型,double的使用相较于float更精确。浮点数后缀有两种:F或f表示float类型,而D或d表示double类型。不加后缀默认为double类型。也可以用16进制表示浮点数值:由于0.125=2的-3次幂,故可表示成0x1.0p-3。同时,还有三个特殊的浮点数值:正无穷大,负无穷大,NaN(Not a Number,不是一个数字)。例如0/0的结果就是NaN。可用Double.isNaN()可以检测一个变量是否为数值。另外,浮点数值采用二进制系统表示,因而不能精确表示1/10。此时可以使用BigDecimal类作为替代。
关于char类型:char原本表示单个字符。不过如今部分Unicode字符需要两个char来表示。和C一样,单引号表示字符,双引号表示字符串,反斜杠表示转义符。另外,可以直接用诸如\u2122而不加引号的方式表示字符,比如:
1 | public static void main(String\u005B\u005D args) |
也就是说,这种Unicode转义字符会在编译前被处理。因此使用反斜杠时一定注意。
关于boolean类型:C中没有布尔类型,而是使用int类型替代。Java中boolean类型只有true和false两个值。它和整数不能相互转换。这可以预防很多潜在的编程错误(例如if(x=0)在C语言中永远为假)。
Java的变量声明和C/C++基本一样,都是关键字 变量名的形式。同样可以在声明时对变量进行初始化(例如int a=5)。和C++一样,Java的声明可以在代码中的任何地方。用关键字final可以声明常量,这种变量只能被赋值一次。final就相当于C中的const关键字(const也是Java的关键字,不过Java并没有用它)。常量名一般习惯全部大写。常量也可以声明在main外部,类内部,使用关键字static final即可:
1 | public class Example |
运算符
这和C/C++基本一致:+ - * /表示四则运算,%表示整数求模运算。对于除法,整数被0除会产生异常,而浮点数被0除则会得到无穷大或者NaN结果。
Java中有一个很有用的Math库,用来进行各种数学运算,并且还有一些数学常量。
Math.sqrt(x):返回一个数值的平方根Math.pow(x,a):返回x的a次幂。参数x和a以及返回值都是double类型Math.floorMod(x,a):返回x对a取余的结果。它的存在是为了修补%运算不能正确处理负数的问题:负数的模显然应该是正数Math.sin/cos/tan/atan/atan2:常用三角函数Math.exp/log/log10:指数函数和它的反函数,以及以10为底的对数Math.PI/E:两个近似表示π和e的常量
在源文件顶部加上这行代码,就可以省略这些方法/常量的Math.前缀了:
1 | import static java.lang.Math.*; |
数据类型转换和强制类型转换,和C/C++基本相同。此外,Java还有Math.round方法,可以对浮点数进行四舍五入:
1 | double x = 9.997; |
由于Math.round返回的是long类型,所以需要用(int)显式转换,避免数据丢失。
和C/C++一样,Java也有+=,-=,*=,/=和%=这几个结合赋值和运算符的运算符。左右数据类型不同时会发生强制类型转换,将运算结果转换成左值的类型。自增,自减运算符和C/C++完全一样,不需要说明。
Java中的逻辑运算符和C/C++一致,且支持短路特性。Java也支持三目运算符?:。下面的表达式``
1 | x>y?x:y; |
返回x和y中较大的值。
位运算符有& | ^ ~四个,分别表示与,或,异或,非。利用位运算我们可以获得一个整数的各个位,也就是掩码技术。另外它的运算对象如果是布尔类型,则返回值也是布尔类型,但这种方式不使用路求值。
<<和>>是移位运算符,用法和C/C++一样:将左值左移/右移右值相应的位数。>>>会用0填充高位,而>>会用符号位填充高位。没有<<<运算符。
枚举类型包括有限个命名的值,例如
1 | enum Size{SMALL, LARGE}; |
字符串
Java字符串就是Unicode字符序列。Java没有内置字符串类型,而是在标准Java类库中提供了一个String预定义类。每个用双引号括起来的字符串都是String类的一个实例:
1 | String e = ""; |
substring方法可以从一个较大的字符串提取出一个子串:
1 | String greeting = "hello"; |
s是一个由”hel”组成的字符串。这方法表示从第0个字符开始,复制到第三个(不包括)为止。
+用来连接字符串。非字符串值被应用于这个操作符时,会被转换成字符串类型。任何一个Java对象都可以转换成字符串。如果需要用定界符分隔并连接,只需要用String.join静态方法:
1 | String s = String.join(",","a","b","c"); |
上面的s为a,b,c。
Java的String类对象被称为不可变字符串,也就是说一旦创建String对象,就不能对其进行修改。
利用String.equals方法检测两个字符串是否相等。例如s.equals(t),返回s和t的比较结果。这里的s和t可以是字符串实例,也可以是字符串字面量。
char类型在Java中并不是很常用,因为现在很多字符需要两个char类型存储单元才能表示。因此尽量不要用char类型。
下面是常用的String类的方法:
boolean equals(Object other)字符串比较boolean equalsIgnoreCase(String other)字符串比较,忽略大小写boolean startsWith(String str)判断字符串是否以str开头boolean endsWith(String str)判断字符串是否以str结束int length()返回字符串的长度String substring(int begin)String substring(int begin, int end)String toLowerCase()String toUpperCase()String trim()返回删除左右空格的字符串String join(CharSequence delimiter, CharSequence... elements)就是上面的String.join方法
构建字符串时,可以用StringBuilder类避免每次都新建一个String对象,节省空间:
1 | StringBuilder sb = new StringBuilder(); |
下面是StringBuilder类的方法:
StringBuilder()构造器int length()StringBuilder append(String str/char c)追加字符串/字符并返回thisStringBuilder insert(String str/char c)插入字符并返回thisStringBuilder delete(int start, int end)删除start到end(不包括end)的代码单元并返回thisString toString()返回一个内容相同的字符串
输入输出
输入基于Scanner类。首先得声明Scanner对象,并与标准输入流System.in关联:
1 | Scanner in = new Scanner(System.in); |
随后就可以使用Scanner类的各种方法实现输入操作了。比如:
1 | System.out.print("input your name:"); |
要使用Scanner类,需要在源码开头导入java.util.*
1 | import java.util.*; |
下面是Scanner类的方法:
Scanner(InputStream in)用给定的输入流创建一个Scanner对象String nextLine()读取下一行输入的内容int nextInt()读取下一个整数int nextDouble()读取下一个整数或浮点数boolean hasNex()检测输入中是否还有其他单词boolean hasNextInt()boolean hasNextDouble()
用Scanner类进行格式化输出非常简单。使用System.out.print()方法可以直接输出x,用System.out.printf()可以格式化输出字符串。它的用法和C中的printf()完全一致。同时还新增了一些标志。详见用于printf的标志。同时,printf支持输出格式化日期与时间,但它已经被废弃(Deprecated),应当使用java.time包的方法。
此外,也可以使用String.format()静态方法创建一个 格式化的字符串而不输出:
1 | String message = String.format("Hllo, %s. Next year, you'll be %d", name, age); |
Scanner类也支持文件输入输出:
1 | Scanner in = new Scanner(Paths.get("myfile.exe"), "UTF-8"); |
文件名中包含反斜杠的话,则需要再多添加一个反斜杠转义。另外,其中的UTF-8可省略,缺省值为运行 该程序的机器的默认编码。不过为了兼容性尽量不要这么做。还有,路径支持相对路径,不过位置是相对于Java虚拟机的启动路径而言的:即命令解释器的当前路径。也可以用下面的方式得到路径位置:
1 | String dir = System.getProperty("user.dir"); |
流程控制
Java中也有块(block)的概念。大多数内容都和C一致,除了嵌套的块中不能声明重名变量。下面说一下流程控制语句:
if-else if-else和C一样while/do-while和C一样for和C一样。不过添加了一种for each循环switch和C一样。不过从Java SE 7 开始,case标签可以是字符串字面量break后面可以带标签,用法和C中的goto一样。不过只能跳出语句块而不能跳入continue和C一样
大数值
java.math包中有BigInterger和BigDecimal两个类,分别表示任意精度的整数和浮点数。使用静态方法valueOf()将普通数值转换成大数值:
1 | BigInterger a = BigInterger.valueOf(100); |
然而因为Java没有提供运算符重载,所以不能用+-*/来进行大数的四则运算,只能使用它们的add subtract mulyiply divide mod compareTo方法进行加减乘除以及求模、比较运算。
数组
和C差不多。不过[]得写在数据类型后而非变量名后:
1 | int[] a; |
可以用a.length获取数组a的长度,其余的用法和C无异:数组长度也是不可变的。如果需要长度可变则应该考虑使用array list。
数组除了可以用for循环遍历,也可以用for each循环遍历:
1 | for(value : collection) statement |
不过,打印数组还可以用System.out.println(Arrays.toString(a));来完成。
和上面类似,用Arrays.copyOf(array, length)可以复制数组。
现在可以说说main()函数的参数String[] args了。这是一个参数数组,和C的argv参数基本一样。不过这里的args[0]指示的不是程序名,而是第一个参数。
使用Arrays.sort(a)可以对数组进行排序。Arrays还有很多方法:
Arrays.binarySearch(type[] a, type v)二分搜索值v,返回下标或负数值(若为未查找到)Arrays.fill(typr[] a, type v)用v填充数组Arrays.equals(type[] a, type[] b)数组比较,长度和对应位置的值都相等则返回true
多位数组使用这样的方式声明:int[][] a = new int[100][100]; 赋值和迭代等都和C差不多,按照java中一维数组的情况类推即可。
好了,这些就是Java的基本内容了。下一节是关于Java的面向对象体系的介绍。