type
status
date
slug
summary
tags
category
icon
password
自定义类
Java代码映射成现实事物的过程就是定义类的过程。
以一部手机分析,它能用来做什么呢?
它可以打电话,上网,聊微信等,这些就是手机所提供的功能,也就是方法;手机也有它的特征,如颜色、尺寸大小、品牌型号等,这些就是手机的特征,也就是属性。
定义
类用于描述多个对象的共同特征,它是对象的模板;使用类的形式对现实中的事物进行描述, 事物由方法和属性两部分组成。
- 属性: 这个事物具备的特征
- 方法: 这个事物具备的功能
使用关键字class来定义java中的类:
通过类的定义格式,来进行手机类的描述,如下所示:
上述代码,就是创建一个类的的过程,类的名称为Phone,类中包含了三个属性(brand品牌型号、color颜色、size尺寸大小);注意,类中定义的属性没有个数要求。
使用
Phone类定义好后,就可以使用这个类了,使用方式和使用引用数据类型Scanner类相似。
格式如下:
- 导包:将所有的类放到同一个文件夹下,可以避免导包
- 创建对象:数据类型 变量名 = new 数据类型();
- 访问属性:变量名.属性 (这是当前的方式,后期会采取调用方法的方式替代掉直接访问的方式来完成对属性的访问)
- 调用方法:变量名.方法();
上述代码中,通过类Phone创建出来的变量p,它相当于我们生活中的盒子,里面包含了它能够使用的属性。
- 通过 p.属性名 就可以对属性进行操作
- 与引用类型数组类似,引用类型的自定义类型的变量,直接变量时,结果为对象地址值
练习
面向对象
面向对象与面向过程
面向过程与面向对象都是编写程序的一种思维方式。
- 面向过程的程序设计方式,是遇到一件事时思考“我该怎么做”,然后一步步实现的过程
- 面向对象的程序设计方式,是遇到一件事时思考“我该让谁来做”,那个“谁”就是对象,他要怎么做这件事是他自己的事,最后一群对象合力能把事就好就行了
例如买电脑(组装机):
- 面向过程:自己该怎么做
- 面向对象:找人帮我们做
面向对象三大特征:
- 封装
- 继承
- 多态
面向对象好处
- 面向对象的思维方式更符合人们的思考习惯
- 面向过程的思维方式更多体现的是执行者(自己做事情),面向对象更多的体现是指挥者(指挥对象做事情)
- 面向对象思维方式将复杂的问题简单化
类与对象
对象用于描述现实中的个体,它是类的实例。
对象在需求中的使用
需求:把大象装冰箱里:
- 面向过程
- 自己打开冰箱门
- 自己将大象装进去
- 自己关闭冰箱门
- 面向对象
- 分析发现打开、装、关闭都是冰箱的功能;即冰箱对象具备如下功能:
- 冰箱打开
- 冰箱存储
- 冰箱关闭
通过伪代码描述大象和冰箱:
- 描述大象:
- 描述冰箱:
使用对象:
- 创建冰箱的对象
- 调用冰箱的功能
总结:
- 先按照名词提炼问题领域中的对象
- 对对象进行描述,其实就是在明确对象中应该具备的属性和功能
- 通过new的方式就可以创建该事物的具体对象
- 通过该对象调用它以后的功能。
对象在代码中的体现
描述小汽车,分析小汽车的属性和功能:
- 属性
- 颜色
- 轮胎个数
- 功能
- 运行
- 通过伪代码描述小汽车:
- 通过Java代码描述小汽车:
- 定义汽车类
- 测试汽车类
创建对象的格式:
类和对象的关系
类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体。
如:可以将玩具模型看作是一个类,将一个个玩具看作对象,从玩具模型和玩具之间的关系便可以看出类与对象之间的关系。
成员变量和局部变量的区别
- 定义的位置不同
- 定义在类中的变量是成员变量
- 定义在方法中或者{}语句里面的变量是局部变量
- 在内存中的位置不同
- 成员变量存储在对内存的对象中
- 局部变量存储在栈内存的方法中
- 声明周期不同
- 成员变量随着对象的出现而出现在堆中,随着对象的消失而从堆中消失
- 局部变量随着方法的运行而出现在栈中,随着方法的弹栈而消失
- 初始化不同
- 成员变量因为在堆内存中,所有默认的初始化值
- 局部变量没有默认的初始化值,必须手动的给其赋值才可以使用
基本数据类型和引用数据类型
- 基本数据类型
如下例代码所示,基本类型作为参数传递时,其实是将基本类型变量x空间中的值复制了一份传递给调用的方法show(),当在show方法中x接受到了复制的值,再在show方法中对x变量进行操作时,只会影响到show中的x;当show方法执行完成,弹栈后,程序又回到main方法执行,main方法中的x值还是原来的值。
- 引用数据类型
如下例代码所示,当引用变量作为参数传递时,其实是将引用变量空间中的内存地址(引用)复制了一份传递给了show方法的d引用变量,这时会有两个引用同时指向堆中的同一个对象;当执行show方法中的d.x=6时,会根据d所持有的引用找到堆中的对象,并将其x属性的值改为6.show方法弹栈;由于是两个引用指向同一个对象,不管是哪一个引用改变了引用的所指向的对象的中的值,其他引用再次使用都是改变后的值。
结论:
- 对于基本类型形式参数改变不会影响到实际参数
- 对于引用类型形式参数改变会影响到实际参数
封装
定义
隐藏实现的细节,并对外提供可以访问的方式,便于调用者的使用。
- 封装的表现:
- 变量使用 private 修饰就是变量的封装
- 方法就是一个最基本封装体
- 类也是一个封装体
- 封装的好处:
- 提高了代码的复用性
- 隐藏了实现细节,还要对外提供可以访问的方式,便于调用者的使用
- 提高了安全性
例如一台电脑,它是由CPU、主板、显卡、内存、硬盘、电源等部件组成,可以使用机箱把这些部件都装在里面,并留下一些插口,机箱其实就是隐藏了电脑设备的细节,并对外提供了插口以及开关等访问内部细节的方式。
private关键字
概述
private关键字即为私有的意思。
- private可以修饰成员内容包括成员方法和成员变量
- 被private修饰的内容只能在当前类中访问,不能在其他类中直接访问
get和set方法
年龄被私有,错误的值无法赋值,但正确的值也赋值不了,这样还是不行;按照之前所学习的封装的原理,隐藏后,还需要对外提供可以访问的方法,让其他程序访问这些方法,同时在方法中可以对数据进行验证。
一般对成员属性的访问动作:赋值或设置(set),取值或获取(get),对私有的变量访问的方式可以提供对应的setX或者getX的方法。
总结:
- 类中不需要对外提供的内容都私有化,包括属性和方法
- 以后再描述事物,属性都私有化,并提供set和get方法对其进行访问
注意:私有仅仅是封装的体现形式而已。
案例
this关键字
this关键字是本类对象的引用。
成员变量和局部变量同名问题
当类中存在成员变量和局部变量同名的时候为了区分,需要使用this关键字。
this对象的内存
程序执行流程说明:
- 先执行main方法(压栈),执行其中的Person p = new Person();
- 在堆内存中开辟空间,并为其分配内存地址如0x1234,紧接着成员变量默认初始化(age=0);将内存地址0x1234赋值给栈内中的p变量
- 继续执行p.setAge(30)语句,这时会调用setAge(int age)方法,将30赋值为setAge方法中的“age”变量;执行this.age = age语句,将age变量值30 赋值给成员变量this.age为30
- setAge()方法执行完毕后(弹栈),回到main()方法,执行输出语句System.out.println(),控制台打印p对象中的age年龄值
注意:this是当创建对象的时候存在的;this代表的是对象,哪个对象调用了this所在的方法,this就代表哪个对象。
上述代码中的 p.setAge(30)语句中,setAge(int age)方法中的this代表的就是p对象。
案例
需求:在Person类中定义功能,判断两个人是否是同龄人。
随机点名器重构
需求分析
随机点名器,即在全班同学中随机的找出一名同学,打印这名同学的个人信息。
它具备以下3个内容:
- 存储所有同学姓名
- 总览全班同学姓名
- 随机点名其中一人,打印到控制台
将原来使用的简单Student类,封装为包装属性和方法的相对完整的Student类,并将所有访问属性的地方改为通过get/set方法访问。
代码实现
继承
概述
继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。
在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有非private修饰的属性和方法。
定义
格式
案例
通过子类对象既可以调用自身的非private修饰的成员,也可以调用父类的非private修饰的成员。
优缺点
优点:
- 继承的出现提高了代码的可维护性和复用性,提高软件开发效率
- 继承的出现让类与类之间产生了关系,提供了多态的前提
缺点:
类与类之间的耦合度过高。
注意事项
- 在Java中,类只支持单继承,不允许多继承,也就是说一个类只能有一个直接父类,例如下面这种情况是不合法的:
假如支持多继承例如:
- 多个类可以继承一个父类,例如下面这种情况是允许的:
- 在Java中,多层继承是可以的,即一个类的父类可以再去继承另外的父类,例如C类继承自B类,而B类又可以去继承A类,这时,C类也可称作A类的子类;下面这种情况是允许的:
- 在Java中,子类和父类是一种相对概念,也就是说一个类是某个类父类的同时,也可以是另一个类的子类。例如上面的这种情况中,B类是A类的子类,同时又是C类的父类
- 所有的类都直接或者间接的继承了 Object类,Object类称为祖宗类
- 父类私有的成员,子类不能继承
体系
动物体系是对每个具体事物共性的抽取,子类的共性抽取形成父类:
- 父类具有所有子类的共性内容
- 子类不但有共性还有自身特有的内容
整个继承体系越向上越抽,,越向下越具体。
继承后子父类成员变量的特点
- 子类的对象调用成员变量的时候,子类自己有则使用子类自己的,子类自己没有则调用父类的
- 当子父类中出现了同名成员变量
继承后子子类重写父类方法
为什么要有重写?因为类中的方法最先存在,如果项目需求变了,该方法功能不能够满足现有的需求,此时不会去改这个方法,因为项目中可能有大量的功能已经使用到该方法,如果随意修改可能使调用该方法的功能出现问题,所以需要使用重写方式基于原有功能提供更强的功能。
- 子类的对象调用方法的时候,子类自己有使用自己的,子类自己没有则调用父类的
- 子类中出现与父类一模一样的方法时,会出现覆盖操作,也称为override重写、复写或者覆盖
- super:用来表示当前对象中包含的父类对象空间的引用
注意事项
定义
子类方法和要重写的父类方法,方法的方法名和参数列表都要一样;父类私有的方法,子类不能够进行方法重写。
权限
子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
四大权限:public>默认=protected>private
返回值
- 如果是基本数据类型,子类的方法和重写的父类的方法返回值类型必须相同
- 如果是引用数据类型,子类的方法和重写的父类的方法返回值类型可以相同或者子类方法的返回值类型是父类方法返回值类型的子类
重载和重写
重载:
- 权限修饰符(public private 默认):无关
- 方法名:重载的两个方法的方法名必须相同
- 形参列表:
- 形参类型的顺序不同
- 形参的个数不同
- 形参的类型不同
- 三者至少满足一个
- 返回值类型:重载与返回值类型无关
重写:
- 权限修饰符(public private 默认):子类方法的权限>=父类的方法的权限
- 方法名:子类方法和父类方法必须相同
- 形参列表:子类方法和父类方法的形参列表必须相同
- 返回值类型;
- 基本类数据类型:必须相同
- 引用数据类型:子类方法的返回值类型和父类方法的返回值类型相同,或者子类方法的返回值类型是父类方法的返回值类型的子类
案例
案例介绍
描述一个手机时,它具有发短信,打电话,显示来电号码功能,后期由于手机需要在来电显示功能中增加显示姓名和头像,这时可以重新定义一个类描述智能手机,并继承原有描述手机的类,并在新定义的类中覆盖来电显示功能,在其中增加显示姓名和头像功能。
需求分析
不改装(破坏)原来的手机,而是再买一个新的智能手机,不但有原有的功能,而且还有特有功能;例如厂商发布新手机都是基于原有手机的升级,不会拿着原有的手机在卖,而是新产一款。
- 分析类的构建:
- 手机类:
- 属性(成员变量):无
- 行为(成员方法):
- 发短信
- 打电话
- 来电显示:显示来电号码
- 智能手机类:
- 属性(成员变量):无
- 行为(成员方法):
- 发短信
- 打电话
- 来电显示:显示来电号码,显示姓名和头像
- 手机类和智能手机类有共性内容:
- 发短信
- 打电话
- 显示来电号码
- 分析继承关系:
对于发短信和打电话功能,让智能手机直接沿用(继承)手机的就可以,但是在智能手机中的来电显示不但实现号码,还显示姓名和头像,同样的都是来电显示功能,智能手机的来电显示比手机的功能更加强大,考虑使用重写。
代码实现
抽象类
分析事物时,发现了共性内容,就出现向上抽取;有这样一种特殊情况,方法功能声明相同,但方法功能主体不同;这时也可以抽取,但只抽取方法声明,不抽取方法主体,此方法就是一个抽象方法。
当定义了抽象函数的类也必须被abstract关键字修饰,被abstract关键字修饰的类是抽象类。
定义
抽象方法定义的格式:方法只有声明部分,没有方法体。
抽象类定义的格式:包含抽象方法的类,一定是抽象类。
使用方式
特点
- 抽象类和抽象方法都需要被abstract修饰,抽象方法一定要定义在抽象类中
- 抽象类不可以直接创建对象,因为调用抽象方法没有意义
- 抽象类中可以有抽象方法,也可以没有抽象方法
- 只有覆盖了抽象类中所有的抽象方法后,其子类才可以创建对象,否则该子类还是一个抽象类
- 抽象类的子类
- 实现了抽象方法的具体类
- 抽象类
之所以继承抽象类,更多的是在思想,是面对共性类型操作会更简单。
设计思想
抽象类的作用:继承的体系抽象类,强制子类重写抽象的方法。
细节
- 抽象类一定是个父类?
是的,因为是不断抽取而来的。
- 抽象类中是否可以不定义抽象方法?
可以的,不让该类创建对象,直接让子类去使用它的方法
适配器设计模式:
- 抽象关键字abstract不可以和哪些关键字共存?
- private:私有的方法子类是无法继承到的,也不存在覆盖,而abstract和private一起使用修饰方法,abstract既要子类去实现这个方法,而private修饰子类根本无法得到父类这个方法,互相矛盾
- final
- static
案例
介绍
某IT公司有多名员工,按照员工负责的工作不同,进行了部门的划分(研发部员工、维护部员工);研发部根据所需研发的内容不同,又分为JavaEE工程师、Android工程师;维护部根据所需维护的内容不同,又分为网络维护工程师、硬件维护工程师。
公司的每名员工都有他们自己的员工编号、姓名,并要做它们所负责的工作。
工作内容:
- JavaEE工程师:员工号为xxx的 xxx员工,正在研发淘宝网站
- Android工程师:员工号为xxx的 xxx员工,正在研发淘宝手机客户端软件
- 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通
- 硬件维护工程师:员工号为xxx的 xxx员工,正在修复打印机
请根据描述,完成员工体系中所有类的定义,并指定类之间的继承关系。进行XX工程师类的对象创建,完成工作方法的调用。
需求分析
需求描述:
- 某IT公司有多名员工,按照员工负责的工作不同,进行了部门的划分(研发部员工、维护部员工)
- 研发部根据所需研发的内容不同,又分为JavaEE工程师、Android工程师
- 维护部根据所需维护的内容不同,又分为网络维护工程师、硬件维护工程师
- 公司的每名员工都有他们自己的员工编号、姓名,并要做它们所负责的工作
工作内容:
- JavaEE工程师:员工号为xxx的 xxx员工,正在研发淘宝网站
- Android工程师:员工号为xxx的 xxx员工,正在研发淘宝手机客户端软件
- 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通
- 硬件维护工程师:员工号为xxx的 xxx员工,正在修复打印机
继承体系:
详细描述:
根据员工信息的描述,确定每个员工都有员工编号、姓名、要进行工作;则,把这些共同的属性与功能抽取到父类中(员工类),关于工作的内容由具体的工程师来进行指定。
代码实现
接口
概念
接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”。
接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成;这样将功能的定义与实现分离,优化了程序设计。
记住:一切事物均有功能,即一切事物均有接口。
定义
与定义类的class不同,接口定义时需要使用interface关键字;定义接口所在的仍为.java文件,虽然声明时使用的为interface关键字的编译后仍然会产生.class文件,因此将接口看做是一种只包含了功能声明的特殊类。
定义格式:
定义步骤:使用interface代替了原来的class,其他步骤与定义类相同,接口中的方法均为公共访问的抽象方法,接口中无法定义普通的成员变量。
实现类
类与接口的关系:类与接口的关系为实现关系,即类实现接口。实现的动作类似继承,只是关键字不同,实现使用implements;其他类(实现类)实现接口后,就相当于声明:”我应该具备这个接口中的功能”;实现类仍然需要重写方法以实现具体的功能。
类实现接口的格式:
一个类如果实现类接口,有两种操作方法:
- 实现类是非抽象类,需要重写接口中所有的抽象方法
- 实现类也声明为抽象类,那么实现类可以不重写接口中的抽象方法
注意事项
- 在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑
- 接口中定义功能,当需要具有该功能时,可以让类实现该接口,只声明了应该具备该方法,是功能的声明
- 在具体实现类中重写方法,实现功能,是方法的具体实现
成员变量与成员方法
成员变量
- 接口中可以定义变量,但是变量必须有固定的修饰符修饰:public static final,所以接口中的变量也称之为常量,其值不能改变
- 接口不可以创建对象
成员方法
- 接口中可以定义方法,方法也有固定的修饰符,public abstract
- 子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化,否则子类是一个抽象类
类和接口的多实现
接口最重要的体现是解决多继承的弊端,将多继承这种机制在java中通过多实现完成了。
多继承时,当多个父类中有相同功能时,子类调用会产生不确定性;其实核心原因就是在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个主体内容。
接口中的功能都没有方法体,由子类来明确。
类在继承类的同时实现多接口
接口和类之间可以通过实现产生关系,同时也学习了类与类之间可以通过继承产生关系。
当一个类已经继承了一个父类,它又需要扩展额外的功能,这时接口就派上用场了。
子类通过继承父类扩展功能,通过继承扩展的功能都是子类应该具备的基础功能;如果子类想要继续扩展其他类中的功能,就可以通过实现接口来完成。
接口的出现避免了单继承的局限性,在父类中定义的事物的基本功能,接口中定义的事物的扩展功能。
接口的出现避免了单继承的局限性,父类中定义的事物的基本功能,接口中定义的事物的扩展功能。
接口的多继承
多个接口之间可以使用extends进行继承。
在开发中如果多个接口中存在相同方法,这时若有个类实现了这些接口,那么就要实现接口中的方法,由于接口中的方法是抽象方法,子类实现后也不会发生调用的不确定性。
思想
电脑上留有很多个插口,而这些插口可以插入相应的设备,这些设备为什么能插在上面呢?
主要原因是这些设备在生产的时候符合了这个插口的使用规则,否则将无法插入接口中,更无法使用。发现这个插口的出现让我们使用更多的设备。
接口的出现方便后期使用和维护,一方是在使用接口(如电脑),一方在实现接口(插在插口上的设备)。例如:笔记本使用这个规则(接口),电脑外围设备实现这个规则(接口)。
接口的好处:
- 接口的出现扩展了功能
- 接口其实就是暴漏出来的规则
- 接口的出现降低了耦合性,即设备与设备之间实现了解耦
接口和抽象类的区别
通过实例进行分析和代码演示抽象类和接口的用法。
思考:
- 由于犬分为很多种类,他们吼叫和吃饭的方式不一样,在描述的时候不能具体化,也就是吼叫和吃饭的行为不能明确
- 当描述行为时,行为的具体动作不能明确,这时可以将这个行为写为抽象行为,那么这个类也就是抽象类
- 可是当缉毒犬有其他额外功能时,而这个功能并不在这个事物的体系中,这时可以让缉毒犬具备犬科自身特点的同时也有其他额外功能,可以将这个额外功能定义接口中
接口和抽象类区别
- 相同点:
- 都位于继承的顶端,用于被其他类实现或继承
- 都不能直接实例化对象
- 都包含抽象方法,其子类都必须覆写这些抽象方法
- 区别:
- 抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码重用性;接口只能包含抽象方法
- 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口(接口弥补了Java的单继承)
- 抽象类是这个事物中应该具备的你内容,继承体系是一种 is..a关系
- 接口是这个事物中的额外内容,继承体系是一种 like..a关系
- 二者的选用:
- 优先选用接口,尽量少用抽象类
- 需要定义子类的行为,又要为子类提供共性功能时才选用抽象类
多态
概述
多态是继封装、继承之后,面向对象的第三大特性。
现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即人会出现两种形态。
Java作为面向对象的语言,同样可以描述一个事物的多种形态;如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
Java中多态的代码体现在一个子类对象(实现类对象)既可以给这个子类(实现类对象)引用变量赋值,又可以给这个子类(实现类对象)的父类(接口)变量赋值。
如Student类可以为Person类的子类,那么一个Student对象既可以赋值给一个Student类型的引用,也可以赋值给一个Person类型的引用。
最终多态体现为父类引用变量可以指向子类对象。
多态的前提是必须有子父类关系或者类实现接口关系,否则无法完成多态。
在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
调用格式
多态的定义格式就是父类的引用变量指向子类对象。
- 普通类多态定义的格式
- 抽象类多态定义格式
- 接口多态定义的格式
注意事项
- 同一个父类的方法会被不同的子类重写,在调用方法时,调用的为各个子类重写后的方法
- 当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法
成员变量和成员方法
成员变量
当子父类中出现同名的成员变量时,多态调用该变量时:
- 编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量,没有,编译失败
- 运行时期:也是调用引用型变量所属的类中的成员变量
简而言之:编译和运行都参考等号的左边,编译运行看左边。
成员方法
- 编译时期:参考引用变量所属的类,如果没有类中没有调用的方法,编译失败
- 运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法
简而言之:编译看左边,运行看右边。
instanceof关键字
作用:可以通过instanceof关键字来判断某个对象是否属于某种数据类型。如学生的对象属于学生类,学生的对象也属于人类。
格式:
转型
多态的转型分为向上转型与向下转型两种。
向上转型
当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
向下转型
一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型;如果是直接创建父类对象,是无法向下转型的!
优缺点
当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。
- 向上转型的好处是隐藏了子类类型,提高了代码的扩展性
- 但向上转型也有弊端,只能使用父类共性的内容,而无法使用子类特有功能,功能有限制
总结
- 什么时候使用向上转型:当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作,这时就可以使用向上转型
- 什么时候使用向下转型:当要使用子类特有功能时,就需要使用向下转型
- 向下转型的好处:可以使用子类特有功能
- 向下转型的弊端:需要面对具体的子类对象;在向下转型时容易发生ClassCastException类型转换异常。在转换之前必须做类型判断
举例
总结
总结下封装、继承、多态的作用:
- 封装:把对象的属性与方法的实现细节隐藏,仅对外提供一些公共的访问方式
- 继承:子类会自动拥有父类所有可继承的属性和方法
- 多态:配合继承与方法重写提高了代码的复用性与扩展性;如果没有方法重写,则多态同样没有意义
笔记本电脑案例
案例介绍
定义USB接口(具备开启功能、关闭功能),笔记本要使用USB设备,即笔记本在生产时需要预留可以插入USB设备的USB接口,即就是笔记本具备使用USB设备的功能,但具体是什么USB设备,笔记本并不关心,只要符合USB规格的设备都可以。鼠标和键盘要想能在电脑上使用,那么鼠标和键盘也必须遵守USB规范,不然鼠标和键盘的生产出来无法使用。
描述笔记本类,实现笔记本使用USB鼠标、USB键盘:
- USB接口,包含开启功能、关闭功能
- 笔记本类,包含运行功能、关机功能、使用USB设备功能
- 鼠标类,要符合USB接口
- 键盘类,要符合USB接口
案例分析
- 使用笔记本,笔记本有运行功能,需要笔记本对象来运行这个功能
- 想使用一个鼠标,又有一个功能使用鼠标,并多了一个鼠标对象
- 还想使用一个键盘 ,又要多一个功能和一个对象
问题:每多一个功能就需要在笔记本对象中定义一个方法,程序扩展性极差,降低鼠标、键盘等外围设备和笔记本电脑的耦合性。
代码实现
构造方法
概述
什么是构造方法?
从字面上理解即为构建创造时用的方法,即就是对象创建时要执行的方法;既然是对象创建时要执行的方法,那么只要在new对象时,知道其执行的构造方法是什么,就可以在执行这个方法的时候给对象进行属性赋值。
作用
在new的同时给成员变量赋值,给对象属性进行初始化。
定义
构造方法定义格式:
特点
构造方法的特点:
- 构造方法没有返回值类型,也不需要写返回值。因为它是为构建对象的,对象创建完,方法就执行结束
- 构造方法名称必须和类型保持一致
- 构造方法没有具体的返回值
- 在 new 对象的时候自动调用执行
默认构造方法
每一class类都必须有一个构造方法,构造方法不写也有。
编译的时候,javac会自动检查类中是否有构造方法,如果没有编译器就会自动添加一个构造方法。
调用赋值
构造方法是专门用来创建对象的,也就是在new对象时要调用构造方法,现在来看看如何调用构造方法。
上述代码演示了创建对象时构造方法的调用,即在创建对象时,会调用与参数列表对应的构造方法。
内存
内存加载的过程:有一个Person类, 创建Person 对象new Person()。
- 首先将main方法压入栈中,执行main方法中的 new Person(23,"张三");
- 在堆内存中分配一片区域,用来存放创建的Person对象,这片内存区域会有属于自己的内存地址(0x88),然后给成员变量进行默认初始化(name=null,age=0)
- 执行构造方法中的代码(age = a ; name = nm;),将变量a对应的23赋值给age,将变量nm对应的“张三”赋值给name,这段代码执行结束后,成员变量age和name的值已经改变;执行结束之后构造方法弹栈,Person对象创建完成,将Person对象的内存地址0x88赋值给p2
重载
若创建对象时不需要明确具体的数据,这时可以不用书写构造方法(不书写也有默认的构造方法);当描述的事物在创建其对象时就要明确属性的值,这时就需要在定义类的时候书写带参数的构造方法。
构造方法的细节:
- 一个类中可以有多个构造方法,多个构造方法是以重载的形式存在的
- 构造方法是可以被private修饰的,作用:其他程序无法创建该类的对象
构造方法和一般方法区别
- 格式不同
- 构造方法:
- 一般方法:需要有返回值类型
- 作用不同
- 构造方法一般用来给成员变量初始化
- 一般方法根据需求而定
- 调用方式不同
- 构造方法创建对象时调用, 或者this() super() 语句调用
- 普通方法需要对象调用或者静态方法直接调用静态方法.
- 执行不同
- 构造方法在对象创建时就执行了,而且只执行一次
- 一般方法是在对象创建后,需要使用时才被对象调用,并可以被多次调用
this关键字
调用构造方法
构造方法无法通过构造方法名来相互调用,需要通过this关键字来完成。
构造方法调用格式:
调用构造方法的内存
被加载的代码:
- 先执行main方法,main方法压栈,执行其中的new Person(“张三”,23);
- 堆内存中开辟空间,并为其分配内存地址0x33,紧接着成员变量默认初始化(name=null,age = 0)
- 拥有两个参数的构造方法(Person(String nm , int a))压栈,在这个构造方法中有一个隐式的this,因为构造方法是给对象初始化的,那个对象调用到这个构造方法,this就指向堆中的那个对象
- 由于Person(String nm , int a)构造方法中使用了this(nm),构造方法Person(String nm)就会压栈,并将“张三”传递给nm;在Person(String nm , int a)构造方法中同样也有隐式的this,this的值同样也为0x33,这时会执行其中name = nm,即把“张三”赋值给成员的name,当赋值结束后Person(String nm , int a)构造方法弹栈
- 程序继续执行构造方法(Person(String nm , int a))中的age = a;这时会将23赋值给成员属性age,赋值结束构造方法(Person(String nm , int a))弹栈
- 当构造方法(Person(String nm , int a))弹栈结束后,Person对象在内存中创建完成,并将0x33赋值给main方法中的p引用变量
注意
- 哪个对象调用了this所在的方法,this就代表哪个对象
- 调用其他构造方法的语句必须定义在构造方法的第一行,原因是初始化动作要最先执行
应用
当在方法中出现了局部变量和成员变量同名时,可以在成员变量名前面加上this.来区别成员变量和局部变量。
需求:在Person类中定义功能,判断两个人是否是同龄人。
super关键字
子父类中构造方法的调用
在创建子类对象时,父类的构造方法会先执行,因为子类中所有构造方法的第一行有默认添加的构造方法:super();语句。
格式:
为什么子类对象创建都要访问父类中的构造方法?
因为子类继承了父类的内容,所以创建对象时,必须要先看父类是如何对其内容进行初始化的。
如下程序:
执行结果:
通过结果发现,子类构造方法执行时中,调用了父类构造方法,这说明,子类构造方法中有一句super()。
那么,子类中的构造方法为什么会有一句隐式的super()?
因为:子类会继承父类中的内容,所以子类在初始化时,必须先到父类中去执行父类的初始化动作,这样,才可以使用父类中的内容。
当父类中没有空参数构造方法时,子类的构造方法必须有显示的super语句,指定要访问的父类有参数构造方法。
创建子类对象时必须调用父类的构造方法。
子类默认会调用父类的无参构造, 但如果父类没有无参构造,子类的构造方法继续调用父类的无参构造就会报错。
因此子类构造方法的第一行需要调用父类的构造方法,既可以调用父类的无参构造,也可以调用父类的有参构造,这样语法上就不会报错。
构造方法第一行写this()还是super()?
this()是调用本类的构造方法,super()是调用父类的构造方法,且两条语句不能同时存在,保证子类的所有构造方法调用到父类的构造方法即可
小结:
- 子类的所有构造方法必须直接或间接调用到父类构造方法
- 子类的构造方法什么都不写,默认的构造方法第一行super()
创建子类对象过程的细节
- 如果子类的构造方法第一行写了this调用了本类其他构造方法,那么super调用父类的语句还有吗? 这时是没有的,因为this()或者super(),只能定义在构造方法的第一行,因为初始化动作要先执行。
- 父类构造方法中是否有隐式的super呢? 也是有的。记住:只要是构造方法默认第一行都是super();。
- 父类的父类是谁呢?super调用的到底是谁的构造方法呢? Java体系在设计,定义了一个所有对象的父类Object。
注意:
类中的构造方法默认第一行都有隐式的super()语句,在访问父类中的空参数构造方法,所以父类的构造方法既可以给自己的对象初始化,也可以给自己的子类对象初始化。
如果默认的隐式super()语句在父类中没有对应的构造方法,那么必须在构造方法中通过this或者super的形式明确要调用的构造方法。
应用
描述学生和工人这两个类,将他们的共性name和age抽取出来存放在父类中,并提供相应的get和set方法,同时需要在创建学生和工人对象就必须明确姓名和年龄。
员工案例
案例介绍
某IT公司有多名员工,按照员工负责的工作不同,进行了部门的划分(研发部员工、维护部员工);研发部根据所需研发的内容不同,又分为JavaEE工程师、Android工程师;维护部根据所需维护的内容不同,又分为网络维护工程师、硬件维护工程师。
公司的每名员工都有他们自己的员工编号、姓名,并要做它们所负责的工作。
工作内容:
- JavaEE工程师:员工号为xxx的 xxx员工,正在研发淘宝网站
- Android工程师:员工号为xxx的 xxx员工,正在研发淘宝手机客户端软件
- 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通
- 硬件维护工程师:员工号为xxx的 xxx员工,正在修复打印机
请根据描述,完成员工体系中所有类的定义,并指定类之间的继承关系。进行XX工程师类的对象创建,完成工作方法的调用。
案例分析
根据员工信息的描述,确定每个员工都有员工编号、姓名、要进行工作;则,把这些共同的属性与功能抽取到父类中(员工类),关于工作的内容由具体的工程师来进行指定。
工作内容:
- JavaEE工程师:员工号为xxx的 xxx员工,正在研发淘宝网站
- Android工程师:员工号为xxx的 xxx员工,正在研发淘宝手机客户端软件
- 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通
- 硬件维护工程师:员工号为xxx的 xxx员工,正在修复打印机
创建JavaEE工程师对象,完成工作方法的调用。
代码实现
- 定义员工类(抽象类)
- 定义研发部员工类Developer继承员工类Employee
- 定义维护部员工类Maintainer继承员工类Employee
- 定义JavaEE工程师继承研发部员工类,重写工作方法
- 定义Android工程师继承研发部员工类,重写工作方法
- 定义Network网络维护工程师继承维护部员工类,重写工作方法
- 定义Hardware硬件维护工程师继承维护部员工类,重写工作方法
- 在测试类中,创建JavaEE工程师对象,完成工作方法的调用
final关键字
概念
继承的出现提高了代码的复用性,并方便开发;但随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写;可是当子类继承了这些特殊类之后,就可以对其中的方法进行重写,那怎么解决呢?
要解决上述的这些问题,需要使用到一个关键字final,final的意思为最终,不可变。
final是个修饰符,它可以用来修饰类,类的成员,以及局部变量。
修饰类
final修饰类不可以被继承,但是可以继承其他类。
修饰方法
final修饰的方法不可以被覆盖,但父类中没有被final修饰方法,子类覆盖后可以加final。
修饰局部变量
- 修饰基本数据类型变量
final修饰的变量称为常量,这些变量只能赋值一次
- 修饰引用数据类型
引用类型的变量值为对象地址值,地址值不能更改,但是地址内的对象属性值可以修改
修饰成员变量
final修饰成员变量,需要在创建对象前赋值,否则报错(当没有显式赋值时,多个构造方法的均需要为其赋值)。
static关键字
概念
当在定义类的时候,类中都会有相应的属性和方法,而属性和方法都是通过创建本类对象调用的;当在调用对象的某个方法时,这个方法没有访问到对象的特有数据时,方法创建这个对象有些多余,可是不创建对象,方法又调用不了,那么能不能不创建对象,就可以调用方法呢?
可以的,可以通过static关键字来实现,static它是静态修饰符,一般用来修饰类中的成员。
修饰对象的特有数据
被static修饰的成员变量属于类,不属于这个类的某个对象(也就是说,多个对象在访问或修改static修饰的成员变量时,其中一个对象将static成员变量值进行了修改,其他对象中的static成员变量值跟着改变,即多个对象共享同一个static成员变量)。
注意事项
- 被static修饰的成员可以并且建议通过类名直接问。
访问静态成员的格式:
- 静态内容是优先于对象存在,只能访问静态,不能使用this/super,静态修饰的内容存于静态区。
- 同一个类中,静态成员只能访问静态成员
- main方法为静态方法仅仅为程序执行入口,它不属于任何一个对象,可以定义在任意类中
使用场景
- 什么时候使用static修饰成员变量? 加static修饰成员的时候,这个成员会被类的所有对象所共享,一般把共性数据定义为静态的变量。
- 什么时候使用static修饰成员方法? 静态的方法只能访问静态的成员,如果静态方法中引用到了静态的其他成员,那么这个方法需要声明为静态的方法。
对象中的静态调用
在多态中,非静态编译看父类,运行看子类,父类没有编译失败;但多态中的静态方,编译看父类,运行仍然看父类,因为静态和对象没有关系,属于静态绑定。
定义静态常量
开发中想在类中定义一个静态常量,通常使用public static final修饰的变量来完成定义。
此时变量名用全部大写,多个单词使用下划线连接。
定义格式:
注意:
- 接口中的每个成员变量都默认使用public static final修饰
- 所有接口中的成员变量已是静态常量,由于接口没有构造方法,所以必须显示赋值。可以直接用接口名访问
匿名对象
概述
匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。
特点
- 创建匿名对象直接使用,没有变量名。
- 匿名对象在没有指定其引用变量时,只能使用一次。
- 匿名对象可以作为方法接收的参数、方法返回值使用
内部类
概述
将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类,其他类也称为外部类。
在描述事物时,若一个事物内部还包含其他可能包含的事物,比如在描述汽车时,汽车中还包含这发动机,这时发动机就可以使用内部类来描述。
内部类分为成员内部类与局部内部类。
在定义内部类时,就是一个正常定义类的过程,同样包含各种修饰符、继承与实现关系等,在内部类中可以直接访问外部类的所有成员。
成员内部类
调用格式
成员内部类定义在外部类中的成员位置,与类中的成员变量相似,可通过外部类对象进行访问。
定义格式:
访问方式:
同名变量调用
局部内部类
局部内部类定义在外部类方法中的局部位置,与访问方法中的局部变量相似,可通过调用方法进行访问。
定义格式:
访问方式:在外部类方法中,创建内部类对象,进行访问。
匿名内部类
最常用的内部类就是匿名内部类,它是局部内部类的一种。
定义的匿名内部类有两个含义:临时定义某一指定类型的子类;定义后即刻创建刚刚定义的这个子类的对象。
匿名内部类的本质是一个实现了接口或继承了某个类的子类匿名对象。
包
概念
Java的包其实就是电脑系统中的文件夹,包里存放的是类文件;当类文件很多的时候,通常会采用多个包进行存放管理他们,这种方式称为分包管理;在项目中,会将相同功能的类放到一个包中,方便管理,并且日常项目的分工也是以包作为边界;类中声明的包必须与实际class文件所在的文件夹情况相一致,即类声明在a包下,则生成的.class文件必须在a文件夹下,否则,程序运行时会找不到类。
声明
通常使用公司网址反写,可以有多层包,包名采用全部小写字母,多层包之间用”.”连接
注意:声明包的语句,必须写在程序有效代码的第一行(注释不算)。
访问
在访问类时,为了能够找到该类,必须使用含有包名的类全名(包名.类名)。
带有包的类,创建对象格式:
前提:包的访问与访问权限密切相关,这里以一般情况来说,即类用public修饰的情况。
类的简化访问:当要使用一个类时,这个类与当前程序在同一个包中(即同一个文件夹中),或者这个类是java.lang包中的类时通常可以省略掉包名,直接使用该类。
如:cn.code包中有两个类,PersonTest类,与Person类。我们在PersonTest类中,访问Person类时,由于是同一个包下,访问时可以省略包名,即直接通过类名访问 Person。
当要使用的类与当前程序不在同一个包时(即不同文件夹中),要访问的类必须用public修饰才可访问。
导包
每次使用类时,都需要写很长的包名,很麻烦,可以通过import导包的方式来简化,避免使用全类名编写(即,包类.类名)。
导包的格式:
import导包代码书写的位置:在声明包package后,定义所有类class前。
静态导入
在导包的过程中可以直接导入静态部分,这样某个类的静态成员就可以直接使用了,在源码中经常会出现静态导入。
如果本类中有和静态导入的同名方法会优先使用本类的,如果还想使用静态导入的,依然需要类名来调用。
访问修饰符
在Java中提供了四种访问权限,使用不同的访问权限时,被修饰的内容会有不同的访问权限。
以下表来说明不同权限的访问能力:
ㅤ | public | protected | default | private |
同一类中 | √ | √ | √ | √ |
同一包中(子类与无关类) | √ | √ | √ | ㅤ |
不同包的子类 | √ | √ | ㅤ | ㅤ |
不同包中的无关类 | √ | ㅤ | ㅤ | ㅤ |
小结:
- 要想仅能在本类中访问使用private修饰
- 要想本包中的类都可以访问不加修饰符即可
- 要想本包中的类与其他包中的子类可以访问使用protected修饰
- 要想所有包中的所有类都可以访问使用public修饰
注意:如果类用public修饰,则类名必须与文件名相同,一个文件中只能有一个public修饰的类。
代码块
概述
程序中用大括号括起来的代码叫代码块,可以分为四类:
- 局部代码块
- 构造代码块
- 静态代码块
- 同步代码块
局部代码块
局部代码块定义在方法或语句中,可以用来限定变量的声明周期。
特点:
- 以”{}”划定的代码区域,此时只需要关注作用域的不同即可
- 方法和类都是以代码块的方式划定边界的
结果:
构造代码块
构造代码块是定义在类中成员位置的代码块。
特点:
- 优先于构造方法执行,构造代码块用于执行所有对象均需要的初始化动作
- 每创建一个对象均会执行一次构造代码块
静态代码块
静态代码块是定义在成员位置,使用static修饰的代码块。
特点:
- 优先于主方法执行、优先于构造代码块执行,当以任意形式第一次使用到该类时执行
- 该类不管创建多少对象,静态代码块只执行一次
- 可用于给静态变量赋值,用来给类进行初始化
同步代码块
同步代码块: 在代码块声明上 加上synchronized。
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
修饰符
常用来修饰类、方法、变量的修饰符如下:
- public权限修饰符,公共访问类、方法、成员变量
- protected权限修饰符,受保护访问方法、成员变量
- 默认什么也不写也是一种权限修饰符,默认访问类、方法、成员变量
- private权限修饰符,私有访问方法、成员变量
- static静态修饰符,静态方法、成员变量
- final最终修饰符,类、方法、成员变量、局部变量
- abstract抽象修饰符,类、方法
不能同时使用的修饰符:
- abstract与private不能同时使用
- abstract与static不能同时使用
- abstract与final不能同时使用
修饰类能够使用的修饰符:
- 修饰类只能使用public、默认的、final、abstract关键字
- 使用最多的是 public关键字
修饰成员变量能够使用的修饰符:
- public : 公共的
- protected : 受保护的
- : 默认的
- private :私有的
- final : 最终的
- static : 静态的
- 使用最多的是private
修饰构造方法能够使用的修饰符:
- public : 公共的
- protected : 受保护的
- : 默认的
- private :私有的
- 使用最多的是public
修饰成员方法能够使用的修饰符:
- public : 公共的
- protected : 受保护的
- : 默认的
- private :私有的
- final : 最终的
- static : 静态的
- abstract : 抽象的
- 使用最多的是public
自定义数据类型
局部变量和成员变量解析
定义长方形类,包含求周长与求面积的方法;定义数学工具类,包含求两个数和的二倍与求两个数积的方法;这两个类的计算方法均需要两个数参与计算,请问两个数定义在成员位置还是形参位置更好,为什么?
如果变量是该类的一部分时,定义成成员变量;如果变量不应该是类的一部分,而仅仅是功能当中需要参与计算的数,则定义为形参变量。
类作为方法的参数与返回值
- 类作为方法参数
在编写程序中,会经常碰到调用的方法要接收的是一个类类型的情况,那么这时要向方法中传入该类的对象。
- 类作为方法返回值
写程序调用方法时,会经常碰到返回一个类类型的返回值,那么这时该方法要返回一个该类的对象。
抽象类作为方法参数与返回值
- 抽象类作为方法参数
开发中抽象类作为方法参数的情况也很多见,当遇到方法参数为抽象类类型时,要传入一个实现抽象类所有抽象方法的子类对象。
- 抽象类作为方法返回值
抽象类作为方法返回值时,需要返回一个实现抽象类所有抽象方法的子类对象。
接口作为方法参数与返回值
- 接口作为方法参数
当遇到方法参数为接口类型时,该方法要传入一个接口实现类对象。
- 接口作为方法返回值
当遇到方法返回值是接口类型时,该方法需要返回一个接口实现类对象。
星级酒店案例
案例介绍
某五星级酒店,资金雄厚,要招聘多名员工(经理、厨师、服务员),入职的员工需要记录个人信息(姓名、工号、经理特有奖金属性),他们都有自己的工作要做。
本案例要完成如下需求:
- 获取酒店幸运员工
- 酒店开设VIP服务,酒店的厨师与服务员可以提供VIP服务(厨师做菜加量、服务员给顾客倒酒)
- 编写测试类
- 向酒店中,增加多名员工(其中包含1名经理,1名厨师、2名服务员)
- 调用酒店员工的工作功能
- 调用酒店员工的VIP服务功能
- 根据“某五星级酒店,资金雄厚……都有自己的工作要做。”分析出,该题目中包含酒店,可以把它封装成类,多名员工。
员工的类型有经理、厨师、服务员,它们有共同的属性(姓名、工号、),经理额外属性(奖金)。
- 根据“向酒店中,增加多名员工(其中包含1名经理,1名厨师、2名服务员)”。分析出,要创建一个酒店对象,并添加4名员工到酒店对象的员工集合中。
- 酒店员工集合添加新员工: 经理对象
- 酒店员工集合添加新员工: 厨师对象
- 酒店员工集合添加新员工: 服务员对象
- 酒店员工集合添加新员工: 服务员对象
- 根据“获取酒店幸运员工”。分析出,从酒店员工集合随机得到一名员工对象。
- 从酒店员工集合长度范围内,随机产生一个随机数
- 使用该随机数作为集合的索引,返回该索引处对应的员工对象
- 根据“酒店开设VIP服务,酒店的厨师与服务员可以提供VIP服务;(厨师做菜加量、服务员给顾客倒酒)”。分析出,这是要增加一个VIP的接口,接口中提供个VIP服务的方法,让厨师与服务员实现该接口。
代码实现
- VIP服务
- 员工
- 服务员
- 经理
- 厨师
- 作者:青山🌊
- 链接:http://cxuan.me/article/java-se-oob
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。