Java 中的三大特性(超详细篇)

Java 中的三大特性(超详细篇)简介Java的三大特性:封装、继承、多态乍一听,好像很高大上,其实当你真正用的时候,会发现高大上的还在后面呢。。。热身在正式讲解三大特性之前,先普及几个知识1.访问权限修饰符Java中关于访问权限的四个修饰符,表格如下 private friendly(默认) protected public 当前类访问权限 √ √ √ √ 包访问权限 × √ √ √ 子类访问权限 × × √

大家好,又见面了,我是你们的朋友全栈君。

简介

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的默认方法和静态方法
  • 等等等
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/156343.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • c++迭代器遍历list集合_list迭代器遍历删除对象

    c++迭代器遍历list集合_list迭代器遍历删除对象迭代器可用于遍历ArrayList。如果ArrayList中有更多元素,则hasNext()方法返回true,否则返回false。next()方法返回ArrayList中的下一个元素,如果没有下一个元素,则引发异常NoSuchElementException。演示该程序的程序如下。示例importjava.util.ArrayList;importjava.util.Iterator;publ…

  • 10种不同画法画平行线怎么画_平行线的标准画法

    10种不同画法画平行线怎么画_平行线的标准画法数学活动《平行线的画法》活动目标:1、通过活动探究巩固熟悉平行线的判定方法,能正确地画出已知直线的平行线,并能用平行线的判定方法说明依据。2、采用多种方法画平行线,让学生从不同的角度来寻求解决问题的方案.让学生在动手操作中进一步学会画平行线,培养一定的操作技能。3、通过观察、动手操作、推断、交流等数学活动,发展空间观念,体验知识来源于生活,又服务于生活。活动重点:运用所学的知识熟练的画出平行线。活…

  • css基础选择器有哪些

    css基础选择器有哪些css基础选择器有哪些(熟记)一、选择器作用:规范了页面中哪些元素能够定义好样式,同时也能帮助我们去二、选择器分类1.通用选择器(只能放在样式表)1.作用:匹配页面上的所有元素 2.语法:* 3.*{ 属性:属性值; }2.元素择器1.作用:匹配页面上某个元素的样式2.语法: 3.元素名{ 属性:属性…

  • 解决Execution failed for task ‘:app:checkDebugDuplicateClasses‘.

    解决Execution failed for task ‘:app:checkDebugDuplicateClasses‘.

  • linux下DNS配置及域名解析服务

    linux下DNS配置及域名解析服务一.DNS(DomainNameSystem,域名系统)    因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。DNS协议运行在UDP协议之上,使用端口号53。    DNS服务器将域名解析出来,找到域名对应的ip地址的…

  • php 0xffffffff,[已解决]怎么随机出0xFF000000 – 0xFFFFFFFF 之间的数?

    php 0xffffffff,[已解决]怎么随机出0xFF000000 – 0xFFFFFFFF 之间的数?importwin.ui;importgdip;//导入GDI+库importmath;/*DSG{{*/varwinform=..win.form(bottom=399;parent=…;right=599;text=”aardioForm”)winform.add(button={bottom=363;text=”button”;left=423;top=318;z=1…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号