类型提升规则
Java定义了几个应用于表达式的类型提升规则:所有byte、short和char类型的值都被提升为int类型。如果有一个操作数是long类型,将这个表达式提升为 long
类型;如果有一个操作数是float类型,就将整个表达式提升为float类型;如果任何一个操作数为double类型,结果都为double。
break 语句作为goto语句的一种形式
break语句可以用于提供goto语句的一种“文明”形式。Java没有提供togo语句,因为goto语句可以随意进入另外一个程序分支,并且是一种非结构化的方法。
java定义了break语句的一种扩展形式,可以中断一个或多个代码块。这些代码块不必是某个循环或者switch语句的一部分,他们可以使任何代码块。因为这种形式的break
语句使用标签进行工作。
break label;
动态方法调用
方法重写形成了动态方法调度的基础,动态方法调度是Java中最强大的功能之一。动态方法调度可以在运行时,而不是在编译时解析对重写方法的调用。
这是java实现运行时多态的机理所在。
一个重要的原则:超类引用变量可以指向子类对象。java利用这一事实,在运行时解析对重写方法的调用。实现原理:当通过超类引用调用重写方法时,Java根据
在调用时所引用对象的类型来判断调用哪个版本的方法。因此,这个决定是在运行时做出的。当前正在引用的对象的类型决定了将要执行哪个版本的重写方法。
所以,如果超类包含被子类重写的方法,那么当通过超类引用变量引用不同类型的对象时,会执行不同版本的方法。
包(Package)
是类的多个容器,他们用于保持类的名称空间的相互隔离。
接口中的变量、默认方法、静态方法
可以使用接口将共享的变量导入到多个类中,具体方法是简单地声明包含变量的接口,并将变量初始化为期望的值。当在类中实现这种接口时,所有这些变量在
作用域内都会被作为常量。
默认方法允许为接口方法定义默认实现。开发默认方法的主要动机是提供一种扩展接口的方法,而不破坏现有代码。另外一个动机是希望在接口中指定本质上可选的方法,
根据接口的使用方式选择使用的方法。添加默认方法没有改变接口的关键特征:“不能维护状态信息”。
接口中的静态方法独立于任何对象调用。在调用静态方法时,不需要实现接口,也不需要接口的实例。实现接口的类或子类接口不会集成接口中的静态方法。
嵌套try语句
每次遇到try语句时,异常的上下文就会被推入到堆栈中。如果内层的try语句没有特定的异常提供catch
处理程序,堆栈就会弹出该try语句,检查下一条try语句的catch处理程序,这个过程一直继续下去。
链式异常
通过链式异常,可以为异常关联另一个异常。第二个异常描述第一个异常的原因。使用链式异常,Throwable类的两个构造函数和两个方法:
Throwable(Throwable causeExc)
Throwable(String msg, Throwable causeExc)
causeExc是引发当前异常的异常,第二种形式允许在指定引发异常的同时指定异常描述。
添加到Throwable类中的链式异常方法getCause()和initCause():
getCause()方法返回引发当前异常的异常。如果不存在背后的异常,就返回null
initCause()方法将causeExc和调用异常关联到一起,并返回对异常的引用。因此,可以在创建异常之后将异常和背后异常关联到一起。但是
背后异常只能设置一次,对于每个异常对象只能调用initCause()方法一次。如果通过构造函数设置引发了异常,那么就不能再使用initCause()方法
进行设置。
从JDK7开始,异常系统添加了3个有趣并且有用的特性:
1、当资源不在需要时自行释放,称为带资源的try语句,
2、多重捕获
try {...} catch(ArithmeticException | ArrayIndexOutBoundException) {...}
为了使用多重捕获,在catch子句使用或运算符(|)分割每个异常。每个多重捕获参数都被隐式声明为final。因此,不能为他们赋值。
3、最后重新抛出或更精确地重新抛出。
注解基础知识
java支持在源文件中嵌入补充信息,这类信息称为注解(annotation)。注解不会改变程序的动作,因此也就不会改变程序的语义。
@interface MyAnnotation {
//所有注解只包含方法声明。不能为这些方法提供方法体,而是由java实现这些方法。这些方法的行为更像是域变量。
//可以为注解成员提供默认值,应用注解时如果没有为注解成员指定值,就会使用默认值。
//
}
//当为注解成员提供数值时,只使用成员的名称
public static void myMeth() { //..
注解不能包含extends子句,但是,所有注解类型自动扩展Annotation接口。
注解保留策略决定了在什么位置丢弃注解,3中策略:
-SOURCE,只在源文件中保留,编译期间被抛弃
-CLASS,在编译器被存储到.class文件中。在运行期通过JVM不能得到这些注解
-RUNTIME,在编译器被存储到.class文件中,并且在运行时可以通过JVM获取这些注解。RUNTIME保留策略提供了最永久的策略。
局部变量生命的注解,不能保留在.class文件中。
@Rentention(RententionPolicy.RUNTIME)
@interface MyAnnotation {
String str(); //所有注解只包含方法声明。不能为这些方法提供方法体,而是由java实现这些方法。这些方法的行为更像是域变量。
int val();
}
AnnotatedElement接口,在运行时可以使用反射(getAnnotations()和getAnnotaion())获取注解。getAnnotations()和getAnnotaion()是由AnnotatedElement接口定义的。
这个注解支持注解反射,Method、Field、Constructor、Class及Package也都实现了该接口。
内置注解:@Target,只能注解其他注解,用于指定可以应用注解的声明的类型。
标记注解,是特殊类型的注解,其中不包含成员,其唯一目的就是标记声明
单成员注解,只包含一个成员。应用注解时可以简单地为该成员指定值,不需要指定成员的名称。为了使用这种缩写形式,成员名称必须是value.
MySingle(100) 没有具体指定“value=”
类型注解,JDK8中在能够使用类型的大多数地方,也可以指定注解。扩展后的这种注解称为类型注解。如,可以注解方法的返回类型、方法内置的this类型、
强制类型转换、数组级别、被继承的类及throws子句、泛型等。类型注解允许工具对代码执行额外的检查,避免错误。javac本身不执行这些检查,需要使用
单独的工具,不过这种工具可能需要作为编译器插件发挥作用。
类型注解必须包含ElementType.TYPE_USE作为目标。放在要应用该注解的类型的前面。
也可以对this的类型进行注解。this是所有实例方法的隐式参数,他引用的是调用对象。从JDK8开始,可以显示地将this声明为方法的第一个参数。在这种声明中,
this的类型必须是其类型。
class SomeClass {
int myMeth(SomeClass this, int i, int j) {//..
}
重复注解, JDK8新增的另一注解特性允许在相同元素上重复应用注解,这种特性称为重复注解。可重复注解必须用@Repeatable进行注释。
PrintWriter: 向文本输出流打印对象的格式化表示形式 ,可以使用程序国际化更容易。
自动关闭文件,JDK7增加了一个新特性,该特性有时被称为自动资源管理,该特性以try语句的扩展版为基础。优点是:当不再需要文件时,可以防止无意中释放他们。
try(resource-specification) {
//use the resource
}
resource-specification是用来声明和初始化资源的语句。该语句包含一个变量声明,在该变量声明中使用将被管理的对象引用初始化变量。当try代码块结束时,自动
释放资源。对于文件,这意味着会自动关闭文件。这样新形势的try语句被称为带资源的try语句。
只有对那些实现了AutoCloseable接口的资源,才能使用带资源的try语句。java.io包中的Closeable接口继承自AutoCloseable接口,所有的流类都实现了这两个接口。
因此,当使用流时——包括文件流,可以使用带资源的try语句。
//变量fin局限于try代码块,当进入try代码块时创建,当离开try代码块时,隐式地调用close方法关闭fin关联的流
//...
} catch () {
}
try语句中声明的资源被隐式声明为final,这意味着在创建资源变量后,不能将其他资源赋给该变量。此外,资源的作用域局限于带资源的try语句。可以在一条try语句中
管理多个资源,只需要简单地使用分号分隔每个资源约定即可。
try (FileInputStream fin = new FileInpuStream(...);
FileOutStream fout = new FileOutStream(...)) {....
还有另外一个方面需要提及:一般来说,当执行try代码块时,当在finally子句中关闭资源时,try代码块中的异常有可能导致发生另外一个异常,对于“常规”try语句,原始异常
丢失,被第二个取代。但是使用带资源的try语句时,第二个异常会被抑制,但是它没有丢失,而是被添加到与第一个异常相关联的一直异常列表中。通过使用throwable定义
的getSuppresed()方法可以获取异常列表。
静态导入
Java提供了静态导入特性,静态导入扩展了import关键字功能。通过在import后面添加static关键字,可以使用import语句导入类或接口的静态成员。使用静态导入可以直接
通过名称引用静态成员,而不必使用它们的类名进行限定,从而简化并缩短使用静态成员所需的语法。
通过this()调用重载的构造函数
通过this调用重载的构造函数对于阻止不必要的重复代码可能是有用的。对于非常短的构造函数是否使用this()对代码的大小通常没有影响。这是因为调用this()时设置和返回的
字节自对象文件中添加了指令。所以,this()最适合用于包含大量初始化代码的构造函数,而不适合用于那些只简单设置少量域变量值的构造函数。
使用this()需要牢记两条限制,首先,在调用this()时不能使用当前类的任何实例变量。其次,在同一个构造函数中不能同时使用super()和this(),因为他们都必须是构造函数中的
第一条语句。
泛型
(泛型参数T,通配符?)
有界通配符
通配符建立上界
<? extends superclass>
superclass是作为上界的类的名称。这一句是包含子句,因为行程上界的类也位于边界之内。
通配符建立下届
<? super subclass>
对于这种情况,只有subclass的超类是可接受参数。与subclass指定的类不匹配,这是一条排除子句。
泛型方法
泛型类中的方法可以使用类的类型参数,所以他们是自动相对于类型参数泛型化的。不过可以声明函数资深使用一个或者多个类型参数的泛型方法。
static<T extends Comparble<T>,V extends T> boolean isIn(T x, V[] y)
泛型构造函数
可以将构造函数泛型化,即使他们的类不是泛型类
class GenCons {
private double val;
<T extends Number> GenCons(T arg) {
val = arg;
}
因为GenCons()指定了一个泛型类型的参数,并且这个参数必须是Number的子类,所以可以使用任意数值类型调用GenCons(),
包括Integer、Float以及Double。虽然GenCons不是泛型类,但是它的构造函数可以泛化。
泛型接口
如果类实现了泛型接口,那么类也必须是泛型化的,至少需要带有将被传递给接口的类型参数。
//Wrong!
因为MyClass没有声明类型参数,所以无法为MinMax传递类型参数.对于这种情况,标识符T是未知的,编译器会报告错误。如果
类实现了某种具体类型的泛型接口如下
//OK
泛型类层次
泛型类可以是类层次的一部分,就像非泛型类那样。因此泛型类可以作为超类或者子类。泛型和非泛型层次之间的关键区别是:在泛型层次中,
类层次中的所有子类都必须向上传递超类所需要的所有类型参数,这与必须沿着类层次向上传递构造函数的参数类似。
class Gen<T> {
T ob;
Gen(T o) {
ob = o;
}
T getOb() {
return ob;
}
}
//Gen2指定的参数类型T也被传递给extends子句中的Gen,这意味着传递给Gen2的任务类型也会被传递给Gen.
除了将T传递给Gen超类外,Gen2没有再使用类型参数T。因此,即使泛型超类的子类不必泛型化,也仍然必须指定
泛型超类所需要的类型参数。如果需要的话,子类可以自由添加自己的类型参数。
}
}
泛型子类 ,非泛型类作为泛型子类的超类是完全是可以的。
泛型层次中的运行时类型比较
Gen<Integer> gen = new Gen<Integer>();
Gen2<Integer> gen2 = new Gen2<Integer>();
//OK
//不能被编译
在运行时不能使用泛型类型信息,所以instanceof无法知道gen2是否是Gen2<Integer>类型实例。
强制类型转换
只有当两个泛型类实例的类型相互兼容并且他们的类型参数也相同时,才能将其中的一个实例转换为另一个实例。
可以像重写其他任何方法那样重写泛型类的方法。
擦除
擦除的工作原理如下:编译java代码时,所有泛型信息被移除。这意味着使用它们的界定类型替换类型参数,如果没有显示地指定界定类型,
然后应用适当的类型转换,以保持与类型参数所指定类型的兼容性。编译器也会强制实现这种类型兼容性。使用这种方式
实现泛型,意味着在运行时没有类型参数。他们只是一种源代码机制。
桥接方法,编译器偶尔需要为类添加桥接方式,用于如下情形:子类中重写方法的类型擦除不能产生与超类中方法相同的擦除。对于这种情形
会生成使用超类类型擦除的方法,并且这个方法调用具有由子类指定的类型擦除的方法。当然,桥接方法只会再字节码级别发生,你不会看到,
也不能使用。
class Gen<T> {
T ob;
Gen(T o) {
ob = o;
}
T getob() {
return ob;
}
}
class Gen2 extends Gen<String> {
Gen2(String o) {
super(o);
}
String getob() {
System.out.println("You called String getob():");
}
}
class BridgeDemo {
public static void main(String[] arg) {
Gen2 strOb2 = new Gen2("Generics Test");
System.out.println(strOb2.getob());
}
}
Gen2扩展了Gen,对getob()方法进行了重写,指定了String作为返回类型。现在的麻烦是由类型擦除引起的,本来期望
一下形式的getob()方法:Object getob() {//...
为了处理这个问题,编译器生成一个桥接方法,这个桥接方法使用调用String版本的那个签名。因此,如果检查javap给Gen2生成的类文件,
就会看到以下方法:
class Gen2 extends Gen<java.lang.String> {
Gen2(java.lang.String);
java.lang.String gejob();
java.lang.Object getjob();//bridge methos
}
最后一点需要说明。注意两个getob之间唯一的区别是他们的返回类型。在正常情况下,这会导致错误,但是因为这种情况不是在源文件代码中发生的。
所以不会引起问题,JVM会正确地进行处理。
模糊性错误
泛型的引入,增加了引起一种新类型错误——模糊性错误的可能,必须注意防范。当擦除导致两个看起来不同的泛型生命,在擦除后会变成相同的类
型而导致冲突时,就会发生模糊错误。
class MyGenClass<T, V> {
T ob1;
V ob2;
//...
void set(T o) {
ob = o;
}
void set(V o) {
ob2 = o;
}
}
注意MyGenClass声明了两个泛型类型参数:T和V。在MyGenClass中,试图根据类型参数T和V重载set()方法。这看起来是合理,因为
T和V表面上是不同类型。但是有模糊性问题:T和V如果是相同类型,使得set()方法的两个版本完全相同,这是错误。
使用泛型的限制
不能实例化形式参数,原因很容易理解:编译器不知道创建哪种类型的对象。T只是一个占位符。
静态成员不能使用在类中声明类型参数。 class Wrong<T> {static T ob; ...} 但是可以定义声明静态的泛型方法。
对泛型数组的一些限制,对数组有两条重要的泛型限制。首先,不能实例化元素类型为类型参数的数组,其次,不能创建特定类型的泛型引用数组
//wrong
//ok
//ok
Gen<Integer> gens[] = new Gen<Integer>[10];//wrong
//ok
泛型不能扩展Throwable,这意味着不能创建泛型异常类。