JAVA中String对象的比较

1.首先介绍三个String对象比较的方法:
(1)equals:比较两个String对象的是否相等。例如: 
String str1 = "hello quanjizhu";
String str2 =str1+"haha";
String str3 = new String("hello quanjizhu");

System.out.println(str1.equals(str2)); 
System.out.println(str1.equals(str3)); 
输出结果都为true。

(2)= =:比较两个String对象的指向的内存地址是否相等。例如: 
String str1 = "hello quanjizhu";
String str2 =str1+"haha";
String str3 = new String("hello quanjizhu");

System.out.println(str1.equals(str2)); 
System.out.println(str1.equals(str3)); 
输出结果都为false。

3.原理
要理解 java中String的运作方式,必须明确一点:String是一个非可变类(immutable)。什么是非可变类呢?简单说来,非可变类的实例是不能被修改的,每个实例中包含的信息都必须在该实例创建的时候就提供出来,并且在对象的整个生存周期内固定不变。java为什么要把String设计为非可变类呢?你可以问问 james Gosling :)。但是非可变类确实有着自身的优势,如状态单一,对象简单,便于维护。其次,该类对象对象本质上是线程安全的,不要求同步。此外用户可以共享非可变对象,甚至可以共享它们的内部信息。(详见 《Effective java》item 13)。String类在java中被大量运用,甚至在class文件中都有其身影,因此将其设计为简单轻便的非可变类是比较合适的。 
(1)创建。
    好了,知道String是非可变类以后,我们可以进一步了解String的构造方式了。创建一个Stirng对象,主要就有以下两种方式:


java 代码
String str1 = new String("abc");    
Stirng str2 = "abc";  
     虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。对于第二种,jvm首先会在内部维护的strings pool中通过String的 equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 String的intern方法。看下面的例子:

java 代码
String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  
 //jvm 在strings pool中找不到值为“abc”的字符串,因此   
 //在堆上创建一个String对象,并将该对象的引用加入至strings pool中   
 //此时堆上有两个String对象   
Stirng str2 = "abc";   
  
 if(str1 == str2){   
         System.out.println("str1 == str2");   
 }else{   
         System.out.println("str1 != str2");   
 }   
  //打印结果是 str1 != str2,因为它们是堆上两个不同的对象   
  
  String str3 = "abc";   
 //此时,jvm发现strings pool中已有“abc”对象了,因为“abc”equels “abc”   
 //因此直接返回str2指向的对象给str3,也就是说str2和str3是指向同一个对象的引用   
  if(str2 == str3){   
         System.out.println("str2 == str3");   
  }else{   
         System.out.println("str2 != str3");   
  }   
 //打印结果为 str2 == str3  
   再看下面的例子:


java 代码
String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  
str1 = str1.intern();   
//程序显式将str1放到strings pool中,intern运行过程是这样的:首先查看strings pool   
//有没“abc”对象的引用,没有,则在堆中新建一个对象,然后将新对象的引用加入至   
//strings pool中。执行完该语句后,str1原来指向的String对象已经成为垃圾对象了,随时会   
//被GC收集。   
  
//此时,jvm发现strings pool中已有“abc”对象了,因为“abc”equels “abc”   
//因此直接返回str1指向的对象给str2,也就是说str2和str1引用着同一个对象,   
//此时,堆上的有效对象只有一个。   
Stirng str2 = "abc";   
  
 if(str1 == str2){   
         System.out.println("str1 == str2");   
 }else{   
         System.out.println("str1 != str2");   
 }   
  //打印结果是 str1 == str2   
  



    为什么jvm可以这样处理String对象呢?就是因为String的非可变性。既然所引用的对象一旦创建就永不更改,那么多个引用共用一个对象时互不影响。


