Java学习笔记之基础语法

主要是记录Java中与C++不同的地方,快速浏览,参考书是《Java核心技术 卷I》
Java 8 SE API:https://docs.oracle.com/javase/8/docs/api/

HelloWorld

1.Java中类名的命名方法通常采用驼峰法(CamelCase),java源代码的文件名必须与公共类的名字相同,并以.java作为扩展名。
2.Java的运行需要有java虚拟机JVM,使用javac Hello.java将java源代码编译成Hello.class文件,使用java Hello来让JVM加载Hello.class中的内容并执行。JVM的执行入口是java中的main方法,main方法同样需要在一个类中,并且main需要是public的静态方法。下面是一个HelloWorld程序:

1
2
3
4
5
6
7
public class HelloWorld {

public static void main(String[] args) {
System.out.println("Hello world!"); //println默认输出结尾有换行,print输出结尾没有换行
}

}

3.Java中的所有函数都属于某个类的方法(称为类方法而不是成员函数),java的main方法为void,即没有返回值,如果java程序正常退出,则退出代码为0,如果想返回其他退出值,需要调用System.exit(),该函数的参数即为指定的java程序返回值。

4.Java中注释与C++一样,但/*...*/不支持嵌套,并增加了一种/**...*/的注释方式,这种注释可以自动生成文档

基本数据类型

5.Java中的基本数据类型有4类,8种:
整型:

类型 占用存储空间 表示范围
byte 1字节 -128 ~ 127
short 2字节 -2^15 ~ 2^15-1
int 4字节 -2^32 ~ 2^31-1
long 8字节 -2^63 ~ 2^63-1

同样支持十进制、八进制和十六进制,默认整数类型都是int型,如果声明为long类型,就需要在数字后接上l或者L
Java 7开始支持二进制,以0b为前缀,例如:0b1001即是十进制的9,还可以为了易读在数字字面量加上下划线,如1_024_123,java编译器会自动去掉下划线。
浮点型:

类型 占用存储空间 表示范围
float 4字节 -3.403E38 ~ 3.4083E38
double 8字节 -1.798E308 ~ 1.798E308

科学计数法与C++一样,Java中默认小数类型是double,所以如果要声明float类型,后面需要添加字母f或者F。
float有效位数为6~7位,double有效位数为15位
有3个用于表示浮点类型溢出或者出错的常量:

  • -Infinity:负无穷大,例如-1.0/0.0
  • Infinity:正无穷大,例如:1.0/0.0
  • NaN:不是一个数字,例如:0.0/0.0
    整数被0除时将产生异常
    可以使用关键字strctfp来修饰方法,使该方法中的浮点计算采用严格的浮点计算来处理,严格浮点计算更精确,但有可能导致溢出

