跳转至

Java 数据类型 II

原文:http://zetcode.com/lang/java/datatypes2/

在 Java 教程的这一部分中,我们将继续介绍 Java 的数据类型。 我们介绍了包装器类,装箱和拆箱,默认值,转换和促销。

Java 包装器类

包装器类是原始数据类型的对象表示。 需要Object时,包装器类用于表示原始值。 例如,Java 集合仅适用于对象。 它们不能采用原始类型。 包装器类还包括一些有用的方法。 例如,它们包括进行数据类型转换的方法。 将原始类型放入包装器类称为boxing。 反向过程称为unboxing

通常,在有某种理由的情况下,我们使用包装器类。 否则,我们使用原始类型。 包装器类是不可变的。 创建它们后,就无法更改它们。 基本类型比装箱类型更快。 在科学计算和其他大规模数字处理中,包装类可能会严重影响性能。

原始类型 包装类 构造器参数
byte Byte byteString
short Short shortString
int Integer intString
long Long longString
float Float floatdoubleString
double Double doubleString
char Char char
boolean Boolean booleanString

Table: Primitive types and their wrapper class equivalents

Integer类将原始类型int的值包装在对象中。 它包含在处理int时有用的常量和方法。

com/zetcode/IntegerWrapper.java

package com.zetcode;

public class IntegerWrapper {

    public static void main(String[] args) {

        int a = 55;
        Integer b = new Integer(a);

        int c = b.intValue();
        float d = b.floatValue();

        String bin = Integer.toBinaryString(a);
        String hex = Integer.toHexString(a);
        String oct = Integer.toOctalString(a);

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);

        System.out.println(bin);
        System.out.println(hex);
        System.out.println(oct);
    }
}

本示例适用于Integer包装器类。

int a = 55;

该行创建一个整数原始数据类型。

Integer b = new Integer(a);

Integer包装器类是从原始int类型创建的。

int c = b.intValue();
float d = b.floatValue();

intValue()方法将Integer转换为int。 同样,floatValue()返回float数据类型。

String bin = Integer.toBinaryString(a);
String hex = Integer.toHexString(a);
String oct = Integer.toOctalString(a);

这三种方法返回整数的二进制,十六进制和八进制表示形式。

$ java IntegerWrapper.java
55
55
55
55.0
110111
37
67

这是程序输出。

集合是用于处理对象组的强大工具。 原始数据类型不能放入 Java 集合中。 将原始值装箱后,可以将它们放入集合中。

com/zetcode/Numbers.java

package com.zetcode;

import java.util.ArrayList;
import java.util.List;

public class Numbers {

    public static void main(String[] args) {

        List<Number> ls = new ArrayList<>();

        ls.add(1342341);
        ls.add(new Float(34.56));
        ls.add(235.242);
        ls.add(new Byte("102"));
        ls.add(new Short("1245"));

        for (Number n : ls) {

            System.out.println(n.getClass());
            System.out.println(n);
        }
    }
}

在示例中,我们将各种数字放入ArrayList中。 ArrayList是动态的,可调整大小的数组。

List<Number> ls = new ArrayList<>();

创建一个ArrayList实例。 在尖括号中,我们指定容器将容纳的类型。 Number是 Java 中所有五个数字基本类型的抽象基类。

ls.add(1342341);
ls.add(new Float(34.56));
ls.add(235.242);
ls.add(new Byte("102"));
ls.add(new Short("1245"));

我们将五个数字添加到集合中。 请注意,整数和双精度值未装箱; 这是因为对于整数和双精度类型,编译器将执行自动装箱。

for (Number n : ls) {

    System.out.println(n.getClass());
    System.out.println(n);
}

我们遍历容器并打印类名称及其每个元素的值。

$ java Numbers.java
class java.lang.Integer
1342341
class java.lang.Float
34.56
class java.lang.Double
235.242
class java.lang.Byte
102
class java.lang.Short
1245

com.zetcode.Numbers程序给出该输出。 请注意,这两个数字是由编译器自动装箱的。

Java 装箱

从原始类型转换为对象类型称为boxingUnboxing是相反的操作。 它正在将对象类型转换回原始类型。

com/zetcode/BoxingUnboxing.java

package com.zetcode;

public class BoxingUnboxing {

    public static void main(String[] args) {

        long a = 124235L;

        Long b = new Long(a);
        long c = b.longValue();

        System.out.println(c);
    }
}

