java 字符串,表达式,等陷阱

1. 关于字符串的陷阱

java程序中创建对象的常规方式有如下4种:


通过new()调用构造器创建java对象

通过class对象的newInstance()方法调用构造器创建java对象

通过java的反序列化机制从IO流中恢复java对象

通过java对象提供的clone()方法复制一个新的java对象

除此之外,还允许直接量创建java对象,如 String str="apple";,也可以通过简单的表达式,连接运算来创建java对象,String str2="apple"+"sweet";


对于java的字符直接量,JVM会使用一个字符串池来保存它们,当第一次使用某字符串直接量时,JVM会将它放入字符串池进行缓存。在一般情况下,字符串池中的字符串对象不会被垃圾机制回收,当程序再次需要该字符串时,无需创建一个新的字符串,而是直接让引用变量直接指向字符串池中已有的字符串。

public class inittest {
public static void main(String[] args)
{
String str1="hello.java";
String str2="hello."+"java";
System.out.println(str1==str2);

}

输出结果为true, 因为上式的表达式中,所有的运算都是字符串直接量,没有调用方法,或者有变量参加其中,所有在编译的时候就确定该字符串连接表达式的值,可以让字符串变量指向字符串池中对应的字符串。但如果使用了变量,或者调用了方法,那就只能等程序运行的时候才可以确定该字符串连接表达式的值,因此无法使用JVM的字符串池。如下程序会输出false

public class inittest {
public static void main(String[] args)
{
String str1="hello.java";
String str2="hello.";
String str3="java";
String str4=str2+str3;
System.out.println(str1==str4);

}

但前面讲过,可以用final修饰变量,让它实现宏替换,这样也可以实现在编译时就确定值。修改如下即可

public class inittest {
public static void main(String[] args)
{
String str1="hello.java";
final String str2="hello.";
final String str3="java";
String str4=str2+str3;
System.out.println(str1==str4);

}

}

一个简单的问题,String str="hello"+"java"+"happy"+"to learn";这个语句创建了几个字符串对象?

其实只创建了一个字符串对象,因为这些直接量,可以在编译的时候就确定下来。在编译的时候就确定str的值为hellojavahappytolearn,然后将变量str指向字符串池中的字符串对象。

通过这里可以看出,当程序中需要使用字符串、基本类型包装类实例时,应该尽量使用字符串直接量、基本类型的直接量,避免通过new string()、new integer()形式来创建字符串。


String类是一个典型的不可变类,当一个String对象创建完成后,该String类里包含的字符串被固定下来了,以后永远不能改变。不是说变量str1不可变,而是它指向的字符串对象不可变。

public class inittest {
public static void main(String[] args)
{
String str1="hello";
System.out.println(System.identityHashCode(str1));
str1=str1+"java";
System.out.println(System.identityHashCode(str1));
str1=str1+"happy to learn";
System.out.println(System.identityHashCode(str1));

}
}

输出结果为

2065827189
1989444474
272890728

使用了System.identityHashCode(str1)方法来获取str1的hashcode值,发现返回三个不同的返回值,这是因为每次访问str1时,它都指向了不同的string对象。


System类的identityHashCode()与是否重写hashcode()函数无关。


上述程序执行时的内存分配如下;


图中的hello,hello java字符串可能以后都不会被用到,但这个字符串不会被垃圾机制回收,因为它将一直存在于字符串池中,这就是java内存泄露的原因之一


对于String类而言,它代表字符串序列不可改变的字符串,因此如果程序需要一个可变的字符串,那就考虑stringBuider和StringBuffer,两者之间的区别是后者是线程安全的,但实际上在没有多线程的环境下,应该考虑使用StringBuilder

public class inittest {
public static void main(String[] args)
{
StringBuilder str1=new StringBuilder("hello");
System.out.println(System.identityHashCode(str1));
str1.append("java");
System.out.println(System.identityHashCode(str1));
str1.append("happy to learn");
System.out.println(System.identityHashCode(str1));

}
}

输出结果为:

1573625905
1573625905
1573625905

System.identityHashCode(str1)返回的值都相同,表明str1一直指向同一个对象。String是不可变类,所以它总是线程安全的。


2.表达式类型的陷阱


java语言规定:当一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。

所有byte型、short型和char型将被提升到int型,下面为提升的等级,右边的等级高于左边类型的等级


short a=5;
a=a-2;

编译出错错误,因为a为short ,而2为int型,最后赋值给short,会发生错误


byte b=40;
char c='a';
int i=80;
double d=.21;
double result=b+c+i+d;

不会出现错误,因为最高级的为double, 最后那个语句,表达式右边的类型被提升到double,将计算结果赋值给double型的result变量,没问题。


public class inittest {
public static void main(String[] args)
{
System.out.println("hello"+'a'+7);
System.out.println('a'+7+"hello");
}
}输出结果为:

helloa7
104hello

当基本类型值和String进行连接运算时,系统会将基本类型的值转换成String类型,这样才可以让连接运算正常进行。


复合赋值运算符的陷阱

short a=5;
a=a-2;如果改为

short a=5;

a-=2;

则没有错误!这是因为,复合赋值运算会自动将它的结果值强制类型转换为其左侧变量的类型。等价于E1=(E1的类型)(E1 OP E2)    如果结果类型与该变量的类型相同,那么这个转换不会造成任何影响,但是如果结果值的类型比这个变量的类型大,那么复合赋值运算符将会执行一次强制类型转换,这个强制类型转换将有可能导致高位“截断”。


由此可见,复合赋值运算简单,方便但是有一定的危险,它潜在的隐式类型转换可能在不知不觉中导致计算结果的高位被“截断”


3.输入法的陷阱

public class inittest {
public static void main(String[] args)
{
System.out.println("hello"+'a'+7);

}
}

一个简单的程序,会出现错误,这个是因为你在全角状态下,输入空格当成分隔符,所以出现错误。


4. 注释的字符必须合法

大部分情况下,java编译会直接忽略注释部分,但又一种例外:java要求注释部分的所有字符必须是合法的字符。

/***

d:\codes\unit3\...

***/

这段注释就会出现错误,java允许使用\uXXXXX,但要求后4个必须是0-F的字符,这段注释不符合java对unicode转义字符的要求。



5.转义字符的陷进


java提供3种方式来表示字符:

直接使用单引号起来的字符值‘a'

使用转义字符,如'\n'

使用unicode转义字符,如’\u6002'

但谨慎用第三种!


6.泛型可能引起的错误


原始类型变量的赋值

import java.util.List;
import java.util.ArrayList;


public class inittest {
public static void main(String[] args)
{
List list=new ArrayList();
list.add("hello");
list.add("java");
List<Integer> intList=list;//将集合赋给泛型
for (int i=0;i<intList.size();i++)
{
System.out.println(intList.get(i));
}



}
}

输出结果为:

hello
java

可以看出当程序把一个原始类型的变量赋给一个带泛型信息的变量时,只要它们的类型保持兼容,例如将List赋给List<Integer>, 无论集合中实际包含什么类型的元素,系统都不会有任何问题。

但是看下面的程序

import java.util.List;
import java.util.ArrayList;


public class inittest {
public static void main(String[] args)
{
List list=new ArrayList();
list.add("hello");
list.add("java");
List<Integer> intList=list;//将集合赋给泛型
for (int i=0;i<intList.size();i++)
{
Integer in=intList.get(i);
System.out.println(in);
}



}
}输出结果:Exception in thread "main" java.lang.ClassCastException

所以:

当程序把一个原始类型的变量赋给一个带泛型信息的变量时,总是可以通过编译,只会显示一些警告信息

当程序试图访问带泛型声明的集合的集合元素时,编译器总是把集合元素当成泛型类型处理

当程序试图访问带泛型声明的集合的集合元素时,JVM会遍历每个集合元素自动执行强制转换,如果集合元素的实际类型与集合所带的泛型信息不符,运行时将引发classcastexception


java 不支持泛型数组!


7.正则表达式的陷阱


matches( String regex);

String replaceAll(String regex,String replacement);

String replaceFirst(String regex,String replacement);

String[] split( String regex);// regex 正则表达式

replace();

下面是一个例子:注意使用转义

public class inittest {
public static void main(String[] args)
{
String str1="hello.java.happy to learn";
String path1=str1.replace(".", "\\");
System.out.println(path1);
String path2=str1.replaceAll("\\.", "\\\\");


}
}

输出结果为:hello\java\happy to learn






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值