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"); } }; }}
面向对象部分内容到此总结完毕。
- Java基础-面向对象小知识(下)
- Java基础-面向对象小知识(上)
- java基础面向对象(下)
- JAVA面向对象-----面向对象(基础预备知识汇总)
- Java面向对象一点小基础总结
- 面向对象基础(下)
- Java面向对象知识
- Java面向对象知识
- 面向对象(三):常用知识下
- java基础4--面向对象下(几道练习题)
- java基础4--面向对象(下)--要点总结
- 黑马程序员---java基础---05面向对象(下)
- Java基础学习笔记——面向对象(下)
- 黑马程序员 Java基础 ---> 面向对象(下)
- Java基础<五>_面向对象(下)
- 黑马程序员-Java基础:面向对象(下)
- Java编程基础-面向对象(下)
- java基础(面向对象)
- js创建对象的几种常用方式小结
- Tyvj P1863 [Poetize I]黑魔法师之门
- DrawerLayout的使用
- 最新ffmpeg编译和用eclipse进行源码调试(linux)
- Koa框架实践与中间件原理剖析
- Java基础-面向对象小知识(下)
- WIN7下VC6.0无法使用打开功能(Unable to register this add-in because its DLLRegisterServer return an error)
- HibernateTool的安装和使用(Eclipse中)
- 算法入门经典例题自解 2-1 水仙花数
- Longest Substring Without Repeating Characters
- Spring加载应用上下文的三种方式
- NYOJ 14 会场安排问题【贪心】
- 经典语录-让心灵不再寂寞
- mysql事物简说