Java基础语法——面向对象

Posted RAIN 7

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础语法——面向对象相关的知识,希望对你有一定的参考价值。

文章目录

前言

千呼万唤始出来,Java的这篇面向对象介绍终于出炉… 在草稿箱积压了半年的存货终于发布。。。。

这篇作为Java基础语法专栏的最后一块补全。有兴趣的大家可以订阅我的专栏JavaSE 小白的进阶之路(为了分享给读者,专栏系列文章全部免费阅读,请大家多多关注!)

Java是一门面向对象的语言,面向对象编程有三大特性: 封装、继承、和多态,在上一篇文章中我们呢已经介绍了 封装的具体特性,而在这篇博客中,我们那就将面向对象的其他特性 一 一介绍.

一、包

包 (package) 是组织类的一种方式.

使用包的主要目的是保证类的唯一性.

而使用包的目的是为了什么?

我们来举一个例子:

在公司中,我们肯定要协同开发项目,一个游戏肯定不能是1个人写,肯定是5个人、10个人、n个人一块写.

那么有一种情况是什么,我们自己写了一个类 起名叫Test,你的同事张三也写了一个类 也起名叫Test,那么这种情况我们想一想,在IDEA 当中 src 目录下不能有两个相同类名的Java文件,为了解决这种冲突,我们就引入了一个概念——包。


将类放到包中


1.基本规则

1.在文件的最上方加上一个 package 语句指定该代码在哪个包中.

2.包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).

3.包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.

4.如果一个类没有 package 语句, 则该类被放到一个默认包中.

2.操作步骤

(1) 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包

(2) 在弹出的对话框中输入包名, 例如 com.bit.demo1

(3) 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可.

(4) 同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句

3.导入包中的类

Java 中已经提供了很多现成的类供我们使用.

例如:

可以使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类.

但是这种写法比较麻烦一些,我们通常不这样使用, 可以使用 import 语句导入包.


IDEA 通常在我们输入的时候直接就可以回车导入包了,十分方便。

4.常见的系统包

那么我们常见的有哪些包呢?


我们需要注意的是:

java.lang 这个包不需要我们手动导入,在JDK 中直接自动导入

java.util 这个包中 Java 提供了很多的工具类 ,也非常的重要。

5.包的访问权限

我们已经了解了类中的 public 和 private.

private 中的成员只能被类的内部使用.

如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用.

具体的访问的关键字修饰符 会在下文中继承的 protected 关键字中详细解释。


二、继承


1.引入

假设我们有一个类 Animal , 他有自己 的 name 属性,sleep()、eat() 方法。

那么我们还有 两个类

cat类 ,也有 name 属性,eat()、sleep方法。

Bird类 除了由以上的属性方法,它还有 自己的 fly()方法。


这个代码我们发现其中存在了大量的冗余代码.

仔细分析, 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系:

这三个类都具备一个相同的 eat 、sleep 方法, 而且行为是完全一样的.

这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.

从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义).

此时我们就可以让 Cat 和 Bird 分别继承 Animal 类, 来达到代码重用的效果.


继承呢是两个类之间的关系,符合 is a 关系的两个类 ,我们就可以使用继承。

我们先简单的看一下继承的使用效果:

我们将Cat 类 用extends 关键字 来继承 Animal 类


我们再将Cat 类中与Animal 中相同的代码全部进行屏蔽

我们来看一下 继承之后的实现效果。

我们看到即使 Cat 没有相应的代码,但是他继承的类中有对应的属性方法,我们照样可以使用。

所以面试中有一个经典的问题:

子类继承了父类的什么?

答案就是:

子类继承了父类除构造方法之外的所有。

同时啊,在这个例子中,我们也可以看到 继承的优点:

可以达到代码的复用效果

我们还可以用一串代码来理解继承之间的关系

那么这组代码中,继承的关系如何体现呢?我们来看一下下图:

Base 中有一个 int a, Derive 继承了Base 的a 同时还有自己的 int b。

对于上面 Animal 这样被继承的类, 我们称为 父类 , 基类超类, 对于像 Cat 和 Bird 这样的类, 我们称为 子类, 派生类

和现实中的儿子继承父亲的财产类似, 子类也会继承父类的属性和方法, 以达到代码重用的效果


2.基本语法规则

1.Java当中只能继承一个父类

我们来看一下


我们写了两个父类 Animal 和A,让 Dog 类来同时继承这两个父类。

但是IDEA 进行报错,这说明啊,在Java当中 使用 extends 只能继承 一个类。所以,Java都是单继承,不是多继承。

2.子类会继承父类的所有 public 的字段和方法.

对于父类的 private 的字段和方法, 子类中是无法访问的.

之前我们说过,子类会继承父类除构造方法外的所有。那么private 修饰的内容呢?

我们看一下,

