java里的Object

一、Object对象

1.2 java创建对象的四种方式

java程序中对象的创建有四种方式:

● 调用new语句创建对象,最常见的一种

● 运用反射手段创建对象,调用java.lang.Class 或者 java.lang.reflect.Constructor 类的newInstance()实例方法

● 调用对象的clone()方法

● 运用序列化手段,调用java.io.ObjectInputStream 对象的 readObject()方法,其实就是一种深拷贝

@see https://www.cnblogs.com/avivahe/p/5702132.html

二、hashcode与equals

2.1 hashcode定义

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。
这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode() 定义在JDK的Object.java中,Java中的任何类都包含有
hashCode() 函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。
这其中就利用到了散列码!(可以快速找到所需要的对象)

让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。在Java中,哈希码代表对象的特征。

注意:有些朋友误以为默认情况下,hashCode返回的就是对象的存储地址,事实上这种看法是不全面的,确实有些JVM在实现时是直接返回对象的存储地址,但是大多时候并不是这样,只能说可能存储地址有一定关联。

hashCode方法的作用:

增加查询速度。快速判断对象是否不相等。

2.2 equals方法实现原理

先比较两个对象的hashCode,然后比较两个对象所指向的值

可以对照散列表的数据结构理解,hashcode值相当于桶的索引值,equals方法主要是判断在相同索引值下,遍历链表的值是否相同。

Object.equals方法实质就是判断对象的存储地址。

源码如下:

 public boolean equals(Object obj) {
        return (this == obj);
    }

String中的equals方法其实重写了Object.equals方法

源码如下:

public boolean equals(Object anObject) {
        if (this == anObject) {//先进行地址比较
            return true;
        }
 
        if (anObject instanceof String) {//接着判断是否为string类,下面为字符比较
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
 }
 

执行逻辑为:先判断对象存储地址是否相等,如果不等,判断对应的值,如果值相等则返回true。

故可以的得出如下结论:

	 如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;

  如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;

  如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;

  如果两个对象的hashcode值相等,则equals方法得到的结果未知。

或者:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同
3.两个对象不相同,他们的hashCode值不一定不相同。
4.两个对象的hashCode值不相同,他们一定是两个不同的对象      

在什么场景下需要重新实现hashcode和equals这两个方法。

1、加入到hashset中的自定义类的对象,为确保他们不重复,需要对他们的类重写equals()和hashcode()的方法。

如果不重写equals,相同内容不同引用的对象会被当做不同的对象被加入到hashset中。

2.在重写equals方法的同时,必须重写hashCode方法

两个对象相等,首先要满足其hashcode值相等,因为两个不同的对象很有可能共用一个hashcode值。然后equals方法来判断是否相等。只有两种同时满足才能确定他们两个对象是相等的。

注意:equals方法最初是在所有类的基类Object中进行定义的,源码是

在这里插入代码片

public boolean equals(Object obj) {
    return (this == obj);
    }

由equals的源码可以看出这里定义的equals与是等效的(Object类中的equals没什么区别),不同的原因就在于有些类(像String、Integer等类)对equals进行了重写,但是没有对equals进行重写的类(比如我们自己写的类)就只能从Object类中继承equals方法,其equals方法与就也是等效的,除非我们在此类中重写equals。

@see https://www.cnblogs.com/zjc950516/p/7877511.html

思考

1.java 比较大小的坑和总结

  1. “== ”用于判断的是对象的内存地址
public class ArrayTest {

    public static void main(String[] args){

        String a = new String("aw");
        String b = new String("aw");
        System.out.println(a==b);//false
        System.out.println(a.equals(b));//true
    }
}

显然,尽管 a 与 b 对象的值相同,但是在内存中的地址是不同的,即hashcode不同,所以两个对象是不一样的。但用equals是返回的true。

再看一个例子:

public class ArrayTest {

    public static void main(String[] args){

         String a = new String("aw");
        String b = new String("aw");
        String c= "aa";
        String d= "aa";
        System.out.println(a.hashCode());
        System.out.println(b.hashCode());
        System.out.println(c.hashCode());
        System.out.println(d.hashCode());
        System.out.println(a==b);//false
        System.out.println(c==d);//true
    }
}

运行结果为

3126
3126
3104
3104
false
true

可见两个对象不相同,他们的hashCode值不一定不相同。

2.数字大小的比较最好用compareTo而不是equals

 		Long num = 1L;
        Integer num2 = 1;
        boolean b = num.compareTo(num2.longValue()) == 0;//true
        boolean obj = Objects.equals(num2.intValue(),num.longValue());//false
        System.out.println(Objects.equals(num,num2));//false
        boolean equals = num2.intValue() == num.longValue();//true

2.解决hash冲突的几种方法

1.链表法
链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位,如Java中的HashMap

2,不易探测到整个散列表的所有空间(线性探测法除外,但线性探测会出现堆积)

2.开放地址法

开放地址法有个非常关键的特征,就是所有输入的元素全部存放在哈希表里,也就是说,位桶的实现是不需要任何的链表来实现的,换句话说,也就是这个哈希表的装载因子不会超过1。它的实现是在插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法(探查序列),去寻找下一个地址,若发生冲突再去寻找,直至找到一个为空的地址为止。所以这种方法又称为再散列法。

有几种常用的探查序列的方法:

①线性探查

dii=1,2,3,…,m-1;这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

(使用例子:ThreadLocal里面的ThreadLocalMap)

②二次探查

di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 );这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

