一、Java主类结构
Java主类包括包声明、导入类库、成员变量和局部变量、方法等等,主类存在如下规范
-
每个Java文件有且只有一个与文件名同名的public主类
- 除了主类,可以*定义任意个内部类与普通类(只能是包访问权限)
- main()方法可以定义在某个Java文件的任意类中(如果在内部类中就必须是静态内部类),可以在不同类中有多个main()方法
- 每个Java文件最好只有一个类,即为主类,这样便于程序维护
二、关键字与标识符
标识符可以简单地理解为一个名字,用来标识类、方法和变量等等。而关键字就是Java语言中已经被赋予特定意义的一些单词,比如public、int、static等等。
标识符的编写规范:
- 必须以字母、下划线(_)或者$符号开头,后面可以使用字母、下划线、$符号和数字
- 不能将关键字作为标识符使用
- 如果标识符由多个单词组成,使用“驼峰风格”书写,类名首字母大写
- 区分大小写,同类型的标识符不能重复
- 选择有意义的单词作为标识符,加强程序可读性
三、变量与常量
Java的数据类型分为基本数据类型和引用类型,今天先说一下基本数据类型。
Java的基本数据类型包括:
- 整数类型:byte(8位)、short(16位)、int(32位)、long(64位)
- 浮点类型:float(32位)、double(64位)
- 字符类型:char(16位) 注:1字节为8位,Java使用Unicode编码,中英文字符都是2字节
- 布尔类型:boolean
在实际开发中,这八种基本数据类型都有其特定的使用环境:
- 表示整数首先要考虑的就是int
- 表示小数一定使用double
- 表示日期时间数字、文件和内存大小使用long
- 进行编码转换、数据IO使用byte
- 处理中文使用char
- 处理逻辑使用boolean
- float和short几乎不会使用
需要注意的是,编译器默认将整数字面量视作int类型,将小数字面量视作double类型。在给float类型的变量赋值时,必须在小数后面加f,否则会因为赋值关系错误而使得编译器报错;给long类型赋值时,如果超过了int类型的范围,需要在数值后面加l或L。给Long类型对象初始化时,数值后面必须加l或L,诸如使用equals()等方法时也必须加l或L,比如Long userId = 1L;System.out.println(userId.equals(1L)); 。实际开发中,无论是给long类型变量还是给Long类型对象进行初始化时,最好都要加L。
Java基本数据类型的赋值关系:按照byte、short、int、long、float、double的顺序,靠前的可以赋值给靠后的,这就是隐式类型转换。如果靠后的想赋值给靠前的,或者将超出该类型范围的数值赋值给该类型的变量,就需要进行强制类型转换。强转的原理就是将多出来的位“削掉! 比如将int型变量赋给byte型,byte只有8位,而int有32位,所以需要进行强转将高24位“削掉”;再比如给byte变量赋值128,128会被编译器视作int类型(这里必须注意!!),且其第9位为1,编译器判断如果进行强制类型转换会丢失精度,所以不通过编译,让我们自己决定是否进行强转。
public class Test01 {说到现在有一个疑问,刚才不是说整数的默认类型是int吗?那么给byte型变量赋值1的时候,难道不需要我们进行强转吗?答案是使用常量赋值时,编译器会自动判断强转会不会丢失数据精度!如果判断不会丢失精度,编译器自动帮我们完成强转 ,所以给byte变量赋值常量1可以通过编译;如果判断会丢失精度,编译器就不会帮我们完成强转,而是让我们自己决定是不是使用强转,所以给byte变量赋值常量128的时候,编译器判断强转会丢失精度(因为第9位为1),所以不会通过编译。 但是!!由于等号右边的运算结果一定是默认的int类型,如果我们使用变量赋值的话,编译器就无法判断强转会不会丢失精度,进而无法通过编译(面试重点)!
public static void main(String[] args){
byte b = 1;
short s = 2;
int i1 = (int)3.1;//将小数强制类型转换之后赋给整数,会将小数部分截掉
int i2 = 0;
long l = 4;
float f = 0.1f;//给float变量赋值时必须加上f,否则会被当做double值,出现错误
double d = 0.2;
char c = 'a';
//!b = c;char类型的变量不能赋给byte类型的变量
//!s = c;char类型的变量不能赋给short类型的变量
i2 = c;//char类型的变量可以赋给int类型的变量
l = c;//char类型的变量可以赋给long类型的变量
f = c;//char类型的变量可以赋给float类型的变量
d = c;//char类型的变量可以赋给double类型的变量
//!byte b1=128;
byte b1 = (byte)128;
//!byte b2 = i1;
byte b2 = (byte)i1;
System.out.println("将小数强制类型转换之后赋给整数类型,结果为i="+i1);//结果为3
System.out.println("字符常量'a'赋值给int,long,float,double变量的结果为"+i2+" "+l+" "+f+" "+d);//结果为97 97 97.0 97.0
System.out.println("使用强制类型转换给byte变量赋值128之后的结果:b1="+b1);//结果为-128
System.out.println("使用强制类型转换将int变量赋给byte变量的结果:b2="+b2);//结果为3
}
}
public class Test02 {
public static void main(String[] args) {
byte b = 1 + 2;//由于编译器确定1+2一定在byte的范围内,所以自动进行了强制类型转换,可以通过编译
byte b1 = 1;
//!b = b1 + 1;b1+1的结果是int,由于b1是变量,编译器无法判断b1+1是否在byte范围内,所以不能通过编译
byte b2 = 2;
//!b = b1 + b2;b1 + b2的结果是int,由于b1, b2都是是变量,编译器无法判断b1 + b2是否在byte范围内,所以不能通过编译
final byte b3 = 1;
b = b3 + 1;//虽然b3+1的结果也是int,但是由于b3是常量,所以编译器确定b3+1一定在byte的范围内,所以自动进行了强制类型转换,可以通过编译
}
}
从代码我们可以看出:虽然1+2是int值,但是1+2的结果是常量,编译器可以确定1+2肯定在byte的范围内,所以可以通过编译,并由编译器完成自动强制类型转换(“削掉”高24位)。但是后面两种情况,b1,b2都是变量,编译器不能确定运算结果是否会超出范围(万一b1改为127呢?“削掉”前三个字节就会造成丢失精度),所以不能通过编译,它的意思就是:你自己决定是不是要强转!注意第三种情况,编译器会将字面量与final变量统一视作常量!!所以编译器可以确定b3+1肯定在byte的范围内,所以可以通过编译,并由编译器自动完成强转。
char和short也存在这种问题。但是int和long不会出现这种问题,因为无论如何等号右边的运算结果都默认为int。如果用变量给int类型变量赋值,编译会通过,但如果运算结果超出了int类型的范围,底层会自动完成强制类型转换(后果是溢出,所以实际上这是一个缺陷)。而由于运算结果是int,赋值给long符合隐式类型转换,更加没有问题~
最后说下变量的作用域、生命周期和常量的声明。
- 变量分为成员变量(类中声明的变量)与局部变量(方法体中声明的变量),成员变量又分为实例变量与静态变量
- 成员变量可以不初始化,局部变量必须初始化(这是由不同内存区域的特性决定的,后面会说)
- 例外:final成员变量也必须初始化
- 实例变量的作用域是整个类;静态变量的作用域是整个应用程序;而局部变量的作用域是从定义它的最近的‘{’符号到对应的‘}’符号
- 静态变量存在于方法区,JVM进程结束才会被销毁;实例变量存在于堆中的对象里,除非对象被回收,否则不会被销毁;而局部变量存在于虚拟机栈的帧栈,一旦定义该局部变量的方法结束调用,帧栈弹出,局部变量就会被销毁
- 局部变量可以与成员变量的名字相同,在其作用域内成员变量被覆盖
- Java不能在局部代码块中重复定义同名局部变量(局部代码块的作用是控制局部变量的作用域,不常用)
- 常量声明使用static和final关键字,常量名最好大写,final代表该值不能被改变,至于为什么要用static等学到静态再做总结
public class Test03 {
private int i1;//实例变量声明,可以不初始化,但是JVM会给变量默认初始化为0
private static int i2;//静态变量声明,可以不初始化,但是JVM会给变量默认初始化为0
public static void main(String[] args) {
Test03 t = new Test03();
t.test();
System.out.println("方法调用结束后,i1的值为" + t.i1 + ",证明方法调用结束后,局部变量被销毁了");//不管是不是在本类中,要在静态方法里面调用实例变量,必须使用引用.变量的格式
System.out.println("i2 = " + i2);//在本类中,静态变量的可以直接在静态和非静态方法调用,之所以能直接调用静态变量,是编译器自动在i2前加了类名Test03.
System.out.println("Test类的final静态变量COUNT = " + Test.COUNT);//在不同类中,要使用类名.变量的格式调用静态变量
}
public void test() {
System.out.println("在非静态方法中,i1的值为" + i1);//在本类中,非静态变量的可以直接在非静态方法调用,之所以能直接调用实例变量,是编译器自动在i1前加了this.
int i1 = 1;//局部变量必须初始化
System.out.println("定义局部变量i1后,i1的值为" + i1 + ",很明显,局部变量覆盖了成员变量");
{
//!int i1 = 2;不能在局部代码块中重复定义同名局部变量
}
}
}
class Test{
static final int COUNT = 3;//final静态变量(也可以视作常量)的声明与指定初始化 (final变量必须初始化)
}
其中有关于静态变量与静态方法的知识后面再说,静态很重要!