在代码示例中,我们将long值放入Long对象中,反之亦然。

Long b = new Long(a);

该行执行拳击。

long c = b.longValue();

在这一行,我们进行拆箱。

Java 自动装箱

Java 5 引入了自动装箱。 Autoboxing是原始类型及其对应的对象包装器类之间的自动转换。 自动装箱使编程更加容易。 程序员不需要手动进行转换。

当一个值是原始类型而另一个值是包装器类时,将执行自动装箱和拆箱:

  • 赋值
  • 将参数传递给方法
  • 从方法返回值
  • 比较操作
  • 算术运算
Integer i = new Integer(50);

if (i < 100) {
   ...
}

if表达式的方括号内,将Integerint进行比较。 Integer对象被转换为原始int类型,并与 100 值进行比较。 自动取消装箱。

com/zetcode/Autoboxing.java

package com.zetcode;

public class Autoboxing {

    private static int cube(int x) {

        return x * x * x;
    }

    public static void main(String[] args) {

        Integer i = 10;
        int j = i;

        System.out.println(i);
        System.out.println(j);

        Integer a = cube(i);
        System.out.println(a);
    }
}

此代码示例演示了自动装箱和自动拆箱。

Integer i = 10;

Java 编译器在此代码行中执行自动装箱。 将int值装箱为Integer类型。

int j = i;

在这里会自动开箱。

Integer a = cube(i);

当我们将Integer传递给cube()方法时,便完成了自动拆箱。 当我们返回计算值时,将执行自动装箱,因为int转换回了Integer

Java 语言不支持运算符重载。 当我们对包装类应用算术运算时,自动装箱由编译器完成。

com/zetcode/Autoboxing2.java

package com.zetcode;

public class Autoboxing2 {

    public static void main(String[] args) {

        Integer a = new Integer(5);
        Integer b = new Integer(7);

        Integer add = a + b;
        Integer mul = a * b;

        System.out.println(add);
        System.out.println(mul);
    }
}

我们有两个Integer值。 我们对这两个值执行加法和乘法运算。

Integer add = a + b;
Integer mul = a * b;

与 Ruby,C# ,Python,D 或 C++ 等语言不同,Java 没有实现运算符重载。 在这两行中,编译器调用intValue()方法,并将包装器类转换为int,然后通过调用valueOf()方法将结果包装回Integer

Java 自动装箱和对象内化

Object intering仅存储每个不同对象的一个​​副本。 该对象必须是不可变的。 不同的对象存储在内部池中。 在 Java 中,当将原始值装箱到包装对象中时,将插入某些值(任何布尔值,任何字节,0 到 127 之间的任何char以及 -128 和 127 之间的任何shortint),以及这两个值之一的任意装箱转换可以确保得到相同的对象。

根据 Java 语言规范,这些是最小范围。 因此,行为取决于实现。 对象交互可以节省时间和空间。 从字面值,自动装箱和Integer.valueOf()中获得的对象是内部对象,而使用new运算符构造的对象始终是不同的对象。

比较包装类时,对象交互会产生一些重要的后果。 ==运算符比较对象的引用标识,而equals()方法比较值。

com/zetcode/Autoboxing3.java

package com.zetcode;

public class Autoboxing3 {

    public static void main(String[] args) {

        Integer a = 5; // new Integer(5);
        Integer b = 5; // new Integer(5);

        System.out.println(a == b);
        System.out.println(a.equals(b));
        System.out.println(a.compareTo(b));

        Integer c = 155;
        Integer d = 155;

        System.out.println(c == d);
        System.out.println(c.equals(d));
        System.out.println(c.compareTo(d));
    }
}

该示例比较了一些Integer对象。

Integer a = 5; // new Integer(5);
Integer b = 5; // new Integer(5);

两个整数装在Integer包装器类中。

System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));

使用三种不同的方法比较这些值。 ==运算符比较两种盒装类型的引用标识。 由于对象的嵌入,该运算结果为true。 如果使用new运算符,则将创建两个不同的对象,并且==运算符将返回falseequals()方法在数值上比较两个Integer对象。 它返回布尔值truefalse(在我们的例子中为true)。

最后,compareTo()方法还对两个对象进行了数值比较。 如果此Integer等于参数Integer,则返回值 0; 如果此Integer在数值上小于参数Integer,则该值小于 0; 如果此Integer在数值上大于自变量Integer,则该值大于 0。

