前言

大家好啊,我是汤圆,今天给大家带来的是《Java中的三大特性 - 超详细篇》,希望对大家有帮助,谢谢

这一节的内容可能有点多,大家可以选择性的来看

简介

Java的三大特性:封装、继承、多态

乍一听,好像很高大上,其实当你真正用的时候,会发现高大上的还在后面呢。。。

热身

在正式讲解三大特性之前,先普及几个知识

1. 访问权限修饰符

Java中关于访问权限的四个修饰符,表格如下

private friendly(默认) protected public
当前类访问权限
包访问权限 ×
子类访问权限 × ×
其他类访问权限 × × ×

其中比较尴尬的是protected修饰符,有点卡在中间,不上不下的感觉

因为它不适合用来修饰属性

假设用它修饰属性,那么任何一个人都可以通过继承这个类,来直接访问到这个类的属性,从而破坏"封装性"

2. 抽象类(abstract)

什么是抽象类?

抽象类就是用abstract修饰,且不能被直接初始化的类,但是可以通过子类来初始化

比如:Father father = new Son()

对应的,抽象方法就是用abstract修饰的方法

抽象方法是一种很特殊的方法,它没有方法体,即方法实现代码为空,比如abstract public void fun();

抽象方法一般在子类中进行实现,它就好像是在说:我不写代码,我只是声明一个方法名,剩下的交给我的子孙后代(继承类)去做

抽象类有一个很重要的特点:抽象类可以没有抽象方法,但是如果一个类有抽象方法,那么这个类肯定是抽象类

为什么会有抽象类

解耦,使代码结构更加清晰

因为抽象类不能被直接创建为对象,它只是作为一个通用接口来供别人实现和调用,所以这样就使得抽象的代码更加清晰(它只声明方法,不实现方法)

就好比,老板和员工,老板负责分发任务,员工负责去具体的实现任务

好了,关于抽象类,先介绍到这里,更详细的后面的章节再深入

3. 重载(overloading)和覆写(overwriting)

重载和覆写是两个很容易混淆的概念

重载:同一个类中,一个方法的多种表现形式(参数类型不同,参数个数不同)

覆写:继承设计中,子类覆盖父类的方法(也可以叫做重写,不过这样跟重载有点混淆,所以个人喜欢叫做覆写)

他们之间的区别如下

重载 覆写
访问权限 可以不同 可以不同(但是子类的可见性不能比父类的低)
方法返回值 可以不同 相同
参数类型 不同(充分条件) 相同
参数个数 不同(充分条件) 相同

这里要注意几点

  1. 覆写时,子类的方法访问权限不能低于父类,比如父类方法为public,那么子类也只能为public
  2. 重载时,访问权限和方法返回值,不能作为用来判断一个方法是否为重载的依据;只能说重载允许不同的访问权限和返回值

覆写示范

代码示范如下,

// 覆写一:正确示范
@Override
public void fun(){
System.out.println("son fun");
}
// 覆写二:错误示范,访问权限低了
@Override
private void fun(){
// 报错:'fun()' in 'SonDemo' clashes with 'fun()' in 'Father'; attempting to assign weaker access privileges ('private'); was 'public'
System.out.println("son fun");
}

@Override这个是干嘛的?之前没见过啊

这个修饰符用来说明这个方法是覆写方法,不写也可以,系统会自己识别方法是不是覆写的

那为啥还要多此一举呢?用系统默认的识别机制不好吗?

要多此一举;不好;

因为加了注解,代码可读性更高,代码更加规范,别人看了代码后,立马就知道这个方法是覆写方法

重载示范

重载用图展示可能会更加清晰

图示解释:

  1. 参数类型和参数个数,只要满足其一,就可以说这个方法被重载了

  2. 访问权限和方法返回值用虚线框,是为了说明他们两个只是重载的一个附加表现形式(可有可无),不能作为重载的判断依据

下面用代码演示下

// 基础方法
public void fun1(int a){ }
// 重载一:参数个数不同
public void fun1(){ }
// 重载二:参数类型不同
public void fun1(float a){ }
// 重载三:错误示范,仅仅用访问权限的不同来重载
private void fun1(int a){
// 编译报错:'fun1(int)' is already defined
}
// 重载四:错误示范,仅仅用返回值的不同来重载
public int fun1(int a){
// 编译报错:'fun1(int)' is already defined
return 0;
}

下面进入正文,开始顺序介绍这三大特性

正文

1. 封装(Encapsulation)

就是把类的属性私有化(private修饰),再通过公有方法(public)进行访问和修改

为什么要封装呢?

  1. 追踪变化:可以在set方法中,编写代码来追踪属性的改变记录

    public void setName(String name) {
    System.out.println("名字即将被修改");
    System.out.println("旧名字:" + this.name);
    System.out.println("新名字:" + name);
    this.name = name;
    }
  2. 修改底层实现:在修改属性名时,不会影响外部接口对属性的访问

    比如:name属性改为firstName和lastName,name就可以在get方法中修改返回值为firstName+lastName,对外接口没变化

    // 修改前
    private String name; public String getName() {
    return name;
    } // 修改后
    private String firstName;
    private String lastName;
    // 方法名不用变,只是方法内容作了修改
    public String getName() {
    return firstName + lastName;
    }

  3. 校验数据:可以在set方法中,校验传来的数据是否符合属性值的设定范围,防止无效数据的乱入

    public void setAge(int age) throws Exception {
    if(age>1000 || age<0){
    throw new Exception("年龄不符合规范,0~1000");
    }
    this.age = age;
    }