我们 A类继承了 Animal 这个父类,在 main 方法中调用 A.name 却发现 报错,不能访问,那么 父类中 private 修饰的内容真的被继承了吗?

我们还是那句话,子类继承了父类除构造方法外的全部, private 关键字修饰的内容 在此时只能被当前类中进行访问,不能被类外访问, 继承的子类中只是不能够访问,不是不能被继承

3.子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.

那我们再来看一下,我们将 父类Animal 进行改造一下


我们给Animal 类加上了 自己的构造方法,子类Cat不变,写完后我们会发现 ,Cat类进行报错。

如果我们不加构造方法,我们再来看一下。


这时 Cat 类没有问题。这个原因是什么呢?

子类在构造的时候,要先帮助父类进行构造

当我们没有加构造方法时,这是Animal类 默认带一个不带参数的构造方法,Cat继承的时候 也是给父类构造了一个没有参数的构造方法。

当我们加上一个 有参数的构造方法

  public Animal(String name)
      this.name = name;
  

这时子类 Cat 在继承父类 Animal 时,要先帮助 父类构造,那么父类怎么构造?

那么我们之前说过,构造一个对象只能通过 构造方法来构造,那我们就可以这样写。


这时则没有问题了!那么有同学就要问了 这个super 是个什么?---- 在这里我们就要介绍另一个关键字 super 了


关键字 super

super 关键字的认识

在面试中我们经常看到这样的问题,

请说出 this 关键字 与 super 关键字的区别?

那我们就通过 this、super 的对比来认识 super 关键字

我们先看一下 this 的使用

this 代表当前对象的引用

this在使用时有三种用法

this() ----- 调用本类其他的构造方法

this.data ----- 访问当前类当中的属性

this.func() ------ 访问当前类当中的其他成员方法

而 super 呢

super 代表父类对象的引用

super 与之对应也有三种用法

1.super() ---- 调用父类的构造方法

2.super.data -----访问父类当中的属性

3.super.func() ------ 访问父类当中的其他成员方法

在继承时,我们之前遇到的就是 super()


我们 类Cat 在继承Animal 时,在自己的构造方法当中 首先要帮助父类构造,于是就是用 super(),调用父类的构造方法。

我们通过这串代码来认识一下 构造的过程

我们来看一下运行的结果


子类在构造自己的时候,先帮助父类进行构造,然后才调用自己。

注意

还有一个非常重要的事项

我们来看一种情况:


在子类的构造方法里面, super() 必须放在第一行,否则会进行报错。

小结:

3.protected 关键字

刚才我们发现, 如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.

两全其美的办法就是 protected 关键字.

说到 protected 关键字,就不得不和其他的 访问权限修饰符 一块作比较。

我们在这之前的博客中已经介绍到了 public private 关键字,接下来我们就将 所有的访问关键字 列出、并说明其对应的权限。


我们先建立同一个包 、和不同包的情景


TestDemo1\\2\\3 都在 com.bit 这个包下, TestDemo 在 src 这个目录下、和前面不在同一个包内。

default

---- 默认访问权限、包访问权限

我们知道 default 英文是默认的意思,在Java当中就相当于在 变量的前面不加访问修饰限定符,如以下:

他的访问权限就是在同一个包里可以进行访问,那么 我们在TestDemo1 中创建这个类,那么在TestDemo1、2、3 当中都可以访问A中的a\\b

但是在 TestDemo 这个不同包下 则不能够访问 a、b

所以 default 叫做 包访问修饰限定符,在同一个包的同一个类中 或者不同类当中 都可以进行访问。

public

public 代表访问的最大权限,它可以在同一个包下进行访问,也可以在不同包下进行访问。

private

私有的

只能在同一个包当中 当前类中进行访问

通常用于封装类的属性方法等,private 通常体现在封装上。

protected

对于类的调用者来说, protected 修饰的字段和方法是不能访问的

对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的

protected 的访问权限 是 同一个包下的同一个类、不同类当中,不同包当中的子类,我们来理解一下

首先是同一个包下的不同类

我们在 包 com.bit 里创建一个 Animal 的类,并带有一个 protected 修饰 的 int a;

我们在 同一个包下的 TestDemo1 进行访问 Animal 中的a


成功访问,说明 protected 可以在被 同一个包中的不同类访问

我们在 不和 Animal 同一个包的 Test Demo 中进行访问 Animal 的 a

首先按照其规定,protected 可以访问不同包中的子类,所以 在Test Demo 创建一个方法继承Animal 这个类,进行访问。

我们发现编辑器报错,访问失败,那我们换一种方式。

我们用 super 进行访问父类对象发现可以实现。

所以 在访问不同包下的子类 时,我们要通过super 关键字来进行访问。

protected 关键字 主要体现在继承上。


访问修饰符的选择


什么时候下用哪一种呢?

我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public.

