博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java——面向对象
阅读量:3956 次
发布时间:2019-05-24

本文共 31463 字,大约阅读时间需要 104 分钟。

文章目录

一. 面向对象编程

Java核心思想就是OOP

1. 面向过程&面向对象区别

1.1 从语言方面出发

对于C语言来说,是完全面向过程的

对于C++语言来说,是一半面向过程,一半面向对象。
对于Java语言来说,是完全面向对象的

1.2 面向过程的开发方式

面向过程思想(线性思维)

特点:

  • 步骤清晰简单,第一步做什么,第二步做什么…
  • 面向过程也注重实现功能的因果关系,因为A所以B,因为B所以C
  • 面对过程适合处理一些较为简单的问题

缺点:

  • 面向过程最主要是每一步与每一步的因果关系,其中A步骤因果关系关系到B步骤,A和B联合起来形成一个子模块,子模块和子模块之间又因为因果关系结合在一起,假设其中任何一个因果关系出现了问题(错误),此时整个系统的运转都会出现问题。

优点:(快速开发)

  • 对于小型项目(功能),采用面向过程的方式进行开发,效率较高。不需要前期进行对象的提取,模型的建立,采用面向过程方式可以直接开始干活。一上来直接写代码,编写因果关系,实现功能。

1.3 面向对象的开发方式

采用面向对象的方式进行开发,更符合人类的思维方式

人类就是以对象的方式认识世界的

面向对象思想(分类思维)

  • 面向对象就是将现实世界分割成不同的单元,然后每一个单元都实现成对象,然后给一个环境驱动一下,让各个对象之间协作起来形成一个系统。
  • 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
  • 面向对象适合处理复杂的问题,适合处理需要多人协作的问题!

对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,任然需要面向过程的思路去处理。

2. 什么是面向对象

2.1 面向对象常用术语

  • OOA:面向对象分析(Object-Oriented Analysis)
  • OOD:面向对象设计(Object-Oriented Design)
  • OOP:面向对象编程(Object-Oriented Programming)

当我们采用面向对象的方式贯穿整个系统的话,设计以上三个术语

实现一个软件的过程:分析——>设计——>编码

面向对象编程的本质就是:以类的方式组织代码,以对象的形式(封装)数据

2.1 面向对象核心思想:抽象

抽象

抽取现实中实体的共同点

三大特征

  • 封装

    将数据包装起来
  • 继承

    子类可继承父类,子类有了父类的所有东西
  • 多态

    同一个事物表现出不同的形态

从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象

从代码运行角度考虑是先有类后有对象。类是对象的模板

二. 分析类与对象

对象还有另一个名字:实例

2.1 类与对象的概念

类是一种抽象的数据类型,实际上在现实世界中是不存在的,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。它表示一个模板。使我们人类大脑进行思考、总结、抽象的一个结果

类本质:是现实世界当中某些事物具有共同特征,将这些特征提取出来形成的概念就是一个类,类就是一个模板

  • 动物、植物、手机、电脑
  • Person类、Pet类、Car类,这些类都是用来描述/定义某一类具体的事物应具备的特点和行为。

对象是抽象概念的具体实例,是实际存在的个体

  • 张三就是人的一个具体实例,张三家的狗旺财就是一个狗的一个具体实例
  • 能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念

在java语言中,要想得到对象,必须先定义类,对象是通过类这个模板创造出来的

  • 类就是一个模板:类中描述的是所有对象的共同特征信息
  • 对象就是通过类创建出的个体

2.2 类与对象中的术语

  • 类:不存在的,人类大脑思考总结一个模板(这个模板当中描述了共同特征)
  • 对象:实际存在的个体
  • 实例:对象还有另一个名字叫实例
  • 实例化:通过类这个模板创建对象的过程
  • 抽象:多个对象具有共同特征,进行思考总结抽取共同特征的过程
    在这里插入图片描述
    类 —— 【实例化】——>对象(实例)
    对象 —— 【抽象】——>类

2.3 java软件工程师在开发中起到的作用?

软件开发是为了解决现实生活中的问题。

首先java软件必须能够模拟现实世界

而软件是一个虚拟的世界,这个虚拟的世界需要和现实世界一一对应,这才叫做模拟。

在这里插入图片描述

2.4 类的定义

2.4.1 语法格式

