如何正确地表示电话号码?

63

我在我的应用程序中遇到了表示手机号码的问题。

我想知道是否有一种整数类可以存储以0417254482开头的这样一个号码。也许使用字符串更为适当? 目前,我尝试使用int、double、long等类型来表示电话号码时,似乎会存储随机数字而不是我要存储的数字。


9
在这种情况下不需要任何代码。 - Jon Skeet
@Jon 我的意思是关于它显示一个随机数的那一部分...如果它显示的数字带有前导零被截断,那就可以理解,但是一个随机数?我需要看代码来解决这个问题... - Hari Menon
1
@Raze2dust:嗯,不是“随机”的——但将其存储在double中肯定会丢失数据。 - Jon Skeet
1
如果在PHP中出现了一个数字前面有个“0”,那么准备好迎接地狱吧 :P - Cole Tobin
所有固定长度的“数字”类型都存储前导零。但由于前导零很少有意义,大多数数字格式化程序会删除它们。但请注意,固定长度数字类型中的前导零数量总是必需的,以便将给定的数字值“填充”到数字类型可能代表的最大位数 - 没有办法“告诉它”存储一个前导零或两个或没有。为此,您需要字符表示或可变长度十进制表示。 - Hot Licks
1
但是有一个“技巧”可以使用:在将数字转换为二进制之前,始终在数字的字符表示形式前面添加一个“虚假”的“1”数字,然后在格式化回十进制数后始终剥离值的第一个字符(并可能“断言”它是“1”)。这将允许您将大多数电话号码(包括实质前缀)存储为64位整数。 - Hot Licks
9个回答

143

使用String。除此之外,如果你使用整数,你将无法存储前导零。你绝对不应该使用int(太小)、floatdouble(数据丢失的风险太大-见下文);longBigInteger可能是适当的选择(除了前导零问题),但坦率地说我会选择String。这样你还可以存储用户输入的任何破折号或空格,以便更容易记住号码。

至于上述提到的floatdouble的“数据丢失”问题- float绝对没有足够的精度;double可以工作,如果你确信你永远不需要超过16个数字(比long少两个),但你需要非常非常小心,在任何地方将值从double转换回string时,你得到的是确切的值。许多格式转换将给你一个约等于10个有效数字的近似值-但你需要一个确切的整数。基本上,对于电话号码使用浮点数是一个根本上错误的想法。如果你必须使用固定宽度的数字类型,使用long,但理想情况下,完全避免这种情况。


7
所有英国电话号码都以0开头。例如,Reading地区的区号是0118。 - Jon Skeet
1
这并不是@ColeJohnson的错,他错误地编辑了C#代码。有人关闭了这个问题的C#版本,说它是这个问题的重复。在我看来,区分编程语言是非常重要的。 - HappyNomad
有趣的是,我的工作中的编码标准有以下规定:“电话号码和邮政编码属于数字值”,并要求我们在数据库级别上使用数字。其想法是,如果是字符串,你可能会遇到各种奇怪的格式问题(例如,一个人输入 (555) 123-4567,另一个人输入 555-123-4567),而如果是数字,你可以自己格式化它。虽然公平地说,我们不处理国际事务,所以对我们来说这样做更有意义。 - Wayne Molina
2
@WayneM:对我来说,那听起来像是个坏主意,如果你需要处理其他国家的情况,那几乎肯定会让你吃亏。 - Jon Skeet
@WesleyMurch 还有,大多数日本电话号码以0开头。 090或080或03 - Trevor Wood
显示剩余4条评论

48

想一想:电话号码真的是一个数字吗?将电话号码与数字相加(或进行其他算术运算)有意义吗?电话号码是一种代码,通常用数字表示,但这只是一种约定俗成,也许在另一个国家里使用字母。(我刚意识到,那么国际电话号码呢?它们以 + 开头。)你必须思考你想要表示的事物的本质,然后找到最合适的表示方法。