字符类型:
char:以单引号(')括住,java字符采用Unicode编码,每个字符占2个字节,所以可以表示中文,也可以采用16进制编码形式表示,如:
char ch = '\u03C0';中ch即对应圆周率的字符派,与C++一样,字符中也可以有转义字符,如char ch = '\a';
\u还可以出现在字符常量或字符串的引号之外,例如main方法可以这样写:public static void main(String\u005B\u005D args)
UTF-16编码采用不同长度的编码表示所有Unicode代码点,代码点(code point)是指一个编码表中的某个字符对应的代码值。Unicode编码通常以U+为前缀,
后面跟4位16进制。由于UTF-16只能表示最多65536个字符,Java为了支持多语言,从JDK 5.0开始,采用的Unicode代码点可以分成17个级别(code plane),
其中第一个级别称为基本的多语言级别(basic multilingual plane),代码点从U+0000到U+FFFF;其余的16个附加级别代码点从U+10000到U+10FFFF,
其中包括了一些辅助字符(supplementary character)。在基本的多语言级别中,每个字符用16位表示,通常被称为代码单元(code unit)。
而对于那些不在基本语言级别中的辅助字符使用一对连续的代码单元进行编码,这对代码单元区域被称为替代区域(surrogate area),
其中U+D800~U+DBFF用于表示第一个代码单元,U+DC00~U+DFFF用于第二个代码单元。这样的巧妙设计可以很方便的通过一个代码单元知道该编码是一个字符编码,
还是一个辅助字符编码的第一或第二部分。例如表示整数集合的数学符号(𝕫)的代码点是U+1D56B,并且是用两个代码单元U+D835和U+DD6B编码的。

Java中强烈建议不要使用char类型,除非是需要对UTF-16代码单元进行操作,最好将需要处理的字符串用抽象数据类型表示

bool类型:
boolean:只有两个值truefalse,与C++不同的是boolean类型不能转换为整型

6.Java中不区分变量的声明与定义,只声明未初始化的变量直接使用是错误的,所有变量必须在初始化之后才能使用。
7.使用final定义常量,类似C++中的const,const也是java中的保留关键字,但并未使用。常量可以被定义在main方法之外,定义为static的类常量,以便可以被其他类方法使用,如果常量被声明为public,那么该常量将可以被其他类方法使用,通常常量的命名为全大写。
8.位运算符中>>>将使用0填充高位,>>运算符用符号位填充高位,没有<<<运算符
9.java中没有幂运算符,需要使用类Math中的pow方法,该方法默认返回值为double类型。x的a次方调用方法为Math.pow(x, a),如果想直接可以调用pow而不需要使用Math前缀,可以在源文件顶部通过import static java.lang.Math.*;来导入,注意这句同样需要以分号结尾。
10.强制类型转换与C++类似,但boolean类型除外,boolean类型不能与任何数值类型转换,但可以通过b? 1 : 0的方式来获得整型值。可以使用Math.round()方法获取最接近的整数值,该方法返回值为long,例如:double x = 9.997; int nx = (int) Math.round(x);中nx的值为10
11.java中不存在逗号运算符,但for循环中可以使用逗号分隔表达式列表。

字符串

12.java中没有内置的字符串类型,而是在标准Java类库中提供一个String类来处理字符串,Java中每个用双引号括起来的字符串都是String类的一个实例。
13.Java中当将一个非字符串使用+号与字符串拼接时,将被自动转换为字符串
14.Java中的字符串类String中的字符串是不可变字符串,即无法修改某个字符,类似于C++中的字符串常量或字符串字面值。
15.Java中的字符串不能通过==号来判等,需要使用类方法equals,"Hello".equals(greeting),如果忽略大小写则可使用equalsIgnoreCase方法。==号在java中只能判断两个字符串是否放在同一个位置上,java中只有字符串常量在同一个位置上共享,而被+或substring等操作产生的字符串并不共享。
16.空串””是长度为0的字符串,判空使用if (str.length() == 0)if (str.equals("")),声明的String可以是null,所以如果要检查一个字符串即不为空,也不是null,需要使用if (str != null && str.length() != 0),直接在null上调用方法会出错
17.Java中的字符串是由char序列组成,而char类型是由采用UTF-16编码表示Unicode代码点的代码单元,也就是一个字符是占用2个字节的,而length方法刚好返回的是采用UTF-16编码表示的给定字符串所需要的代码单元的数量,也就是我们实际的字符个数。

10.通过String的+号来连接字符串或单个字符通过+号添加到字符串末尾时效率比较低,可以通过StringBuilder类来构建字符串,效率更高,该类在Java 5.0之后加入,前身是StringBuffer,他们API相同,但StringBuilder支持多线程。用法:

1
2
3
4
StringBuilder builder = new StringBuilder();  //创建一个字符串构造器
builder.append(ch); //将字符ch追加到builder
builder.append(str); //将字符串str追加到builder
String newstr = builder.toString(); //调用toString将字符串构建器中的字符序列转成字符串

StringBuilder相当于支持在原地修改的字符串

输入输出

输入

1.Java中通过将类Scanner对象与标准输入流System.in关联起来来实现输入操作,Scanner类定义在java.util包中,当某个类不是定义在java.lang包中时,必须通过import指令将相应的包加载进来。
2.Scanner对象通过方法nextLine方法获取输入流中的一行,通过next读取一个以空白分隔的字符串,通过nextInt方法获取一个整数,nextDouble方法获取一个浮点数;hasNext检测输出中是否还有其他单词,相应的有hasNextInt、hasNextDouble。
用法举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.*;

public class HelloWorld {

public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("What's your name:");
String name = in.next();

System.out.println("How old are you:");
int age = in.nextInt();

System.out.println("How many dollar you spend:");
double money = in.nextDouble();

System.out.println("Hello, " + name + " , your age is " + age + " and have spent " + money + " dollars.");

in.close();
}
}

