Java面向对象05

1.接口的思想

前面学习了接口的代码体现,现在来学习接口的思想,接下里从生活中的例子进行说明。
举例:我们都知道电脑上留有很多个插口,而这些插口可以插入相应的设备,这些设备为什么能插在上面呢?主要原因是这些设备在生产的时候符合了这个插口的使用规则,否则将无法插入接口中,更无法使用。发现这个插口的出现让我们使用更多的设备。
总结:接口在开发中的它好处

  1. 接口的出现扩展了功能。
  2. 接口其实就是暴漏出来的规则。
  3. 接口的出现降低了耦合性,即设备与设备之间实现了解耦。
    接口的出现方便后期使用和维护,一方是在使用接口(如电脑),一方在实现接口(插在插口上的设备)。例如:笔记本使用这个规则(接口),电脑外围设备实现这个规则(接口)

2.接口与抽象类的区别

通过实例进行分析和代码演示抽象类和接口的用法。
1. 举例
犬:
 行为:
  吼叫;
  吃饭;
缉毒犬:
 行为:
  吼叫;
  吃饭;
  缉毒;
2. 思考
由于犬分为很多种类,他们吼叫和吃饭的方式不一样,在描述的时候不能具体化,也就是吼叫和吃饭的行为不能明确,描述行为时,行为的具体动作不能明确,可以将这个行为写为抽象行为,那么这个类也就是抽象类。
可是当犬有其他额外功能时,而这个功能并不在这个事物的体系中。这时可以让犬具备犬科自身特点的同时也有其他额外功能,可以将这个额外功能定义接口中。

interface 缉毒{
        void 缉毒();
}
//定义犬科的这个提醒的共性功能
abstract class 犬科{
        abstract void 吃饭();
        abstract void 吼叫();
}
// 缉毒犬属于犬科一种,让其继承犬科,获取的犬科的特性,
//由于缉毒犬具有缉毒功能,那么它只要实现缉毒接口即可,这样即保证缉毒犬具备犬
科的特性,也拥有了缉毒的功能
class 缉毒犬 extends 犬科 implements 缉毒{
        public void 缉毒() {
        }
        void 吃饭() {
              
        }
        void 吼叫() {
        }
}
class 缉毒猪 implements 缉毒{
        public void 缉毒() {
        }
}

3. 通过上面的例子总结接口和抽象类的区别
相同点:
 都位于继承的顶端,用于被其他实现或继承;
 都不能实例化;
 都包含抽象方法,其子类都必须覆写这些抽象方法;
区别:
 抽象类为部分方法提供实现,避免子类重复实现这些方法,提供代码重用性;接口只能包含抽象方法;
 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口;(接口弥补了Java的单继承)
二者的选用:
 优先选用接口,尽量少用抽象类;
 需要定义子类的行为,又要为子类提供共性功能时才选用抽象类。

3.多态

多态由来

//描述狗,狗有吃饭,看家的行为
class Dog
{
        public void eat()
        {
                System.out.println("啃骨头");
        }
        public void lookHome()
        {
                System.out.println("看家");
        }
}
//描述猫,猫有吃饭,抓老鼠行为
class Cat
{    
        public void eat()
        {
                System.out.println("吃鱼");
        }
        public void catchMouse()
        {
                System.out.println("抓老鼠");
        }
}
class DuoTaiDemo
{
        public static void main(String[] args)
        {
                /*有多个狗和猫对象都要调用吃饭这个行为
                  这样会导致d.eat();代码重复性非常严重
                  Dog d = new Dog();
                  d.eat();
                  为了提高代码的复用性,可以将d.eat();代码进行封装
                  public static void method(Dog d)
                  {
                          d.eat();
                  }
                  然后创建对象,直接调用method方法即可
                  method(new Dog());
                  method(new Dog());
                  但当创建Cat对象时,同样需要调用eat方法,同样可以将eat方法进行封装
                  public static void method(Cat c)
                  {
                          c.eat();
                  }
                */
        }
}

后期当有了猪对象时,那么同样要封装eat方法,当每多一个动物,都要单独定义功能,封装方法让动物的对象去做事,会发现代码的扩展性很差。如何提高代码的扩展性呢?发现既然是让动物去eat,无论是dog,还是cat,eat是他们的共性,那么将eat进行抽取,抽取到父类中。

abstract class Animal
{
        //由于每一个小动物的eat方式都不一样,因此在父类中无法准确描述eat的具体
行为
        //因此只能使用抽象方法描述,从而导致这个类也为抽象类
        abstract public void eat();
}