(2)串接(Concatenation)。
     java程序员应该都知道滥用String的串接操作符是会影响程序的性能的。性能问题从何而来呢?归根结底就是String类的非可变性。既然String对象都是非可变的,也就是对象一旦创建了就不能够改变其内在状态了,但是串接操作明显是要增长字符串的,也就是要改变String的内部状态,两者出现了矛盾。怎么办呢?要维护String的非可变性,只好在串接完成后新建一个String 对象来表示新产生的字符串了。也就是说,每一次执行串接操作都会导致新对象的产生,如果串接操作执行很频繁,就会导致大量对象的创建,性能问题也就随之而来了。
    为了解决这个问题,jdk为String类提供了一个可变的配套类,StringBuffer。使用StringBuffer对象,由于该类是可变的,串接时仅仅时改变了内部数据结构,而不会创建新的对象,因此性能上有很大的提高。针对单线程,jdk 5.0还提供了StringBuilder类,在单线程环境下,由于不用考虑同步问题,使用该类使性能得到进一步的提高。

(3)String的长度
   我们可以使用串接操作符得到一个长度更长的字符串,那么,String对象最多能容纳多少字符呢?查看String的源代码我们可以得知类String中是使用域 count 来记录对象字符的数量,而count 的类型为 int,因此,我们可以推测最长的长度为 2^32,也就是4G。
    不过,我们在编写源代码的时候,如果使用 Sting str = "aaaa";的形式定义一个字符串,那么双引号里面的ASCII字符最多只能有 65534 个。为什么呢?因为在class文件的规范中, CONSTANT_Utf8_info表中使用一个16位的无符号整数来记录字符串的长度的,最多能表示 65536个字节,而java class 文件是使用一种变体UTF-8格式来存放字符的,null值使用两个字节来表示,因此只剩下 65536- 2 = 65534个字节。也正是变体UTF-8的原因,如果字符串中含有中文等非ASCII字符,那么双引号中字符的数量会更少(一个中文字符占用三个字节)。如果超出这个数量,在编译的时候编译器会报错。


(3)compareTo:比较两个String对象的是否相等。例如: 
String str1 = "hello quanjizhu";
String str2 =str1+"haha";
String str3 = new String("hello quanjizhu");

System.out.println(str1.compareTo(str2)); 
System.out.println(str1.compareTo(str3)); 
输出结果都为0。(若输出结果大于0表示str1大于str2)

2.String类的几种初始化方法的区别
(1) String str1 = "hello quanjizhu";
首先到String pool中查找有没有值为hello quanjizhu的对象,若有则让str1直接指向此内存地址;若没有则在内存堆中重新开辟空间给str1,并把hello quanjizhu加到String pool中。
(2)String str3 = new String("hello quanjizhu");
每次初始化都会重新在内存堆中开辟空间给新的对象,而不会到String pool中查找,更不会添加到String pool中。除非显示的调用intern方法。
str3.interl();这时就会把hello quanjizhu加到String pool中。
(3)
String str1 = "hello quanjizhu";
String str2 ="hello" +"quanjizhu";
 String str3 ="hello "+"quanjizhu";在编译的时候会优化成String str3 = "hello quanjizhu";所有str1和str2指向的是同一内存地址。
(4)
String var = “quanjizhu“;
String str4 = “hello “+var;
  System.out.println(str1= =str4)的结果是什么呢?输出结果是false,证明了String str4 = “hello “+var;
在内存堆中会重新分配空间,而不是让str4指向var的地址。换用一种定义方法:str4 = (“hello “+var4).intern();intern()方法告诉编译器将此结果放到String pool里,因此,System.out.println(str1= =str4)输出结构将是true;


posted @ 2008-07-22 22:18 chenkai 阅读(2148) | 评论 (3)编辑 收藏

java中子类继承父类时是否继承构造函数

java继承中对构造函数是不继承的,只是调用(隐式或显式)。

以下是例子:

 public class FatherClass {

 public FatherClass() {
       System.out.println(100);
 }

 public FatherClass(int age) {
      System.out.println(age);
 }

}

 

public class SonClass extends FatherClass{

        public SonClass() {
         }

        
public SonClass(int c) {
                 System.out.println(1234);
        }
  