另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用).


小结

Java 中对于字段和方法共有四种访问权限

private: 类内部能访问, 类外部不能访问

default(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.

protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.

public : 类内部和类的调用者都能访问


4.更复杂的继承关系

刚才我们的例子中, 只涉及到 Animal, Cat 和 Bird 三种类. 但是如果情况更复杂一些呢?

针对 Cat 这种情况, 我们可能还需要表示更多种类的猫~

这个时候使用继承方式来表示, 就会涉及到更复杂的体系

如刚才这样的继承方式称为多层继承, 即子类还可以进一步的再派生出新的子类.

时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.

但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.

如果想从语法上进行限制继承, 就可以使用 final 关键字


5.final 关键字

曾经我们学习过 final 关键字, 修饰一个变量或者字段的时候, 表示 常量 (不能修改).

final int a = 10; 
a = 20; // 编译出错

final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.

一旦一个类 ,被 final 所修饰,那么这个类 必然不能被继承,所以 final 可以限制我们的继承关系。

我们来看一下这串代码

当我们想要限制一下 这么多的继承关系时,比如说,我们想要到 C类就停止继承了,我们就要使用关键字 final 来限制。


C 类 加上 final 之后我们就会发现之后继承 C 类的子类会进行报错。

由此我们可以引出 final 关键字的作用

final 关键字的功能是 限制 类被继承

“限制” 这件事情意味着 “不灵活”. 在编程中, 灵活往往不见得是一件好事. 灵活可能意味着更容易出错.

是用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的

我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承.


三、组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.

例如表示一个学校:

public class Student  
 ... 
 
public class Teacher  
 ... 
 
public class School  
 public Student[] students; 
 public Teacher[] teachers; 

组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.

这是我们设计类的一种常用方式之一.

组合表示 has - a 语义

在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师.

继承表示 is - a 语义

在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物.

大家要注意体会两种语义的区别.


四、多态


1.向上转型


(1)概念了解

讲到多态,那么向上转型、向下转型的知识就非常重要。

那我们在这里所说的 “上” 指的是父类,“下” 指的是 子类。

向上转型 就是 把子类 给了父类,那么到底是什么意思呢?

我们来用代码讲示例:


我们 new 了一个 Cat 对象,将 cat 赋值给了 Animal 这个类型的变量,其实这就是向上转型

有同学不禁问了,cat 的类型是Cat ,animal 的类型是 Animal,两边类型不一样啊,况且我们之前还说过,

a 的类型是 int ,b 的类型是 short,我们将 b 赋值给a ,就会发生类型不匹配,所以两个类型不同的变量是不可以赋值的。

但是上面的情况则是不同的, cat 、animal 这些都是引用, 我们将 cat 赋值给 animal,那么 animal 这个引用 引用了 cat这个引用引用的对象。 所以说 cat 赋给 animal 上面的情况是可以实现的,同时我们也可以将上面的代码缩写一下

这就是向上转型,也就是说 把子类对象的地址 给了 父类对象的引用

父类引用 引用了 子类对象

这样的引用方式,就叫做向上转型。

向上转型这样的写法可以结合 is - a 语义来理解.

例如, 我让我媳妇去喂圆圆, 我就可以说, “媳妇你喂小鸟了没?”, 或者 “媳妇你喂鹦鹉了没?”
因为圆圆确实是一只鹦鹉, 也确实是一只小鸟~~

为啥叫 “向上转型”?

在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表
示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , 表示往父类的方向转.

注意: 关于 UML 图的规则我们课堂上不详细讨论, 有兴趣的同学自己看看即可.

(2)发生时机

那么什么情况下 我们 可以用到 向上转型呢?

  1. 直接赋值

就是我们在上面举例的情况

直接把子类对象 赋值给了 父类引用,那么这种情况就叫做 直接赋值。

2.函数的传参

传参是个什么情况呢?给大家做一个示例

我们在函数的定义中要求传一个 Animal 类的参数,实际上却传了 Cat 类的变量,因为他们是有继承关系的,在传参的时候就发生了向上转型。

3.函数的返回值

我们再写一个函数 func2,func2函数的返回值 是Animal 类型的,但是 我们在函数内部 返回了 cat 类型,没有报错, 为什么可以返回成功呢? 原因就是 此时在这个过程中也是发生了向上转型。


小结:

向上转型 概念及发生的时机

掌握了这三种,以后我们就能够区分出来当前这个代码,它是否发生了向上转型。

现在向上转型我们都已经全部了解了,我们下面就来理解一下向下转型。


2.向下转型


向下转型就是 父类的引用赋值给子类,其他的和上面的向上转型都差不多

向下转型直接赋值时,父类赋值给子类,必须进行强制类型转换。


向下转型,使用的情况不多,因为使用向下转型非常的不安全


3.动态绑定


当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?

对前面的代码稍加修改, 给 Cat 类也加上同名的 eat 方法, 并且在两个 eat 中分别加上不同的日志


我们调用了 Animal 的eat方法,但是呢执行的是子类的eat方法。

因此, 在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.


4.方法重写


针对刚才的 eat 方法来说:

子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)

