1.前置知识
Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。
1.1 第一个hello world程序
1.1.1 源文件说明
public class test {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
先对以上代码进行一些解释。源文件最外层是类,在一个源文件中只能有一个public修饰的类,而且public修饰的类名必须与源文件名字相同。在类中包含方法,此处的main就是一个方法,返回类型为void,参数String为字符串类型,String[]就是字符串数组。方法中再包含变量,在示例中,方法中是一个打印语句。
1.1.2 运行java程序
首先需要通过文本编辑编写java源文件,后缀为.java。然后使用javac.exe编译器对原文件进行编译,生成后缀为.class的字节码文件。最后使用java运行.class字节码文件。而在完成这些步骤之前首先需要配置好环境,需要安装JDK(Java开发工具包),在JDK中包含编辑器、调试工具等,还有JRE(Java运行时环境)。JRE包含了java基础类库和JVM。JVM是java虚拟机,是运行java程序的地方。
1.2 注释
java的注释分为三种:单行注释(//),多行注释(/* */),文档注释(/** */)。文档注释可以被javadoc工具解析,生产以网页文件形式体现的程序说明文档。
/**
* 这是文档注释
*/
public class Test {
public static void main(String[] args) {
//这是单行注释
/*
--分割线--
这是多行注释
--分割线--
*/
}
}
1.3 标识符
标识符的使用和C语言规定相似,标识符由字母、数字、下划线和$符号组成。标识符不可以以数字开头,也不可以是关键字。
2.数据类型与变量
2.1 数据类型
在java中,数据类型分为基本数据类型和引用数据类型。
基本数据类型有四类(整型、浮点型、字符型、布尔型),共八种。
引用数据类型则包括String、数组、类、接口等。
2.2 变量
变量的定义方式和C语言相同:数据类型 变量名 = 初始值;
对于变量的使用需要注意:
①java中的局部变量在使用时必须是经过初始化的,否则就会报错。
int a = 10;
int b;
b=20;
System.out.println(b);
int c;
//System.out.println(c); //error,java中使用未赋值变量会报错
②在java中,会对字面值常量进行检查,当发现赋值的字面常量超过了当前变量的范围,就会报错。但是注意,这只是对字面常量赋值的规定,对于一个表达式,当结果超出范围仍然可以完成赋值操作,只是存在溢出的现象。
int d = Integer.MAX_VALUE+1; //Integer是int的包装类型,Integer.MAX_VALUE可以得到int类型的最大值
System.out.println(d);
//int e = 2147483648;
//java中赋值超过范围的数字会报错,但是如果是表达式结果超范围则会溢出而不会报错
2.2.1 整型变量
①java中整型字面常量默认为int型,所以为长整型赋值时在数字最后加一个字母l或L,否则可能会超出整型范围而报错。
//long gg = 20000000000; //error
long g = 20000000000L;//为了区分int和long类型,在long类型变量的初始值后加L或l
②对于byte和short类型,定义时不需要做处理,因为byte和shourt赋值的字面量当没有超过最大范围的时候不会被解析为整型。
③java中的整型都是带符号的,java中没有无符号整型。
④每一个整型都有自己的包装类型,int<->Integer、long<->Long、short<->Short、byte<->Byte。
2.2.2 浮点型变量
①java中的浮点型字面常量默认为double型,所以定义float变量时需要在末尾加字母f或F,否则会由于可能丢失数据而报错。
//float f = 2.4;//error,因为2.4默认为double,要给float变量赋值需要在数字后加F或f
float h = 6.5f;
②浮点型在内存中的存储方式遵循IEEE754的标准。
③浮点类型也有自己的包装类型,float<->Float、double<->Double。
2.2.3 字符型变量
①java中使用Unicode表示字符,可以表示包括中文在内的多种字符,所以一个字符变量占用两个字节。
char c1 = 'A';
char c2 = '~';
char c3 = '1';
char c4 = '囍';
//java采用Unicode表示字符,所以一个字符占两个字节,且可以存放汉字
②char的包装类型为Char。
注:当javac报错未结束的字符文字时,需要指定使用UTF-8解码,即输入指令javac -encoding UTF-8 Test.java 。
2.2.4 布尔型变量
①boolean类型的变量只有true和false两种取值,且和整型之间没有任何关系,即不存在0为false而非0为true的规定。
boolean b1 = true;
//boolean类型的变量只有两种取值:true或false
//boolean类型不能和int相互转换,即非零为true的说法在java中不适用
②boolean的包装类型为Boolean。
2.3 类型转换
2.3.1 隐式(自动)类型转换
隐式类型转换只在由小范围数据转换为大范围数据时可以正常发生,当试图将大范围数据转换为小范围数据时会因为可能存在数据丢失而编译失败。
int n1 = 12;
long n2 = 33;
//n1 = n2; //error,long->int
n2 = n1; //int->long,隐式类型转换
//隐式类型转换只发生在 小类型的值 赋值给 大类型的变量,大类型给小类型会报错
2.3.2 显式(强制)类型转换
显式类型转换和C语言的语法相同,强制类型转换一般用在大范围数据转换为小范围数据时,所以显式类型转换可能造成数据丢失。
n1 = (int)n2;//强制类型转换
char c5 = 'k';
int aa = (int)c5;
System.out.println(aa);
//int bb = (int)true; //error,不相干的类型不可以强制类型转换
2.3.3 类型提升
当不同类型的数据进行运算时会发生类型提升,将小类型提升为大类型,所以需要注意表达式最后的类型。如int和long运算,int被提升为long,所以表达式结果为long类型,需要用long类型的变量进行接收,如果使用int则需要强制类型转换。
对于byte和short类型的变量,计算时会先提升至int再计算,所以即便是两个byte类型的数据计算,结果也是int类型的。
byte a = 10;
byte b = 20;
byte c = (byte)(a + b);
System.out.println(c);
2.4 字符串类型
java中使用String类来定义字符串类型,类中有诸多方法比如将String和int互换类型等。在打印时可以使用加号进行字符串拼接。
String s1 = "hello";//定义字符串
String s2 = "world";
int m1 = 10;
int m2 = 20;
System.out.println(m1+s1+' '+s1+m2);
System.out.println(m1+m2+s1+' '+s1+m2+m1);
System.out.println(m1+m2+s1+' '+s1+(m2+m1));
//可以在打印时使用加号进行字符串拼接,注意+在字符串后认为是拼接,否则也有两个数字相加的含义
//String和int间可以相互转换
System.out.println(String.valueOf(m1));
String s3 = "5689";
System.out.println(Integer.parseInt(s3));
3.运算符
java中的运算符基本与C语言用法相同,这里重点说明与C语言中的不同的点。
3.1 取模运算
在java中取模运算的操作数可以是浮点数,计算结果也就是余数是一个浮点数,由于浮点数精确度问题求得的余数一般不是很精确,所以一般很少使用。
因为取模运算会存在负数操作数情况,所以只需要记住取模运算的结果符号和被除数相同,这样也可以推出负数除法的商。
System.out.println(10%3);//1
System.out.println(-10%3);//-1
System.out.println(10%-3);//1
System.out.println(-10%-3);//-1
3.2 逻辑运算符
逻辑与&&和逻辑或||遵循短路求值规则,即&&左表达式为false就忽略右表达式,||左表达式为true就忽略右表达式。
java给出另一种逻辑与&和逻辑或|,这两个运算符不支持短路求值规则,即左右表达式都会计算。
3.3 移位运算符
左移操作的逻辑左移和算数左移一样,都是末位补0,所以使用<<操作符。对于右移操作,由于算数右移会根据符号位补位,正数补0,负数补1,所以使用>>表示算数右移;而逻辑右移(无符号右移)不管符号位,一定补0,所以使用>>>表示逻辑右移。
4.程序逻辑控制
4.1 分支语句
4.1.1 if语句
if语句的使用方法和C语言相同,只需要注意布尔表达式的结果一定是boolean类型的。
4.1.2 switch语句
switch括号内的表达式只能是byte、char、short、int、String常量串、枚举类型。
4.2 循环结构
while语句、for语句、do while语句和C语言用法相同。
4.3 输入输出
4.3.1 输出到控制台
输出到控制台的方式我们已经使用过了,就是System.out.println();语句。使用println是输出内容并换行,使用print则是不换行,而printf则是格式化输出,与C语言形式相同。
4.3.2 从键盘输入
要输入数据,则需要使用Scanner来读取字符串、整数、浮点数。使用方法:①导入util包;②new一个Scanner对象;③选择对应的方法进行数据输入,如scanner.nextInt()读取一个整型,scanner.nextLine()读取一个字符串,scanner.nextFloat()读取一个浮点数。
import java.util.Scanner;//输入需要的导入的util包
public class Test {
public static void main(String[] args) {
//当需要输入时需要使用scanner
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
}
}
5.方法
java中的方法就类似于C语言中的函数,是一个代码块,可以被传参调用完成特定功能。
java中的方法定义方式:
修饰符 返回值类型 方法名称(参数类型 形参, ... ){
方法的代码;
}
需要注意:
①方法必须写在类中,且不存在声明。
②使用的过程中需要注意形参和实参的区别。
③java的方法支持重载,其重载的规则与C++一致。
④java方法支持递归。
6.数组
6.1 数组的创建及初始化
数组的定义方式:
元素类型[] 数组名 = new 元素类型[数组长度];
java的数组定义方式和C++中new一个数组的操作相似,注意数组名前一定是 类型[] 。在创建数组的时候需要指出数组长度,数组长度的指出方式可以是方括号,也可以是根据初始化的元素个数,但是只能由二者中的一种方式指出。所以定义数组的方式还可以是:
元素类型[] 数组名 = new 元素类型[]{数组内容};
java语法支持将上面两句拆开来写,也支持第二种方式省略 new 元素类型[] 。但是既拆开写又省略 new 元素类型[] 是不可以的。C语言定义数组的方式不会报错,但不推荐这种写法。
public static void main(String[] args) {
//数组的定义
//元素类型[] 数组名 = new 元素类型[数组长度];
int[] arr1 = new int[4];//定义一个数组长度为4,元素为int类型的数组
String[] arr2 = new String[2];//定义一个数组长度为4,元素为String类型的数组
//数组的初始化
//动态初始化:创建数组时指定数组长度
int[] arr3 = new int[5];
//可以分开写
double[] arr4;
arr4 = new double[3];
//静态初始化:创建数组时不给定数组长度,而是给出数组内容,根据内容数量确定长度
int[] arr5 = new int[]{1,2,3,4};//定义一个int类型的数组,根据初始化内容可知数组长度为4
//可以分开写
String[] arr6;
arr6 = new String[]{"hello","world","XLZ"};
//静态初始化可以省去new int[]部分
int[] arr7 = {1,2,3,4,5,6};//定义一个int类型的长度为6的数组并初始化
//不推荐的写法(不是错误)
//int arr8[] = {1,2};//c语言写法
//错误的写法:省略new且分开写
//int[] arr8;
//arr8 = {1,2,3};
}
如果数组中的元素没有初始化,则基本数据类型初始化为默认值,引用数据类型默认初始化为null。
6.2 数组的访问与遍历
数组可以通过 数组名[下标] 的方式进行访问。遍历数组则可以考虑采用循环结构。数组的长度可以通过 数组对象.lenth的方法来获取。
int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
for(int i = 0;i<arr.length;i++)
{
System.out.print(arr[i]+" ");
}
也可以采用for-each的方式遍历数组。(和C++的范围for比较相似?)
int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
for(int e:arr)
{
System.out.print(e+" ");
}
6.3 对数组的理解
6.3.1 引用数据类型
数组作为一个引用数据类型,其存储数据的实际空间在JVM的堆中,这和C++一样,通过new开辟的空间在堆中。而JVM的栈中存储的就是函数的内部变量,引用变量在栈中存储,其值为堆中对应的空间的地址。所以引用在C++中作为指针的替身,在java中也发挥类似指针的作用,引用数据类型在栈中仅仅存放一个堆区new出来的空间的地址,而在堆区中才存储着真正的数据信息。
理解了数组在内存中的存在形式,我们就很容易理解为什么浅拷贝存在问题,而必须采用深拷贝进行数组拷贝了。浅拷贝会让两个引用变量指向同一块空间,相互不独立;而深拷贝则是另起炉灶,new出一块自己的空间存放同样的数据,做到了相互独立。
6.3.2 null
null在java中表示“空引用”,就是表示不指向任何对象,类似于空指针NULL(nullptr),但是在java中并未约定null和0号地址有什么关系。一旦尝试对一个null赋值的引用变量进行读写,就会抛出NullPointerException的异常。
6.3.3 传参和返回值
因为数组的数据存储在堆中,所以可以将其地址信息作为参数或返回值来传递,且通过地址在函数内外都可以访问到并且进行有效修改。并且将引用变量作为参数仅仅是传递了一个地址,而不是拷贝数组,开销也不会很大。
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5};
func1(arr);
for(int i = 0;i<arr.length;i++)
{
System.out.print(arr[i]+" ");
}
System.out.println();
int[] arr2 = func2();
for(int i = 0;i<arr2.length;i++)
{
System.out.print(arr2[i]+" ");
}
System.out.println();
}
public static void func1(int[] a)
{
a[1] = 99;
}
public static int[] func2()
{
int[] ret = new int[]{12,13};
return ret;
}
6.4 二维数组
二维数组本质是数组的数组,其每个元素都是数组。二维数组和一维数组定义方式相同,但是需要注意类型是 元素类型[][] ,并且不初始化时不可以省略行。
java的二维数组可以是不规则的,行数不能省略表示将二维数组当做一维数组:数组的数组,行数就是其元素个数,不可以省略。而列数起始就是数组的数组的元素个数,不相同是完全可以的。二维数组的引用变量存在栈中,内容是堆区的一块地址,这块地址空间中存储着行数个地址,分别指向堆区的其他地址,这些其他的地址空间每个都存储着真正的数据。
public static void main(String[] args) {
int[][] arr1 = new int[][]{ {1, 2},
{7,8,9},
{5}};
for(int i = 0;i<arr1.length;i++) {
for (int j = 0; j < arr1[i].length; j++) {
System.out.print(arr1[i][j] + " ");
}
System.out.println();
}
int[][] arr2 = new int[3][4];
//int[][] arr3 = new int[][4]; //error
int[][] arr4 = new int[3][]; //二维数组不可以省略行
//int[][] arr5 = new int[][]; //error
}