 public static void main(String[] args) {

  SonClass s = new SonClass(66);
 
 }
}

编译后执行结果如下是什么呢?

分析:SonClass s = new SonClass(66);执行这句时,调用

 public SonClass(int c) {
                 System.out.println(1234);//系统会自动先调用父类的无参构造函数(super())
        }

在这个构造函数中,等价于

 public SonClass(int c) {

                super();//必须是第1行,否则不能编译
                 System.out.println(1234);
        }

所以结果是    100
             
1234

3.如果子类构造函数是这样写的

public SonClass(int c) {

                super(22);//必须是第1行,否则不能编译
                         
//显式调用了super后,系统就不再调用无参的super()了;
                 System.out.println(1234);
        }

执行结果是   22
            1234

总结1:构造函数不能继承,只是调用而已。

如果父类没有无参构造函数

创建子类时,不能编译,除非在构造函数代码体中第一行,必须是第一行显式调用父类有参构造函数

如下:

SonClass (){

super(777);//显示调用父类有参构造函数

System.out.println(66);

}

如果不显示调用父类有参构造函数,系统会默认调用父类无参构造函数super();

但是父类中没有无参构造函数,那它不是不能调用了。所以编译就无法通过了。

总结2:创建有参构造函数后,系统就不再有默认无参构造函数。

如果没有任何构造函数,系统会默认有一个无参构造函数。

posted @ 2008-07-02 11:13 chenkai 阅读(6870) | 评论 (1)编辑 收藏

Class.forName("package.class")的意义

java里面任何class都要装载在虚拟机上才能运行。Class.forName(xxx.xx.xx)就是装载类用的(和new 不一样,要分清楚),装载后jvm将执行类中的静态代码。

至于什么时候用,可以考虑一下这个问题,给你一个字符串变量,它代表一个类的包名和类名,你怎么实例化它?只有用提到的这个方法了,不过要再加一点。
A a = (A)Class.forName("pacage.A").newInstance();
这和
A a = new A();
是一样的效果。

有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一些是Class.forName(xxx.xx.xx).newInstance(),为什么会有这两种写法呢?

Class.forName(xxx.xx.xx) 返回的是一个类Class。
Class.forName(xxx.xx.xx).newInstance() 是创建一个对象,返回的是Object。
Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。

在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC Driver的Driver类的代码都必须类似如下:
public class MyJDBCDriver implements Driver {
static {
DriverManager.registerDriver(new MyJDBCDriver());
}
}

所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了

we just want to load the driver to jvm only, but not need to user the instance of driver, so call Class.forName(xxx.xx.xx) is enough, if you call Class.forName(xxx.xx.xx).newInstance(), the result will same as calling Class.forName(xxx.xx.xx), because Class.forName(xxx.xx.xx).newInstance() will load driver first, and then create instance, but the instacne you will never use in usual, so you need not to create it.

在JDBC驱动中,有一块静态代码,也叫静态初始化块,它执行的时间是当class调入到内存中就执行(你可以想像成,当类调用到内存后就执行一个方法)。所以很多人把jdbc driver调入到内存中,再实例化对象是没有意义的。

 

posted @ 2008-06-12 14:51 chenkai 阅读(270) | 评论 (0)编辑 收藏

关于toString()

    toString()是在Object类中定义的方法。所有类都是继承Object类,所以“所有对象都有这个方法” 。

    它通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法。总而言之,它只是sun公司开发java的时候为了方便所有类的字符串操作而特意加入的一个方法。

例子1:
public class A{
public String toString(){return "this is A";}
}
如果某个方法里面有如下句子:
A obj=new A();
System.out.println(obj);
会得到输出:this is A

例子2:
public class A{
public String getString(){return "this is A";}//toString改个名字试试看
}
A obj=new A();
System.out.println(obj);
会得到输出:xxxx@xxxxxxx的类名加地址形式
System.out.println(obj.getString());
会得到输出:this is A

看出区别了吗,toString的好处是在碰到“println”之类的输出方法时会自动调用,不用显式打出来。

