Java对象的封装、继承、多态
一.封装(Encapsulation)
1.1概念
如字面意思,将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
我们对程序的追求:高内聚,低耦合
高内聚:类的内部数据操作自己完成,不允许外部干扰
低耦合:仅暴露少量的方法给外部使用,减少类(模块)之间的相互依赖
1.2封装的优点
-
良好的封装能够减少耦合。
-
类内部的结构可以自由修改。
-
可以对成员变量进行更精确的控制。
-
隐藏信息,实现细节。
1.3实现方法
首先记住这样一句话:属性私有,get/set(大多数对属性私有)
记住这样的两个基本的成员变量修饰符:(关于其他修饰符,会另起一章介绍)
(1)public:公共访问控制符,指定该变量为公共的,他可以被任何对象的方法访问。
(2)private:私有访问控制符,指定该变量只允许自己的类的方法访问,其他任何类(包括子类)中的方法均不能访问。
- 修改属性修饰符:private
- 创建get/set(用于属性的读写,也就是访问方法)
- 在get/set方法中加入属性控制语句
public class Person {
//属性私有
private String name;
private int age;
private char gender;
//创建get/set方法
//获得这个数据
public String getName(){
return this.name;
}
//设计数据值
public void setName(String name){
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
//防止数据不合法
if (age<120 && age>0){
this.age = age;
}else
{this.age = 0;}
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
}
public class Application {
public static void main(String[] args) {
//实例化 类 对象名 = new 类名();
Person zhangsan = new Person();
zhangsan.setName("张三");
zhangsan.setAge(200);
zhangsan.setGender('男');
System.out.println(zhangsan.getName());
System.out.println(zhangsan.getAge());
System.out.println(zhangsan.getGender());
}
}
这样写的好处:
- 提高代码安全性,保护数据
- 隐藏代码实现细节(例如案例中的年龄合法性设置)
- 统一接口
- 提高系统维护性
二.继承
2.1概念
继承是类与类的一种关系。比如“男人,女人”继承“人”,这里人类是男人,女人类的父类或者基类,男人,女人类类是人类的子类或者派生类。如下图所示:
在Java中继承是单继承,没有多继承
简单理解,父亲可以有多个儿子,而儿子只能有一个父亲
需要注意的是,继承是类与类之间的关系之一,除此还有依赖,组合,聚合等
2.2实现方法
extends意为扩展,延伸
class 子类 extends 父类
class Men extends Person
2.3继承好处
子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用;
私有的可以继承,但是无法访问
例如:
//父类
public class Person {
private int money = 1000;
public void say(){
System.out.println("说了一句话");
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
//子类
public class Men extends Person{
}
//主方法
public class Application {
public static void main(String[] args) {
Men men = new Men();
men.say();
System.out.println(men.getMoney());
}
}
2.3 Object类
我们Men方法界面按下Ctrl + H,可以弹出层次结构。
可以清晰地看到类之间的关系。这里我们发现有一个没有写的Object类,我们切换其他类界面调出结构图,发现都有这个Object类。那么这是个什么呢?
在java中,所有的类,都直接或者间接继承于这个类
2.4 super关键字
在对象的内部使用,可以代表父类对象。
//父类
public class Person {
public String name = "张三";
public void print(){
System.out.println("我是父类");
}
}
//子类
public class Men extends Person {
private String name = "李四";
public void test() {
System.out.println(this.name); //李四
System.out.println(super.name); //张三
}
public void print() {
System.out.println("我是子类");
}
public void test1(){
this.print(); //我是子类
super.print(); //我是父类
}
}
public class Application {
public static void main(String[] args) {
Men men = new Men();
men.test();
men.test1();
}
}
在涉及到构造器时
//父类
public class Person {
public Person() {
System.out.println("父类无参执行啦");
}
}
//子类
public class Men extends Person {
public Men() {
System.out.println("子类无参执行啦");
}
}
public class Application {
public static void main(String[] args) {
Men men = new Men();
}
}
我们对比结果会发现默认先执行了父类的构造器,然后子类的。相当于在子类的无参构造器的第一行有super();
如果自己用super关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的第一行。
注意:如果在父类中没有显式定义无参构造,定义了有参构造。这时,子类只能显式调用有参构造
2.5 方法重写
2.5.1什么方法重写?什么时候进行重写?
子类如果对继承的父类的方法不适合,可以自己编写继承的方法,这种方式就称为方法重写。当调用方法时会优先调用子类的方法。
2.5.2方法重写特征
子类重写的方法与父类方法:
- 方法名称相同
- 参数类型,个数相同
- 返回值类型相同
//父类
public class Person {
public void test(){
System.out.println("父类测试");
}
}
//子类
public class Men extends Person {
@Override //注解:有功能的注释
public void test() {
System.out.println("子类测试");;
}
}
public class Application {
public static void main(String[] args) {
Men a = new Men();
a.test();
//父类的引用(b)指向了子类
Person b = new Men(); //子类重写了父类的方法
b.test();
}
}
idea中可按快捷键Alt +insert 重写方法,override意为重写
注意点:
- 重写方法后,执行的是子类的方法
- 只有非静态方法可以重写
- 修饰符:范围可以扩大,但是不能缩小(public>Protexted>Default>provate)
- 抛出的异常:范围可以缩小,但是不能扩大
三.多态
3.1概念
即同一方法可以根据发送对象(引用类型)的不同采用多种不同的行为方式
多态的三个前提:
- 继承
- 重写
- 父类引用指向子类对象
3.2实例
//父类
public class Person {
public void test(){
System.out.println("父类测试");
}
}
//子类
public class Men extends Person {
@Override //注解:有功能的注释
public void test() {
System.out.println("子类测试");
}
public void run(){
System.out.println("run");
}
}
public class Application {
public static void main(String[] args) {
//一个对象的实际类型是确定的(等号右侧),但是可以指向的引用类型不确定(等号左侧)
Men a = new Men(); //父类的引用可以指向本类
Person b = new Men(); //父类的引用可以指向子类
Object c = new Men(); //父类的引用可以指向子类
//对象执行的方法,取决于对象左边的引用类型
a.test();
a.run(); //子类可以调用自己的,和继承自父类的
b.test();
((Men) b).run(); //父类只能调用自己的,不能调用子类的,否则会出现强制转换
}
}
当父类的引用指向子类的对象时,该对象将只是看成一种特殊的父类(里面有重写的方法和属性)
不存在子类的引用属性指向父类,就好比“狗是一种动物”,但是不能说“动物是一种狗”
3.3多态的注意事项
- 继承是多态的基础
- 多态是方法的多态 属性不存在
3.4 instanceof
instanceof是Java的一个二元操作符,和==,>,<是同一类。也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据.
public class Application {
public static void main(String[] args) {
Object object = new Student();
System.out.println(object instanceof Student);
System.out.println(object instanceof Person);
System.out.println(object instanceof Object);
System.out.println(object instanceof Teacher);
System.out.println(object instanceof String);
}
}
返回结果
true
true
true
false
false
public class Application {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student instanceof Student);
System.out.println(student instanceof Person);
System.out.println(student instanceof Object);
//System.out.println(student instanceof Teacher);//编译报错
//System.out.println(student instanceof String);//编译报错
}
}
返回结果
true
true
true
关系如下图
但是instanceof在Java的编译状态和运行状态是有区别的:
在编译状态中,右侧可以是左侧的父类,自身类,子类。在这三种情况下Java编译时不会报错。
在运行转态中,右侧可以是左侧对象的父类,自身类,子类。在前两种情况下的结果为true,最后一种为false。
3.5类型转换
public class Application {
public static void main(String[] args) {
//类型之间的转换(高转低强制转换)
Person a = new Student();
//此时a对象无法使用go方法
//转换为Student类型
//高 ==================> 低
Student a1 = (Student) a;
a1.go();
//上面两句写为一句
((Student) a).go();
//类型之间的转换(低转高)子类转换为父类可能会丢失自己本来的方法
Student b = new Student();
b.go();
Person b1 = b; //此时b1无法调用go方法
}
}
- 父转子(高转低;向下转型):父子都有相同的成分,但是子类还有自己本身的方法.所以父转子时就需要在开辟空间,所以是强制转换.有风险,易发生数据溢出
- 子转父(低转高;向上转型):由于已经继承父类所有,并且还有自己本身的方法.所以转换为父类就要删去自身的方法