Java——类谜题
1、令人混淆的构造器
代码如下格式:
public class Confusing {
private Confusing(Object o) {
System.out.println("Object");
}
private Confusing(double[] dArray) {
System.out.println("double array");
}
public static void main(String[] args) {
new Confusing(null);
}
}
分析:null这个参数,两个构造器都可以接受,会怀疑这段代码是否会通过编译,运行后发现可以通过编译并且打印出double array?Why?
正解:知识点——Java重载解析过程是以两个阶段运行的。第一个阶段,选取所有可以获得并且可以使用的方法或构造器。第二阶段,从上一阶段选取的方法或构造器中选取一个最精准的方法。精准的理解——能接受的参数类型越少越精准。例如:本例中的Object,他接收的范围太广了,至少能够把double数组和int数组接受了,所以他就不精准。但是,能接收double数组但不能接收其他数据对象类型例如int,所以就相对来说显得更精准,所以在第二个阶段,选取了相对精准的方法,所以才会有了double的输出。
2、静态方法的覆盖
代码:
class Dog {
public static void bark() {
System.out.print("woof ");
}
}
class Basenji extends Dog {
public static void bark() { }
}
public class Bark {
public static void main(String args[]) {
Dog woofer = new Dog();
Dog nipper = new Basenji();
woofer.bark();
nipper.bark();
}
}
先来说明一件事情:nipper编译期类型为Dog类型,运行期类型为Basenji类型。
分析:静态方法的调用是在编译期选好的,而这个调用当然就要看对象的编译期类型。本例子中,woofer和nipper具有相同的编译类型Dog,虽然nipper在运行期间类型为Basenji,所以他们两个都会打印出woof即打印结果为:woof woof。
静态方法的覆盖叫隐藏,一般方法的覆盖叫重载。重载能够得到动态分配的特性(其实就是看运行期的对象类型),隐藏不能得到这种特性(隐藏只能看编译期类型)。所以,如果将Dog和Basenji中的函数方法去掉static,变为重载,那么将会只打印一个woof。
3、instanceof和强制类型转换
三个程序的代码:
public class Type1 {
public static void main(String[] args) {
String s = null;
System.out.println(s instanceof String);
}
}
public class Type2 {
public static void main(String[] args) {
System.out.println(new Type2() instanceof String);
}
}
public class Type3 {
public static void main(String args[]) {
Type3 t3 = (Type3) new Object();
}
}
(1)instanceof左边对象运行期为null的时候返回false。
(2)instanceof在编译的时候,如果两边都是类(A instanceof B A、B都是类),则其中一个必须是另外一个的子类,否则就不能通过编译。很明显本例中的Type2和String没有子类关系,所以不能通过编译。
(3)先讨论下强制类型转换的知识:在Java中由于继承和向上转型,子类可以非常自然地转换成父类,但是父类转换成子类则需要强制转换。因为子类拥有比父类更多的属性、更强的功能,所以父类转换为子类需要强制。这种强制类型转换需要条件的。
父类能强制转换为子类:
Father father=new Son();//因为Java的继承和向上转型,子类可以很自然地转换为父类
但是请注意这个Son对象实例在内存中的本质还是Son类型的,只不过它的能力临时被消弱了而已,如果我们想变强怎么办?将其对象类型还原!
Son son=(Son)father; //这种强制类型转换时可以的。
其实father引用仍然是Father类型的,只不过是将它的能力加强了,将其加强后转交给son引用了,Son对象实例在son的变量的引用下,恢复真身,可以使用全部功能了。
父类不能强制转换为子类:当引用类型的真实身份(运行期的类型)是父类本身的类型时,强制类型转换就会产生错误。
Father father=new Father();
Son son=(Son)father;这个系统会抛出ClassCastException异常信息。
编译器在编译时只会检查类型之间是否存在继承关系,有则通过;而在运行时就会检查它的真实类型,是则通过,否则抛出ClassCastException异常。
所以在继承中,子类可以自动转型为父类,但是父类强制转换为子类时只有当引用类型真正的身份为子类时才会强制转换成功,否则失败。
4、Java类初始化顺序
JVM调用main函数时,会触发main函数所在类的初始化。首先,静态域被设置为缺省值(0),接下来按照静态域的出现顺序进行初始化。
我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(这里先假设没有继承) :
(静态变量、静态初始化块(他们两个初始化顺序是根据写的位置))>(变量、初始化块(他们两个初始化顺序是根据写的位置))>构造器。
如果有继承的话,是按照这个顺序先去加载父类。
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器
5、Java实例初始化顺序
代码如下:
class Point {
protected final int x, y;
private final String name; // Cached at construction time
Point(int x, int y) {
this.x = x;
this.y = y;
name = makeName();
}
protected String makeName() {
return "[" + x + "," + y + "]";
}
public final String toString() {
return name;
}
}
public class ColorPoint extends Point {
private final String color;
ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
protected String makeName() {
return super.makeName() + ":" + color;
}
public static void main(String[] args) {
System.out.println(new ColorPoint(4, 2, "purple"));
}
}
分析:打印出了[4,2]:null,并不是期望的[4,2]:purple。
总之,不要在构造器中调用可覆写的方法。在实例初始化中产生的循环将是致命的。该问题的解决方案就是惰性初始化,即当它第一次被使用时初始化,以此取代积极初始化,即当Point实例被创建时初始化,代码如下:
class Point {
protected final int x, y;
private String name; // Lazily initialized
Point(int x, int y) {
this.x = x;
this.y = y;
// name initialization removed
}
protected String makeName() {
return "[" + x + "," + y + "]";
}
// Lazily computers and caches name on first use
public final synchronized String toString() {
if (name == null)
name = makeName();
return name;
}
}
6、这是一个奇怪的谜题
代码:
public class Creator {
public static void main(String[] args) {
for (int i = 0; i < 100; i++)
Creature creature = new Creature();
System.out.println(Creature.numCreated());
}
}
class Creature {
private static long numCreated = 0;
public Creature() {
numCreated++;
}
public static long numCreated() {
return numCreated;
}
}
这样不能通过编译,因为,它违背了一个Java规范。Java语言规范不允许一个本地变量声明语句作为一条语句在for、while或do循环中重复执行[JLS 14.12-14]。一个本地变量声明作为一条语句只能直接出现在一个语句块中。(一个语句块是由一对花括号以及包含在这对花括展中的语句和声明构成的。)所以,如果将这个循环改为:
for(int i=0;i<100;i++){
Creature creature = new Creature();
}
加一个大括弧之后,他就能正常编译运行。
Java——类谜题的更多相关文章
- 如何用Java类配置Spring MVC(不通过web.xml和XML方式)
DispatcherServlet是Spring MVC的核心,按照传统方式, 需要把它配置到web.xml中. 我个人比较不喜欢XML配置方式, XML看起来太累, 冗长繁琐. 还好借助于Servl ...
- jvm系列(一):java类的加载机制
java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...
- java类与实例
最近在看设计模式,感觉自己对java的三大特性的理解不够清晰,搞不清楚抽象类.接口.泛型的用处和优缺点.设计模式学了一半,想着还是停下来脑补一下java的基础,就从java对象开始吧. 一.java对 ...
- oracle调用JAVA类的方法
导入jar包 在oracle中导入需要的jar包,我们把编辑好的java类打成jar包,直接在oarcle里面写简单的调用就可以了, 1.操作系统需要拥有支持loadjava命令的jdk. 2.加 ...
- Java 类的实例变量初始化的过程 静态块、非静态块、构造函数的加载顺序
先看一道Java面试题: public class Baset { private String baseName = "base"; // 构造方法 public Baset() ...
- hibernate中java类的成员变量类型如何映射到SQL中的数据类型变化
hibernate映射文件??.hbm.xml配置映射元素详解--Hibernate映射类型 在从Hibernate的java的成员类型映射到SQL中的数据类型,其内映射方式它满足,SQL可以自己调制 ...
- kettle系列-[KettleUtil]kettle插件,类似kettle的自定义java类控件
该kettle插件功能类似kettle现有的定义java类插件,自定java类插件主要是支持在kettle中直接编写java代码实现自定特殊功能,而本控件主要是将自定义代码转移到jar包,就是说自定义 ...
- Myeclipse中导入项目后java类中汉字注释出现乱码问题(已解决)
今天重装系统,安装了新的Myeclipse后,导入之前的项目后,,出现了乱码问题.乱码问题主要是java类中的注释,而jsp页面中汉字却完好如初: 右键项目,查看项目的编码格式,UTF-8,把java ...
- Java类初始化
Java类初始化 成员变量的初始化和构造器 如果类的成员变量在定义时没有进行显示的初始化赋值,Java会给每个成员变量一个默认值 对于 char.short.byte.int.long.float. ...
随机推荐
- Perl 之 use(), require(), do(), %INC and @INC
来源: http://www.cnblogs.com/itech/archive/2013/03/12/2956185.html 转自:http://perl.apache.org/docs/gene ...
- beagleboneblack HDMI不能显示
beagleboneblack 接上HDMI到电视上,没有显示画面 看了资料之后才知道 http://elinux.org/Beagleboard:BeagleBoneBlack_HDMI#Conn ...
- 学习笔记:GLSL Core Tutorial – Pipeline (OpenGL 3.2 – OpenGL 4.2)
GLSL Core Tutorial – Pipeline (OpenGL 3.2 – OpenGL 4.2) GLSL 是一种管道,一种图形化的流水线 1.GLSL 的具体工作流程: 简化流程如下: ...
- Windows任务管理器中内存使用、虚拟内存区别及与页面文件的关系
原文地址:Windows任务管理器中内存使用.虚拟内存区别及与页面文件的关系 虚拟内存(VirtualMemory)是Windows管理所有可用内存的方式.对于32位Windows系统,每个进程所用到 ...
- java transient修饰符
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问. 2)transient关键字只能修饰变量,而不能修饰方法和类.注意,本地变量是不能被trans ...
- echarts学习总结(一):图表溢出窗口,图表数据窗口显示不全
如上图所示:echarts图形左面的数据没有完全显示 只需在option中定义一下grid,例如: gri ...
- java中io对文件操作的简单介绍
11.3 I/O类使用 由于在IO操作中,需要使用的数据源有很多,作为一个IO技术的初学者,从读写文件开始学习IO技术是一个比较好的选择.因为文件是一种常见的数据源,而且读写文件也是程序员进行IO编程 ...
- 命令行调试smali
am start -D -n com.agilebits.onepassword/.activity.LoginActivity ps | grep passu0_a126 1107 217 5107 ...
- hdu 4463 Outlets(最小生成树)
Outlets Time Limit : 2000/1000ms (Java/Other) Memory Limit : 32768/32768K (Java/Other) Total Submi ...
- HDU 3594 Cactus (强连通分量 + 一个边只能在一个环里)
题意:判断题目中给出的图是否符合两个条件.1 这图只有一个强连通分量 2 一条边只能出现在一个环里. 思路:条件1的满足只需要tarjan算法正常求强连通分量即可,关键是第二个条件,我们把对边的判断转 ...