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. ...
随机推荐
- Linux文件系统中硬链接和软链接的区别 (转)
建立硬链接命令:ln src-link dest-link建立软链接:ln -s src-link dest-link 1. 硬链接是别名,软链接是快捷方式 2. 硬链接和源链接指向同一个i节 ...
- web工程中地址的写法
硬盘地址用\ 如:c:\\ 虚拟地址用 / /* 只要是写地址,建议大家最好以"/"开头,/到底代表什么呢? 1.看这个地址是给服务器用的,还是给浏览器用的. 2.如果给浏览器 ...
- SCRIPT7002: XMLHttpRequest: 网络错误 0x2ef3, 由于出现错误 00002ef3 
项目使用的是tomcat7 ,浏览器是ie11 突然打开浏览器发现不能获取数据了 tomcat conf server.xml 文件 <Connector port="8080&quo ...
- if __name__ == '__main__'在python中的应用
当你打开一个.py文件时,经常会在代码的最下面看到if __name__ == '__main__':,现在就来介 绍一下它的作用. 模块是对象,并且所有的模块都有一个内置属性 __name__.一个 ...
- scull_p_read()函数分析
/* * Data management: read and write */ static ssize_t scull_p_read (struct file *filp, char __user ...
- 学习笔记——Windows下cocos2d-x,eclipse中自编译
cocos2d-x创建的安卓项目导入eclipse后. 在项目属性中配置Builders. 在eclipse编译还需要配置相应的变量,即后面提到的cygwin编译中要添加的变量. D:/cygdriv ...
- action参数绑定
thinkPHP支持操作方法的参数绑定功能 action参数通过直接绑定URL中的变量作为操作方法的参数,可以简化方法的定义甚至路由的简析. 原理是把URL的中参数(不包括模块,控制器和操作名)和控制 ...
- Swift学习(1)
swif(1) println("Hello, world") 输出结果: Hello, world swift使用let来声明常量,使用var来声明变量 //变量 var myV ...
- android 5.0新特性学习--RecyclerView
在过去很多年,我们的PC或者手机设备都是采用拟物化的设计风格,IOS采用扁平化的特性,android在2014年IO大会上说采用Material Design的设计风格,显示效果不能过于生硬的转换,而 ...
- Yii config 配置
Yii2 配置文件 常用配置总结 <?php // 主配置文件 $config = array( 'modules' => array( 'gii' => array( 'class ...