6.1 封装
6.1.1 封装概述
1、为什么需要封装?
我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
-
面向对象编程语言是对客观世界的模拟,客观世界里每一个事物的内部信息都是隐藏在对象内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。
-
适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
-
通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
-
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”:
-
高内聚:相关的功能和数据尽可能地组织在同一个类中,各个类内部数据操作细节自己完成,以确保其独立性。
-
低耦合:各个类之间的依赖关系应尽可能低。也就是说,一个类的变化应尽量减少对其他类的影响。有助于提高软件的可扩展性、可复用性和灵活性。
-
2、如何实现封装呢?
实现封装就是指控制类或成员的可见性范围?这就需要依赖访问控制修饰符,也称为权限修饰符来控制。
权限修饰符:private,缺省,protected,public
修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
外部类:public和缺省
成员变量、成员方法、构造器、成员内部类:private,缺省,protected,public
6.1.2 成员变量/属性私有化
在面向对象编程中,将成员变量(也称为属性)私有化是一种常见的做法。这样做的目的是保护数据,防止外部直接访问和修改,以确保数据的完整性和安全性。同时,通过提供公共的getter和setter方法来访问和修改这些私有变量,可以更好地控制对这些变量的访问和修改。
1、成员变量封装的目的
-
隐藏类的实现细节
-
让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
2、实现步骤
-
使用
private
修饰成员变量
private 数据类型 变量名 ;
代码如下:
public class Person { private String name; private int age; private boolean marry; }
-
提供
getXxx
方法 /setXxx
方法,可以访问成员变量,代码如下:
public class Person { private String name; private int age; private boolean marry; public void setName(String n) { name = n; } public String getName() { return name; } public void setAge(int a) { age = a; } public int getAge() { return age; } public void setMarry(boolean m){ marry = m; } public boolean isMarry(){ return marry; } }
3、测试
package com.haogu.encapsulation; public class TestPerson { public static void main(String[] args) { Person p = new Person(); //实例变量私有化,跨类是无法直接使用的 /* p.name = "张三"; p.age = 23; p.marry = true;*/ p.setName("张三"); System.out.println("p.name = " + p.getName()); p.setAge(23); System.out.println("p.age = " + p.getAge()); p.setMarry(true); System.out.println("p.marry = " + p.isMarry()); } }
6.1.3 IDEA自动生成get/set方法模板
1、如何解决局部变量与实例变量同名问题
当局部变量与实例变量(非静态成员变量)同名时,在实例变量必须前面加“this.”
package com.haogu.encapsulation; public class Employee { private String name; private int age; private boolean marry; 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 boolean isMarry() { return marry; } public void setMarry(boolean marry) { this.marry = marry; } }
package com.haogu.encapsulation; public class TestEmployee { public static void main(String[] args) { Employee e = new Employee(); e.setName("张三"); System.out.println("e.name = " + e.getName()); e.setAge(23); System.out.println("e.age = " + e.getAge()); e.setMarry(true); System.out.println("e.marry = " + e.isMarry()); } }
2、IDEA自动生成get/set方法模板
-
大部分键盘模式按Alt + Insert键。
-
部分键盘模式需要按Alt + Insert + Fn键。
6.2 继承
6.2.1 继承的概述
继承有延续(下一代延续上一代的基因、财富)、扩展(下一代和上一代又有所不同)的意思。
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用或更一般,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承的好处
-
提高代码的复用性。
-
提高代码的扩展性。
-
表示类与类之间的is-a关系
6.2.2 继承的语法格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
【修饰符】 class 父类 { ... } 【修饰符】 class 子类 extends 父类 { ... }
1、父类
package com.haogu.inherited.grammar; /* * 定义动物类Animal,做为父类 */ public class Animal { // 定义name属性 String name; // 定义age属性 int age; // 定义动物的吃东西方法 public void eat() { System.out.println(age + "岁的" + name + "在吃东西"); } }
2、子类
package com.haogu.inherited.grammar; /* * 定义猫类Cat 继承 动物类Animal */ public class Cat extends Animal { int count;//记录每只猫抓的老鼠数量 // 定义一个猫抓老鼠的方法catchMouse public void catchMouse() { count++; System.out.println("抓老鼠,已经抓了" + count + "只老鼠"); } }
3、测试类
package com.haogu.inherited.grammar; public class TestCat { public static void main(String[] args) { // 创建一个猫类对象 Cat cat = new Cat(); // 为该猫类对象的name属性进行赋值 cat.name = "Tom"; // 为该猫类对象的age属性进行赋值 cat.age = 2; // 调用该猫继承来的eat()方法 cat.eat(); // 调用该猫的catchMouse()方法 cat.catchMouse(); cat.catchMouse(); cat.catchMouse(); } }
6.2.3 继承的特点
1.子类会继承父类所有的实例变量和实例方法
从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。
-
当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
-
当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。
所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。
2.Java只支持单继承,不支持多重继承
public class A{} class B extends A{} //一个类只能有一个父类,不可以有多个直接父类。 class C extends B{} //ok class C extends A,B... //error
3.Java支持多层继承(继承体系)
class A{} class B extends A{} class C extends B{}
顶层父类是Object类。所有的类默认继承Object,作为父类。
4.一个父类可以同时拥有多个子类
class A{} class B extends A{} class D extends A{} class E extends A{}
6.2.5 权限修饰符限制
权限修饰符:private,缺省,protected,public
修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √(本包子类非子类都可见) | × | × |
protected | √ | √(本包子类非子类都可见) | √(其他包仅限于子类中可见) | × |
public | √ | √ | √ | √ |
外部类:public和缺省
成员变量、成员方法等:private,缺省,protected,public
1、外部类要跨包使用必须是public,否则仅限于本包使用
(1)外部类的权限修饰符如果缺省,本包使用没问题
(2)外部类的权限修饰符如果缺省,跨包使用有问题
2、成员的权限修饰符
(1)本包下使用:成员的权限修饰符可以是public、protected、缺省
(2)跨包下使用:要求严格
(3)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义
3、父类成员变量私有化(private)
子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。
父类代码:
package com.haogu.inherited.modifier; public class Person { private String name; private int age; 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 "姓名:" + name + ",年龄:" + age; } }
子类代码:
package com.haogu.inherited.modifier; public class Student extends Person { private int score; public int getScore() { return score; } public void setScore(int score) { this.score = score; } public String getInfo(){ // return "姓名:" + name + ",年龄:" + age; //在子类中不能直接使用父类私有的name和age return "姓名:" + getName() + ",年龄:" + getAge(); } }
测试类代码:
package com.haogu.inherited.modifier; public class TestStudent { public static void main(String[] args) { Student student = new Student(); student.setName("张三"); student.setAge(23); student.setScore(89); System.out.println(student.getInfo()); } }
6.2.6 方法重写(Override)
我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)
1、方法重写
比如新的手机增加来电显示头像的功能,代码如下:
package com.haogu.inherited.method; public class Phone { public void sendMessage(){ System.out.println("发短信"); } public void call(){ System.out.println("打电话"); } public void showNum(){ System.out.println("来电显示号码"); } }
package com.haogu.inherited.method; //smartphone:智能手机 public class Smartphone extends Phone{ //重写父类的来电显示功能的方法 public void showNum(){ //来电显示姓名和图片功能 System.out.println("显示来电姓名"); System.out.println("显示头像"); } }
package com.haogu.inherited.method; public class TestOverride { public static void main(String[] args) { // 创建子类对象 Smartphone sp = new Smartphone(); // 调用父类继承而来的方法 sp.call(); // 调用子类重写的方法 sp.showNum(); } }
2、在子类中如何调用父类被重写的方法
package com.haogu.inherited.method; //smartphone:智能手机 public class Smartphone extends Phone{ //重写父类的来电显示功能的方法 public void showNum(){ //来电显示姓名和图片功能 System.out.println("显示来电姓名"); System.out.println("显示头像"); //保留父类来电显示号码的功能 super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出 } }
3、IDEA重写方法快捷键:Ctrl + O
package com.haogu.inherited.method; //smartphone:智能手机 public class Smartphone extends Phone{ //重写父类的来电显示功能的方法 public void showNum(){ //来电显示姓名和图片功能 System.out.println("显示来电姓名"); System.out.println("显示头像"); //保留父类来电显示号码的功能 super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出 } @Override public void call() { super.call(); System.out.println("视频通话"); } }
@Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
4、重写方法的要求
1.必须保证父子类之间重写方法的名称相同。
2.必须保证父子类之间重写方法的参数列表也完全相同。
2.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
3.子类方法的权限必须【大于等于】父类方法的权限修饰符。
注意:public > protected > 缺省 > private
父类私有方法不能重写
跨包的父类缺省的方法也不能重写
5、方法的重载和方法的重写
方法的重载:方法名相同,形参列表不同。不看返回值类型。
方法的重写:见上面。
(1)同一个类中
package com.haogu.inherited.method; public class TestOverload { public int max(int a, int b){ return a > b ? a : b; } public double max(double a, double b){ return a > b ? a : b; } public int max(int a, int b,int c){ return max(max(a,b),c); } }
(2)父子类中
package com.haogu.inherited.method; public class TestOverloadOverride { public static void main(String[] args) { Son s = new Son(); s.method(1);//只有一个形式的method方法 Daughter d = new Daughter(); d.method(1); d.method(1,2);//有两个形式的method方法 } } class Father{ public void method(int i){ System.out.println("Father.method"); } } class Son extends Father{ public void method(int i){//重写 System.out.println("Son.method"); } } class Daughter extends Father{ public void method(int i,int j){//重载 System.out.println("Daughter.method"); } }