一、封装
1、封装的概念
封装的引入
我要看电视,只需要按一下开关和换台就可以了。有必要了解电视机内部的结构吗?有必要碰碰显像管吗?制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。具体内部是怎么实现的,我们不需要操心。
需要让用户知道的才暴露出来,不需要让用户知道的全部隐藏起来,这就是封装。从java的角度上来分析,封装是指隐藏对象的属性和实现细节,仅对外提供公共的访问方式。
封装的优点
- 提高代码的安全性。
- 提高代码的复用性。
- 使用时,只需要了解访问方式,不需要知道内部实现细节。
封装原则:
1、将不需要对外提供的内容都隐藏起来。
2、把属性都隐藏,提供公共方法对其访问。
2、访问控制符
Java中使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。
下面详细讲述它们的访问权限问题,如下表所示:
| 访问权限修饰符 | ||||
| 修饰符 | 同一个类 | 同一个包 | 子类 | 所有类 |
| private | * | |||
| default | * | * | ||
| protected | * | * | * | |
| public | * | * | * | * |
private:表示私有的,作用范围是当前类(类可见性)。
default:表示默认的,作用范围是当前类+当前包(包可见型)。
protected:表示受保护的,作用范围是当前类+当前包+其它包中的子类(子类可见性)。
public:表示公开的,作用范围是是当前类+当前包+其它包(项目可见性)。
注意事项:
1、类中的属性和方法访问权限共有四种:private、default、protected和public。
2、类的访问权限只有两种:public和default。
3、访问权限控制符不能修饰局部变量。
3、成员变量封装
为什么要对成员变量的访问进行封装?因为没有封装的代码会出现一些问题,例如以下代码。
class Student {
// 成员变量
public String name;
public int age;
// 成员方法
public void show() {
System.out.println("name: " + name + " age: " + age);
}
}
public class EncapsulationDemo {
public static void main(String[] args) {
Student p = new Student();
p.name = "小明";
// 年龄可以通过这种方式随意赋值,没有任何限制
p.age = -14;
p.show();
}
}
我们都知道,年龄不可能是负数,也不可能超过130岁,但是如果没有使用封装的话,便可以给年龄赋值成任意的整数,这显然不符合我们的正常逻辑思维,而封装恰恰能解决这样的问题。
我们在操作属性时,通常会对数据进行封装,这样就可以增加了数据访问限制,增加了程序可维护性。
实现方式:提供相应的get/set方法来访问相关属性,使用set方法实现对属性的赋值操作,使用get方法实现对属性的取值操作。
关于使用权限修饰符补充:
- 属性一般使用private访问权限。
- 提供访问相关属性get/set方法通常是public修饰的,以方便对属性的赋值与读取操作。
- 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。
【例】JavaBean的封装实例
class Student {
// 成员方法,一般使用private修饰
private String name;
private int age;
private boolean marry; // 是否已婚
// set和get方法,一般使用public修饰
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;
}
// boolean类型的成员变量,get方法是is开头的。
public boolean isMarry () {
return marry;
}
public void setMarry(boolean marry) {
this.marry = marry;
}
}
public class EncapsulationDemo {
public static void main(String[] args) {
Student stu = new Student();
// 赋值操作
// stu.name = "小明"; // 编译失败
stu.setName("小明");
stu.setAge(18);
stu.setMarry(true);
// 取值操作
// String name = stu.name; // 编译失败
String name = stu.getName();
int age = stu.getAge();
boolean isMarry = stu.isMarry);
System.out.println("name:"+name+" age:"+age+" isMarry:"+isMarry);
}
}
JavaBean 是一种JAVA语言写成的可重用组件。为写成JavaBean,类必须是具体的和公共的,并且具有无参构造器。JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性,并提供set和get方法操作成员成员变量。
接下来使用封装来解决一下上面提到的年龄非法赋值的问题。
【例】可见性封装的使用案例
class Student {
// 成员方法
private String name;
private int age;
// 构造方法
public Student() {}
public Student(String name, int age) {
this.name = name;
// 调用set方法来给年龄赋值
setAge(age);
}
// set和get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 新增需求:获取年龄的时候要求返回虚岁
public int getAge() {
return age + 1;
}
// 设置年龄的时候是实岁
public void setAge(int age) {
// 在赋值之前先判断年龄是否合法
if (age > 130 || age < 0) {
this.age = 0; // 不合法赋默认值0
} else {
this.age = age; // 合法才能赋值给属性
}
}
// 成员方法
void show() {
// 输出:name: 小明 age: 0,思考:为什么会这样???
System.out.println("name: " + name + " age: " + age);
}
}
public class EncapsulationDemo {
public static void main(String[] args) {
Student stu = new Student();
// 赋值操作
stu.setName("小明");
stu.setAge(150);
// 取值操作
String name = stu.getName();
int age = stu.getAge();
// 调用成员方法
stu.show();
// 输出:name:小明 age:1,思考:为什么会这样???
System.out.println("name:" + name + " age:" + age);
}
}
二、继承
1、继承的概念
封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而言它的内部细节是隐藏的,暴露给外界的只是它的访问方法。
在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。从英文字面意思理解,extends的意思是“扩展”。子类是父类的扩展,现实世界中的继承无处不在,如下图所示:

