Java基础-面向对象小知识(下)

来源:互联网 发布:mac如何删除应用程序 编辑:程序博客网 时间:2024/05/04 20:57

前面一篇已经总结了许多的面向对象方面的小知识点(点此传送门),下面继续整理。


多态

多态(polymorphic):事物存在的多种形态

多态前提:

要有继承关系

要有方法重写

要有父类引用指向子类对象

class Animal {public void eat() {System.out.println("动物吃饭");}}class Cat extends Animal{//继承@Overridepublic void eat() {//重写System.out.println("猫吃鱼");}}class Demo {    public static void main(String[] args) {    Animal animal = new Cat();//父类引用指向子类对象    animal.eat();    }}//outPut:猫吃鱼
多态的应用小例子:

class Fruit {public void squeeze() {System.out.println("榨水果汁");}}class Apple extends Fruit {@Overridepublic void squeeze() {System.out.println("榨出一杯苹果汁");}}class Orange extends Fruit {@Overridepublic void squeeze() {System.out.println("榨出一杯橘子汁");}}class Juicer {public void run(Fruit f) {f.squeeze();}}class Demo {    public static void main(String[] args) {    Juicer juicer = new Juicer();    juicer.run(new Apple());    juicer.run(new Orange());    }}/*outPut: * 榨出一杯苹果汁 * 榨出一杯橘子汁 */

程序绑定的概念

绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定

静态绑定:

在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。

java当中的方法只有final,static,private和构造方法(隐式也是静态的)是前期绑定,属性也是前期绑定的

动态绑定

在运行时根据具体对象的类型进行绑定。

过程:  Parent child = new Child()        child.call()

1.编译器检查对象的声明类型和方法名    搜索Parent类的所有call方法和超类继承下来的call方法(重载)

2.编译器检查方法调用中提供的参数类型,匹配最匹配的方法并调用(重载解析)

3.程序运行并且使用动态绑定调用方法时,虚拟机必须调用child实际对象类型的匹配方法(Child类中的call),若Child类中有call方法则调用,否则从超类中寻找call方法,以此类推

多态的好处和弊端

多态的好处:

提高了代码的维护性(继承保证)

提高了代码的扩展性(由多态保证)

多态的弊端:

不能使用子类的特有属性和行为

多态小习:

观察以下程序是否有问题,无则写出结果

class Fu {    public void show() {        System.out.println("fu show");    }}class Zi extends Fu {    public void show() {        System.out.println("zi show");    }    public void method() {        System.out.println("zi method");    }}class Test1Demo {    public static void main(String[] args) {        Fu f = new Zi();        f.method();        f.show();    }}
/* * 错误 * 多态的弊端,不能使用子类的特有属性和行为 * f.method();  会报错 */
观察以下程序是否有问题,无则写出结果
class A {    public void show() {        show2();    }    public void show2() {        System.out.println("大");    }}class B extends A {    public void show2() {        System.out.println("家");    }}class C extends B {    public void show() {        super.show();    }    public void show2() {        System.out.println("好");    }}class Demo {    public static void main(String[] args) {        A a = new B();        a.show();        B b = new C();        b.show();    }}
首先先再来深入了解下this:

A a= new A();  a.aa();    实际表示方式    A.aa(a);   编译器会暗自吧“所操作的对象的引用”作为第一个参数传给方法aa

/* * A a = new B(); * a.show(); * 分析: * a.show(),由于多态,会调用B里面的show方法,但是其B类中无show方法,所以会调用父类的show方法 * 但在show方法中又调用了show2(),那么这个show2是调用子类的show2还是父类的show2。 * 其实在show()方法内增加一句:System.out.println(this); * 可以发现其结果打印的是 B@15db9742 那么可以说明了是类B调用了show,this.show2当然是子类的方法。 * 故 a.show(); 结果为 家 */
下面的两句可以会稍微的难点。

首先,来改造我们的类B,使其变得更加明显

class B extends A {@Overridepublic void show() {//这里的show 来自于父类继承    System.out.println(this);//这里增加个打印this方法,可确定谁调用        show2();    }    public void show2() {        System.out.println("家");    }}

先来看下结果:

C@6d06d69c好
/* * B b = new C(); * b.show(); * 分析: * 由于多态,调用的是类C内的show方法,然后super.show() 调用了类B的show方法(继承下来),这里编译器也还是会传一个this进去 * 根据其结果分析该this为类C,那么再调用show2()方法自然也是调用类C里的方法了 */


抽象类

什么是抽象类?抽象类通俗点说就是看不懂。拿我们上面的Animal类来说,每一个动物吃这个行为都是不一样的,就是看不懂。所以我们也不能实例化(new)这个看不懂的东西。

抽象类和抽象方法必须用abstract关键字修饰

abstract class 类名 {}public abstract void eat();
抽象类不一定有抽象方法,有抽象方法的类一定是抽象类或者是接口。

抽象类的子类,要么是抽象类,要么重写抽象类中的所有抽象方法。

改进上述的Animal类

abstract class Animal {public void eat() {System.out.println("动物吃饭");}}class Cat extends Animal{@Overridepublic void eat() {System.out.println("猫吃鱼");}}class Demo {    public static void main(String[] args) {    //Animal animal = new Animal();  编译错误,不能被实例化    Animal animal = new Cat(); //抽象类多态    animal.eat();    }}//outPut:猫吃鱼

抽象类的成员特点:

成员变量:既可以是变量,也可以是常量。

Q:abstract是否可以修饰成员变量?当然不能,想想一个变量如何的抽象。

Q:抽象类是否有构造方法?有,用于子类访问父类数据的初始化

成员方法:既可以是抽象的,也可以是非抽象的

抽象方法 强制要求子类做的事情

非抽象方法 子类继承的事情,提高代码复用性


Q1: 一个抽象类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?

可以,这么做目的只有一个,就是不让其他类创建本类对象,交给子类完成。

Q2:abstract不能和哪些关键字共存

abstract和static

被abstract修饰的方法没有方法体,而被static修饰的可以用类名.调用,但是类名.调用抽象方法是没有意义的

abstract和final

被abstract修饰的方法强制子类重写,而被final修饰的不让子类重写,所以他俩是矛盾的

abstract和private

被abstract修饰的是为了让子类看到并强制重写,而被private修饰不让子类访问,所以他俩是矛盾的

abstract class Demo {    public static abstract void print1();//编译错误,非法的修饰符组合    public final abstract void print2();//编译错误,非法的修饰符组合    private abstract void print3();//编译错误,非法的修饰符组合}

接口

从狭义的角度讲就是指java中的interface;从广义的角度讲对外提供规则的都是接口(比如电脑上的USB接口)

接口特点:

接口用关键字interface表示,类实现接口用implements表示

interface 接口名 {}class 类名 implements 接口名 {}
接口的子类

可以是抽象类。但是意义不大。也可以是具体类。要重写接口中的所有抽象方法。

接口的成员特点:

成员变量;只能是常量,并且是静态的并公共的。  默认修饰符:public static final

构造方法:接口没有构造方法

成员方法:只能是抽象方法。 默认修饰符:public abstract

类与类,类与接口,接口与接口的关系

类与类:继承关系,只能单继承,可以多层继承

类与接口:实现关系,可以单实现,也可以多实现。并且还可以在继承一个类的同时实现多个接口。

接口与接口:继承关系,可以单继承,也可以多继承

抽象类和接口的区别:

成员区别
抽象类:
成员变量:可以变量,也可以常量
构造方法:有
成员方法:可以抽象,也可以非抽象
接口:
成员变量:只可以常量
成员方法:只可以抽象
关系区别
类与类
继承,单继承
类与接口
实现,单实现,多实现
接口与接口
继承,单继承,多继承
设计理念区别
抽象类 被继承体现的是:”is a”的关系。抽象类中定义的是该继承体系的共性功能。

接口 被实现体现的是:”like a”的关系。接口中定义的是该继承体系的扩展功能。

Q:Java为什么不支持多继承

这是我在知乎上看到的一个问题,感觉写的挺好的(点此打开链接)

先举一个多重继承的例子,我们定义一个动物(类)既是狗(父类1)也是猫(父类2),两个父类都有“叫”这个方法。那么当我们调用“叫”这个方法时,它就不知道是狗叫还是猫叫了,这就是多重继承的冲突。

而java对此的解决方法是,一个物体的本质只能有一个。一个动物只能是狗或只能是猫,如果你想创造一个会玩毛线球会玩激光(被激光玩?)的狗,那么只需要创造一个描述这类行为的接口(就叫玩耍吧),然后在自己的类里面实现“玩耍”接口,具体实现这些玩的行为,最终你同样会得到一个既像狗又像猫的动物。如果你想让这个动物叫起来像猫而不是狗,那么使用覆写(override)机制,子类里重新定义“叫”这个行为即可。但是无论如何,这样得到的类是绝对不会有多重继承的冲突的。

再来说说abstract class和interface的区别

abstract class的核心在于,我知道一类物体的部分行为(和属性),但是不清楚另一部分的行为(和属性),所以我不能自己实例化。还是刚才那个例子,如果你有个abstract class叫哺乳动物,那么你可以定义他们胎生,恒定体温等共同的行为,但是具体“叫”这个行为时,你得留着让非abstract的狗和猫等等子类具体实现。
interface的核心在于,我只知道这个物体能干什么,具体是什么不需要遵从类的继承关系。比如上述的“玩耍”interface,狗有狗的玩法,猫有猫的玩法,妖魔鬼怪机器人都可以玩耍,只要你告诉我这个物体有玩耍接口,我就能让它玩起来
所以abstract class和interface是不能互相替代的,interface不能定义(它只做了声明)共同的行为,事实上它也不能定义“非常量”的变量。而abstract class只是一种分类的抽象,它不能横跨类别来描述一类行为,它使得针对“别的分类方式”的抽象变得无法实现(所以需要接口来帮忙)。而多重继承不但会造成冲突,还让一个类变得不伦不类,看不出这个类的本质,所以java毅然舍弃掉了这个祸害。


内部类

内部类就是在类的内部的类。你可以把这个类当做一个类的成员来使用

内部类访问特点:

内部类可以直接访问外部类的成员,包括私有。

外部类要访问内部类的成员,必须创建对象。

外部类名.内部类名 对象名 = 外部类对象.内部类对象;

class Outer {private int i = 20;class Inner {public void hello() {System.out.println("hello " + i);//可直接访问外部类的成员}}}public class Demo {public static void main(String[] args) {//Inner inner = new Inner();  编译报错Outer.Inner oi = new Outer().new Inner();oi.hello();}}//outPut:hello 20

成员内部类私有使用:

class Outer {private int i = 20;private class Inner {//私有化public void hello() {System.out.println("hello " + i);//可直接访问外部类的成员}}public void print() {Inner inner = new Inner();inner.hello();}}public class Demo {public static void main(String[] args) {//Outer.Inner oi = new Outer().new Inner(); 编译报错,不能直接访问私有的Outer outer = new Outer();outer.print();}}//outPut:hello 20

静态成员内部类:

class Outer {private int i = 20;static class Inner {public void hello() {//System.out.println("hello " + i);内部类使用static后不需要依靠外部类对象,所以这里不能访问非static的成员变量System.out.println("hello!");}public static void print() {System.out.println("go go go!");}}}public class Demo {public static void main(String[] args) {//外部类名.内部类名 对象名 = 外部类名.内部类对象Outer.Inner inner = new Outer.Inner();//这里应该表达为 Outer.new Inner(); 它将new移动到了前面inner.hello();Outer.Inner.print();//静态内部类里的静态方法直接一路.调用即可}}/* * outPut: * hello! * go go go! */

内部类小习:

要求:使用已知的变量,在控制台输出30,20,10。

class Outer {public int num = 10;class Inner {public int num = 20;public void show() {int num = 30;System.out.println(?);//以下填空System.out.println(??);System.out.println(???);}}}public class Demo {public static void main(String[] args) {Outer.Inner oi = new Outer().new Inner();oi.show();}}

答案:

public void show() {int num = 30;System.out.println(num);System.out.println(this.num);System.out.println(Outer.this.num);}
前面两个空比较好理解就不解释,说下最后一个空

当外围类对象创建了一个内部类的对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用,在你访问那个外围类成员时就是那个引用来选择外围类对象。既然内部类有着一个外围类的引用,那么如果返回该引用:使用 外围类名.this

匿名内部类

匿名内部类就是内部类的简化写法。属于局部内部类(在方法中)的一种

前提:存在一个类(具体类和抽象类都行)或者接口

格式:

//new 类名(){} 代表着继承这个类的实例对象//new 接口名(){} 代表着实现这个接口的实例对象new 类名或者接口名(){重写方法;}
其本质就是一个继承了该类或者实现了该接口的子类匿名对象。

interface Inter {public void print();}class Outer {class Inner implements Inter{//正常写法@Overridepublic void print() {System.out.println("Hello");}}public void method() {new Inner() {//匿名写法,表示实现Inner接口的一个实例对象@Overridepublic void print() {System.out.println("Hello");}}.print();//该对象调用print() (就是上面写的这个方法)}}

匿名内部类的小应用:

abstract class Person {    public abstract void show();}class PersonDemo {    public void method(Person p) {        p.show();    }}public class Demo {    public static void main(String[] args) {        //如何调用PersonDemo中的method方法呢?        PersonDemo pd = new PersonDemo ();        pd.method(new Person() {//匿名内部类当做参数传递(把匿名内部类看做一个对象)            @Override            public void show() {                System.out.println("show!");            }        });    }}//outPut:show!

匿名内部类小习:

按照要求,补齐代码interface Inter { void show(); }class Outer { //补齐代码 }class OuterDemo {public static void main(String[] args) {  Outer.method().show();  }}要求在控制台输出”HelloWorld”
答案:

/* * 从主方法分析,Outer.method()说明method肯定是一个静态方法,再看后面又调用了一个方法, * 那么可以确定Outer.method()会返回一个实例对象来调用show方法 */class Outer {     public static Inter method() {        return new Inter() {            @Override            public void show() {                System.out.println("HelloWorld");            }        };    }}

面向对象部分内容到此总结完毕。

0 0
原创粉丝点击