一、封装
封装是什么呢?不扯定义,简单点来说就是将对象的属性以private进行修饰,大家到隐藏的效果,需要调用时,通过高权限的访问权限修饰符(通常是public)定义的方法进行获取与修改。
举个例子,假设定义一个Person类,类中有着age这个属性,数据类型为int整型,那么我们可以通过对象名.age的方式进行调用。
package com.h.test3;
public class TestMain {
public static void main(String[] args) {
Person person = new Person();
person.age = 10;
System.out.println(person.age);
}
}
package com.h.test3;
public class Person {
public int age;
}
但是,这样是存在隐患的,Int型的变量的范围是-231~231-1,但是age的年龄是不允许为负值的,于是这个时候就要用到封装了。
封装的实现步骤:
- 修改属性的可见性(访问权限修饰符)
- 创建getter / setter方法
- 在getter / setter方法中加入属性控制语句
package com.h.test3;
public class TestMain {
public static void main(String[] args) {
Person person = new Person();
person.setAge(-10);
System.out.println(person.getAge()); // output: 0
person.setAge(10);
System.out.println(person.getAge()); // output: 10
}
}
package com.h.test3;
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0) {
this.age = age;
}
}
}
PS. 属性不一定需要都设置getter和setter方法,看个人的需求。
二、继承
说到继承,那就得先说一个类:Object,Object是所有类的父类,也就是所有类的老祖宗。
继承的理解其实也不难,可以把它等同于人类Person与学生Student的关系。
首先,我们知道学生一定是人类,但是反过来,人类不一定是学生。那么,在这种情况下,人类就是父类,学生就是子类。在人类的群体中,学生只是一部分,还有一部分可以是教师,可以是公司职员等等,但是不论是学生,还是教师,亦或者公司职员,他们都是人类。
那么,理解了继承后,我们来看看继承是如何实现的:
- 编写父类
- 编写子类,使用extends关键字继承父类
package com.h.test3;
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0) {
this.age = age;
}
}
}
package com.h.test3;
public class Student extends Person{
private String studentID;
public String getStudentID() {
return studentID;
}
public void setStudentID(String studentID) {
this.studentID = studentID;
}
}
package com.h.test3;
public class TestMain {
public static void main(String[] args) {
Person person = new Person();
person.setAge(10);
System.out.println(person.getAge()); // output: 10
Student student = new Student();
student.setAge(15);
System.out.println(student.getAge()); // output: 15
student.setStudentID("Stu01");
System.out.println(student.getStudentID()); // output: Stu01
}
}
使用类的好处就在于,我们可以避免写重复无意义的代码,只要使用继承,父类所允许子类继承的属性和方法,子类都可以调用。
继承实现后的初始顺序:
package com.h.test3;
public class Person {
static {
System.out.println("父类静态代码块");
}
{
System.out.println("父类构造代码块");
}
public Person() {
System.out.println("父类构造方法");
}
}
package com.h.test3;
public class Student extends Person{
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类构造代码块");
}
public Student() {
System.out.println("子类构造方法");
}
}
package com.h.test3;
public class TestMain {
public static void main(String[] args) {
Student student = new Student();
}
}
/* 运行结果如下:
父类静态代码块
子类静态代码块
父类构造代码块
父类构造方法
子类构造代码块
子类构造方法
*/
那么,说完继承,就不得不提一下方法的重写与重载。
针对方法的重写与重载,有这么一张表格:
重载 | 重写 | |
---|---|---|
有效范围 | 同一个类中 | 有继承关系的子类中 |
方法名 | 相同 | 相同 |
参数列表 | 不同(参数顺序,个数,类型) | 相同(参数顺序,个数,类型) |
返回值 | 任意 | 相同,允许不同,但必须为子类 |
访问修饰符 | 任意 | 访问范围大于等于父类的访问范围 |
参数名 | 无关 | 无关 |
举例如下:
package com.h.test3;
public class Person {
public void methodOverload() {
System.out.println("父类重载无参");
}
public void methodOverload(String str) {
System.out.println("父类重载带参");
}
public void methodOverride() {
System.out.println("父类未重写");
}
}
package com.h.test3;
public class Student extends Person{
@Override
public void methodOverload() {
System.out.println("子类重写无参");
}
@Override
public void methodOverride() {
System.out.println("子类重写");
}
}
package com.h.test3;
public class TestMain {
public static void main(String[] args) {
Person person = new Person();
Student student = new Student();
student.methodOverload(); // output: 子类重写无参
student.methodOverload("1"); // output: 父类重载带参
student.methodOverride(); // output: 子类重写
person.methodOverload(); // output: 父类重载无参
person.methodOverload("1"); // output: 父类重载带参
person.methodOverride(); // output: 父类未重写
}
}
三、多态
说起类的三大特征,封装、继承都很理解,也很容易理解,但是要说起多态,那似乎就变得有点抽象了起来,但尽管如此,多态却还是类的三个特征中最为重要的那一个。
什么是多态呢?多态是指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
我们可以举个例子,比如动物的叫声,首先动物是一个父类,猫和狗都是一个继承于动物父类的一个子类,动物有一个方法:叫,这时根据“叫”这个行为,猫和狗的表现是不一样的,这就是多态。
实现多态有着3个必要条件:
- 继承
- 重写
- 向上转型(父类引用指向子类对象)
package com.h.test3;
public class Animal {
public void say() {
System.out.println("神秘的叫声");
}
}
package com.h.test3;
public class Cat extends Animal {
@Override
public void say() {
System.out.println("喵喵");
}
}
package com.h.test3;
public class Dog extends Animal {
@Override
public void say() {
System.out.println("汪汪");
}
}
package com.h.test3;
public class TestMain {
public static void main(String[] args) {
Animal animal = new Animal();
animal.say(); // output: 神秘的叫声
animal = new Cat();
animal.say(); // output: 喵喵
animal = new Dog();
animal.say(); // output: 汪汪
}
}
从案例上我们可以看出,同一个对象animal,其指向的对象引用不同,那么对于重写的方法say()上来说,其所表现的形式也就不同,也就是说,animal的say()方法的表现形式由实例化对象决定。
既然说到了向上转型,那我们再来说说向下转型。向上转型是将父类引用指向子类对象,那么向下转型反过来就是将子类引用指向父类对象。
向上转型 | 向下转型 | |
---|---|---|
意义 | 父类引用指向子类对象 | 子类引用指向父类对象 |
举例 | Father f = new Son | Son s = (Son) f; |
举个例子:
package com.h.test3;
public class Animal {
public void say() {
System.out.println("神秘的叫声");
}
}
package com.h.test3;
public class Cat extends Animal {
@Override
public void say() {
System.out.println("喵喵");
}
public void eatFish() {
System.out.println("猫吃鱼");
}
}
package com.h.test3;
public class Dog extends Animal {
@Override
public void say() {
// TODO Auto-generated method stub
System.out.println("汪汪");
}
public void eatBone() {
System.out.println("吃骨头");
}
}
package com.h.test3;
public class TestMain {
public static void main(String[] args) {
Animal animal = new Animal();
// 向上转型
animal = new Cat();
animal.say(); // output: 喵喵
// animal = new Dog(); // 转型失败
// 向下转型
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.say(); // output: 喵喵
cat.eatFish(); // output: 猫吃鱼
}else {
System.out.println("转型失败");
}
}
}
通过上面这案例,我们可以把向上转型和向下转型转换成一个故事来例于理解。
很久以前,有一只猫,它喜欢自己“喵喵”的叫声和吃鱼。有一天,它被某种不知名的力量给改造成了“动物”(向上转型),变成“动物”的它,失去了身为猫时那种喜欢吃鱼的爱好(丢失了子类特有的成员方法与属性),但是幸运的是,它还保有从前那种“喵喵”的叫声(多态)。某天,它突然想吃点什么,听狗说骨头的味道很不错,于是它准备去变成狗去尝尝骨头的滋味,但是它失败了。于是,忍无可忍的它,决定变回猫,结果很显然,它成功了(向下转型),同时,它也找回了身为猫身的那种喜欢吃鱼的爱好,美滋滋地饱餐了一顿。可喜可贺,可喜可贺。