当有了Animal抽象类之后,狗和猫只要继承这个类,实现他们特有的eat方法即可。

//描述狗,狗有吃饭,看家的行为
class Dog extends Animal
{
        public void eat()
        {
                System.out.println("啃骨头");
        }
        public void lookHome()
        {
                System.out.println("看家");
        }
}
//描述猫,猫有吃饭,抓老鼠行为
class Cat extends Animal
{    
        public void eat()
        {
                System.out.println("吃鱼");
        }
        public void catchMouse()
        {
                System.out.println("抓老鼠");
        }
}

既然Dog属于Animal中一种,Cat也属于Animal中一种,那么不用具体面对具体的动物,而只要面对Animal即可。
Dog d = new Dog();
Animal a = new Dog();
Cat c = new Cat();
Animal aa = new Cat();
通过上述代码发现,Animal类型既可以接受Dog类型,也可以接受Cat类型,当再让动物去做事时,不用面对具体的动物,而只要面对Animal即可。因此上述method方法可以修改为:

public static void method(Animal a)
 {
         a.eat();
 }

method(Animal a)可以接受Animal的子类型的所有小动物,而method方法不用在关心是具体的哪一个类型。即就是只建立Animal的引用就可以接收所有的Dog和Cat对象进来,让它们去eat。从而提高了程序的扩展性。
其实上述代码就已经形成了多态
父类的引用或者接口的引用指向了自己的子类对象
Dog d = new Dog();//Dog对象的类型是Dog类型。
Animal a = new Dog();//Dog对象的类型右边是Dog类型,左边Animal类型。
多态的好处
提高了程序的扩展性。
多态的弊端
通过父类引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法。
多态的前提

  1. 必须有关系:继承,实现。
  2. 通常都有重写操作

当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。向上转型的好处是隐藏了子类类型,提高了代码的扩展性。
但向上转型也有弊端,只能使用父类共性的内容,而无法使用子类特有功能,功能有限制。

//描述动物这种事物的共性eat
abstract class Animal{
        abstract void eat();
}
//描述dog这类事物
class Dog extends Animal{
        void eat(){
                System.out.println("啃骨头");
        }
        void lookHome(){
                System.out.println("看家");
        }
}
//描述小猫
class Cat extends Animal{
        void eat(){
                System.out.println("吃鱼");
        }
        void catchMouse(){
                System.out.println("抓老鼠");
        }
}
public class Test {
        public static void main(String[] args) {
                Animal a = new Dog(); //这里形成了多态
                a.eat();
                //a.lookHome();//使用Dog特有的方法,需要向下转型
                Dog d = (Dog)a;
                d.lookHome();
                Animal a1 = new Cat();
                a1.eat();
                /*
                 由于a1具体指向的是Cat的实例,而不是Dog实例,这时将a1强制转成Dog
类型,将会发生ClassCastException异常,在转之前需要做健壮性判断
                 if(!Dog instanceof a1){ // 判断当前对象是否是Dog类型
                       System.out.println("类型不匹配,不能转换");
                       return;// 方法执行中止
                 }
                Dog d1 = (Dog)a1;
                d1.catchMouse();
                */
        }
}

什么时候使用向上转型
当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作,这时就可以使用向上转型。
什么时候使用向下转型
当要使用子类特有功能时,就需要使用向下转型。
向下转型的好处:可以使用子类特有功能。
弊端是:需要面对具体的子类对象;在向下转型时容易发生ClassCastException类型转换异常。在转换之前必须做类型判断
举例

/*
   描述毕老师和毕姥爷,
   毕老师拥有讲课和看电影功能
   毕姥爷拥有讲课和钓鱼功能
 */
class 毕姥爷{
        void 讲课(){
                System.out.println("政治");
        }
        void 钓鱼(){
                System.out.println("钓鱼");
        }
}
//毕老师继承了毕姥爷,就有拥有了毕姥爷的讲课和钓鱼的功能,
//但毕老师和毕姥爷的讲课内容不一样,因此毕老师要覆盖毕姥爷的讲课功能
class 毕老师 extends 毕姥爷{
        void 讲课(){
                System.out.println("Java");
        }
        void 看电影(){
                System.out.println("看电影");
        }
      
}
public class Test {
        public static void main(String[] args) {
                //多态形式
                毕姥爷 a = new 毕老师(); //向上转型
                a.讲课(); // 这里表象是毕姥爷,其实真正讲课的仍然是毕老师,因此调用的
也是毕老师的讲课功能
                a.钓鱼(); // 这里表象是毕姥爷,但对象其实是毕老师,而毕老师继承了毕姥
爷,即毕老师也具体钓鱼功能
              
                // 当要调用毕老师特有的看电影功能时,就必须进行类型转换
                毕老师 b = (毕老师)a; //向下转型
                b.看电影();
                }
}

