Java中一些基础概念的使用详解_java

  类的初始化顺序
  在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来做例子,但它属于享元模式吗?

  字符串与享元的关系,大家可以参考下面的文章:深入C#字符串和享元(Flyweight)模式的使用分析
  字符串的反转输出
  这种情况下,一般会将字符串看做是字符数组,然后利用反转数组的方式来反转字符串。

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

  来看下面的代码:

复制代码 代码如下:

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语句被执行

Finally语句被执行

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,每种基本类型都对应一个“包装类”,其他一些基本信息如下:

复制代码 代码如下:

. 基本类型:byte 二进制位数:8
. 包装类:java.lang.Byte
. 最小值:Byte.MIN_VALUE=-128
. 最大值:Byte.MAX_VALUE=127
. 基本类型:short 二进制位数:16
. 包装类:java.lang.Short
. 最小值:Short.MIN_VALUE=-32768
. 最大值:Short.MAX_VALUE=32767
. 基本类型:int 二进制位数:32
. 包装类:java.lang.Integer
. 最小值:Integer.MIN_VALUE=-2147483648
. 最大值:Integer.MAX_VALUE=2147483647
. 基本类型:long 二进制位数:64
. 包装类:java.lang.Long
. 最小值:Long.MIN_VALUE=-9223372036854775808
. 最大值:Long.MAX_VALUE=9223372036854775807
. 基本类型:float 二进制位数:32
. 包装类:java.lang.Float
. 最小值:Float.MIN_VALUE=1.4E-45
. 最大值:Float.MAX_VALUE=3.4028235E38
. 基本类型:double 二进制位数:64
. 包装类:java.lang.Double
. 最小值:Double.MIN_VALUE=4.9E-324
. 最大值:Double.MAX_VALUE=1.7976931348623157E308
. 基本类型:char 二进制位数:16
. 包装类:java.lang.Character
. 最小值:Character.MIN_VALUE=0
. 最大值: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
-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的字符串转换为日期类型。

时间: 2024-11-03 19:23:26

Java中一些基础概念的使用详解_java的相关文章

java中哈希表及其应用详解_java

哈希表也称为散列表,是用来存储群体对象的集合类结构. 什么是哈希表 数组和向量都可以存储对象,但对象的存储位置是随机的,也就是说对象本身与其存储位置之间没有必然的联系.当要查找一个对象时,只能以某种顺序(如顺序查找或二分查找)与各个元素进行比较,当数组或向量中的元素数量很多时,查找的效率会明显的降低. 一种有效的存储方式,是不与其他元素进行比较,一次存取便能得到所需要的记录.这就需要在对象的存储位置和对象的关键属性(设为 k)之间建立一个特定的对应关系(设为 f),使每个对象与一个唯一的存储位置

JAVA中static方法的用法实例详解_java

本文实例讲述了JAVA中static方法的用法.分享给大家供大家参考,具体如下: static表示"全局"或者"静态"的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念. 被static修饰的成员变量和成员方法独立于该类的任何对象.也就是说,它不依赖类特定的实例,被类的所有实例共享.只要这个类被加载,Java虚拟机就能根据类名在运行时数据区或者方法区内找到他们.因此,static对象可以在它的任何对象创建之前访

Java中自然排序和比较器排序详解_java

前言 当指执行插入排序.希尔排序.归并排序等算法时,比较两个对象"大小"的比较操作.我们很容易理解整型的 i>j 这样的比较方式,但当我们对多个对象进行排序时,如何比较两个对象的"大小"呢?这样的比较 stu1 > stu2 显然是不可能通过编译的.为了解决如何比较两个对象大小的问题,JDK提供了两个接口 java.lang.Comparable 和 java.util.Comparator . 一.自然排序:java.lang.Comparable C

Java中堆和栈的区别详解_java

当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更糟糕的是,Java中存在栈这样一个后进先出(Last In First Out)的顺序的数据结构,这就是java.util.Stack.这种情况下,不免让很多人更加费解前面的问题.事实上,堆和栈都是内存中的一部分,有着不同的作用,而且一个程序需要在这片区域上分配内存.众所周知,所有的Java程序都运行在JVM虚拟机内部,我们这里介绍的自然

JAVA中的final关键字用法实例详解_java

本文实例讲述了JAVA中的final关键字用法.分享给大家供大家参考,具体如下: 根据上下文环境,java的关键字final也存在着细微的区别,但通常指的是"这是无法改变的."不想改变的理由有两种:一种是效率,另一种是设计.由于两个原因相差很远,所以关键子final可能被误用. 接下来介绍一下使用到final的三中情况:数据,方法,类 final数据 许多编程语言都有某种方法,来向编译器告知一块数据是恒定不变的.有时数据的恒定不变是很有用的,例如: 1. 一个编译时恒定不变的常量 2.

Java中FilterInputStream和FilterOutputStream的用法详解_java

FilterInputStream FilterInputStream 的作用是用来"封装其它的输入流,并为它们提供额外的功能".它的常用的子类有BufferedInputStream和DataInputStream. BufferedInputStream的作用就是为"输入流提供缓冲功能,以及mark()和reset()功能". DataInputStream 是用来装饰其它输入流,它"允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类

java中Struts2文件上传问题详解_java

首先是网页部分,upload_file.jsp <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML> <html> <head> <title>Upload File</title> </head> <body> <form act

java集合——Java中的equals和hashCode方法详解_java

Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这两个方法,今天就来介绍一些这两个方法的作用. equals()和hashCode()方法是用来在同一类中做比较用的,尤其是在容器里如set存放同一类对象时用来判断放入的对象是否重复. 这里我们首先要明白一个问题: equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等.换

Java中Vector与ArrayList的区别详解_java

首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList.Vector和LinkedList.List用于存放多个元素,能够维护元素的次序,并且允许元素的重复.3个具体实现类的相关区别如下:1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中.当从ArrayList的中间位置插入或者删除元素时,需要对数组