JAVA语言规范 JAVA SE 8 - 类型、值和变量
Java编程语言是一种静态类型语言
,这意味着每个变量和每个表达式都具有在编译时就明确的类型。
Java编程语言还是一种强类型语言
,因为类型限制了变量可以持有的值和表达式可以产生的值,限定了在这些值上可以进行的操作,并且确定了这些操作的含义。强静态类型机制有助于在编译时探测错误
。
Java编程语言的类型可以分成两类:简单类型
和引用类型
。简单类型包括 boolean
类型和数字
类型,其中数字类型包括整数类型byte、short、int、long和char
, 以及浮点数类型float和double
。引用类型包括类类型
、接口类型
和数组类型
, 另外还有一个特殊的空类型
。对象
是动态创建的类类型的实例或者是动态创建的数组,而引用类型的值是对对象的引用
。所有对象,包括数组在内,都支持Object类的方法。字符串字面常量是用String对象表示的。
类型和值的种类
在Java编程语言中有两种类型:简单类型和引用类型。相应地, 有两种数据值
,它们可以被存储在变量中
,被当作引元传递
、作为返回值
,或者在其上执行操作
,它们就是简单值
和引用值
。
还有一种特殊的空类型
,即表达式null的类型,它没有 名字。
因为空类型没有名字,所以不能声明一个变量是空类型的
,也不能将变量类型转换为空类型
。
空引用
只能是空类型表达式的值。
空引用总是可以被赋值给或被类型转换为任何引用类型。
在实践中,程序员可以忽略空类型,只需认为空仅仅是一个特殊的字面常量,它可以具有任何引用类型。
简单类型和值
简单类型是Java编程语言预定义
,并且以其保留关键字来命名的
简单类型值 互相
之间并不共享状态
。
数字类型
包括整数
类型和浮点数
类型。
整数
类型包括byte
、short
、int
和long
,它们的值分别是8位、16位、32位和64位
有符号二进制补码表示
的整数;char
也是一种整数类型,它的值是16位无符号整数
,表示 UTF-16码元
。
浮点数
类型包括float
和double
,前者的值包括32位 IEEE 754浮点数
,而后者的值包括64位 IEEE 754浮点数
。
boolean
类型只有两个值:true
和false
。
整数类型和值
整数类型的取值范围分别如下:
对于byte
类型,是从-128到127的闭区间
。
对于short
类型,是从-32 768到32 767的闭区间
。
对于int
类型,是从-2 147 483 648到2 147 483647的闭区间
。
对于 long
类型,是从-9 223 372 036 854 775 808 到 9 223 372 036 854 775 807 的闭区间
。
对于char
类型,是从 ‘\u0000
’ 到 ‘\uffff
’ 的闭区间
,即从0 到 65 535
。
整数操作
Java编程语言提供了大量可作用于整数值的操作符
,包括:
1.比较操作符
,它们会产生boolean类型的值:
数字`比较`操作符 <、<=、> 和 >=
数字`判等`操作符==和!=
2.数字操作符
,它们会产生int或long类型的值:
一元加减操作符 + 和 -
乘除操作符 *、/ 和 %
加减操作符 + 和 -
递增操作符 ++,包括前缀式和后缀式
递减操作符 --,包括前缀式和后缀式
有符号和无符号移位操作符 << 、>> 和 >>>
位运算补码操作符 ~
整数位操作符 & 、^ 和 !
3.条件操作符
? :
。
4.类型转换操作符
,可以将整数值转换为任何指定的数字类型。
5.字符串连接操作符
+ ,当给定一个String类型操作数和一个整数操作数时,该操作符会将整数操作数转换为以十进制形式表示这个数值的String, 然后将两个字符串连接起来产生一个新的字符串。
在Byte
、Short
、Integer
、Long
和Character
类中还预定义了其他有用的构造器、方法和常量。
对于除移位操作符之外
的整数操作符,如果
其至少有一个操作数是long类型的
,那么该操作就会按照64位精度执行
,并且该数值操作符
的结果
也是long类型
。如果另一个操作数不是long类型的
,那么它首先会通过数字上下文被拓宽到 long类型
。
否则,该操作就会按照32位精度执行
,并且该数值操作符的结果
也是int类型
的。如果两个操作数其中之一不是int类型的,那么它首先会通过数字提升被拓宽到int类型
。
整数类型的任何值都可以通过类型转换
与其他任何数字类型进行双向转换
,但是在整数类型和boolean类型之间是无法转换的
。
整数操作符不会以任何方式提示上溢或下溢
。
整数操作符
会因为 下列原因
而 抛出异常
:
1.如果需要对空引用进行拆箱转换
,那么任何整数操作符都会抛出 NullPointerException
。
2.如果右操作数是0
,那么整数除法操作符 / 和整数取余操作符 % 都会抛出 ArithmeticException
。
3.如果需要进行装箱转换
,但是没有足够的内存可用来执行该转换
,那么递增和递减操作符 ++ 与 -- 就会抛出 OutOfMemoryError
。
整数操作代码示例:
class Test {
public static void main(String[] args){
int i = 1000000;
System.out.printin(i * i);
long l = i;
System.out.printin(l * l);
System.out.printin(20296 / (l - i));
}
}
这个程序会产生下面的输出:
-727379968
1000000000000
并且在执行到除以 l-i
时会产生ArithmeticException
异常,因为等于0。程序中第一 个乘法是按照32位精度执行的,而第二个乘法是一个long乘法。产生-727379968
这样的数值输出的原因是因为这个乘法的数学结果应该是1 000 000 000 000
,它对于int类型来说太大了,以至于产生了溢出,而-727379968
正是正确结果的低32表示的十进制数值
。
浮点数类型、格式和值
浮点数类型包括float和double,它们在概念上分别对应单精度32位和双精度64位格式的IEEE 754数值以及由IEEE二进制浮点数算术运算标准,即ANSI/IEEE 754标准754- 1985 (IEEE,纽约)指定的操作。
IEEE 754标准
不仅包含由一个符号位
和一个量值
构成的正负数字
,而且还包含正负0、 正负无穷
,以及特殊的“NaN”值
(表示非数字Not a Number)。NaN值用来表示某些无效操作的结果,例如0除0。float
和 double
的 NaN常量
被预定义为Float.NaN
和Double.NaN
。
Java编程语言的每个实现都需要支持浮点值的两个标准集
,即单精度浮点值集
(float value set)和双精度浮点值集
(double value set)。另外,任何Java编程语言的实现可以同时支持两个扩展指数浮点值集
,或者只支持其中之一
,它们是单精度扩展指数值集
(float- extended-exponent value set)和双精度扩展指数值集
(double-extended-exponent value set)。这些扩展指数值集可以在某些情况下替代标准值集
,用来表示类型为float和double的表达式的值。
任何浮点值集中的有限非0值全部都可以表示成 s . m . 2 e − N + 1 s.m.2^{e - N +1} s.m.2e−N+1 的形式,其中s是 +1 或-1, m是小于 2 N 2^{N} 2N的正整数,e是位于从 E m i n = − ( 2 K − 1 − 1 ) E{_{min}}= -(2^{K-1} - 1) Emin=−(2K−1−1) 到 E m a x = 2 K − 1 − 1 E{_{max}}= 2^{K-1} - 1 Emax=2K−1−1 的闭区间中的整 数,其中N和K是依赖于所用的值集的参数。按照这种形式,某些值可能会有多种表示方式。例如,假设值集中某个值v可以用s、m和e的恰当取值来按照这种形式表示,那么当m是偶数,e小于 2 K − 1 2^{K-1} 2K−1 时会怎样呢?我们可以将m取值减半,然后给e增加1,这样就会产生v的第二种表示。在符合这种形式的表示中,如果m>= 2 K − 1 2^{K-1} 2K−1,那么这种表示就称为规格化
的表示,否则,这种表示就称为非规格化
的表示。如果值集中的某个值不能按照m>= 2 K − 1 2^{K-1} 2K−1的 方式来表示,那么这个值就称为非规格化值
,因为它没有任何规格化的表示。
对于两个必须支持的值集,以及两个扩展的浮点值集来说,在参数N和K上的约束(以及在推导出的参数 E m i n E{_{min}} Emin 到 E m a x E{_{max}} Emax上的约束)如下表所示。
表4-1 浮点值集参数
参数 | 单精度 | 单精度扩展指数 | 双精度 | 双精度扩展指数 |
---|---|---|---|---|
N | 24 | 24 | 53 | 53 |
K | 8 | >=11 | 11 | >=15 |
E m i n E{_{min}} Emin | +127 | >=+1023 | +1023 | >=-16383 |
E m a x E{_{max}} Emax | -126 | <=-1022 | -1022 | <=-16382 |
如果某个Java实现支持一个或同时支持两个扩展指数值集,那么对于所支持的每一个 扩展指数值集,都有一个依赖于该实现的特定的常数K,它的值由 上表 限定,而这个值也就决定了 E m i n E{_{min}} Emin 和 E m a x E{_{max}} Emax 的值。
这四个值集每一个都不仅包含上述有限非0值,并且还包含NaN值和四个分别表示正 0、负0、正无穷和负无穷的值。
需要注意的是,表4-1中的约束条件的设计方式,是为了使得单精度浮点值集的每个元素也必然都是单精度扩展指数值集、双精度浮点值集和双精度扩展指数值集的元素
。类似地,双精度浮点值集的每个元素也必然都是双精度扩展指数值集的元素
。每个扩展指数值集的指数取值范围都比对应的标准值集要大,但是其精度并不会提高
。
单精度浮点值集的元素就是可以用IEEE 754标准中定义的单精度浮点数格式表示的值, 而双精度浮点值集就是可以用IEEE 754标准中定义的双精度浮点数格式表示的值。但是要注意,这里定义的单精度浮点扩展指数值集和双精度浮点扩展指数值集并没有分别对应于可以用IEEE 754单精度扩展和双精度扩展格式表示的值
。
单精度浮点、单精度扩展指数浮点、双精度浮点和双精度扩展指数浮点值集都不是类型
。对于Java编程语言的实现来说,使用单精度浮点值集的元素来表示类型的值总是 正确的。但是,在某些特定的代码部分使用单精度扩展指数浮点集的元素来替换也是允许的。类似地,对于Java编程语言的实现来说,使用双精度浮点值集的元素来表示double类型的值总是正确的。但是,在某些特定的代码部分使用双精度扩展指数浮点集的元素来替换也是允许的。
除了 NaN, 浮点数都是有序的
,按照从小到大排列
,分别是负无穷、负有穷非0值、 正0和负0、正有穷非0值,以及正无穷
。
IEEE 754允许为NaN定义多个不同的值
,每一种都有单精度和双精度两种浮点格式。 尽管在产生新的NaN时,每一种硬件架构都会返回其特有的NaN位模式
,但是程序员还是 可以创建具有不同位模式的NaN
,用于对诸如回归诊断信息
等内容进行编码。
基本上,JavaSE平台会将给定类型的NaN值当作单一的规范值处理,因此本规范通常会引用任意一个NaN值当作是对这个规范值的引用。
但是,Java SE的1.3版本中定义了可以让程序员对NaN值进行区分的方法,即: Float .floatToRawIntBits
和 Double. doubleToRawLongBits
方法。感兴趣的读者可以参考 Float类和Double类的规范以了解更多信息。
正0和负0相比较结果
是相等
,因此表达式0.0==-0.0
的结果是true,而0.0>-0.0
的 结果是false。但是其他操作可以区分正0和负0
,例如,1.0/0.0的值是正无穷
,但是 1.0/-0.0的结果是负无穷
。
NaN是无序的
,因此:
1.如果操作数中有一个或两个同时是NaN,则数字比较操作符 <、<=、> 和>=返回 false。
2.如果操作数中有一个是NaN,则判等操作符 == 返回false。
特别地,如果x或y是NaN,则(x<y) == !(x>=y)为false。
3.如果操作数中有一个是NaN,则判不等操作符 != 返回true 。
特别地,当且仅当x是NaN,则x != x 为true。
浮点数操作
Java编程语言提供了大量可作用于浮点值的操作符,包括:
1.比较操作符
,它们会产生boolean类型的值:
数字比较操作符 <、<=、> 和 >=
数字判等操作符 == 和 !=
2.数字操作符
,它们会产生float或double类型的值:
一元正负操作符+和-
乘除操作符*、/和%
加减操作符+和-
递增操作符++,包括前缀式和后缀式
递减操作符--,包括前缀式和后缀式
3.条件操作符
?:。
4.类型转换操作符
,可以将浮点值转换为任何指定的数字类型
。
5.字符串连接操作符
+,当给定一个String类型操作数和一个浮点操作数时,该操作符会将浮点操作数转换为以十进制形式(不丢失任何信息)表示这个数值的String,然后将两个字符串连接起来产生一个新的字符串。
在Float, Double和Math类中还预定义了其他的一些构造器、方法和常量。
如果二元操作符的操作数至少有1个是浮点类型的,那么该操作就是浮点操作,即使另一个操作数是整数也是如此。
如果数值操作符的操作数至少有一个是double类型的,那么该操作就会按照64位浮点算术运算执行,并且该数值操作符的结果也是double类型的。如果另一个操作数不是 double类型的,那么它首先会通过数字提升被拓宽
到double类型。
否则,该操作就会按照32位浮点算术运算执行,并且该数值操作符的结果也是float类型的。如果另一个操作数不是float类型的,那么它首先会通过数字提升被拓宽
到float类型。
浮点类型的任何值都可以通过类型转换
与其他任何数字类型进行双向转换,但是在浮点类型和boolean类型之间是无法转换的
。
作用于浮点数的操作符将按照IEEE 754规定的方式执行(只有取余操作符例外)。特别地,Java编程语言要求支持IEEE 754的非规格化浮点数
和渐进下溢
,这 可以使得对特定的数值算术运算所具有属性的验证变得更加容易。如果浮点操作的运算结果是非规格化数,它不会进行“置0 (flush to zero)
"操作。
Java编程语言要求浮点算术运算
的行为必须遵循这样的规则
,即每个浮点操作符都会将其浮点运算结果舍入到所需的精度
,这个不精确的结果必须是无限精确的结果经过舍入得到的最接近于它的可表示值
。如果经舍入后,有两个最接近的可表示值与无限精确的结果的距离是相等的,那么就会选择最低有效位为0的那个可表示值
。这就是IEEE 754标准的缺省舍入模式
,称为“最近舍入
”。
Java编程语言在将浮点值向整数转换
时,会使用向0舍入的模式
,在这种情况下,会将数字截尾,即丢弃尾部的位
。向0舍入会对结果进行选择,其结果的格式所表示的值在量级上最接近并且不大于无限精确的结果。
上溢的浮点操作会产生一个有符号的无穷。
下溢的浮点操作会产生一个非规格化的值或有符号的0。
其结果没有数学定义的浮点操作会产生一个NaN。
操作数中有NaN的所有数值操作都会产生一个NaN作为结果。
浮点操作符
会因为 下列原因
而 抛出异常
:
1.如果需要对空引用进行拆箱转换
,那么任何浮点操作符都会抛出 NullPointerException
。
2.如果需要进行装箱转换
,但是没有足够的内存
可用来执行该转换,那么递增和递减 操作符++ 与--
就会抛出 OutOfMemoryError
。
浮点操作范例
class Test {
public static void main(String[] args) {
// An example of overflow:
double d = 1e308;
System.out.print("overflow produces infinity:");
System.out.printin(d + "*10==" + d*10);
// An example of gradual underflow:
d = 1e-305 * Math.PI;
System.out.print("gradual underflow: " + d + "\n ");
for (int i = 0; i < 4; i++)
System.out.print(" "+ (d /= 100000));
System.out.printin();
// An example of NaN:
System.out.print("0.0/0.0 is Not-a-Number:");
d = 0.0/0.0;
System.out.printin(d);
//An example of inexact results and rounding:
System.out.print("inexact results with float:");
for (int i = 0; i < 100; i++) {
float z = 1.0f / i;
if (z * i != 1.0f)
System.out.print(" " + i);
}
System.out.printin();
// Another example of inexact results and rounding:
System.out.print("inexact results with double:");
for (int i = 0; i < 100; i++) {
double z = 1.0 / i;
if (z * i != 1.0)
System.out.print(" " + i);
}
System.out.printin();
// An example of cast to integer rounding:
System.out.print("cast to int rounds toward 0:");
d = 12345.6;
System.out.printin((int)d + " " + (int)(-d));
}
}
这个程序会产生下面的输出:
overflow produces infinity: 1.0e+308*10==Infinity
gradual underflow: 3.141592653589793E-305
3.1415926535898E-310 3.141592653E-315 3.142E-320 0.0
0.0/0.0 is Not-a-Number: NaN
inexact results with float: 0 41 47 55 61 82 83 94 97
inexact results with double: 0 49 98
cast to int rounds toward 0: 12345 -12345
这个示例说明,和其他操作相比,渐进下溢会产生精度的渐进丢失
。
当i为0时,其结果涉及被0除,因此z会变成正无穷,而z*0会变成并不等于1.0的NaN。
boolean类型和布尔值
boolean类型表ZK―种逻辑量,它有两种可能的取值,由字面常量true和false表示。
布尔操作符包括:
1.判等操作符
== 和 !=。
2.逻辑取反操作符
!。
3.逻辑操作符
&、^ 和 !。
4.条件与
和 条件或操作符
&& 和 || 。
5.条件操作符
?:。
6.字符串连接操作符
+ ,当给定一个String类型操作数和一个boolean 操作数时,该操作符会将boolean操作数转换为String ( true或false),然后将两个字符串连接起来产生一个新的字符串。
布尔表达式在下列几种语句中可用来确定控制流:
if语句
while语句
do语句
for语句
boolean表达式还可以确定条件操作符 ?:中哪一个子表达式会用来计算。
只有boolean或Boolean表达式才能用于控制流语句和用作条件操作符 ?:的第一个操作数。
整数或浮点表达式x可以通过x!=0这样的表达式转换为boolean值,这遵循了C语言的习惯,即非0的值表示true。
对象引用obj可以通过obj !=null这样的表达式转换为boolean值,这遵循了C语言的习惯,即任何除null之外的引用都是true。
boolean值可以通过字符串转换转换为String值
。
将boolean值类型转换为boolean、Boolean和Object类型是允许的
,而对boolean类型做其他任何类型转换都是不允许的
。
引用类型和值
Java有四种引用类型:类类型、接口类型、类型变量和数组类型。
下面的样例代码:
class Point {
int[] metrics; }
interface Move{
void move(int deltax, int deltay); }
声明了一个类类型Point, 一个接口类型Move,并且用一个数组类型int[](整型数组)声 明 了 Point 类的 metrics 域。
类或接口类型由一个标识符或由圆点分隔的标识符序列构成,其中每个标识符后面都可选地包含类型引元
。如果类型引元在类或接口类型的任何地方出现,那么该类 型就是一个参数化类型
。
在类或接口类型中的每个标识符都会被当作包名或类型名,其中被当作类型名的标识符可以被注解。如果某个类或接口类型的形式为T.id (后面可选地跟着类型引元),那么id必须是T的可访问成员类型的简单名,否则就会产生编译时错误,而该类或接口类型表示的就是该成员类型。
对象
对象是类的实例或数组
。
引用值
(经常直接简称“引用
”)是指向这些对象的指针
,并且有一个特殊的空引用
, 它不指向任何对象
。
类实例
是显式地通过类实例创建表达式
创建的。
数组
是显式地通过数组创建表达式
创建的。
如果字符串连接操作符+
用于非常量
表达式中,那么就 会隐式地创建一个新的类实例
,从而产生一个string类型的新对象
。
如果对数组初始化器表达式求值
,那么就会隐式地创建一个新的数组对象
。这会发生在下列情况中:初始化类或接口,创建类的新实例, 以及执行局部变量声明语句。
Boolean、Byte、Short、Character、Integer、Long、Float 和 Double 类型的新对象可以通过装箱转换
被隐式地创建
。
对象创建
class Point {
int x, y;
Point() {
System.out.printin("default"); }
Point(int x, int y) {
this.x = x; this.y = y; }
/* A Point instance is explicitly created at
class initialization time: */
static Point origin = new Point(0, 0);
/* A String can be implicitly created by a *
operator: */
public String toString() {
return "(" + x + "," + y +")" }
}
class Test{
public static void main(String[] args) {
/* A Point is explicitly created
using newlnstance: */
Point p = null;
try {
p = (Point)Class.forName("Point"). newlnstance();
} catch(Exception e){
System.out.printin(e);
}
/* An array is implicitly created
by an array constructor: */
Point a[] = {
new Point(0,0), new Point(1,1) };
/* Strings are implicitly created
by + operators: */
System.out.prihtln("p: " + p);
System.out.printin("a: { " + a[0] + ", " + a[l] + " }" );
/* An array is explicitly created
by an array creation expression: */
String sa[] = new String[2];
sa[0] ="he"; sa[l] ="llo";
System.out.printIn(sa[0] + sa [1]);
}
}
这个程序会产生下面的输出:
default
p:(0,0)
a: {
(0,0), (1,1) }
hello
作用于对象引用的操作符有:
1.使用限定名
或域访问表达式
的域访问
。
2.方法调用
。
3.类型转换操作符
。
4.字符串连接操作符
+,当给定一个String类型操作数和一个引用时, 该操作符会调用被引用对象的tostring方法,将引用转换为String (如果该引用或 toString的结果是空引用,则使用“null”),然后将两个字符串连接起来产生一个新的字符串。
5.instanceof 操作符
。
6.引用判等操作符
== 和 !=。
7.条件操作符
?:。
多个引用可以指向同一个对象
。绝大多数对象都具有状态
,这些状态存储在对象的域中
,而这些对象是类的实例;或者存储在变量中
,而这些变量是数组对象的成员。如果两个变量包含指向同一个对象的引用,那么当通过其中一个变量的对象引用修改该对象的状态时,通过另一个变量中的引用可以观察到变化后的状态
。
简单标识和引用标识
class Value {
int val; }
class Test {
public static void main(String[] args) {
int i1 = 3;
int 12 = i1;
i2 = 4;
System.out.print("i1==" + i1);
System.out.println(" but i2==" + i2);
Value v1 = new Value();
v1.val = 5;
Value v2 = v1;
v2.val = 6;
System.out.print("vl.val==" + v1.val);
System.out.println(" and v2.val==" + v2.val);
}
}
这个程序会产生下面的输出:
i1==3 but i2==4
v1.val==6 and v2.val==6
因为v1.val和v2.val引用的是由唯一的new表达式创建的Value对象里的同一个实例变量,而i1和i2是不同的变量。
每个对象都与一个监视器
关联,它会被synchronized方法
和 synchronized语句
用来对多线程并发访问对象状态
进行控制。
Object 类
Object类是所有其他类的超类。
所有类和数组类型都继承了Object类的方法,具体包括:
1.Clone方法
,用来创建对象的副本。
2.equals方法
,定义了对象判等的标准,该标准应该是基于值比较而不是引用比较的。
3.finalize方法
,在对象销毁前运行。
4.getClass方法
,返回表示对象所属类的Class对象。
对于每一个引用类型都存在Class对象。它有很多用处,例如用来发现某个类的完全限定名、它的成员、它的直接超类,以及它实现的所有接口。
getClass的方法调用表达式的类型是Class<? extends |T|>, 其中T是为getClass搜索到的类或接口。
被声明为synchronized的类方法
会在与该类的Class对象相关联的监视器上同步
。
5.hashcode方法
,它与equals方法一起,在诸如java.uti l.Hashmap这样的散列表中非常有用。
6.wait、notify和notifyAll方法
,在使用线程的并发编程中会用到它们。
7.toString方法
,返回对象的String表示。
String 类
String类的实例表示Unicode码位序列。
每个String对象都有常量值(不可修改)。
字符串字面常量是对String类的实例的引用。
如果字符串连接操作符运算的结果不是编译时的常量表达式
,那么该操作符会隐式地创建新的String对象
。
当引用类型相同时
如果两个引用类型具有相同的二进制名字
,并且如果它们有类型引元
的话, 通过递归地运用本定义
,也可以认为它们是相同
的,那么这两个引用类型就是相同的编译时类型
。
如果两个引用类型是相同的,有时我们会称其为同一个类
或同一个接口
。
在运行时
,若干个具有相同的二进制名字的引用类型可以被不同的类加载器同时加载
。 这些类型可以表示也可以不表示相同的类型声明
,而即便这样的两个类型表示相同的类型声明,它们也会被认为是有区别的
。
如果满足下列条件
,则两个引用类型
就是同一个运行时类型
:
1.它们都是类或接口类型
,由相同的类加载器定义
,并且有相同的二进制名字
,此时它们也被称为是同一个运行时类或同一个运行时接口。
2.它们都是数组类型
,并且其成员的类型也都是同一个运行时类型
。
类型变量
类型变量是在类、接口、方法和构造器中用作类型的非限定标识符。
类型变量可以声明
为泛化类声明
、泛化接口声明
、泛化方法声明
和泛化构造器声明
中的类型参数。
类型变量声明为类型参数时,其作用域在第6.3节中说明。
每个声明为类型参数
的类型变量
都有一个边界
。如果对某个类型变量没有声明任何边界,则默认其边界为Object。如果声明了边界,则它由下列两种情况构成:
1.单个类型变量T。
2.类或接口类型T,后面可能还有接口类型 I 1 α − I 1 n I{_1} α- I{_1}n I1α−I1n
如果 I 1 − I n I{_1} - I{_n} I1−In类型中任何一个是类类型或类型变量,那么就会产生一个编译时错误
。
对于边界来说,其所有构成类型的擦除
必须都是互不相同的,否则就会产 生一个编译时错误
。
如果两个接口类型是同一个泛化接口的不同参数化版本,那么类型变量就不能同时是这两个接口类型的子类型,否则就会产生一个编译时错误。
类型在边界中的顺序
只在两种情况下才显得重要,第一种是类型变量的擦除取决于边界中的第一个类型
;第二种是类类型或类型变量只能出现在第一个类型的位置
。
边界为 I 1 α − I 1 n I{_1} α- I{_1}n I1α−I1n 的类型变量X的成员,是由位于类型变量声明处的交集类型 I 1 α − I 1 n I{_1} α- I{_1}n I1α−I1n 的成员构成的。
类型变量的成员
package TypeVarMembers;
class C{
public void mCPublic() {
}
protected void mCProtected() {
}
void mCPackage() {
}
private void mCPrivate() {
}
}
interface I {
void mI();
}
class CT extends C implements I {
public void mI() {
}
}
class Test {
<T extends C & I> void test(T t) {
t.mI(); // OK
t.mCPublic(); // OK
t.mCProtected(); // OK
t.mCPackage(); // OK
t.mCPrivate(); // Compile-time error
}
}
类型变量T有交集类型C & I,
也就是与用等价的超类在相同的作用域中声明的空类CT具有相同的成员。接口的成员总是public的,因此总是可以被继承(除非被覆盖),所以mH 是CT和T的成员。在C的成员中,除了 mCPrivate之外的成员都被CT继承了,因此也就是 CT和T的成员。
如果C与T在不同的包中,那么对mCPackage的调用将会产生一个编译时错误,因为这个成员在T被声明的地方是不可访问的。
参数化类型
泛化类
或泛化接口的声明
定义了一个参数化类型集
。
参数化类型是