类的初始化顺序

  在Java中,类里面可能包含:静态变量,静态初始化块,成员变量,初始化块,构造函数。在类之间可能存在着继承关系,那么当我们实例化一个对象时,上述各部分的加载顺序是怎样的?

  首先来看代码:

 
 class Parent
{
public static StaticVarible staticVarible= new StaticVarible("父类-静态变量1");
public StaticVarible instVarible= new StaticVarible("父类-成员变量1"); static
{
System.out.println("父类-静态块");
} {
System.out.println("父类-初始化块");
} public static StaticVarible staticVarible2= new StaticVarible("父类-静态变量2");
public StaticVarible instVarible2= new StaticVarible("父类-成员变量2"); public Parent()
{
System.out.println("父类-实例构造函数");
}
} class Child extends Parent
{
public static StaticVarible staticVarible= new StaticVarible("子类-静态变量1");
public StaticVarible instVarible= new StaticVarible("子类-成员变量1"); static
{
System.out.println("子类-静态块");
} public Child()
{
System.out.println("子类-实例构造函数");
} {
System.out.println("子类-初始化块");
} public static StaticVarible staticVarible2= new StaticVarible("子类-静态变量2");
public StaticVarible instVarible2= new StaticVarible("子类-成员变量2"); } class StaticVarible
{
public StaticVarible(String info)
{
System.out.println(info);
}
}
 

  然后执行下面的语句:

 Child child = new Child();

  输出结果如下:

 
父类-静态变量1
父类-静态块
父类-静态变量2
子类-静态变量1
子类-静态块
子类-静态变量2
父类-成员变量1
父类-初始化块
父类-成员变量2
父类-实例构造函数
子类-成员变量1
子类-初始化块
子类-成员变量2
子类-实例构造函数
 

  结论  

  从上述结果可以看出,在实例化一个对象时,各部分的加载顺序如下:

  父类静态成员/父类静态初始化块 -> 子类静态成员/子类静态初始化块 -> 父类成员变量/父类初始化块 -> 父类构造函数 -> 子类成员变量/子类初始化块 -> 子类构造函数

  和String相关的一些事儿

  首先,我们聊一聊Java中堆和栈的事儿。

  • 栈:存放基本类型,包括char/byte/short/int/long/float/double/boolean
  • 堆:存放引用类型,同时一般会在栈中保留一个指向它的指针,垃圾回收判断一个对象是否可以回收,就是判断栈中是否有指针指向堆中的对象。

  String作为一种特殊的数据类型,它不完全等同于基本类型,也不是全部的引用类型,许多面试题都有它的身影。

  String类型变量的存储结构

  String的存储结构分为两部分,我们以String a = "abc";为例,描述String类型的存储方式:

  1)在栈中创建一个char数组,值分为是'a','b','c'。

  2)在堆中创建一个String对象。

  Java中的字符串池

  为了节省空间和资源,JVM会维护一个字符串池,或者说会缓存一部分曾经出现过的字符串。

  例如下面的代码:

 String v1 = "ab";
String v2 = "ab";

  实际上,v1==v2,因为JVM在v1声明后,已经对“ab”进行了缓存。

  那么JVM对字符串进行缓存的依据是什么?我们来看下面的代码,非常有意思:

 
 public class StringTest {
public static final String constValue = "ab";
public static final String staticValue; static
{
staticValue="ab";
} public static void main(String[] args)
{
String v1 = "ab";
String v2 = "ab";
System.out.println("v1 == v2 : " + (v1 == v2));
String v3 = new String("ab");
System.out.println("v1 == v3 : " + (v1 == v3));
String v4 = "abcd";
String v5 = "ab" + "cd";
System.out.println("v4 == v5 : " + (v4 == v5));
String v6 = v1 + "cd";
System.out.println("v4 == v6 : " + (v4 == v6));
String v7 = constValue + "cd";
System.out.println("v4 == v7 : " + (v4 == v7));
String v8 = staticValue + "cd";
System.out.println("v4 == v8 : " + (v4 == v8));
String v9 = v4.intern();
System.out.println("v4 == v9 :" + (v4 == v9));
String v10 = new String(new char[]{'a','b','c','d'});
String v11 = v10.intern();
System.out.println("v4 == v11 :" + (v4 == v11));
System.out.println("v10 == v11 :" + (v10 == v11));
}
}
 

  请注意它的输出结果:

 
