7.1 类的封装
封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其载体就是类。本节将详细扫一扫,看介绍如何将类封装。
例7.1创建R1这个类,实现餐馆点菜的场景.
-
public class R1 { 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 + "切葱花"); } }
例7.2将厨师封装成cook类,实现餐馆点菜的场景.
-
public class R2 { 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); } }
将厨师单独封装成一个类,将厨师的工作定义成厨师类的行为,当我们想让厨师做菜,只能通过调用对象成员方法的方式实现,而我们却不知道这个方法到底是怎么写的,所以就无法随意修改了。餐馆没有义务告诉我们厨师的任何信息,并且厨师也不会随意受我们差遣,所以说厨师有些属性和行为不公开
例7.3将厨师的属性和部分法方法用private修饰
public class R3 { public static void main(String[] args) { Cook2 cook =new Cook2();//创建厨师类的对象 System.out.println("**请让厨师为我做一份香辣肉丝。"); cook.cooking("香辣肉丝");//厨师烹饪香辣肉丝 System.out.println("**你们的厨师叫什么名字?"); System.out.println(cook.name);//厨师回答自己的名字 System.out.println("**请让厨师给我切一点葱花。***"); cook.cutOnion();// 厨师去切葱花 }} class Cook2{ private String name;//厨师的名字 public Cook2() { this.name ="Tom Cruise"; }//厨师的名字叫Tom Cruise private void cutOnion() {//厨师切葱花 System.out.println(name+"切葱花"); } private void washVegetables() {//厨师洗蔬菜 System.out.println(name+ "洗蔬菜"); } void cooking(String dish) {//厨师烹饪顾客点的菜 washVegetables(); cutOnion(); System.out.println (name+"开始烹饪"+ dish); } }
从这个例子我们就能看出,作为顾客,我始终是和服务员进行交流,再由服务员与厨师进行交流,整个过程中,顾客与厨师是完全没有交集的。作为顾客,我不知道我品尝的美食是由哪在厨师用何种方法烹饪出来的,这种编程模式,就是封装。
将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。7.2 类的继承的
继承在面向对象开发思想中是一个非常重要的概念,它使整个程序架构具有一定的弹性,在程序中复用已经定义完善类不仅可以减少软件开发周期,还可以提高软件的可维护性和可扩展性。本节将详细讲解类的继承。
在第6章中曾简要介绍过继承,其基本思想是基于某个父类的扩展,制定出一个新的子类,子米可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父可以说平行四边形类继承了四边形类public class R4 { 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) { R4 water = new R4();//创建餐厅对象,为顾客提供服务 System.out.println("**请让厨师为我做一份香辣肉丝。***"); water.takeOrder("香辣肉丝");//服务员给顾客下单 System.out.println("**你们的厨师叫什么名字?***"); System.out.println(water.saySorry());// 服务员给顾客善意的答复 System.out.println("**请让厨师给我切一点葱花。***"); System.out.println(water.saySorry());// /服务员给顾客善意的答复 } }
例7.5创建Pad2类,继承Computer类
-
class Computer {//父类:电脑 String screen="液晶显示屏"; void startup() { System.out.println("电脑正在开机,请等待..."); } } public class Pad2 extends Computer { String battery="5000毫安电池";// 子类独有的属性 public static void main(String[] args) { Computer pc = new Computer();// 电脑类 System.out.println("computer的屏幕是:"+pc.screen); pc.startup(); Pad2 ipad = new Pad2();//平板电脑类 System.out.println("pad的屏幕是:"+ ipad.screen);//子类可以直接使用父类 System.out.println("pad的电池是:"+ipad.battery);//子类独有的属性 ipad.startup();// 子类可以直接使用父类方法 } }
7.2.2方法的重写
1.重写的实现继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重新编写成员方法的实现内容,更改成员方法的存储权限,或修改成员方法的返回值类型(重写父类成员方法的返回值类型是基于J2SE5.0版本以上编译器提供新功能)。
例7.6创建Pad3类继承Computer2类,并重写父类的showPicture方法
class Computer2{//父类:电脑 void showPicture(){ System.out.println("手指点击触摸屏"); }} public class Pad3 extends Computer2{//子类:平板电脑 void showPicture() { System.out.println("手机点击触摸屏"); } public static void main(String[] args) { Computer2 pc=new Computer2();// 电脑类 System.out.print("pc打开图片:"); pc.showPicture(); Pad3 ipad=new Pad3();//平板电脑类 System.out.print("ipad打开图片:"); ipad.showPicture();//重写父类方法 Computer2 computerpad=new Pad3();//父类声明,子类实现 System.out.print("computerpad打开图片:"); computerpad.showPicture();//调用父类方法,实现子类重写的逻辑 }}
例7.7创建Pad4类,继承Computer3类,重写父类方法,并使用super关键字代表父类方法
class Computer3 {// 父类:电脑 String sayHello() { return"欢迎使用"; }} public class Pad4 extends Computer3{//子类:平板电脑 String sayHello() {// 子类重写父类方法 return super.sayHello()+"平板电脑";//调用父类方法,在其结果后添加字符串 } public static void main(String[] args) { Computer3 pc = new Computer3();// 电脑类 System.out.println(pc.sayHello()); Pad4 ipad = new Pad4();// 平板电脑类 System.out.println(ipad.sayHello()); }}
7.2.3 所有类的父类——Object类
在开始学习使用class关键字定义类时,就应用了继承原理,因为在Java中,所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一个类 class Anything (时,总是在继承,除非某个类已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的,可见Java中的每个类 等价于都源于java.lang.Object类,如 String、Integer等类都是继承于Object类;除此之外自定义的类也都继承于Object类。由于所 class Anything extends Object有类都是Object子类,所以在定义类时,省略了extends Object
关键字,如图7.10所示便描述了这一原则。 图7.10定义类时可以省略extends Objec
在Object类中主要包括cloneO、finalizeO、equalsO、toStringO 关键字
等方法,其中常用的两个方法为equals()和toString0方法。由于所有的类都是Object 类的子类,所以任何类都可以重写Object类中的方法。例7.8在项目中创建ObjectInstance类,在类中重写Object类的toSpring方法,并在组方法中输出该类的实例对象.
-
public class Ob { public String toString() {//重写toString()方法 return"在"+getClass().getName()+"类中重写toString()方法"; } public static void main(String[] args) { System.out.println(new Ob());//打印本类对象 } }
1. getClass()方法
getClass方法是Objecet类定义的方法,它会返回对象执行时的Class实例,然后使用比实的用getName(0方法可以取得类的名称。
语法如下:
getClass ().getName () ;
可以将getClass()方法与toString0方法联合使用。
2. toString()方法
toString)方法的功能是将一个对象返回为字符串形式,它会返回一个String实例。在实际题用中通常重写toString0方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字形得连接时,将自动调用重写的toString0方法。3. equals()方法
前面章节曾讲解过equals0方法,当时是比较“=”运算符与equals方法,说明“=”比较链是两个对象的引用是否相等,而equals方法比较的是两个对象的实际内容。带着这样一个理论看下面的实例。
例7.9项目中创建类OverWritrEqual类,在类的组方法中定义两个字符串对象,调用法刚发判断两个字符串对象是否相等
class V{} public class Over { public static void main(String[] args) { String s1="123";//实例化两个对象,内容相同 String s2="123"; System.out.println(s1.equals(s2));//使用equals()方法调用 V v1=new V();//实例化两个V类对象 V v2=new V(); System.out.println(v1.equals(v2));//使用equals()方法比较v1与v2对象 } }
7.3类的多态
多态意为一个名字可具有多种语义,在程序设计语言中,多态性是指“一种定义,多种实现”,例如,运算符“+”作用于两个整型量时是求和,而作用于两个字符型量时则是将其连接在一起。利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。类的多态性可以从两方面体现:一是方法的重载,二是类的上下转型,本节将分别对它们进行详细讲解。
7.3.1 方法的重载
在第6章中曾学习过构造方法,知道构造方法的名称由类名决定,所构造方法只有一个名称,
但如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需Stanc要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到“方法重载”。
虽然方法重载起源于构造方法,但是它也可以应用到其他方法中。本节将讲述方法的重载。
方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。为了更好地解释重载,来看下面的实例。例7.10在项目中创建类,在类中编写方法的多个重载形式,然后在组方法中分别输出这些方法的返回值
public class OverLoadText { public static int add(int a) { return a; } //定义第一个方法参数个数不同的方法 public static int add(int a,int b) { return a+b; } //定义与第一个方法相同名称.参数类型不同的方法 public static double add(double a,double b) { return a+b; } //定义一个成员方法 public static int add(double a,int b) { return (int)(a+b); }//定义不定长参数 public static int add(int...a) { int s=0; //根据参数个数循环操作 for(int i=0;i<a.length;i++) { s +=a[i];//将每个参数的值相加 } return s;//将计算结果返回 } public static void main(String args[]) {//主方法 System.out.println("调用add(int)方法:"+ add(1));//调用add(int)方法 System.out.println("调用add(int,int)方法:"+ add(1,2));//调用add(int,int)方法方法 System.out.println("调用add(double,double)方法:"+ add(2.1,3.3));//调用add(double,double)方法 System.out.println("调用add(int a, double b)方法:"+ add(1, 3.3));//调用add(int a, double b)方法 System.out.println("调用add(double a, int b)方法:"+ add(2.1, 3));//调用add(double a, int b)方法 System.out.println("调用add(int... a)不定长参数方法:"+ add(1,2,3,4,5,6,7,8,9));//调用add(int... a)方法 System.out.println("调用add(int... a)不定长参数方法:"+ add(2,3,4));//调用add(int... a)方法 }}
内 部 类
前面曾经学习过在一个文件中定义两个类,但其中任何一个类都不在另一个类的内部,而如果在类中再定义一个类,则将在类中再定义的那个类称为内部类,这里可以想像一下汽车和发动机的关系,很显然,此处不能单独用属性或者方法表示一个发动机,发动机是一个类,而发动机又在汽车之中,汽车也是一个类,正如同内部类在外部类之中,这里的发动机类就好比是一个内部类。内部类可分为成员内部类、局部内部类以及匿名类。本节将对内部类的使用进行讲解
在项目中创建OuterClass类,在类中定义innerClas 内部类和dot)方法,在生展创建OmarClass类的实例对象和doit()方法
public class OuterClass { innerClass in=new innerClass(); public void ouf() { in.inf(); }class innerClass{//创建一个类 innerClass(){ }public void inf() {//内部类成员方法 }int y=0;//定义内部类成员变量 }public innerClass doit() { //外部类方法,返回值为内部类引用 in.y= 4; //外部类不可以直接访问内部类成员变量 return new innerClass(); }public static void main(String args[]) { OuterClass out = new OuterClass(); //内部类的对象实例化操作必须在外部类或外部类的非静态方法中实现 OuterClass.innerClass in = out.doit(); OuterClass.innerClass in2 = out.new innerClass(); }}
下面修改例,在项目中创建Interfacelnner类,并定义接口Outnterfas类InnerClass实现这个接口,最后使doit)方法返回值类型为该接口。代码如下
-
interface OutInterface{ public void f(); }//定义一个接口 public class InterfaceInner { public static void main(String args[]){//实例化一个OuterClass2对象 OuterClass2 out = new OuterClass2(); //调用doit()方法,返回一个OutInterface接口 OutInterface outinter = out.doit(); outinter.f(); //调用f()方法 }} class OuterClass2 { //定义一个内部类实现OutInterface 接口 private class InnerClass implements OutInterface {//继承类 OutInterface InnerClass(String s){//内部类构造方法 System.out.println(s); } public void f() {//实现接口中的f()方法 System.out.println("访问内部类中的f()方法"); }} public OutInterface doit() { //定义一个方法,返回值类型OutInter接口 return new InnerClass("访问内部类构造方法"); } }
3.使用this 关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用 his关键字。例 7.23在项目中创建TheSameName类,在类中定义成员变量x,再定义一个内部类 hmmser,部类中也创建x变量,并在内部类的doit(方法中分别操作两个x变量。局部内部类
内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的字
中均可以定义内部类。 -
匿名内部类
下面将例中定义的内部类再次进行修改,在doit方法中将retum语句和内部关类定义语句工面通过一个实例说明。
例在returm语句中编写返回值为一个匿名内部类。interface OutInterface2 {//定义一个接口 }class OuterClass4{ public OutInterface2 doit() {//定义doit()方法 return new OutInterface2() {//声明匿名内部类 private int i= 0; public int getValue() { return i;//返回值 } }; }}
静态成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一个最大的特点,用外部类的非静态成员,所以静态内部类在程序开发中比较少见。
可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部意。但如果内部类被定义为static,就会有更多的限制。静态内部类具有以下两个特点:
(1)如果创建静态内部类的对象,不需要创建其外部类的对象;
(2)不能从静态内部类的对象中访问非静态外部类的对象。
例如,定义一个静态内部类StaticInnerClass,可以使用如下代码:
public classStaticInnerClass{ int x = 100;//定义一个变量x static classInnert{//静态内部类 void doitInner(){ // System.out.print1n("外部类"+x);}//不能调用外部类的成员变量x
上面代码中,在内部类的doidnner()方法中调用成员变量x,由于Inner被修饰为 state k而成员变量x却是非static类型的,所以在doitInner0方法中不能调用x变量。
进行程序测试时,如果在每一个Java文件中都设置一个主方法,将出现很多额外代码,而画。本身并不需要这些主方法,为了解决这个问题,可以将主方法写入静态内部类中。例7.26在静态内部类中定义主方法。
public class Static { int x=100;//定义变量X static class Inner{//静态内部类 void doitInner() { //System.out.println("外部类"+x); } } public static void main(String[] args) {//主方法 System.out.println();//输出 } }
如果编译例7.26中的类,将生成一个名称为由的内家 这样业StateChass类,只要使用java StaticInnerClassSInner,就可以运行主方法中的内容,这样当完成测试,将所有.class文件打包时,只要删除StaticInnerClass$Inner独立类即可。
7.6.5内部类的继承
看视频 内部类和其他普通类一样可以被继承,但是继承内部类比继承普通类复杂,需要设置专门的法来完成。
例 7.27在项目中创建OutputInnerClass类,使OutputInnerClass类继承ClassA类中的内部ClassB。(实例位置:资源包\code(07\27)public class OutputInnerClass extends ClassA.ClassB{//继承内部类classB public OutputInnerClass(ClassA a) { a.super(); } }class ClassA{ class ClassB }}
super()是当前对象的直接父类的无参的构造函数。
在这个问题中super()的执行就是执行了Outer.Inner 的默认构造函数。
构造函数继承
规则:
1 子类无条件继承父类不含参数的构造函数
2 若子类无自己的构造函数,父类的无参构造函数做为子类构造函数,若有创建对象先执行父类无参构造函数,再执行自己的构造函数
3 父类中含参构造函数,子类可以在自己的构造函数中用super()来调用,必须放在第一句.本章小结
重载构造函数是一种特殊的函数,使用构造函数的目的是用来在对象实例化时初始化对象的成员变量。由于构造函数名字必须与类名一致,我们想用不同的方式实例化对象时,必须允许不同的构造方法同时存在
重写
重写(又叫覆盖)是继承的实现方式之一,也就是说只有在子类中才会出现方法的重写。重写是指在子类中保留父类成员方法的名称不变,参数列表不变,重写成员方法的实现内容,修改成员方法的返回值类型,或更改成员方法的存储权限。
重构
重构是继承中一种特殊的重写方式,只重写子类方法中的实现内容,成员方法的名称,参数类型、个数及成员方法的返回值都保持不变。
向上转型
1. 本质:父类的引用指向了子类的对象。
2. 语法:父类类型 引用名 = new 子类类型();向下转型
1. 语法:子类类型 引用名 = (子类类型) 父类引用。
静态内部类
1、一个静态内部类中可以声明 static成员,但是在非静态内部类中不可以声明静态成员
2、静态内部类有一个最大的特点,就是不可以使用外部类的非静态成员,所以静态内部类在程序开发中比较少见
3、静态内部类不可以直接调用外部类的非静态成员变量
4、进行程序调试时,如果在每一个 Java 文件中都设置一个主方法,将出现很多额外代码,而程序本身并不需要这些主方法,为了解决这个问题,可以将主方法写入静态内部类中内部类的继承
必须要满足四个条件:
1、继承内部类的前面要跟随外部类名+"."
2、在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法
3、并且该构造方法的参数为需要继承内部类的外部类的引用
4、在构造方法体中使用a.super()语句,这样才为继承提供了必要的对象引用接口的定义 接口:方法定义和常量值的集合
接口的特点:
1、接口是一种特殊的抽象类,只包含常量和方法的定义,而没有方法的实现。
2、通过接口可以指明多个需要实现的方法,而不需考虑这些类之间的层次关系。
在类体中可以使用接口中定义的常量,必须实现接口中定义的所有方法。
3、一个类可以实现多个接口,在implements字句中用逗号隔开。