https://mp.weixin.qq.com/s/yKCUlvaL9GwgpvSjBtvihQ
https://mp.weixin.qq.com/s?__biz=MzI3ODc3NzQ4NQ==&mid=2247483771&idx=1&sn=dbf76d7f98630b2911253d1919965fd1&chksm=eb509a99dc27138f226e099234db6716051d739533e0e00d6912b796588e5243c0fd73dc75cb&scene=21#wechat_redirect
问:谈谈 Java 中 final、finally、finalize 的区别?
答:这道题其实没有任何意义,无非就是考察开发者有没有区分这几个关键字的含义,仅仅关联是因为长得像而已。
final 是一个修饰符,
- 如果一个类被声明为 final 则其不能再派生出新的子类,所以一个类不能既被声明为 abstract 又被声明为 final [所谓不可同时出现]的;
- 将变量或方法声明为 final 可以保证它们在使用中不被改变(对于对象变量来说其引用不可变,即不能再指向其他的对象,但是对象的值可变),
package javabasics.finaldemo; public class FinalDemo { private String s; public FinalDemo(String s){ this.s = s; } public static void main(String[] args) { final String s = new String("55"); System.out.println("final修饰的对象变量:" + s);
// s = new String("");//Cannot assign a value to final variable 's' final声明的对象变量不可以再指向其他对象
final FinalDemo finalDemo = new FinalDemo("ss"); System.out.println(finalDemo.s); finalDemo.s = "改变数值"; System.out.println(finalDemo.s);//对象的值可变 } } 输出结果: final修饰的对象变量:55 ss 改变数值
- 被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改,被声明为 final 的方法也同样只能使用不能重载。
- 使用 final 关键字如果编译器能够在编译阶段确定某变量的值则编译器就会把该变量当做编译期常量来使用,如果需要在运行时确定(譬如方法调用)则编译器就不会优化相关代码;将类、方法、变量声明为 final 能够提高性能,这样 JVM 就有机会进行估计并进行优化;接口中的变量都是 public static final 的。
finally 用来在异常处理时提供块来执行任何清除操作,如果抛出一个异常,则相匹配的 catch 子句就会执行,然后控制就会进入 finally 块。
finalize 是一个方法名,Java 允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作,这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,它是在 Object 类中定义的,因此所有的类都继承了它,子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作,finalize() 方法在垃圾收集器删除对象之前对这个对象调用的。
问:java 中 static、final、static final 的区别是什么?
答:final 可以修饰属性、方法、类、局部变量(方法中的变量),修饰属性的初始化可以在编译期,也可以在运行期【此测试没有通过,有待商榷】,初始化后不能被改变;修饰的属性表明是一个常数;修饰方法表示方法不能在子类中被重写;修饰类表示类不能被继承。
final String attribute;//Variable 'attribute' might not have been initialized
//修饰方法表示方法不能在子类中被重写
class Father{ final void fatherFinal(){ System.out.println("final修饰的方法"); } //final修饰的方法可以被重写 final void fatherFinal(String s){ System.out.println("final修饰的方法"); } } class Son extends Father{ //'fatherFinal()' cannot override 'fatherFinal()' in 'javabasics.finaldemo.FinalDemo.Father'; overridden method is final void fatherFinal(){ System.out.println("final修饰的方法"); } }
//修饰类表示类不能被继承。
final class Father{ final void fatherFinal(){ System.out.println("final修饰的方法"); } //final修饰的方法可以被重写 final void fatherFinal(String s){ System.out.println("final修饰的方法"); } } //Cannot inherit from final 'javabasics.finaldemo.FinalDemo.Father' class Son extends Father{ }
static 可以修饰属性、方法、代码段、内部类(静态内部类或嵌套内部类)【不可以修饰局部变量】,修饰属性的初始化在编译期(类加载的时候),初始化后可以被修改值;修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用 static 修饰的属性、方法等;static 不可以修饰局部变量。
static final(或者 final static)是组合修饰,static 修饰的属性强调它们只有一个,final 修饰的属性表明是一个常数(创建后不能被修改),static final 修饰的属性表示一旦给值就不可修改并且可以通过类名访问,static final 也可以修饰方法,表示该方法不能重写,可以在不 new 对象的情况下调用。
问:下面程序的有问题吗,结果是什么?
class Test { public static String foo(){ System.out.println("foo called."); return "return called."; } } public class Demo { public static void main(String[] args) { Test obj = null; System.out.println(obj.foo()); } }
答:没有问题,运行结果如下:
foo called.
return called.
因为 jvm 内存里有栈区、堆区,栈区主要用来存放基础类型数据和局部变量,堆区主要存放 new 出来的对象,在堆区又有一个叫做方法区的内存区域用来存放常量、static 变量和 static 方法、还有类的信息,static 的变量和方法不依赖对象,即使对象没有创建,在类加载的时候已经存在信息了(Test 在声明时就被加载了),jvm 识别出是 static 方法就直接调用了在方法区内存里的方法,没有报空指针异常。
问:下面程序的运行结果是什么?为什么?
public class Test { public static void main(String[] args) { String a = "hello2"; final String b = "hello"; String c = b + 2; String d = "hello"; String e = d + 2; System.out.println((a == c)); System.out.println((a == e)); } }
答:运行结果如下:
true
false
因为当 final 变量是基本数据类型以及 String 类型时如果在编译期间能知道它的确切值则编译器会把它当做编译期常量使用,也就是说在用到该 final 变量的地方相当于直接访问了这个常量,不需要在运行时确定,所以上面代码中由于变量 b 被 final 修饰从而被当做编译器常量,故在使用到 b 的地方会直接将变量 b 替换为它的值,而对于变量 d 的访问却需要在运行时通过链接来进行。
问:简单说说 Java 中 this 和 super 的区别和应用场景?
答:this 为当前类的引用对象,谁调用代表谁;super 为父类存储空间标识,可以理解为父类对象,谁调用代表谁的父亲。
对于 this 的应用场景主要分下面几类:
-
构造方法:通过 this 调用同类中另一个满足指定参数类型的构造方法的用发是 this(参数列表); 这个仅仅在类的构造方法中,别的地方不能这么用,同时要注意 this(参数列表); 语句只能用在子类构造方法体中的第一行。
-
变量:函数参数或者函数中的局部变量和成员变量同名的情况下成员变量被屏蔽,此时要访问成员变量则需要用 this.成员变量名; 的方式来引用成员变量,在没有同名的情况下可以直接用成员变量的名字而不用 this。
-
函数:在函数中需要引用该函所属类的当前对象时候可以直接用 this,特别注意,this 不能用在 static 方法中,因为 static 方法是类级别的,this 是对象级别的。
对于 super 的应用场景主要分下面几类:
-
构造方法:在子类构造方法中要调用父类的构造方法可以用 super(参数列表); 的方式调用,参数不是必须的,同时要注意 super(参数列表); 语句只能用在子类构造方法体中的第一行。
-
变量:当子类方法中局部变量或子类成员变量与父类成员变量同名(即子类局部变量覆盖父类成员变量)时用 super.成员变量名; 来引用父类成员变量,如果父类的成员变量没有被覆盖也可以用 super.成员变量名; 来引用父类成员变量,只是多此一举。
-
成员方法:当子类成员方法覆盖父类成员方法(子类和父类有完全相同的方法定义)时用 super.方法名(参数列表); 的方式访问父类方法。
注意:this 和 super 不能同时出现在一个构造函数里面,因为 this 必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过;此外由于 this 和 super 都指的是对象,所以均不可以在 static 中使用(包括 static 变量、static 方法、static 语句块)。
问:为什么构造方法里 this 或者 super 函数调用必须放在第一行且无法共存?
答:super 方法在构造函数的第一行原因是子类有可能访问了父类对象,比如在构造函数中使用父类对象的成员函数和变量,在成员初始化使用了父类,在代码块中使用了父类等,所以放在第一行可以保证在子类可以访问父类对象之前完成对父类对象的初始化。
this 方法在构造函数的第一行原因是为保证父类对象初始化的唯一性,
- 因为假设类 B 是类 A 的子类,如果 this 方法可以在构造函数的任意行使用则首先程序运行到构造函数 B() 的第一行发现没有调用 this() 和 super(),就自动在第一行补齐了 super() 方法(这是 java 默认的机制),以此完成了对父类对象的初始化,然后返回子类的构造函数继续执行,当运行到构造函数 B() 的 this(参数) 调用行时, 调用 B 类对象的另一个构造方法 B(参数),在 B(参数) 中还会对父类对象再次初始化,这就造成了对资源的浪费,也有可能造成某些意想不到的结果,所以 this 方法不能出现在构造方法除第一行以外的其他行。
这也就解释了为啥在构造方法里面 this 与 super 方法不能同时存在的原因。
问:如下程序有什么问题吗,结果是什么?
class Base { Base() { System.out.println("Base"); } } public class Demo extends Base { public static void main(String argv[]) { Demo demo = new Demo(); super(); } Demo() { System.out.println("Demo"); } }
答:上面的程序无法编译通过,在 IDE 中 super(); 行语句会提示 "Only constructors can invoke constructors" 错误,因为 Java 里在子类中用 super 调用父类构造函数时调用函数必须放在子类构造函数的第一条语句位置,上面代码那种调用是完全错误的用法,即便是调用父类的普通方法也应该是 super.方法 的形式。