1 关于JavaBean
JavaBean 是一种符合命名规范的 class
,即它通过 private
修饰字段,使用 getter/setter
来定义及获取,此时字段也称为属性,属性是一种通用的叫法,并非Java语法规定。
2 关于包装类
基本数据类型可以转换成对应的包装类,从而获取一些功能上的扩展。除了 Integer
和 Character
,其余的基本数据类型对应的包装类都是其首字母大写的形式表示。
基本数据类型转换为对应的包装类的过程叫做 装箱,包装类转化为基本数据类型的过程叫做 拆箱
装箱的形式有两种:
- 自动装箱:
int t1 = 10;
Integer t2 = t1;
- 手动装箱:
Integer t1 = new Integer(10);
拆箱的形式也有两种:
- 自动拆箱:
Integer t1 = 10;
int t2 = t1;
- 手动拆箱:
Integer t1 = 10;
int t2 = t1.intValue();
3 关于常量池
包装类也是引用类型,也是对象,但是在一些情况下的对象之间的比较,却有点特殊,同样的 String
类型也有如此状况。
常量池 指的是在编译期被确定,并被保存在已编译的 .class
文件中的一些数据,除了包含代码中所定义的各种基本类型,还有对象型的常量值(final
)如:包装类型(Float
,Double
除外),String
类型,还有包含一些以文本形式出现的符号引用,比如:
- 类和接口的全限定名(全限定名有绝对路径的意思,比如 Java 类包的定名)
- 字段的名称和描述符
- 方法和名称和描述符
3.1 关于 String
的内存分配
对于字符串,其对象的引用都是存储在栈中的,而实际内容如果是 编译期 已经创建好(直接用双引号定义的)的就存储在常量池中,如果是 运行期(new
出来的)就存储在堆中。对于 equals
相等的字符串,常量池中永远只有一份,在堆中可以有多份。
String str1 = "123";
String str2 = "123";
String str3 = new String("123");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
// true false
注意:对于通过
new
产生一个字符串时,会先去常量池中查找是否已经有该对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此对象的拷贝对象。
Strings=new String(“xyz”);
会产生几个对象?答案是:一个或两个,如果常量池中原来没有xyz
,就是两个。
3.2 关于包装类的内存分配
如同 String
类,包装类除了 Float
和 Double
外,其它类型都会有使用常量池的情况。
当包装类使用 自动装箱 的形式去实例化对象的时候,它首先会在常量池中查找是否存在这个对象,如果没有就直接在常量池中创建一个,如果有就直接指向该对象。
Integer int1 = 100;
Integer int2 = 100;
Integer int3 = new Integer(100);
System.out.println(int1 == int2);
System.out.println(int1 == int3);
// true false
注意:
Integer
类型使用常量池是有限制的,大小必须在-128
到127
之间;包装类型与基本数据类型的比较时,包装类型会自动拆箱后再进行比较
4 枚举
枚举Enum可以定义常量类型,它被编译器编译为: final class Xxx extends Enum {...}
。
可以为枚举类声明构造函数、字段和方法,但值得注意的是枚举类 只能通过自身内部实例化,即无法再外部构造实例,因为 枚举类的构造函数是私有类型,所以其构造函数不能声明为 public
。
因为枚举实例只有一个,所以可以通过两个枚举实例是否相同可以使用等号判断,使用 name()
方法可以获取枚举实例的常量定义字符串。
eg:
public enum WeekDay {
// 由于构造函数是private,所以实例化在类的内部创建
SUN("星期日"), MON("星期一"), TUE("星期二"), WED("星期三"), THU("星期四"), FRI("星期五"), SAT("星期六");
private String chinese;
public String toChinese() {
return chinese;
}
WeekDay(String chinese) {
this.chinese = chinese;
}
}
5 关于异常
根据Java的异常继承树状图,可以发现Java的异常包括分为两个分支,分别是 Error
错误 和 Exception
异常:
Error
错误一般不是编写的问题,而是由于JVM运行你这段代码出现了问题,比如堆溢出,配置文件出错等,这些你是无法人为的在你代码里可以保证的,必须要额外的去操作,重新配置虚拟机,修改配置文件等等Exception
异常是程序本身可以处理的异常,也就是你常见的空指针异常(NullPointerException
),数组超出范围异常(IndexOutOfBoundsException
)等等。Exception
异常又可分为运行时异常和非运行时异常,非运行时异常需要程序员作出相应的处理
同时Java的异常(即 Error
和 Exception
)也可以分为 检测异常 和 非检测异常。
5.1 检查异常(checked exceptions)
检测异常就是编译器要求你必须处理的异常,比如说当写某段代码时,编译器会警告你,要求你添加 try...catch
或者 throws
处理异常,此时这个异常就是检测异常,即代码还没有执行,编译器就会检测你的代码是否可能会发生错误异常,并要求你对这种可能出现的情况 必须 作出相应的处理,所以对于检查的异常必须处理,或者必须强制捕获或者必须抛出。
除了
RuntimeException
与其子类,以及Error
错误,其他的都是检查异常
5.2 非检查异常(unchecked exceptions)
编译器不要求强制处置的异常,虽然你有可能出现错误,但是我不会在编译的时候检查,没必要,也不可能。比如 NullPointerException
,这些异常通常都需要运行过后才能发生。
非检查异常有:
RuntimeException
与其子类,以及Error
错误
5.3 异常的转换
printStackTrace
可以打印出异常传播的方法调用栈
当捕获一个异常后,可以在捕获之后的 catch
语句中将返回另一个异常,从而达到异常的转换效果,但此时值得注意的是,如果这时候使用了 printStackTrace
打印异常传播的调用栈,会发现并没有原始的异常信息,如果想要跟踪到原始异常的信息,则需要在抛出新的异常时,将保留的原始异常传入到异常中。
eg:
public static void main(String[] args) {
try {
Integer.parseInt(null);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e); // 传入原始异常
} finally {
System.out.println("finally");
}
}
- 当
catch
语句抛出异常的时候,finally
语句会比catch
语句先执行- 当
catch
语句和finally
语句同时都抛出异常的时候,由于异常只能抛出一个,所以只有finally
语句抛出的异常会被抛出,而catch
语句中的异常会被屏蔽掉
5.4 自定义异常
当需要抛出异常的时候,应该优先抛出JDK定义的异常,JDK定义的常用异常:
RuntimeException
NullPointerException
:空指针IndexOutOfBoundsException
:越界SecurityException
IllegalArgumentException
:参数错误NumberFormatException
IOException
UnsupportedCharsetException
,FileNotFoundException
,SocketException
。。。
ParseException
,GeneralSecurityException
,SQLException
,TimeoutException
。。。
自定义异常推荐派生自 RuntimeException
,自定义异常应该提供多个构造方法,可以使用IDE根据父类快速创建构造方法。