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

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


多态

多态(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");
            }
        };
    }
}

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值