格式化输出

1.Java中的System.out.printf()函数可以像C语言中的printf一样进行格式化输出。
2.System.out.printf支持分组的分隔符输出,例如System.out.printf(“%,.2f”, 10000.0 / 3.0);的输出结果为3,333.33
当然printf还支持其他很多格式化输出标志,可以参见官方API:https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax
3.printf中可以使用s转换符格式化任意的对象,对于任意实现了Formattable接口的对象都将调用formatTo方法,否则将调用toString方法。
4.可以使用静态的String.format方法创建一个格式化的字符串,而不是打印输出,如:

1
String message = String.format("Hello, %s. Next year, you'll be %d", name, age);

5.java中有非常多针对时间的格式化输出控制字符,并且可以通过%号后面紧跟参数索引及$来指定复用某个参数。如下面输出当前日期,格式为月 日, 年:

1
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Now date:", new Date());

注意$前面的序号是指后面的参数序列,从左到右从1开始记,t表示格式化输出时间,t后面跟的B、e、Y分别表示月的完整拼写、两位数字的月、4位数字的年。

文件输入与输出

1.读取文件需要用File对象构造一个Scanner对象,由于默认情况下字符串构造的Scanner对象中包含的是字符串本身,所以需要使用File对象来构造,下面的Paths.get方法返回的就是一个File对象:

1
Scanner in = new Scanner(Paths.get("myfile.txt"));

Scanner的用法与读写字符串是一样的。

2.对于文件的输出使用的是PrintWriter对象,该对象可以直接通过一个表示文件名的字符串来构造:

1
PrintWriter out = new PrintWriter("myfile.txt");

可以像System.out一样来对PrintWriter对象使用print、println等命令
3.可以使用System.getProperty方法来获取当前程序的启动路径
4.对于打开文件失败时有可能是由于文件不存在引起的异常需要在main方法中用throws子句标记,如:

1
2
3
4
public static void main(String[] args) throws FileNotFoundException
{
Scanner in = new Scanner(Paths.get("myfile.txt"));
}

5.java中可以使用重定向将任意文件绑定到System.in和System.out:

1
java MyProg < myfile.txt > output.txt

控制流程

1.Java的控制流程结构与C/C++的控制流程结构一样,只有很少的例外情况,没有goto,但break语句可以带标签,利用它可以实现从内层循环跳出的目的,C语言中可以使用goto实现。
注意for循环是在每次迭代之后更新计数器或类似的变量来控制迭代次数
2.Java支持类似C++11中的foreach循环来遍历数组或一个实现了Iterable接口的类对象(ArrayList)中的所有元素。语法:for (type variable : collection) statement,与C++11中用法基本完全一样。

大数值类

Java的java.math包中有两个可用于计算任意长度和精度的数值类:BigInteger和BigDecimal。普通数值需要使用valueOf方法转换为大数值,由于Java不支持运算符重载,所以常见的四则运算都需要通过调用方法来实现,例如BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); //d = c * (b + 2)

数组

