01、String、Long源码解析

String

1、不变性

不变性指的是值一旦被初始化,就不能再被改变,如果值被修改了,就会生成新的类,内存的物理地址会被改变。

String之所以具有不变性的原因:

  • String类被final修饰,所有String类不能被继承,也就是说任何对String的操作方法都不会被继承覆写;
  • String中保存数据的是一个char类型的数组,该数组也被final修饰了,也就是说一旦给String变量赋值了,内存地址是绝对无法再被修改的,而且该数组被private修饰,外部无法访问到该数组变量,只能通过String类提供的方法获取值

如果我们自己的类也想是不可变的,可以模仿String,将类用final修饰,然后将类里面的变量用private和final修饰,外部只能通过我们提供的方法访问类变量

因为String具有不变性,所以String的大部分方法都会返回一个新的String

2、字符串乱码

字符串乱码的原因是不同的操作系统,默认的字符编码是不一样的,从而导致在本地运行正常,部署到服务器上出现乱码,解决办法是在需要用到字符编码的地方,统一指定使用UTF-8,例如可以使用String的getBytes和new String指定字符编码

3、相等判断

可以使用String的equals方法和equalsInnoreCase方法判断两个字符串是否相等,equalsIgnoreCase方法会忽略大小写

equals方法比较两个字符串是否相等的步骤:

  • ==比较两个字符串的内存地址是否相同,如果相同返回true
  • 用instanceof判断参数是否是String类型,如果不是,返回false
  • 将参数强转成String类型,判断两个字符串的长度是否相同,如果不相同,返回false
  • 将字符串转成字符数组,用while循环遍历数组,判断两个数组相同下标的字符是否相同,如果有一个不相同,返回false
  • 返回true

源码如下:

public boolean equals(Object anObject) {
  // 判断是否是同一个对象
  if (this == anObject) {
    return true;
  }
  // 检查参数是否是String类型
  if (anObject instanceof 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;
}

从String的equals的源码可以看出,逻辑非常清晰,完全是根据String的底层结构来编写的,我们实现自定义类的equals方法时也可以参考。

equalsInnoreCase方法忽略大小写比较字符是否相同,原理跟equals差不多,只不过是在循环比较每个字符时调用了toUpperCase方法,将字符都转成大写再进行比较

4、equals与==的区别

equals方法和==都是判断两个对象是否相等,区别是equals是使用自定义的逻辑判断两个对象是否相等,而==只是简单的判断两个对象的内存地址是否相等,如果没有重写equals方法,equals方法也是判断两个对象的内存地址是否相同,此时跟==是一样的,String重写了equals方法

public static void main(String[] args) {
  // 案例一
  String s1 = "123";
  String s2 = "123";
  System.out.println(s1 == s2); // true
  // 打印变量的内存地址
  System.out.println("s1: " + System.identityHashCode(s1) + "  s2: " + System.identityHashCode(s2)); // s1: 1347137144  s2: 1347137144

  // 案例二
  s1 = new String("123");
  s2 = new String("123");
  System.out.println(s1 == s2); // false
  // 打印变量的内存地址
  System.out.println("s1: " + System.identityHashCode(s1) + "  s2: " + System.identityHashCode(s2)); // s1: 997608398  s2: 1973336893
}

案例一之所以是true,是因为s1和s2的内存地址是相同的,原因是String对象被放进常量池里面了,所以s1和s2都指向了常量池里面的同一个对象

案例二之所以是false,是因为每new一个对象,都会重新创建对象

5、equals跟hashCode的关系

hashCode方法的作用是为了减少equals方法的调用次数,从而提高效率。
hashCode方法会生成类的哈希码,如果两个对象相同,那么这两个对象的哈希码一定相同,如果两个对象的哈希码相同,这两个对象不一定相同,根据这个原则,在比较两个对象是否相同时,先调用hashCode方法检查两个对象的哈希码是否相同,如果不相同那这两个对象肯定不同,如果哈希码相同,再调用equals方法判断两个对象是否真的相同(hashCode方法的开销和效率比equals方法小得多)。

但是,并不是所有的类都需要同时实现equals方法和hashCode方法的,只有在需要创建"类的散列表"中时hashCode方法才会跟equals方法有关系,在其他情况下,hashCode方法和equals方法是没有关系的。简单的说就是当我们自定义的对象会被放到HashSet、HashTable、HashMap等这些本质是散列表的结构中时,才会使用到HashCode方法,当把对象加进去时,会先调用HashCode方法检查对象是否存在,这种情况下,如果只重写了equals方法,equals是不会生效的,因为hashCode不相同(hashCode默认是内存地址),会直接认为是两个对象,而不会继续调用equals方法。所以建议重写了equals方法也同时重写hashCode方法

6、replace与replaceAll方法的区别

replace方法和replaceAll方法都可以实现string替换指定字符,区别是replace方法参数是字符串,而replaceAll参数可以是正则表达式

public static void main(String[] args) {
  String s1 = "123";
  String s2 = "123";

  System.out.println("replace: " + s1.replace("1", "0")); // 023
  System.out.println("replaceAll: " + s1.replaceAll("\\d", "0")); // 000
}

可以用replace和replaceAll方法实现删除字符串中指定字符的功能。

Long

1、Long缓存机制

Long缓存了从-128到127内所有的Long值,如果是这个范围内的Long值,就不会初始化,而是从缓存中拿。

public static void main(String[] args) {
  Long l1 = 12L;
  Long l2 = 12L;
  System.out.println("l1 == l2 : " + (l1 == l2) + "   l1.equals(l2): " + l1.equals(l2));
  l1 = new Long(12);
  l2 = new Long(12);
  System.out.println("l1 == l2 : " + (l1 == l2) + "   l1.equals(l2): " + l1.equals(l2));
  l1 = 128L;
  l2 = 128L;
  System.out.println("l1 == l2 : " + (l1 == l2) + "   l1.equals(l2): " + l1.equals(l2));
}

/**
* 运行结果:
* l1 == l2 : true   l1.equals(l2): true
* l1 == l2 : false   l1.equals(l2): true
* l1 == l2 : false   l1.equals(l2): true
*/

相关源码:

private static class LongCache {
  private LongCache(){}

  static final Long cache[] = new Long[-(-128) + 127 + 1];

  static {
    for(int i = 0; i < cache.length; i++)
      cache[i] = new Long(i - 128);
  }
}
2、valueOf与parseLong的区别

valueOf方法可以将数字字符串或数字转成Long类型,而parseLong方法只能将字符串转成Long类型,valueOf方法如果参数是数字,并且范围是在-128到127之间,会从Long缓存中拿值,从而减少资源开销;如果参数是字符串,valueOf底层也是调用parseLong方法。

// valueOf方法参数是long
public static Long valueOf(long l) {
  final int offset = 128;
  if (l >= -128 && l <= 127) { // will cache
    return LongCache.cache[(int)l + offset];
  }
  return new Long(l);
}

// valueOf方法参数是string
public static Long valueOf(String s) throws NumberFormatException {
  return Long.valueOf(parseLong(s, 10));
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值