[修饰符列表] class 类名{	//类体 = 属性 + 方法}

属性:在代码上以变量的形式存在(描述状态)

方法:描述动作/行为

2.4.2 为什么属性以变量形式存在?

因为属性对应的是数据,数据在程序中只能方法变量中

结论:属性其实就是变量

2.5 创建与初始化对象

使用new关键字创建对象

  • 通过类创建对象的过程我们可以成为:创建或者称为“实例化”

创建对象语法:

new 类名();	类是模板,通过一个类,是可以创建N多个对象的	new是一个运算符。专门负责创建对象

使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。

类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:

  • 必须和类的名字相同
  • 必须没有返回类型,也不能写void

访问创建的对象语法:

类型 变量名 = new 类名();	Student s1 = new Student();	和	int i =100;				解释一下:			i是变量名			int是变量的数据类型			100是具体的数据						s1是变量名(s1不能叫对象,s1只是一个变量名字)			Student是变量s1的数据类型(引用数据类型)			new Student()这是一个对象(学生类创建出来的学生对象)

例如:通过Person pudding = new Person();

  • 对象的属性 pudding.name
  • 对象的方法 pudding.sleep()

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.6 创建对象内存分析

2.6.1 对象和引用的区别

  • 对象是通过new出来的,在堆内存中存储
  • 引用是:但凡是变量,并且该变量中保存了内存地址指向了堆内存当中的对象
    在这里插入图片描述

2.6.2 画内存图分析代码

1. 画内存图注意事项

  • 第一:内存图上不要体现代码,内存上应该体现数据
  • 第二:内存图应该有先后顺序
  • 画内存图好处:程序不运行也知道代码,有助于你调试程序

2. 举例理解java内存关系

题一:创建两个用户对象

在这里插入图片描述

在这里插入图片描述

题二:用户类和地址类和测试类的关系
在这里插入图片描述
在这里插入图片描述
题三:宠物类和主类之间关系
在这里插入图片描述
在这里插入图片描述
栈里面:里面存放方法+变量的引用

堆里面:存放我们创建的对象

对象是啥:堆里面new出来的

引用是啥:是存储对象内存地址的一个变量

2.6.3 引用升级版

package com.object;public class T {
A o1; //成员变量中的实例变量。必须先创建对象,通过引用来访问 public static void main(String[] args) {
D d = new D(); C c = new C(); B b = new B(); A a = new A(); T t = new T(); //这里不写代码会出现空指针异常 c.o4 = d; b.o3 = c; a.o2 = b; t.o1 = a; //编写代码通过t来访问d中的i System.out.println(t.o1.o2.o3.o4.i); }}class A{
B o2;}class B{
C o3;}class C{
D o4;}class D{
int i;}结果为:0

2.6.4 空指针异常是怎么发生的

出现空指针异常的前提条件是?(本质)

“空引用”访问实例相关【对象相关】的数据时,都会出现空指针异常

  • 实例相关的包括:实例变量 + 实例方法
package com.object;/*关于垃圾回收器GC    在java语言中,垃圾回收器主要针对的是堆内存    当一个java对象没有任何引用指向该对象的时候    GC会考虑将该垃圾数据释放回收掉 */public class Test {
public static void main(String[] args) {
//创建客户对象 Customer c = new Customer(); System.out.println(c.id); c.id = 9479; System.out.println("客户的id是=" + c.id); c=null; //NullPointerException //编译器没问题,因为编译器只检查语法,编译器发现c是Customer类型 System.out.println(c.id); }}class Customer{
int id; //成员变量中的实例变量,应该先创建对象,通过引用.的方式访问}

在这里插入图片描述

空指针异常对实例变量和实例方法都有关系

package com.Method;public class NullPointerTest {
public static void main(String[] args) {
User u = new User(); System.out.println(u.id); //0 u.doSome(); //引用变成空null u = null; //id的访问,需要对象的存在 //System.out.println(u.id); 空指针异常 //一个实例方法的调用也必须有对象的存在 u.doSome(); }}//类 = 属性 + 方法//属性描述状态//方法描述行为动作class User{
//实例变量 int id; //实例方法(对象相关的方法,对象级别的方法,应该是一个对象级别的行为) //方法模拟的是对象的行为动作 public void doSome(){
System.out.println("do some!"); }}

三. 面向对象三大特性

封装、继承、多态

有了封装,才有继承,有了继承,才能说多态

3.1 封装(Encapsulation)

3.1.1 什么是封装?有什么用?

现实生活中有很多的例子都是封装的

例如:手机、电视、电脑、照相机这些都是外部有一个坚硬的壳儿。封装起来,保护内部的部件。保证内部的部件是安全的。另外封装了之后,对于我们使用者来说,我们看不见内部的复杂结构,我们也不需要关系内部有多么复杂,我们只需要操作外部壳儿上的几个按钮即可

封装的作用:

  • 第一个作用:保证内部结构的安全
  • 第二个作业:屏蔽复杂,暴露简单

在代码级别上,封装有什么用?

  • 一个类体当中的数据,假设封装之后,对于代码调用人员来说,不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问了。
  • 另外,类体中安全级别较高的数据封装起来,外部人员不能随意访问,来保证数据的安全性。

总结:该露的露,该藏的藏

  • 我们程序设计要追求"高内聚、低耦合"高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:尽暴露少量的方法给外部使用

封装(数据的隐藏)

  • 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。

属性私有,get/set

3.1.2 封装的好处

我们如下不使用封装机制,Person类的age属性对外暴露,可以在外部程序中随意访问,导致不安全。

在这里插入图片描述

怎么解决这个问题?

封装

3.1.3 进行封装

不再对外暴露复杂的数据,封装起来

  • 对外只提供简单的操作入口
  • 优点:第一数据安全了,第二调用者也方便了

代码的实现步骤:

  • 第一步:属性私有化(使用private关键字进行修饰)
  • 第二步:对外提供简单的操作入口(一个属性对外提供set和get方法)外部程序只能通过set方法修改,只能通过get 方法读取。可以在set方法中设立关卡来保证数据的安全性

在这里插入图片描述

java开发规范注意事项:

  • get方法要求:

    public 返回值类型 get+属性名首字母大写(无参){    }
  • set方法的要求:

    public void set+属性名首字母大写(有一个参数){  	xxx = 参数;  }

3.1.4 举例

package com.oop.demo04;//类     private:私有public class Student {
//属性私有 private String name;// 名字 private int id;//学号 private char sex;//性别 private int age; //提供一些可以操作这个属性的方法! //提供一些的public 的get、set方法 //get获得这个数据 public String getName(){
return this.name; } //set给这个数据设置值 public void setName(String name){
this.name = name; } public int getId() {
return id; } public void setId(int id) {
this.id = id; } public char getSex() {
return sex; } public void setSex(char sex) {
this.sex = sex; } public int getAge() {
return age; } public void setAge(int age) {
if (age>120 || age<0){
//不合法 this.age = 3; }else {
this.age = age; } }}主类package com.oop;import com.oop.demo04.Student;/*封装的意义1. 提高程序的安全性,保护数据2. 隐藏代码的实现细节3. 统一接口4. 系统可维护性增加了 */public class Application {
public static void main(String[] args) {
Student s1 = new Student(); s1.setName("pudding"); System.out.println(s1.getName()); s1.setAge(-1);//不合法的 System.out.println(s1.getAge()); }}

3.2 继承(extends)

3.2.1 什么是继承?有什么用?

继承:在现实世界当中也是存在的

例如:父亲很有钱,儿子不用努力也很有钱

继承的作用:

  • 基本作用:子类继承父类,代码可以得到复用(这个不是重要的作用,是基本的作用)
  • 主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制

继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模

没有使用继承的时候代码如下:package com.extend;//分析以下代码存在什么问题,// 代码臃肿。代码没有得到重复利用public class ExtendsTest01 {
public static void main(String[] args) {
Account act = new Account(); act.setActno("111111"); act.setBalance(10000); System.out.println(act.getActno()+"余额"+act.getBalance()); CreditAccount ca = new CreditAccount(); ca.setActno("222222"); ca.setBalance(-10000); ca.setCredit(0.99); System.out.println(ca.getActno()+"余额"+ca.getBalance()+",信誉度"+ca.getCredit()); }}//银行账户类//账户的属性:账户、余额class Account{
//属性 private String actno; private double balance; //构造方法 public Account() {
} public Account(String actno, double balance) {
this.actno = actno; this.balance = balance; } //setter and getter方法 public String getActno() {
return actno; } public void setActno(String actno) {
this.actno = actno; } public double getBalance() {
return balance; } public void setBalance(double balance) {
this.balance = balance; }}//其他类型的账户:信用卡账户//账号、余额、信誉度class CreditAccount{
//属性 private String actno; private double balance; private double credit; //构造方法 public CreditAccount() {
} //setter and getter方法 public String getActno() {
return actno; } public void setActno(String actno) {
this.actno = actno; } public double getBalance() {
return balance; } public void setBalance(double balance) {
this.balance = balance; } public double getCredit() {
return credit; } public void setCredit(double credit) {
this.credit = credit; }}
使用继承之后的代码如下:package com.Extends;//使用继承机制来解决代码复用问题public class ExtendsTest02 {
public static void main(String[] args) {
Account act = new Account(); act.setActno("111111"); act.setBalance(10000); System.out.println(act.getActno()+"余额"+act.getBalance()); CreditAccount ca = new CreditAccount(); ca.setActno("222222"); ca.setBalance(-10000); ca.setCredit(0.99); System.out.println(ca.getActno()+"余额"+ca.getBalance()+",信誉度"+ca.getCredit()); }}//银行账户类//账户的属性:账户、余额class Account{
//父类 //属性 private String actno; private double balance; //构造方法 public Account() {
} public Account(String actno, double balance) {
this.actno = actno; this.balance = balance; } //setter and getter方法 public String getActno() {
return actno; } public void setActno(String actno) {
this.actno = actno; } public double getBalance() {
return balance; } public void setBalance(double balance) {
this.balance = balance; }}//其他类型的账户:信用卡账户//账号、余额、信誉度class CreditAccount extends Account{
//子类 //属性 private double credit; //构造方法 public CreditAccount() {
} //setter and getter方法 public double getCredit() {
return credit; } public void setCredit(double credit) {
this.credit = credit; }}

