在java中,常量池、堆、栈,进而探讨equals方法和==的区别,以及在equals在object的应用

一、java中的常量池,堆,栈的简单理解。

(1)这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。

     栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。    

    对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

     如以下代码: Java代码 

        String s1 = "china"; 

        String s2 = "china";

        String s3 = "china"; 

        String ss1 = new String("china"); 

        String ss2 = new String("china"); 

        String ss3 = new String("china");   

        这里解释一下,对于通过 new 产生一个字符串(假设为 ”china” )时,会先去常量池中查找是否已经有了 ”china” 对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此 ”china” 对象的拷贝对象。

        也就是有道面试题: String s = new String(“xyz”); 产生几个对象?

         一个或两个。如果常量池中原来没有 ”xyz”, 就是两个。如果原来的常量池中存在“xyz”时,就是一个。

        对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。

如下图所示,代码为

   String str1="abc";

   String str2="abc";

   String str3="abc";

   String str4=new String("abc");

   String str5=new String("abc");


    对于浅蓝色箭头,通过new操作产生一个字符串(“abc”)时,会先去常量池中查找是否有“abc”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此“abc”对象的拷贝对象,所以,对于String str=new String("abc"),如果常量池中原来没有"abc"则产生两个对象,否则产生一个对象。

    而对于上面提到的,对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中,有如下例子



    (1)最后总结一句话,当用String  str=“abc”的时候,首先去常量池去找,没有的话,就创建,abc保存在哪里呢,常量池和堆中都有。如果再有String str2=“abc”,就会再次查看常量池,因为已经存在abc,就不需要进行创建了,也就是具有了相同的引用和地址。str==str1.为true。

    (2)如果是String str =new String(“abc”),则是在堆中进行创建,创建的同时,会查看常量池中是否存在abc,存在的话,就将两者联系起来,不存在的话就会在常量池中进行创建abc。

结论建议(转):

(1) 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向 String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因 此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认 识到这一点对排除程序中难以发现的bug是很有帮助的。

(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是 享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率 

二、java中的数据类型

 Java中的数据类型有两种。

 (1)一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出 后,字段值就消失了),出于追求速度的原因,就存在于栈中。 另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义   int a = 3;   int b = 3; 编 译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处 理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。 特 别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对 象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与 b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存 放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

   (2)另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。  

   举例如下: Java代码 

   public class Test {       

    public static void main(String[] args)       

    {   

        int a1=1;         

        int b1=1;         

        int c1=2;         

        int d1=a1+b1;         

        Integer a = 1;           

        Integer b = 2;           

        Integer c = 3;           

        Integer d = 3;           

        Integer e = 321;           

        Integer f = 321;

        Long g = 3L;  

       System.out.println(a1==b1);   //true  结果1           

       System.out.println(c1==d1);   //true  结果2         

       System.out.println(c==d);   //true  结果3            

       System.out.println(e==f);   //false  结果4          

      }  

  }   

 分析:

   结果1:a1==b1如上面所述,会在栈 中开辟存储空间存放数据。          

   结果2:首先它会在栈 中创建一个变量为c1的引用,然后查找有没有字面值为2的地址,没找到,就开辟一个存放2这个字面值的地址,然后将c1指向2的地址,d1为两个字面值相加也为2, 由于在栈中已经有2这个字面值,便将d1直接指向2的地址。这样,就出现了c1与d1同时均指向3的情况。          在分析下面结果以前让我们先对Java自动拆箱和装箱做个了结:在自动装箱时,把int变成Integer的时候,是有规则的,当你的int的值在-128-IntegerCache.high(127) 时,返回的不是一个新new出来的Integer对象,而是一个已经缓存在堆 中的Integer对象,(我们可以这样理解,系统已经把-128到127之 间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新new一个),如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象。           

  结果3:由于3是在范围内所以是从缓存中取数据的,c和d指向同一个对象,结果为true;         

  结果4:由于321不是在范围内所以不是从缓存中取数据的而是单独有new对象,e和f并没有指向同一个对象,结果为false;  

三、equal和==在字符串中的用法区别(根据上面的知识以下就很好理解了)

     (1)字符串中对象调用equal(String s)方法比较的是当前字符串对象的实体是否与参数s指定的字符串实体相同,而不是引用或者地址。

   进行对象比较时最好还是使用equals,便于按照自己的目的进行控制。equals比较的是字符串字面值即比较内容,==比较引用。

String tom=new String(“We are Students”);
String boy=new String(“You are students”);
String jerry=new String(“We are Students”);
如果  tom.equal(boy)   得到的值是True
但如果是 tom==jerry,得到的值false
因为字符串是对象,tom和jerry中存放的是引用,equal比较的时候,忽略大小写。
(2)如果是不是new声明,直接是以下这种情况的话,s3和s4都在栈中,how are you 只有一个地址,存在于常量池中。
 String s1,s2;  
 s3="how are you";   
 s4="how are you";
 System.out.println(s3.equal(s4));     //输出结果为true  
ystem.out.println(s3==s4);    //输出结果为true;

四equal和==在不同类型变量中的应用比较

链接:http://deony2jacob1314.iteye.com/blog/1845572
(1)对于字符串变量来说,使用“==”和“equals()”方法比较字符串时,其比较方法不同。“==”比较两个变量本身的值,即两个对象在内存中的首地址。 “equals()”比较字符串中所包含的内容是否相同。 这个内容在上面部分已经列出。
(2)如果是基本类型比较,那么只能用==来比较,不能用equals 
(3)对于继承object类的,又实现了自定义equal(重写其equal),则是按自定义的equal执行,一般重写的就是重写成对其内容进行比较。一般api中继承object的类都已重写equal对内容进行比较。
public class Product {
    private String id;
    private String name;
    private double price;
	public Product(String id, String name, double price) {
		super();
		this.id = id;
		this.name = name;
		this.price = price;
	}
	@Override
	public String toString() {
		return id+"_"+name;      //这个地方化很重要,是整个类的返回值
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Product other = (Product) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}
	
}
下面是main方法调用
package a20151119;
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListDemo1 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//声明三个product对象
		Product product1=new Product("001", "数学", 10000);
		Product productX=new Product("001", "数学", 10000);
	    Product product2=new Product("002", "英语", 12000);
	    Product product3=new Product("003", "语文", 9000);
  	System.out.println(product1.equals(productX));ture
}
//如果不重新定义equal方法,最后一句代码,这个地方就为fals,因为equals比较的是地址,但是我们在现实生活中人为,id为001,数学这个是相同的对象,因此,按照实际情况,我们定义,只要id相同,就为相同对象,结果为true。


     

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值