`

Java学习系列(二十六)Java代码优化讲解

 
阅读更多

转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/45506549

 

在开篇之前,先补充一下《Java学习系列》里面的instanceof关键字的使用及其陷阱。简要说明:instanceof是一个简单的二元操作符,它是用来判断一个对象是否为一个类的实例。只要instanceof左右操作数有继承或实现的关系,程序都是可以编译通过的。下面通过一个简单实例来说明一下instanceof关键字的使用及其陷阱:

 

class A<T> {
	public boolean isDateInstance(T t) {
		return t instanceof Date;
	}
}

public class InstanceofTest {

	public static void main(String[] args) {
		// true。一个String对象是Object实例(java中Object是所有类的父类)
		System.out.println("zhangsan" instanceof Object);
		// false。Object是父类,它的对象明显不是String类的实例
		System.out.println(new Object() instanceof String);
		// true。一个String对象是String的实例
		System.out.println(new String() instanceof String);
		// 编译不能通过。'a' 为一个char类型,即基本类型
		System.out.println('a' instanceof Character);
		// false。只要左操作数为null(本质是无类型),那么结果就直接返回false
		System.out.println(null instanceof String);
		// false。即使将null强转也还是个null
		System.out.println((String) null instanceof String);
		// 编译不能通过。因为Date和String并没有继承或实现关系
		System.out.println(new Date() instanceof String);
		// false。在编译成字节码时,T已经是Object类型了,由于传递了一个"lisi"实参字符串,所以T实际是String类型了。
		System.out.println(new A().isDateInstance("lisi"));
		List<String> list = new ArrayList<String>();
		// 编译不能通过。instanceof不允许存在泛型参数。
		System.out.println(list instanceof List<String>);
	}
}

 

【注意】instanceof只能用于对象的判断,不能用于基本类型的判断。

 

下面开始正式进入主题,先从一个自增的陷阱开始吧。

1)自增的陷阱

 

int num = 0;
for (int i = 0; i < 100; i++) {
	num = num++;
}
System.out.println("num = " + num);

打印结果是什么呢?答案是0,为什么呢?先看看执行步骤吧,程序第一次循环时的详细步骤如下:JVM把num值(0)拷贝到临时变量区,然后num值加1,这是num的值为1,接着返回临时变量区的值,注意这个值是1没修改过,最后将返回值赋给num,此时num的值被重置为了0。简单说来就是int temp = num; num += 1; return temp;这3步。所以打印结果还是0,num始终保持着原来的状态。

 

优化:将num=num++; 修改为num++即可。

 

2)常量竟成变量?

大家想想,常量有可能成为变量吗?答案是有可能,只不过这种做法是不被认同的。

 

public static final int RAND_CONST = new Random().nextInt();

public static void main(String[] args) {
	// 通过打印几次,可以看到结果变了,也就是说常量在定义的时候就没有保证它的值运行期保持不变
	System.out.println("常量变了吗?" + RAND_CONST);
}

优化建议:务必常量的值在运行期保持不变,所以可以让RAND_CONST在定义时直接赋值写死。

 

 

3)“l” 你能看出这个字母是i的大写、数字1还是字母l的小写?

 

public static  long l = 11;


优化:字母后缀l尽量大写L

 

 

4)三目运算符的类型不一致?

 

int i = 70;
System.out.println(i < 100 ? 80 : 90.0);

打印结果出人意料,结果竟然为80.0,这是为什么呢?i<100确实为true,但由于最后一个操作数为90.0,是一个浮点数,这时编译器会将第二个操作数80转为80.0浮点数,统一结果类型,所以打印结果为80.0。
优化:90.0改为90

 

5)不要重载含有变长参数的方法

 

简要说明:变长参数必须是方法的最后一个参数,且一个方法不能定义多个变长参数。

 

public class Test01 {
	public static void fruitPrice(int price, int discount) {
		float realPrice = price * discount / 100.0F;
		System.out.println("非变长参数得出的结果:realPrice = " + realPrice);
	}

	public static void fruitPrice(int price, int... discounts) {
		float realPrice = price;
		for (int discount : discounts) {
			realPrice = price * discount / 100.0F;
		}
		System.out.println("变长参数得出的结果:realPrice = " + realPrice);
	}

	public static void main(String[] args) {
		fruitPrice(48888, 85);
	}
}

打印结果是什么呢?答案是:非变长参数得出的结果:realPrice = 41554.8,也就是程序执行的是第一个方法,而没有执行变长参数方法,这是为什么呢?因为Java在编译时,首先会根据实参的数量和类型(这里是2个都是int类型的实参,注意没有转成int数组)来进行处理,也就是找到fruitPrice(int price, int discount)方法,而且确认它符合方法签名条件,由于编译器也爱“偷懒”,所以程序会执行第一个方法。再看一个:

 

public class Test02 {
	public void method1(String str, Integer... integers) {
		System.out.println("变长参数类型为Integer的方法被调用...");
	}

	public void method1(String str, String... strs) {
		System.out.println("变长参数类型为String的方法被调用...");
	}

	public static void main(String[] args) {
		Test02 t = new Test02();
		// 编译不通过。虽然两个方法都符合要求,但编译器并不知道调用哪一个,于是就报错了。
		t.method1("test02");
		// 编译不通过。因为[直接量null是没有类型的],理由同上。
		t.method1("test02", null);
	}

}

 

对于t.method("test02",null);如果我们提前声明String[] strs = null或者Integer[] ints = null;也就是让编译器知道这个null是String或者Integer类型的,那么就可以通过编译了。

6)慎用静态导入

这点比较容易理解,因为静态导入的作用是将某个类的类成员(静态变量、静态方法)引入到本类中,而如果此时刚好本类中也有同名的类成员,那么这样便可能产生混淆,后面维护起来也比较麻烦。

优化:类型.类成员

7) 不要让类型默默转换

 

public class Test03 {
	// 光速为30万公公里
	public static final int LIGHT_SPEED = 30 * 10000 * 1000;

	public static void main(String[] args) {
		long distance = 8 * 60 * LIGHT_SPEED;
		// 打印结果(为负数):地球与太阳的距离为:-2028888064
		System.out.println("地球与太阳的距离为:" + distance);
	}
}

为什么是负数呢?这是因为Java是先运算再进行类型转换的。distance的3个运算参数都是int类型,三者结果相等虽然也是int类型,但已经超过了int取值的最大范围,所以为负数,这样再转为long型,结果仍是负数。解决方案:long distance = 1L * 8 * 60 * LIGHT_SPEED;1L是个长整型,右边等式类型自动升级,计算出来的结果也是长整型。

 

优化:基本类型转换时,最好使用主动声明的方式参与运算。

8)包装类性值为null?

 

 

public static void main(String[] args) {
	List<Integer> list = new ArrayList<Integer>();
	// 自动装箱(基本类型转为包装类型)。装箱过程是调用valueOf方法实现的。
	list.add(1);
	list.add(2);
	list.add(null);
	// 自动拆箱(包装类型转为基本类型)。拆箱过程默认调用包装对象的intValue方法实现的。
	int count = 0;
	for (int item : list) {
		count += item;
	}
	System.out.println("count = " + count);
}

运行结果报异常java.lang.NullPointerException。原因很简单:拆箱过程默认调用包装对象的intValue方法实现的,由于包装类是null值,所以就报空指针异常了。解决方案:

 

 

for (Integer item : list) {
	count += (item == null) ? 0 : item;
}

 

优化:包装类型参与运算时,要做null校验。

9)让工具类不可实例化

工具类的方法和属性都是静态的,不需要生成实例即可访问,而且其类成员在内存中只有一份拷贝,jdk也做了很好的处理。由于不希望被初始化,于是就设置其构造函数为私有(private)访问权限。

 

public class UtilClass {
	// 构造器私有化
	private UtilClass() {

	}
}

但这样有个问题,就是在工具类里面可能方法很多,无意间new了一个新的对象,一时间也没有发现。这样就没有达到真正不需要生成实例的目的。

 

优化:使用工具类时,要保证所有的访问都是通过类名进行的。

public class UtilClass {
	// 构造器私有化
	private UtilClass() {
		throw new Error("please don't instantial this util class...");
	}
}

 

10)不要在循环条件中带有计算

如果在循环(for、while等)条件中计算,则每次循环都得计算一遍,这样就会降低,例如:

 

while (n < count * 2) {
	//...
}

优化:将while里面的运算提取即可

int total = count * 2;
while (n < total) {
	//...
}

11)不要主动进行垃圾回收

 

尽量不要调用System.gc();来主动对垃圾进行回收。因为System.gc它会停止所有响应,才能检查内存中是否有可回收的对象。把所有对象都检查一遍,然后处理掉那些垃圾对象。这对一个应用系统来说风险极大,如果是一个web项目,调用System.gc它会让所有的请求都暂停,等待垃圾回收器执行完毕(可能会严重影响正常业务运行),如果web项目里面对象很多,那么System.gc执行的时间会非常耗时,所以最好不要主动进行垃圾回收。

 

12)静态变量一定要先声明后赋值(或使用)

 

 

public class Test01 {
	static {
		num = 20;
	}
	public static int num = 2;

	public static void main(String[] args) {
		System.out.println(num);
	}
}

大家想想,结果是多少呢?打印结果是:2。为什么呢?这是因为静态变量(类变量)是类加载时被分配到数据区,它在内存中只有一份拷贝,详细说来就是:静态变量是在类初始化时首先被加载的,而JVM会去查找类中所有的静态声明,然后分配地址空间(此时还没有赋值),之后JVM会根据类中静态赋值(包括静态类赋值和静态代码块赋值)的先后顺序来执行

 

优化:静态变量先声明后使用。

 

补充1——字符串常量池

 

大家都知道,Java中的对象是保存在堆内存中的,但是字符串(常量)池非常特殊,它在编译期就已经决定了其存在JVM的常量池中,垃圾回收器是不会对它进行回收的。它的创建机制是这样的:创建一个字符串时,首先检查池中是否有字符序列相等的字符串,如果有则不再创建,直接返回池中该对象的引用;若没有则创建之,然后放入池中并返回创建对象的引用。下面看一个实例:

 

public class Test {
	public static void main(String[] args) {
		String str1 = "java代码优化";
		String str2 = "java代码优化";
		String str3 = new String("java代码优化");
		String str4 = str3.intern();

		System.out.println(str1 == str2);
		System.out.println(str1 == str3);
		System.out.println(str1 == str4);
	}
}

结果是什么呢?答案是true、false、true。解析:创建第一个字符串"java代码优化"时,首先检查字符串池中是否有该对象,发现没有,于是就创建第一个"java代码优化"这个字符串并放入池中,待再创建str2字符串时,由于池中已经有了该字符串,于是就直接返回了该对象的引用,此时str1与str2指向的是同一个地址,所有str1==str2返回true。而new String("java代码优化")声明的是一个String对象,是不检查字符串池,也不会把对象放入池中,那当然返回false了。而使用intern方法为什么会返回true呢?因为intern会检查当前对象在池中是否有字符序列相等的引用对象,如果有则返回true,如果没有则返回false。

 

优化建议:若没有特殊要求,推荐使用String直接量赋值

 

补充2——String、StringBuffer(线程安全)、StringBuilder(线程不安全)的使用场景

 

①String的使用场景:在字符串不经常变化的场景中使用String类,例如常量的声明、少量的变量运算等。

②StringBuffer的使用场景:在频繁进行字符串运算(如:字符串拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用StringBuffer,例如:XML解析、HTTP参数解析和封装等。

③StringBuilder的使用场景:在频繁进行字符串运算(如:字符串拼接、替换、删除等),并且运行在单线程环境中,则可以考虑使用StringBuilder,例如:SQL语句的封装、JSON封装等。

 

参考文献:《编写高质量代码》

 

 

 

分享到:
评论

相关推荐

    Java应用大赛作品代码讲解

    此外,代码优化和性能调优也是讲解的重点,包括内存管理、减少冗余计算、提高算法效率等。 总之,这个Java应用大赛的作品不仅涵盖了J2ME和Java SE的核心技术,还体现了良好的软件工程实践,如模块化设计、异常处理...

    深入Java集合学习系列

    "深入Java集合学习系列:HashSet的实现原理 - 莫等闲 - ITeye技术网站.mht"会讲解HashSet如何避免元素重复以及它的性能特点。 最后,LinkedHashSet结合了HashSet的特性与LinkedHashMap的顺序特性,它维护了元素的...

    java优化编程 java优化编程 java优化编程

    1. **代码优化**:编写高效的Java代码是优化的基础。这包括避免冗余计算,减少不必要的对象创建,使用适当的数据结构和算法,以及遵循Java编程的最佳实践,如有效使用StringBuilder而非String进行字符串拼接,避免...

    JAVA代码+PPT+实例

    通过结合PPT的讲解和实际代码的实践,学习者可以从理论到实践全面掌握Java编程。在学习过程中,建议先了解PPT中的概念,然后通过实例代码加深理解和应用。同时,不断实践和调试代码,将有助于巩固所学知识,并提升...

    Java代码与架构之完美优化配套课件.rar

    通过学习这套【Java代码与架构之完美优化】课件,开发者不仅能够掌握Java编程的深度技巧,还能提升整体架构设计能力,从而在实际项目中实现更高效、更稳定、更易于维护的代码和系统。配合配套的课件,理论结合实践,...

    java雷霆战机源码+视频讲解

    通过对比这两个版本的代码差异,开发者可以学习到软件迭代和优化的过程,理解bug修复、功能添加和性能提升的方法。 "视频地址.txt"文件则很可能包含指向视频教程的链接,这将使得学习者可以直接访问到教学内容,...

    JavaME源程序代码

    这个标题“JavaME源程序代码”指的是包含了一系列用于JavaME平台的原始编程代码,可能是用于教学、实践或者示例项目的代码集合。 在描述中,“JavaME源程序代码”的重复提及强调了内容的核心,即这些代码是专门为...

    java开发实例讲解

    了解 Java 的开发过程不仅有助于新手入门,同时也对有经验的开发人员在优化开发流程、提高代码质量方面有着不可忽视的作用。《java开发实例讲解》一书,正是以实例为基础,深入浅出地向读者展示 Java 程序从编码到...

    java语言0_1背包源代码+PPT讲解

    6. **源代码解析**:逐行解释Java源代码的功能,可能包括类的定义、方法的设计以及关键逻辑的实现。 7. **实例演示**:通过具体的例子,演示0-1背包问题的求解过程,帮助理解算法的实际运行情况。 8. **优化技巧**:...

    Javapuzzler+JAVA解惑 中英双语 完整源代码

    总之,这份压缩包文件为Java开发者提供了一个丰富的学习资源库,不仅有深入的理论解析,还有实践操作的源代码,对于提升Java编程技巧、理解语言细节和优化代码质量都有显著效果。无论你是Java新手还是经验丰富的...

    狂神说java系列笔记(java基础+javaweb+ssm+微服务)全套

    【标题】:“狂神说java系列笔记”涵盖了Java的基础、Web开发、SSM框架以及微服务等关键领域,是一套全面深入的Java学习资源。它旨在帮助初学者和进阶者掌握Java编程语言的核心概念,以及在实际项目开发中的应用。 ...

    java学习笔记markdown

    9. **JVM内部机制**:简述Java虚拟机的工作原理,包括类加载、内存管理、垃圾回收机制,有助于优化代码性能。 10. **泛型**:讨论Java泛型的使用,如何编写类型安全的代码,并理解其类型擦除的原理。 11. **枚举与...

    JAVA学习笔记 林信良

    在内存管理和垃圾回收方面,书中有专门章节讲解Java的内存模型以及自动内存管理机制,解释了如何避免内存泄漏和理解引用类型,这对于优化程序性能和解决内存问题非常有帮助。 书中还详细阐述了异常处理,这是Java...

    Java学习指南第四版下册

    总之,《Java学习指南第四版下册》是Java学习者的宝贵资源,它不仅讲解了Java语言的基础,还深入探讨了高级特性和实践技巧,助你在Java编程世界中游刃有余。通过阅读并实践书中的例子,你将能够提升自己的Java编程...

    深入JAVA虚拟机第二版+随书代码

    《深入JAVA虚拟机第二版》是一本深受Java开发者喜爱的经典著作,它详尽地剖析了Java虚拟机(JVM)的工作原理,为程序员提供了深入了解Java平台核心技术的机会。这本书结合了理论与实践,不仅讲解了JVM的内部机制,还...

    Java学习利器-JAVA解惑

    然而,学习Java的过程中,初学者或经验丰富的开发者都可能会遇到一些棘手的问题,这就是"Java学习利器-JAVA解惑"想要解决的问题。 这份名为"JAVA解惑"的PDF文档,可能是由一位经验丰富的Java开发者或者教育者编写的...

    5本java学习用书

    书中列举了23个编程实践,讲解如何编写高效、可维护的Java代码。例如,它涵盖了枚举优于常量、避免使用原始类型、优先考虑泛型、以及何时使用静态工厂方法而非构造函数等主题。 3. **《Thinking in Java》**:Bruce...

    自己动手写Java虚拟机 (Java核心技术系列)@

    本书摒弃了传统的以解读枯燥的Java虚拟机规范文档和分析繁琐的Java虚拟机源代码的方式来讲解Java虚拟机,取而代之的是,以实践的方式,引导读者如何从零开始构建和实现一个Java虚拟机,整个过程不仅能让读者做到对...

    Java学习、面试必备

    - Java性能优化:如何通过调整JVM参数、代码优化等方式提升系统性能。 - 架构设计:讨论微服务、SOA等架构设计原则和实践。 4. **实战经验** - Web开发:如使用Spring Boot、Struts、Hibernate等框架进行Web...

    JAVA项目开发实践(第二版) 书中源代码

    总的来说,《JAVA项目开发实践(第二版)》不仅提供了一套完整的源代码库,而且是一个全面的学习平台,读者可以通过它深入学习Java编程,并逐步提升自己的项目开发能力。无论是初学者还是经验丰富的开发者,都能从中...

Global site tag (gtag.js) - Google Analytics