3.2.2 继承的相关特性

  • ① B类继承A类,则称A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类

    class A{    }  	  class B extends A{    }
  • ② java中的继承只支持单继承,不支持多继承,C++中支持多继承,这也是java体现简单性的一点,换句话说,java中不允许这样写代码:

    class B extends A,C{    }  这是错误的
  • ③ 虽然java中不支持多继承,但有的时候会产生间接继承的效果,

    例如:class C extends B,class B extends A  也就是说,C直接继承B,C间接继承了A
    package com.Extends;public class ExtendsTest03 {
    }class A{
    }class B{
    }class C extends A{
    }class D extends B{
    }/*语法错误java中只允许单继承,不允许多继承。java是简单的。C++支持多重继承C++更接近显示一些,因为在现实世界中儿子同时继承父母两方的特性class E extends A,B{}*/class X{
    //X会默认继承Object类}class Y extends X{
    }//其实这也说明了Z是继承X和Y的//这样描述:Z直接继承了Y,Z间接继承了Xclass Z extends Y{
    }/* Z继承了Y Y继承了X X继承了Object Z对象具有Object对象的特征(基因) Object是所有类的超类,老祖宗,类体系结构的根 java这么庞大的一个继承结构,最顶点是:Object */
  • ④ java中规定,子类继承父类,除构造方法不能继承之外,剩下的都可以继承。但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中直接访问。可以通过直接的手段来访问。即通过get和set方法)

  • ⑤ java中的类没有显示的继承任何类,则默认继承Object类,Object类是java语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征

  • ⑥ 继承也有一些缺点,例如:CreditAccount类继承Account类会导致它们之间的耦合度非常高,Account类发生改变之后会马上影响到CreditAccount类

extends的意思是扩展。子类是父类的扩展

Java中类只有单继承,没有多继承

  • 继承是类和类之间的一种关系。除此之外,类111111和类之间的关系还有依赖、组合、聚合等
  • 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。
  • 子类和父类之间,从意义上讲应该具有"is a"的关系

3.2.3 举例理解

测试:子类继承父类之后,能使用子类对象调用父类方法吗?

  • 本质上子类继承父类之后,是将父类继承过来的方法归为自己所有。
  • 实际上调用的也不是父类的方法,是它子类自己的方法(因为已经继承过来了)
package com.Extends;public class ExtendsTest04 {
public static void main(String[] args) {
Cat c = new Cat(); c.move(); }}//父类//class Animal extends Objectclass Animal{
//名字(先不封装) String name = "pudding"; //提供一个动物移动的方法 public void move(){
System.out.println(name + "正在移动!"); }}// Cat子类// Cat继承Animal,会将Animal中所有的全部继承过来class Cat extends Animal{
}

理解子类继承父类的过程

在这里插入图片描述