Integer c = 155;
Integer d = 155;

我们还有两种盒装类型。 但是,这些值大于内化的最大值(127); 因此,创建了两个不同的对象。 这次==运算符产生false

$ java Autoboxing3.java
true
true
0
false
true
0

这是程序的输出。

Java 空类型

Java 具有特殊的null类型。 类型没有名称。 结果,不可能声明null类型的变量或将其强制转换为null类型。 null表示一个空引用,不引用任何对象。 null是引用类型变量的默认值。 不能为原始类型分配null字面值。

在不同的上下文中,null表示不存在对象,未知值或未初始化状态。

com/zetcode/NullType.java

package com.zetcode;

import java.util.Random;

public class NullType {

    private static String getName() {

        Random r = new Random();
        boolean n = r.nextBoolean();

        if (n == true) {

            return "John";
        } else {

            return null;
        }
    }

    public static void main(String[] args) {

        String name = getName();

        System.out.println(name);

        System.out.println(null == null);

        if ("John".equals(name)) {

            System.out.println("His name is John");
        }
    }
}

我们在程序中使用null值。

private static String getName() {

    Random r = new Random();
    boolean n = r.nextBoolean();

    if (n == true) {

        return "John";
    } else {

        return null;
    }
}

getName()方法中,我们模拟了一种方法有时可以返回null值的情况。

System.out.println(null == null);

我们比较两个空值。 表达式返回true

if ("John".equals(name)) {

    System.out.println("His name is John");
}

我们将名称变量与"John"字符串进行比较。 注意,我们在"John"字符串上调用了equals()方法。 这是因为如果名称变量等于null,则调用该方法将导致NullPointerException

$ java NullType.java
null
true
$ java NullType.java
null
true
$ java NullType.java
John
true
His name is John

我们执行该程序三次。

Java 默认值

编译器会为未初始化的字段提供默认值。 最终字段和局部变量必须由开发者初始化。

下表显示了不同类型的默认值。

数据类型 默认值
byte 0
char '\u0000'
short 0
int 0
long 0L
float 0f
double 0d
Object null
boolean false

Table: Default values for uninitialized instance variables

下一个示例将打印未初始化的实例变量的默认值。 实例变量是在类中定义的变量,该类的每个实例化对象都具有一个单独的副本。

com/zetcode/DefaultValues.java

package com.zetcode;

public class DefaultValues {

    static byte b;
    static char c;
    static short s;
    static int i;
    static float f;
    static double d;
    static String str;
    static Object o;

    public static void main(String[] args) {

        System.out.println(b);
        System.out.println(c);
        System.out.println(s);
        System.out.println(i);
        System.out.println(f);
        System.out.println(d);
        System.out.println(str);
        System.out.println(o);
    }
}

在示例中,我们声明了八个成员字段。 它们未初始化。 编译器将为每个字段设置默认值。

static byte b;
static char c;
static short s;
static int i;
...

这些是实例变量; 它们在任何方法外声明。 这些字段被声明为static,因为它们是通过static main()方法访问的。 (在本教程的后面,我们将更多地讨论静态变量和实例变量。)

$ java DefaultValues.java
0

0
0
0.0
0.0
null
null

This is the output of the program.

Java 类型转换

我们经常一次处理多种数据类型。 将一种数据类型转换为另一种数据类型是编程中的常见工作。 术语类型转换是指将一种数据类型的实体更改为另一种。 在本节中,我们将处理原始数据类型的转换。 引用类型的转换将在本章后面提到。 转换规则很复杂; 它们在 Java 语言规范的第 5 章中指定。

转换有两种类型:隐式转换和显式转换。 隐式类型转换,也称为强制,是编译器自动进行的类型转换。 在显式转换中,程序员直接在一对圆括号内指定转换类型。 显式转换称为类型转换。

转换发生在不同的上下文中:赋值,表达式或方法调用。

int x = 456;
long y = 34523L;
float z = 3.455f;
double w = 6354.3425d;

在这四个分配中,没有转换发生。 每个变量都被分配了预期类型的​​字面值。

int x = 345;
long y = x;

float m = 22.3354f;
double n = m;

在此代码中,Java 编译器隐式执行了两次转换。 将较小类型的变量分配给较大类型的变量是合法的。 该转换被认为是安全的,因为不会损失任何精度。 这种转换称为隐式加宽转换。