总结:转型过程中,至始至终只有毕老师对象做着类型转换,父类对象是无法转成子类对象的。

/*
       描述笔记本,笔记本使用USB鼠标,USB键盘
       定义USB接口,笔记本要使用USB设备,即笔记本在生产时需要预留可以插入USB
设备的USB接口,即就是笔记本具备使用USB设备的功能,但具体是什么USB设备,笔记本
并不关心,只要符合USB规格的设备都可以。鼠标和键盘要能在电脑上使用,那么鼠标和键
盘也必须遵守USB规范,不然鼠标和键盘的生产出来无法使用
 */
//定义鼠标、键盘,笔记本三者之间应该遵守的规则
interface USB{
        void open();//开启功能
        void close();//关闭功能
}
//鼠标实现USB规则
class Mouse implements USB{
        public void open(){
                System.out.println("鼠标开启");
        }
        public void close(){
                System.out.println("鼠标关闭");
        }
}
//键盘实现USB规则
class KeyBoard implements USB{
        public void open(){
                System.out.println("键盘开启");
        }
        public void close(){
                System.out.println("键盘关闭");
        }    
}
// 定义笔记本
class NoteBook{
        //笔记本开启运行功能
        public void run(){
                System.out.println("笔记本运行");
        }
        //笔记本使用usb设备,这时当笔记本实体调用这个功能时,必须给其传递一个符
合USB规则的USB设备
        public void useUSB(USB usb){
                //判断是否有USB设备
                if(usb != null){
                        usb.open();
                        usb.close();
                      
                }
        }
        public void shutDown(){
                System.out.println("笔记本关闭");
        }
}
public class Test {
        public static void main(String[] args) {
                //创建笔记本实体对象
                NoteBook nb = new NoteBook();
                //创建鼠标实体对象
                Mouse m = new Mouse();
                //创建键盘实体对象
                KeyBoard kb = new KeyBoard();
                //笔记本开启
                nb.run();
                //笔记本使用鼠标
                nb.useUSB(m);
                //笔记本使用键盘
                nb.useUSB(kb);
                //笔记本关闭
                nb.shutDown();
        }
}

多态中成员的特点
多态出现后会导致子父类中的成员变量有微弱的变化。看如下代码

class Fu
{
        int num = 4;
}
class Zi extends Fu
{
        int num = 5;
}
class Demo
{
        public static void main(String[] args)
        {
                Fu f = new Zi();
                System.out.println(f.num);
                Zi z = new Zi();
                System.out.println(z.num);
        }
}

多态成员变量
当子父类中出现同名的成员变量时,多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记:编译和运行都参考等号的左边。编译运行看左边。

class Fu
{
        int num = 4;
        void show()
        {
                System.out.println("Fu show num");
        }
}
class Zi extends Fu
{
        int num = 5;
        void show()
        {
                System.out.println("Zi show num");
        }
}
class Demo
{
        public static void main(String[] args)
        {
                Fu f = new Zi();
                f.show();
        }
}

多态成员函数
编译时期:参考引用变量所属的类,如果没有类中没有调用的函数,编译失败。
运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员函数。
简而言之:编译看左边,运行看右边。

class Fu
{
        int num = 4;
        static void method()
        {
                System.out.println("fu static method run");
        }
}
class Zi extends Fu
{
        int num = 5;
        static void method()
        {
                System.out.println("zi static method run");
        }
}
class Demo
{
        public static void main(String[] args)
        {
                Fu f = new Zi();
                f.method();
        }
}

多态静态函数
多态调用时:编译和运行都参考引用类型变量所属的类中的静态函数。
简而言之:编译和运行看等号的左边。其实真正调用静态方法是不需要对象的,静态方法通过类直接调用。
结论

  1. 对于成员变量和静态函数,编译和运行都看左边。
  2. 对于成员函数,编译看左边,运行看右边。

4.内部类

什么是内部类
将类写在其他类的内部,可以写在其他的成员位置和其他类的局部位置,这时写在其他类内部的类就称为内部类。
什么时候使用内部类
在描述事物,若一个事物内部还包含其他可能包含的事物,比如在描述汽车时,汽车中还包含这发动机这个事物,这时发动机就可以使用内部类来描述。即就是内部事物必须寄宿在外部事物内部。
内部类代码体现