package com.oop.demo05;//在Java中所有的类,都默认直接或间接继承Object类//person 人  父类public class Person /*extends Object*/{
public Person() {
System.out.println("Person无参执行"); } protected String name = "pudding"; public void print(){
System.out.println("Person"); }}package com.oop.demo05;//学生 is 人 派生类/子类//子类继承了父类,就会拥有父类的全部方法!public class Student extends Person{
public Student() {
//隐藏代码:调用了父类的无参构造super(); super(); //默认调用父类的构造器,必须要在子类构造器的第一行 System.out.println("Student无参执行"); } private String name = "张三"; public void print(){
System.out.println("Student"); } public void test(String name){
System.out.println(name); System.out.println(this.name); System.out.println(super.name); } public void test1(){
print(); this.print(); super.print(); }}main方法package com.oop;import com.oop.demo05.Person;import com.oop.demo05.Student;public class Application {
public static void main(String[] args) {
Student student = new Student(); student.test("李四"); student.test1(); }}

3.2.4 实际开发中,满足什么条件时,可以使用继承?

凡是采用"is a"能描述的,都可以继承

IS-A 就是说:一个对象是另一个对象的一个分类。

例如:

Cat is a AnimalDog is a Animal

假设以后的开发中有一个A类,有一个B类,A类和B类确实也有重复的代码,那么他们两个之间就可以继承吗?不一定,还是要看一看它们之间是否能够使用is a来描述。

3.2.5 理解Object类

任何一个类,没有显示继承任何类,默认继承Object,那么Object类当中有哪些方法呢?老祖宗为我们提供了哪些方法?

先模仿后超越

java为什么比较好学呢?

是因为java内置了一套庞大的类库,程序员不需要从0开始写代码,程序员可以基于这套庞大的类库进行"二次开发"。(开发速度较快,是因为JDK内置的这套库实现了很多基础的功能)例如:String是SUN用来编写字符串的类System是SUN用来编写系统类。这些类都可以直接拿来使用

理解如下代码:

  • System.out.println(“Hello World!”);
  • System.out中,out后面没有小括号,说明out是变量名
  • 另外System是一个类名,直接使用类名System.out,说明out是异构静态变量
  • System.out返回一个对象,然后采用"对象. "的方式访问println()方法。
    在这里插入图片描述
package com.多态的基础语法;public class Test {
//静态变量 static Student stu = new Student(); public static void main(String[] args) {
//拆分为两行 Student s = Test.stu; //Test.stu返回的是一个学生对象 s.exam(); //学生对象里面有一个exam考试的方法 //合并代码 Test.stu.exam(); System.out.println("Hello World!"); }}class Student{
//实例方法 public void exam(){
System.out.println("考试...."); }}

研究了Object类中,其中toString()方法我们进行测试。

  • 发现System.out.println()引用;
  • 当直接输出一个"引用"的时候,println()方法会自动调用"引用."toString(),然后输出toString()方法的执行结果
package com.Extends;public class ExtendsTest05 {
//ExtendsTest05默认继承Obeject //ExtendsTest05类当中是有toString()方法 //不过toString()方法是一个实例方法,需要创建对象才能调用 /* public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } */ public static void main(String[] args) {
ExtendsTest05 et = new ExtendsTest05(); String retValue = et.toString(); //154617c可以等同看做对象在堆内存当中的内存地址 //实际上是内存地址经过"哈希算法"得出的十六进制结果 System.out.println(retValue); //结果:com.Extends.ExtendsTest05@154617c //创建对象 Product pro = new Product(); System.out.println(pro.toString()); //com.Extends.Product@a14482 System.out.println(pro); //默认调用了toString方法了,结果:com.Extends.Product@a14482 }}class Product{
/* public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } */}

toString()方法的作用是什么

  • 作用:将java对象转换为字符串的形式

Object类中toString()方法默认实现是什么?

public String toString() {    return getClass().getName() + "@" + Integer.toHexString(hashCode());}

toString:方法名的意思是转换成String

  • 含义:调用一个java对象的toString()方法就可以将该java对象转换为字符串的表示形式。

3.2.6 super和this的总结

注意:显示调用父类构造器,即写super();方法的话,必须写在子类构造方法第一行,调用自己的构造器也是一样的,this();

如果写了有参构造之后,无参构造就没有了,想要使用的话,必须显示定于,所以重新写了有参构造最好加上无参构造

super总结:

  1. super调用父类的构造方法,必须在构造方法的第一个
  2. super必须只能出现在子类的方法或者构造方法中!
  3. super和this不能同时调用构造方法!

VS this总结

  1. 代表的对象不同:

    this:代表本身调用者这个对象super:代表父类对象的引用
  2. 前提不同:

    this:没有继承也可以使用 super:只能在继承条件下才可以使用
  3. 构造方法区别:

    this();	本类的构造方法 super(); 父类的构造

3.2.7 方法的重写(方法覆盖)

1. 什么时候我们考虑使用方法重写(Override)

子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权对这个方法进行重写编写,有必要进行"方法的覆盖"。

父类的功能,子类不一定需要,或者不一定满足

package com.Override;/*当前程序的问题    鸟儿在执行move()方法的时候,最好输出结果是"鸟儿在飞翔"    但是当前的程序在执行move()方法的时候输出的结果是:动物在移动    很显然Bird子类从Animal父类中继承过来的move()方法已经无法满足子类的业务需求 */public class OverrideTest01 {
public static void main(String[] args) {
//创建鸟对象 Bird b = new Bird(); //让鸟移动 b.move(); }}//父类class Animal{
//移动 public void move(){
System.out.println("动物在移动"); }}//子类class Bird extends Animal{
//子类继承父类中,有一些行为可能不需要改进,有一些行为必须改进 //因为父类中继承过来的方法已经无法满足子类业务的需求 //鸟儿在移动的时候希望输出鸟儿在飞翔}

更深的理解一下

