相信大家在最初接触Java基础学习的时候,也只是跟着课本上的描述学习,知其然,不知所以然,要想成为一个Java老鸟,不仅要学会怎么用,也要知道为何这么用。在Java基础系列的博客中,我会列举一系列大家日常开发中只知道会用的,却不知道为何如此这么用的那些常用知识点。

1.1 一个简单的String例子,看看个人功底如何?

下面看一段代码片段:

public static void main(String[] args){String a = "a" + "b" + 1;String b = "ab1";System.out.println(a == b);
}

我们在日常开发中,对于字符串的比较是通过equals()方法进行比较,而在上面这段代码中却使用了==比较两个字符串,我们知道,在我们刚接触Java程序设计的时候,老师教我们比较两个字符串用等号是匹配不了的,那这段代码运行结果是TRUE还是FALSE呢?

运行结果:

true

这肯定会让大家很迷茫,难道我们老师的真理是错的吗?其实也不能怪老师,老师带进门,修行靠个人。

分析:

分析之前你需要知道==和equals的区别?==用于比较内存单元格上的内容,比较对象时就是比较两个对象的内存地址是否一样,对于基本类型:byte,short,int,float,long等,其实是比较它们的值是否相等;在默认情况下,不重写equals方法也是比较内存地址,String类之所以能够用equals来比较两个字符串的值,是因为它重写了equals方法。为何 a == b运行结果true,答案是编译时优化,a = "a" + "b" + 1,等号右侧全是常量,当编译器编译代码时,无需运行时知道右侧是什么值,直接将其编译成,a = "ab1",所以,a和b指向了同一个引用。为何要做如此优化呢?提高整体效率呗,能提前做的事就提前做,为何要等到要做的时候在做呢,各位,是不是呢?

补充例子,看看大家掌握的如何了?

private static String getA(){return "a";
}public static void main(String[] args){String a = "a";final String c = "a";String b = a + "b";String d = c + "b";String e = getA() + "b";String compare = "ab";System.out.println(b == compare);System.out.println(d == compare);System.out.println(e == compare);
}

1.2 使用“+”拼接字符串的误区

其实“+”在拼接少量的字符串的时候,效率比append()方法效率更高,String通过“+”拼接字符串的时候,如果拼接的是对象是常量,则会在编译时合并优化,在编译阶段就完成了,无需运行时;append()更适合去拼接大量的字符串。下面我们来看一段代码片段以及编译后的代码:

编译前:

public void sample(){String a = "a";String b = "b";String c = a + b + "f";
}

编译后:

public void sample(){String a = "a";String b = "b";StringBuilder temp = new StringBuilder();temp.append(a).append(b).append("f");String c = temp.toString();
}

我们接着看下使用“+”来拼接大量字符串会带来什么结果?

String a = "";
for(int i=0; i<10000; i++){a += i;
}

看下编译后,会怎样?

String a = "";
for(int i=0; i<10000; i++){StringBuilder temp = new StringBuilder();temp.append(a).append(i);a = temp.toString();
}

在这个循环的过程中,导致a引用的值越来越来,每次拼接都会产生一个临时变量,这个就会导致产生大量的临时垃圾,随着数量的增加,垃圾空间也会越来越大,可能会导致OOM,直接导致系统宕机或者僵死,大量的垃圾也会导致新生代堆内存不足频繁进行minor GC,当新生代中的对象转移至老年代,随着老年代内存空间被占满,会直接导致full GC,依次full GC时间持续之长,运行的系统性能极速下降。

总结:在开发过程知道拼接的全是常量或者少量拼接就可以使用"+",对于大量拼接请使用append()方法。

1.3 覆盖Object的equals方法

有的时候,我们重写了equals方法,却忘了重写hashCode()方法,可能导致我们找了好久才想起我们忘了重写这个方法了,白白浪费了时间。在每个覆盖了equals方法的类中,也必须覆盖其hashCode方法。如果不这么做的话,就违反了Object.hashCode通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括:HashMap,HashSet和Hashtable。

下面我们来看一下,关于Object规范对equals和hashCode的约定吧

  • 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个数字。
  • 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。

下面看一段代码:

public class Student{private String id;private String name;private int age;public Student(String id, String name,  int age){this.id = id;this.name = name;this.age = age;//setter和getter方法省略@Overridepublic boolean equals(Object o){if(o == this){return this;}if(o instanceof Student)Student stu = (Student) o;return o.getId().equals(id) && o.getName().equals(name)&& o.getAge() == age;}}

假设你企图将这个类与HashMap一起用:

Map<Student, String> stuMap = new HashMap<Student, String>;
stuMap.put("2010214139", "wangf", 26);

这时候,你可能期望stuMap.get(new Student("2010214139", "wangf", 26))会返回wangf,但它实际返回的是null。由于Student类没有覆盖HashCode方法,从而导致两个相等的实例具有不相等的散列码,违反了约定。只需为Student重写hashCode方法即可。

比如下面这种:

@Override
pulic int hashCode(){return 31;
}

虽然这种hashCode是合法的,但每次调用都返回相同的散列码,因此,每个对象都被映射到同一个散列桶中,使散列表退化为链表。一个好的散列函数通常倾向于"为相等的对象产生不相等的散列码"。如何写一个合法的hashCode呢?

