好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java回顾之一些基础概念

Java回顾之一些基础概念

Java回顾之一些基础概念

第一篇: Java回顾之I/O

  第二篇: Java回顾之网络通信

  第三篇: Java回顾之多线程

  第四篇: Java回顾之多线程同步

  第五篇: Java回顾之集合

  第六篇: Java回顾之序列化

  第七篇: Java回顾之反射

  这两天,无意间在网上翻到一本关于Java面试解惑的文章集,里面提到了很多基础的概念,但一不留神,还是可能会“掉到坑里”。里面的文章写的很不错,大家可以通过下面的地址下载: http://zangweiren.iteye.com/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://www.cnblogs.com/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.com/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

分类:  Java相关

标签:  面试 ,  Java ,  基础概念 ,  继承 ,  多态 ,  String ,  Date

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Java回顾之一些基础概念的详细内容...

  阅读:40次