Java常见的坑(二)

简介: 你猜上述程序输出的是什么? 是 ABC easy as 123 吗?你执行了输出操作,你才发现输出的是 ABC easy as [C@6e8cf4c6 ,这么一串丑陋的数字是什么鬼?实际上我们知道字符串与任何数值的相加都会变为字符串,上述事例也不例外,numbers输出其实实际上是调用了Object.toString()方法,让numbers转变为'[c' + '@' + 无符号的十六进制数。

   解惑六、ABC

1public class SolutionTwelve {
 2
 3    public static void main(String[] args) {
 4
 5        String letters = "ABC";
 6        char[] numbers = {'1','2','3'};
 7        // 能够直接输出
 8        System.out.println(letters + " easy as " + numbers);
 9
10    }
11}

你猜上述程序输出的是什么? 是 ABC easy as 123 吗?

你执行了输出操作,你才发现输出的是 ABC easy as [C@6e8cf4c6 ,这么一串丑陋的数字是什么鬼?

实际上我们知道字符串与任何数值的相加都会变为字符串,上述事例也不例外,

numbers输出其实实际上是调用了Object.toString()方法,让numbers转变为'[c' + '@' + 无符号的十六进制数。

改观

那么既然知道了原因,该如何去解决这个问题才是我们应该考虑的问题,恰好String 类为我们提供了String.valueOf(char[] c)把char数组变为字符串

1        // 修改程序
2    System.out.println(letters + " easy as " + String.valueOf(numbers));

实际上,对于上述程序,如果你想打印出char数组,应该重写toString() 方法,使其返回数组中包含的字符串

解惑七、动物庄园

1public class SolutionThirteen {
 2
 3    public static void main(String[] args) {
 4        final String pig = "length: 10";
 5        final String dog = "length: " + pig.length();
 6
 7        System.out.println("pig = " + pig + " dog = " + dog);
 8        System.out.println("Animal are equal: " + pig == dog);
 9
10    }
11}

对于上面的程序会输出什么?你可以能以为第二个输出语句会输出 Animal are equal: true

但是当你了输出语句的时候,你会发现输出的是Animal are equal: false


我相信大部分人都是理解这个意思的,因为 == 比较的是两个值的引用是否相同,那么如何改变呢?

改观

  1. 我们都知道a.equals(b) 可以用来判断两个字符串的值是否相等
1        final String pig = "length: 10";
2        final String dog = "length: " + pig.length();
3        System.out.println("Animal are equal: " + pig.equals(dog));
  1. 可以使用字符串常量来处理pig 和 dog 的引用问题
1        static final String pig = "length: 10";
2        static final String dog = "length: 10";
3        System.out.println("Animal are equal: " + pig == dog);

解惑八

1public class Indecisive {
 2
 3    public static boolean descritpion(){
 4        try {
 5            return true;
 6        }finally {
 7            return false;
 8        }
 9    }
10
11    public static void main(String[] args) {
12        System.out.println(descritpion());
13    }
14}


以上程序输出什么? description() 方法同时返回了两个值,而我们知道一个表达式是不能同时返回两个值的,所以必定有一个返回值是无效的。

原因就是try-finally语句中,finally语句块总是在控制权离开try语句块时执行,无论try语句块是正常结束的,还是非正常结束的,都是如此

解惑九

1public class SolutionThirtyNine {
 2
 3
 4    public static void description(){
 5        try {
 6            System.out.println("Hello World");
 7            System.exit(0);
 8        }finally {
 9            System.out.println("GoodBye World");
10        }
11    }
12
13    public static void main(String[] args) {
14        description();
15    }
16}你一定知道finally 在块语句结束前一定会执行,但是你猜上面的程序输出的是什么?会是Hello World,GoodBye World吗?你执行一下才会发现,输出的语句只有一条,Hello World,为什么呢?
正如你说知道的一样,finally语句块确实是会执行,但是执行过程中发生了一件重要的事情,System.exit(0)方法将停止当前线程和所有其他当场死亡的线程。finally语句块并不能给予线程继续执行的权限。也不能将线程恢复继续执行。所以finally语句块并不是没有执行,而是Dead...

改观

1public class SolutionThirtyNineChange {
 2
 3    public static void main(String[] args) {
 4        System.out.println("Hello World");
 5        Runtime.getRuntime().addShutdownHook(new Thread(){
 6
 7            public void run(){
 8                System.out.println("GoodBye World");
 9            }
10        });
11        System.exit(0);
12    }
13

当System.exit()方法被执行时,虚拟机(VM)在关闭前需要进行两项清理工作。首先,需要执行所有的关闭挂钩操作,这些挂钩已经注册到Runtime.addShutdownHook上,这对于释放VM之外的资源很有帮助,务必要为那些必须在VM退出之前发生的行为关闭挂钩


在 System. Exit 被调用时 VM 执行的第二个清理任务与终结器有关。如果 System. RunFinalizersOnExit 或它的魔鬼双胞胎 Runtime.RunFinalizersOnExit 被调用了,那么 VM 将在所有还未终结的对象上调用终结器。这些方法很久以前就已经过时了,而且其原因也很合理。无论什么原因,永远不要调用 System. RunFinalizersOnExit 和 Runtime.RunFinalizersOnExit:它们属于 Java 类库中最危险的方法【ThreadStop】。调用这些方法导致的结果是,终结器会在那些其他线程正在并发操作的对象上运行,从而导致不确定的行为或死锁。


总之,System. Exit 将立即停止所有的程序线程,它并不会使 finally 语句块得到调用,但是它在停止VM 之前会执行关闭挂钩操作。当 VM 被关闭时,请使用关闭挂钩来终止外部资源。通过调用 System.Halt 可以在不执行关闭挂钩的情况下停止 VM,但是很少使用这个方法。


解惑十

1public class SolutionFourty {
 2
 3    private SolutionFourty internalInstance = new SolutionFourty();
 4
 5    public SolutionFourty() throws Exception{
 6        System.out.println("I'm not Coming out");
 7    }
 8
 9    public static void main(String[] args) {
10        try {
11            SolutionFourty b = new SolutionFourty();
12            System.out.println("Surprise!");
13        }catch (Exception e){
14            System.out.println("I told you so");
15        }
16    }
17}

main方法调用了SolutionFourty()的构造器,它将抛出一个异常。你可能希望catch子句能够捕获这个异常,并且打印I told you so语句。但是仔细观察后会发现,SolutionFourty实例内部还会包含另外一个SolutionFourty实例,它的构造器也抛出一个异常。无论哪个抛出异常,看起来catch子句都应该捕获它,但是当你运行之后你会发现根本没有捕获异常,它抛出了StackOverflowError异常,为什么呢?


与大多数StackOverflowError异常一样,本程序也包含了一个无限递归,当你调用一个构造器时,实例变量的初始化操作将先于构造器的程序体运行,上述例子中,internalInstance变量的初始化操作递归调用了构造器,而该构造器通过再次调用SolutionFourty构造器而初始化自己的internalInstance域,如此递归循环下去。因为是StackOverflowError,所以程序无法捕获

再来看下面一个例子

1public class Car {
2
3    private static Class engineClass;
4    private Engine engine = (Engine)engineClass.newInstance();
5    public Car(){}
6}
7public class Engine {}

你觉得这个例子正确吗?

事实上尽管上面的Car中的engineClass.newInstance() 会抛出两个异常InstantiationException 和 IllegalAccessException,但是却没有捕获这两个异常,所以上述例子不会经过编译

改观

1public class Car {
 2
 3    private static Class engineClass;
 4    private static Engine engine = new Engine();
 5
 6    public static Engine methodThrowException(){
 7        try {
 8            return (Engine)engineClass.newInstance();
 9        } catch (IllegalAccessException e) {
10            throw new AssertionError();
11        } catch (InstantiationException e) {
12            throw new AssertionError();
13        }
14    }
15
16    public Car(){}
17}总之,实例变量的初始化操作是先于构造器的程序体运行的。实例初始化操作抛出的异常都会传播给构造器,如果初始化操作抛出的是受检查的异常,那么构造器也必须声明抛出这些异常,但是不应该这样做,以免造成混乱。
想了解更多类的初始化操作,请搜索类的加载顺序来了解更多
相关文章
|
Java
Java一些常见的坑
总是觉得自己Java基础还是不行,需要恶补。今天偶然mark了一本《Java解惑》,其中以端程序的方式罗列了95个即常见又不常见的xian(坑)jing(儿),拿来瞻仰一下。
66 0
|
Java
Java常见的坑(二)
你猜上述程序输出的是什么? 是 ABC easy as 123 吗? 你执行了输出操作,你才发现输出的是 ABC easy as [C@6e8cf4c6 ,这么一串丑陋的数字是什么鬼? 实际上我们知道字符串与任何数值的相加都会变为字符串,上述事例也不例外, numbers输出其实实际上是调用了Object.toString()方法,让numbers转变为'[c' + '@' + 无符号的十六进制数。
71 0
Java一些常见的坑
总是觉得自己Java基础还是不行,需要恶补。今天偶然mark了一本《Java解惑》,其中以端程序的方式罗列了95个即常见又不常见的xian(坑)jing(儿),拿来瞻仰一下。
|
11天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
19天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
2天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
2天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
20 1
|
10天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
10天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
9天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####