本文出自:http://blog.csdn.net/dt235201314/article/details/69162998
一丶概述
雏鹰计划第二周(2017/4/4),学习内容:Java基础之 OOP--面向对象程序设计 考查结果得了B,了解到不足之处
1. 总结不够全面。行动措施:添加常见面试题,及擦边知识点
2. 没有看到自己的理解及输出。 行动措施:按自己思路实现一些Demo,体现知识点
二丶正文
当看到本周的内容OOP,我都忘了什么意思,Object Oriented Programming,OOP,面向对象程序设计。
听着好像大学的一门课的名称,那么这里的OOP主要设计的内容是那些呢?
image.png面向对象三大特性五大原则,原则了解(看着纯理论,自己都没那么懂),特性重点。
上面是提纲,下边开始系统学
1.继承 多态 抽象 封装:点击打开链接
先前写得文章
2.百度百科OOP:点击打开链接
这里了解概念历史就行,概念太多反而读着晕
3.OOP几大原则:点击打开链接
没怎么懂,看以后研究设计模式的时候再多一些了解
4.Java 抽象类与oop三大特征:点击打开链接
这篇写得比较好,多出参考
设计模式遵循的一般原则:
1.开-闭原则(Open-Closed Principle, OCP):
一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。
2.里氏代换原则(Liskov Substitution Principle,常缩写为.LSP)
(1).由Barbar Liskov(芭芭拉.里氏)提出,是继承复用的基石。
(2).严格表达:如果每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换称o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型.
换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别.只有衍生类可以替换基类,软件单位的功能才能不受影响,基类才能真正被复用,而衍生类也能够在基类的基础上增加新功能。
(3).反过来的代换不成立
(4).<墨子.小取>中说:"白马,马也; 乘白马,乘马也.骊马(黑马),马也;乘骊马,乘马也."
(5).该类西方著名的例程为:正方形是否是长方形的子类(答案是"否")。类似的还有椭圆和圆的关系。
(6).应当尽量从抽象类继承,而不从具体类继承,一般而言,如果有两个具体类A,B有继承关系,那么一个最简单的修改方案是建立一个抽象类C,然后让类A和B成为抽象类C的子类.即如果有一个由继承关系形成的登记结构的话,那么在等级结构的树形图上面所有的树叶节点都应当是具体类;而所有的树枝节点都应当是抽象类或者接口.
(7)."基于契约设计(Design By Constract),简称DBC"这项技术对LISKOV代换原则提供了支持.该项技术Bertrand Meyer伯特兰做过详细的介绍:
使用DBC,类的编写者显式地规定针对该类的契约.客户代码的编写者可以通过该契约获悉可以依赖的行为方式.契约是通过每个方法声明的前置条件(preconditions)和后置条件(postconditions)来指定的.要使一个方法得以执行,前置条件必须为真.执行完毕后,该方法要保证后置条件为真.就是说,在重新声明派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件.
3.依赖倒置原则(Dependence Inversion Principle),要求客户端依赖于抽象耦合
(1)表述:抽象不应当依赖于细节,细节应当依赖于抽象.(Program to an interface, not an implementaction)
(2)表述二:针对接口编程的意思是说,应当使用接口和抽象类进行变量的类型声明,参量的类型声明,方法的返还类型声明,以及数据类型的转换等.不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明,参量类型声明,方法的返还类型声明,以及数据类型的转换等.
要保证做到这一点,一个具体的类应等只实现接口和抽象类中声明过的方法,而不应当给出多余的方法.
只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括参量的类型声明,方法返还类型的声明,属性变量的类型声明等.
(3)接口与抽象的区别就在于抽象类可以提供某些方法的部分实现,而接口则不可以,这也大概是抽象类唯一的优点.如果向一个抽象类加入一个新的具体方法,那么所有的子类型一下子就都得到得到了这个新的具体方法,而接口做不到这一点.如果向一个接口加入了一个新的方法的话,所有实现这个接口的类就全部不能通过编译了,因为它们都没有实现这个新声明的方法.这显然是接口的一个缺点.
(4)一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的登记结构中,而由于一般语言都限制一个类只能从最多一个超类继承,因此将抽象作为类型定义工具的效能大打折扣.
反过来,看接口,就会发现任何一个实现了一个接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个接口.
(5)从代码重构的角度上讲,将一个单独的具体类重构成一个接口的实现是很容易的,只需要声明一个接口,并将重要的方法添加到接口声明中,然后在具体类定义语句中加上保留字以继承于该接口就行了.
而作为一个已有的具体类添加一个抽象类作为抽象类型不那么容易,因为这个具体类有可能已经有一个超类.这样一来,这个新定义的抽象类只好继续向上移动,变成这个超类的超类,如此循环,最后这个新的抽象类必定处于整个类型等级结构的最上端,从而使登记结构中的所有成员都会受到影响.
(6)接口是定义混合类型的理想工具,所为混合类型,就是在一个类的主类型之外的次要类型.一个混合类型表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为.
(7)联合使用接口和抽象类:
由于抽象类具有提供缺省实现的优点,而接口具有其他所有优点,所以联合使用两者就是一个很好的选择.
首先,声明类型的工作仍然接口承担的,但是同时给出的还有一个抽象类,为这个接口给出一个缺省实现.其他同属于这个抽象类型的具体类可以选择实现这个接口,也可以选择继承自这个抽象类.如果一个具体类直接实现这个接口的话,它就必须自行实现所有的接口;相反,如果它继承自抽象类的话,它可以省去一些不必要的的方法,因为它可以从抽象类中自动得到这些方法的缺省实现;如果需要向接口加入一个新的方法的话,那么只要同时向这个抽象类加入这个方法的一个具体实现就可以了,因为所有继承自这个抽象类的子类都会从这个抽象类得到这个具体方法.这其实就是缺省适配器模式(Defaule Adapter).
(8)什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节的改变而改变的真理. 它是系统内部的系统____隐喻.
4.接口隔离原则(Interface Segregation Principle, ISP) (1)一个类对另外一个类的依赖是建立在最小的接口上。
(2)使用多个专门的接口比使用单一的总接口要好.根据客户需要的不同,而为不同的客户端提供不同的服务是一种应当得到鼓励的做法.就像"看人下菜碟"一样,要看客人是谁,再提供不同档次的饭菜.
(3)胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系.当一个客户程序要求该胖接口进行一个改动时,会影响到所有其他的客户程序.因此客户程序应该仅仅依赖他们实际需要调用的方法.
5.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP) 在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.
6.迪米特法则(Law of Demeter LoD)又叫做最少知识原则(Least Knowledge Principle,LKP),就是说,一个对象应当对其他对象有尽可能少的了了解.
迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,与1987年秋天由Ian Holland在美国东北大学为一个叫做迪米特(Demeter)的项目设计提出的,因此叫做迪米特法则[LIEB89][LIEB86].这条法则实际上是很多著名系统,比如火星登陆软件系统,木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则.
没有任何一个其他的OO设计原则象迪米特法则这样有如此之多的表述方式,如下几种:
(1)只与你直接的朋友们通信(Only talk to your immediate friends)
(2)不要跟"陌生人"说话(Don't talk to strangers)
(3)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位.
就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
7.单一职责原则(Simple responsibility pinciple SRP)
就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责
另外:常说的OO五大原则就是指其中的 :
1、单一职责原则;
2、开放闭合原则;
3、里氏替换原则;
4、依赖倒置原则;
5、接口隔离原则。
三大特性:
image一、封装
封装是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节(当然也无从知道),但可以通过该对象对外的提供的接口来访问该对象。
使用封装有四大好处:
1、良好的封装能够减少耦合。
2、类内部的结构可以自由修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实现细节。
很多时候大神的代码比较绕,带代码又少又简介就因为封装的好。
简单的封装在实体类中就可以体现及属性私有,方法共有
实现Java封装的步骤
1. 修改属性的可见性来限制对属性的访问(一般限制为private),例如:
public class Person {
private String name;
private int age;
}
这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。
复杂的封装,提升阅读:
带你一步一步实现RecyclerView.Adapter的封装,理解封装的过程
二、继承
面向对象中代码复用有两种方法,一种被称之为组合,即在一个新的类中使用一个现有的类,比如在新创建的类中又一个String类,一般应用在has-a的关系;还有一种便是继承了,一般用于is-a的关系。
继承是在不改变原有类的情况下,采用原有的类的形式,并向里面添加新的代码而形成的一个新类,新类一般被称为之类,原类被称之为父类。
其中,继承所用到的关键字为 extends,使用过程中也会用到this,super,public,protect,private,final关键字。
下例是继承的用法,并且还包含了一些其他java语法的特性。
class Cleanser{
private String s = "Cleanser";
public void append(String a) { s+=a;}
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public String toString() { return s;}
public static void main(String[] args){
Cleanser x = new Cleanser();
x.dilute();x.apply();x.scrub();
System.out.println(x);
}
}
public class Detergent extends Cleanser{
//Change a method;
public void scrub() {
append(" Detergent scrub()");
super.scrub(); //call base-class version;
}
//add methods to the interface;
public void foam() {append(" foam()");}
//test the new class ;
public static void main(String[] args){
Detergent x = new Detergent();
x.dilute(); x.apply(); x.scrub(); x.foam();
System.out.println(x);
System.out.print("Testing base class:");
Cleanser.main(args);
}
}
在java中,没有C++的析构函数的概念,C++中的析构函数用于销毁创建的对象,需要程序员手动操作,而JAVA中由于垃圾回收机制的存在,对象在合适的时机会自动销毁。
重载与复写:
当子类出现和父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。这种情况是函数的另一个特性:重写(覆盖)。
当子类继承父类,沿袭了父类的功能,到子类中。但是子类虽具备该功能,但是功能的内容却和父类不一致,这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。子类同时具有父类方法中的内容时,可以用super.方法
重载是函数名一样,函数参数列表不一样,与返还值无关;
复写是子类与父类函数一模一样。
@Override
@Override是伪代码,表示重写(当然不写也可以)
建议写上,有如下好处:
1、可以当注释用,方便阅读;
2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错,比如你如果没写@Override而你下面的方法名又写错了,这时你的编译器是可以通过的(它以为这个方法是你的子类中自己增加的方法)。
重写与重载之间的区别
image.png方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载是一类中多态性的一种表现。
访问权限
public:接口访问权限,表示对每个人都是可用的(每个编译单元都只能拥有一个public 类,且该类的名称必须与编译单元名称一样,包括大小写)。
protect:继承访问权限,表示对派生类可用而不是所有类。
private:表示私有,除了包含该成员的类意外,任何类都无法访问。
final:修饰基本数据表示数值永远不改变,修饰引用时表示引用不变,即初始化指向一个对象之后不能改为指向另一个对象,修饰方法为了锁定方法防止继承类改变它。修饰类时表示不允许该类有子类,即不能被继承。
权限关系如下所示。
访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同的包 |
---|---|---|---|---|---|
公开 | public | y | y | y | y |
受保护 | protected | y | y | y | |
默认 | default | y | y | ||
私有 | private | y |
需要注意的是,继承从某种程度来说,是对于封装的一种破坏操作。
继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
implements关键字
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
public class SuperDemo {
public static void main(String []args) {
new SubClass().showMessage();
}
}
class SuperClass {
int i = 50;
}
class SubClass extends SuperClass {
int i =100;
public void showMessage() {
System.out.printf("super.i = %d, this.i = %d\n", super.i, this.i);
}
}
输出结果为:
super.i = 50, this.i = 100
final关键字
final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:
声明类:
final class 类名 {//类体}
声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注:实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final
构造器
子类不能继承父类的构造器(构造方法或者构造函数),但是父类的构造器带有参数的,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。
如果父类有无参构造器,则在子类的构造器中用super调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
SubClass(){
super(300);
System.out.println("SubClass");
}
public SubClass(int n){
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
SubClass sc = new SubClass();
SubClass sc2 = new SubClass(200);
}
}
输出结果为:
SuperClass(int n)
SubClass
SuperClass()
SubClass(int n):200
三、多态
多态的定义 :指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
实现多态的技术称为 :动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
多态的作用 :消除类型之间的耦合关系。
现实中,关于多态的例子不胜枚举 。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
多态弊端 : 提高扩展性,但是只能使用父类引用指向父类成员。
注意:
在多态的情况下,字符类存在同名的成员(成员变量和成员函数)时,访问的是父类的成员,只有是同名的非静态成员函数时,才访问子类的成员函数;
多态用于 形参 类型时,可以接受多个类型的数据;
多态用于返回类型时,可以返回多个类型的数据,使用了多态的方法,定义的变量类型要与返回的类型一致。
简单例子:
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 catchMouse
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
执行以上程序,输出结果为:
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠
复杂例子(从入门到放弃系列):
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
输出结果为:
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
首先我们先看一句话: 当超类对象引用变量引用子类对象时, 被引用对象的类型 而不是引用变量的类型决定了调用谁的成员方法, 但是这个被调用的方法必须是在超类中定义过的 ,也就是说被调用的方法必须是 被子类重写的方法 。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级: this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
对于前半句的意思就是:当父类变量引用子类对象时,在调用成员函数时,应该调用向子类的成员函数,但前提是此函数时被子类重写的函数。
A B C D的继承关系如下:
图片描述
分析:
对于1和2,B和C属于A的子类,调用a1.show(b),a1.show(b),可以找到A.show(A boj),因为多态情况下,父类做形参时,可以接受其子类的实参。
对于3,直接就可以找到A.show(D odj)。
对于4,本来由于a2引用的是其子类B的一个对象,因此调用的成员函数应为B.show(B obj),但是由于B.show(B obj)不是重写的函数,因此不会调用B.show(B obj)。故将按照优先级,先看this.show(O),而类A里面没有找到show(B obj)方法,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为B,(super)O即(super)B即A,因此它到类A里面找show(A obj)的方法,类A有这个方法, 但是由于a2引用的是类B的一个对象,且B覆盖了A的show(A obj)方法 ,因此最终锁定到类B的show(A obj),输出为”B and A”。
对于5,同样将按照优先级,先看this.show(O),而类A里面没有找到show(C obj)方法,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为C,由于A是C的超类,因此它到类A里面找show(A obj)的方法,类A有这个方法,但是 由于a2引用的是类B的一个对象,且B覆盖了A的show(A obj)方法 ,因此最终锁定到类B的show(A obj),输出为”B and A”。
对于6,同样将按照优先级,先看this.show(O),而类A里面刚好找到了show(D obj)方法,输出为”D and A”.
对于7,可以直接调用this.show(O)。
对于8,同样将按照优先级,先看this.show(O),而类B里面没有找到show(C obj)方法,于是到B的super(超类)找,而类A里面没有找到show(C obj)方法,因此转到第三优先级this.show((super)O),this仍然是b,这里O为C,由于B是C的超类,因此它到类B里面找show(B obj)的方法,因此输出为”B and B”。
对于9,同样将按照优先级,先看this.show(O),而类B里面没有找到show(D obj)方法,于是到B的super(超类)找,而类A里面找到了show(D obj)方法,因此输出为”A and D”。
这个例子很搞人,开发中虽不常见,但得记住优先级: this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
写在最后
如果文章对你有帮助,点赞支持下。不懂地方,微信扫码提问
image
网友评论