现实世界中的继承
上图中,哺乳动物继承了动物。意味着,动物的特性,哺乳动物都有;在我们编程中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类(也称派生类),现有类被称作父类(也称作超类、基类等),子类会自动拥有父类所有可继承的属性和方法。
2、继承的使用
继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
在Java之中,如果要实现继承的关系,可以使用如下的语法:
class 父类 {
}
class 子类 extends 父类 {
}
子类又被称为派生类,父类又被称为超类(Super Class)。
【例】使用extends实现继承
// 父类:Person类
class Person {
// 成员变量
private String name;
// set和get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 成员方法
public void eat() {
System.out.println("eat...");
}
}
// 子类,Teacher类
public class Teacher extends Person {
// 成员变量
private String title; // 职称
// set和get方法
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
// 成员方法
public void teaching() {
System.out.println("授课...");
}
}
// 测试类
public class InheritanceDemo {
public static void main(String[] args) {
Teacher t = new Teacher();
t.setName("周老师"); // 父类属性
t.setTitle("JavaSE阶段讲师"); // 子类属性
t.eat(); // 父类方法
t.teaching(); // 子类方法
System.out.println("name:"+t.getName()+" age:"+t.getAge());
}
}
在上述代码中,Teacher类通过extends关键字继承了Person类,这样Teacher类便是Person类的子类。
3、从运行结果不难看出,子类虽然没有定义name属性和eat()方法,但是子类却能访问。这就说明,子类在继承父类的时候,会自动拥有父类的属性和方法,并且子类还可以拥有自己的属性和方法,即子类可以对父类进行扩展!
使用继承的好处
1、继承的出现提高了代码的复用性,提高软件开发效率。
2、继承的出现让类与类之间产生了关系,提供了多态的前提。
继承的注意点
- java只支持单继承,不允许多继承,也就是说一个类只能有一个直接父类。
- java支持多层(重)继承,即一个类的父类可以再去继承另外的父类(继承体系)。
- 如果定义一个类时,没有使用extends,则它的父类默认是:java.lang.Object。
4、方法的重写
当父类的方法不能满足子类的需求时,我们可以在子类中重写父类的方法,重写也称为复写或者覆盖。
方法重写需要符合以下三个要点:
==,要求重写方法的“方法名”和“形参列表(个数和类型)”必须一模一样!
>=,要求子类重写方法的修饰符权限必须大于等于父类被重写方法的修饰符权限。
public > protected > default > private
注意:
1)父类中被private修饰的方法不能被子类重写!“>=”强调的是权限关系。
2)父类被static修饰的方法不能被子类重写!
<=,返回值类型和异常类型,子类小于等于父类,“<=”强调的是辈分关系
如果返回值类型为“基本数据类型”、“字符串类型”和“void”,
那么要求子类重写方法的返回值类型必须和父类被重写方法的返回值类型保持一致!
如果返回值类型为“引用数据类型”(不包含“字符串”),
要么子类重写方法的返回值类型和父类被重写方法的返回值类型保持一致。
要么子类重写方法的返回值类型为父类被重写方法的返回值类型的子类。
【例】方法重写案例
// 父类
class Parent {
public void show(){
System.out.println("父类中的 show方法执行");
}
}
// 子类
class Child extends Parent {
// 子类重写父类的show方法
public void show(){
System.out.println("子类中的 show方法执行");
}
}
public class ExtendsMethodDemo {
public static void main(String[] args) {
Child p = new Child();
p.show(); // 执行的是子类的show方法
}
}
注意:如果子类重写了父类的方法,通过子类对象调用该方法时,调用的方法就是被覆写过的方法!
如果子类方法需要使用父类方法的功能,可使用super关键字来调用父类方法,该关键字引用了当前对象的父类对象。这样,即沿袭了父类的功能,又定义了子类特有的功能 (super关键字的使用和this非常相似)。
【例】方法重写应用案例
// 功能机
class Phone {
void showPhoneNum() {
System.out.println("显示来电手机号码");
}
}
// 智能机
class IntelligentPhone extends Phone {
// 覆盖父类方法,并添加新的功能
void showPhoneNum() {
// 使用super调用父类成员方法
super.showPhoneNum();
System.out.println("显示来电姓名");
System.out.println("显示来电头像");
}
}
public class OverrideDemo {
public static void main(String[] args) {
IntelligentPhone ip = new IntelligentPhone();
ip.showPhoneNum(); // 调用的是子类的方法
}
}
方法重写和重载有什么区别?
| 区别点 | 方法重载 | 方法重写 |
| 英文名 | overload | override |
| 范围 | 发生在同一个类中 | 发生在继承关系中 |
| 定义 | 两同三不同 | 方法名相同、形参列表相同 |
| 对访问权限没有要求 | 访问权限,子类大于等于父类 | |
| 对返回值类型和异常类型没有要求 | 返回值类型和异常类型,子类小于等于父类 |
三、super关键字
super可以理解为直接父类对象的引用,或者说super指向子类对象的父类对象存储空间。我们可以通过super来访问父类中被子类覆盖的方法或属性,super的使用和this关键字非常的相似。
1、super关键字使用
super可以理解为直接父类对象的引用,可以通过super来访问父类中被子类覆盖的方法或属性,super的使用和this关键字非常的相似。
【例】super关键字使用
class Parent {
String name = "父类";
public void study() {
System.out.println("父类中的study方法");
}
}
class Child extends Parent {
public void show() {
// 调用父类的成员方法
super.study();
// 调用父类的成员变量
System.out.println("name:" + super.name);
}
}
public class SuperDemo extends Parent {
public static void main(String[] args) {
new Child().show();
}
}
子类可以继承的父类成员变量,如果在子类中出现了和父类同名的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。
子类可以继承的父类成员方法,如果在子类中出现了和父类同名的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。
注意:在子类中不能直接访问父类用private修饰的成员变量和成员方法,因为private属于类可见性。
2、super调用父类构造方法
子类继承父类,子类的构造方法必须调用父类的构造方法,如果子类的构造方法中没有显示的调用父类的构造方法,则系统默认调用父类的无参数构造方法。
【例】默认调用父类的无参数构造方法
class Parent {
public Parent() {
System.out.println("parent 构造方法");
}
}
class Child extends Parent {
public Child() {
// 如我们没有显示的使用super(),那么会默认调用父类的无参构造方法
System.out.println("Child 构造方法");
}
}
public class SuperDemo {
public static void main(String[] args) {
// 创建子类对象,父子类构造方法都执行!!!!
new Child();
}
}
若想指定调用父类中的有参构造函数,那么可在super()中添加参数,指定调用父类中的某个构造函数,而且必须放在构造方法的第一条语句。
【例】指定调用父类构造方法
class Parent {
String name;
public Parent() {} // 无参构造方法建议加上!!!
public Parent(String name) {
this.name = name;
System.out.println("parent 构造方法");
}
}
class Child extends Parent {
int age;
public Child(String name, int age) {
// super(形参列表) 必须放在构造方法的第一行
super(name);
this.age = age;
System.out.println("Child 构造方法");
}
void show() {
// 输出:name:小明 age:18
System.out.println("name:" + name + " age:" + age);
}
}
public class SuperDemo {
public static void main(String[] args) {
Child child = new Child("小明", 18);
child.show();
}
}
super()和this()调用构造方法总结:
1、super():调用父类中的某一个构造方法(必须为构造方法中的第一条语句)。
2、this():调用本类中某一个构造方法(必须为构造方法中的第一条语句)。
super()和this()都必须在第一条语句,就证明super()和this()不可以同时出现在同一个构造方法中,其原因是super()保证在子类访问父类之前完成对父类的初始化操作,而this()保证父类初始化的唯一性。
this和super总结
this和super使用非常的相似,但是表现上却是大不相同,详情请看以下表格。
| 区别点 | this | super |
| 定义 | this代表本类对象的引用 | super代表父类存储空间 |
| 使用 | this.属性 this.方法 this() | super.属性 super.方法() super() |
| 调用构造 | 调用本类构造,放在第一条语句 | 调用父类构造,放在第一条语句 |
| 查找范围 | 先从本类找,找不到查找父类 | 直接查找父类,不查找子类 |
3、创建对象内存分析
public static void main(String[] args) {
Child child = new Child("小明", 18);
}
当程序执行main方法中的第一个语句时,首先在栈空间里面会产生一个变量child,这个变量child用来保存新创建的对象的首地址。
由于Child类继承于Parent类,而Parent类又继承于Object类,所以当我们new一个Child对象的时候,Child对象里面会包含有一个Object对象和Parent对象。
Object对象中没有成员变量,而Parent对象中包含name成员变量,因为name成员变量在Parent类中并没有对它进行显示初始化,所以系统默认给它初始化为null。
Child类在继承Parent类name属性的同时(Object类中没有属性),自己也单独定义了一个age属性,所以当我们new出一个子类对象的时候,这时这个对象会拥有两个属性,一个是从父类继承下来的name,另一个是自己的age。在子类的成员变量age并没有对它进行显示初始化,所以编译器默认给它初始化为0。此时内存的布局如下图所示:

当new一个对象出来的时候,这个对象会产生一个this的引用,这个this引用指向对象自身。如果new出来的对象是一个子类对象的话,那么这个子类对象里面还会有一个super引用,这个super指向当前对象里面的父类存储空间。
对象创建完毕后,开始调用构造方法。先进入子类的构造方法,因为第一条语句是super(name),所以进入父类构造方法中,先对父类中的成员变量做初始化,然后再在子类中的构造方法中给子类的成员变量做初始化。此时内存的布局如下图所示:

&spm=1001.2101.3001.5002&articleId=157181774&d=1&t=3&u=29bbef9993574e2b8b8e5edc538910b4)
212

被折叠的 条评论
为什么被折叠?