posted @ 2008-06-12 13:38 chenkai 阅读(173) | 评论 (0)编辑 收藏

Java反射学习

所谓反射,可以理解为在运行时期获取对象类型信息的操作。传统的编程方法要求程序员在编译阶段决定使用的类型,但是在反射的帮助下,编程人员可以动态获取这些信息,从而编写更加具有可移植性的代码。严格地说,反射并非编程语言的特性,因为在任何一种语言都可以实现反射机制,但是如果编程语言本身支持反射,那么反射的实现就会方便很多。
1,获得类型类
    我们知道在Java中一切都是对象,我们一般所使用的对象都直接或间接继承自Object类。Object类中包含一个方法名叫getClass,利用这个方法就可以获得一个实例的类型类。类型类指的是代表一个类型的类,因为一切皆是对象,类型也不例外,在Java使用类型类来表示一个类型。所有的类型类都是Class类的实例。例如,有如下一段代码:
A a = new A();
if(a.getClass()==A.class)
    System.out.println("equal");
else System.out.println("unequal");

    可以看到,对象a是A的一个实例,A某一个类,在if语句中使用a.getClass()返回的结果正是A的类型类,在Java中表示一个特定类型的类型类可以用“类型.class”的方式获得,因为a.getClass()获得是A的类型类,也就是A.class,因此上面的代码执行的结果就是打印出“equal”。特别注意的是,类型类是一一对应的,父类的类型类和子类的类型类是不同的,因此,假设A是B的子类,那么如下的代码将得到“unequal”的输出:
A a = new A();
if(a.getClass()==B.class)
    System.out.println("equal");
else System.out.println("unequal");

    因此,如果你知道一个实例,那么你可以通过实例的“getClass()”方法获得该对象的类型类,如果你知道一个类型,那么你可以使用“.class”的方法获得该类型的类型类。
   
2,获得类型的信息
    在获得类型类之后,你就可以调用其中的一些方法获得类型的信息了,主要的方法有:
getName():String:获得该类型的全称名称。
getSuperClass():Class:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。
getInterfaces():Class[]:获得该类型实现的所有接口。
isArray():boolean:判断该类型是否是数组。
isEnum():boolean:判断该类型是否是枚举类型。
isInterface():boolean:判断该类型是否是接口。
isPrimitive():boolean:判断该类型是否是基本类型,即是否是int,boolean,double等等。
isAssignableFrom(Class cls):boolean:判断这个类型是否是类型cls的父(祖先)类或父(祖先)接口。
getComponentType():Class:如果该类型是一个数组,那么返回该数组的组件类型。
此外还可以进行类型转换这类的操作,主要方法有:
asSubclass(Class clazz):Class:将这个类型转换至clazz,如果可以转换,那么总是返回clazz这个引用,否则抛出异常。
cast(Object obj):Object:将obj强制转换为这个类型类代表的类型,不能转换的话将抛出异常。

    除了这些以外,利用类型类还可以反射该类型中的所有属性和方法。在Java中所有的属性信息都用Field表示,所有的方法信息都用Method表示,这辆各类都是java.lang.reflect包中的类。在Class中提供了4个相关的方法获得类型的属性:
getField(String name):Field
getFields():Field[]
getDeclaredField(String name):Field
getDeclaredFields():Field[]
其中getField用于返回一个指定名称的属性,但是这个属性必须是公有的,这个属性可以在父类中定义。如果是私有属性或者是保护属性,那么都会抛出异常提示找不到这个属性。getFields则是返回类型中的所有公有属性,所有的私有属性和保护属性都找不到。getDeclaredField获得在这个类型的声明中定义的指定名称的属性,这个属性必须是在这个类型的声明中定义,但可以使私有和保护的。getDeclaredFields获得在这个类型的声明中定义的所有属性,包括私有和保护的属性都会被返回,但是所有父类的属性都不会被返回。举个例子,先考虑下面两个类的声明:
class A extends B {
    public int a1;
    private int a2;
}
class B {
 public int b1;
 private int b2;
}
如果利用A的类型类调用getFields,那么会返回a1和b1两个属性,如果调用getField("a2")则会报错;如果调用getDeclaredFields则会返回a1和a2,如果调用getDeclaredField("b1")则会报错。

    对于方法也有类似的函数即:
