一杯温水谈天下,一壶炉酒论古今。
今天整理记录的内容:
1. 浅谈垃圾回收
2. 静态代码块
3. 内部类
4. 局部变量
5. 被final常量标识的变量。
一、浅谈垃圾回收
1.当类对象被垃圾回收器回收前会调用类的finalize(),类似C语言中的析构函数。 可以把finalize()方法理解为类对象的临终遗言。
2.很多人看到system.gc()这个方法时,都会认为是强制垃圾回收器进行垃圾回 收,其实不然,system.gc()只是建议垃圾回收器进行垃圾回收,垃圾回收器是否立即听从建议还要看垃圾回收器的当时状态。
3.所谓的垃圾回收就是将无变量名指向的对象从内存中去除
Data d = new Data();//此时变量d 指向 对象 new Data()。
d = null;//此时变量b 指向 null也就是什么也不指向,此时new Data()对象就没有变量指向,这是就很容易被垃圾回收器回收。
解释:
在这里我们既然知道了垃圾回收器倾向于回收没有变量名指向的对象,那么我们在开发的过程中,就要时刻记住无用对象的内存释放。也就是在这个对象被用完的时候在适当的地方就变量指空(类似上面的 d =null;)。
给大家一个Java内存泄漏的例子,自己可以研究研究为什么造成内存泄漏。对于原因我们会在后面章节讲解。
public class ReflectPoint {
public int x;
public int y;
public ReflectPoint(int x, int y) {
this.x = x;
this.y = y;
}
}
......
Collection collections = new HashSet();
ReflectPoint rp1 = new ReflectPoint(1,2);
ReflectPoint rp2 = new ReflectPoint(2,2);
ReflectPoint rp3 = new ReflectPoint(3,2);
collections.add(rp1);
collections.add(rp2);
collections.add(rp3);
rp1.x = 6;
collection.reomve(rp1);
System.out.print(collections.size());
......
输出结果:
3
....
二、静态代码块
1、经常用于将静态成员变量初始化。
private static String str1;//静态成员1
private static String str2;//静态成员2
//利用静态代码块初始化静态成员
static{
str1 = "a";
str2 = "b";
}
2.静态代码块只有当类从硬盘文件中加载进内存时,才会执行。这也是为什么静态代码块执行一遍的原因,因为将类的二进制数据从硬盘文件加载进内存并转换为字节码的行为只执行一次。
解释:
Java中用static关键字标识静态成员,属于类级别;既然是属于类级别,那么这些成员就是在类被操作时(这里指的操作是将硬盘中类的二进制数据加载进内存中并转换为字节码的过程)被执行。这也是为什么静态代码块一般用来初始化静态成员的原因。
Data d = new Data();//我们通过这个例子给大家讲解一下类从被加载到实例化对象的过程,
当Data类第一次被使用时,就会先执行①, 然后通过②过程实例化Data类对象;如果不是第一次被使用,就会直接执行②实例化出Data类对象。用static关键字标识的属性在①阶段被执行。
三、内部类
1、非静态内部类持有外部类的引用,用法:外部类名+this
2、得到非静态内部类的实例对象
解释:
public class OutClass {
private int x = 1;
public static void main(String[] args) {
// TODO Auto-generated method stub
//得到非静态内部类的实例对象
OutClass out = new OutClass();//实例化外部类对象
OutClass.IntClass in = out.new IntClass();//通过这种方式实例化非静态内部类
in.print();
}
/*非静态内部类*/
class IntClass{
public void print(){
System.out.print(OutClass.this.x); //也可以省略 OutClass.this
}
}
/*静态内部类*/
static class IntClass2{
public void print(){
System.out.print(OutClass.this.x);//这里编译报错。也就是静态的内部类不持有内外部类的引用。
}
}
}
输出结果:
1
四、局部变量
1.局部变量不能像成员那样被自动初始化(这里说的是java的8大基本数据类型)
2.匿名内部类访问局部变量时,局部变量要被final修饰。
解释:
class Variable{
private int x;
public static void main(String[] args) {
// TODO Auto-generated method stub
int y;
System.out.println(x);//输出的x值是0,x被自动初始化。
System.out.println(y);//编译报错,局部变量不能被自动初始化。
}
}
我们知道局部变量的生命周期,是从方法执行开始到方法结束消亡,也就是当方法开始执行的时候,栈为局部变量分配内存空间,当方法结束的时候,栈就会释放局部变量所占的内存空间;匿名内部类是被垃圾回收器不定时回收的,而局部变量是被栈自动释放的;所以有时候匿名内部类的生命周期比局部变量的生命周期要长。当一个匿名内部类访问局部变量的时候,如果不被final修饰,那么就有可能出现匿名内部类访问了已经不存在的局部变量。
五、被final常量标识的变量。
1.final方法比非final方法要快,因为在编译的时候已经静态绑定了变成编译期常量,不需要在运行时再动态绑定。
2.final标识变量后,变量指向的内容便不可变更。
解释:
在这里我们通过反编译.class文件的方式来讲解final标识符。
FinalTest.java文件
public class FinalTest {
public static void main(String[] args){
final int x = 0;//被final修饰
System.out.println(x);//大家看反编译后这里的变化
System.out.println("大家看反编译之后这里x的变化:::"+x);
}
}
编译上面的FinalTest.java文件得到 FinalTest.class文件。
我们看看反编译FinalTest.class文件之后的java代码
public class FinalTest
{
public static void main(String[] args)
{
int x = 0;
System.out.println(0);//这里在编译的时候就变成0了。
System.out.println("大家看反编译之后这里x的变化:::0"); //这里在编译的时候就直接进行字符串相加并把相加的结果给出。
}
}
接下来我们看看没有被final修饰的变量,反编译后的java代码
NoFinalTest .java文件
public class NoFinalTest {
public static void main(String[] args){
int x = 0;//没有被final修饰
System.out.println(x);//大家看反编译后这里的变化
System.out.println("大家看反编译之后这里x的变化:::"+x);
}
}
编译上面的NoFinalTest .java文件得到 NoFinalTest .class文件。
我们看看反编译NoFinalTest .class文件之后的java代码
public class NoFinalTest
{
public static void main(String[] args)
{
int x = 0;
System.out.println(x);//这里还是x,编译时不知道这是什么,需要运行时确定
System.out.println("大家看反编译之后这里x的变化:::"+x); //同上
}
}
相信看完上面的代码,你对final的理解更深了吧,其实就是把fianl修饰的变量,在程序中用到的地方,都变成指定的常量,这样加快了运行速度(不用去内存中拿变量指向的内容)。
最后给大家一个网上常用的例子,看完上面再看这个例子是不是觉得so easy!
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;//注意这里在运行时才分配内存
System.out.println((a == c));
System.out.println((a == e));
}
}
运行结果:
true
false
学习心得:观看资料—》看时思考—》实践证明—》整理记录 == 有思想的大牛!