zoukankan      html  css  js  c++  java
  • java基本思想

    面向对象

    众所周知,面向对象编程思想是java的基本思想。java的整个体系和技术实现都是以这个思想为基础。(这个通过类和接口能看出来,后面提到)

    对这个事情的认知度甚至变成了很多公司的面试标准。比如有的公司会问你什么是面向对象、面向对象和面向过程的区别、面向对象有哪些特性。

    不过细心的人会发现,没有公司会问你,你是怎么将面向对象思想应用到实际工作中的。这是因为:
    其一,大多数人了解面向对象的思想是为了应付面试;
    其二,确实很少有人会在实际工作中使用面向对象的思想。
    第一点是因为教育机构(大学、培训机构)的老师大都是从c也就是面向过程的语言走过来的。他们的思想已经习惯了使用面向过程编程。然后老师们就使用这个思想去教导学生,这就形成了新一代年轻的程序员也会习惯面向过程的思想去思考问题。师徒制也存在这个问题。如此往复循环,就形成了第二点。而如此积累,当工作量逐渐加大的时候就会出现程序架构设计不合理、数据库设计不方便、不想写注释、不知道些什么注释等情况。然后就变成了“卧槽这代码是我写的么???”。这个时候再遇上个需求变更,心态估计就崩了。

    在这里感慨一下,真正将面向对象的思想落地,才是高级编程语言程序员的出路。(有兴趣的可以了解一下领域模型驱动,就是常说的DDD)

    对于面向对象思想的个人理解:

    面向对象思想,是一种将现实世界抽象成代码的思想。
    即通过将现实世界中独立个体(事物)的属性和行为封装在一个类中,通过抽象和继承实现个体间的相互影响、协作。形成了对象多态和行为多态的特性。
    话说得稍微有点抽象了。简单来说就是把实际转换成代码,代码之间相互作用形成了代码间的相互协调,表现了实际中一个事物的多种形态。这种思想叫做面向对象的思想。

    与面向过程的区别

    面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
    所以,面向对象相较于面向过程来说更具有拥抱变化的能力。
    说到这里,忍不住跟看这篇文章的人推荐一部电影:《模仿游戏》。主演是鼎鼎大名的卷福。故事情节大致是主角为了破译德军的密码建造了一个机器。聚焦于机器的建造过程来看,机器使用了穷举法来实验密码的正确性,设计这个机器就是为了破解这个密码,这样来看是一个面向过程的设计思想。聚焦于整部电影来看,主角建造这个机器有一部分原因是为了纪念儿时的玩伴,甚至还给机器取了玩伴的名字。当需要破译密码的时候就把工作交给这个机器,机器计算完了之后会返回结果。主角在内心里把这个机器当做了一个人,在思想上将这个人抽象成了机器的实现逻辑。这样来看就是面向对象的思想了。这个机器也是人工智能的起源——图灵机。

    咳咳,有点扯远了。不过我确实推荐朋友们找一找自己专业相关的电影看看,能极大程度提高对专业的兴趣和积极性。

    分割线1,其实这里也没有特别长,但是下面主要讨论技术和思想了,给上面的一些闲扯做个分隔。
    封装

    正如上面所说,java体现面向对象思想的一个基本点就是封装。这个动作的设计初衷就是将现实世界中的事物描述成代码。所以设计了一个容器叫做类,类中可以放描述事物的属性(成员变量)和描述事物的行为(成员方法)。
    封装完类之后将一些类分门别类的放在对应的包中,然后整合成了一个项目。如此一来,通过封装这个动作就把一系列功能封装成了对象。

    接口(抽象)

    单纯从封装这个动作来看,将实际事物封装为类的思想过程就是抽象。而通过抽象,可以把一些类中的共同点汇集到一个类中复用。那么将不同实现相同名字的变量和方法抽象到一个不用写逻辑实现的类中,这个类就叫做接口。这个接口就是最高层次的抽象了。

    那么从实际工作来看,可以从两个格局理解接口。
    一个是系统级的战略格局,一个是代码级的战术格局。

    从战略格局来看,系统之间的相互交互需要制定规则和标准;制定了统一的规则和标准之后,大家按照这个标准去开发。这样可以做到多个协调系统之间的并行开发,以及系统之间的耦合性降低。
    战略上,接口定义了系统之间相互协调通讯的方式和入口。(从单个系统看其他的系统,接口也描述了其他系统的功能)

    从战术格局来看,类可以描述实际事物的具体属性和行为。也就是说类可以代表一个具体事物,但是没有办法表示事物可以实现的功能。
    举个例子说明:dog类可以描述狗。那么二哈和拉布拉多属于狗的品种,是狗的一种属性,所应该作为dog类中的一个属性(成员变量)。而狗可以啃骨头,可以定义成dog类中的一个成员方法。
    但是拉布拉多可以做导盲犬,二哈可以做雪橇犬。当然你可以把这个当做狗的职业封装为一个属性,但是显然是不合适的。因为,导盲犬和雪橇犬可以完成的动作没有几个一样的。动作需要定义成方法。所以这个时候需要一个新的类来定义这两个职业,而不能封装为一个属性。
    可是,通过继承的方式实现由有了单根性的限制(一个类只能继承一个类)。对于代码描述事物的功能就很麻烦了。于是java就使用接口解决了这个问题。
    所以从战术格局来看,接口描述的是对象的功能(类描述的事对象的属性,这个后面说)。

    抽象类(抽象和细节)

    抽象类其实是类到接口中间的一个过度产物。

    从代码上来看,抽象类既可以定义复用的方法逻辑还可以定义需要子类实现的抽象方法。
    所以说,抽象类是在类设计之后才抽象出来的。
    对比接口,可以说接口是设计出来的结果而抽象类是重构出来的结果。两者方向不一样。是不同过程的产物。

    实现类(细节)

    类是描述实际事物的基本单位,而这里要说的实现类,则是特指实现了接口的类。
    这种类虽然在本质上跟普通的类是一样的,但是意义不同。
    实现类是用来实现接口的逻辑的,他有接口的规则限定。而普通的类则没有这么明确的限定。
    所以实现类的命名一般是接口名+Impl组成;而普通类根据实际要代表的事物命名即可。

    这里小总结一下,接口是抽象的规则,实现类是具体的细节。两者缺其一,那么这个系统从设计上来讲是不完整的。接口是编写代码之前设计出来的;其目的是制定规则,以便可以进行并行开发和拥抱变化。而抽象类,是抽象和细节的混合物,是在开发阶段重构出来的结果,目的是复用代码。

    继承

    继承是面向对象中思想中的一个概念,其实基类拓展超类实现在一个类中可以直接调用另一个类的方法是通过拓展指针实现的(extends)。不过从实现效果上来看,跟领域模型中的父子非常相像。所以就把基类称为子类,超类称为父类了。

    extends

    继承的技术实现就是利用extends关键字,其实他就是个指针而已。(这里不叫引用,因为它确实和java中的引用不太一样,所以加以区别、便于理解)
    先来看一段代码:

    public class A{}
    public class B extends A{}

    大家都知道B类继承了A类所以B中可以使用A中的构造方法和成员(变量和方法)。
    这是因为当编译器检测到B类后面的extends关键字时,会把其后的类名(也就是A)的地址(类的完全限定名)编译在B类的class源码中。

    之后运行的时候,类加载器会在加载B类的时候将A类的信息同时加载到方法区中;
    而java默认调用子类构造方法的时候会先调用父类的构造方法。
    那么这个时候通过B类实例化出来的对象自然就能调用A类的方法了。

    所以,这样来看。extends关键字其实就是一个指针变量,其值为class定义时关键字后面的类名,也就是拓展类的地址。

    extends翻译过来就是拓展的意思。所以B extends A。B就能调用A中的成员。反过来A并没有拓展B ,所以A就不能调用B中的成员。这就形成了子类可以使用父类的成员,而父类不能使用子类的。
    而且为了保证程序的设计顺序性和可读性,java限制了两个类互相拓展。就是说当B extends A时,A就不能再拓展B了。再加上拓展的单根行,效果就变成了一个父类可以有多个子类,而一个子类只能有一个父类。这样如果把成员看做财产的话;效果就跟领域模型(现实社会)中的父子非常相像了。所以基类和超类也被称为子类和父类,而拓展也被称作继承。

    implments

    接口的设计一是为了打破java继承的单根性,二是为了跟接口代表的含义更加贴切。
    上面说到继承是单根性的。这样就造成了父类只能是通过代码抽象出来的复用代码类,很难从设计阶段定义;而且确实有了多继承的需求。(接口代表了功能,一个对象可以同时有多个功能)
    所以java设计了接口来代表设计模型中的功能和系统间的规则。

    那么接口中的方法都是没有具体实现的,所以需要一个类来实现这个接口中的规则。那么当一个类跟一个接口关联起来的时候,自然也就称作这个类实现了这个接口。

    接口之间是可以相互继承的就像大规则包含小规则;接口和类之间是实现的,因为接口定义的规则最终要通过类来定义运行逻辑。

    this

    this关键字跟extends其实也是一个指针变量,但是this的用法跟c中的宏定义非常相像;所以要跟extends关键字加以区分。

    this() 调用当前对象的构造方法
    this.成员(变量或方法) 调用当前对象的成员(变量或方法)

    发生上面两种情况的调用时,其实可以直接想象成new一个本类的对象赋值给引用a。然后把this换成a,就行了。所说的this代表当前调用的对象,就是这个意思。

    super

    由于类的构造方法执行顺序是: 父类构造方法–>本类成员变量初始化–>本类构造方法。

    这样就还需要一个指向父类对象的指针。所以就设计了super关键字。用法和this关键字一样,只是super指向了父类。

    super() 调用父类对象的构造方法
    super.成员(变量或方法) 调用父类对象的成员(变量或方法)

    这里要说一下extends 、this、super的区别:
    三者均可以看成是指针变量,但是extends存放的是类的地址;
    this和super存放的是对象的地址。
    换句话说extends是在编译期工作的;而this和extends是在程序运行时工作的。
    这样记忆就不容易混淆了。

    多态

    多态是从面向对象思想和设计的角度来说的。
    当我们设计了一个类时,在另一个类中调用这个类的对象,但是表现的好像是这个类的父类;这种情况就是向上造型。
    有些时候我们需要一个类的子类,但是持有的却是其父类的引用,这个是我们需要做一个强制类型转换来得到我们需要的子类。这个操作就是所谓的向下造型。
    上下造型在面向对象编程时非常实用。当这种方式在一个系统中频繁使用的时候,表现出来的效果经常是一个对象扮演多种角色,还可以相互转换。这种现象被人们称为多态。

    对象多态(类型转换、上下造型)

    对象多态是多态中的一种主要表现形式。发生在对象之间,也就是说对象多态的基本单位是对象,不可向下细分。

    这里先说一下实现原理,还是比较巧妙的。


    我们来看,main方法第一行第二行不用解释,就是创建了两个对象 (ac表示 aClass bc同理)。
    然后会发生之前说extends关键字时说过的现象:bc可以调用A类和本类的成员,也就是变量a和变量b。但是ac却只能调用A类的成员。原因在extends那里说过了这里就不在详细说明。

    第三行就是我们常说的向上造型。原理大概是这样的:
    编译器从左到右检测代码,首先检测到A ab。此时编译器会读取A类的信息,并标记有一个局部变量ab数据类型是A。

    然后检测到操作符 = 编译器会去检查右侧的表达式。如果是字面量表达式(比如3+5或者简单字符串拼接)那么会直接在编译期计算,之后检查结果的数据类型是否合规;如果是复杂表达式(除了字面量表达式之外的表达式)则只检查数据类型是否合规。
    我们上面的代码属于复杂表达式,编译器只进行数据类型的检查。
    java语法规定new 后面只能是类的构造函数,也就是说这里必须实例化一个对象出来。这里编译器只需要将new关键字后边的字符拿出来,然后把()和;拿掉,就得到了实例化对象的数据类型了。
    这也就是java要求构造方法必须与类同名的原因。

    然后编译器拿到了右边表达式结果的数据类型B跟左边的数据类型作比较。这个时候extends关键字就开始起作用了。由于B extends A,编译器会在读取B类信息的时候同时将A类的地址添到信息中,算作B类信息的一部分。这样,B就能匹配两种数据类型,一种是自己,一种是扩展类,也就是A。

    这样在比较左右两边数据类型的时候就变成了 A = B|A 。表达式成立,编译通过。

    同理mian方法第四句,由于A并没有拓展B。所以数据类型比较不成立就会编译报错。

    到这里就是向上造型的大概实现原理了。同样的原理也可以推出继承的几种限制,比如成员访问权限和抛出异常类型的限制。这里不做详细介绍了。

    可能有人会问上面的第四句,比较左右数据类型的时候不应该是B|A = A嘛?这样应该也成立呀。
    这个其实是编译器的识别规则,上面提到了构造方法的执行顺序,第一步就是执行父类构造方法。也就是说真正运行起来的时候,只有执行构造方法,才能有父类的对象,才能调用父类的成员。所以这里的表达式应该是B = A,是不成立的。

    说完了向上造型,向下造型就很好理解了。上面的例子中,A类型的变量ab 实际的值确实B类的对象。
    而做.运算的时候,编译器只根据变量的数据类型决定能.出来的东西。所以父类型的引用,虽然是向上造型,但是仍然不能调用子类的成员。如果某一个时刻我们确实需要使用子类的成员,可以使用强制类型转换。也就是:

    B b = (b)ab;
    1
    这里编译器不会报错,是因为检测到了强制类型转换。但是转换成功的前提是ab的值确实是B类型的。如果不是,在程序运行的时候会抛出类型转换异常。

    到这里,上下造型就说完了。利用上下造型极大的提高了程序的灵活性,使得java编程跟面向对象思想更加贴切。在上下造型被大范围应用的时候,同一个对象可以赋值给不同类型的变量,在程序中某一个运行时段代表的意义也就不一样,这就形成了对象多态的效果。

    行为多态(重写和重载)

    行为多态的基本单位是方法,不可向下细分。行为多态是发生在对象内部的。
    表现为一个对象在代表的意义不同时行为也不同。

    这里提一下重写和重载的区别,好多面试里也问道。其实只要搞清楚这两个动作是怎么回事就很容易回答这个问题了。

    先说重载。一般发生在同一个类中,编译期就会绑定,方法名相同参数列表不同。
    这个设计是为了模仿银行窗口这样的对象。假设客户要取钱,但是事先并不知道客户要那什么凭证来取钱。所以设立一个窗口,无论什么凭证,只要是取钱的业务都可以到窗口办理。
    有了重载就可以很方便的实现了。

    再说重写。发生在父子类中,运行期才会确定,方法名相同参数列表也相同。
    注意如果在父子类中方法名相同参数列表不同的情况是重载不是重写。虽然说父子类不是同一个类,但是有拓展关系。就是说在编译期就能看到两个类中的成员,所以在父子类中也可以发生重载。
    这里来举个例子:

     


    还是上面的AB类,这次在B类中定义了一个跟A类中print方法完全相同的print();

    当在编译时,第一句向上造型上面说过了。ab.print(1);编译器会去查看ab变量的数据类型,也就是A类,A类中含有print方法,参数为int。符合语法,编译通过。同理第三句也是一样。

    当在运行时,程序会取ab的值,这个时候ab就是一个B对象了,那么去内存中B对象的领域里取print方法运行,实际上执行的逻辑是B类中定义的print方法的逻辑。所以就打印了balabala。这个现象好像A的方法被重新写了一遍一样,所以就把这种现象叫做重写了。

    然后执行第四句,同样的逻辑,找到ac的数据类型,比较正确编译通过;
    执行的时候取ac的值,执行A对象的print方法,自然就打印1了。

    到这里重写和重载就说了个大概了。这两种语法规定的出现,让java对象实现了一个对象多种行为姿态的效果。被称为行为多态。

    行为多态和对象多态放在一起组成了java语言的第三大特性,统称为多态。而java语言又是面向对象的代表语言,自然也就说面向对象的第三大特性是多态了。

    其实这里是推崇了一下java,事实应该是先有思想的特性才有语言的实现。上面所说只是为了调一下胃口,并不代表真实历史。 不过单凭java的开源,个人觉得就值得推崇一下。
    ---------------------
    作者:AgentRich
    来源:CSDN
    原文:https://blog.csdn.net/Agent_Rich/article/details/81986910
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    什么是内卷?
    iphone与PC端如何传BUG截图
    java应用服务占用cpu过高,如何优化
    性能测试常见问题FAQ
    性能测试工程师能力进阶三部曲
    jmeter分布式压测试部署
    了解token及分类
    常见端口号及其服务
    2714
    python
  • 原文地址:https://www.cnblogs.com/guankelei/p/11224047.html
Copyright © 2011-2022 走看看