getMethods():Method[]
getMethod(String name, Class ... parameterTypes):Method
getDeclaredMethods():Method[]
getDeclaredMethod(Strubg name, Class ...parameterTypes):Method
不定长参数...是JDK5.0以后新加入的语法。这几个方法的用法和上面的类似,只是在获得特定方法时,除了要告知方法的名字,还需要告知方法的参数,如果没有参数,那么可以传递null,或者空数组,但是最好的方法就是什么都不写,编译器会自行解决不定长参数问题。
    如果要获得所有的属性(方法),包括公有和私有的,那么就必须利用getDeclareFields(getDeclareMethods)方法,然后再利用getSuperClass的方法获得父类,然后递归下去。
   
3,属性和方法
    所有的属性都使用Field表示,所有的方法都使用Method表示。利用Field和Method可以获得属性和方法的信息,甚至执行是获取、修改属性值和调用方法。
   
    对于属性,主要有以下方法可以使用:
getType():Class:获得该属性的类型。
getName():String:获得属性名称。
isAccessible():boolean:判断该属性是否是可以访问的,通常私有和保护的类型都是不可以访问的。
get(Object obj):Object:获得实例obj的属性值,如果该实例的类型中不包含这个属性,那么就会报错。
set(Object obj, Object value):设置该实例的属性值
setAccessible(boolean flag):设置该属性是否可以访问,如果你调用get和set方法,那么有可能会引发访问权限的错误,这个时候你可以调用setAccessible方法使得该属性可以访问。例如下面的代码:
A a = new A();
Field f = A.class.getDeclaredField("a2");
f.setAccessibe(true);
System.out.println(f.get(a));
f.set(a,12);
System.out.println(f.get(a));
如果移出中间的f.setAccessibe(true);那么代码会报错,反之输出0 12。
    对于属性而言,如果该属性的类型是基本类型,那么还可以使用一些便捷的set和get操作,例如getInt,setInt什么的,你可以根据自己的需要调用相应的方法。
   
    对于方法,可以有以下的方法:
getName():String:获得方法的名字。
getReturnType():Class:获得方法的返回值类型。
getParameterTypes():Class[]:获得方法的参数类型。
isAccessible():boolean:判断该方法是否是可以访问的。
setAccessible(boolean flag):设置该方法是否可以访问。
invoke(Object obj, Object... args):Object:调用实例obj的相应方法,其参数由args给定,如果没有参数那么可以什么都不写。
getExceptionTypes():Class[]:获得该方法可能抛出的异常类类型。
这几个方法的含义和用法都和Field的类似,这里不再赘述。

4,创建实例
    利用Class对象可以创建一个类型的实例。如果一个类型拥有无参数的构造函数,那么可以简单地调用Class.newInstance()方法创建一个实例。如果该类型没有无参数的构造函数,或者你希望是用某个有参数的构造函数,那么可以首先使用getConstructors()、getConstructor(Class[] parameterTypes)和getDeclaredConstructors()、getDeclaredConstructor(Class[] parameterTypes)获得构造函数,这两个方法的返回值都使Constructor类型。特别注意的是,构造函数不能继承,因此你调用getConstructor也只能返回这个类型中定义的所有公有构造函数。
   
    Constructor的使用方法和Method的类似,它也存在getParameterTypes()方法和getExceptionTypes()方法,不同的是,它使用newInstance(Object... args)来调用一个构造函数,注意newInstance不需要实例对象,因为这个时候你还没创建出来这个实例呢

posted @ 2008-06-12 13:28 chenkai 阅读(1224) | 评论 (0)编辑 收藏

仅列出标题
共2页: 上一页 1 2 
<2024年5月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

导航

统计

常用链接

留言簿(2)

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