C#解惑46: 令人混淆的构造器案例

谜题46: 令人混淆的构造器案例

本谜题呈现了两个容易令人混淆的构造器。Main方法调用了一个构造器,但是它调用的究竟是哪一个呢?该程序的输出取决于这个问题的答案。那么它会打印什么呢?甚至它是否合法?

class Confusing

{

Confusing(object o)

{

System.Console.WriteLine("object");

}

Confusing(double[] dArray)

{

System.Console.WriteLine("double array");

}

static void Main()

{

new Confusing(null);

}

}

解惑46: 令人混淆的构造器案例

传递给构造器的参数是一个空的对象引用,因此,初看起来,该程序好像应该调用参数类型为object的重载版本,并且将打印object。另一方面,数组也是引用类型,因此null也可以应用于类型为double[]的重载版本。你由此可能会得出结论:这个调用是模棱两可的,该程序应该不能编译。如果你试着去运行该程序,就会发现这些直观感觉都是不对的:该程序打印的是double array。这种行为可能显得有悖常理,但是有一个很好的理由可以解释它。

Java的重载解析过程是分两阶段运行的。第一阶段选取所有可获得并且可应用的方法或构造器。第二阶段在第一阶段选取的方法或构造器中选取最精确的一个。如果一个方法或构造器可以接受传递给另一个方法或构造器的任何参数,那么我们就说第一个方法比第二个方法缺乏精确性(C#的重载决策请参见[C#语言规范
7.4.2]
,主要意思也差不多)。

在我们的程序中,两个构造器都是可获得并且可应用的。构造器Confusing(object)可以接受任何传递给Confusing(double[])的参数,因此Confusing(object)相对缺乏精确性。(每一个double数组都是一个object,但是每一个object并不一定是一个double数组。)因此,最精确的构造器就是Confusing(double[]),这也就解释了为什么程序会产生这样的输出。

如果传递的是一个double[]类型的值,那么这种行为是有意义的;但是如果你传递的是null,这种行为就有违直觉了。理解本谜题的关键在于在测试哪一个方法或构造器最精确时,这些测试没有使用实参:即出现在调用中的参数。这些参数只是被用来确定哪一个重载版本是可应用的。一旦编译器确定了哪些重载版本是可获得且可应用的,它就会选择最精确的一个重载版本,而此时使用的仅仅是形参:即出现在声明中的参数。

要想用一个null参数来调用Confusing(object)构造器,你需要这样写代码:new Confusing((object)null)。这可以确保只有Confusing(object)是可应用的。更一般地讲,要想强制要求编译器选择一个精确的重载版本,需要将实参转型为形参所声明的类型。

以这种方式来在多个重载版本中进行选择是相当令人不快的。在你的API中,应该确保不会让客户端走这种极端。理想状态下,你应该避免使用重载:为不同的方法取不同的名称。当然,有时候这无法实现,例如,构造器就没有名称,因而也就无法被赋予不同的名称。然而,你可以通过将构造器设置为私有的并提供公有的静态工厂,以此来缓解这个问题。如果构造器有许多参数,你可以用Builder模式来减少对重载版本的需求量。

如果你确实进行了重载,那么请确保所有的重载版本所接受的参数类型都互不兼容,这样,任何两个重载版本都不会同时是可应用的。如果做不到这一点,那么就请确保所有可应用的重载版本都具有相同的行为。

总之,重载版本的解析可能会产生混淆。应该尽可能地避免重载,如果你必须进行重载,那么你必须遵守上述方针,以最小化这种混淆。如果一个设计糟糕的API强制你在不同的重载版本之间进行选择,那么请将实参转型为你希望调用的重载版本的形参所具有的类型。

版权声明:本文为博主http://www.zuiniusn.com原创文章,未经博主允许不得转载。

时间: 2024-10-07 07:36:25

C#解惑46: 令人混淆的构造器案例的相关文章

爬虫之JS混淆和加密案例

需求: 中国空气质量在线监测分析平台是一个收录全国各大城市天气数据的网站,包括温度.湿度.PM 2.5.AQI 等数据,链接为:https://www.aqistudy.cn/html/city_detail.html,网站显示为: 一连串的分析 该网站所有的空气质量数据都是基于图表进行显示的,并且都是触发鼠标滑动或者点动后才会显示某点的数据,所以如果基于selenium进行数据爬取很吃力,因此考虑采用requests模块进行数据爬取. 首先要找到空气质量数据所在的数据包: 使用抓包工具抓取,经

