码出高效:Java开发手册-第2章(13)

简介: 本章开始讲解面向对象思想,并以Java 为载体讲述面向对象思想在具体编程语言中的运用与实践。当前主流的编程语言有50 种左右,主要分为两大阵营:面向对象编程与面向过程编程。面向对象编程(Object-Oriented Programming,OOP)是划时代的编程思想变革,推动了高级语言的快速发展和工业化进程。OOP 的抽象、封装、继承、多态的理念使软件大规模化成为可能,有效地降低了软件开发成本、维护成本和复用成本。面向对象编程思想完全不同于传统的面向过程编程思想,使大型软件的开发就像搭积木一样隔离可控、高效简单,是当今编程领域的一股势不可......

2.7.2 包装类型

      前8种基本数据类型都有相应的包装类,因为Java 的设计理念是一切皆是对象,在很多情况下,需要以对象的形式操作,比如hashCode() 获取哈希值,或者getClass() 获取类等。包装类的存在解决了基本数据类型无法做到的事情:泛型类型参数、序列化、类型转换、高频区间数据缓存。尤其是最后一项,我们都知道Integer会缓存-128~127 之间的值,对于Integer var=? 在-128~127 之间的赋值,Integer 对象由IntegerCache.cache 产生,会复用已有对象,这个区间内的Integer 值可以直接使用== 进行判断,但是这个区间之外的所有数据都会在堆上产生,并不会复用已有对象,这是一个大问题。因此,推荐所有包装类对象之间值的比较,全部使用equals() 方法。

      事实上,除Float 和Double 外,其他包装数据类型都会缓存,6 个包装类直接赋值时,就是调用对应包装类的静态工厂方法valueOf(),以Integer 为例,源码如下:

@HotSpotIntrinsicCandidate

public static Integer valueOf(int i) {

      if (i >= IntegerCache.low && i <= IntegerCache.high)

         return IntegerCache.cache[i + (-IntegerCache.low)];

      return new Integer(i);

}

      如上源代码,赋值数据i 在缓存区间内直接返回缓存中的Integer 对象,否则就会new 一个对象。在JDK9 直接把new 的构造方法过时,推荐使用valueOf(),合理利用缓存,提升程序性能。各个包装类的缓存区间如下:

  • Boolean:使用静态 final 变量定义,valueOf() 就是返回这两个静态值。
  • Byte:表示范围是-128~127,全部缓存。
  • Short:表示范围是-32768~32767,缓存范围是 -128~127。
  • Character:表示范围是 0~65535,缓存范围是 0~127。
  • Long:表示范围是 [-263, 263-1],缓存范围是 -128~127。
  • Integer:表示范围是 [-231, 231-1]。最后详细介绍 Integer,因为它是 Java 数据世界里应用最广的数据类型,缓存范围是-128~127。但它是唯一可以修改缓存范围的包装类,在VM options 加入参数-XX:AutoBoxCacheMax=7777,即可设置最大缓存值为7777,示例代码如下:

public class LongIntegerCacheTest {

      public static void main(String[] args) {

             Long a = 127L;

             Long b = 127L;

             System.out.println("Long max cached value is 127, "

             + "and the result is:" + (a == b));

             Long a1 = 128L;

             Long b1 = 128L;

             System.out.println("Long=128 cache is " + (a1 == b1));

             Long c = -128L;

             Long d = -128L;

             System.out.println("Long min cached value is -128, "

             + "and the result is:" + (c == d));

             Long c1 = -129L;

             Long d1 = -129L;

             System.out.println("Long=-129 cache is " + (c1 == d1));

             // Long 类型只缓存-128 ~ 127 之间的数值

             Long e = 1000L;

             Long f = 1000L;

             System.out.println("Long=1000 is " + (e == f));

             // JVM AutoBoxCacheMax 只对Integer 对象有效

             Integer x = 1001;

             Integer y = 1001;

             System.out.println("Integer=1001 is " + (x == y));

      }

}

执行结果如下:

Long max cached value is 127, and the result is:true

Long=128 cache is false

Long min cached value is -128, and the result is:true

Long=-129 cache is false

Long=1000 is false

Integer=1001 is true


      该例很好地说明了Long 只是缓存了-128~127 之间的值,而1000L 没有被缓存;在将Integer 最大缓存值改为7777 后,1001 被成功缓存。合理掌握包装类的缓存策略,防止遇到问题是一个方面,使自己的程序性能最大化,更是程序员的情怀所在。在选择使用包装类还是基本数据类型时,推荐使用如下方式:

   (1) 所有的POJO 类属性必须使用包装数据类型。

      (2) RPC 方法的返回值和参数必须使用包装数据类型。

      (3) 所有的局部变量推荐使用基本数据类型。

      与包装类型的相关故障并不鲜见,在淘宝某个团队,曾经出现某些客户的产品页无法打开,存在账号问题。通过预发debug,发现这些正常的账号在执行下面一段逻辑的时候,代码执行结果不符合预期。

public static MemberStatus getStatusFromSuspend(Integer suspend) {

      if(suspend == Integer.valueOf(1) || suspend == Integer.valueOf(2)) {

           return ENABLED;

      }

      ...

}

      suspend 是通过RPC 框架接口调用返回的,而反序列化是new 一个对象的,同一个值在== 判断时,一边是new 出来的新对象,另一边是缓存中已有对象,两者并不相等,导致故障。

