垃圾回收
- 垃圾回收器只释放经由
new
分配的内存。 - 如果JVM并未面临内存耗尽的情形,他是不会浪费时间去执行垃圾回收的。
初始化
- 即使变量定义于方法体之间,仍旧会在任何方法(包括构造器)之前初始化。
- 静态变量会比非静态变量提前初始化,并且只初始化一次。
(局部变量不会被初始化)
关于静态初始化
static
标明的属性在类加载时就同时加载了,它会逐层寻找基类。
什么意思呢?举个例子:有一个boy
类,其父类为people
程序运行时会首先根据main
所在类逐层向上找到people
类中静态属性的声明,再逐层向下,找boy
类中的静态属性,为其初始化。
代码如下:
class People {
private int i = 9;
People(){
System.out.println("This is a people.");
}
private static int x1 = printInit("static People.x1 init.");
static int printInit(String s) {
System.out.println(s);
return 47;
}
}
public class Boy extends People {
private int k = printInit("Boy.k init.");
public Boy() {
System.out.println("This is a Boy.");
}
private static int x2 = printInit("static Boy.x2 init.");
public static void main(String[] args) {
System.out.println("Boy constructor");
Boy b = new Boy();
}
}
我们可以看到输出结果为:
static People.x1 init.
static Boy.x2 init.
Boy constructor
This is a people.
Boy.k init.
This is a Boy.
就像我上面说的一样,先执行了顶层基类people
中静态属性x1
的初始化
而后执行其子类boy
中静态属性x2
的初始化
初始化之后,进入main
方法
而在创建对象时,也是先执行其基类的构造方法,并初始化非静态属性后,再执行其构造方法
可变参数
- 可变参数传入格式:
String... trailing
, 不传也可以,但是要注意重载。
方法的重写
- 尽量避免在构造器中调用其他方法(唯一安全的是调用
private/final
方法)
可能造成的混乱:如果在基类的构造其中调用重写的方法
则访问的是由多态决定的子类的方法
而属性因为还未构造,所以值为默认的0
同样用代码来举例:
class People {
protected int i;
People(){
System.out.println("This is a people.");
test();
}
protected void test() {
System.out.println("This is People.i = " + i );
}
}
public class Boy extends People {
protected int i = 10;
public Boy() {
System.out.println("This is a Boy.");
test();
}
@Override
protected void test() {
System.out.println("This is Boy.i = " + i );
}
public static void main(String[] args) {
System.out.println("Boy constructor");
Boy b = new Boy();
}
}
可以看到,在子类Boy
中重写了test()
方法
那么如果在基类People
的构造器中调用test()
,会是什么结果呢?
Boy constructor
This is a people.
This is Boy.i = 0
This is a Boy.
This is Boy.i = 10
出人意料的,我们发现当我们是声明一个Boy
对象时,会执行其父类People
的构造器
而在People
构造器中执行的test()
方法,却是子类Boy
中重写的方法
这是由于多态的原因,具体原因这里不赘述,我会单开一章来详细说明多态的实现原理。
而我们发现,即使调用的是子类重写的方法,其打印出的属性却是因还未构造所以默认为0的属性。
这就会为程序带来不可知的错误,所以应尽量避免这种情况的发生。
- 重写的方法可以返回子类。
对于重写的方法来说,要求其入参和返回参数一致,但返回参数也可以是其子类,代码如下:
class People {
protected int i;
People(){
System.out.println("This is a people.");
}
protected People test() {
System.out.println("This is People test()");
return new People();
}
}
public class Boy extends People {
protected int i = 10;
public Boy() {
System.out.println("This is a Boy.");
}
@Override
protected Boy test() {
System.out.println("This is Boy test()");
return new Boy();
}
public static void main(String[] args) {
System.out.println("Boy constructor");
Boy b = new Boy().test();
}
}
我们在子类中重写了test()
方法,并且返回的是People
的子类,一样可以执行。
输出结果:
This is a people.
This is a Boy.
This is Boy test()
This is a people.
This is a Boy.
组合优于继承
为什么会这么说?组合到底比继承优秀在了哪里?
其实并不能说“优秀”,而应该是“各司其职”
通常,我们用继承来区分不同的行为
而组合的优点在于其可以在类中灵活应用绑定不同对象
我们用代码来说明组合的用处:
class People {
public void people(){}
}
class Boy extends People {
@Override
public void people() {
System.out.println("This is a boy.");
}
}
class Girl extends People {
@Override
public void people() {
System.out.println("This is a girl.");
}
}
class Stage {
private People people = new Boy();
public void change() {
people = new Girl();
}
public void performPlay() {
people.people();
}
}
public class Test {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
}
输出结果:
This is a boy.
This is a girl.
可以看到,在Stage
类中声明了一个People
,将其组合起来,我们就能在Stage
中随意改变其引用
比如一开始people
的引用指向的为boy
,操作其行为也是boy
中重写的方法
但是当我们想引发其他行为的时候,就可以用change()
将boy
转型为girl
,因为其所属基类一致
此时,再执行行为就是girl
的行为了,而这是继承不能达成的效果,因为不能在运行期间继承不同的对象
继承决定行为间的差异,而组合表达状态的变化。
继承接口可以获得新的接口
但要注意的一点是,尽量避免去组合多个接口
比如接口A中有方法f()
,接口B中同样有方法f()
同时实现A与B就会造成混乱
内部类
- 在匿名内部类使用的参数应设定为
final
- 内部类无法被覆盖
- 使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响
集合
看下面一段代码:
List<Integer> ls = Arrays.asList(1, 2, 3);
ls.add(4);
这段代码会报错:Exception in thread "main" java.lang.UnsupportedOperationException
原因是:Arrays.asList(T... a)
返回的是Arrays
的内部类ArrayList
,而不是util
包下的ArrayList
。
后者重写了remove
和add
等方法而前者没有重写,所以会抛出异常。
异常
对于子类覆写的父类的方法而言,只能声明父类方法声明的异常或不声明异常,比如:
class AA {
public void testA() throws NullPointerException {}
}
class C extends AA {
@Override
public void testA() throws Exception {}
}
这样写是错误的,因为父类的方法中只抛出了NullPointerException
。
所以子类如果抛出异常,只能抛出父类声明的异常,当然不抛出异常也是可以的。
可以在子类方法中尝试捕捉其他异常:
class AA {
public void testA() throws NullPointerException {}
}
class C extends AA {
@Override
public void testA() {
try {
int[] is = new int[1];
System.out.println(is[1]);
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
而对于父类方法中未声明异常列表,子类覆写的方法可以随意抛出异常:
class AA {
public void testA() {}
}
class C extends AA {
@Override
public void testA() throws NullPointerException {}
}
这样写同样是可行的。
静态内部类
注意静态内部类是不随着外部类加载而加载的,只有当访问静态内部类中的变量或者方法时才会进行加载,其加载时会先加载外部类。
关于asList方法
要注意的是,当将基本类型的数组,比如整型数组int[]
用Arrays.asList
方法进行转化时,数组是作为一个整体被传入list之中的,也就是说list的大小为1.
(String不属于基本类型所以不受限制)
Java8提供了一种stream方法可以将基本类型数组(只支持int、long、double)转为List:
List<Integer> aList = Arrays.stream(testList).boxed().collect(Collectors.toList());
(.boxed方法是一个中间方法,可以将每个元素包装为一个Integer作为stream返回)