从java内存角度分析java三大特性
一、背景
上一篇文章记录了鄙人学习java的内存结构的一点小感想,现在回到刚接触java的时候,对java的三大特性,继承,封装,多态好像有了新的认识,用jvm的内存分布来解释一下这三大特性会不会是一件很有意思的事情。
二、分析
一、java封装性:
1、字面意思就是包装,专业一点了,我参照维基百科,也就是一种信息的隐藏吧,就是使用抽象的数据类型将属性,数据和一些基于数据的操作进行包装起来,形成一个整体,实现java的万物皆对象的中心思想,数据和方法尽可能的隐藏了内部的细节,只是提供一些对外的接口,供其使用,比如常见的get/set方法等,对于使用者来说呢,他不需要知道也无法知道内部的一些细节,但是压根也不影响使用。
- 例子:
封装呢,我们可以理解为一台iphone手机,内部很复杂,包含很多的东西,cpu/声卡/显卡/甚至一些系统的东西,对于使用者来说呢我们不需要知道也根本不可能知道内部是如何运行的,各个属性和对属性的一个操作被封装到一个手机里面,我们就可以叫它对象,但是如果手机没有屏幕,没法操作,一个大黑盒子,虽然将封装的特性实现的很完美,但是没有什么意义,所以提供了手机屏幕,和一些基本的操作,比如打电话,发短信等。 - 封装的好处:
1、良好的封装性实现了低耦合;
2、内部结构自由修改,方便快捷,使用者一如既往;
3、对封装内部的成员属性变量更加精确的控制;
4、可以隐藏实现的细节和一些信息; -
java内存看封装;
如图所示:
在这里插入图片描述
对于封装,jvm就干了这么多了。
注意:抽象类不能new,也就是因为抽象类的内存不完整。
二、java的继承
- 名词解释:java中extends关键字的英文意思是延伸和扩展的意思,中文翻译为继承,我觉得也是合理的,因为在我国,子女确实是继承了父母的一切,什么财产,房屋,公司,资金,但是父母有的思想情怀却属于个人,难以复制。
- 继承就是子类继承父类的特性和行为,使得子类实例具有父类的实例域和方法,或者使得子类具有父类相同的行为,我觉得继承需要符合的关系是is-a,父类更加通用,子类更加的具体。
-
java只满足单继承关系。(对于java的内存我觉得我的理解是这样)
在这里插入图片描述 -
继承的特性:
1、子类可以拥有父类的非privite的属性,方法;(如果子类中的成员变量和父类中的成员变量相同时,不能被继承,方法同理,如果子类中成员方法的名字,返回类型,参数列表相同时,同理。)
2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展;
3、子类可以用自己的方式实现父类的方法;
4、java的继承是单继承,就是一个子类只能继承一个父类;如果没有关键字,默认继承上帝类object。
缺点:提高了类之间的耦合性。 -
结合内存分析对特性进行解释:
1、结合内存分析,p只能看到p自己的一亩三分地,但是对于s来说他能看见p+s的一亩三分地,但是在上面的封装讲到,他虽然能通过手机屏幕看见手机的操作,但是对于手机内部的cpu啊,一些私有的东西也是不得而知的,也是我上面所说的,继承不了父辈的思想和情怀。
2、对于非private方法,子类可以对父类进行扩展,这也就是中国古话说的青出于蓝而胜于蓝啦,哈哈。
3、利用implement关键字可以变相的使java具有多继承的特性,可以同时的实现多个接口(接口之间用,号隔开)
4、运行流程:
public class Person {
private String think;
private String name;
public String age;
public String gender;
public Person(){
System.out.println("I am father");
}
public void say(){
System.out.println("人会说话的");
}
public void walk(){
System.out.println("人会走路的");
}
public class Student extends Person{
privite String school;
public Student(){
System.out.println("I am son");
}
public void say(){
System.out.println("学生也会说话");
}
public void run(){
System.out.println("学生一般跑");
}
}
//执行测试类代码
1、Student s=new Student();
2、Person person=new Person();
3、person.say();
4、s.age="22';
5、s.gender="男";
6、s.say();
7、Person p =new Student();
8、p.say()
--------------------------------------------------------------------------------------------------
1、当新建对象Student时,运行代码发现父类构造方法被执行,因为在student继承父类Person,虚拟机首先加载Person类
到方法区,并在堆空间中为父类的成员变量在子类的空间中初始化,然后加载Student类到方法区,为student类的成员变
量在堆空间中分配空间,默认初始化,并且将栈中的引用s指向堆,可以看到上述的内存结构图中,s指向的区域是包含p+s的。
2、子类继承了父类的非privte方法和属性,如果我们给s的age和gender属性赋值,这些值会被保存在子类区域的父类空
间,所以可以根据栈中的引用s可以找到s+p的那一块区域,然后给age和gender赋值。
3、当调用上述3,5和8方法时,会发现控制台输出的都是分别是,父类的方法,子类的方法,子类的方法,第一个方法,第二个
方法,执行的操作很容易想到,该对象的引用指向对应堆内存相应的方法,但是第三步执行为什么会输出子类的方法。
方法的重写:
上述被称为方法的重写,java规定子类和父类中有两个名称,参数列表和返回值均相同,子类中的新方法会覆盖父类中
原有的方法。
解释:
当执行上述第8行代码的时候,其实是有一步指针改写的操作,调用p.say()时,指针发生了改变,直接指向了子类的方法。
注意:重写是一个运行期间的概念,在运行的时候,会根据p真正指向的实际对象类型进行调用方法,实现这一步指针的
改写。是属于第三点需要讲的java第三特性多态的。
-
关于继承的一些关键字分析;
1、既然记录了重写,需要记录一下重载,重载,简单的就是说,具有相同名称,但是参数列表不同的形式,这种同名不同参数
的函数或则方法,称为重载函数或者方法。
注意:重载是一个编译期的概念,所谓的编译期绑定,就是在编译期时根据参数变量的类型判断应该调用哪个方法。
2、super和this关键字:
super:对于父类来说,被重写的方法,或者子类定义了和父类相同的成员变量,那不是就永无出头之日了,super关键字在
java中作用就是使被屏蔽的的成员变量和成员方法变为可见。this:java中的this关键字是用在方法中引用当前的实例。
1、当成员变量和局部变量名称一样时。明显的标识使用的是成员变量而不是局部或者静态变量。 eg:private String name; public void XXX(String name){ this.name=name; } 这里就明确的用this指定了第一个name是成员变量。 2、用来表示构造函数 3、将当前的java实例作为参数传递 4、返回当前的java实例 总结:因为java中的this都与他的实例相关,换句话说就是我this得啃老啊,但是永久代里面的东西到底属于谁啊,所以this不能在静态方法中 使用
三、java的多态性
-
多态的三个条件:1、继承;2、子类重写了父类的方法;3、父类引用指向了子类的对象。
从上述的三个条件可以看出,上一章节记录的继承内存分析图就很有用了。
在这里插入图片描述 - 分析:
父类的引用指向了子类的实例,提出三个问题,为什么1、子类有的,父类没有的;2、子类有,父类有,重写的
非静态,子类有,父类有,静态会怎么运行呢。 - 内存分析:
从内存中可以看到,一旦实现了继承,子类中是包含父类的,子类引用的视角是可以看见父类和子类的,但是子类会对
父类说,借用一句网络用语,你的就是我的,我的还是我的,对于Person p来说他的视角只能看见自己,既然自己都
没有,如何调用呢,所以第一个问题,编译就过不了。
第二个问题在继承里已经说了。这里就不说了。
第三个问题,因为静态是属于类的,由实例共享,所以只能看当前的的引用属于什么类型,就调用哪个类的方法。
总结一下:无论是编译器还是运行期,多态的成员变量都只参考引用类型所属的类中是否有对象的成员变量。
-
三、总结
个人的一次总结,希望能记录一下学习和思考后的收获,看得不够仔细,纰漏之处还请指出。谢谢。