2.7.3 字符串

      字符串类型是常用的数据类型,它在JVM 中的地位并不比基本数据类型低,JVM 对字符串也做了特殊处理。String 就像是流落到基本数据类型部落的一个副首领,虽然很神气,但是终归难以得到族人对它的认同,毕竟它是堆上分配来的。

      字符串相关类型主要有三种:String、StringBuilder、StringBuffer。String 是只读字符串,典型的immutable 对象,对它的任何改动,其实都是创建一个新对象,再把引用指向该对象。String 对象赋值操作后,会在常量池中进行缓存,如果下次申请创建对象时,缓存中已经存在,则直接返回相应引用给创建者。而StringBuffer 则可以在原对象上进行修改,是线程安全的。JDK5 引入的StringBuilder 与StringBuffer 均继承自AbstractStringBuilder,两个子类的很多方法都是通过“super. 方法()”的方式调用抽象父类中的方法,此抽象类在内部与String 一样,也是以字符数组的形式存储字符串的。StringBuilder 是非线程安全的,把是否需要进行多线程加锁交给工程师决定,操作效率比StringBuffer 高。线程安全的对象先产生是因为计算机的发展总是从单线程到多线程,从单机到分布式。

      String t1 = new String(“abc”) 与String t2 = “abc”,用== 判断是否相同呢,答案是不同的。如果调用t1.intern() 方法,则是相同的。

      在非基本数据类型的对象中,String 是仅支持直接相加操作的对象。这样操作比较方便,但在循环体内,字符串的连接方式应该使用StringBuilder 的append 方法进行扩展。如下的方式是不推荐的:

String str = "start";

for (int i = 0; i < 100; i++) {

      str = str + "hello";

}

      此段代码的内部实现逻辑是每次循环都会new 一个StringBuilder 对象,然后进行append 操作,最后通过toString 方法返回String 对象,不但造成了内存资源浪费,而且性能更差。

相关文章
|
2月前
|
设计模式 安全 Java
面向对象编程的精髓:Java设计模式 - 原型模式(Prototype)完全参考手册
【4月更文挑战第7天】原型模式是OOP中的创建型设计模式,用于通过复制现有实例创建新实例,尤其适用于创建成本高或依赖其他对象的情况。它包括Prototype接口、ConcretePrototype实现和Client客户端角色。优点是性能优化、避免子类化和动态增加产品族。实现包括定义原型接口、实现具体原型和客户端调用克隆方法。最佳实践涉及确保克隆正确性、选择深拷贝或浅拷贝及考虑线程安全。但需注意克隆方法管理、性能开销和循环引用等问题。在Java中,实现Cloneable接口和覆盖clone方法可实现原型模式。
|
2月前
|
算法 Java
「译文」Java 垃圾收集参考手册(四):Serial GC
「译文」Java 垃圾收集参考手册(四):Serial GC
|
2月前
|
算法 Java PHP
「译文」Java 垃圾收集参考手册(一):垃圾收集简介
「译文」Java 垃圾收集参考手册(一):垃圾收集简介
|
1月前
|
Java 开发者
【技术成长日记】Java 线程的自我修养:从新手到大师的生命周期修炼手册!
【6月更文挑战第19天】Java线程之旅,从新手到大师的进阶之路:始于创建线程的懵懂,理解就绪与运行状态的成长,克服同步难题的进阶,至洞悉生命周期的精通。通过实例,展示线程的创建、运行与同步,展现技能的不断提升与升华。
|
26天前
|
Java 数据安全/隐私保护
Java基础手册二(类和对象 对象创建和使用 面向对象封装性 构造方法与参数传递 this关键字 static关键字 继承 多态 方法覆盖 final关键字 访问控制权限修饰符)
Java基础手册二(类和对象 对象创建和使用 面向对象封装性 构造方法与参数传递 this关键字 static关键字 继承 多态 方法覆盖 final关键字 访问控制权限修饰符)
20 0
|
26天前
|
存储 Java
Java基础手册(标识符 关键字 字面值 变量 数据类型 字符编码 运算符 控制语句 方法及方法重载和递归 面向对象与面向过程)
Java基础手册(标识符 关键字 字面值 变量 数据类型 字符编码 运算符 控制语句 方法及方法重载和递归 面向对象与面向过程)
23 0
|
28天前
|
存储 自然语言处理 Java
Java IO流完全手册:字节流和字符流的常见应用场景分析!
【6月更文挑战第26天】Java IO流涵盖字节流和字符流,字节流用于二进制文件读写及网络通信,如图片和音频处理;字符流适用于文本文件操作,支持多语言编码,确保文本正确性。在处理数据时,根据内容类型选择合适的流至关重要。
|
2月前
|
Java 开发者
效率工具RunFlow完全手册之Java开发者篇
这篇手册面向Java开发者,介绍了如何使用QLExpress执行Java代码和进行方法验证。示例包括数学计算、读取系统环境变量及格式化输出。QLExpress提供运行时执行Java代码的功能,并支持QLExpress语法的脚本文件。
30 3
效率工具RunFlow完全手册之Java开发者篇
|
2月前
|
Java
Java开发手册之控制语句,2024最新Java笔经
Java开发手册之控制语句,2024最新Java笔经
|
2月前
|
算法 安全 Java
「译文」Java 垃圾收集参考手册(三):GC 算法基础篇
「译文」Java 垃圾收集参考手册(三):GC 算法基础篇