目录
一、类的封装
封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其载体就是类。本节将详细介绍如何将类封装。
创建Restaurant这个类,实现餐馆点菜的场景。
代码如下:
package d7z;
public class Restaurant1 {//创建类
public static void main(String[] args) {//主方法
String CookName="Tom Cruise";//厨师的名字叫Tom Cruise
System.out.println("**请让厨师为我做一份鱼香肉丝***");//输出信息
System.out.println(CookName+"切葱花");//输出动作
System.out.println(CookName+"洗蔬菜");//输出动作
System.out.println(CookName+"开始烹饪"+"鱼香肉丝");//输出动作
System.out.println("**你们的厨师叫什么名字?***");//输出动作
System.out.println(CookName);//输出名字
System.out.println("**请让厨师给我切一些葱花***");//输出动作
System.out.println(CookName+"切葱花");//输出动作
}
}
运行结果:
将厨师封装成Cook类,实现餐馆点菜的场景。
代码如下:
package d7z;
public class Restaurant2 {//创建类
public static void main(String[] args) {//主方法
Cook1 cook=new Cook1();//创建厨师类对象
System.out.println("**请让厨师为我做一份鱼香肉丝***");//输出信息
cook.cooking("鱼香肉丝");//厨师烹饪鱼香肉丝
System.out.println("**你们的厨师叫什么名字?***");//输出信息
System.out.println(cook.name);//厨师回答自己的问题
System.out.println("**请让厨师给我切一些葱花***");//输出信息
cook.cutOnion();//厨师去切葱花
}
}
class Cook1{//创建类
String name;//厨师名字
public Cook1() {//创建类
this.name = "Tom Cruise";//厨师的名字叫Tom Cruise
}
void cutOnion() {//厨师切葱花
System.out.println(name+"切葱花");//输出信息
}
void washVegetables() {//厨师洗蔬菜
System.out.println(name+"洗蔬菜");//输出信息
}
void cooking(String dish) {//厨师烹饪顾客点的菜
washVegetables();//洗蔬菜的动作
cutOnion();//切洋葱的动作
System.out.println(name+"开始烹饪"+dish);//输出信息
}
}
运行结果:
将厨师的属性和部分方法用private修饰。
代码如下:
package d7z;
public class Restaurant3 {//创建类
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
Cook2 cook=new Cook2();//创建厨师类对象
System.out.println("**请让厨师为我做一份鱼香肉丝***");//输出信息
cook.cooking("鱼香肉丝");//厨师烹饪鱼香肉丝
System.out.println("**你们的厨师叫什么名字?***");//输出信息
System.out.println(cook.name);//厨师回答自己的问题
System.out.println("**请让厨师给我切一些葱花***");//输出信息
cook.cutOnion();//厨师去切葱花
}
}
class Cook2{//创建类
String name;//厨师名字
public Cook2() {//创建类
this.name = "Tom Cruise";//厨师的名字叫Tom Cruise
}
void cutOnion() {//厨师切葱花
System.out.println(name+"切葱花");//输出信息
}
private void washVegetables() {//厨师洗蔬菜
System.out.println(name+"洗蔬菜");//输出信息
}
void cooking(String dish) {//厨师烹饪顾客点的菜
washVegetables();//洗蔬菜的动作
cutOnion();//切洋葱的动作
System.out.println(name+"开始烹饪"+dish);//输出信息
}
}
运行结果:
此时再运行餐馆的主方法,就会抛出异常常,如图 所示,提示Cook2的name和cutOnion()不可以直接调用。
将厨师对象封装在餐馆类中,顾客无法接触到厨师的任何信息。
代码如下:
package d7z;
public class Restaurant4 {//创建类
private Cook2 cook = new Cook2();// 餐厅封装的厨师类
public void takeOrder(String dish) {// 下单
cook.cooking(dish);// 通知厨师做菜
System.out.println("您的菜好了,请慢用。");//输出结果
}
public String saySorry() {// 拒绝顾客请求
return "抱歉,餐厅不提供此项服务。";//输出结果
}
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
Restaurant4 water = new Restaurant4();// 创建餐厅对象,为顾客提供服务
System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出结果
water.takeOrder("香辣肉丝");// 服务员给顾客下单
System.out.println("**你们的厨师叫什么名字?***");//输出结果
System.out.println(water.saySorry());// 服务员给顾客善意的答复
System.out.println("**请让厨师给我切一点葱花。***");//输出结果
System.out.println(water.saySorry());// /服务员给善意的答复顾客
}
}
运行结果:
二、类的继承
继承在面向对象开发思想中是一个非常重要的概念,它使整个程序架构具有一定的弹性,在程序中复用已经定义完善的类不仅可以减少软件开发周期,还可以提高软件的可维护性和可扩展性。
继承:其基本思想是基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。
1、extend关键字
在Java中,让一个类继承另一个类,用extend关键字,语法如下:
child extends parents
这里的child这个类作为子类继承了parents这个类,并继承parents中的属性和方法。
创建Pad类,继承Computer类。
代码如下:
package d7z;
class Computer {// 父类:电脑
String screen = "液晶显示屏";//创建字符串
void startup() {//开机
System.out.println("电脑正在开机,请等待...");//输出结果
}
}
public class Pad extends Computer{//创建类
String battery = "5000毫安电池";// 子类独有的属性
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
Computer pc = new Computer();// 电脑类
System.out.println("computer的屏幕是:" + pc.screen);//输出结果
pc.startup();//父类方法
Pad ipad = new Pad();// 平板电脑类
System.out.println("pad的屏幕是:" + ipad.screen);// 子类可以直接使用父类属性
System.out.println("pad的电池是:" + ipad.battery);// 子类独有的属性
ipad.startup();// 子类可以直接使用父类方法
}
}
运行结果:
2、方法的重写
(1)、重写的实现
继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重新编写成员方法的实现内容,更改成员方法的存储权限,或是修改成员方法的返回值类型。
在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构。
子类重写父类的方法还可以修改方法的返回值类型,但这只是在J2SE 5.0以上的版本中支持的新功能,但这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值类型的子类。
创建 Pad2 类,继承 Computer2 类,并重写父类的 showPicture()方法。
代码如下:
package d7z;
class Computer2 {// 父类:电脑
void showPicture() {//父类方法
System.out.println("鼠标点击");//输出结果
}
}
public class Pad2 extends Computer2{//创建类
void showPicture() {//子类重写父类方法
System.out.println("手指点击触摸屏");//输出结果
}
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
Computer2 pc = new Computer2();// 电脑类
System.out.print("pc打开图片:");//输出结果
pc.showPicture();// 调用方法
Pad2 ipad = new Pad2();// 平板电脑类
System.out.print("ipad打开图片:");//输出结果
ipad.showPicture();// 重写父类方法
Computer2 computerpad = new Pad2();// 父类声明,子类实现
System.out.print("computerpad打开图片:");//输出结果
computerpad.showPicture();// 调用父类方法,实现子类重写的逻辑
}
}
运行结果:
如果父类声明的对象是由子类实例化的,那么这个对象所调用的方法也是被子类重写过的。
注意:
在Java语言中,一个类只可以有一个父类!
(2)、super关键字
super 关键字的使用方法与this 关键字类似。this 关键字代表本类对象,super 关键字代表父类对象,使用方法如下:
super .property; //调用父类的属性
super.method(); //调用父类的方法
创建Pad3类,继承Computer3类,重写父类方法,并用使用super关键字调用父类方法。
代码如下:
package d7z;
class Computer3 {// 父类:电脑
String sayHello(){//创建字符串
return "欢迎使用";//返回"欢迎使用"值
}
}
public class Pad3 extends Computer3{//创建类
String sayHello() {// 子类重写父类方法
return super.sayHello() + "平板电脑";// 调用父类方法,在其结果后添加字符串
}
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
Computer3 pc = new Computer3();// 电脑类
System.out.println(pc.sayHello());//输出结果
Pad3 ipad = new Pad3();// 平板电脑类
System.out.println(ipad.sayHello());//输出结果
}
}
运行结果:
3、所有类的父类-Object类
在开始学习使用class关键字定义类时,就应用了继承原理,因为在Java中,所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是 Java 类层中的最高层类。
在Object类中主要包括clone()、finalize()、equals()、toString()等方法,其中常用的两个方法为equals()和toString()方法。由于所有的类都是Object 类的子类,所以任何类都可以重写 Object类中的方法。
注意:
Object类中的getClass()、notify()、notifyAll()、wait()等方法不能被重写,因为这些方法被定义为final类型。
(1)、getClass()方法
getClass()方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称。
语法如下:
getClass().getName();
可以将getClass()方法与toString()方法联合使用。
(2)、toString()方法
toString()方法的功能是将一个对象返回为字符串形式,它会返回一个 String实例。在实际的运用中通常重写toString()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。
在项目中创建ObjectInstance类,在类中重写Object类的toString()方法,并在主方法中输出该类的实例对象。
代码如下:
package d7z;
public class ObjectInstance {//创建类
public String toString() { //重写toString()方法
return "在" + getClass().getName() + "类中重写toString()方法";//返回
}
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
System.out.println(new ObjectInstance()); //打印本类对象
}
}
运行结果:
(3)、equals()方法
前面章节曾讲解过equals()方法,当时是比较“==”运算符与equals()方法,说明“==”比较的是两个对象的引用是否相等,而equals()方法比较的是两个对象的实际内容。
在项目中创建OverWriteEquals类,在类的主方法中定义两个字符串对象,调用 equals()fang 方法判断两个字符串对象是否相等。
代码如下:
package d7z;
class V { // 自定义类V
}
public class OverWriteEquals {//创建类
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
String s1 = "123"; // 实例化两个对象,内容相同
String s2 = "123";//实例化两个对象,内容相同
System.out.println(s1.equals(s2)); // 使用equals()方法调用
V v1 = new V(); // 实例化两个V类对象
V v2 = new V();//实例化两个V类对象
System.out.println(v1.equals(v2)); // 使用equals()方法比较v1与v2对象
}
}
运行结果:
三、类的多态
多态意为一个名字可具有多种语义,在程序设计语言中,多态性是指“一种定义,多种实现”例如,运算符“+”作用于两个整型量时是求和,而作用于两个字符型量时则是将其连接在一起。利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。类的多态性可以从两方面体现:一是方法的重载,二是类的上下转型。
1、方法的重载
方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。
在项目中创建 OverLoadTest类,在类中编写add()方法的多个重载形式,然后在主方法中分别输出这些方法的返回值。
代码如下:
package d7z;
public class OverLoadTest {//创建类
public static int add(int a) {//定义一个对象
return a;//返回到a的值
}
public static int add(int a, int b) {// 定义与第一个方法参数个数不同的方法
return a + b;//返回到“a+b”的值
}
public static double add(double a, double b) {// 定义与第一个方法相同名称、参数类型不同的方法
return a + b;//返回到“a+b”的值
}
public static int add(int a, double b) {// 定义一个成员方法
return (int) (a + b);//返回
}
public static int add(double a, int b) {// 这个方法与前一个方法参数次序不同
return (int) (a + b);//返回
}
public static int add(int... a) {// 定义不定长参数
int s = 0;//定义整型变量s的值为0
for (int i = 0; i < a.length; i++) {// 根据参数个数循环操作
s += a[i];// 将每个参数的值相加
}
return s;// 将计算结果返回
}
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
System.out.println("调用add(int)方法:" + add(1));//输出结果
System.out.println("调用add(int,int)方法:" + add(1, 2));//输出结果
System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));//输出结果
System.out.println("调用add(int a, double b)方法:" + add(1, 3.3));//输出结果
System.out.println("调用add(double a, int b) 方法:" + add(2.1, 3));//输出结果
System.out.println("调用add(int... a)不定长参数方法:"+ add(1, 2, 3, 4, 5, 6, 7, 8, 9));//输出结果
System.out.println("调用add(int... a)不定长参数方法:" + add(2, 3, 4));//输出结果
}
}
运行结果:
2、向上转型
对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型操作。
在项目中创建Quadrangle父类,再创建Parallelogram子类,并使Parallelogram子类 Quadrangle父类,然后在主方法中调用父类的draw()方法。
代码如下:
package d7z;
class Quadrangle { // 四边形类
public static void draw(Quadrangle q) { // 四边形类中的方法
// SomeSentence
}
}
public class Parallelogram2 extends Quadrangle{//创建主类
public static void main(String args[]) {//主方法
Parallelogram2 p = new Parallelogram2(); // 实例化平行四边形类对象引用
draw(p); // 调用父类方法
}
}
运行结果:无结果
3、向下转型
通过向上转型可以推理出向下转型是将较抽象类转换为较具体的类。
修改例7.11,在Parallelogram子类的主方法中将父类Quadrangle的对象赋值给子类Parllelogram的对象的引用变量将使程序产生错误。
代码如下:
package d7z;
class Quadrangle {// 四边形类
public static void draw(Quadrangle q) {// 四边形类中的方法
// SomeSentence
}
}
public class H7 extends Quadrangle {//创建主类
public static void main(String args[]) {//主方法
draw(new H7());
// 将平行四边形类对象看作是四边形对象,称为向上转型操作
Quadrangle q = new H7();// 将父类对象赋予子类对象
H7 p = (H7) q;
// //将父类对象赋予子类对象,并强制转换为子类型
//Parallelogram p = (Parallelogram) q;
}
}
结果错误:
在Parallelogram 子类的主方法中将父类 Quadrangle 的对象赋值给子类Parallelogram 的对象的引用变量将使程序产生错误。
4、instanceof关键字
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。
instanceof的语法格式如下:
myobject instanceof ExampleClass
myobject:某类的对象引用。
ExampleClass:某个类。
注意:
instanceof是Java语言的关键字,在Java语言中的关键字都为小写。
在项目中创建Parallelogram类和另外3个类Quadrangle、Square、Anything。其中 Parallelogram 类和 Square 类继承 Quadrangle类,在Parallelogram类的主方法中分别创建这些类的对象,然后使用instanceof操作符判断它们的类型并输出结果。
代码如下:
package d7z;
class Quadrangle {// 四边形类
public static void draw(Quadrangle q) {// 四边形类中的方法
// SomeSentence
}
}
class Square extends Quadrangle {//square类
// SomeSentence
}
class Anything {//anything类
// SomeSentence
}
public class Parallelogram2 extends Quadrangle {//创建主类,继承
public static void main(String args[]) {//主方法
Quadrangle q = new Quadrangle(); // 实例化父类对象
if (q instanceof Parallelogram2) {// 判断父类对象是否为Parallelogram子类的一个实例
Parallelogram2 p = (Parallelogram2) q; // 进行向下转型操作
}
if (q instanceof Square) {// 判断父类对象是否为Parallelogram子类的一个实例
Square s = (Square) q; // 进行向下转型操作
}
// 由于q对象不为Anything类的对象,所以这条语句是错误的
// System.out.println(q instanceof Anything);
}
}
运行结果:
四、抽象与接口
1、抽象类与抽象方法
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体图形,但它的子类却可以。
Java中定义抽象类时,需要使用abstract关键字,其语法如下
[权限修饰符] abstract class 类名{
类体
}
使用abstract关键字定义的类称为抽象类,而使用abstract关键字定义的方法称为抽象方法,抽象方法的定义语法如下:
[权限修饰符] abstract 方法返回值类型方法名(参数列表);
从上面的语法可以看出,抽象方法是直接以分号结尾的,它没有方法体,抽象方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上,抽象类除了被继承之外没有任何意义。
使用抽象类模拟“去商场买衣服”场景。去商场买衣服,这句话描述的是一个抽象的行为:到底去哪个商场买衣服,是实体店还是网店,买什么样的衣服,是短衫、裙子,还是其他的什么衣服?在“去商场买衣服”这句话中,并没有对“买衣服”这个抽象行为指明一个确定的信息。因此,我们可以封装一个商场的抽象类,并在其中定义个买东西的抽象方法,具体是什么商场、买什么东西,交给子类去实现即可。
代码如下:
public abstract class Market {//创建主类
public String name;//商场名称
public String goods;//商品名称
public abstract void shop();//抽象方法,用来输出信息
}
定义一个TaobaoMarket类,继承自Market抽象类,实现其中的shop抽象方法,代码如下:
public class TaobaoMarket extends Market {//创建TaobaoMarket主类,继承了Market类
@Override
public void shop() {//shop抽象方法
// TODO Auto-generated method stub
System.out.println(name + "实体店购买"+goods);//输出结果
}
}
定义一个WallMarket类,继承子Market抽象类,实现其中的shop抽象方法。
public class WallMarket extends Market {//创建WallMarket主类,继承Market类
@Override
public void shop() {//shop抽象方法
// TODO Auto-generated method stub
System.out.println(name + "网购"+goods);//输出结果
}
}
定义一个GoShopping类,该类中分别使用实现的 WallMarket子类和TaobaoMarket子类创建抽象类的对象,并分别给抽象类中的成员变量赋不同的值,使用shop方法分别输出结果。
代码如下:
package d7z;
public class Goshopping2 {//创建类
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
Market market=new WallMarket();//使用派生类对象创建抽象类对象
market.name="沃尔玛";//商场名称为沃尔玛
market.goods ="七匹狼西服";// 商品名称为七匹狼西服
market.shop();//使用shop抽象方法
market = new TaobaoMarket();//使用派生类对象创建抽象类对象
market.name="淘宝";//商场名称为淘宝
market.goods="韩都衣舍花裙"; //商品名称为韩都衣舍花裙
market.shop();//使用shop抽象方法
}
}
运行结果:
综上所述,使用抽象类和抽象方法时,需要遵循以下原则:
(1)在抽象类中,可以包含抽象方法,也可以不包含抽象方法,但是包含了抽象方法的类必须被定义为抽象类。
(2)抽象类不能直接实例化,即使抽象类中没有声明抽象方法,也不能实例化。
(3)抽象类被继承后,子类需要实现其中所有的抽象方法。
(4)如果继承抽象类的子类也被声明为抽象类,则可以不用实现父类中所有的抽象方法。
2、接口的声明及实现
接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于7.4.1小节中遗留的问题,可以将draw()方法封装到一个接口中,这样可以让一个类既能继承图形类,又能实现draw()方法接口,这就是接口存在的必要性。在图7.21中描述了各个子类继承图形类后使用接口的关系。
接口使用interface关键字进行定义,其语法如下:
[修饰符]interface 接口名 [extends父接口名列表] {
[public] [static] [final] 常量;
[public] [abstract] 方法;
}
修饰符:可选,用于指定接口的访问权限,可选值为public。如果省略则使用默的访问权限。
接口名:必选参数,用于指定接口的名称,接口名必须是合法的 Java 标识符。一般情况下,要求首字母大写。
extends 父接口名列表:可选参数,用于指定要定义的接口继承于哪个父接口。当使用 extends 关键字时,父接口名为必选参数。
方法:接口中的方法只有定义而没有被实现。
一个类实现一个接口可以使用implements关键字,代码如下:
public class Parallelogram extends Quadrangle implements drawTest!
…//
}
在项目中创建QuadrangleUseInterface类,该类中,首先创建一个drawTest接口,该接口中定义一个公有的draw()方法;然后创建两个类ParallelogramgleUseInterface 和 SquareUseInterface,使它们分别实现 drawTest接口,并分别实现接口中的draw()方法;然后在主方法中分别调用这两个子类的 draw0方法。
代码如下:
package d7z;
interface drawTest{ //定义接口
public void draw();//定义方法
}
class ParallelogramgleUseInterface implements drawTest {// 定义平行四边形类,该类实现了drawTest接口
public void draw() { // 由于该类实现了接口,所以需要覆盖draw()方法
System.out.println("平行四边形.draw()");//输出结果
}
}
class SquareUseInterface implements drawTest { // 定义正方形类,该类实现了drawTest接口
public void draw() {// 由于该类实现了接口,所以需要覆盖draw()方法
System.out.println("正方形.draw()");//输出结果
}
}
public class QuadrangleUseInterface {//创建主类
public static void main(String[] args) {//主方法
drawTest[] d = { // 接口也可以进行向上转型操作
new SquareUseInterface(), new ParallelogramgleUseInterface() };//创建新类
for (int i = 0; i < d.length; i++) {//循环语句
d[i].draw(); // 调用draw()方法
}
}
}
运行结果:
3、多重继承
在Java 中类不允许多重继承,但使用接口就可以实现多重继承,因为一个类可以同时实现多个接口,这样可以将所有需要实现的接口放置在 implements 关键字后并使用逗号“,”隔开,但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。
通过类实现多个接口模拟家庭成员的继承关系,比如,爸爸喜欢抽烟和钓鱼,妈妈喜欢看电视和做饭,儿子完全继承了爸爸妈妈的爱好。定义一个IFather接口,并在其中定义两个 smoking和goFishing。
代码如下:
public interface IFather {//定义一个接口
void smoking();//抽烟的方法
void gofishing();//钓鱼的方法
}
定义一个 IMother 接口,并在其中定义两个方法 watchTV 和 cooking。
代码如下:
public interface IMother {//定义一个接口
void cooking();//做饭的方法
void watchTV();//看电视的方法
}
创建一个名称为 Me 的类,继承IFather 和 IMother 两个接口,并实现接口中定义的方法;然后在main方法中使用Me子类对象分别创建IFather和Mother两个接口的对象,并通过这两个接口对象调用相应的方法执行。
代码如下:
package d7z;
public class Me implements IFather, IMother {// 继承IFather 接口和IMother物
public void watchTV() { //重写watchTV()方法
System.out.println("我喜欢看电视");//输出我喜欢看电视
}
public void cooking() { //重写cooking()方法
System.out.println("我喜欢做饭");//输出我喜欢做饭
}
public void smoking() { //重写smoking()方法
System.out.println("我喜欢抽烟");//输出我喜欢抽烟
}
public void goFishing() { //重写goFishing()方法
System.out.println("我喜欢钓鱼");//输出我喜欢钓鱼
}
public static void main(String[] args){//主方法
IFather father = new Me();//通过子类创建IFather接口对象
System.out.println("爸爸的爱好:");//输出爸爸的爱好:
father.smoking();//使用接口对象调用子类中实现的方法
father.gofishing();//使用接口对象调用子类中实现的方法
IMother mather = new Me(); //通过子类创建IMother接口对象
System.out.println("\n 妈妈的爱好:");//换行输出妈妈的爱好:
mather.cooking();// 使用接口对象调用子类中实现的方法
mather.watchTV();//使用接口对象调用子类中实现的方法
}
public void gofishing() {//gofishing方法
// TODO Auto-generated method stub
}
}
运行结果:
4、区分抽象类与接口
抽象类和接口的区别主要有以下几点。
(1)子类只能继承一个抽象类,但可以实现任意多个接口。
(2)一个类要实现一个接口必须实现接口中的所有方法,而抽象类不必。
(3)抽象类中的成员变量可以是各种类型,而接口中的成员变量只能是public static final的。
(4)接口中只能定义抽象方法,而抽象类中可以定义非抽象方法。
(5)抽象类中可以有静态方法和静态代码块等,接口中不可以。
(6)接口不能被实例化,没有构造方法,但抽象类可以有构造方法。
五、访问控制
前面多次提到了public、private、包等关键字或者概念,这些都是用来控制类、方法或者变量的访问范围的,Java中主要通过访问控制符、类包和final关键字对类、方法或者变量的访问范围进行控制。
1、访问控制符
Java中的访问控制符主要包括public、protected、private和default(缺省)等4种,这些控制符控制着类和类的成员变量以及成员方法的访问权限。
使用访问控制符时,需要遵循以下原则。
(1)大部分顶级类都使用public 修饰;
(2)如果某个类主要用作其他类的父类,该类中包含的大部分方法只是希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰;
(3)类中的绝大部分属性都应该使用private修饰,除非一些static或者类似全局变量的属性,才考虑使用public修饰;
(4)当定义的方法只是用于辅助实现该类的其他方法(即工具方法),应该使用private修饰;
(5)希望允许其他类自由调用的方法应该使用public修饰。
2、Java类包
在Java 中每定义好一个类,通过 Java编译器进行编译之后,都会生成一个扩展名为.class的文件,当这个程序的规模逐渐庞大时,就很容易发生类名称冲突的现象。那么JDK API中提供了成千上万具有各种功能的类,又是如何管理的呢?Java 中提供了一种管理类文件的机制,就是类包。
3、final 关键字
(1)、final类
定义为final 的类不能被继承。
如果希望一个类不允许任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为 final 形式。
final 类的语法如下:
final class类名{ }
如果将某个类设置为final形式,则类中的所有方法都被隐式地设置为final形式,但是final类中的成员变量可以被定义为 final 或非 final 形式。
在项目中创建FinalClass 类,在类中定义doit()方法和变量a,实现主方法中操作变量a自增。
代码如下:
package d7z;
public class FinalClass {//创建类
int a =3;//定义int型变量a并赋值3
void doit() {//调用doit()方法
}
public static void main(String[] args) {//主方法
FinalClass f =new FinalClass();//新建数组
f.a++;//累加
System.out.println(f.a);//输出结果
}
}
运行结果:
(2)、final方法
将方法定义为final类型可以防止子类修改该类的定义与实现方式,同时定义final的方法的执行效率要高于非final方法,在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为 private的方法隐式被指定为final类型,这样将无需将一个定义为 private 的方法再定义为final类型。例如的语句:
private final void test(){
…//省略一些程序代码
}
但是在父类中被定义为private final法似乎可以被子类覆盖,来看下面的实例。
项目中创建FinalMethod类,在该类中创建Parents 类和继承该类的Sub类,在主
法中分别调用这两个类中的方法,并查看 final 类型方法能否被覆盖。
代码如下:
package d7z;
class Parents{ //创建父类
private final void doit() { //调用final类
System.out.println("父类.doit()"); //输出调用父类
}
final void doit2() { //调用doit2()方法
System.out.println("父类.doit2()");//输出调用父类
}
public void doit3() { //调用doit3()方法
System.out.println("父类.doit3()");//输出调用父类
}
}
class Sub extends Parents { //子类继承父类
public final void doit() { //在子类中定义一个doit()方法
System.out.println("子类.doit()");//输出调用子类
}
// final void doit2(){ //调用doit2()方法
// System.out.println("子类.doit2()"); //输出调用子类
// }
public void doit3() { //调用doit3()方法
System.out.println("子类.doit3()");//输出调用子类
}
}
public class FinalMethod {//创建类
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
Sub s=new Sub(); //实例化
s.doit(); //调用 doit()方法
Parents p=s; //执行向上转型操作
//p.doit(); //不能调用private方法
p.doit2(); //调用 doit2()方法
p.doit3(); //调用 doit3()方法
}
}
运行结果:
(3)、final变量
final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final的常量赋值。例如,在类中定义PI值,可以使用如下语句:
final double PI-3.14;
当在程序中使用PI这个常量时,它的值是3.14,如果在程序中再次对定义为final的常量赋值,编译器将不会接受。
final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看作一个对象来引用,所以final可以修饰数组。一个对象引用被修饰为final后,它能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。为了深入了解 final 关键字,来看下面的实例。
在项目的com.lzw包中创建FinalData类,在该类中创建Test内部类,并定义各种类型的final变量。
代码如下:
package d7z;
import static java.lang.System.out; // 导入System.out
import java.util.Random; // 导入需要java.util.Random的包
class Test { //类名
int i = 0;
}
public class FinalData {//创建类
static Random rand =new Random(); //创建新数组
private final int VALUE_1 = 9; //声明一个final常量
private static final int VALUE_2 = 10; //声明一个 final、static常量
private final Test test = new Test(); //声明一个 final引用
private Test test2 = new Test(); //声明一个不是 final 的引用
private final int[] a = {1,2,3,4,5,6 }; //声明一个定义为final 的数组
private final int i4 = rand.nextInt(20); //声明一个final常量
private static final int i5= rand.nextInt(20); //声明一个final常量
public String toString() { //调用toString()
return i4 +" "+i5+" "; //输出结果
}
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
FinalData data = new FinalData(); //创建新数组
//data.test=new Test();
//可以对指定为final的引用中的成员变量赋值
//但不能将定义为final的引用指向其他引用
//data.VALUE_2++;
//不能改变定义为final的常量值
data.test2=new Test(); //可以将没有定义为 final的引用指向其他
for (int i = 0; i < data.a.length; i++) { //控制长度
//a[i]=9;
//不能对定义为final的数组赋值
}
out.println(data); //输出结果
out.println("data2"); //输出data2结果
out.println(new FinalData()); //输出数组结果
out.println(data); //输出结果
}
}
运行结果:
在项目的com.lzw包中创建FinalStaticData类,在该类中创建Random类的对象,在主方法中分别输出类中定义的final变量a1与a2。
代码如下:
package d7z;
import java.util.Random;// 导入需要java.util.Random的包
import static java.lang.System.out;// 导入System.out
public class FinalStaticData {//创建类
private static Random rand = new Random();//实例化一个Random类对象
//随机产生0~10之间的随机数赋予定义为final的a1
private final int a1 =rand.nextInt(10);
//随机产生0~10之间的随机数赋予定义为static final的a2
private static final int a2 =rand.nextInt(10);
public static void main(String[] args) {//主方法
FinalStaticData fdata = new FinalStaticData();//实例化一个对象
//调用定义的为final的a1
out.println("重新实例化对象调用a1的值:"+fdata.a1);
//调用定义的为static final的a2
out.println("重新实例化对象调用a2的值:"+fdata.a2);
//实例化另外一个对象
FinalStaticData fdata2 = new FinalStaticData();//实例化另外一个对象
out.println("重新实例化对象调用a1的值:"+fdata2.a1);//输出结果
out.println("重新实例化对象调用a2的值:"+fdata2.a2);//输出结果
}
}
运行结果:
六、内部类
一个文件中定义两个类,但其中任何一个类都不在另一个类的内部,而在类中再定义一个类,则将在类中再定义的那个类称为内部类。内部类可以分为成员内部类、局部内部类以及匿名类。
1、成员内部类
(1)、成员内部类简介
在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。
成员内部类的语法如下:
public class OuterClass { //外部类
private class InnerClass{//内部类
//…
}
}
在项目中创uterOuterClass类,在类中定义innerClass内部类和doit()方法,在主方法中创建OuterClass类的实例对象和doit()方法。
代码如下:
public class OuterClass {//创建主类
innerClass in = new innerClass();//在外部类实例化内部类对象引用
public void ouf() { //普通类
in.inf();//在外部类方法中调用内部类方法
}
class innerClass{//innerClass类
innerClass(){ //内部类构造方法
}
public void inf() { //内部类成员方法
}
int y=0; //定义内部类成员变量
}
public innerClass doit() {//外部类方法,返回值为内部类引用
//外部类不可以直接访问内部类成员变量
in.y=4;//y=4;
return new innerClass(); //返回内部类引用
}
public static void main(String args[]){//主方法
OuterClass out = new OuterClass();//创建新数组
//内部类的对象实例化操作必须在外部类或外部类的非静态方法中实现
OuterClass.innerClass in = out.doit();//创建新数组doit()
OuterClass.innerClass in2 =out.new innerClass();//创建新数组innerClass()
}
}
运行结果:空
(2)、内部类向上转型为接口
如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口,在接口中声命一个方法。
下面修改例7.21,在项目中创建InterfaceInner类,并定义接口 OutInterface,使内部类InnerClass实现这个接口,最后使doit()方法返回值类型为该接口。
代码如下:
interface OutInterface { //接口
public void f(); // 普通类
}
public class InterfaceInner { //创建类
public static void main(String[] args) { // 主函数
OutClass2 out = new OutClass2(); //实例化一个OutClass2对象
OutInterface outinter = out.doit();//调用doit()方法,返回一个OutInterface 接口
outinter.f(); //存放值
}
}
class OutClass2 { //普通类 OutClass2
private class InnerClass implements OutInterface {//定义一个内部类实现OutInterface接口
InnerClass(String s) { //返回参数
System.out.println(s); // 输出结果
}
public void f() { //创建类
System.out.println("访问内部类中的f()方法");//输出访问内部类中的f()方法
}
}
public OutInterface doit() { //创建 OutInterface doit()方法
return new InnerClass ("访问内部类构造方法"); //输出访问内部类构造方法
}
}
运行结果:
(3)、使用this关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用 this关键字。
在项目中创建TheSameName类,类中定义成员变量x,再定义一个内部类Inner,在内部类中也创建x变量,并在内部类的doit()方法中分别操作两个x变量。
代码如下:
public class TheSameName {//创建主类
private int x;//private定义方法
private class Inner{//普通方法
private int x = 9;//定义x的值为9
public void doit(int x) {//调用的形参是x
x++;//累加
this.x++;//调用内部类变量x
TheSameName.this.x++;//调用外部类变量x
}
}
}
运行结果:空
综上所述,使用员内部类时,应该遵循以下原则:
(1)可以有各种修饰符,可以用private、public、protected、static、final、abstract 等修饰;(2)如果内部类有static限定,就是类级别的,否则为对象级别。类级别可以通过外部类直接访问,对象级别需要先生成外部的对象后才能访问; }
(3)内外部类不能同名;
(4)非静态内部类中不能声明任何 static 成员;
(5)内部类可以互相调用。
2、局部内部类
内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类。
修改例 7.22,将InnerClass 类放在doit()方法的内部。
代码如下:
interface OutInterface2 {//创建主类
}
class OuterClass3 {//主方法
public OutInterface2 doit(final String x) { //doit()方法参数为final
class InnerClass2 implements OutInterface2 {在doit()方法中定义一个内部类
InnerClass2(String s) {//内部类
s = x;//定义s的值为x
System.out.println(s);//输出s
}
}
return new InnerClass2("doit"); 输出结果doit()方法中定义一个内部类
}
}
运行结果:
3、匿名内部类
在return语句编写回值为一个匿名内部类。
代码如下:
interface OutInterface2{ //定义一个接口
}
class OuterClass4 {//创建类
public OutInterface2 doit() { //定义doit()方法
return new OutInterface2() { //声明匿名内部类
private int i = 0;//定义整型变量i的值为0
public int getvalue() {//
return i;//返回到i
}
};
}
}
运行结果:空
匿名类的所有实现代码都需要在大括号之间进行编写。语法如下:
return new A(){
…//内部类体
};
其中,A指类名。
由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成 OutInterface2对象。在匿名内部类定义结束后,需要加分号标识,这个分号并不是代表定义内部类结束的标识,而是代表创建OutInterface2引用表达式的标识。
说明:匿名内部类编译以后,会产生以“外部类名$序号”为名称的.class 文件,序号以 1~n 排列,分别代表 1~n个匿名内部类。
使用匿名内部类时应该遵循以下原则:
(1)匿名类没有构造方法;
(2)匿名类不能定义静态的成员;
(3)匿名类不能用private、public、protected、static、final、abstract 等修饰;
(4)只可以创建一个匿名类实例。
4、静态内部类
在内部类中添加修饰符static,这个内部类就变成为静态内部类了。一个静态内部类中可以声明静态成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一个最大的特点,就是不能使外部类的非静态成员,所以静态内部类在程序开发中比较少见。
可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为 static,就会有更多的限制。静态内部类具有以下两个特点:
(1)如果创建静态内部类的对象,不需要创建其外部类的对象;
(2)不能从静态内部类的对象中访问非静态外部类的对象。
例如,定义一个StaticInnerClass,可以使用如下代码:
public class StaticInnerClass{//创建主类
int x = 100;//定义整型变量x的值为100
static class Inner{//Inner类
void doitInner() {//doitInner方法
//System.out.println("外部类" + x);//不能调用外部类的成员变量x
}
}
}
在静态内部类中定义主方法。
代码如下:
Public class StaticInnetrClass {//创建主类
int x = 100;//定义整型变量x的值为100
static class Inner{//创建Inner类
void doitInner() {//doitInner方法
//System.out.println("外部类" + x);//不能调用外部类的成员变量x
}
public static void main(String[] args) {//主方法
// TODO Auto-generated method stub
System.out.println();//换行
}
}
}
运行结果:空
5、内部类的继承
内部类和其他普通类一样可以被继承,但继承内部类比继承普通类复杂,需要设置专门的语法来完成。
在项目中创建OutputInnerClass类,使OutputInnerClass类继承ClassA类中的内部类ClassB。
代码如下:
package d7z;
public class OutputInnerClass extends ClassA.ClassB{//创建主类,继承内部类
public OutputInnerClass(ClassA a) {//继承类中内部类
a.super();构造方法体中使用 a.super()
}
}
class ClassA {//创建ClassA类
class ClassB{//创建ClassB类
}
}
运行结果:空
在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数必须是该内部类的外部类引用,就像例子中的ClassA a,同时在构造方法体中使用a.super()语句。
七、小结
通过对本章的学习,读者可以了解继承与多态的机制,掌握重载、类型转换等技术,学会使用接口与抽象类,从而对继承和多态有一个比较深入的了解。另外,本章还介绍了 Java 语言中的包、 final关键字的用法以及内部类,尽管读者已经了解过本章所讲的部分知识点,但还是建议初学者仔细揣摩继承与多态机制,因为继承和多态本身是比较抽象的概念,深入理解需要一段时间,使用多态机制必须扩展自己的编程视野,将编程的着眼点放在类与类之间的共同特性以及关系上,使软件开发具有更快的速度、更完善的代码组织架构,以及更好的扩展性和维护性。
THE END!