+1 我猜你可以用 00 替换 +,这样丹麦(我所在的地方)就是 0045 XXXX XXXX。 - Lasse Espeholt
是的,这是正确的,可以使用“是”代替“00”,反之亦然。 - Damian Leszczyński - Vash
8
我没有在ID上执行任何算术运算,但我将它们存储为长整型。 - rds
10
由于ID通常会在每个新记录时递增1,这是一种算术操作。你可能不自己执行此操作,但它确实会发生。 - Sumit
@rds 有一个很好的理由可以不使用数字ID,因为人们最终会将它们用于排序等操作,尽管它们没有实际意义。如果工具支持得更好,我们最好使用GUID样式的随机标识符,以紧凑的二进制形式存储。 - IMSoP
在我看来,最好的答案是只有在你需要直接进行计算时才使用整数(int/long)或浮点数。即使是受检位数保护的ID号码(例如信用卡/银行账户号码)也不是长整型等类型,因为你可能想要对单个数字进行计算,但从不会将其作为一个整体数字进行计算。 - Merijn Vogel

7

6

尽管电话号码被称为数字,但它们通常不是数字(例如前导零、国家前缀+XX等)。

因此,在程序内正确表示电话号码有两种可能:

  1. Using String to keep the whole number like entered.
  2. Using a custom data type that offers additional support for phone number features

    public class PhoneNumber implements Comparable<PhoneNumber>{
    
        private String countryCode;
    
        private String areaCode;
    
        private String subscriberNumber;
    
        // Constructor(s)
    
        // Getter
    
        // HashCode + Equals
    
        // compareTo
    
        @Override
        public String toString(){
            return countrycode + " " + areaCode + " " + subscriberNumber;
        }
    }
    

看各国使用的不同电话号码书写规则真是有趣。


此外,这还取决于您的应用程序变得多么复杂。普通应用程序只需要字符串表示,而对于保留地址并对其执行查询的复杂应用程序,则需要更复杂的处理。 - Johannes Wachter
你可以采用这样的约定,即电话号码必须包括国家前缀,因此没有前导零。 - rds

5
创建一个自己的PhoneNumber类,其中包含一个私有字段,类型为String,用于表示电话号码。
public class PhoneNumber {
   private String number;
   public PhoneNumber(String number) {
      //check validity of number
      this.number = number;
   }
   //getter, comparator, etc...
}

如果所有电话号码长度相同,您还可以使用long或BigInteger表示数字,但要小心前导零。

电话号码实际上不是整数(或字符串)。它应该有自己的类。

编辑:还有一件事:我不会为这个类实现setter,因为电话号码对象最好是不可变的。


添加一个compareTo和equals方法将使得排序按照提问者的要求进行。 - matto1990
@matto:你说得对。他应该实现compareTo和equals方法。谢谢。 - snakile
1
如果您只是将数字本身存储在原子字符串中,则无需将其包装到新类中,因为所有方法(如hashCode等)实质上都会委托给String提供的实现。 - Johannes Wachter

2
你应该使用字符串或更专业的数据结构。
主要原因是你可以在电话号码上执行的操作是词法的,而不是算术的。 例如,你可以说法国的电话号码以+33开头,但你不能假设它们在一个数字范围内。
我认为这些其他论点都无效。
  • 电话号码可以包含*#。这些符号可以通过电话线传输,但它们不是电话号码本身的一部分,我认为这超出了范围。
  • 电话号码可以以前导零开头。本地电话号码可以,但它们本来就是有限的表示。国际电话号码以国家代码开头,没有一个电话号码以前导零开头。因此,没有国际电话号码以前导零开头。
  • 电话号码以+开头。一个数字可以完美地表示这个,只需简单地为正数。以+开头只是E164号码的一种表示形式,以便它们可以与本地号码区分开来。如果你只处理E164号码,它们真的不必这样做。
  • 电话号码可以包含空格或括号。这是荒谬的,因为它只是数字的文本表示。你不应该将这些存储下来,因为人们可能有不同的个人喜好来分隔数字组(.-等)。

2
关于本地电话号码的争论只有在您可以自信地将输入的号码规范化为国际形式时才是正确的。如果您要使用自动拨号器,这可能是一个合理的限制,但如果您要在某个地方显示号码,则最好保留原始格式,包括潜在的模糊本地形式。 - IMSoP

0
每个数字都有无限数量的零在左右两侧,
为了表示它,您应该使用字符串格式化。
class PhoneNumber implements Comparable<PhoneNumber> {