long x = 345;
int y = (int) x;

double m = 22.3354d;
float n = (float) m;

在 Java 中,将较大类型的变量分配给较小类型是不合法的。 即使值本身适合较小类型的范围。 在这种情况下,可能会降低精度。 为了允许这种分配,我们必须使用类型转换操作。 这样,程序员说他是故意这样做的,并且他意识到可能会丢失一些精度这一事实。 这种转换称为显式变窄转换。

byte a = 123;
short b = 23532;

在这种情况下,我们处理一种特定类型的分配转换。 123 和 23532 是整数字面值,ab变量为byteshort类型。 可以使用铸造操作,但不是必需的。 字面值可以在赋值左侧的变量中表示。 我们处理隐式变窄转换。

private static byte calc(byte x) {
...
}
byte b = calc((byte) 5);

以上规则仅适用于分配。 当我们将整数字面值传递给需要一个字节的方法时,我们必须执行强制转换操作。

Java 数字提升

数值提升是隐式类型转换的特定类型。 它发生在算术表达式中。 数字提升用于将数字运算符的操作数转换为通用类型,以便可以执行操作。

int x = 3;
double y = 2.5;
double z = x + y;

第三行中有一个加法表达式。 x操作数为inty操作数为double。 编译器将整数转换为双精度值,然后将两个数字相加。 结果是两倍。 这是隐式扩展原始类型转换的情况。

byte a = 120;
a = a + 1; // compilation error

此代码导致编译时错误。 在第二行的右侧,我们有一个字节变量a和一个整数字面值 1。该变量将转换为整数并添加值。 结果是一个整数。 以后,编译器尝试将值分配给a变量。 没有显式的强制转换运算符,就不可能将较大的类型分配给较小的类型。 因此,我们收到一个编译时错误。

byte a = 120;
a = (byte) (a + 1);

此代码可以编译。 请注意a + 1表达式中使用圆括号。 (byte)强制转换运算符的优先级高于加法运算符。 如果要对整个表达式应用转换,则必须使用圆括号。

byte a = 120;
a += 5;

复合运算符自动执行隐式转换。

short r = 21;
short s = (short) -r;

+-一元运算符应用于变量,即可执行一元数提升。 short类型升级为int类型。 因此,必须使用强制转换运算符来使分配通过。

byte u = 100;
byte v = u++;

如果是一元递增++或递减--运算符,则不会进行任何转换。 不需要铸造。

Java 装箱,拆箱转换

装箱转换将原始类型的表达式转换为包装器类型的对应表达式。 拆箱转换将包装器类型的表达式转换为原始类型的相应表达式。 从booleanBoolean或从字节到Byte的转换是装箱转换的示例。 反向转换,例如从Booleanboolean或从Bytebyte的翻译是取消装箱转换的示例。

Byte b = 124;
byte c = b;

在第一行代码中,自动装箱转换由 Java 编译器执行。 在第二行中,完成了拆箱转换。

private static String checkAge(Short age) {
...
}
String r = checkAge((short) 5);

在这里,我们在方法调用的上下文中进行装箱转换。 我们将short类型传递给需要Short包装类型的方法。 该值已装箱。

Boolean gameOver = new Boolean("true");
if (gameOver) {
    System.out.println("The game is over");
}

这是拆箱转换的示例。 在if表达式内部,调用booleanValue()方法。 该方法返回Boolean对象的值作为boolean原语。

对象引用转换

对象,接口和数组是引用数据类型。 任何引用都可以转换为Object。 对象类型确定在运行时使用哪种方法。 引用类型确定在编译时将使用哪种重载方法。

接口类型只能转换为接口类型或Object。 如果新类型是接口,则它必须是旧类型的超级接口。 可以将类类型转换为类类型或接口类型。 如果要转换为类类型,则新类型必须是旧类型的超类。 如果要转换为接口类型,则旧类必须实现该接口。 数组可以转换为类Object,接口CloneableSerializable或数组。

引用变量转换有两种类型:下播和上播。 正在向上转换(泛型或扩展)正在从子类型转换为父类型。 我们正在将单个类型转换为通用类型。 向下转换(专业化或缩小)正在从父类型转换为子类型。 我们正在将通用类型转换为单个类型。

向上转换缩小了对象可用的方法和属性的列表,向下转换可以扩展它。 向上转换是安全的,但是向下转换涉及类型检查,并且可能抛出ClassCastException