Unicode(UTF-8, UTF-16)令人混淆的概念

为啥需要Unicode 我们知道计算机其实挺笨的,它只认识0101这样的字符串,当然了我们看这样的01串时肯定会比较头晕的,所以很多时候为了描述简单都用十进制,十六进制,八进制表示.实际上都是等价的,没啥太多不一样.其他啥文字图片之类的其他东东计算机不认识.那为了在计算机上表示这些信息就必须转换成一些数字.你肯定不能想怎么转换就怎么转,必须得有定些规则.于是刚开始的时候就有ASCII字符集(American Standard Code for Information Interchange, "

【转】Unicode(UTF-8, UTF-16)令人混淆的概念

参考地址:http://www.cnblogs.com/kingcat/archive/2012/10/16/2726334.html Java中,char类型用UTF-16编码描述一个代码单元 为啥需要Unicode 我们知道计算机其实挺笨的,它只认识0101这样的字符串,当然了我们看这样的01串时肯定会比较头晕的,所以很多时候为了描述简单都用十进制,十六进制,八进制表示.实际上都是等价的,没啥太多不一样.其他啥文字图片之类的其他东东计算机不认识.那为了在计算机上表示这些信息就必须转换成一些数

(转) Unicode(UTF-8, UTF-16)令人混淆的概念

原文地址:http://www.cnblogs.com/kingcat/archive/2012/10/16/2726334.html 为啥需要Unicode 我们知道计算机其实挺笨的,它只认识0101这样的字符串,当然了我们看这样的01串时肯定会比较头晕的,所以很多时候为了描述简单都用十进制,十六进制,八进制表示.实际上都是等价的,没啥太多不一样.其他啥文字图片之类的其他东东计算机不认识.那为了在计算机上表示这些信息就必须转换成一些数字.你肯定不能想怎么转换就怎么转,必须得有定些规则.于是刚开

《Java解惑》读书笔记

 摘选自<Java解惑>一书,之前整理了部分,一直没看完,最近为了督促自己每天读点这本书,决定一天至少更新一个谜题的内容,欢迎讨论. 欢迎关注技术博客http://blog.sina.com.cn/u/1822488043 Java解惑读书笔记 谜题1:奇数性 取余操作的定义: ( a / b ) * b + ( a % b ) = a 其中(a/b)是java运算的结果,也就是a/b是一个整数,比如3/2=1. 所以当取余操作返回一个非零结果的时候,它与左操作数具有相同符号. 请测试你的

《Java 解惑》学习笔记 (二)

令人混淆的构造器案例 构造函数在学习的过程中是容易混淆的,下面这段代码现给你了两个容易令人混淆的构造器.main 方法调用了一个构造器,但是它调用的到底是哪一个呢?该程序的输出取决于这个问题的答案.那么它到底会打印出什么呢?甚至它是否是合法的呢? public class Confusing { private Confusing(Object o) { System.out.println("Object"); } private Confusing(double[] dArray)

JAVA 重载方法,参数为NULL时,调用的处理 (精确性原则)

引子:大家可以思考一下下面程序的输出结果 public class TestNull { public void show(String a){ System.out.println("String"); } public void show(Object o){ System.out.println("Object"); } public static void main(String args[]){ TestMain t = new TestMain(); t

Java——类谜题

1.令人混淆的构造器 代码如下格式: 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

Java面向对象学习笔记 -- 1(类、对象、构造器)

1. 类 1)是同类型东西的概念,是对现实生活中事物的描述,映射到Java中描述就是class定义的类. 2)其实定义类,就是在描述事物,就是在定义属性(变量)和方法(函数). 3)类中可以声明:属性,方法,构造器: 属性就是实例变量,用于声明对象的结构的,在创建对象时候分配内存,每个对象有一份! 实例变量(对象属性)在堆中分配,并作用于整个类中,实例变量有默认值,不初始化也能参与运算. 4)类与类之间的关系: ① 关联:一个类作为另一个类的成员变量 public class A { pulic