v1 == v2 : true
v1 == v3 : false
v4 == v5 : true
v4 == v6 : false
v4 == v7 : true
v4 == v8 : false
v4 == v9 :true
v4 == v11 :true
v10 == v11 :false
 

  我们会发现,并不是所有的判断都返回true,这似乎和我们上面的说法有矛盾了。其实不然,因为

  结论

  1. JVM只能缓存那些在编译时可以确定的常量,而非运行时常量。

    上述代码中的constValue属于编译时常量,而staticValue则属于运行时常量。

  2. 通过使用 new方式创建出来的字符串,JVM缓存的方式是不一样的。

    所以上述代码中,v1不等同于v3。

  String的这种设计属于享元模式吗?

  这个话题比较有意思,大部分讲设计模式的文章,在谈到享元时,一般就会拿String来做例子,但它属于享元模式吗?

  字符串与享元的关系,大家可以参考下面的文章:http://www.cnblogs.com/winter-cn/archive/2012/01/21/2328388.html

  字符串的反转输出

  这种情况下,一般会将字符串看做是字符数组,然后利用反转数组的方式来反转字符串。

  眼花缭乱的方法调用

  有继承关系结构中的方法调用

  继承是面向对象设计中的常见方式,它可以有效的实现”代码复用“,同时子类也有重写父类方法的自由,这就对到底是调用父类方法还是子类方法带来了麻烦。

  来看下面的代码:

 
 public class PropertyTest {

     public static void main(String[] args)
{
ParentDef v1 = new ParentDef();
ParentDef v2 = new ChildDef();
ChildDef v3 = new ChildDef();
System.out.println("=====v1=====");
System.out.println("staticValue:" + v1.staticValue);
System.out.println("value:" + v1.value);
System.out.println("=====v2=====");
System.out.println("staticValue:" + v2.staticValue);
System.out.println("value:" + v2.value);
System.out.println("=====v3=====");
System.out.println("staticValue:" + v3.staticValue);
System.out.println("value:" + v3.value);
}
} class ParentDef
{
public static final String staticValue = "父类静态变量";
public String value = "父类实例变量";
} class ChildDef extends ParentDef
{
public static final String staticValue = "子类静态变量";
public String value = "子类实例变量";
}
 

  输出结果如下:

 
=====v1=====
staticValue:父类静态变量
value:父类实例变量
=====v2=====
staticValue:父类静态变量
value:父类实例变量
=====v3=====
staticValue:子类静态变量
value:子类实例变量
 

  结论

  对于调用父类方法还是子类方法,只与变量的声明类型有关系,与实例化的类型没有关系。

  到底是值传递还是引用传递

  对于这个话题,我的观点是值传递,因为传递的都是存储在栈中的内容,无论是基本类型的值,还是指向堆中对象的指针,都是值而非引用。并且在值传递的过程中,JVM会将值复制一份,然后将复制后的值传递给调用方法。

  按照这种方式,我们来看下面的代码:

 
 public class ParamTest {

     public void change(int value)
{
value = 10;
} public void change(Value value)
{
Value temp = new Value();
temp.value = 10;
value = temp;
} public void add(int value)
{
value += 10;
} public void add(Value value)
{
value.value += 10;
} public static void main(String[] args)
{
ParamTest test = new ParamTest();
Value value = new Value();
int v = 0;
System.out.println("v:" + v);
System.out.println("value.value:" + value.value);
System.out.println("=====change=====");
test.change(v);
test.change(value);
System.out.println("v:" + v);
System.out.println("value.value:" + value.value);
value = new Value();
v = 0;
System.out.println("=====add=====");
test.add(v);
test.add(value);
System.out.println("v:" + v);
System.out.println("value.value:" + value.value);
}
} class Value
{
public int value;
}
 

  它的输出结果:

 