2. 继承(Inheritance)

如果子类继承了父类,那么子类就可以复用父类的方法和属性,并且可以在此基础上新增方法和属性

这里要注意的一点是:Java是单继承语言,即每个类只能有一个父类

这里还要普及一个常识:如果一个类没有指定父类(即没有继承任何类),那么这个类默认继承Object类

为什么要用继承呢?

为了代码复用,减少重复工作

单继承不会太局限吗?为啥不用多继承?

因为多继承会导致"致命方块"问题(因为像扑克牌的方块符号)

  • 比如A同时继承B和C,然后B和C各自继承D
  • B和C各自覆写了D的fun方法
  • 那这时A该调用哪个类的fun方法呢

下面用图来说话

那为什么叫致命方块,而不是致命三角形呢?那个D类好像是多余的

不多余

这个D类其实就是上面讲到的抽象类的作用:将共有的部分fun()抽象出来(或者提供一个基础的实现),然后子类分别去实现各自的,这也是多态的一种体现(下面会将多态)

如果没有D类,那么B和C的fun()就会存在重复代码,这时你可能就想要搞一个父类出来了,这个父类就是D类

那要怎么判断继承类设计得好不好呢?

通过is-a关系来判断

is-a关系指的是一个是另一个的关系,男人是人(说得通),人是男人(一半说得通)

用is-a关系可以很好地体现你的继承类设计的好还是坏

  • 如果子类都可以说是一个父类,那么这个继承关系设计的就很好(男人是人,is-a关系)
  • 如果子类和父类只是包含或者引用的关系,那么这个继承关系就很糟糕(猫是猫笼,包含关系)

有没有什么办法可以阻止类的继承?就像private修饰符用来封装属性,其他人访问不到一样

有啊,final修饰符可以阻止类的继承

这里重点讲一下final修饰符

final可以用来修饰属性、方法、类,表示他们是常量,不可被修改的

final修饰属性:属性是常量,必须在定义时初始化,或者构造函数中初始化

final修饰方法:方法不能被覆写

final修饰类:类不能被继承

说到final,有必要提一下内联

内联指的是,如果一个方法内容很短,且没有被其他类覆写时,方法名会被直接替换为方法内容

比如:final getName()这个方法可以内联为name属性

再比如:getSum(){return a+b},会直接被内联为a+b

为什么会有内联这个东西呢?

因为这样可以提高效率(细节:CPU在处理方法调用的指令时,使用的分支转移会扰乱预取指令的策略,这个比较底层,这里先简单介绍,后面章节再深入)

那它有没有什么缺点呢?

有,如果一个方法内容过长,又误被当做内联处理,那么就会影响性能

比如你的代码多个地方都调用这个方法,那么你的代码就会膨胀变得很大,从而影响性能

那有没有办法可以解决呢?

有,虚拟机的即时编译技术

即时编译会进行判断,如果一个方法内容很长,且被多次调用,那么它会自动关闭内联机制,防止代码膨胀

3. 多态(Polymorphism)

字面理解,就是多种形态,在Java中,多态指的是,一个类可以有多种表现形态

多态主要是 用来创建可扩展的程序

像我们上面提到的继承就是属于多态的一种

还有一种就是接口(interface)

接口类一种是比抽象类更加抽象的类

因为抽象类起码还可以实现方法,但是接口类没得选,就只能定义方法,不能实现

不过从Java8开始,接口支持定义默认方法和静态方法

接口的默认方法(default修饰符)和静态方法(static修饰符),会包含方法内容,这样别人可以直接调用接口类的方法(后面章节再细讲)

这样你会发现接口变得很像抽象类了,不过接口支持多实现(即一个类可以同时实现多个类,但是一个类同时只能继承一个类)

这样一来,Java相当于间接地实现了多继承

下图说明继承和实现的区别:单继承,多实现

多态一般用在哪些场景呢?

场景很多,这里说两个最常用的

  • 场景一:方法的参数,即方法定义时,父类作为方法的形参,然后调用时传入子类作为方法的实参
  • 场景二:方法的返回值,即方法定义时,父类作为方法的返回值,然后在方法内部实际返回子类

代码示范如下:

public class PolyphorismDemo {
public static void main(String[] args) {
PolyphorismDemo demo = new PolyphorismDemo();
//场景一:形参,将猫(子类)赋值给动物(父类)
demo.fun(new Cat());
//场景二:返回值,将猫赋值给动物
Animal animal = demo.fun2();
} public void fun(Animal animal){ } public Animal fun2(){
return new Cat();
}
} class Animal{ } class Cat extends Animal{ }

总结

其中还有很多知识点没总结,太多了,看起来会不方便,所以其他的内容会陆续放到后面章节来讲

