第六章 面向对象(中)
6.1 封装
6.1.1 封装的概念
-
适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。
方式:暴露该暴露的,隐藏需要隐藏
-
高内聚,低耦合
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用
- 封装思想的应用
- 类的封装:把属性和方法封装到类的内部,外部通过调用方法完成指定的功能,不需要了解类的内部实现
- 组件的封装:例如支付宝等支付组件,对外只提供使用接口,我们不需要也无法了解内部的实现
- 系统的封装:例如我们使用操作系统等,我们只需要知道怎么用,不需要了解内部的实现
6.1.2 属性的封装
-
原则
将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。
-
封装的目的
- 隐藏类的实现细节
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。
- 可以进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改,提高代码的可维护性。
- 实现步骤
- 使用private修饰成员变量
private 数据类型 变量名 ;
- 提供get/set方法,可以访问成员变量
//get方法的标准格式:
属性的类型 get属性名(){ //属性名首字母大写,因为它对于get方法来说是第二个单词
return 属性;
}
//set方法的标准格式:
void set属性名(属性的类型 形参名){
属性名 = 形参名;
}
6.1.3 权限修饰符
- 权限修饰符的可见性范围
权限修饰符 | 本类 | 本包 | 其它包子类 | 其它包非子类 |
---|---|---|---|---|
private | Y | |||
缺省 | Y | Y | ||
protect | Y | Y | Y | |
public | Y | Y | Y | Y |
- 权限修饰符的说明
//类:
【修饰符】 class 类名{
}
//属性:
【修饰符】 数据类型 属性名;
//方法:
【修饰符】 返回值类型 方法名(【形参列表】){
}
//属性一般都是private,方法一般都是public
//用public修饰的类必须与源文件名相同
- 对于类的成员:四种权限修饰符都可以使用;
- 对于外部的类:只能使用public和缺省两种(目前的类都是外部类);
- 案例演示
-
声明学生类
有属性:姓名,年龄,成绩,并且私有化,
声明姓名和成绩实例变量,私有化,提供get/set
getInfo()方法:用于返回学生对象的信息
- 测试类的main中创建一个可以装3个学生对象的数组,并且按照学生成绩排序,显示学生信息
class Test22_Exer{
public static void main(String[] args){
//创建一个可以装3个学生对象的数组
Student[] arr = new Student[3];//只是申明这个数组,可以用来装3个学生,此时里面没有学生对象
//手工赋值
arr[0] = new Student();
arr[0].setName("张三");
arr[0].setScore(78);
arr[1] = new Student();
arr[1].setName("李四");
arr[1].setScore(96);
arr[2] = new Student();
arr[2].setName("王五");
arr[2].setScore(56);
//先显示一下目前的顺序
for(int i=0; i<arr.length; i++){
System.out.println(arr[i].getInfo());
}
System.out.println("------------------------------------------");
//冒泡排序
for(int i=1; i<arr.length; i++){
for(int j=0; j<arr.length-i; j++){
//arr[j] > arr[j+1]//错误的
if(arr[j].getScore() > arr[j+1].getScore()){
//交换两个元素,这里是两个学生对象,所以temp也得是Student类型
Student temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
//再显示一下目前的顺序
for(int i=0; i<arr.length; i++){
System.out.println(arr[i].getInfo());
}
}
}
class Student{
private String name;
private int score;//使用int或double都可以
//get/set方法,
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
public void setScore(int s){
score = s;
}
public int getScore(){
return score;
}
public String getInfo(){
return "姓名:" + name +",成绩:" + score;
}
}
6.2 构造器(Constructor)
构造器又称为构造方法,那是因为它长的很像方法。但是和方法还有有所区别的。
要创建一个类的实例对象,必须调用一个对象的构造器,来完成类的实例初始化过程。实例初始化过程就是为实例变量赋初始值的过程。
当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。
6.2.1 构造方法的定义格式
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
注:
- 构造器名必须与它所在的类名必须相同。
- 它没有返回值,所以不需要返回值类型,甚至不需要void
- 如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
- 如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
- 构造器是可以重载的,既可以定义参数,也可以不定义参数。
- 构造器前面的修饰符:public,protected,缺省,private,不能被static、final、synchronized、abstract、native修饰
class Test08Constructor{
public static void main(String[] args){
//调用无参构造创建对象
Student stu1 = new Student();
//调用有参构造创建对象
Student stu2 = new Student("张三",23);
System.out.println(stu1.getInfo());
System.out.println(stu2.getInfo());
//Student stu3 = Student();//错误的调用无参构造
//System.out.println(Student());//错误的调用无参构造
}
}
class Student{
private String name;
private int age;
//无参构造
public Student(){
System.out.println("一个学生对象被创建");
//无参构造中,将属性初始化为默认值
}
//有参构造
public Student(String n, int a){
name = n;
age = a;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age;
}
}
6.3 this关键字
this代表当前对象的引用(地址值),即对象自己的引用。
- this可以用于构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁
- this用于实例方法中:表示调用该方法的对象,即谁在调用,this就代表谁。
6.3.1 使用格式
-
this.成员变量名
当方法的局部变量与当前对象的成员变量重名时,就可以在成员变量前面加this.,如果没有重名问题,就可以省略this.
this.成员变量名;
-
this.成员方法
调用当前对象自己的成员方法时,都可以加"this.",也可以省略,实际开发中都省略
【变量=】this.成员方法(【实参列表】);
-
this()或this(实参列表)
当需要调用本类的其他构造器时,就可以使用该形式。
要求:
-
必须在构造器的首行
-
如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(【实参列表】)",否则会发生递归调用死循环
-
6.4 static
static是一个成员修饰符,可以修饰类的成员:成员变量、成员方法、成员内部类(后面讲)、代码块(后面讲)。被修饰的成员是属于类的,而不是单单是属于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。
它可以修饰:属性、方法、成员内部类、代码块.
6.4.1 静态方法
-
static修饰的成员方法,称为类方法、静态方法。
-
什么情况下可以声明静态方法?
当这个方法的调用和运行和“对象”无关,这样的方法才可以声明为静态的。
- 语法格式:
【其他修饰符】 static 返回值类型 方法名 (【形参列表】){
// 执行语句
}
- 说明
-
在本类中,静态方法可以直接访问静态方法和静态变量;
-
在其他类中:可以使用“类名.方法"进行调用;
-
在静态方法中,不能出现:this,也不能直接使用本类的非静态的成员。相反,非静态的实例成员方法可以直接访问静态的类变量或静态方法。
注:静态方法只能访问静态成员。
public class Test{
public static void main(String[] args){
Son s = new Son();
s.fun();//有警告,没错误
Son.fun();
}
}
class Son{
private int a;
public static void fun(){
// method();//错误的
// System.out.println(a);//错误
// System.out.println(this.a);//错误
System.out.println("Son:fun()");
}
public void method(){
System.out.println("Son:method()");
}
}
6.4.2 静态变量
- 静态变量的值是该类所有对象**“共享的”**,不单独属于某一个对象。如果其中一个对象修改了静态变量,所有对象都会受到影响。因此,为了可读性,静态变量建议使用“类名.”进行访问,而不是通过“对象.”;
- 静态变量的get/set方法也是静态的;
- 静态变量的值,一般不在构造器中进行初始化;
- 当静态变量与局部变量同名时,我们可以使用“类名.”进行区别;
- 特别要强调,在静态方法中,绝对不能出现“this”等;
- 静态变量的值不是在堆的对象中,而是在“方法区”中;
public class TestStatic {
public static void main(String[] args) {
//建议静态的属性通过类名.进行赋值,因为它不单独属于某一个对象,而是这个类的所有对象“共享”
//Chinese.country = "中国人民共和国";
Chinese.setCountry("中国人民共和国");
//创建对象
Chinese c1 = new Chinese();
c1.setName("张三");
c1.setAge(23);
// c1.setCountry("中华人民共和国");
System.out.println(c1.getInfo());
Chinese c2 = new Chinese();
c2.setName("李四");
c2.setAge(24);
System.out.println(c2.getInfo());
c1.setCountry("中国");
System.out.println(c1.getInfo());
System.out.println(c2.getInfo());
}
}
class Chinese{//中国人
// public static String country;//国籍
private static String country;//国籍
private String name;//姓名
private int age;//年龄
public Chinese() {
}
public Chinese(String name, int age) {
this.name = name;
this.age = age;
}
public static String getCountry() {
return country;
}
public static void setCountry(String country) {
Chinese.country = country;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "国籍:" + country + ",姓名:" + name + ",年龄:" + age;
}
}
6.4.3 变量的分类与区别
1、变量按照数据类型分:
(1)基本数据类型的变量,里面存储数据值
(2)引用数据类型的变量,里面存储对象的地址值
2、变量按照声明的位置不同:
(1)成员变量
(2)局部变量
3、成员变量与局部变量的区别
(1)声明的位置不同
成员变量:类中方法外
局部变量:(1)方法的()中,即形参(2)方法体的{}的局部变量(3)代码块{}中
(2)存储的位置不同
成员变量:
如果是静态变量(类变量),在方法区中
如果是非静态的变量(实例变量),在堆中
局部变量:栈
(3)修饰符不同
成员变量:4种权限修饰符、static等多种修饰符
局部变量:不能有任何修饰符
(4)作用域
成员变量:
如果是静态变量(类变量),在本类中随便用,在其他类中使用“类名.静态变量"
如果是非静态的变量(实例变量),在本类中只能在非静态成员中使用,在其他类中使用“对象名.非静态的变量"
局部变量:有作用域,出了作用域就不能使用
(5)生命周期
成员变量:
如果是静态变量(类变量),和类相同,随着类的加载而分配,随着的类的卸载才消亡。
如果是非静态的变量(实例变量),和所属的对象相同,每一个对象是独立。对象创建时,才在堆中分配内存,随着对象被垃圾回收而消亡。
局部变量:每次方法调用执行都是新的,而且仅在作用域范围内有效。
6.5 继承
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。
继承别人的类称为子类(subclass),被继承的的类称为父类(superclass)。子类又称为派生类,父类又称为超类、基类。
6.5.1 继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 类与类之间产生了关系,是学习多态的前提。
6.5.2 继承的格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
6.5.3 继承的特点一:成员变量
1. 私有化
- 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
- 子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的公有方法(get/set)进行访问。
[外链图片转存失败(img-BcBguciC-1568520084839)(E:\02-课堂笔记\day11\img\继承私有成员1.jpg)]
/*
* 定义动物类Animal,做为父类
*/
class Animal {
// 定义name属性
private String name;
// 定义age属性
public int age;
// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的" + name + "在吃东西");
}
}
/*
* 定义猫类Cat 继承 动物类Animal
*/
class Cat extends Animal {
// 定义一个猫抓老鼠的方法catchMouse
public void catchMouse() {
System.out.println("抓老鼠");
}
}
/*
* 定义测试类
*/
public class ExtendDemo01 {
public static void main(String[] args) {
// 创建一个猫类对象
Cat t = new Cat();
// 为该猫类对象的name属性进行赋值
//t.name = "Tom";// 编译报错
// 为该猫类对象的age属性进行赋值
t.age = 2;
// 调用该猫的catchMouse()方法
t.catchMouse();
// 调用该猫继承来的eat()方法
t.eat();
}
}
2. 成员变量的重名问题
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的;如果子类父类中出现重名的成员变量,这时的访问是有影响的
-
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用
super
关键字,修饰父类成员变量,类似于之前学过的this
。- 格式:
super.父类成员变量名
- 案例演示
class Zi extends Fu { // Zi中的成员变量 int num = 6; public void show() { //访问父类中的num System.out.println("Fu num=" + super.num); //访问子类中的num System.out.println("Zi num=" + this.num); } } 演示结果: Fu num = 5 Zi num = 6
6.5.4 继承的特点二:成员方法
1. 成员方法重名——重写(Override)
- 如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子类重写了父类的show方法
public void show() {
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args) {
Zi z = new Zi();
// 子类中有show方法,只执行重写后的show方法
z.show(); // Zi show
}
}
-
说明
- 在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就优先用谁,如果没有则向上找。
- 在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
-
注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。
1) 重载和重写的区别
A. 方法的重载(Overload)
同一个类中,出现方法名称相同,形参列表不同的两个或多个方法。和返回值类型无关。
B. 方法的重写(Override)
当子类继承了父类时,子类会继承父类所有的方法,但是有的时候,父类的某个方法功能的“实现”(即方法体的代码)它不适用于子类,此时,子类可以选择对父类的这个方法的方法体进行“重写、覆写”,即重新实现。
class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
//智能手机类
class NewPhone extends Phone {
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum(){
//调用父类已经存在的功能使用super
super.showNum();
//增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
public class ExtendsDemo06 {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}
2) 注意事项
-
必须保证父子类之间方法的名称相同,参数列表也相同。
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。 -
子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类)。
小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。注意:如果返回值类型是基本数据类型和void,那么必须是相同
-
子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > 缺省 > private
备注:缺省不是汉字缺省,而是什么都不写,留空。 -
几种特殊的方法不能被重写
- 静态方法不能被重写
- 私有等在子类中不可见的方法不能被重写
- final方法不能被重写
6.5.5 继承的特点三:构造方法
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如下:
//子类的初始化过程中,必须先执行父类的初始化动作
class Fu {
private int n;
Fu(){
System.out.println("Fu()");
}
}
class Zi extends Fu {
Zi(){
// super(),调用父类构造方法
super();
System.out.println("Zi()");
}
}
public class ExtendsDemo07{
public static void main (String args[]){
Zi zi = new Zi();
}
}
输出结果:
Fu()
Zi()
6.5.6 继承的特点四:单继承限制
-
Java只支持单继承,不支持多继承;
即 :一个类只能有一个父类,不可以有多个父类。
-
Java支持多层继承(继承体系);
一个父类可以有多个子类。(顶层父类是Object类,所有的类默认继承Object,作为父类。)
6.6 super关键字
6.6.1 概念
-
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。
-
只能通过它访问父类在子类中可见的成员(属性、方法、构造器)
-
不能通过它访问父类的私有的属性、方法、构造器
-
如果跨包,还不能访问父类中缺省的属性、方法、构造器
-
6.6.2 super的用法
-
super.属性
当子类中声明了与父类中同名的属性时,在子类的某个方法中,需要区别是父类的,还是子类的属性时,可以加super.父类的属性。
-
super.方法
当子类重写了父类的方法,但是又想要在子类中调用父类被重写的方法,可以加"super."。
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test(30);
}
}
class Father{
int a = 10;
}
class Son extends Father{
int a = 20;
public void test(int a){
System.out.println(super.a);//10
System.out.println(this.a);//20
System.out.println(a);//30
}
}
-
super()或super(实参列表)
-
super():表示访问父类的无参构造.
-
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super();
-
如果父类有无参构造,那么在子类的构造器中想要表示调用父类的无参构造器时,可以写super(),也可以省略;
-
如果父类“没有”无参构造,那么在子类的构造器中不能写super(),必须使用下面的super(实参列表),而且还不能省略;
-
-
super(实参列表):表示访问父类的有参构造.
-
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现.
-
6.6.3 super和this的区别
-
super :代表父类的存储空间标识(可以理解为父亲的引用)。
-
通过super找成员变量和成员方法时,直接从父类空间(包含父类的父类继承的)找
-
super()或super(实参列表)只能从直接父类找
-
通过super只能访问父类在子类中可见的(非private,跨包还不能是缺省的)
-
-
this :代表当前对象的引用。
- 通过this找成员变量和成员方法时,先从当前类中找,没有的会往上找父类的。
- 但是this()或this(实参列表)只会在本类中找
this(...) -- 本类的构造方法
super(...) -- 父类的构造方法
-
关于变量(就近原则,起点不同,要注意可见性)
-
没有this.的:先从局部变量开始找,如果有就是局部变量
如果没有,再从本类的成员变量找,
再没有,从直接父类找
再没有,从间接父类找,
再没有,报错
-
有this.的,先从本类的成员变量开始找,如果有就是本类的成员变量
再没有,从直接父类找,
再没有,从间接父类找,
再没有,报错
-
有super.的,先从直接父类的成员变量开始找,如果有就是直接父类的成员变量
再没有,从间接父类找,
再没有,报错
-
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test();
}
}
class Father{
public void method(){
System.out.println("aa");
}
}
class Son extends Father{
public void method(){
System.out.println("bb");
}
public void test(){
method();//bb
this.method();//bb
super.method();//aa
}
}
6.6.4 this关键字(复习)
-
this代表的是当前对象:
- 在构造器中,this()代表的是正在new的那个对象;
- 在成员方法中,this代表的是调用当前方法的对象;
-
this.属性
当局部变量与成员变量同名时,在成员变量的前面加"this.
6.7 final关键字
-
概念
-
final意思是最终的,不可更改的;
-
它可以修饰:类(包括外部类和内部类)、方法(静态方法和非静态方法)、变量(静态变量,实例变量、局部变量) ;
-
不能用来修饰构造器,构造器的修饰符只能是4个权限修饰符(public,protected,缺省,private);
-
-
final修饰类
表示这个类不能被继承,没有子类。
-
final修饰方法
表示这个方法不能被子类重写。
-
声明常量
再没有,从间接父类找,
再没有,报错
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test();
}
}
class Father{
public void method(){
System.out.println("aa");
}
}
class Son extends Father{
public void method(){
System.out.println("bb");
}
public void test(){
method();//bb
this.method();//bb
super.method();//aa
}
}
6.6.4 this关键字(复习)
-
this代表的是当前对象:
- 在构造器中,this()代表的是正在new的那个对象;
- 在成员方法中,this代表的是调用当前方法的对象;
-
this.属性
当局部变量与成员变量同名时,在成员变量的前面加"this.