1 Java语言的编译运行
Java语言号称一处编译,多处执行,但是Java语言并不是一种编译型语言,而是一种解释型语言。他的运行逻辑是,编译成一种特定的文件格式——字节码,在不同系统的机器中,只要有Java运行时环境(主要是解释器)就可以运行Java程序。
解释型虚拟机肯定比全速运行机器码要慢得多,但是Java虚拟机有一个选项,可以将执行比较频繁的字节码序列转换成机器码,这一过程称为即时编译,从而来提高程序的运行效率。
2 Java主函数
public static void main(String[] args){...}
Java运行编译后的程序的时候,虚拟机总是从指定类中的main方法开始执行的,因此为了能够执行,需要在所执行的类中添加main方法。
在Java1.4之前,存在一个bug,main方法不是public的也能在一些版本的解释器中执行,在1.4之后修复,强制main方法必须是public类型的。
main方法没有为操作系统返回“退出码”,如果正常退出,Java应用程序退出码为0,如果希望在终止程序时返回其他的返回码,需要使用System.exit方法。
System.exit(10);
输出
Process finished with exit code 10
main方法的参数
main方法在调用的时候可以传入参数,参数传入后存储在String数组中,在main函数中可以使用这些参数。
java Scratch 100 200
3 注释
Java中支持三种注释
- 单行注释
//注释内容,从注释内容开始,到本行结尾 - 多行注释
/*注释内容*/ - 生成注释文档
/**注释内容*/,该内容通常放在类前或方法前,用于生成api文档。
4 Java中的数据类型和控制语句
Java是一种强类型的语言,每一种变量必须声明一种数据类型。Java中只存在两种数据类型,一种是基本数据类型,有8种,除了8种基本数据类型之外,其他的都是引用数据类型。
4.1 八种基本数据类型
基本数据类型 | 占用字节数 | 取值范围 |
---|---|---|
byte | 1 | -2^7 ~ 2^7-1 |
short | 2 | -2^15 ~ 2^15-1 |
int | 4 | -2^31 ~ 2^31-1 |
long | 8 | -2^63 ~ 2^63-1 |
float | 4 | IEEE754 |
double | 8 | IEEE754 |
char | 2 | 0~2^16-1 |
boolean | 1 | true or false |
八种基本类型,其中整型四种,为byte,short,int,long。int为整型的默认类型,当byte、short、char与整型数字运算时,会自动提升成int值进行计算,返回的值也是int型。int型与long型计算会自动提升成long型。整型与浮点型计算会自动提升成double型。double是浮点型的默认类型,float定义时需要加f或F,否则被识别为double型。
byte b1 = 10; //true
byte b2 = b1 + 10; //false,编译报错,类型不匹配
byte与整型数字相加,会自动提升类型成int型,如果要在赋值给byte,需强转,但需考虑是否溢出。
char a = 'c'; //true
char b = a + 2; //false,编译报错,类型不匹配
char c = -1; //false,编译拨错,类型不匹配
char d = '\u0041'; //true,使用Unicode形式定义,对应于A
char e = 65; //true,对应于A
char f = '\101'; //true,对应于A,八进制表示
char值只有正值,存储字符,对应于Unicode编码值范围0~65535。如果给char值传入负值,会默认传入的为int值,导致类型不匹配编译报错。char存储Unicode编码字符串,可以使用Unicode编码进行初始化,如’\u0041’,‘\u’标识Unicode转义序列的开始,该值对应于’A’,其16进制为41,十进制为65,也可用其十进制值对char进行初始化。
除此之外,字符还可以使用八进制形式进行初始化,‘\nnn’,可表示0到255之间的Unicode值,前面的零可以省略,如’\41’。
float f1 = 0.02; //false,默认成double
float f2 = 0.02f; //true
浮点型定义默认成double型,所以定义float类型的浮点型,需要加f或F修饰。浮点数值的计算都遵循IEEE 754规范,有三个特殊的浮点数值表示溢出和出错情况:
- 正无穷大
- 负无穷大
- NaN(非数字)
Double.POSITIVE_INFINITY
Double.NEGATIVE_INFINITY
Double.NaN
上面表示double类型的正负无穷和非数字,float类型也存在
非数字的检测不能使用==
if(x==Double.NaN) //is never true
应该使用
if(Double.isNaN(x)
浮点数不能应用于无法接受舍入误差的金融系统,因为计算机存储使用的是二进制,无法精确的表示小数。如果需要数值计算中无任何误差,则应该使用BigDecimal类。
int a = 2_014;
Java允许在数字中加入下划线来使之更容易阅读。
4.2 引用数据类型
Java中除了8种基本数据类型外,其他都是引用数据类型,包括数组、类、接口这些。
4.2.1 数组是类吗?
个人认为数组是一种特殊的类。
首先,我们知道数组是引用数据类型的,且基本数据类型的数组,并没有该类的存在。但是我们却可以打印数组的类名和父类名。
int[] arr = new int[10];
System.out.println(arr.getClass());
System.out.println(arr.getClass().getSuperclass());
输出结果为
class [I
class java.lang.Object
可以看到,我们打印了一下int数组的输出,可以获取类名和其父类。类名就是[I,其父类就是Object类。但是我们又找不到一个叫做[I的类,同时,数组在初始化的时候new int后面没有括号,说明没有调用任何的构造函数。一个可以获得类名,且有父类,但是又不实际存在的类,所以可以将其理解为一个特殊的类。
4.3 值传递和引用传递
对于基本数据类型,在调用方法时,传递参数使用的是值传递。形参是直接拷贝实参的值,在传入方法后,在方法中对于形参的操作都不会影响到实参。对于引用数据类型,传递的是引用,形参是实参引用的映射,他们指向了同一块内存空间,通过形参修改的是内存空间的值,所以从实参看来数据也是变化了的。
4.4 switch
switch支持byte、short、int、char。jdk7加入String。case必须是字符串常量或者字面量。
4.5 foreach
foreach是for在特殊时候的简化版本,并不能完全的替代for,因为不能获取到数组序号。foreach常用来遍历集合元素和数组。
5 Java中的修饰符
Java中的修饰符主要分为两类
- 访问修饰符
- 非访问修饰符
5.1 访问修饰符
访问修饰符 | 本类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
private | √ | × | × | × |
default | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
private私有访问权限,面向对象编程中封装的主要实现,将属性封装在类的内部,不暴露,只暴露公共的设置和获取接口给外部类使用。
default是默认访问权限,不添加权限修饰词,则是default访问权限。需注意的是在接口中和类中不同,接口中的变量默认是public static final类型的常量,方法默认是public类型的。
protected是保护类型的,对本包和其子类可见。不能用来修饰接口和类。保护不相关的类对该类的访问。
public是公有类型的,都可以进行访问。
- 父类中为public的,子类重写也必须是public的
- 父类中为protected的,子类中可以使protected或public的
- 只有private不会被继承
5.2 非访问修饰符
- static:静态修饰符,可用于修饰变量和方法,也可定义静态代码块。被static修饰的属于类,随着类的加载而初始化。静态的方法中只能调用静态元素,非静态的方法中既可以调用静态的元素,也可以调用非静态的元素。这是因为,非静态的方法调用依赖于对象,对象的创建晚于类的加载。static方法是不能重写的,同名的方法只是一个新的方法,跟父类的static方法无关,这是因为静态方法是属于类的。
- final:可用于修饰类、成员变量和成员方法。final修饰的类不能够被继承;final修饰的成员变量值不能发生更改;final修饰的方法不能被重写,但是会被继承。
- abstract:用于修饰抽象类或者抽象方法。拥有抽象方法的类必然是抽象类,但是抽象类不一定有抽象方法。子类必须重写所有的抽象方法,否则子类也必须声明为抽象的。抽象类不能实例化。不能被final和static修饰,因为final修饰的必须重写,与abstract冲突。
- synchronized:同步修饰符,会加互斥锁。
- transient:修饰成员变量,修饰后的不会被序列化。
- volatile:被该修饰符修饰的变量,读取时都从共享内存中读取,写入时会立即同步到共享内存中,保证可见性。
6 类和对象
6.1 变量类型
- 局部变量:定义在方法中的变量,作用域只在方法中
- 成员变量:定义在类中,方法外的变量。属于对象,类每初始化一个对象,就会存在一个该变量。
- 类变量:定义在类中,方法外,用static修饰的变量。随着类的初始化而初始化,属于类,所有对象共享该变量。
被final声明的变量为常量,第一次赋值后值不可更改。但是对于引用数据类型,指的是指向的变量是一个变量,他的地址不更改,但是地址中的内容是可以更改的。
成员变量和类变量会自动进行初始化,赋值为默认的值,其中整型为0,char为空字符,引用类型为null。
int a;
static char b;
static double c;
static byte d;
static String str;
public static void main(String[] args) {
Scratch scratch = new Scratch();
System.out.println(scratch.a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println(str);
}
输出
0
0.0
0
null
声明一个局部变量后,必须用赋值语句对变量进行显示的初始化后才能使用该变量。
Java10开始,对于局部变量,如果可以从变量的初始化值推断出它的类型,就不再需要声明类型。只需要使用关键字var让其自行推断。
var number = 12;
var str = "hello";
字节码文件中的
boolean var1 = true;
String var2 = "hello";
可以看到编译成字节码文件后,编译器自动将其转化为了特定的实际类型,因为java是强类型语言,需要有实际的数据类型,编译器对其推断后更改为了特定类型。
6.2 构造函数
- 每个类都有构造函数,如果没有定义构造函数,编译器会为该类提供一个默认的构造函数。
- 子类会在构造函数的第一行调用父类的构造函数,如果没有定义,会提供一个隐藏的super调用父类的无参构造函数。如果父类没有无参构造函数,则子类必须显示的调用父类的有参构造函数,否则会报错。这是因为在子类初始化之前必须先初始化父类。
- 构造函数是一个没有返回值类型的函数。
- 可以通过this调用本类的其他构造函数。
- 构造函数可私有,私有构造函数后,不能在其他地方创建该类对象。
6.3 源文件声明规则
- 一个源文件只能有一个public类
- 一个源文件可以有多个非public类,每个类都会生成一个对应的class文件
- 源文件的名字应与public类名相同
- 包名放在首行
- import导入放在包名和类名之间
- package和import作用于整个源文件的类
7 Java中参数传递
7.1 类名作为形参和返回值
- 类名作为参数
方法的形参是类名,传入的是该类的对象,实际传递的是对象的地址。 - 类名作为返回值
返回值是类名,返回的是该类的对象,传递的也是对象的地址。
7.2 抽象类作为形参和返回值
- 抽象类作为参数
方法的形参是抽象类类型的,传入的应该是抽象类的子类对象,因为抽象类是不能实例化的。 - 抽象类作为返回值
返回值是抽象类类型的,返回的是抽象类的子类对象。
7.3 接口作为形参和返回值
- 接口类型作为参数
接口类型作为参数,传入的是实现类的对象,因为接口不能实例化。 - 接口类型作为返回值
返回值是接口类型的,返回的是接口的实现类对象。