夯实Java基础系列1:Java面向对象三大特性(基础篇)

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看

[https://github.com/h2pl/Java-Tutorial](https://github.com/h2pl/Java-Tutorial)

喜欢的话麻烦点下Star、Fork、Watch三连哈,感谢你的支持。

文章首发于我的个人博客:

[www.how2playlife.com](https://github.com/h2pl/Java-Tutorial)

本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作者。

该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架。为了更好地总结和检验你的学习成果,本系列文章也会提供每个知识点对应的面试题以及参考答案。

如果对本系列文章有什么建议,或者是有什么疑问的话,也可以关注公众号【Java技术江湖】联系作者,欢迎你参与本系列博文的创作和修订。

@[toc]

# Java面向对象三大特性(基础篇)

面向对象简称 OO(Object Oriented),20 世纪 80 年代以后,有了面向对象分析(OOA)、 面向对象设计(OOD)、面向对象程序设计(OOP)等新的系统开发方式模型的研究。

对语言来说,一切皆是对象。把现实世界中的对象抽象地体现在编程世界中,一个对象代表了某个具体的操作。一个个对象最终组成了完整的程序设计,这些对象可以是独立存在的,也可以是从别的对象继承过来的。对象之间通过相互作用传递信息,实现程序开发。

## 对象的概念

Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。对象有以下特点:

- 对象具有属性和行为。
- 对象具有变化的状态。
- 对象具有唯一性。
- 对象都是某个类别的实例。
- 一切皆为对象,真实世界中的所有事物都可以视为对象。

## 面向对象和面向过程的区别

- 面向过程:
一种较早的编程思想,顾名思义就是该思想是站着过程的角度思考问题,强调的就是功能行为,功能的执行过程,即先后顺序,而每一个功能我们都使用函数(类似于方法)把这些步骤一步一步实现。使用的时候依次调用函数就可以了。

- 面向过程的设计:
最小的程序单元是函数,每个函数负责完成某一个功能,用于接受输入数据,函数对输入数据进行处理,然后输出结果数据,整个软件系统由一个个的函数组成,其中作为程序入口的函数称之为主函数,主函数依次调用其他函数,普通函数之间可以相互调用,从而实现整个系统功能。
  面向过程最大的问题在于随着系统的膨胀,面向过程将无法应付,最终导致系统的崩溃。为了解决这一种软件危机,我们提出面向对象思想。

- 面向过程的缺陷:
是采用指定而下的设计模式,在设计阶段就需要考虑每一个模块应该分解成哪些子模块,每一个子模块又细分为更小的子模块,如此类推,直到将模块细化为一个个函数。

- 存在的问题

​ 设计不够直观,与人类的思维习惯不一致
系统软件适应新差,可拓展性差,维护性低

- 面向对象:

​ 一种基于面向过程的新编程思想,顾名思义就是该思想是站在对象的角度思考问题,我们把多个功能合理放到不同对象里,强调的是具备某些功能的对象。

  具备某种功能的实体,称为对象。面向对象最小的程序单元是:类。面向对象更加符合常规的思维方式,稳定性好,可重用性强,易于开发大型软件产品,有良好的可维护性。

  在软件工程上,面向对象可以使工程更加模块化,实现更低的耦合和更高的内聚。

## 面向对象的三大核心特性简介

面向对象开发模式更有利于人们开拓思维,在具体的开发过程中便于程序的划分,方便程序员分工合作,提高开发效率。

该开发模式之所以使程序设计更加完善和强大,主要是因为面向对象具有继承、封装和多态 3 个核心特性。

**1、继承的概念**

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

[外链图片转存失败(img-LkGM7vxm-1569146238577)(https://www.runoob.com/wp-content/uploads/2013/12/14B0951E-FC75-47A3-B611-4E1883887339.jpg)]

兔子和羊属于食草动物类,狮子和豹属于食肉动物类。

食草动物和食肉动物又是属于动物类。

所以继承需要符合的关系是:is-a,父类更通用,子类更具体。

虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。

**2、Java 多态**

------

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:

[外链图片转存失败(img-zA9F3Rja-1569146238578)(https://www.runoob.com/wp-content/uploads/2013/12/dt-java.png)]

多态性是对象多种表现形式的体现。

> 现实中,比如我们按下 F1 键这个动作:
>
> - 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
> - 如果当前在 Word 下弹出的就是 Word 帮助;
> - 在 Windows 下弹出的就是 Windows 帮助和支持。
>
> 同一个事件发生在不同的对象上会产生不同的结果。

**3、Java 封装**

------

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

## 面向对象编程三大特性详解

面向对象编程是利用 类和对象编程的一种思想。万物可归类,类是对于世界事物的高度抽象 ,不同的事物之间有不同的关系 ,一个类自身与外界的封装关系,一个父类和子类的继承关系, 一个类和多个类的多态关系。万物皆对象,对象是具体的世界事物,面向对象的三大特征封装,继承,多态,封装,封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;继承是父类和子类的关系,多态说的是类与类的关系。

### 一、继承

#### 1、继承的概念

如同生活中的子女继承父母拥有的所有财产,程序中的继承性是指子类拥有父类[数据结构](http://c.biancheng.net/data_structure/)的方法和机制,这是类之间的一种关系;继承只能是单继承。

例如定义一个语文老师类和数学老师类,如果不采用继承方式,那么两个类中需要定义的属性和方法如图 1 所示。

[外链图片转存失败(img-1wmYKDrO-1569146238578)(http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40613629.jpg)]
图1 语文老师类和数学老师类中的属性和方法

从图 1 能够看出,语文老师类和数学老师类中的许多属性和方法相同,这些相同的属性和方法可以提取出来放在一个父类中,这个父类用于被语文老师类和数学老师类继承。当然父类还可以继承别的类,如图 2 所示。

[外链图片转存失败(img-gJ5pmYDN-1569146238578)(http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40AR23.jpg)]
图2 父类继承示例图

总结图 2 的继承关系,可以用概括的树形关系来表示,如图 3 所示。

[外链图片转存失败(img-JsPYr5ao-1569146238579)(http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40RT47.jpg)]
图3 类继承示例图

从图 3 中可以看出,学校主要人员是一个大的类别,老师和学生是学校主要人员的两个子类,而老师又可以分为语文老师和数学老师两个子类,学生也可以分为班长和组长两个子类。

使用这种层次形的分类方式,是为了将多个类的通用属性和方法提取出来,放在它们的父类中,然后只需要在子类中各自定义自己独有的属性和方法,并以继承的形式在父类中获取它们的通用属性和方法即可。

 继承是类与类的一种关系,是一种“is a”的关系。比如“狗”继承“动物”,这里动物类是狗类的父类或者基类,狗类是动物类的子类或者派生类。如下图所示:

[外链图片转存失败(img-TlRQMo8J-1569146238579)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701123011243-2128400556.png)]

注:java中的继承是**单继承**,即**一个类只有一个父类。**

**补充:Java中的继承只能单继承,但是可以通过内部类继承其他类来实现多继承。**

```
public class Son extends Father{
public void go () {
System.out.println("son go");
}
public void eat () {
System.out.println("son eat");
}
public void sleep() {
System.out.println("zzzzzz");
}
public void cook() {
//匿名内部类实现的多继承
new Mother().cook();
//内部类继承第二个父类来实现多继承
Mom mom = new Mom();
mom.cook();
}
private class Mom extends Mother {
@Override
public void cook() {
System.out.println("mom cook");
}
}
}
```

#### 2、继承的好处

 子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用; 

#### 3、语法规则

[外链图片转存失败(img-RgZ9dRoy-1569146238579)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701123421961-647167245.png)]

------

##### A、方法的重写

 子类如果对继承的父类的方法不满意(不适合),可以自己编写继承的方法,这种方式就称为**方法的重写。当调用方法时会优先调用子类的方法。**

 **重写要注意:**

  a、返回值类型

  b、方法名

  c、参数类型及个数

 都要与父类继承的方法相同,才叫方法的重写。

 **重载和重写的区别:**

  方法重载:在同一个类中处理不同数据的多个相同方法名的多态手段。

  方法重写:相对继承而言,子类中对父类已经存在的方法进行区别化的修改。

------

##### B、继承的初始化顺序

  1、初始化父类再初始化子类

  2、先执行初始化对象中属性,再执行构造方法中的初始化。

 基于上面两点,我们就知道实例化一个子类,java程序的执行顺序是:

 **父类对象属性初始化---->父类对象构造方法---->子类对象属性初始化--->子类对象构造方法**   

 下面有个形象的图:

[外链图片转存失败(img-i03JI2xe-1569146238580)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701144019071-1063399032.png)]

------

##### C、final关键字

 使用final关键字做标识有“最终的”含义。

1. final 修饰类,则该类**不允许被继承。**
2. final 修饰方法,则该方法不允许被**覆盖(重写)**。
3. final 修饰属性,则该类的该属性不会进行隐式的初始化,所以 该final 属性的**初始化属性必须有值**,或在**构造方法中赋值(但只能选其一,且必须选其一,因为没有默认值!),**且初始化之后就不能改了,**只能赋值一次**。
4. final 修饰变量,则该变量的值只能赋一次值,在声明变量的时候才能赋值,即变为**常量**。

------

##### D、super关键字

 在对象的内部使用,可以代表父类对象。

  1、访问父类的属性:super.age

   2、访问父类的方法:super.eat()

 super的应用:

 首先我们知道子类的构造的过程当中必须调用父类的构造方法。其实这个过程已经隐式地使用了我们的super关键字。

 这是因为如果子类的构造方法中没有显示调用父类的构造方法,则系统默认调用父类无参的构造方法。

 那么如果自己用super关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的**第一行**。

 **要注意的是:如果子类构造方法中既没有显示调用父类的构造方法,而父类没有无参的构造方法,则编译出错。**

(补充说明,虽然没有显示声明父类的无参的构造方法,系统会自动默认生成一个无参构造方法,但是,如果你声明了一个有参的构造方法,而没有声明无参的构造方法,这时系统不会动默认生成一个无参构造方法,此时称为父类有没有无参的构造方法。)

------

### 二、封装

#### 1、封装的概念

封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。封装的目的在于保护信息,使用它的主要优点如下。

- 保护类中的信息,它可以阻止在外部定义的代码随意访问内部代码和数据。
- 隐藏细节信息,一些不需要程序员修改和使用的信息,比如取款机中的键盘,用户只需要知道按哪个键实现什么操作就可以,至于它内部是如何运行的,用户不需要知道。
- 有助于建立各个系统之间的松耦合关系,提高系统的独立性。当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。例如 U 盘,不管里面的存储方式怎么改变,只要 U 盘上的 USB 接口不变,就不会影响用户的正常操作。
- 提高软件的复用率,降低成本。每个系统都是一个相对独立的整体,可以在不同的环境中得到使用。例如,一个 U 盘可以在多台电脑上使用。

Java 语言的基本封装单位是类。由于类的用途是封装复杂性,所以类的内部有隐藏实现复杂性的机制。Java 提供了私有和公有的访问模式,类的公有接口代表外部的用户应该知道或可以知道的每件东西,私有的方法数据只能通过该类的成员代码来访问,这就可以确保不会发生不希望的事情。

#### 2、封装的优点

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

封装的优点

1. 良好的封装能够减少耦合。
2. 类内部的结构可以*修改。
3. 可以对成员变量进行更精确的控制。
4. 隐藏信息,实现细节。

Java 封装,说白了就是将一大坨公共通用的实现逻辑玩意,装到一个盒子里(class),出入口都在这个盒子上。你要用就将这个盒子拿来用,连接出入口,就能用了,不用就可以直接扔,对你代码没什么影响。

对程序员来说,使用封装的目的:

1. 偷懒,辛苦一次,后面都能少敲很多代码,增强了代码得复用性
2. 简化代码,看起来更容易懂
3. 隐藏核心实现逻辑代码,简化外部逻辑,并且不让其他人修改,jar 都这么干
4. 一对一,一个功能就只为这个功能服务;避免头发绳子一块用,导致最后一团糟

#### 3、封装的实现步骤

[外链图片转存失败(img-kED45iQa-1569146238580)(https://images2015.cnblogs.com/blog/1189312/201706/1189312-20170630170717493-357592353.png)]

    需要注意:对封装的属性不一定要通过get/set方法,其他方法也可以对封装的属性进行操作。当然最好使用get/set方法,比较标准。

------

##### A、访问修饰符

[外链图片转存失败(img-mjPpp7qr-1569146238580)(https://images2015.cnblogs.com/blog/1189312/201706/1189312-20170630174919274-1857293801.png)]

    从表格可以看出**从上到下封装性越来越差**。

##### B、this关键字

 1.this关键字**代表当前对象**

  this.属性 操作当前对象的属性

  this.方法 调用当前对象的方法。

 2.封装对象的属性的时候,经常会使用this关键字。

 3.当getter和setter函数参数名和成员函数名重合的时候,**可以使用this****区别。如:**

[外链图片转存失败(img-812wFgw3-1569146238581)(https://images2015.cnblogs.com/blog/1189312/201706/1189312-20170630180217524-833886832.png)]

##### C、Java 中的内部类

 内部类( Inner Class )就是定义在另外一个类**里面**的类。与之对应,包含内部类的类被称为外部类。

 那么问题来了:那为什么要将一个类定义在另一个类里面呢?清清爽爽的独立的一个类多好啊!!

 答:内部类的主要作用如下:

1. 内部类提供了**更好的封装**,可以把内部类**隐藏**在外部类之内,**不允许**同一个包中的其他类访问该类。
2. 内部类的方法可以**直接访问外部类的所有数据**,包括**私有的数据**。
3. 内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便。

  内部类可分为以下几种:

- 成员内部类
- 静态内部类
- 方法内部类
- 匿名内部类  

### 三、多态

#### 1、多态的概念

面向对象的多态性,即“一个接口,多个方法”。多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。多态概念可以用树形关系来表示,如图 4 所示。

[外链图片转存失败(img-2rZe5SBZ-1569146238582)(http://c.biancheng.net/uploads/allimg/181017/3-1Q01G4095bW.jpg)]

图4 多态示例图

从图 4 中可以看出,老师类中的许多属性和方法可以被语文老师类和数学老师类同时使用,这样也不易出错。

#### 2、多态的好处

可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。

可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。

接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。

灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。

简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

子代父类实例化,然后就相当于一个父亲有很多儿子,送快递的给这个父亲的儿子送东西,他只需要送到父亲的家就行了,至于具体是那个儿子的,父亲还会分不清自己的儿子么,所以你就不用操心了。

**使用多态是一种好习惯**
多态方式声明是一种好的习惯。当我们创建的类,使用时,只用到它的超类或接口定义的方法时,我们可以将其索引声明为它的超类或接口类型。

它的好处是,如果某天我们对这个接口方法的实现方式变了,对这个接口又有一个新的实现类,我们的程序也需要使用最新的实现方式,此时只要将对象实现修改一下,索引无需变化。

比如Map map = new HashMap ();

想换成HashTable实现,可以Map map = new HashTable ();

比如写一个方法,参数要求传递List类型,你就可以用List list = new ArrayList()中的list传递,但是你写成ArrayList list = new ArrayList()是传递不进去的。尽管方法处理时都一样。另外,方法还可以根据你传递的不同list(ArrayList或者LinkList)进行不同处理。

#### 3、Java中的多态

java里的多态主要表现在两个方面:

##### A、引用多态  

  父类的引用可以指向本类的对象;

  父类的引用可以指向子类的对象;

  这两句话是什么意思呢,让我们用代码来体验一下,首先我们创建一个父类Animal和一个子类Dog,在主函数里如下所示:

[外链图片转存失败(img-YUHxvSiU-1569146238582)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701155536086-1897896282.png)]

  注意:我们不能使用一个子类的引用来指向父类的对象,如:[外链图片转存失败(img-ohyxfag1-1569146238583)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701155839586-923083573.png)]。

  这里我们必须深刻理解引用多态的意义,才能更好记忆这种多态的特性。为什么子类的引用不能用来指向父类的对象呢?我在这里通俗给大家讲解一下:就以上面的例子来说,我们能说“狗是一种动物”,但是不能说“动物是一种狗”,狗和动物是父类和子类的继承关系,它们的从属是不能颠倒的。当父类的引用指向子类的对象时,该对象将只是看成一种特殊的父类(里面有重写的方法和属性),反之,一个子类的引用来指向父类的对象是不可行的!!

##### B、方法多态

  根据上述创建的两个对象:本类对象和子类对象,同样都是父类的引用,当我们指向不同的对象时,它们调用的方法也是多态的。

  创建本类对象时,调用的方法为本类方法;

  创建子类对象时,调用的方法为子类重写的方法或者继承的方法;

  使用多态的时候要注意:**如果我们在子类中编写一个独有的方法(没有继承父类的方法),此时就不能通过父类的引用创建的子类对象来调用该方法!!!**

  **注意: 继承是多态的基础。**

------

##### C、引用类型转换

 了解了多态的含义后,我们在日常使用多态的特性时经常需要进行引用类型转换。

 引用类型转换:

 **1.向上类型转换(隐式/自动类型转换),是小类型转换到大类型**

  就以上述的父类Animal和一个子类Dog来说明,当父类的引用可以指向子类的对象时,就是**向上类型转换**。如:

[外链图片转存失败(img-cA31LZrI-1569146238584)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701162630508-961507659.png)]

  **2. 向下类型转换(强制类型转换),是大类型转换到小类型(有风险,可能出现数据溢出)。**

  将上述代码再加上一行,我们再次将父类转换为子类引用,那么会出现错误,编译器不允许我们直接这么做**,**虽然我们知道这个父类引用指向的就是子类对象,但是编译器认为这种转换是存在风险的**。**如:

[外链图片转存失败(img-DPI8b2WP-1569146238585)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701162926477-3857975.png)]

  那么我们该怎么解决这个问题呢,我们可以在animal前加上(Dog)来强制类型转换。如:[外链图片转存失败(img-e9ds8jO5-1569146238585)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701163408383-2003626729.png)]

  但是如果父类引用没有指向**该子类的对象**,则不能向下类型转换,虽然编译器不会报错,但是运行的时候程序会出错,如:[外链图片转存失败(img-3k7k0NZg-1569146238586)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701164229899-1055190774.png)]

  其实这就是上面所说的子类的引用指向父类的对象,而强制转换类型也不能转换!!

  还有一种情况是父类的引用指向**其他子类的对象**,则不能通过强制转为**该子类的对象**。如:

    [外链图片转存失败(img-tl2WhRgR-1569146238586)(https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701165133289-717439360.png)]

  这是因为我们在编译的时候进行了强制类型转换,编译时的类型是我们强制转换的类型,所以编译器不会报错,而当我们运行的时候,程序给animal开辟的是Dog类型的内存空间,这与Cat类型内存空间不匹配,所以无法正常转换。这两种情况出错的本质是一样的,所以我们在使用强制类型转换的时候要特别注意这两种错误!!下面有个更安全的方式来实现向下类型转换。。。。

  **3. instanceof运算符,来解决引用对象的类型,避免类型转换的安全性问题。**

  **instanceof**是Java的一个二元操作符,和==,>, int -> long ->double -> ..。
//当没有基本类型对应的方法时,先自动装箱,调用包装类方法。
//如果没有包装类方法,则调用包装类实现的接口的方法。
//最后再调用持有多个参数的char...方法。
a.eat('a');
a.eat('a','c','b');
}
public void eat(short i) {
System.out.println("short");
}
public void eat(int i) {
System.out.println("int");
}
public void eat(double i) {
System.out.println("double");
}
public void eat(long i) {
System.out.println("long");
}
public void eat(Character c) {
System.out.println("Character");
}
public void eat(Comparable c) {
System.out.println("Comparable");
}
public void eat(char ... c) {
System.out.println(Arrays.toString(c));
System.out.println("...");
}

// public void eat(char i) {
// System.out.println("char");
// }
```

## 参考文章

https://blog.csdn.net/android_hl/article/details/53228348

## 微信公众号

### Java技术江湖

如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!

**Java工程师必备学习资源:** 一些Java工程师常用学习资源,关注公众号后,后台回复关键字 **“Java”** 即可免费无套路获取。

![我的公众号](https://img2018.cnblogs.com/blog/1813823/201909/1813823-20190924192623155-1695944816.jpg)

### 个人公众号:黄小斜

作者是 985 硕士,蚂蚁金服 JAVA 工程师,专注于 JAVA 后端技术栈:SpringBoot、MySQL、分布式、中间件、微服务,同时也懂点投资理财,偶尔讲点算法和计算机理论基础,坚持学习和写作,相信终身学习的力量!

**程序员3T技术学习资源:** 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 **“资料”** 即可免费无套路获取。

![](https://img2018.cnblogs.com/blog/1813823/201909/1813823-20190924192623315-499286585.jpg)

上一篇:Java工程师学习指南第1部分:夯实Java基础系列


下一篇:夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理