1.Java中有2种数组声明方法:int[] a;或者int a[];,这样只是声明数组变量a,并没有将a初始化为一个真正的数组,需要使用new运算符创建数组,或者在声明时直接创建:int[] a = new int[10];,数组a中含有10个元素。Java中数组一旦创建,其大小也无法改变,如果想创建可变大小的数组,需要使用另一种数组结构——数组列表(array list)。通过array.length属性获取数组长度,注意length是属性,后面不加方法调用符(())。
2.使用Arrays.toString(a)方法可以打印出数组a中所有的数值。
3.使用new方法创建一个数组时,如果是数字数组,所有元素都会被初始化为0,boolean数组的元素会被初始化为false,对象数组的元素则初始化为一个特殊值null,这表示这些元素还未存放任何对象。如:String[] names = new String[10];,names中的10个元素都是null,要获得空串,必须手动赋值,如:for (int i = 0; i < 10; i++) names[i] = "";
4.可以在创建数组时使用花括号元素对数组进行初始化,此时不需要关键字new,也不需要指定数组大小:int[] arr = {1, 2, 3, 4, 5};;还可以创建一个匿名数组:new int[] {21, 33, 44, 215, 36};,使用这种方法可以在不创建新变量的情况下重新初始化一个数组,如:arr = new int[] {11, 12, 13, 14, 15};,其等价于int[] anonymous = {11, 12, 13, 14, 15}; arr = anonymous;
Java中可以创建长度为0的数组,数组长度为0与null不同。
5.Java通过Arrays类的copyOf的方法拷贝数组,而直接通过赋值运算符只相当于C++中的引用,例如int[] luckyNumbers = arr;此时luckyNumbers与arr指向同一个数组,luckyNumbers只相当于arr的别名,而int[] luckyNumbers = Arrays.copyOf(arr, arr.length);则将arr中的元素逐一拷贝到luckyNumbers数组中,该方法还可以用来增加数组的大小:luckyNumbers = Arrays.copyOf(arr, 2 * arr.length);
Java中的数组类似于C++中分配在堆上的数组。
6.Java的命令行参数存储在args中,与C++不同的是其第一个元素不是程序名,而是确确实实的第一个命令行参数。

7.java.util.Arrays中包含很多用于数组的实用函数,比如前面的toString(返回括号包含,逗号分隔的数组元素的字符串),copyOf(拷贝字符串),sort(排序),binarySearch(二分查找),fill(快速填充数组元素),equals(比较两个数组元素是否相同)等。
下面是一个举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.*;
public class LuckyNumber {
//从一个长度为length中序列中随机抽取luckyNumber个不重复的数, 并按升序输出
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Your total number:");
int length = in.nextInt();
System.out.print("Your lucky number:");
int luckyNumber = in.nextInt();
int[] total = new int[length];
//生成一个1~n的序列
for (int i = 0; i < total.length; ++i)
total[i] = i + 1;
int[] lucky = new int[luckyNumber];
//随机选择luckyNumber个数,通过使用Math.random函数来生成一个随机下标,然后从序列中选取该下标对应的数,
//并将最后一个数移到该位置之后,从剩下的n-1个数中选取下一个数
for (int i = 0; i < lucky.length; ++i)
{
int r = (int)(Math.random() * length); //Math.random产生一个0~1的随机数,含0,不含1
lucky[i] = total[r]; //将该下标中的数字取出
total[r] = total[length - 1]; //移出被取出的数字,确保不重复
--length;
}
Arrays.sort(lucky); //排序数组
for (int l : lucky)
System.out.printf("%d ", l);
System.out.println();
in.close();
}
}

8.二维数组声明方式为int[][] arr = new int[n][m],获取其中一维的长度可用arr[0].length,java中的多维数组也是数组的数组,当然也支持直接在声明时初始化,如:

1
2
3
4
5
int[][] arr = 
{
{1, 2, 3, 4},
{5, 6, 7, 8}
}

二维数组的一个举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FileTest {
public static void main(String[] args) {
int[][] arr = new int[10][10];
for (int i = 0; i < arr[0].length; i++)
for (int j = 0; j < arr.length; j++)
arr[i][j] = i + j;
for (int[] r : arr) {
for (int a : r)
System.out.printf("%d\t", a);
System.out.println();
}
}
}

9.由于Java中的多维数组是数组的数组,所以可以创建不规则的二维数组(即第一行的元素个数不同),并且可以直接交换两行:

1
2
3
int[] temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;

Java中创建一个形如:

1
double[][] arr = new double[10][6];

与C++中的以下定义不与同,:

1
double arr[10][6];//创建一个10行6列的二维数组

也不同于:

1
double (*arr)[6] = new double[10][6];//创建一个指向含有6个元素的数组的指针,该指针指向含有10个元素的数组的首地址

而是等同于分配一个包含10个指针的数组,然后指针数组的每一个元素被填充了一个包含6个数字的数组:

1
2
3
double** arr = new double*[10];
for (int i = 0; i < 10; i++)
arr[i] = new double[6];