一、 什么是面向对象

1.1 类

类指对共享相同的属性、操作方法、行为及关系的一组对象的描述,是创建对象的模板。

示例:

public class Dog {
    
    //属性
    private String name;

    private String color;

    ...
    
    //操作方法
    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name=name;
    }

    ...

    //行为
    public void running(){
        ....
    }

    public void barking(){
        ...
    }
}

1.2 对象

对象是类创建的实例,具有自己的信息结构及行为方式,可以是我们研究的任何事物(一条狗是一个对象,它的属性有:颜色、名字等;行为有:叫、跑等)。

图示:

Java面向对象get和set方法_Java

 

1.3 对象的创建方法

1.3.1 通过关键字new创建对象

Dog dog = new Dog();

1.3.2 通过反射方法创建对象

Class class = Class.forName("'org.balre.Dog");
Dog dog = class.newInstance();

1.3.3 通过实现Serializable接口,经由反序列化方式生成对象

//序列化
Dog dog = new Dog();
ByteArrayInputStream byteArrayOutputStreamRead = new ByteArrayOutputStream();
ObjectInputStream objectOutputStreamRead = new ObjectOutputStream(byteArrayOutputStreamRead);
objectOutputStreamRead.writeObject(dog);
byte[] bytes = byteArrayOutputStream.toByteArray();
objectOutputStream.flush();       
objectOutputStream.close();

//反序列化
ByteArrayInputStream byteArrayInputStreamWrite = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStreamWrite = new ObjectInputStream(byteArrayInputStreamWrite);
Dog dog = (Dog)objectInputStreamWrite.readObject();

1.3.4 通过实现Cloneable接口。调用Object类的clone()方法生成对象

Dog dog1 = new Dog();
dog1.setColor("黑色");
dog1.setName("哈士奇");
System.out.println(dog1.getColor());

Dog dog2 = (Dog)dog1.clone();
System.out.println(dog2.getColor());
System.out.println("dog2和dog1是同一个对象::::"+(dog2 == dog1));

1.4 面向过程

面向过程(POP:Process Oriented Programming)是一种以过程为中心的编程思想,就是分析出解决问题所需要的步骤,然后使用函数将这些步骤一步一步实现,使用时在一个一个调用,即自上而下,逐步细化。

1.5 面向对象

面向对象(OOP:Object Oriented Programming)是一种以事物为中心的编程思想,它吸取了面向过程的程序设计优点,同时有考虑到了现实世界与计算机间的处理关系,它整个程序是由一系列连续的对象组成,对象间交互通过发送消息来实现,对象通过响应消息来实现程序功能。

1.6 三大基本特征

1.6.1 封装

封装是指将对象内部的状态信息和实现细节隐藏起来,不允许外部对象直接访问,然后通过该类提供的一些公用方法来实现对内部信息的操作和访问。这样可以保护该类对象的内部属性,防止对象间交互以及提高程序的安全性。

1.6.2 继承

继承是指子类通过关键字extends来继承父类,从而直接获得父类的属性和方法。java类仅支持单继承,java接口可支持多继承,这也是java面向对象实现软件复用的重要手段。

在OOP的术语中,我们把Person称为超类(super class),父类(parent class),基类(base class),把Student称为子类(subclass),扩展类(extended class)。

