前面一篇已经总结了许多的面向对象方面的小知识点(点此传送门),下面继续整理。
多态
多态(polymorphic):事物存在的多种形态
多态前提:
要有继承关系
要有方法重写
要有父类引用指向子类对象
class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
class Cat extends Animal{//继承
@Override
public 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 {
@Override
public void squeeze() {
System.out.println("榨出一杯苹果汁");
}
}
class Orange extends Fruit {
@Override
public 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 {
@Override
public 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{
@Override
public 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{//正常写法
@Override
public void print() {
System.out.println("Hello");
}
}
public void method() {
new Inner() {//匿名写法,表示实现Inner接口的一个实例对象
@Override
public 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");
}
};
}
}
面向对象部分内容到此总结完毕。