v:0
value.value:0
=====change=====
v:0
value.value:0
=====add=====
v:0
value.value:10
 

  我们看到,在调用change方法时,即使我们传递进去的是指向对象的指针,但最终对象的属性也没有变,这是因为在change方法体内,我们新建了一个对象,然后将”复制过的指向原对象的指针“指向了“新对象”,并且对新对象的属性进行了调整。但是“复制前的指向原对象的指针”依然是指向“原对象”,并且属性没有任何变化。

  final/finally/finalize的区别

  final可以修饰类、成员变量、方法以及方法参数。使用final修饰的类是不可以被继承的,使用final修饰的方法是不可以被重写的,使用final修饰的变量,只能被赋值一次。

  使用final声明变量的赋值时机:

  1)定义声明时赋值

  2)初始化块或静态初始化块中

  3)构造函数

  来看下面的代码:

 
 class FinalTest
{
public static final String staticValue1 = "静态变量1";
public static final String staticValue2; static
{
staticValue2 = "静态变量2";
} public final String value1 = "实例变量1";
public final String value2;
public final String value3; {
value2 = "实例变量2";
} public FinalTest()
{
value3 = "实例变量3";
}
}
 

  finally一般是和try...catch放在一起使用,主要用来释放一些资源。

  我们来看下面的代码:

 
 public class FinallyTest {

     public static void main(String[] args)
{
finallyTest1();
finallyTest2();
finallyTest3();
} private static String finallyTest1()
{
try
{
throw new RuntimeException();
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("Finally语句被执行");
}
try
{
System.out.println("Hello World");
return "Hello World";
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
System.out.println("Finally语句被执行");
}
return null;
} private static void finallyTest2()
{
int i = 0;
for (i = 0; i < 3; i++)
{
try
{
if (i == 2) break;
System.out.println(i);
}
finally
{
System.out.println("Finally语句被执行");
}
}
} private static Test finallyTest3()
{
try
{
return new Test();
}
finally
{
System.out.println("Finally语句被执行");
}
}
}
 

  执行结果如下:

 
java.lang.RuntimeException
at sample.interview.FinallyTest.finallyTest1(FinallyTest.java:16)
at sample.interview.FinallyTest.main(FinallyTest.java:7)
Finally语句被执行
Hello World
Finally语句被执行
0
Finally语句被执行
1
Finally语句被执行
Finally语句被执行
Test实例被创建
Finally语句被执行
 

  注意在循环的过程中,对于某一次循环,即使调用了break或者continue,finally也会执行。

  finalize则主要用于释放资源,在调用GC方法时,该方法就会被调用。

  来看下面的示例:

 
 class FinalizeTest
{
protected void finalize()
{
System.out.println("finalize方法被调用");
} public static void main(String[] args)
{
FinalizeTest test = new FinalizeTest();
test = null;
Runtime.getRuntime().gc();
}
}
 

  执行结果如下:

finalize方法被调用

  关于基本类型的一些事儿

  基本类型供分为9种,包括byte/short/int/long/float/double/boolean/void,每种基本类型都对应一个“包装类”,其他一些基本信息如下:

 