任何类,除了Object,都会继承自某个类(默认Object

1.6.3 多态

多态是指子类对象可直接赋给父类变量,但运行时表现出子类的行为特征。这样同一个类型的对象在执行相同方法时,可能表现出多种行为特征。

Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

1.7 五大基本原则

1.7.1 单一责任原则(SRP)

Singgle Responsibility Principle,其核心思想为:一个类,最好只做一件事,只有一个引起它变化的原因。

单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。

专注,是一个人优良的品质;同样的,单一也是一个类的优良设计。交杂不清的职责将使得代码看起来特别别扭,牵一发而动全身,有失美感和必然导致丑陋的系统错误风险。

1.7.2 开放关闭原则(OCP)

Open Close Principle,其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。

开放封闭原则主要体现在两个方面

  1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  2. 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。

实现开放封闭原则的核心就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。 “需求总是变化”,没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。

1.7.3 里氏替换原则(LSP)

Liskov Substitution Principle,其核心思想是:子类必须能够替换其基类。

这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类

Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。

Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。 Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

1.7.4 依赖倒置原则(DIP)

Dependancy Inversion Principle,其核心思想是:依赖于抽象,不依赖于具体。

具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。 我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。 抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。 依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。

依赖于抽象,就是对接口编程,不要对实现编程

1.7.5 接口分离原则(ISP)

The Interface Segregation principle,其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。

具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口

一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。 接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。

分离的手段主要有以下两种:

  1. 委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。
  2. 多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。

二、 平台无关性

2.1 什么是平台无关性

平台无关性指一种语言在计算机上运行不受平台限制,一处编译处处运行。

2.2 Java 如何实现的平台无关

Java 是一种半编译半解释性语言,即源程序>编译>字节码>字节码解释程序>对应平台的机器语言。Java 通过JVM将.java编译成.class字节码文件,只要运行平台上有JVM就可以直接运行,无需再次编译。

2.3 JVM 还支持哪些语言

JVM还支持Kotlin、Scala、Clojure、Groovy、Jython、JRuby、Ceylon、Eta、Haxe等。

三、 值传递

3.1 引用传递

引用传递指方法调用时,实参的地址通过方法调用传递给相应的形参,在方法体中,实参和形参指向同一块内存地址,此时对形参进行操作会影响到真实内容。

3.2 值传递

值传递指方法调用时,实参通过形参将自己的一个内容副本传递到方法内部,此时形参接收到的内容是实参值的一个拷贝,此时对形参进行操作,都仅仅是对这个副本的操作,不会影响到真实内容。

3.3 为什么说 Java 中只有值传递

3.3.1 变量存储

  1. 基本数据类型的局部变量存储
    声明并初始化基本数据类型的局部变量时,变量名以及字面量值都是存储在栈(虚拟机栈,JVM stack)中,而且是真实的内容。
    基本数据类型的数据本身是不会改变的,当局部变量重新赋值时,并不是在内存中改变字面量内容,而是重新在栈中寻找已存在的相同的数据,若栈中不存在,则重新开辟内存存新数据,并且把要重新赋值的局部变量的引用指向新数据所在地址。
  2.  基本数据类型的成员变量存储
    基本数据类型的成员变量名和值都存储于堆(虚拟机堆,JVM heap)中,其生命周期和对象的是一致的。
  3. 基本数据类型的静态变量
    基本数据类型的静态变量名以及值存储于方法区的运行时常量池中,静态变量随类加载而加载,随类消失而消失。
    备注:JDK8之后,取消了永久代,提出了元空间,并且常量池、静态成员变量等迁移到了堆中;
  4. 引用数据类型的存储
    对于引用数据类型的对象或数组,变量名存在栈中,变量值存储的是对象的地址,并不是对象的实际内容。

3.3.2 实参和形参

 

  1. 形参:在定义函数名称和函数体的时候使用的参数,目的是用来接收调用该函数时传递的参数。
  2. 实参:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数。

3.3.3 严格的求值策略

Java面向对象get和set方法_java_02

  1. 传值调用(值传递):在传值调用中,实际参数被求值,然后通过复制,被传递给被调用的形参。这里形参拿到的是一个拷贝,对其操作并不影响实参。
  2. 传引用调用(引用传递):在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。此时如果改变的形参的值,实参同样会被改变。
  3. 传共享对象调用(共享对象传递):传共享对象调用中,先获取到实参的引用地址,然后将其拷贝,并把这个拷贝传递给函数的形参。因为实参和形参都指向同一个引用地址,如果改变了形参的值,实参同样会改变。

3.3.4 Java 中的对象传递

先看下下面图:

Java面向对象get和set方法_父类_03

可见Java在参数传递过程中,实参的地$User@502被拷贝给了形参。即在Java中使用的对象求值策略就是传共享对象调用。这过程其实就是值传递。

《The Java™ Tutorials》中有关于这部分内容的说明的。首先是关于基本类型描述如下:

Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.

翻译:原始参数(例如int或double)按值传递给方法。 这意味着对参数值的任何更改仅存在于方法范围内。 当方法返回时,参数消失,对它们的任何更改都将丢失。

Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.

翻译:引用数据类型参数(例如对象)也按值传递到方法中。 这意味着当方法返回时,传入的引用仍然引用与以前相同的对象。 但是,如果具有适当的访问级别,则可以在方法中更改对象字段的值。

总结:在Java中,其实是通过值传递实现的参数传递,只不过对于Java对象的传递,传递的内容是对象的引用。

四、封装、继承、多态等核心概念

4.1 封装

封装是指将对象内部的状态信息和实现细节隐藏起来,不允许外部对象直接访问,然后通过该类提供的一些公用方法来实现对内部信息的操作和访问。这样可以保护该类对象的内部属性,防止对象间交互以及提高程序的安全性。

4.2 继承

继承是指子类通过关键字extends来继承父类,从而直接获得父类的属性和方法。Java类仅支持单继承,java接口可支持多继承,这也是Java面向对象实现软件复用的重要手段。

4.3 多态

多态是指子类对象可直接付给父类变量,但运行时表现出子类的行为特征。这样同一个类型的对象在执行相同方法时,可能表现出多种行为特征。

4.4 重写(Override)

重写即覆盖,存在于子类与父类间,指子类包含父类同名方法及相同形参列表的现象。

方法的重写遵循原则(两同两小一大):

  1. 方法名和形参列表相同
  2. 返回值类型和抛出的异常要比父类小或相等
  3. 访问权限要比父类大或相等

4.5 重载(OverLoading)

重载存在于同一个类中,指在同一个类中的两个或两个以上的方法,具有相同的方法名但形参列表和方法体不同的现象。

方法重载遵循原则(一同两不同):

  1. 方法名相同
  2. 形参列表和方法体不同。

4.6 构造器

构造器,即构造方法,指具有和类名相同的名称,但无返回值的方法,是创建对象的主要途径,其最大的作用时创建对象时执行初始化。一个类中必须要有一个或一个以上的构造器,在没有给java类显示提供构造器的情况下,系统会自动为这个类提供一个默认的构造器。构造器间是可以进行重载,重载的构造器可以通过关键字this进行相互调用

4.7 类变量

类变量,即静态变量,指在类体内定义并且通过关键字static修饰的变量,从类的准备阶段开始存在,其作用域和该类的生命周期相同。在未对静态进行初始化的情况下,系统会自动进行初始化处理,可以通过类名或该类的对象进行调用。如果我们需要定义一个变量是作用于某个类并且对该类的所有实例完全相同即类相关,那么该变量理应定义成类变量。

4.8 实例变量

实例变量,即非静态变量,指在类体内定义并且没有通过关键字static修饰的变量,从该类的实例被创建时开始存在,作用域与该类实例的生命周期性相同。在未对实例变量进行初始化的情况下,系统会自动进行初始化处理,可以通过该类实例对象进行调用。如果我们定义一个变量是作用与该类的某个实例,表示该实例运行时的状态信息即实例相关,那么该变量理应定义成实例变量。

4.9 局部变量

局部变量指在方法内或代码块内定义的,它定义的方式有三种:形参、方法局部变量、代码块局部变量。局部变量除了形参外都需要进行显性初始化。如果我们需要定义一个变量仅在该类的多个方法间进行共享,那么该变量理应定义为局部变量。

4.10 作用域

作用域,即对象之间访问权限的控制。

Java面向对象get和set方法_Java面向对象get和set方法_04

下面对四个作用域说明下:

  1. public修饰符:对象本身、同一个包下、子类、其他包下对象都可以调用
  2. protected修饰符:对象本身、同一个包下、子类对象可以调用
  3. default修饰符:对象本身、同一个包下对象可以调用
  4. private修饰符:对象本身可以调用,常用来修饰成员变量

4.11 java 继承和实现的区别和联系

4.11.1 使用继承的情况

如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。

4.11.2 使用实现的情况

如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标。

4.11.3 继承与实现的联系

继承和接口(实现)都是不断向上抽取而来的,都能完成代码重用,提高开发效率。

4.11.4 继承与实现的区别

  1. 修饰符不同:不同的修饰符修饰,实现:implements,继承:extends;
  2.  数量不同:Java只支持接口的多继承,不支持的多继承即类单继承;而继承在java中具有单根性,子类只能继承一个父类即单继承,但可多实现。
  3. 属性定义不同:在接口中只能定义全局常量(static final)、无实现的方法和默认方法;而在继承中可以定义属性方法、变量、常量等。
  4. 调用不同:某个接口被类实现时,在类中一定要实现接口中的抽象方法;而继承想调用那个方法就调用那个方法。

五、统一建模语言

5.1 UML

UML(Unified Modeling Language),用于描述、记录面向对象分析(OOA)和面向对象设计(OOD)结果的符号表示法。包括用例图、类图、组件图、部署图、活动图、状态机图。

5.2 用例图

用于描述系统提供的系列功能,而每个例图则代表系统的一个功能模块。用例图包括用例(Use Case,椭圆表示,用例名位于椭圆内部或下面)、角色(Actor,人形符号表示)、用例与角色关系(简单短实线表示)、系统用例间关系(简单带箭头虚线表示)。

用例图主要在需求分析阶段使用,用于描述系统实现的功能,方便与客户交流。

注意:用例不可做的过多,能够描述清楚功能即可,以免造成难以阅读与理解,可以尽量使用文字说明。

Java面向对象get和set方法_子类_05

5.3 类图

用于描述系统的静态内部结构、实体间的关联关系。类图一般用三部分矩形来描述,最上面部分显示类名,中间部分包含类属性,最下面部分包含类方法。类之间关联关系:关联、泛化、依赖。

Java面向对象get和set方法_子类_06

5.4 组件图

用于描述系统的物理用图,显示系统中的软件对其他软件组件的依赖关系。组件图通常包含组件、接口、port等。

Java面向对象get和set方法_Java面向对象get和set方法_07

5.5 部署图

用于描述系统软件如何部署到硬件环境中,显示软件系统不同的组件将在何处物理运行,以及他们如何彼此通信。即描述系统软件的不同组件的物理运行方式和通信方式。

Java面向对象get和set方法_父类_08

5.6 顺序图

用于显示具体用例(或用例的一部分)的详细流程,并且显示流程中不同对象之间的调用关系。即描述对象之间的交互,重点描述消息及其时间顺序。

Java面向对象get和set方法_Java_09

5.7 活动图

用于描述用例内部的活动和方法的流程图,即用于描述过程原理、业务逻辑以及工作流技术。

Java面向对象get和set方法_Java_10

5.8 状态机图

描述系统用例中的对象所处的不同状态和该类状态转换信息。

Java面向对象get和set方法_子类_11