这里先简单列出来,比如:

  • equals和hashcode的关系
  • instanceof和getClass()的区别
  • 静态绑定和动态绑定
  • Java8的默认方法和静态方法
  • 等等等

后记

最后,感谢大家的观看,谢谢

Java中的三大特性 - 超详细篇的更多相关文章

  1. Java 中的三大特性

    我们都知道 Java 中有三大特性,那就是继承 ,封装和多态 .那我今天我就来说说这几个特性 . 老样子 ,先问问自己为什么会存在这些特性 .首先说封装 ,封装就是使用权限修饰符来实现对属性的隐藏 , ...

  2. java中的三大特性

    java的三大特性是封装.继承.多态: 继承是OOD(面向对象设计)为了更好的建模,编程的时候是OOP(面向对象编程)提高代码的复用性.这里有个注意点:一个类只有一个直接的父类,但不是只有一个父类. ...

  3. Java中面向对象三大特性之——多态

    多态的概述:  多态是继封装.继承之后,面向对象的第三大特性. 生活中,比如跑的动作,小猫.小狗和大象,跑起来是不一样的.再比如飞的动作,昆虫.鸟类和飞机,飞起来也是不一样的.可见,同一行为,通过不同 ...

  4. Java中面向对象三大特性之继承

    1. 继承的概述 继承就是子类继承父类的变量和方法,下面用代码解释一下: class Student {// 定义学生类 String name; int age; void study() { Sy ...

  5. Java中的三大特性:封装、继承、多态

    封装: 概念:封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问,适当的封装可以让代码更容易理解与维护,也加强了代码的安全性. 原则:将属性隐藏起来,若需要访问某个属性,提供公共方法对 ...

  6. Java中面向对象三大特性之——继承

    继承的概述 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可. 现实生活中继承:子承父业,用来描述事物之间的关系 代码中继承:就是用 ...

  7. Java中面向对象三大特性之——封装

    概述 面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改. 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问.要访问该类的数据,必须通 ...

  8. Java中的equals()和hashCode() - 超详细篇

    前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的equals()和hashCode() - 详细篇>,希望对大家有帮助,谢谢 文章纯属原创,个人总结难免有差错,如果有,麻烦在评论 ...

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

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...

随机推荐

  1. 2020 front-end interview

    2020 front-end interview https://juejin.im/post/5e083e17f265da33997a4561 xgqfrms 2012-2020 www.cnblo ...

  2. js in depth: closure function & curly function

    js in depth: closure function & curly function 闭包, 科里化 new js 构造函数 实例化, 不需要 new var num = new Ar ...

  3. 【目标检测】用Fast R-CNN训练自己的数据集超详细全过程

    目录: 一.环境准备 二.训练步骤 三.测试过程 四.计算mAP 寒假在家下载了Fast R-CNN的源码进行学习,于是使用自己的数据集对这个算法进行实验,下面介绍训练的全过程. 一.环境准备 我这里 ...

  4. django中间件介绍

    在学习django中间件之前,先来认识一下django的生命周期,如下图所示: django生命周期:浏览器发送的请求会先经过wsgiref模块处理解析出request(请求数据)给到中间件,然后通过 ...

  5. ffmpeg:为视频添加静态水印

    在ffmpeg中,添加水印需要用overlay滤镜,这是一个复杂滤镜,因为它需要两个输入,默认第一个输入是主画面,第二输入为水印,先执行一个简单的看看. 下面有两个文件,一个是可爱的大雄兔,一个是可爱 ...

  6. Anno&Viper -分布式锁服务端怎么实现

    1.Anno简介 Anno是一个微服务框架引擎.入门简单.安全.稳定.高可用.全平台可监控.依赖第三方框架少.底层通讯RPC(Remote Procedure Call)采用稳定可靠经过无数成功项目验 ...

  7. 详细探秘Linux 和 Window 双系统访问Windows 磁盘需要输入密码问题解决过程分析

    将要讲很多的内容真正产生作用的配置就只有下面这一句而已.如果你只是想要解决问题看这一句就行了,后面都没有必要在看下去了. 将allow-active标签中的auth_admin_keep 改为 yes ...

  8. 又长又细,万字长文带你解读Redisson分布式锁的源码

    前言 上一篇文章写了Redis分布式锁的原理和缺陷,觉得有些不过瘾,只是简单的介绍了下Redisson这个框架,具体的原理什么的还没说过呢.趁年前项目忙的差不多了,反正闲着也是闲着,不如把Rediss ...

  9. 【Notes_8】现代图形学入门——几何(基本表示方法、曲线与曲面)

    几何 几何表示 隐式表示 不给出点的坐标,给数学表达式 优点 可以很容易找到点与几何之间的关系 缺点 找某特定的点很难 更多的隐式表示方法 Constructive Solid Geometry .D ...

  10. Linux文件常用指令

    目录 Linux文件常用指令 1.pwd 显示当前目录 2.cd 切换目录 3.mkdir 创建目录 4.touch 修改或创建文件 5.ls 显示目录下的内容 6.cat 查看文件信息 7.echo ...