java如何初始化类对象
Java中的类和对象必须在使用前进行初始化。 您之前已经了解到 ,在加载类时,将类字段初始化为默认值,并且通过构造函数初始化了对象,但是还有更多要初始化的内容。 本文介绍了Java的所有用于初始化类和对象的功能。
如何初始化Java类
在探讨Java对类初始化的支持之前,让我们回顾一下初始化Java类的步骤。 考虑清单1。
清单1.将类字段初始化为默认值
class SomeClass
{
static boolean b;
static byte by;
static char c;
static double d;
static float f;
static int i;
static long l;
static short s;
static String st;
}
清单1声明类SomeClass
。 此类声明九个类型为boolean
, byte
, char
, double
, float
, int
, long
, short
和String
字段。 加载SomeClass
,每个字段的位都设置为零,您可以按如下解释:
false
0
\u0000
0.0
0.0
0
0
0
null
先前的类字段隐式初始化为零。 但是,您还可以通过直接为它们分配值来显式初始化类字段,如清单2所示。
清单2.将类字段初始化为显式值
class SomeClass
{
static boolean b = true;
static byte by = 1;
static char c = 'A';
static double d = 2.0;
static float f = 3.0f;
static int i = 4;
static long l = 5000000000L;
static short s = 20000;
static String st = "abc";
}
每个分配的值必须与类字段的类型兼容。 每个变量直接存储值, st
除外。 变量st
存储对包含abc
的String
对象的引用。
引用类字段
初始化类字段时,将其初始化为先前初始化的类字段的值是合法的。 例如,清单3将y
初始化为x
的值。 两个字段都初始化为2
。
清单3.引用先前声明的字段
class SomeClass
{
static int x = 2;
static int y = x;
public static void main(String[] args)
{
System.out.println(x);
System.out.println(y);
}
}
但是,相反的做法是不合法的:您无法将类字段初始化为随后声明的类字段的值。 Java编译器在遇到这种情况时会输出illegal forward reference
。 考虑清单4。
清单4.尝试引用随后声明的字段
class SomeClass
{
static int x = y;
static int y = 2;
public static void main(String[] args)
{
System.out.println(x);
System.out.println(y);
}
}
当编译器遇到static int x = y;
时,它将报告illegal forward reference
static int x = y;
。 这是因为源代码是从上到下编译的,并且编译器尚未看到y
。 (如果未明确初始化y
,它也会输出此消息。)
类初始化块
在某些情况下,您可能需要执行复杂的基于类的初始化。 您将在加载类之后,从该类创建任何对象之前(假定该类不是实用程序类)执行此操作。 您可以将类初始化块用于此任务。
类初始化块是语句块,其前面是引入到类主体中的static
关键字。 当类加载时,将执行这些语句。 考虑清单5。
清单5.初始化正弦和余弦值的数组
class Graphics
{
static double[] sines, cosines;
static
{
sines = new double[360];
cosines = new double[360];
for (int i = 0; i < sines.length; i++)
{
sines[i] = Math.sin(Math.toRadians(i));
cosines[i] = Math.cos(Math.toRadians(i));
}
}
}
清单5声明了一个Graphics
类,该类声明了sines
和cosines
数组变量。 它还声明了一个类初始化块,该块创建了360个元素的数组,这些数组的引用分配给sines
和cosines
。 然后,通过调用Math
类的sin()
和cos()
方法,使用for
语句将这些数组元素初始化为适当的正弦和余弦值。 ( Math
是Java标准类库的一部分。我将在以后的文章中讨论此类和这些方法。)
性能技巧
因为性能对图形应用程序很重要,并且访问数组元素比调用方法要快,所以开发人员诉诸性能技巧,例如创建和初始化正弦和余弦数组。
组合类字段初始化器和类初始化块
您可以在应用程序中组合多个类字段初始化器和类初始化块。 清单6提供了一个示例。
清单6.以自上而下的顺序执行类初始化
class MCFICIB
{
static int x = 10;
static double temp = 98.6;
static
{
System.out.println("x = " + x);
temp = (temp - 32) * 5.0/9.0; // convert to Celsius
System.out.println("temp = " + temp);
}
static int y = x + 5;
static
{
System.out.println("y = " + y);
}
public static void main(String[] args)
{
}
}
清单6声明并初始化一对类字段( x
和y
),并声明一对static
初始化器。 如下所示编译此清单:
javac MCFICIB.java
然后运行生成的应用程序:
java MCFICIB
您应该观察以下输出:
x = 10
temp = 37.0
y = 15
此输出显示以自顶向下的顺序执行类初始化。
<clinit>()方法
编译类初始化程序和类初始化块时,Java编译器将编译后的字节码(按自上而下的顺序)存储在名为<clinit>()
的特殊方法中。 尖括号可防止名称冲突 :您不能在源代码中声明<clinit>()
方法,因为<
和>
字符在标识符上下文中是非法的。
加载类后,JVM会在调用main()
之前main()
存在main()
情况下)先调用此方法。
让我们看一下MCFICIB.class
。 以下部分反汇编显示了x
, temp
和y
字段的存储信息:
Field #1
00000290 Access Flags ACC_STATIC
00000292 Name x
00000294 Descriptor I
00000296 Attributes Count 0
Field #2
00000298 Access Flags ACC_STATIC
0000029a Name temp
0000029c Descriptor D
0000029e Attributes Count 0
Field #3
000002a0 Access Flags ACC_STATIC
000002a2 Name y
000002a4 Descriptor I
000002a6 Attributes Count 0
Descriptor
行标识该字段的JVM 类型描述符 。 该类型用单个字母表示: I
表示int
, D
表示double
。
以下部分反汇编揭示了<clinit>()
方法的字节码指令序列。 每行以一个十进制数字开头,该数字标识后续指令的从零开始的偏移地址:
0 bipush 10
2 putstatic MCFICIB/x I
5 ldc2_w #98.6
8 putstatic MCFICIB/temp D
11 getstatic java/lang/System/out Ljava/io/PrintStream;
14 new java/lang/StringBuilder
17 dup
18 invokespecial java/lang/StringBuilder/<init>()V
21 ldc "x = "
23 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
26 getstatic MCFICIB/x I
29 invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
32 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
35 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
38 getstatic MCFICIB/temp D
41 ldc2_w #32
44 dsub
45 ldc2_w #5
48 dmul
49 ldc2_w #9
52 ddiv
53 putstatic MCFICIB/temp D
56 getstatic java/lang/System/out Ljava/io/PrintStream;
59 new java/lang/StringBuilder
62 dup
63 invokespecial java/lang/StringBuilder/<init>()V
66 ldc "temp = "
68 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
71 getstatic MCFICIB/temp D
74 invokevirtual java/lang/StringBuilder/append(D)Ljava/lang/StringBuilder;
77 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
80 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
83 getstatic MCFICIB/x I
86 iconst_5
87 iadd
88 putstatic MCFICIB/y I
91 getstatic java/lang/System/out Ljava/io/PrintStream;
94 new java/lang/StringBuilder
97 dup
98 invokespecial java/lang/StringBuilder/<init>()V
101 ldc "y = "
103 invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
106 getstatic MCFICIB/y I
109 invokevirtual java/lang/StringBuilder/append(I)Ljava/lang/StringBuilder;
112 invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
115 invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
118 return
从偏移量0到偏移量2的指令序列等效于以下类字段初始化器:
static int x = 10;
从偏移量5到偏移量8的指令序列等效于以下类字段初始化器:
static double temp = 98.6;
从偏移量11到偏移量80的指令序列等效于以下类初始化块:
static
{
System.out.println("x = " + x);
temp = (temp - 32) * 5.0/9.0; // convert to Celsius
System.out.println("temp = " + temp);
}
从偏移量83到偏移量88的指令序列等效于以下类字段初始化程序:
static int y = x + 5;
从偏移量91到偏移量115的指令序列等效于以下类初始化块:
static
{
System.out.println("y = " + y);
}
最后,偏移量为118的return
指令将执行从<clinit>()
返回到JVM的调用此方法的部分。
不用担心字节码的含义
此练习的收获是看到清单6的类字段初始化器和类初始化块中的所有代码都位于<clinit>()
方法中,并以自上而下的顺序执行。
如何初始化对象
加载并初始化一个类后,您通常会希望从该类创建对象。 正如您在我最近对使用类和对象进行编程的介绍中所了解的那样,您可以通过放置在类的构造函数中的代码来初始化对象。 考虑清单7。
清单7.使用构造函数初始化一个对象
class City
{
private String name;
int population;
City(String name, int population)
{
this.name = name;
this.population = population;
}
@Override
public String toString()
{
return name + ": " + population;
}
public static void main(String[] args)
{
City newYork = new City("New York", 8491079);
System.out.println(newYork); // Output: New York: 8491079
}
}
清单7声明了一个带有name
和population
字段的City
类。 创建City
对象时,将调用City(String name, int population)
构造函数,以将这些字段初始化为被调用构造函数的参数。 (我还重写了Object
的public String toString()
方法,以方便地以字符串形式返回城市名称和人口值System.out.println()
最终调用此方法以返回对象的字符串表示形式,并由它输出。 )
在调用构造函数之前, name
和population
包含哪些值? 您可以通过插入System.out.println(this.name); System.out.println(this.population);
来查找System.out.println(this.name); System.out.println(this.population);
System.out.println(this.name); System.out.println(this.population);
在构造函数的开头。 在编译了源代码( javac City.java
)并运行了应用程序( java City
)之后,您将观察到null
表示name
, 0
表示population
。 new
运算符在执行构造函数之前将对象的object(实例)字段清零。
与类字段一样,您可以显式初始化对象字段。 例如,您可以指定String name = "New York";
或int population = 8491079;
。 但是,这样做通常无济于事,因为这些字段将在构造函数中初始化。 我唯一想到的好处是为对象字段分配了默认值。 当您调用未初始化字段的构造函数时,将使用此值:
int numDoors = 4; // default value assigned to numDoors
Car(String make, String model, int year)
{
this(make, model, year, numDoors);
}
Car(String make, String model, int year, int numDoors)
{
this.make = make;
this.model = model;
this.year = year;
this.numDoors = numDoors;
}
对象初始化反映了类的初始化
翻译自: https://www.infoworld.com/article/3040564/java-101-class-and-object-initialization-in-java.html
java如何初始化类对象