学java的都知道String这个类,是我们一般都要用的一个类,那么我们对这个类又了解多少呢?
下面我来给大家介绍下这个类!
String 这个类有两大模式一个是对象池的概念,而另一个就是不变模式!
String类和对象池
我们知道得到String对象有两种办法: String str1="hello"; String str2=new String("hello"); 这两种创建String对象的方法有什么差异吗?当然有差异,差异就在于第一种方法在对象池中拿对象,第二种方法直接生成新的对象。在JDK5.0里面,Java虚拟机在启动的时候会实例化9个对象池,这9个对象池分别用来存储8种基本类型的包装类对象和String对象。当我们在程序中直接用双引号括起来一个字符串时,JVM就到String的对象池里面去找看是否有一个值相同的对象,如果有,就拿现成的对象,如果没有就在对象池里面创建一个对象,并返回。所以我们发现下面的代码输出true: String str1="hello"; String str2="hello"; System.out.println(str1==str2); 这说明str1和str2指向同一个对象,因为它们都是在对象池中拿到的,而下面的代码输出为false: String str3="hello" String str4=new String("hello"); System.out.println(str3==str4); 因为在任何情况下,只要你去new一个String对象那都是创建了新的对象。与此类似的,在JDK5.0里面8种基本类型的包装类也有这样的差异: Integer i1=5;//在对象池中拿 Integer i2 =5;//所以i1==i2 Integer i3=new Integer(5);//重新创建新对象,所以i2!=i3 对象池的存在是为了避免频繁的创建和销毁对象而影响系统性能,那我们自己写的类是否也可以使用对象池呢?当然可以,考察以下代码: import java.util.*;
public class Student {
private String name;
private int age;
private static HashSet<Student> pool = new HashSet<Student>();
// 对象池
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 使用对象池来得到对象的方法
public static Student newInstance(String name, int age) {
// 循环遍历对象池
for (Student stu : pool) {
if (stu.name.equals(name) && stu.age == age) {
return stu;
}
}
// 如果找不到值相同的Student对象,则创建一个Student对象
//并把它加到对象池中然后返回该对象。
Student stu = new Student(name, age);
pool.add(stu);
return stu;
}
}
class Test {
public static void main(String[] args) {
Student stu1 = Student.newInstance("tangliang", 30);
// 对象池中拿
Student stu2 = Student.newInstance("tangliang", 30);
// 所以stu1==stu2
Student stu3 = new Student("tangliang", 30);
// 重新创建,所以stu1!=stu3
System.out.println(stu1 == stu2);
System.out.println(stu1 == stu3);
}
}
6,2.0-1.1==0.9吗? 考察下面的代码: double a=2.0,b=1.1,c=0.9; if(a-b==c){ System.out.println("YES!"); }else{ System.out.println("NO!"); } 以上代码输出的结果是多少呢?你认为是“YES!”吗?那么,很遗憾的告诉你,不对,Java语言再一次cheat了你,以上代码会输出“NO!”。为什么会这样呢?其实这是由实型数据的存储方式决定的。我们知道实型数据在内存空间中是近似存储的,所以2.0-1.1的结果不是0.9,而是0.88888888889。所以在做实型数据是否相等的判断时要非常的谨慎。一般来说,我们不建议在代码中直接判断两个实型数据是否相等,如果一定要比较是否相等的话我们也采用以下方式来判断: if(Math.abs(a-b)<1e-5){ //相等 }else{ //不相等 } 上面的代码判断a与b之差的绝对值是否小于一个足够小的数字,如果是,则认为a与b相等,否则,不相等。
7,判断奇数 以下的方法判断某个整数是否是奇数,考察是否正确: public boolean isOdd(int n){ return (n%2==1); } 很多人认为上面的代码没问题,但实际上这段代码隐藏着一个非常大的BUG,当n的值是正整数时,以上的代码能够得到正确结果,但当n的值是负整数时,以上方法不能做出正确判断。例如,当n=-3时,以上方法返回false。因为根据Java语言规范的定义,Java语言里的求余运算符(%)得到的结果与运算符左边的值符号相同,所以,-3%2的结果是-1,而不是1。那么上面的方法正确的写法应该是: public boolean isOdd(int n){ return (n%2!=0); }
8,拓宽数值类型会造成精度丢失吗? Java语言的8种基本数据类型中7种都可以看作是数值类型,我们知道对于数值类型的转换有一个规律:从窄范围转化成宽范围能够自动类型转换,反之则必须强制转换。请看下图: byte-->short-->int-->long-->float-->double char-->int 我们把顺箭头方向的转化叫做拓宽类型,逆箭头方向的转化叫做窄化类型。一般我们认为因为顺箭头方向的转化不会有数据和精度的丢失,所以Java语言允许自动转化,而逆箭头方向的转化可能会造成数据和精度的丢失,所以Java语言要求程序员在程序中明确这种转化,也就是强制转换。那么拓宽类型就一定不会造成数据和精度丢失吗?请看下面代码: int i=2000000000; int num=0; for(float f=i;f< 哈哈,你快要不相信你的眼睛了,结果竟然是true;难道f1和f2是相等的吗?是的,就是这样,这也就能解释为什么上一段代码输出的结果是0,而不是50了。那为什么会这样呢?关键原因在于你将int值自动提升为float时发生了数据精度的丢失,i的初始值是2000000000,这个值非常接近Integer.MAX_VALUE,因此需要用31位来精确表示,而float只能提供24位数据的精度(另外8位是存储位权,见IEEE745浮点数存储规则)。所以在这种自动转化的过程中,系统会将31位数据的前24位保留下来,而舍弃掉最右边的7位,所以不管是2000000000还是2000000050,舍弃掉最右边7位后得到的值是一样的。这就是为什么f1="=f2的原因了。" System.out.println(f1="=f2);" f2="i+50;" float f1="i;" i="2000000000;" int ,那么请运行一下,结果会让你大吃一惊!没错,输出结果是0,难道这个循环根本就没有执行哪怕一次?确实如此,如果你还不死心,我带你你看一个更诧异的现象,运行以下代码,看输出什么? 如果你回答50 请考察以上代码输出多少? System.out.println(num); } num++;>
9,i=i+1和i+=1完全等价吗?
可能有很多程序员认为i+=1只是i=i+1的简写方式,其实不然,它们一个使用简单赋值运算,一个使用复合赋值运算,而简单赋值运算和复合赋值运算的最大差别就在于:复合赋值运算符会自动地将运算结果转型为其左操作数的类型。看看以下的两种写法,你就知道它们的差别在哪儿了:
(1) byte i=5;
i+=1;
(2) byte i=5;
i=i+1;
第一种写法编译没问题,而第二种写法却编译通不过。原因就在于,当使用复合赋值运算符进行操作时,即使右边算出的结果是int类型,系统也会将其值转化为左边的byte类型,而使用简单赋值运算时没有这样的优待,系统会认为将i+1的值赋给i是将int类型赋给byte,所以要求强制转换。理解了这一点后,我们再来看一个例子:
byte b=120;
b+=20;
System.out.println("b="+b);
说到这里你应该明白了,上例中输出b的值不是140,而是-116。因为120+20的值已经超出了一个byte表示的范围,而当我们使用复合赋值运算时系统会自动作类型的转化,将140强转成byte,所以得到是-116。由此可见,在使用复合赋值运算符时还得小心,因为这种类型转换是在不知不觉中进行的,所以得到的结果就有可能和你的预想不一样。
对于J2ME的答案也是1一个 在J2ME里面没有对象池的概念。
在JDK中有jdk里面有9个对象池,8个基本类包装类对象和String类对象。
不变模式
什么是不变模式呢?
String s = "xx";
String s = "aa";
不变模式就是 一个字符串对象 创建后它的值就不能再被改变。
每改变一次就产生一个垃圾,它只是一个对象的引用地址,指向一个值,如果把他的值换了,那么只是换了一个对象的引用地址,至始至终没有改变它本身的值,如果要在系统中频繁的使用字符串引用,那么建议使用StringBuffer,先放到缓冲区里,然后直接全部写入。先暂时写到这里把!
分享到:
相关推荐
### 基于对象池模式的自适应线程池技术 #### 一、引言 在现代软件开发中,特别是对于高性能服务器应用而言,优化线程管理是提升系统响应能力和资源利用率的关键因素之一。传统的线程池技术虽然能显著减少线程创建...
根据给定的信息,我们可以深入探讨这本《c#面向对象23种设计模式手册》中的设计模式知识点。这本书不仅适合初学者快速上手C#语言和面向对象编程的基础概念,同时也为进阶学习者提供了丰富的设计模式案例。下面将详细...
- **1.4 Builder模式**:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 - **应用场景**:当创建复杂对象的算法应该独立于部分组成对象的类时。 - **优点**:各个部件灵活组装,...
- **应用场景**:需要频繁实例化的资源消耗较大的对象,如数据库连接池。 - **优点**:节省内存资源。 - **缺点**:增加了系统的复杂度,降低了程序的可测试性。 #### 1.4 Builder模式 - **定义**:将一个复杂对象...
C++中的享元模式通常涉及对象池或共享数据结构。 14. 命令模式:将请求封装为一个对象,以便使用不同的请求、队列请求、或者支持撤销操作。C++中的命令模式通常使用对象和函数指针实现。 15. 责任链模式:处理请求...
- **应用场景**:当算法的步骤相同,但某些步骤的实现不同,或者当需要确保一个算法的不变的部分时,可以使用此模式。 - **优缺点**:优点是提高了代码的复用性;缺点是模板方法通常需要一个抽象类,这增加了类的...
- 不变模式:确保对象在生命周期内的不变性,从而简化线程同步。 - Future模式:异步执行任务并返回结果,提供了一种机制来获取异步任务的结果。 - 生产者消费者模式:管理不同线程之间的数据交换。 #### 三、...
在实际应用中,装饰模式常用于扩展或修改对象的行为,比如在数据库连接池中,我们可以使用装饰模式为数据库连接添加日志记录、超时处理等功能。装饰模式的优势在于保持了原有对象的结构和接口不变,使得扩展更加灵活...
2. **共享和缓存**:由于不变对象的值不会改变,它们的引用可以安全地被共享和缓存,而不用担心值的改变会导致引用失效或与其他状态不一致。例如,字符串常量池就是基于不变性实现的,可以高效地重用相同的字符串...
总结,享元模式是通过共享技术优化内存使用的设计模式,适用于内部状态不变、外部状态可变的对象。通过合理地划分内部和外部状态,享元模式能够在保证功能完整性的前提下,减少系统资源的消耗。在C#编程中,我们可以...
此外,在远程调用、数据访问优化、对象池等方面也能看到代理模式的身影。 总结一下,代理模式是软件设计中一种非常重要的模式,它为我们提供了控制对目标对象访问的手段,可以灵活地扩展功能,同时保持了原有对象的...
- **装饰模式**:动态地给一个对象添加一些额外的职责,装饰模式可以独立于对象实例进行扩展功能,同时保持接口不变,提供了比继承更灵活的扩展方式。 这些设计模式在实际开发中有着广泛的应用。例如,抽象工厂...
7. **装饰器模式**:动态地给一个对象添加一些额外的职责,保持了对象的接口不变,可以独立增加功能。 8. **代理模式**:为其他对象提供一种代理以控制对这个对象的访问,例如远程代理、虚拟代理等。 9. **桥接...
享元模式通过共享技术来解决这个问题,它将相同或相似对象的实例化成本降低,同时保持客户端对这些对象的使用方式不变。 享元模式的核心思想是区分对象的内部状态和外部状态。内部状态是存储在享元对象内部且不会随...
创建型模式关注的是如何创建对象,以使系统的扩展更加灵活。 1. **Factory模式**(工厂模式): - **定义**:定义一个用于创建对象的接口,让子类决定实例化哪一个类。该模式使得一个类的实例化延迟到其子类。 - ...
同样地,面向对象设计员也沿袭一些模式,像“用对象表示状态”和“修饰对象以便 于你能容易地添加/删除属性”等。一旦懂得了模式,许多设计决策自然而然就产生了。 我们都知道设计经验的重要价值。你曾经多少次有过...
在IT行业中,工厂模式被广泛应用,例如在数据库连接池的管理、对象持久化操作、GUI组件的创建等场景。通过工厂模式,我们可以将对象的创建与使用解耦,提高系统的灵活性和可维护性。 接下来,我们转向模板方法模式...