本谜题呈现给你了两个容易令人混淆的构造器。main方法调用了一个构造器,但是它调用的到底是哪一个呢?该程序的输出取决于这个问题的答案。那么它到底会打印出什么呢?甚至它是否是合法的呢?
public class Confusing {
private Confusing(Object o) {
System.out.println("Object");
}
private Confusing(double[] dArray) {
System.out.println("double array");
}
public static void main(String[] args) {
new Confusing(null);
}
}
传递给构造器的参数是一个空的对象引用,因此,初看起来,该程序好像应该调用参数类型为Object的重载版本,并且将打印出Object。另一方面,数组也是引用类型,因此null也可以应用于类型为double[ ]的重载版本。你由此可能会得出结论:这个调用是模棱两可的,该程序应该不能编译。如果你试着去运行该程序,就会发现这些直观感觉都是不对的:该程序打印的是double array。这种行为可能显得有悖常理,但是有一个很好的理由可以解释它。
Java的重载解析过程是以两阶段运行的。第一阶段选取所有可获得并且可应用的方法或构造器。第二阶段在第一阶段选取的方法或构造器中选取最精确的一个。如果一个方法或构造器可以接受传递给另一个方法或构造器的任何参数,那么我们就说第一个方法比第二个方法缺乏精确性[JLS 15.12.2.5]。
在我们的程序中,两个构造器都是可获得并且可应用的。构造器Confusing(Object)可以接受任何传递给Confusing(double[ ])的参数,因此Confusing(Object)相对缺乏精确性。(每一个double数组都是一个Object,但是每一个Object并不一定是一个double数组。)因此,最精确的构造器就是Confusing(double[ ]),这也就解释了为什么程序会产生这样的输出。
如果你传递的是一个double[ ]类型的值,那么这种行为是有意义的;但是如果你传递的是null,这种行为就有违直觉了。理解本谜题的关键在于在测试哪一个方法或构造器最精确时,这些测试没有使用实际的参数:即出现在调用中的参数。这些参数只是被用来确定哪一个重载版本是可应用的。一旦编译器确定了哪些重载版本是可获得且可应用的,它就会选择最精确的一个重载版本,而此时使用的仅仅是形式参数:即出现在声明中的参数。
要想用一个null参数来调用Confusing(Object)构造器,你需要这样写代码:new Confusing((Object)null)。这可以确保只有Confusing(Object)是可应用的。更一般地讲,要想强制要求编译器选择一个精确的重载版本,需要将实际的参数转型为形式参数所声明的类型。
以这种方式来在多个重载版本中进行选择是相当令人不快的。在你的API中,应该确保不会让客户端走这种极端。理想状态下,你应该避免使用重载:为不同的方法取不同的名称。当然,有时候这无法实现,例如,构造器就没有名称,因而也就无法被赋予不同的名称。然而,你可以通过将构造器设置为私有的并提供公有的静态工厂,以此来缓解这个问题[EJ Item 1]。如果构造器有许多参数,你可以用Builder模式[Gamma95]来减少对重载版本的需求量。
如果你确实进行了重载,那么请确保所有的重载版本所接受的参数类型都互不兼容,这样,任何两个重载版本都不会同时是可应用的。如果做不到这一点,那么就请确保所有可应用的重载版本都具有相同的行为[EJ Item 26]。
总之,重载版本的解析可能会产生混淆。应该尽可能地避免重载,如果你必须进行重载,那么你必须遵守上述方针,以最小化这种混淆。如果一个设计糟糕的API强制你在不同的重载版本之间进行选择,那么请将实际的参数转型为你希望调用的重载版本的形式参数所具有的类型。
分享到:
相关推荐
编译器自动加入代码到构造器,对于这个,Java 程序员新手可能比较混淆。当我们写一个没有构造器的类,编译的时候,编译器会自动加上一个不带参数的构造器。 六、结论 构造器是 Java 类中最重要的一个概念,用于...
构造器和方法是Java编程语言中的两个核心概念,它们各自承担着不同的角色和功能,但初学者往往容易混淆。为了更好地理解和区分构造器与方法,本文将深入探讨这两个概念的关键差异,包括它们的功能、语法特性以及如何...
标题中的"代码混淆器,专业混淆"指的是一个专门用于进行代码混淆处理的工具,它能够将可读性强的源代码转换为难以理解的形式,从而增加攻击者分析和篡改代码的难度。 **代码混淆的重要性:** 1. **安全防护**:混淆...
jocky混淆器是一款专门针对Java代码的混淆工具,它的主要作用是对编译后的字节码进行混淆,使得逆向工程变得极其困难。通过重命名类、方法和变量,改变控制流结构,jocky能够有效地保护源代码免受恶意攻击。由于其对...
本案例是一个Spring Boot单个Maven工程,通过ProGuard实现代码混淆的实践示例。首先,我们需要了解ProGuard的基本配置和使用方法。ProGuard的配置文件通常命名为`proguard.cfg`,在该文件中,我们可以定义混淆规则,...
JavaScript脚本混淆器是一种工具,它的主要目的是保护JavaScript代码的安全性,防止未经授权的用户阅读、复制或篡改代码。在Web开发中,由于JavaScript代码通常是以明文形式发送到客户端,这使得它容易受到诸如代码...
PB混淆器是一款针对PowerBuilder(PB)开发的应用程序进行代码混淆的专业工具。在软件开发领域,混淆是一种常见的安全措施,用于保护源代码不被轻易逆向工程解析,从而提高程序的安全性。PowerBuilder是一款历史悠久...
自治c语言代码混淆器,可选择移除c语言注释、变量名混淆、函数名混淆,#pragma行删除,#region行删除,空行删除。 可屏蔽不被混淆的关键词、导入导出关键词列表,清空关键词等功能。 可以保护在需要将源码给出时...
"J2ME混淆器"正是解决这一问题的重要工具,它通过对代码进行混淆处理,使得原始的Java源码难以被反编译和理解,从而增加破解的难度,保护开发者权益。 混淆器的工作原理是将源代码中的变量名、类名、方法名替换为无...
【开发人员的混淆器】 在Java开发中,为了保护应用程序的源代码不被轻易反编译和理解,开发者通常会使用混淆器对代码进行混淆。混淆器的主要功能是将源代码中的类名、方法名、变量名转换为无意义的简短标识,以此...
C#代码混淆器是一种工具,用于保护C#编写的源代码不被轻易反编译和理解,从而提高软件的安全性。在.NET环境中,由于代码会被编译为中间语言(IL),并通过公共语言运行时(CLR)执行,这使得通过反编译工具可以相对...
在这个案例中,可能包含了混淆器的二进制版本,我们可以在没有源代码的情况下直接运行它。 4. `obj`:这是编译过程中产生的临时文件和中间输出的目录。这些文件通常不直接与最终产品有关,但它们可以帮助我们了解...
JavaScript代码混淆器是一种用于保护JavaScript源代码的技术,它通过改变代码的可读性来提高代码的安全性。在互联网上发布JavaScript代码时,由于其是解释执行的语言,源代码默认是暴露给用户的,这可能导致恶意用户...
de4dot是一款由C#编写并遵循GPLv3许可的开源.NET解混淆器和解压缩器。它的主要功能是处理那些经过混淆处理的.NET程序集,尝试将它们还原至接近原始状态,以便于开发者能够更轻松地阅读和理解代码。de4dot由Jozef ...
"C#代码混淆器"是用来保护源代码不被轻易反编译的重要工具。本文将深入探讨C#代码混淆器的工作原理、重要性以及如何使用。 首先,让我们了解什么是代码混淆。代码混淆是将可读性强的源代码转换为难以理解的形式,...
"C#源码混淆器"是一款专门针对C#编译后的dll和exe文件进行混淆加密的工具,其主要目的是防止恶意用户通过反编译工具(如Reflector)查看并理解代码逻辑,从而保护知识产权和软件安全。 混淆是一种常见的代码保护...
敏创Java混淆器是广州市敏创信息科技有限公司在保护自己的Java源代码过程中积累的一套实用工具,可以有效地对Java代码进行混淆,达到保护自己知识产权的目的。 工具可以去除空格,回车,注释,有效地减小Java文件...
Java代码混淆器ProGuard是Java开发中用于保护和优化应用程序的重要工具。它的主要功能是对Java字节码进行混淆,使得代码难以被逆向工程解析,从而提高代码的安全性。混淆过程中,函数变量、类和方法名会被重命名为...
在Java世界中,ProGuard是一个常用的混淆器,它不仅可以混淆代码,还能优化、缩小和预校验JAR或APK文件。"proguard.jar"就是ProGuard的库文件,开发者可以通过配置ProGuard的规则文件来控制混淆过程,比如保留特定类...
**ProGuard程序混淆器详解** ProGuard是一款强大的Java字节码处理工具,主要功能包括代码混淆、优化、压缩和预校验。它被广泛应用于Android应用开发中,用于保护源代码的安全性和减小程序体积,提高运行效率。在...