1. 基本类型:byte 二进制位数:8
2. 包装类:java.lang.Byte
3. 最小值:Byte.MIN_VALUE=-128
4. 最大值:Byte.MAX_VALUE=127
5. 基本类型:short 二进制位数:16
6. 包装类:java.lang.Short
7. 最小值:Short.MIN_VALUE=-32768
8. 最大值:Short.MAX_VALUE=32767
9. 基本类型:int 二进制位数:32
10. 包装类:java.lang.Integer
11. 最小值:Integer.MIN_VALUE=-2147483648
12. 最大值:Integer.MAX_VALUE=2147483647
13. 基本类型:long 二进制位数:64
14. 包装类:java.lang.Long
15. 最小值:Long.MIN_VALUE=-9223372036854775808
16. 最大值:Long.MAX_VALUE=9223372036854775807
17. 基本类型:float 二进制位数:32
18. 包装类:java.lang.Float
19. 最小值:Float.MIN_VALUE=1.4E-45
20. 最大值:Float.MAX_VALUE=3.4028235E38
21. 基本类型:double 二进制位数:64
22. 包装类:java.lang.Double
23. 最小值:Double.MIN_VALUE=4.9E-324
24. 最大值:Double.MAX_VALUE=1.7976931348623157E308
25. 基本类型:char 二进制位数:16
26. 包装类:java.lang.Character
27. 最小值:Character.MIN_VALUE=0
28. 最大值:Character.MAX_VALUE=65535
 

  关于基本类型的一些结论(来自《Java面试解惑》)

  • 未带有字符后缀标识的整数默认为int类型;未带有字符后缀标识的浮点数默认为double类型。
  • 如果一个整数的值超出了int类型能够表示的范围,则必须增加后缀“L”(不区分大小写,建议用大写,因为小写的L与阿拉伯数字1很容易混淆),表示为long型。
  • 带有“F”(不区分大小写)后缀的整数和浮点数都是float类型的;带有“D”(不区分大小写)后缀的整数和浮点数都是double类型的。
  • 编译器会在编译期对byte、short、int、long、float、double、char型变量的值进行检查,如果超出了它们的取值范围就会报错。
  • int型值可以赋给所有数值类型的变量;long型值可以赋给long、float、double类型的变量;float型值可以赋给float、double类型的变量;double型值只能赋给double类型变量。

  关于基本类型之间的转换

  下面的转换是无损精度的转换:

  • byte->short
  • short->int
  • char->int
  • int->long
  • float->double

  下面的转换是会损失精度的:

  • int->float
  • long->float
  • long->double

  除此之外的转换,是非法的。

  和日期相关的一些事儿

  Java中,有两个类和日期相关,一个是Date,一个是Calendar。我们来看下面的示例:

 
 public class DateTest {

     public static void main(String[] args) throws ParseException
{
test1();
test2();
test3();
} private static void test1() throws ParseException
{
Date date = new Date();
System.out.println(date);
DateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sf.format(date));
String formatString = "2013-05-12";
System.out.println(sf.parse(formatString));
} private static void test2()
{
Date date = new Date();
System.out.println("Year:" + date.getYear());
System.out.println("Month:" + date.getMonth());
System.out.println("Day:" + date.getDate());
System.out.println("Hour:" + date.getHours());
System.out.println("Minute:" + date.getMinutes());
System.out.println("Second:" + date.getSeconds());
System.out.println("DayOfWeek:" + date.getDay());
} private static void test3()
{
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());
System.out.println(c.getTimeZone());
System.out.println("Year:" + c.get(Calendar.YEAR));
System.out.println("Month:" + c.get(Calendar.MONTH));
System.out.println("Day:" + c.get(Calendar.DATE));
System.out.println("Hour:" + c.get(Calendar.HOUR));
System.out.println("HourOfDay:" + c.get(Calendar.HOUR_OF_DAY));
System.out.println("Minute:" + c.get(Calendar.MINUTE));
System.out.println("Second:" + c.get(Calendar.SECOND));
System.out.println("DayOfWeek:" + c.get(Calendar.DAY_OF_WEEK));
System.out.println("DayOfMonth:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("DayOfYear:" + c.get(Calendar.DAY_OF_YEAR));
}
}
 

  输出结果如下:

 
Sat May 11 13:44:34 CST 2013
2013-05-11
Sun May 12 00:00:00 CST 2013
Year:113
Month:4
Day:11
Hour:13
Minute:44
Second:35
DayOfWeek:6
Sat May 11 13:44:35 CST 2013
sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
Year:2013
Month:4
Day:11
Hour:1
HourOfDay:13
Minute:44
Second:35
DayOfWeek:7
DayOfMonth:11
DayOfYear:131
 

  需要注意的是,Date中的getxxx方法已经变成deprecated了,因此我们尽量使用calendar.get方法来获取日期的细节信息。

  另外,注意DateFormat,它不仅可以对日期的输出进行格式化,而且可以逆向操作,将符合Format的字符串转换为日期类型。