public class Application {
//静态的方法和非静态的方法区别很大 //静态方法 //方法的调用只和左边,定义的数据类型有关 //非静态方法 //重写 public static void main(String[] args) {
A a = new A(); a.test(); //A类 //父类的引用指向了子类 B b = new A(); //子类重写了父类的方法 b.test(); //B类 /* b是A new出来的对象,因此调用了A的方法 没有static时,b调用的是对象的方法,而b是A类new出来的 有static时,b调用了B类的方法,因为b是用b类定义的 因为静态方法是类的方法,而非静态是对象的方法 */ }}

在这里插入图片描述

对比一下方法重载。

什么时候考虑使用方法重载overload?

  • 当一个类当中,如果功能相似的话,建议将名字定义的一样,这样代码美观,并且方便编程

什么条件满足之后能够构成方法重载overload?

  • 条件一:在同一个类当中
  • 条件二:方法名相同
  • 条件三:参数列表不同(个数、顺序、类型)

方法重载和方法覆盖的区别?

  • 方法重载发生在同一个类当中
  • 方法覆盖发生在具有继承关系的父子类之间
  • 方法重载是一个类中,方法名相同,参数列表不同
  • 方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致

2. 什么是方法重写

方法覆盖,就是将继承过来的那个方法给覆盖掉了。继承过来的方法没了

结论:

  • 当子类对父类继承过来的方法进行"方法覆盖"之后,子类对象调用该方法的时候,一定执行覆盖之后的方法。
package com.Override;public class OverrideTest01 {
public static void main(String[] args) {
//创建鸟对象 Bird b = new Bird(); //让鸟移动 b.move(); //鸟儿在飞翔 Cat c = new Cat(); c.move(); //猫在走猫步 }}//父类class Animal{
//移动 public void move(){
System.out.println("动物在移动"); }}//子类class Bird extends Animal{
//对move方法进行方法覆盖,方法重写,override //最好将父类中的方法原封不动的复制过来 public void move(){
System.out.println("鸟儿在飞翔"); }}class Cat extends Animal{
//方法重写 public void move(){
System.out.println("猫在走猫步"); }}

3. 代码怎么编写,在代码级别上构成方法覆盖?

  • 条件一:重写需要两个类有继承关系,子类重写父类的方法!
  • 条件二:重写之后的方法和之间的方法要保证,子类的方法和父类必须要一致,方法体不同!
    1. 方法名必须相同
    2. 参数列表必须相同
    3. 返回值类型必须相同
  • 条件三:访问权限只能更高,不能更低
    1. 修饰符:范围可以扩大但不能缩小:public>protected>default>private
  • 条件四:重写之后的方法不能比之前的方法抛出更多的异常,可以更少
    1. 抛出的异常:范围,可以被缩小,但不能扩大,ClassNotFoundException --> Exception(大)

4. 方法覆盖的注意点

  • 注意一:方法覆盖只是针对于方法,和属性无关
  • 注意二:私有方法无法覆盖
  • 注意三:构造方法不能被继承,所以构造方法也不能被覆盖
  • 注意四:方法覆盖只是针对于"实例方法","静态方法"覆盖没有意义

注意二举例理解:

//私有方法不能覆盖public class OverrideTest05 {
//私有方法 private void doSome(){
System.out.println("OverrideTest05's private method"); } public static void main(String[] args) {
OverrideTest05 ot = new OverrideTest05(); ot.doSome(); //OverrideTest05's private method }}//子类class T extends OverrideTest05{
//尝试重写父类中的dosome方法 //访问权限不能更低,可以更高 public void doSome(){
System.out.println("T's public dosome method"); }}

注意三举例理解:

/*   1. 方法覆盖需要和多态机制联合起来使用才有意义      Animal a = new Cat();      a.move();      要的是什么效果?         编译的时候move()方法是Animal的         运行的时候自动调用到子类重写move()方法上      方法覆盖和多态不能分开    2. 静态方法存在方法覆盖吗?       多态自然和对象有关系了       而静态方法的执行不需要对象       所以一般情况下,我们会说静态方法不存在方法覆盖 */public class OverrideTest04 {
public static void main(String[] args) {
//静态方法可以使用"引用."来调用 //虽然使用"引用."来调用,但是和对象无关 Animals a = new Dog(); a.dosome(); //Animal的dosome方法执行 Dog.dosome();//Dog的dosome方法执行 //静态方法和对象无关 //虽然使用"引用."来调用。但是实际运行的时候还是:Animal.dosome() }}class Animals{
//父类的静态方法 public static void dosome(){
System.out.println("Animal的dosome方法执行"); }}class Dog extends Animals{
//尝试在子类当中对父类的静态方法进行重写 public static void dosome(){
System.out.println("Dog的dosome方法执行"); }}

在方法覆盖中,关于方法的返回值类型。什么条件满足之后,会构成方法的覆盖呢?

  • 发送具有继承关系的两个类之间
  • 父类中的方法和子类重写之后的方法:具有相同的方法名、参数列表相同、返回值类型相同

学习了多态机制之后:"相同的返回值类型"可以修改一下吗?

  • 对于返回值类型是基本数据类型来说,必须一致
  • 对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小(但是意义不大,实际开发中没人这样写)
    在这里插入图片描述
    在这里插入图片描述

5. 方法覆盖的经典案例

package com.Override;public class OverrideTest02 {
public static void main(String[] args) {
// Chinese p1 = new Chinese("张三"); 错误原因,构造方法无法继承,子类没有带参数的构造方法 Chinese p1 = new Chinese(); p1.setName("张三"); p1.speak(); American p2 = new American(); p2.setName("Jack"); p2.speak(); }}class Person{
//属性 private String name; //构造 public Person() {
} public Person(String name) {
this.name = name; } //setter and gett public String getName() {
return name; } public void setName(String name) {
this.name = name; } //人都会说话 public void speak(){
System.out.println(name + "正在说话"); }}//中国人class Chinese extends Person{
//中国人说汉语,需要对speak()方法进行重写 public void speak(){
System.out.println(this.getName() + "说中文"); }}//美国人class American extends Person{
//美国人说英语,需要对父类speak重写 @Override public void speak() {
System.out.println(getName() + "说英语");; }}