重写的规则

1.发生在父子类关系中,子类重写父类的方法

2.方法名相同

3.参数相同(类型个数都相同)

4.返回值类型 与父类返回值相同 或者是父类返回值的子类

4.对访问权限修饰符 子类重写的方法 访问权限必须大于等于 父类的

5.子类的方法抛出的异常 必须 小于等于 父类的原方法

每一点都很重要,强烈建议背诵记住!

注意点

之前我们说了final关键字的作用

如果父类的 方法被 final 修饰了,那么这个方法就不能被重写

方法重写也相当于对父类方法的继承,只继承了除方法体内容的其他,我们可以对内容进行修改。final的作用就是不能继承,所以final修饰的方法不能被重写。

static 修饰的方法是不能被重写的

静态方法是不能被重写的,静态资源属于类创建的所有对象,存在的意义就不是为了继承,任意一个这个类创建的对象都可以对其访问,但是如果某个对象对其重写,就不属于类所拥有的资源了,所以不能被重写。

5.理解多态


多态是一种思想,一个事务表现出不同的形态。

Java当中 发生多态的主要是

1.子类重写父类的方法,一个方法可以表现出不同的内容,这就发生了多态

2.多个类实现了同一个接口,对其方法进行实现。 和上面的一样。


这几个对象都调用了eat方法,执行结果


打印出了不同的内容,这就是一个多态思想的实现

一个方法实现了不同的内容


五、抽象类


abstract 修饰的一个类

语法的规则

包含抽象方法的类就叫做抽象类

abstract class Shape 
abstract public void draw();

在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 , 不能执行具体
代码).

对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.

注意点

抽象类不能直接实例化.

抽象方法不能是 private 的

抽象类的作用就是继承,如果设置成private私有的,子类还怎么进行访问和调用呢?

抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用

这个点等会要和接口进行区分,抽象类里面可以包含非抽象方法,可以包含字段,就是和普通的类使用差不多。

抽象方法的作用

抽象类存在的目的就是为了被其他类继承,位于继承树的顶端

抽象类里面可以有普通方法也可以有抽象方法,也可以定义普通成员变量,也可以定义静态常量

抽象类 可以有构造函数,但是构造函数不是用来创建对象的,而是其子类调用构造函数来完成抽象类的初始化操作。

只能单继承

继承抽象类 ,只能被子类使用extends关键字来继承

不能实例化

有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?

确实如此. 但是使用抽象类相当于多了一重编译器的校验.

很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就
相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.
充分利用编译器的校验, 在实际开发中是非常有意义的


六、接口


接口是抽象类的更进一步.

抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量

语法规则

interface IShape 
void draw();

class Cycle implements IShape 
@Override
public void draw() 
System.out.println("○");


public class Test 
public static void main(String[] args) 
IShape shape = new Rect();
shape.draw();


注意点

使用 interface 定义一个接口

接口中的方法一定是抽象方法, 因此可以省略 abstract

接口中的方法一定是 public, 因此可以省略 public
Cycle

使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”

在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.

接口不能单独被实例化.

扩展(extends) vs 实现(implements)

扩展指的是当前已经有一定的功能了, 进一步扩充功能.

实现指的是当前啥都没有, 需要从头构造出来.
接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).

jdk1.8之后 : 接口中可以有普通方法(非静态方法)和静态方法

默认方法是抽象方法 public abstract,抽象方法是不可以有方法体的,加了default关键字才能实现普通方法

默认字段是常量字段 public static final

接口命名规范

1.我们创建接口的时候, 接口的命名一般以大写字母 I 开头.

2.接口的命名一般使用 “形容词” 词性的单词.

3.阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性

实现多个接口

有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.

然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果

interface  Iswim
   void swim();


interface Irun
    void run();


interface Ifly
    void fly();


class Animal implements Iswim,Irun,Ifly
    @Override
    public void swim() 
        System.out.println("可以游泳");
    

    @Override
    public void run() 
        System.out.println("可以飞");
    

    @Override
    public void fly() 
        System.out.println("可以飞翔");
    

这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而
只关注某个类是否具备某种能力.



这节就到这里吧,还有很多东西没有继续深入,大家可以在网上继续深入学习,希望大家多多练习!

以上是关于Java基础语法——面向对象的主要内容,如果未能解决你的问题,请参考以下文章

Java基础语法——面向对象

面向对象的定义,及基础语法

Java基础语法——面向对象

[Java]基础语法和面向对象

java中面向对象的理解

Java基础语法05-面向对象中