    private Long number;

    public PhoneNumber(Long number) {
        this.number = number;
    }

    public Long getNumber() {
        return this.number;
    }

    public boolean equals(Object object) {

        if (getNumber() == null && object == null) {
            return true; //or false its depend 
        }

        return getNumber().equals(object);
    }

    public int compareTo(PhoneNumber that) {

            if(that == null) {
             return -1;
            }

        Long thisNumber = getNumber();
            Long thatNumber = that.getNumber();

        if (thisNumber == null && thatNumber == null) {
            return 0; //or -1
        }

        if (thisNumber == null && thatNumber != null) {
            return -1;
        }

        return thisNumber.compareTo(thatNumber);

    }

    @Override
    public String toString() {
        return String.format("%010d", getNumber());
    }
}

使用 %010d 表示 %[argument_index$][flags][width][.precision]conversion

标志 0 - 填充零 10 - 填充零的数量 d - 十进制整数

接口 Comparable 的实现使您能够对列表进行排序。

List<PhoneNumber> phoneNumbers = new ArrayList();
 phoneNumbers.add(new PhoneNumber (123L);
 phoneNumbers.add(new PhoneNumber (123777L);
 phoneNumbers.add(new PhoneNumber (125L);
 phoneNumbers.add(new PhoneNumber (124L);
 phoneNumbers.add(new PhoneNumber (126L);

Collections.sort(phoneNumbers);

  for(PhoneNumber phoneNumber : phoneNumbers) {
   System.Console.Out.WriteLine(phoneNumber);
  }

输出结果为:

 0000000000 
 0000000123
 0000000124
 0000000125
 0000000126
 0000123777

可比较 字符串格式化器


代码有一些错误(缺少分号),但除此之外,这是解决它的完美方法。 - matto1990
推荐使用一个数字。抱歉,@Vash,我将你的9,100声望更改为9,098。另外,为什么要使用“class”而不是“struct”?如果结构包含数据(像这个),请使用“struct”。如果它涵盖了“对象”并执行操作(如“Stream”),则使用“class”。如果将“int”定义为“class Int32”是否有意义? - Cole Tobin
@Cole Johnson,选择类型取决于开发人员。在我看来,长整型更好,因为您可以以不同的方式格式化字符串。有时会要求这样做。因此,它比字符串更全球化。它还可以更快地工作并且更轻巧。那么您使用字符串的逻辑是什么?关于第二个问题。您对类和结构的解释不清楚。我选择了类,因为我不需要每次使用电话时都复制它。我想利用引用类型的优势。因此,如果我操作它的内部,则该类不是设计为不可变的。 - Damian Leszczyński - Vash
@Cole Johnson,我认为你对于类和结构体的定义是错误的。选择应该基于数据的使用方式。我绝对不同意那种认为结构体选择应该基于所包含数据的观点。 - Damian Leszczyński - Vash
将填充到固定长度的电话号码视为解决方案,这只能支持世界上极小一部分的电话号码。也许有些国家需要填充前导零,而且所有号码长度相同,因此这种方法足够使用,但绝对不是“完美的解决方案”。 - IMSoP

0

你应该使用字符串来支持带前导零的数字。你提供的代码是:

Order order1 = new PickUpOrder(orderTime, 0473519954); 
//The pickup order requires an orderTime (String) and a contact number(Int). Heres    
//the constructor for PickUpOrder. 

public PickUpOrder(Date orderTime, String number) 
{ 
    discount = .2; 
    phoneNumber = number; 
    super.setOrderTime(orderTime); 
    //Test print 
    System.out.println(phoneNumber) 
    //reads int as 74049273 instead of 0473519954 
}

在构造函数中,号码是字符串,但是当你调用构造函数时,你使用了一个整数作为电话号码。我认为在Java中这里一定会有编译错误。这是你编译的相同代码吗?

-1

我建议不要使用原始数据类型。

原因:原始数据类型无法接受特殊字符,例如+、-、(、)和&。如果您接受此格式的电话号码+1(xxx)-xxx-xxxx


你如何定义“基本数据类型”?你会用哪些其他的数据类型呢? - Nico Haase

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接