Java回顾之一些基础概念的更多相关文章

  1. Java回顾之Spring基础

    第一篇:Java回顾之I/O 第二篇:Java回顾之网络通信 第三篇:Java回顾之多线程 第四篇:Java回顾之多线程同步 第五篇:Java回顾之集合 第六篇:Java回顾之序列化 第七篇:Java ...

  2. JAVA学习笔记之基础概念(一)

    一.Java 简介: Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称. 由 James Gosling和同 ...

  3. JAVA的网络编程基础概念

    网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯.网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输.在TCP/IP协 ...

  4. Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析

    关于Exynos4412 IIC 裸机开发请看 :Exynos4412 裸机开发 —— IIC总线 ,下面回顾下 IIC 基础概念 一.IIC 基础概念 IIC(Inter-Integrated Ci ...

  5. JavaWeb开发技术基础概念回顾篇

    JavaWeb开发技术基础概念回顾篇 第一章 动态网页开发技术概述 1.JSP技术:JSP是Java Server Page的缩写,指的是基于Java服务器端动态网页. 2.JSP的运行原理:当用户第 ...

  6. Java的多线程机制系列:(一)总述及基础概念

    前言 这一系列多线程的文章,一方面是个人对Java现有的多线程机制的学习和记录,另一方面是希望能给不熟悉Java多线程机制.或有一定基础但理解还不够深的读者一个比较全面的介绍,旨在使读者对Java的多 ...

  7. Java面试题精选(一)基础概念和面向对象

    --   基础概念和面向对象   --      全程将为大家剖析几大部分内容,由于学习经验有限,望大家谅解并接受宝贵的意见: 基础概念部分     ★★   : 常出现的高频率单词的区别理解(异常. ...

  8. Java并发程序设计(一) 基础概念

    Java并发程序设计(一) 基础概念 一.必须知道的几个概念 一)同步(Synchronous)和异步(Asynchronous) 同步:同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后 ...

  9. Java多线程--基础概念

    Java多线程--基础概念 必须知道的几个概念 同步和异步 同步方法一旦开始,调用者必须等到方法调用返回后,才能执行后续行为:而异步方法调用,一旦开始,方法调用就立即返回,调用者不用等待就可以继续执行 ...

随机推荐

  1. mongodb拆库分表脚本

    脚本功能: 1. 将指定的报告文件按照指定的字段.切库切表策略切分 2. 将切分后的文件并发导入到对应的Mongodb中 3. 生成日志文件和done标识文件 使用手册: -h    打印帮助信息,并 ...

  2. mysql 选择优化的数据类型

    选择最小的数据类型,因为它们占更少的磁盘,内存和CPU缓存: 选择简单的数据类型,如用整型来存储ip: http://blog.csdn.net/lyd518/article/details/2070 ...

  3. A Benchmark Comparsion of Monocular Visual-Inertial Odometry Algorithms for Flying Robots论文笔记

    摘要: 本文主要比较单目VIO的算法在飞行机器人上运行的性能,测试使用统一数据集为EuRoC.其中评价指标为:姿态估计精度.每帧处理时间以及CPU和内存负载使用率,同时还有RMSE(运行轨迹与真实轨迹 ...

  4. SLF4J其实只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖Log4j、logback等日志框架的。

    小结: 1.加层: 每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性. 为了解决这个问题,就是在日志框架和应用程序之间架设一个 ...

  5. Ubuntu proxychains && setProxy及 unsetProxy

    https://www.socks-proxy.net/ (ubuntu proxy )[ lantern -addr 0.0.0.0:8787 proxychains4 printenv http: ...

  6. Maven安装(linux系统)

    解压: 修改配置: export JAVA_HOME=/usr/java/jdk1..0_80 export MAVEN_HOME=/software/apache-maven- export PAT ...

  7. Select触发事件

     案例1: <script type="text/JavaScript"> function gradeChange(){ var objS = document.ge ...

  8. android 本地通知

    NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); Notificat ...

  9. kindle 应用程序出错,无法启动选定的应用程序,请重试。问题排查过程及处理方案。

    最近一段时间在使用Kindle商城时总是会出现“应用程序出错,无法启动选定的应用程序,请重试.” 对此我花了大约一小时的时间进行测试验证并与客服人员沟通,将过程记录如下,供出现同样问题的朋友们参考. ...

  10. SQLServer cast()函数

    语法: CAST (expression AS data_type) 参数说明: expression:任何有效的SQLServer表达式. AS:用于分隔两个参数,在AS之前的是要处理的数据,在AS ...