面向对象(封装、继承)

封装

封装就是信息隐藏,只保留一些对外接口使之与外部发生联系。 用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。

封装的好处:

  • 减少耦合
  • 类的内部可以自由修改
  • 可以对成员更精确的控制
  • 隐藏信息和实现细节

用一个经典的例子来说明

//丈夫类
public class Mann {

    /**
     * 对属性的封装
     * 丈夫的私有属性
     */
    private String name;
    private int age;
    private Wife wife;

    /**
     * 私有对象对外开放的接口,丈夫肯定不能开放getWife接口
     */
    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 void setWife(Wife wife) {
        this.wife = wife;
    }
}
//妻子类
public class Wife {

    /**
     * 妻子的私有属性
     */
    private String name;
    private int age;
    private Mann man;


    /**
     * 私有属性对外开放的接口,女人一般不会开放年龄接口
     */
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Mann getMan() {
        return man;
    }

    public void setMan(Mann man) {
        this.man = man;
    }
}

解耦 如果我们不使用封装,那么对象就没有setter()和getter(),丈夫类如下:

public class Mann {
    public String name ;
    public int age ;
    public Wife wife;
}
// 我们应该这样来使用它
Mann man = new Mann();
        man.age = 30;
        man.name = "张三";

如果有一天产品经理不知道吃了什么,突然笑呵呵的叫你把年龄改成String类型,如果有上百个地方都调用了,你不是要崩溃?
如果使用了封装,只需要在封装的这一层转换一下就行了,这是对付需求变更的神器!

private String name ;
private String age ;    /* 改成 String类型的*/
private Wife wife;
    
public String getAge() {
   return age;
}
    
public void setAge(int age) {
   //转换即可
   this.age = String.valueOf(age);
}

对成员变量进行精准控制

Mann man = new Mann();
        man.age = 30;   //老妖怪?
// 对setter做一些控制
public void setAge(int age) {
    if(age > 120){
        System.out.println("ERROR:error age input....");    //提示錯誤信息
    }else{
        this.age = age;
    }
}

继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。

通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。 继承就是用来扩展功能,避免修改以前的代码,影响其他功能

  • 子类拥有父类非private的属性和方法
  • 子类可以拥有自己属性和方法,即子类可以对父类进行扩展
  • 子类可以用自己的方式实现父类的方法
  • 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object

this:代表本类对象 super:代表父类空间,不代表父类对象,因为父类没有new instanceof:是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回true;否则,返回false

当子父类变量同名,用super区分

继承--变量

继承-函数

当子父类中出现成员函数一模一样,会运行子类的函数

  • 覆盖
  • 覆写 override
  1. 方法名、形参列表相同。
  2. 返回值类型和声明异常类型,子类小于等于父类。
  3. 方法覆盖时,子类函数的访问权限要大于等于父类函数。
  4. 静态只能覆盖静态

当对一个类进行子类扩展,子类需要保留父类的功能,但要有子类特有功能时就使用覆盖完成

class Vehicle{
	public void run(){
		System.out.println("跑");
	}
	public void stop() {
		System.out.println("停");
	}
	public Person test() {
		return new Person();
	}
}
class Horse extends Vehicle{
	public void run() {
		System.out.println("马儿跑");
	}
	public Student test() {
		return new Student("马儿", 12, "赤兔");
	}
}
//父类Person,具有男人女人共有的属性和方法
public class Person {
    protected String name;
    protected int age = 1;

    Person(){
        System.out.println("i am Person constructor");
    }
}
public class Wife {

    /**
     * 妻子的私有属性
     */
    private Mann man;

    /**
     * 私有属性对外开放的接口,女人一般不会开放年龄接口
     */
    public Mann getMan() {
        return man;
    }

    public void setMan(Mann man) {
        this.man = man;
    }
}
public class Mann extends Person {

    Mann() {
        System.out.println("i am Mann constuctor");
    }

    /**
     * 对属性的封装
     * 妻子的私有属性
     */
    private Wife wife;

    /**
     * 私有对象对外开放的接口,丈夫肯定不能开放getWife接口
     */
    public void setWife(Wife wife) {
        this.wife = wife;
    }
}

构造器

构建过程是从父类开始向子类一级一级地完成构建,编译器会默认先调用父类的构造器

因为子类的所有构造函数不管有没有参数,第一行都有默认super()调用父类空参构造函数

public class Person {
    protected String name;
    protected int age;
    
    Person(){
        System.out.println("Person Constrctor...");
    }
}
public class Mann extends Person{
    private Wife wife;

    Mann(){
        System.out.println("Mann Constructor...");
    }
    
    public static void main(String[] args) {
        Husband Mann  = new Mann();
    }
}

Output:
Person Constructor...
Mann Constructor...

如果父类不是默认构造器,子类则必须用super调用父类构造器,否则报错
所以父类经常多定义一个空参构造器

public class Person {

    protected String name;
    protected int age = 1;

    Person(String name){
        System.out.println("i am Person constructor");
    }
}
public class Mann extends Person {
    private Wife wife;
    public Mann(String name, Wife wife) {
        super(name);
        this.wife = wife;
    }

    Mann() {
        System.out.println("i am Mann constuctor");
    } // 报错
}

如果父类构造函数有初始化变量,子类如果不默认访问或强制访问父类构造器,就会丢失变量的值。

子类实例化过程图解

public class SonInstance {
	public static void main(String[] args) {
		Zi zi = new Zi();
		zi.show();
	}
}
class Fu{
	Fu() {
		super();
		show();
		return;
	}
	void show() {
		System.out.println("fu show....");
	}
}
class Zi extends Fu{
	int num = 8;
	Zi() {
		super();
		return;
	}
	void show() {
		System.out.println("zi show...."+num);
	}
}
//zi show....0
//zi show....8

初始化父类时,子类的num为0 ,还没有赋值,父类初始化完成后才初始化子类

继承树追溯

属性/方法查找顺序:(比如:查找变量h)

  1. 查找当前类中有没有属性h

  2. 依次上溯每个父类,查看每个父类中是否有h,直到Object

  3. 如果没找到,则出现编译错误。

  4. 上面步骤,只要找到h变量,则这个过程终止。

构造方法调用顺序:

构造方法第一句总是:super(…)来调用父类对应的构造方法。所以,流程就是:先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。

注:静态初始化块调用顺序,与构造方法调用顺序一样,不再重复

protected

虽然有的时候子类需要父类的属性,但最好的方式还是将属性保持为private,通过protected方法来控制开放接口的权限,从而类的继承者的访问权限

向上转型

子类可以赋值给父类,就是向上转型,就比如狗是动物,但动物不能是狗,只是子类的属性和方法不能交给父类

public class Mann extends Person {
    public void Say(){
        System.out.println("world");
    }
}
public class Person {
    public void Say(){
        System.out.println("hello");
    }
}
Person p = new Mann("jack");
	p.Say(); // world

继承内存模型

简单的画了一下,比较浅显,后面再深入完善


书籍推荐