toString()方法存在的作用就是:将java对象转换成字符串形式

toString()方法给的默认实现不够用,我们就重新覆盖toString()方法

大多数的java类toString()方法都需要覆盖的,因为Object类中提供的toString()方法输出的是一个java对象的内存地址

至于toString()方法具体怎么进行覆盖?

  • 格式可以自己定义,或者听需求
package com.Override;public class overrideTest03 {
public static void main(String[] args) {
//创建一个日期对象 MyDate t1 = new MyDate(); //调用toString()方法(将对象转换成字符串形式) //重写MyDate中toString()方法之前的结果 //System.out.println(t1.toString()); //com.Override.MyDate@154617c //重写MyDate中toString()方法之后的结果 System.out.println(t1.toString()); //1970年1月1日 //当输出一个引用的时候,println方法会自动调用引用的toString方法 System.out.println(t1); //1970年1月1日 }}//日期类class MyDate{
private int year; private int month; private int day; public MyDate() {
this(1970,1,1); } public MyDate(int year, int month, int day) {
this.year = year; this.month = month; this.day = day; } public int getYear() {
return year; } public void setYear(int year) {
this.year = year; } public int getMonth() {
return month; } public void setMonth(int month) {
this.month = month; } public int getDay() {
return day; } public void setDay(int day) {
this.day = day; } //从Object类中继承的那个toString()方法以及无法满足我业务需求了 //我在子类MyDate中有必要对父类的toString()方法进行覆盖/重写 //我的业务要求是:调用toString()方法转换时候,希望出现结果:xxxx年xx月xx日格式 public String toString(){
return year + "年" + month + "月" + day + "日"; }}

6. 重写与重载之间的区别

  • 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载 (Overloading)。
  • 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写 (Overriding)。
  • 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
    在这里插入图片描述

3.3 多态

3.3.1 多态的基础语法

1. 向上转型和向下转型的概念

学习多态基础语法之前,我们需要普及两个概念

  • 第一个:向上转型(upcasting):子—>父(自动类型转换)

    Animal a = new Cat();
  • 第二个:向下转型(downcasting):父—>子(强制类型转换,需要加强制类型转换符)

    Cat c = (Cat)a;

注意:java中允许向上转型,也允许向下转型

无论是向上转型,还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错

注意:在以后的工作中,要专业一点,说向上转型和向下转型,不要说自动类型转换,也不要说强制类型转换,因为自动类型转换和强制类型转换是使用在基本数据类型方面的,在引用类型转换这里只有向上和向下转型

在这里插入图片描述

2. 类型转换

总结:

  1. 存在条件:父类的引用指向子类的对象
  2. 把子类转换为父类,向上转型
  3. 把父类转换为子类,向下转型,强制转换
  4. 方法的调用,减少重复的代码

在这里插入图片描述

3.3.2 多态的概述

即同一个方法可以根据发送对象的不同而采用多种不同的行为方式

一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类、有关系的类)

多态存在条件

  • 有继承关系
  • 子类重写父类方法
  • 父类引用指向子类对象

注意:多态是方法的多态,属性没有多态性

/*多态注意事项:  1. 多态是方法的多态,属性没有多态  2. 父类和子类,有联系  3. 多态存在的条件:继承关系,方法需要重写,父类引用指向子类对象!        Father f1 = new Son();     不能被重写的方法     1. static静态的方法,属于类,不属于实例     2. final 常量     3. private方法 */

1. 什么是多态

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

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

比如我们说"宠物"这个对象,它就有很多不同的表达或实现,比如有小猫、小狗、蜥蜴等等。那么我到宠物店说"请给我一只宠物",服务员给我小猫、小狗或者蜥蜴都可以,我们就说"宠物"这个对象就具备多态性。

多态指的是:多种形态,多种状态

  • 包括编译阶段和运行阶段,编译和运行有两个不同状态
  • 编译阶段(静态绑定):绑定父类的方法
  • 运行阶段(动态绑定):动态绑定子类型对象的方法

多态的典型代码:父类型引用指向子类型对象(java中允许这样写代码)

Animal a = new Cat();a.move();编译的时候编译器发现a的类型是Animal,所以编译器会去Animal类中找move()方法找到了,绑定,编译通过。但是运行的时候和底层堆内存当中实际对象有关真正执行的时候会自动调用"堆内存中真实对象"的相关方法。

注意:

java中只有"类名"或者"引用"才能去"点"

  • 类名 .
  • 引用 .

以下的类都用于举例理解多态,而创建的类

在这里插入图片描述
多态的向上转型理解:

public class Test01 {
public static void main(String[] args) {
Animal a1 = new Animal(); a1.move(); Cat c1 = new Cat(); c1.move(); Bird b1 = new Bird(); b1.move(); /* 代码可以如下这样写吗? 1. Animal和Cat之间有继承关系吗? 有的 2. Animal是父类,Cat是子类 3. Cat is a Animal 这句话能不能说通? 能 4. 经过测试得知java中支持这样的一个语法 父类型的引用允许指向子类型的对象 Animal a2 = new Cat() a2就是父类型的引用 new Cat()是一个子类型的对象 允许a2这个父类型引用指向子类型的对象 */ Animal a2 = new Cat(); Animal a3 = new Bird(); /* 什么是多态? 多种形态,多种状态 分析:a2.move(); java程序分为编译阶段和运行阶段 先来分析编译阶段: 对于编译器来说,编译器只知道a2的类型是Animal, 所以编译器在检查语法的时候,会去Animal.class字节码文件去找 move()方法,找到了,绑定上move()方法, 编译通过,静态绑定成功。(编译阶段属于静态绑定) 再来分析运行阶段: 运行阶段的时候,实际上在堆内存中创建的java对象是Cat对象, 所以move的时候,真正参与move的对象是一只猫, 所以运行阶段会动态执行Cat对象的move()方法。 这个过程属于运行阶段绑定(运行阶段绑定属于动态绑定) 多态表示多种形态: 编译的时候一种形态 运行的时候一种形态 */ //调用a2的move()方法 a2.move(); //cat在走猫步 //调用a3的move()方法 a3.move(); //鸟儿在飞翔 }}

多态的向下转型理解

public class Test01 {
public static void main(String[] args) {
Animal a5 = new Cat(); //底层对象是一只猫 //a5.catchMouse(); 错误 //因为:编译器只知道a5的类型是Animal,去Animal.class文件夹中找catchMouse()方法 //结果没有找到,所以静态绑定失败,编译报错,无法运行(语法不合法) //当我非要调用catchMouse()方法怎么办 //这个时候必须使用"向下转型了"(强制类型转换) Cat x = (Cat)a5; //因为a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没报错 x.catchMouse(); //猫正在抓老鼠 //向下转型有风险吗? Animal a6 = new Bird(); //表面上a6是一个Animal,运行的时候实际上是一个鸟儿 /* 分析以下程序 编译器检测到a6这个引用是Animal类型, 而Animal和Cat之间存在继承关系,所以可以向下转型 编译没毛病 运行阶段,堆内存实际创建的对象是:Bird对象 在实际运行过程中,拿着Bird对象转换成Cat对象就不行了 因为Bird和Cat之间没有继承关系 运行出现了异常,这个异常非常的重要 java.lang.ClassCastException:类型转换异常 java.lang.ClassCastException:空指针异常 */ System.out.println(a6 instanceof Cat); //false if (a6 instanceof Cat){
//如果a6是一只Cat Cat y = (Cat)a6; //再进行强制类型转换 y.catchMouse(); } }}

2. 举例理解

在这里插入图片描述

主方法public class Application {
public static void main(String[] args) {
//一个对象的实际类是确定的 //new Student() //new Person //可以指向的引用类型就不确定了 //Student能调用的方法都是自己的或者继承父类的 Student s1 = new Student(); //父类的引用指向了子类 //Person父类型,可以指向子类,但是不能调用子类独有的方法 Person s2 = new Student(); Object s3 = new Student(); //对象能执行哪些方法,主要看对象左边的类型,和右边关系不大 //如果对象左边类型里面没有的方法,这个对象是无法调用的 s2.run();//子类重写了父类的方法,执行了子类的方法 s1.run(); }}

3. 什么时候必须进行向下转型?

需要调用或执行子类对象特有的方法,才需要强制类型转换(向下转型)

  • 注父类没有独有的方法,因为父类的方法都会被继承

向下转型有风险吗?

容易出现ClassCastException(类型转换异常)

怎么避免这个风险?

instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象是否为某一种类型

养成好习惯,向下转型之前一定要使用instanceof运算符进行判断

3.3.3 instanceof 运算符的运用

作用:判断两个类之间是否存在父子关系

  • 第一:instanceof可以在运行阶段动态判断引用执行的对象的类型

  • 第二:instanceof的语法:(引用 instanceof 类型)

  • 第三:instanceof运算符的运算结果只能是:true/false

  • 第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。

    假设(c instanceof Cat)为true表示:  	c引用指向的堆内存中的java对象是一个Cat  	  假设(c instanceof Cat)为false表示:  	c引用指向的堆内存中的java对象不是一个Cat

程序员要养成一个好习惯:

  • 任何时候,任何地点,对类型进行向下转换时,一定要使用instancof运算符进行判断。
  • 这样可以很好的避免:ClassCastException
/*这个代码的疑问?    肉眼可以观察到底层是new Bird()还是new Cat()    我们为什么还要进行instanceof的判断呢    原因:        你以后可能肉眼看不到 */public class Test02 {
public static void main(String[] args) {
Animal x = new Bird(); Animal y = new Cat(); if (x instanceof Bird){
Bird b = (Bird)x; b.sing(); //鸟儿在歌唱 } else if (x instanceof Cat){
Cat c = (Cat)x; c.catchMouse(); } if (y instanceof Bird){
Bird b = (Bird)y; b.sing(); } else if (y instanceof Cat){
Cat c = (Cat)y; c.catchMouse(); //猫正在抓老鼠 } }}

为什么需要上述这样写能,不麻烦吗?

解释如下:
在这里插入图片描述

还有一个例子看一看:

public class Application {
public static void main(String[] args) {
//Object > String //Object > Person > Teacher //Object > Person > Student Object object = new Student();// System.out.println(X instanceof Y); //编译能不能通过,看X和Y有没有父子关系 System.out.println(object instanceof Student); //true System.out.println(object instanceof Person); //true System.out.println(object instanceof Object); //true System.out.println(object instanceof Teacher); //false System.out.println(object instanceof String); //false System.out.println("============================="); Person person = new Student(); System.out.println(person instanceof Student); //true System.out.println(person instanceof Person); //true System.out.println(person instanceof Object); //true System.out.println(person instanceof Teacher); //false// System.out.println(person instanceof String); //编译报错 System.out.println("============================="); Student student = new Student(); System.out.println(student instanceof Student); //true System.out.println(student instanceof Person); //true System.out.println(student instanceof Object); //true// System.out.println(student instanceof Teacher); //编译报错// System.out.println(person instanceof String); //编译报错 }}

3.3.4 多态在开发中的作用

1. 通过以下案例理解多态在开发中的作用

编写程序模拟"主人"喂养"宠物"的场景		主人类:Master	宠物类:Pet	宠物类子类:Dog、Cat、YingWu	主人应该有的喂养方法:feed()	宠物应该有吃的方法:eat()	只要主人喂宠物,宠物就吃要求:主人类中只提供一个喂养方法feed(),要求达到可以喂养各种类型的宠物编写测试程序:	创建主人对象	创建各种宠物对象	调用主人的喂养方法feed(),喂养不同的宠物,观察执行结果

没有使用多态之前的版本

在这里插入图片描述
改进之后的多态版本
在这里插入图片描述
在这里插入图片描述

package com.多态在开发中的作用;//主人类public class Master {
//没有多态的缺点 /* //喂养 public void feed(Dog dog){ dog.eat(); } //由于新的需求产生,导致我们不得不去修改Master这个类的代码 public void feed(Cat cat){ cat.eat(); } */ //能不能让Master主人类以后不再修改了 //即便主人又喜欢了其他的宠物,Master也不需要修改 //这个时候就需要使用:多态机制 //最好不要写具体的宠物类型,这样会影响程序的扩展性 public void feed(Pet pet){
//例:Pet pet = new Dog() //编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找打了,编译通过 //运行的时候,底层实际对象是什么,就会自动调用该实际对象对应的eat()方法上 //这就是多态的使用 pet.eat(); 注:这里不能使用Object,虽然Object是所有对象的父类, 但是Object中没有eat方法 }}/* 1. 注意这里的分析: 主人起初的时候只喜欢养宠物狗狗 随着时间的推移,主人又喜欢上养"猫咪" 这就表示在实际开发中这就表示产生了新的需求 作为软件开发人员来说,必须满足客户的需求 2. 在不使用多态的机制下,目前我们只能在Master类中添加一个新的方法 3. 思考:软件在扩展新需求过程当中,修改Master这个类有什么问题? 一定要记住:软件在扩展过程当中,修改的越少越好 修改的越多,你的系统当前的稳定性就越差,为止的风险就会越多 其实这里涉及到一个软件的开发原则: 软件开发原则有七大原则(不属于java,这个开发原则属于整个软件业): 其中一条最基本的原则:OCP(开闭原则) 什么是开闭原则? 对扩展开放(你可以添加),对修改关闭(最好很少的修改现有程序) 在软件的扩展过程当中,修改的越少越好 4. 高手开发项目不仅仅为了实现客户的需求,还需要考虑软件的扩展性 软件的扩展性:例如电脑的内存条坏了,买一个新的插上,直接使用。 这个电脑设计就考虑了扩展性 5. 面向父类型编程,面向更加抽象进行编程,不建议面向具体编程 因为面向具体编程会让软件的扩展力很差 */

2. 总结:多态在开发中的作用

降低程序的耦合度,提高程序的扩展力

public class Master{
public void feed(Dog d){
} public void feed(Cat c){
}}以上代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高),扩展力差
public class Master{
public void feed(Pet pet){
}}以上的代码中表示:Master和Dog以及Cat的关系就脱离了,Master关注的是Pet类这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性

面向对象的三大特征:

  • 封装、继承、多态真的是一环扣一环
  • 有了封装,有了这种整体概念之后,对象和对象之间产生了继承
  • 有了继承之后,才有了方法的覆盖和多态

四. 抽象类和接口

4.1 抽象类

abstract修饰符可以用来修饰方法也可以用来修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。

特点

  1. 不能new这个抽象类,只能靠子类去实现它;是一个约束!
  2. 抽象类里面可以写普通方法,但是抽象方法必须在抽象类中
  3. 本质:抽象的抽象就是约束

作用:

提高开发效率

在这里插入图片描述

4.2 接口

普通类:只有具体实现

抽象类:具体实现和规范(抽象方法)都有!
接口:只有规范!(实现了了约束和实现分离)

接口就是规范,定义的一组规则,体现了现实世界中"如果你是…则必须能…"的思想。

接口的本质就是契约,就像我们人间的法律一样,制定好大家都去遵守

OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(如Java、C++、C#等)就是因为设计模式所研究的,实际上就是如何合理的去抽象。

声明类的关键字是class,声明接口的关键字是interface

在这里插入图片描述
接口的作用:

  • Java的接口是一个约束
  • 定义了一些方法,让不同的人实现
  • 方法都是public abstract
  • 常量都是public static final
  • 接口不能直接被实例化,因为接口中没有构造方法
  • 可以实现多个接口,通过implements
  • 实现接口必须要重写接口里面的方法

转载地址:http://hqxzi.baihongyu.com/

你可能感兴趣的文章
深入Java集合学习系列(一)
查看>>
深入Java集合学习系列(二):
查看>>
图解Spring AOP
查看>>
性能调优之Weblogic调优
查看>>
性能调优之性能参数指标
查看>>
POJ3009---冰壶游戏(深搜剪枝+回溯)
查看>>
POJ3669---跳炸弹(广搜)
查看>>
POJ---1384Piggy-Bank (完全背包+装满问题)
查看>>
并查集基础知识
查看>>
POJ1182---食物链(带权并查集~技巧性超强的解法)
查看>>
POJ2492---A Bug's Life(做完食物链,再秒这个)
查看>>
POJ2063---Investment(完全背包)
查看>>
POJ1458---(最长公共子序列最基础题)
查看>>
POJ3356---(最长公共子序列)
查看>>
二叉树基础知识大全(核心理解遍历)
查看>>
03-树1 树的同构(25 分) 2017秋 数据结构 陈越、何钦铭
查看>>
04-树4 是否同一棵二叉搜索树(25 分)---陈越、何钦铭-数据结构-2017秋
查看>>
表达式求值(C实现,实现多括号,浮点数)---栈的实现以及运用。
查看>>
有序链表的合并(数据结构---单链表)
查看>>
栈实现(数据结构---数组,链表 C实现)
查看>>