③ 伪随机探测

di=伪随机数序列;具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),生成一个位随机序列,并给定一个随机数做起点,每次去加上这个伪随机数++就可以了。

缺点:1,删除工作很困难,假如要从哈希表 HT 中删除一个记录,应将这个记录所在位置置为空,但我们只能标上已被删除的标记,否则,将会影响以后的查找。

2,不易探测到整个散列表的所有空间(线性探测法除外,但线性探测会出现堆积)

3.再散列法

再散列法其实很简单,就是再使用哈希函数去散列一个输入的时候,输出是同一个位置就再次散列,直至不发生冲突位置

缺点:每次冲突都要重新散列,计算时间增加。

4.公共溢出区法
散列表由两个一维数组组成,一个称为基本表,它实际上就是一个散列表。另外一个称为溢出表。插入首先在基本表上进行,假如发生冲突,则将同义词存入溢出表。这样,可以保证基本表不会发生“堆积”

PS:基本表是不会发生堆积了,那溢出表呢?当进行查找时,查找到溢出表,这是不是又开启了新一轮的冲突解决?

@see 【java基础 10】hash算法冲突解决方法 https://www.cnblogs.com/hhx626/p/7534618.html

3.为什么这些操作线程的方法要定义在object类中呢?

答:因为加锁和解锁都是基于对象来的,而锁的信息是存在对象中的一个markword信息里面的,所以要定义在Object类里。

3 静态变量、实例变量、局部变量与线程安全

1.静态变量:线程非安全。

静态变量表示所有实例共享的一个属性,位于方法区,共享一份内存,而成员变量是对象的特殊描述,不同对象的实例变量被分配在不同的内存空间,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。

2.实例变量:单例模式(只有一个对象实例存在)线程非安全,非单例线程安全。

实例变量为对象实例私有,在虚拟机的堆中分配,若在系统中只存在一个此对象的实例,在多线程环境下,被某个线程修改后,其他线程对修改均可见,故线程非安全;

如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变 量的修改将互不影响,故线程安全。

3.局部变量:线程安全。

局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放。由于每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。

@see 静态变量、实例变量、局部变量与线程安全 https://www.cnblogs.com/tonyluis/p/5549149.html

参考资料

1.浅谈Java中的hashcode方法 https://www.cnblogs.com/dolphin0520/p/3681042.html
2.[转]Java 的强引用、弱引用、软引用、虚引用 http://www.cnblogs.com/gudi/p/6403953.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值