class Outer{
        //外部类的成员变量
        int num = 5;
        //写在Outer成员位置上的内部类
        class Inner{
                //内部类的成员函数
                void show(){
                        //在内部类中访问外部类的成员变量
                        System.out.println("Outer num = "+num);
                }
        }
}

内部类访问规则
内部类可以直接访问外部类中的成员,但外部类不能直接访问内部类,若要访问,必须创建内部类对象才能访问。

public class Test {
        public static void main(String[] args) {
                Outer out = new Outer();
                out.method();
        }
}
class Outer{
        //外部类的成员变量
        int num = 5;
        //写在Outer成员位置上的内部类
        class Inner{
                //内部类的成员函数
                void show(){
                        //在内部类中访问外部类的成员变量
                        System.out.println("Outer num = "+num);
                }
        }
      
        public void method(){
                //创建内部类对象,访问内部类的成员函数或者变量
                Inner in = new Inner();
                in.show();
        }
}

非静态非私有内部类
当内部类在外部类成员位置上的时候,内部类就是外部类成员的一份子。这时这个内部类就可以使用成员修饰符修饰,比如public、static、private
如果内部类的权限是非私有的,就可以在外部类以外的其他类中访问。即可以通过创建外部类对完成访问内部类。
比如以上程序:就可以使用如下格式访问内部类的show方法。

public class Test {
    public static void main(String[] args) {
       //通过创建外部类对象,接着创建内部类对象
       Outer.Inner in = new Outer().new Inner();
       in.show();
    }
}
class Outer{
    //外部类的成员变量
    int num = 5;
    //写在Outer成员位置上的内部类
    class Inner{
       //内部类的成员函数
       void show(){
           //在内部类中访问外部类的成员变量
           System.out.println("Outer num = "+num);
       }
    }
  
    public void method(){
       //创建内部类对象,访问内部类的成员函数或者变量
       Inner in = new Inner();
       in.show();
    }
}

静态的非私有内部类
当内部类在外部类成员位置上被static修饰时,由于静态可以直接使用类名调用,则创建内部类对象的方式:

public class Test {
        public static void main(String[] args) {
                //因为内部类是静态,所以不需要创建Outer的对象。直接创建内部类对象
就哦了。
                Outer.Inner in = new  Outer.Inner();
                in.show();
        }
}
class Outer{
        //外部类的成员变量
         static int num = 5;
        //写在Outer成员位置上的静态内部类
        static class Inner{
                //内部类的成员函数
                void show(){
                        //在内部类中访问外部类的成员变量
                        System.out.println("Outer num = "+num);
                }
        }
}

访问静态内部类的静态成员
由于内部类是静态的,可以直接使用外部类名调用内部类,而内部类的成员也是静态的,这时同样可以通过类名调用内部类的静态成员。

public class Test {
    public static void main(String[] args) {
       //既然静态内部类已随外部类加载,而且静态成员随着类的加载而加载,就不需要
对象,直接用类名调用即可
       Outer.Inner.show2();
    }
}
class Outer{
    //外部类的成员变量
     static int num = 5;
    //写在Outer成员位置上的静态内部类
    static class Inner{
       //内部类的静态方法
       static void show2(){
           System.out.println("static Inner method num = "+num);
       }
    }
}

使用static修饰内部类,该内部类属于其外部类,而不属于外部类的对象;
静态内部类可包括静态成员也可包括非静态成员。根据静态成员不能访问非静态成员的规定,所以静态内部类不能访问外部类实例成员,只能访问外部类的静态成员。
非静态内部类细节
注意非静态内部类中不能定义静态成员变量和静态成员函数。但可以定义静态成员常量。原因常量在生成字节码文件时直接就替换成对应的数字。
当内部类在外部类成员位置上被私有修饰,在外部类意外的其他地方是无法访问的。只能在外部类中访问
面试常见的问题
以下代码运行中,请注意如何访问对应的num变量

public class Test {
        public static void main(String[] args) {
                Outer.Inner in = new Outer().new Inner();
                in.show();
        }
}
class Outer {
        int num = 5;// 外部类的成员变量
        class Inner {
                int num = 6;// 内部类的成员变量
                void show() {
                        int num = 7; // 内部类局部变量
                        System.out.println("内部类局部num=" + num);
                        System.out.println("内部类成员num=" + this.num);
                        System.out.println("外部类成员num=" + Outer.this.num);
                }
        }
}

为什么内部类可以直接访问外部类的成员,那时因为内部类持有外部类的引用(外部类.this)。对于静态内部类不持有 外部类.this 而是直接使用 外部类名。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值