java面向对象
类和对象
面向对象的两个重要的概念:类和对象
面向对象的程序设计过程中有两个重要概念:类(class)和对象(object,也被称为实例,instance),其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体,从这个意义上来看,我们日常所说的人,其实都是人的实例,而不是人类。
理解:一切可见的事物都可以成为对象,类是对象的抽象,通过类可以用来创建对象。类是对象的蓝图
定义类:
java定义类的基本语法为:
[修饰符] class 类名{
零到多个构造器定义..
零到多个Field(属性-成员变量)
零到多个方法
}
Field用于定义该类或该类的实例所包含的状态数据,方法则用于定义该类或该类的实例的行为特征或者功能实现。构造器用于构造该类的实例,Java语言通过new关键字来调用构造器,从而返回该类的实例。
对象引用和指针
p=new person();
p.age=19;
p.name=陈汝旭
栈内存里的引用变量并未真正存储对象的Field数据,对象的Field数据实际存放在堆内存里;而引用变量只是指向该堆内存里的对象。从这个角度来看,引用变量与C语言里的指针很像,它们都是存储一个地址值,通过这个地址来引用到实际对象。实际上,Java里的引用就是C里的指针,只是Java语言把这个指针封装起来,避免开发者进行烦琐的指针操作。
**理解:**p=new person();在栈中创建了一个引用的指针p指向堆内存所为person开辟的空间中,所有的对象存放在堆内存中,通过不断的引用获得
当一个对象被创建成功以后,这个对象将保存在堆内存中,Java程序不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象。也就是说,不管是数组还是对象,都只能通过引用来访问它们。
对象的this引用
Java提供了一个this关键字,this关键字总是指向调用该方法的对象。根据this出现位置的不同,this作为对象的默认引用有两种情形:
-
在构造器中引用该构造器正在初始化的对象;
-
在方法中引用调用该方法的对象。
没有使用static修饰的Field和方法都必须使用对象来调用。
方法
方法的总体介绍
方法是类或对象的行为特征的抽象,方法是类或对象最重要的组成部分。但从功能上来看,方法完全类似于传统结构化程序设计里的函数。值得指出的是,Java里的方法不能独立存在,所有的方法都必须定义在类里。方法在逻辑上要么属于类,要么属于对象。-----《java疯狂讲义》
因此,如果需要定义方法,则只能在类体内定义,不能独立定义一个方法。一旦将一个方法定义在某个类的类体内,如果这个方法使用了static修饰,则这个方法属于这个类,否则这个方法属于这个类的实例。
方法的执行和调用:
执行方法时必须使用类或对象来作为调用者,即所有方法都必须使用“类.方法”或“对象.方法”的形式来调用
如果被调方法是普通方法,则默认使用this作为调用者;如果被调方法是静态方法,则默认使用类作为调用者。也就是说,表面上看起来某些方法可以被独立执行,但实际上还是使用this或者类来作为调用者。
方法的参数传递机制
方法中的参数含义面向过程的编程语言的函数的参数相同分为形参和实参。
Java方法的参数传递机制来控制的,Java里方法的参数传递方式只有一种:值传递。所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。
不同于C语言既包括值传递也包括指针传递(引用传递)
package com.xiaoxu.test1;
public class Class1 {
/*
*交换两个变量的值
*/
public void swap(int a,int b){
int t;
t=a;a=b;b=t;
}
public static void main(String[] args) {
Class1 class1=new Class1();
int a=3;int b=4;
class1.swap(3,4);
System.out.println(a+" "+b);
}
}
交换两个变量不会影响原来a和b的值
swap方法的a和b只是main方法里变量a和b的复制品。下面通过示意图来说明上面程序的执行过程。Java程序总是从main方法开始执行,main方法开始定义了a、b两个局部变量,两个变量在内存中的存储示意图
解释:main方法栈区中a、b的值并未有任何改变,程序改变的只是swap方法栈区中的a、b。这就是值传递的实质:当系统开始执行方法时,系统为形参执行初始化,就是把实参变量的值赋给方法的形参变量,方法里操作的并不是实际的实参变量。
前面看到的是基本类型的参数传递,Java对于引用类型的参数传递,一样采用的是值传递方式。但许多初学者可能对引用类型的参数传递会产生一些误会。下面程序示范了引用类型的参数传递的效果。
package com.xiaoxu.test1;
class DataWrap{
int a;
int b;
}
public class Class2 {
public static void swap(DataWrap d){
int t;
t=d.a;
d.a=d.b;
d.b=t;
System.out.println("swap中的值a="+d.a+" b="+d.b);
}
public static void main(String[] args) {
Class2 class2=new Class2();
DataWrap d =new DataWrap();
d.a=3;
d.b=4;
class2.swap(d);
System.out.println("交换后实参的值为:a="+d.a+" b="+d.b);
}
}
从上面运行结果来看,在swap方法里,a、b两个Field值被交换成功。不仅如此,main方法里swap方法执行结束后,a、b两个Field值也被交换了。这很容易造成一种错觉:调用swap方法时,传入swap方法的就是dw对象本身,而不是它的复制品。但这只是一种错觉,下面还是结合示意图来说明程序的执行过程。
如果在操作完a和b之后将d置空此时形参便无法完成引用操作堆内存对应的区域说明Java中依然存在引用关系的传递,但在方法中:传递的是值这种传递有时也被认为是引用传递
形参个数可变的方法
从JDK 1.5之后,Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。下面程序定义了一个形参个数可变的方法。
可变的参数可以直接接收对应类型的多个参数还可以接收数组
实例测试
package com.xiaoxu.test1;
/**
* 测试传递可变参数
*/
public class Class3 {
//可变参数
public void test(String... s){
//foreach--增强for循环遍历
for (String a:s) {
System.out.println(a);
}
}
public static void main(String[] args) {
Class3 class3=new Class3();
class3.test("books","object","tools");
//测试可变参数是否可以接收数组
String[] s=new String[3];
s[0]="1";
s[1]="2";
s[2]="3";
class3.test(s);
}
}
长度可变的形参只能处于形参列表的最后。一个方法中最多只能包含一个长度可变的形参。调用包含一个长度可变形参的方法时,这个长度可变的形参既可以传入多个参数,也可以传入一个数组。
一个方法体内调用它自身,被称为方法递归。方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
方法的重载
何为方法的重载:
Java允许同一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。
从上面介绍可以看出,在Java程序中确定一个方法需要三个要素:
-
调用者,也就是方法的所属者,既可以是类,也可以是对象;
-
方法名,方法的标识;
-
形参列表,当调用方法时,系统将会根据传入的实参列表匹配。
为什么方法的返回值类型不能用于区分重载的方法?
答:对于int f(){}和void f(){}两个方法,如果这样调用int result=f();,系统可以识别是调用返回值类型为int的方法;但Java调用方法时可以忽略方法返回值,如果采用如下方法来调用f();,你能判断是调用哪个方法吗?如果你尚且不能判断,那么Java系统也会糊涂。在编程过程中有一条重要规则:不要让系统糊涂,系统一糊涂,肯定就是你错了。因此,Java里不能使用方法返回值类型作为区分方法重载的依据。
属性(成员变量)
属性(成员变量)的定义
在Java语言中,根据定义变量位置的不同,可以将变量分成两大类:成员变量和局部变量。成员变量和局部变量的运行机制存在较大差异,本节将详细介绍这两种变量的运行差异。成员变量被分为类Field和实例Field两种,定义Field时没有static修饰的就是实例Field,有static修饰的就是类Field。其中类Field从这个类的准备阶段起开始存在,直到系统完全销毁这个类,类Field的作用域与这个类的生存范围相同;而实例Field则从该类的实例被创建起开始存在,直到系统完全销毁这个实例,实例Field的作用域与对应实例的生存范围相同。
访问类变量:类名.属性名
访问实例变量(成员变量) 实例.实例属性
但由于这个实例并不拥有这个类Field,因此它访问的并不是这个实例的Field,依然是访问它对应类的类Field。也就是说,如果通过一个实例修改了类Field的值,由于这个类Field并不属于它,而是属于它对应的类。因此,修改的依然是类的类Field,与通过该类来修改类Field的结果完全相同,这会导致该类的其他实例来访问这个类Field时也将获得这个被修改过的值。
被static修饰的属性或方法属于类,被所有的实例(对象)所共享。
局部变量的讲解
局部变量分为的三种类型
局部变量根据定义形式的不同,又可以被分为如下三种。
-
形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效。
-
方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效。
-
代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到该代码块结束时失效。
与成员变量不同,成员变量在类或者实例创建时会赋初值,而局部变量必须要显示的初始化(即所谓的赋初值)。
局部变量有一定的作用范围有时候会被称为作用域
只要离开了代码块局部变量所在的代码块,这个局部变量就立即被销毁,变为不可见。
局部变量的作用范围
形参的作用域是整个方法体内有效,而且形参也无须显式初始化,形参的初始化在调用该方法时由系统完成,形参的值由方法的调用者负责指定。当通过类或对象调用某个方法时,系统会在该方法栈区内为所有的形参分配内存空间,并将实参的值赋给对应的形参,这就完成了形参的初始化。(对应方法的参数传递机制)
成员变量的初始化和内存中的运行机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。
创建一个用来测试的Person类
package com.xiaoxu.test1;
public class Person {
public String name;//成员变量
public static int age;//类变量
}
编写测试代码
package com.xiaoxu.test1;
public class Class4 {
public static void main(String[] args) {
Person p1=new Person();
Person p2=new Person();
p1.name="chenruxu";
p1.age=20;
p2.name="xiaoxu";
p2.age=19;
}
}
当Person类初始化完成后,系统将在堆内存中为Person类分配一块内存区(当Person类初始化完成后,系统会为Person类创建一个类对象),在这块内存区里包含了保存age Field的内存,并设置age的默认初始值:0**(加载类)**
系统接着创建了一个Person对象,并把这个Person对象赋给p1变量,Person对象里包含了名为name的实例Field,实例Field是在创建实例时分配内存空间并指定初始值的。当创建了第一个Person对象后,系统内存中的存储示意图
age类F变量并不属于Person对象,它是属于Person类的,所以创建第一个Person对象时并不需要为age类变量分配内存,系统只是为name实例Field分配了内存空间,并指定默认初始值:null。接着执行Person p2=new Person();代码创建第二个Person对象,此时因为Person类已经存在于堆内存中了,所以不再需要对Person类进行初始化。创建第二个Person对象与创建第一个Person对象并没有什么不同。当程序执行p1.name=“chenruxu”;代码时,将为p1的name实例Field赋值,也就是让图5.11中堆内存中的name指向"chenruxu"字符串。执行完成后,两个Person对象在内存
局部变量的初始化和内存中的运行机制
局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中。如果局部变量是基本类型的变量,则直接把这个变量的值保存在该变量对应的内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。
局部变量存放的位置都是在栈内存之中,而成员变量存放的位置是在堆内存之中。
栈内存中的变量无须系统垃圾回收,往往随方法或代码块的运行结束而结束。因此,局部变量的作用域是从初始化该变量开始,直到该方法或该代码块运行完成而结束。因为局部变量只保存基本类型的值或者对象的引用,因此局部变量所占的内存区通常比较小。
面向对象的三大特性封装性
封装(Encapsulation)是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
隐藏类的实现细节。
-
让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对Field的不合理访问。
-
可进行数据检查,从而有利于保证对象信息的完整性。
-
便于修改,提高代码的可维护性。为了实现良好的封装,需要从两个方面考虑。
-
将对象的Field和实现细节隐藏起来,不允许外部直接访问。
-
把方法暴露出来,让方法来控制对这些Field进行安全的访问和操作
访问控制权限
-
private(当前类访问权限):如果类里的一个成员(包括Field、方法和构造器等)使用private访问控制符来修饰,则这个成员只能在当前类的内部被访问。很显然,这个访问控制符用于修饰Field最合适,使用它来修饰Field就可以把Field隐藏在该类的内部。
-
default(包访问权限):如果类里的一个成员(包括Field、方法和构造器等)或者一个外部类不使用任何访问控制符修饰,我们就称它是包访问权限,default访问控制的成员或外部类可以被相同包下的其他类访问。关于包的介绍请看5.4.3节。
-
protected(子类访问权限):如果一个成员(包括Field、方法和构造器等)使用protected访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。在通常情况下,如果使用protected来修饰一个方法,通常是希望其子类来重写这个方法。关于父类、子类的介绍请参考5.6节的内容。
-
public(公共访问权限):这是一个最宽松的访问控制级别,如果一个成员(包括Field、方法和构造器等)或者一个外部类使用public访问控制符修饰,那么这个成员或外部类就可以被所有类访问,不管访问类和被访问类是否处于同一个包中,是否具有父子继承关系。
private | default | protected | publlic | |
---|---|---|---|---|
同一个类 | T | T | T | T |
同一个包 | F | T | T | T |
子类中 | F | F | T | T |
全局范围内 | F | F | F | T |
通过上面关于访问控制符的介绍不难发现,访问控制符用于控制一个类的成员是否可以被其他类访问,对于局部变量而言,其作用域就是它所在的方法,不可能被其他类访问,因此不能使用访问控制符来修饰。
对于外部类而言,它也可以使用访问控制符修饰,但外部类只能有两种访问控制级别:public和默认,外部类不能使用private和protected修饰,因为外部类没有处于任何类的内部,也就没有其所在类的内部、所在类的子类两个范围,因此private和protected访问控制符对外部类没有意义。
package import import static
Java引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。
Java引入了import关键字,import可以向某个Java文件中导入指定包层次下某个类或全部类,import语句应该出现在package语句(如果有的话)之后、类定义之前。一个Java源文件只能包含一个package语句,但可以包含多个import语句,多个import语句用于导入多个包层次下的类。
Java默认为所有源文件导入java.lang包下的所有类,因此前面在Java程序中使用String、System类时都无须使用import语句来导入这些类。但对于前面介绍数组时提到的Arrays类,其位于java.util包下,则必须使用import语句来导入该类。
JDK 1.5以后更是增加了一种静态导入的语法,它用于导入指定类的某个静态Field、方法或全部的静态Field、方法。
常用的包
- java.lang:这个包下包含了Java语言的核心类,如String、Math、System和Thread类等,使用这个包下的类无须使用import语句导入,系统会自动导入这个包下的所有类。
- java.util:这个包下包含了Java的大量工具类/接口和集合框架类/接口,例如Arrays和List、Set等。
- java.net:这个包下包含了一些Java网络编程相关的类/接口。
- java.io:这个包下包含了一些Java输入/输出编程相关的类/接口。
- java.text:这个包下包含了一些Java格式化相关的类。
- java.sql:这个包下包含了Java进行JDBC数据库编程的相关类/接口。
- java.awt:这个包下包含了抽象窗口工具集(Abstract WindowToolkits)的相关类/接口,这些类主要用于构建图形用户界面(GUI)程序。
- java.swing:这个包下包含了Swing图形用户界面编程的相关类/接口,这些类可用于构建平台无关的GUI程序。
构造器
构造器是一个特殊的方法,这个特殊方法用于创建实例时执行初始化。构造器是创建对象的重要途径(即使使用工厂模式、反射等方式创建对象,其实质依然是依赖于构造器),因此,Java类必须包含一个或一个以上的构造器。
使用构造器进行初始化
构造器最大的用处就是在创建对象时执行初始化。前面已经介绍过了,当创建一个对象时,系统为这个对象的Field进行默认初始化,这种默认的初始化把所有基本类型的Field设为0(对数值型Field)或false(对布尔型Field),把所有引用类型的Field设为null。如果想改变这种默认的初始化,想让系统创建对象时就为该对象的Field显式指定初始值,就可以通过构造器来实现。注意:如果程序员没有为Java类提供任何构造器,则系统会为这个类提供一个无参数的构造器,这个构造器的执行体为空,不做任何事情。无论如何,Java类至少包含一个构造器。
三大特性(2)-类的继承
Java的继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类被称为父类,有的也称其为基类、超类。父类和子类的关系,是一种一般和特殊的关系。例如水果和苹果的关系,苹果继承了水果,苹果是水果的子类,则苹果是一种特殊的水果。
java子类继承父类的语法格式如下
修饰符 class 字类名 extends 父类名{
//类定义的部分
}
java只i支持单继承,但可以间接的实现多继承
重写父类的方法
这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。
方法的重写要遵循“两同两小一大”规则,“两同”即方法名相同、形参列表相同;“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。尤其需要指出的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法
方法的重写
当子类覆盖了父类方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。如果子类中定义了一个与父类private方法具有相同的方法名、相同的形参列表、相同的返回值类型的方法,依然不是重写,只是在子类中重新定义了一个新方法
super关键字
如果在字类中调用父类的方法需要使用super关键字来执行
super用于限定该对象调用它从父类继承得到的Field或方法。正如this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法中。static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,因而super限定也就失去了意义。
提示:当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。也就是说,当系统创建一个Java对象时,如果该Java类有两个父类(一个直接父类A,一个间接父类B),假设A类中定义了2个实例变量,B类中定义了3个实例变量,当前类中定义了2个实例变量,那么这个Java对象将会保存2+3+2个实例变量。
如果在子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量。注意不是完全覆盖,因此系统在创建子类对象时,依然会为父类中定义的、被隐藏的变量分配内存空间。
package com.xiaoxu.test1;
class patents{
int a=5;
public void test(){
System.out.println("hello world");
}
}
class son extends patents{
private int a=7;
@Override
public void test(){
System.out.println("hello java");
super.test();
}
}
public class Class5{
public static void main(String[] args) {
son son=new son();
System.out.println(((patents)son).a);//向上强制转型后访问
son.test();
}
}
调用父类的构造器
子类不会获得父类的构造器和私有的方法和属性,就无法实现对这些方法的重写。但在子类的构造器中可以调用父类的构造方法。
使用super调用和使用this调用也很像,区别在于super调用的是其父类的构造器,而this调用的是同一个类中重载的构造器。因此,使用super调用父类构造器也必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现。
不管我们是否使用super调用来执行父类构造器的初始化代码,子类构造器总会调用父类构造器一次。
即super关键字省略依然可以先调用父类的方法完成子类的初始化操作
子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
三大特性(3)-多态
定义:Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。
多态存在的条件是一定要有继承的出现
向上转型(自动转换)
多态的向上转型(由系统来自动的完成)
package com.xiaoxu.test1;
class BaseClass{
public int book=6;
public void base(){
System.out.println("普通的父类方法");
}
public void test(){
System.out.println("父类的被覆盖的方法");
}
}
public class SubClass extends BaseClass{
//重新定义一个book实例
public String book="heed first java";
@Override
public void test(){
System.out.println("子类重写父类的方法");
}
public void sub(){
System.out.println("子类普通的方法");
}
public static void main(String[] args) {
//BaseClass baseClass=new BaseClass();编译与运行的类型一致
BaseClass ploymopicBc=new SubClass();//不一致产生多态
//此时自动的向上转型执行父类的构造方法
System.out.println(ploymopicBc.book);
ploymopicBc.base();
ploymopicBc.test();
}
}
子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型(upcasting),向上转型由系统自动完成。
BaseClassploymophicBc=new SubClass();,这个ploymophicBc引用变量的编译时类型是BaseClass,而运行时类型是SubClass,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
多态也称之为方法的多态
上面的main的方法中注释了ploymophicBc.sub();,这行代码会在编译时引发错误。虽然ploymophicBc引用变量实际上确实包含sub()方法(例如,可以通过反射来执行该方法),但因为它的编译时类型为BaseClass,因此编译时无法调用sub()方法。
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如,通过Object p=new Person()代码定义一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。
向下转型(强制转换)
上面说到
编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象确实包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助于类型转换运算符。
类型转换运算符是小括号,类型转换运算符的用法是:(type)variable,这种用法可以将variable变量转换成一个type类型的变量。前面在介绍基本类型的强制类型转换时,已经看到了使用这种类型转换运算符的用法,类型转换运算符可以将一个基本类型变量转换成另一个类型。
向下转型的基本的规则
基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。[插图] 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException异常。
((SubClass)ploymopicBc).sub();//强制的向下转型
当把子类对象赋给父类引用变量时,被称为向上转型(upcasting),这种转型总是可以成功的,这也从另一个侧面证实了子类是一种特殊的父类。这种转型只是表明这个引用变量的编译时类型是父类,但实际执行它的方法时,依然表现出子类对象的行为方式。但把一个父类对象赋给子类引用变量时,就需要进行强制类型转换,而且还可能在运行时产生ClassCastException异常,使用instanceof运算符可以让强制类型转换更安全。
初始化块
初始化块是Java类里可出现的第4种成员(前面依次有Field、方法和构造器),一个类里可以有多个初始化块,相同类型的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块后执行。初始化块的语法格式如下:
[修饰符]{
//初始化可执行代码
.....
}
初始化块的修饰符只能是static,使用static修饰的初始化块被称为静态初始化块。初始化块里的代码可以包含任何可执行性语句,包括定义局部变量、调用其他对象的方法,以及使用分支、循环语句等。
静态初始化块也被称为类初始化块,也属于类的静态成员,同样需要遵循静态成员不能访问非静态成员的规则,因此静态初始化块不能访问非静态成员,包括不能访问实例Field和实例方法。