Java回顾之一些基础概念
Java回顾之一些基础概念
第一篇: Java回顾之I/O
第二篇: Java回顾之网络通信
第三篇: Java回顾之多线程
第四篇: Java回顾之多线程同步
第五篇: Java回顾之集合
第六篇: Java回顾之序列化
第七篇: Java回顾之反射
这两天,无意间在网上翻到一本关于Java面试解惑的文章集,里面提到了很多基础的概念,但一不留神,还是可能会“掉到坑里”。里面的文章写的很不错,大家可以通过下面的地址下载: http://zangweiren.iteye测试数据/blog/241218
在看上述文章的时候,随手写了一些测试代码,以便加深理解。这也就是这篇文章的来源了。
类的初始化顺序
在Java中,类里面可能包含:静态变量,静态初始化块,成员变量,初始化块,构造函数。在类之间可能存在着继承关系,那么当我们实例化一个对象时,上述各部分的加载顺序是怎样的?
首先来看代码:
1 class Parent
2 {
3 public static StaticVarible staticVarible= new StaticVarible("父类-静态变量1" );
4 public StaticVarible instVarible= new StaticVarible("父类-成员变量1" );
5
6 static
7 {
8 System.out.println("父类-静态块" );
9 }
10
11 {
12 System.out.println("父类-初始化块" );
13 }
14
15 public static StaticVarible staticVarible2= new StaticVarible("父类-静态变量2" );
16 public StaticVarible instVarible2= new StaticVarible("父类-成员变量2" );
17
18 public Parent()
19 {
20 System.out.println("父类-实例构造函数" );
21 }
22 }
23
24 class Child extends Parent
25 {
26 public static StaticVarible staticVarible= new StaticVarible("子类-静态变量1" );
27 public StaticVarible instVarible= new StaticVarible("子类-成员变量1" );
28
29 static
30 {
31 System.out.println("子类-静态块" );
32 }
33
34 public Child()
35 {
36 System.out.println("子类-实例构造函数" );
37 }
38
39 {
40 System.out.println("子类-初始化块" );
41 }
42
43 public static StaticVarible staticVarible2= new StaticVarible("子类-静态变量2" );
44 public StaticVarible instVarible2= new StaticVarible("子类-成员变量2" );
45
46
47 }
48
49 class StaticVarible
50 {
51 public StaticVarible(String info)
52 {
53 System.out.println(info);
54 }
55 }
然后执行下面的语句:
1 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会维护一个字符串池,或者说会缓存一部分曾经出现过的字符串。
例如下面的代码:
1 String v1 = "ab" ; 2 String v2 = "ab";
实际上,v1==v2,因为JVM在v1声明后,已经对“ab”进行了缓存。
那么JVM对字符串进行缓存的依据是什么?我们来看下面的代码,非常有意思:
1 public class StringTest {
2 public static final String constValue = "ab" ;
3 public static final String staticValue;
4
5 static
6 {
7 staticValue="ab" ;
8 }
9
10 public static void main(String[] args)
11 {
12 String v1 = "ab" ;
13 String v2 = "ab" ;
14 System.out.println("v1 == v2 : " + (v1 == v2));
15 String v3 = new String("ab" );
16 System.out.println("v1 == v3 : " + (v1 == v3));
17 String v4 = "abcd" ;
18 String v5 = "ab" + "cd" ;
19 System.out.println("v4 == v5 : " + (v4 == v5));
20 String v6 = v1 + "cd" ;
21 System.out.println("v4 == v6 : " + (v4 == v6));
22 String v7 = constValue + "cd" ;
23 System.out.println("v4 == v7 : " + (v4 == v7));
24 String v8 = staticValue + "cd" ;
25 System.out.println("v4 == v8 : " + (v4 == v8));
26 String v9 = v4.intern();
27 System.out.println("v4 == v9 :" + (v4 == v9));
28 String v10 = new String( new char []{'a','b','c','d' });
29 String v11 = v10.intern();
30 System.out.println("v4 == v11 :" + (v4 == v11));
31 System.out.println("v10 == v11 :" + (v10 == v11));
32 }
33 }
请注意它的输出结果:
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://HdhCmsTestcnblogs测试数据/winter-cn/archive/2012/01/21/2328388.html
字符串的反转输出
这种情况下,一般会将字符串看做是 字符数组 ,然后利用反转数组的方式来反转字符串。
眼花缭乱的方法调用
有继承关系结构中的方法调用
继承是面向对象设计中的常见方式,它可以有效的实现”代码复用“,同时子类也有重写父类方法的自由,这就对到底是调用父类方法还是子类方法带来了麻烦。
来看下面的代码:
1 public class PropertyTest {
2
3 public static void main(String[] args)
4 {
5 ParentDef v1 = new ParentDef();
6 ParentDef v2 = new ChildDef();
7 ChildDef v3 = new ChildDef();
8 System.out.println("=====v1=====" );
9 System.out.println("staticValue:" + v1.staticValue);
10 System.out.println("value:" + v1.value);
11 System.out.println("=====v2=====" );
12 System.out.println("staticValue:" + v2.staticValue);
13 System.out.println("value:" + v2.value);
14 System.out.println("=====v3=====" );
15 System.out.println("staticValue:" + v3.staticValue);
16 System.out.println("value:" + v3.value);
17 }
18 }
19
20 class ParentDef
21 {
22 public static final String staticValue = "父类静态变量" ;
23 public String value = "父类实例变量" ;
24 }
25
26 class ChildDef extends ParentDef
27 {
28 public static final String staticValue = "子类静态变量" ;
29 public String value = "子类实例变量" ;
30 }
输出结果如下:
=====v1===== staticValue:父类静态变量 value:父类实例变量 =====v2===== staticValue:父类静态变量 value:父类实例变量 =====v3===== staticValue:子类静态变量 value:子类实例变量结论
对于调用父类方法还是子类方法,只与变量的声明类型有关系,与实例化的类型没有关系。
到底是值传递还是引用传递
对于这个话题,我的观点是值传递,因为传递的都是存储在栈中的内容,无论是基本类型的值,还是指向堆中对象的指针,都是值而非引用。并且在值传递的过程中,JVM会将值复制一份,然后将复制后的值传递给调用方法。
按照这种方式,我们来看下面的代码:
1 public class ParamTest {
2
3 public void change( int value)
4 {
5 value = 10 ;
6 }
7
8 public void change(Value value)
9 {
10 Value temp = new Value();
11 temp.value = 10 ;
12 value = temp;
13 }
14
15 public void add( int value)
16 {
17 value += 10 ;
18 }
19
20 public void add(Value value)
21 {
22 value.value += 10 ;
23 }
24
25 public static void main(String[] args)
26 {
27 ParamTest test = new ParamTest();
28 Value value = new Value();
29 int v = 0 ;
30 System.out.println("v:" + v);
31 System.out.println("value.value:" + value.value);
32 System.out.println("=====change=====" );
33 test.change(v);
34 test.change(value);
35 System.out.println("v:" + v);
36 System.out.println("value.value:" + value.value);
37 value = new Value();
38 v = 0 ;
39 System.out.println("=====add=====" );
40 test.add(v);
41 test.add(value);
42 System.out.println("v:" + v);
43 System.out.println("value.value:" + value.value);
44 }
45 }
46
47 class Value
48 {
49 public int value;
50 }
它的输出结果:
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)构造函数
来看下面的代码:
1 class FinalTest
2 {
3 public static final String staticValue1 = "静态变量1" ;
4 public static final String staticValue2;
5
6 static
7 {
8 staticValue2 = "静态变量2" ;
9 }
10
11 public final String value1 = "实例变量1" ;
12 public final String value2;
13 public final String value3;
14
15 {
16 value2 = "实例变量2" ;
17 }
18
19 public FinalTest()
20 {
21 value3 = "实例变量3" ;
22 }
23 }
finally一般是和try...catch放在一起使用,主要用来释放一些资源。
我们来看下面的代码:
1 public class FinallyTest {
2
3 public static void main(String[] args)
4 {
5 finallyTest1();
6 finallyTest2();
7 finallyTest3();
8 }
9
10 private static String finallyTest1()
11 {
12 try
13 {
14 throw new RuntimeException();
15 }
16 catch (Exception ex)
17 {
18 ex.printStackTrace();
19 }
20 finally
21 {
22 System.out.println("Finally语句被执行" );
23 }
24 try
25 {
26 System.out.println("Hello World" );
27 return "Hello World" ;
28 }
29 catch (Exception ex)
30 {
31 ex.printStackTrace();
32 }
33 finally
34 {
35 System.out.println("Finally语句被执行" );
36 }
37 return null ;
38 }
39
40 private static void finallyTest2()
41 {
42 int i = 0 ;
43 for (i = 0; i < 3; i++ )
44 {
45 try
46 {
47 if (i == 2) break ;
48 System.out.println(i);
49 }
50 finally
51 {
52 System.out.println("Finally语句被执行" );
53 }
54 }
55 }
56
57 private static Test finallyTest3()
58 {
59 try
60 {
61 return new Test();
62 }
63 finally
64 {
65 System.out.println("Finally语句被执行" );
66 }
67 }
68 }
执行结果如下:
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方法时,该方法就会被调用。
来看下面的示例:
1 class FinalizeTest
2 {
3 protected void finalize()
4 {
5 System.out.println("finalize方法被调用" );
6 }
7
8 public static void main(String[] args)
9 {
10 FinalizeTest test = new FinalizeTest();
11 test = null ;
12 Runtime.getRuntime().gc();
13 }
14 }
执行结果如下:
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。我们来看下面的示例:
1 public class DateTest {
2
3 public static void main(String[] args) throws ParseException
4 {
5 test1();
6 test2();
7 test3();
8 }
9
10 private static void test1() throws ParseException
11 {
12 Date date = new Date();
13 System.out.println(date);
14 DateFormat sf = new SimpleDateFormat("yyyy-MM-dd" );
15 System.out.println(sf.format(date));
16 String formatString = "2013-05-12" ;
17 System.out.println(sf.parse(formatString));
18 }
19
20 private static void test2()
21 {
22 Date date = new Date();
23 System.out.println("Year:" + date.getYear());
24 System.out.println("Month:" + date.getMonth());
25 System.out.println("Day:" + date.getDate());
26 System.out.println("Hour:" + date.getHours());
27 System.out.println("Minute:" + date.getMinutes());
28 System.out.println("Second:" + date.getSeconds());
29 System.out.println("DayOfWeek:" + date.getDay());
30 }
31
32 private static void test3()
33 {
34 Calendar c = Calendar.getInstance();
35 System.out.println(c.getTime());
36 System.out.println(c.getTimeZone());
37 System.out.println("Year:" + c.get(Calendar.YEAR));
38 System.out.println("Month:" + c.get(Calendar.MONTH));
39 System.out.println("Day:" + c.get(Calendar.DATE));
40 System.out.println("Hour:" + c.get(Calendar.HOUR));
41 System.out.println("HourOfDay:" + c.get(Calendar.HOUR_OF_DAY));
42 System.out.println("Minute:" + c.get(Calendar.MINUTE));
43 System.out.println("Second:" + c.get(Calendar.SECOND));
44 System.out.println("DayOfWeek:" + c.get(Calendar.DAY_OF_WEEK));
45 System.out.println("DayOfMonth:" + c.get(Calendar.DAY_OF_MONTH));
46 System.out.println("DayOfYear:" + c.get(Calendar.DAY_OF_YEAR));
47 }
48 }
输出结果如下:
Sat May 11 13:44:34 CST 2013 2013-05-11 Sun May 12 0 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的字符串转换为日期类型。
作者: 李胜攀
出处: http://wing011203.cnblogs测试数据/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类: Java相关
标签: 面试 , Java , 基础概念 , 继承 , 多态 , String , Date
作者: Leo_wl
出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息