com/zetcode/ReferenceTypeConverion.java

package com.zetcode;

import java.util.Random;

class Animal {}
class Mammal extends Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

public class ReferenceTypeConversion {

    public static void main(String[] args) {

        // upcasting
        Animal animal = new Dog();
        System.out.println(animal);

        // ClassCastException
        // Mammal mammal = (Mammal) new Animal();

        var returned = getRandomAnimal();

        if (returned instanceof Cat) {

            Cat cat = (Cat) returned;
            System.out.println(cat);
        } else if (returned instanceof Dog) {

            Dog dog = (Dog) returned;
            System.out.println(dog);
        } else if (returned instanceof Mammal) {

            Mammal mammal = (Mammal) returned;
            System.out.println(mammal);
        } else {

            Animal animal2 = returned;
            System.out.println(animal2);
        }
    }

    private static Animal getRandomAnimal() {

        int val = new Random().nextInt(4) + 1;

        Animal anim = switch (val) {

            case 2 -> new Mammal();
            case 3 -> new Dog();
            case 4 -> new Cat();
            default -> new Animal();
        };

        return anim;
    }
}

该示例执行引用类型转换。

// upcasting
Animal animal = new Dog();
System.out.println(animal);

我们从子类型Dog转换为父类型Animal。 这是不安全的,并且始终是安全的。

// ClassCastException
// Mammal mammal = (Mammal) new Animal();

Animal向下广播到Mammal会导致ClassCastException

var returned = getRandomAnimal();

if (returned instanceof Cat) {

    Cat cat = (Cat) returned;
    System.out.println(cat);
} else if (returned instanceof Dog) {

    Dog dog = (Dog) returned;
    System.out.println(dog);
} else if (returned instanceof Mammal) {

    Mammal mammal = (Mammal) returned;
    System.out.println(mammal);
} else {

    Animal animal2 = returned;
    System.out.println(animal2);
}

为了执行合法的向下转换,我们需要首先使用instanceof运算符检查对象的类型。

private static Animal getRandomAnimal() {

    int val = new Random().nextInt(4) + 1;

    Animal anim = switch (val) {

        case 2 -> new Mammal();
        case 3 -> new Dog();
        case 4 -> new Cat();
        default -> new Animal();
    };

    return anim;
}

getRandomAnimal()使用 Java 的switch表达式返回随机动物。

Java 字符串转换

在数字和字符串之间执行字符串转换在编程中非常常见。 不允许进行强制转换操作,因为字符串和基本类型在根本上是不同的类型。 有几种执行字符串转换的方法。 +运算符还具有自动字符串转换功能。

本教程的“字符串”一章将介绍有关字符串转换的更多信息。

String s = (String) 15; // compilation error
int i = (int) "25"; // compilation error

不能在数字和字符串之间进行强制转换。 相反,我们有各种方法可以在数字和字符串之间进行转换。

short age = Short.parseShort("35");
int salary = Integer.parseInt("2400");
float height = Float.parseFloat("172.34");
double weight = Double.parseDouble("55.6");

包装类的parse方法将字符串转换为原始类型。

Short age = Short.valueOf("35");
Integer salary = Integer.valueOf("2400");
Float height = Float.valueOf("172.34");
Double weight = Double.valueOf("55.6");

valueOf()方法从原始类型返回包装器类。

int age = 17;
double weight = 55.3;
String v1 = String.valueOf(age);
String v2 = String.valueOf(weight);

String类具有用于将各种类型转换为字符串的valueOf()方法。

当使用+运算符并且一个运算符是一个字符串,另一个运算符不是一个字符串时,会自动进行字符串转换。 +的非字符串操作数将转换为字符串。

com/zetcode/AutomaticStringConversion.java

package com.zetcode;

public class AutomaticStringConversion {

    public static void main(String[] args) {

        String name = "Jane";
        short age = 17;

        System.out.println(name + " is " +  age + " years old.\n");
    }
}

在示例中,我们有String数据类型和short数据类型。 使用+运算符将这两种类型连接成一个句子。

System.out.println(name + " is " +  age + " years old.");

在表达式中,age变量被转换为String类型。

$ java AutomaticStringConversion.java
Jane is 17 years old.

这是示例输出。

在 Java 教程的这一部分中,我们介绍了包装器类,装箱和拆箱,默认值,转换和促销。



回到顶部