  • 把某个非零的常数值,比如17,保存在名为result的int类型的变量中
  • 对于对象中每个关键域f,完成以下步骤:

1)如果该域是boolean,则计算f ? 1:0;

2)如果该域是byte、char、short或者int类型,则计算(int)f;

3)如果该域是long类型,则计算(int)(f^(f>>>32));

4)如果该域是float类型,则计算Float.floatToIntBits(f);

5)如果该域是double类型,则计算Double.doubleToLongBits(),然后按照步骤3)操作

6)如果该域是一个对象引用,并且该类的equals方法地跪地equals的方式来比较域,则同样为这个域递归的调用hashCode。如果这个域为null,则返回0。

7)如果该域是一个数组,则把每一个元素当做单独的域处理。

  • 按照下面的公式,把步骤2中计算得到的散列码合并到result,result = 31 * result + x;
  • 返回result

下面写出Student的hashCode方法:

@Override
public int hashCode(){int result = 17;result = 31 * result + id.hashCode();result = 31 * result + name.hashCode();result = 31 * result + age;return result;
}

1.4 自动装箱和自动拆箱

Java1.5发行版中增加了自动装箱和自动拆箱,基本类型和装箱类型有三个主要区别:

  1. 基本类型只有值,两个装箱基本类型可以具有相等的值和不同的同一性。
  2. 基本类型只有功能完备的值,而每个装箱基本类型除了它对应基本类型的所有功能之外,还有个非功能值null。
  3. 基本类型通常比装箱类型节省空间。

如果不小心这三点都会让你陷入麻烦中。

下面我们看下这个小程序:

public class Test{private static Integer i;public static void main(String[] args){if(i == 32){System.out.println("hello");}}
}

事实上它并没输出hello,而是抛出了NPE,问题在于i是Integer类型,而不是int基本类型,就像所有的对象引用域一样,它的初始值都是null。

最后,看下这段代码片段:

public static void main(String[] args){Long sum = 0;for(long i=0; i<Integer.MAX_VALUE;i++){sum +=i ;}System.out.println(sum);}

这个程序比预计的要慢一些,为什么呢?因为它把基本类型设计成了装箱类型,变量被反复的装箱和拆箱,导致性能明显下降。

核实使用装箱类型呢?答案有三点:

第一点:作为集合中的元素、键和值

第二点:在参数化类型中,必须使用装箱基本类型作为类型参数,比如ThradLocal<Integer>

第三点:在进行反射的方法调用时,必须使用装箱基本类型

知其然,知其所以然之Java基础系列(一)相关推荐

  1. 知行合一:知其然知其所以然

    知行合一:知其然知其所以然. 一个事情做成了,并不意味着你知道做成这件事背后蕴藏的原理. 只有使用冥思.事上练的方法了,将背后的原理提取出来,才能说做到了执行合一. 这个过程是行对知的精进过程. 转载 ...

  2. 夯实Java基础系列17:一文搞懂Java多线程使用方式、实现原理以及常见面试题

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  3. 夯实Java基础系列9:深入理解Class类和Object类

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  4. 夯实Java基础系列22:一文读懂Java序列化和反序列化

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  5. 夯实Java基础系列11:深入理解Java中的回调机制

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  6. 夯实Java基础系列14:深入理解Java枚举类

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  7. 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  8. 夯实Java基础系列19:一文搞懂Java集合类框架,以及常见面试题

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  9. 夯实Java基础系列18:深入理解Java内部类及其实现原理

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

最新文章

  1. 事务管理最佳实践全面解析
  2. C++ string的使用
  3. 如何分辨PoE工业交换机是否标准供电
  4. 关于IE6.7.8.FF兼容的问题
  5. Microsoft Edge 浏览器开始支持webkit私有样式
  6. [和管子对话] 2 2007-4-6/抽象类和接口的谁是谁非
  7. 印象笔记添加txt附件
  8. 求100~200间的所有素数
  9. docker学习记录 docker 脚本----redis,zookeeper,kafka(三)
  10. latex 表格 每列 分隔符
  11. 推荐几个.NET开源图表组件
  12. access设置 dolby_win10系统设置和安装新款杜比音效的方法
  13. Kali系统学习:弱点扫描工具NMAP实战演示
  14. 在Python中安装了graphvize还出现报错:ExecutableNotFound: failed to execute ‘dot‘, make sure the Graphviz execut
  15. python的help()用法
  16. 怎么样开启红米手机3S 3X的root超级权限
  17. 主宰互联网的超强10大算法!
  18. 一文归纳Ai调参炼丹之法
  19. incremental backups
  20. 论文中不带边框表格制作方法

热门文章

  1. 新旧版本谷歌浏览器自动播放问题
  2. c#中https通讯如何添加证书
  3. MCE公司:新型Kappa阿片受体拮抗剂和偏向性激动剂的发现
  4. 【通讯原理】Ch.4:数字信号调制
  5. java过滤ios表情,JS前端去掉emoji表情和Java后台处理emoji表情方法
  6. Sulley fuzzer learning
  7. java小组的队名,霸气小组名称口号大全
  8. java语言就业方向_Java就业方向有哪些?
  9. java数组初始化的方式_java数组初始化方式
  10. win7 下安装vs2010 pro 失败解决方法