各位小伙伴大家好,我是A哥。如果问:Java的三大特性是什么?你顺口就能答出:封装、继承、多态。如果继续问:你真的了解Java中的继承吗?
或许你本来很懂,但被我这么一问就有点怀疑了。那么,就看看本文吧,保证你会有收获,能让你更好的理解Java中的继承机制。
public class Main {
public static void main(String[] args) {
// 使用多态
Parent chidParent = new Child();
System.out.println("Parent:" + chidParent.getAge()); //40
System.out.println("Parent:" + chidParent.age); //18 这个结果你能接受吗?哈哈
// 直接使用原本类型
Child child = new Child();
System.out.println("Child:" + child.getAge()); //40
System.out.println("Child:" + child.age); //40
}
}
@Getter
@Setter
class Child extends Parent {
public Integer age = 40;
}
@Getter
@Setter
class Parent {
public Integer age = 18;
}
输出结果:
Parent:40
Parent:18
Child:40
Child:40
我相信和最初的我一样,对Parent:18
这个结果大吃一惊,what?其实这就是Java的继承机制,对此说明如下:
getAge()
方法返回的100%是40喽结论同上。
public class Main {
public static void main(String[] args) {
new Child();
}
}
@Getter
class Child extends Parent {
static {
System.out.println("Child的静态块");
}
{
System.out.println("Child的构造块");
}
Child() {
System.out.println("Child的构造方法");
}
}
@Getter
class Parent {
Integer age = 18;
static {
System.out.println("Parent的静态块");
}
{
System.out.println("Parent的构造块");
}
Parent() {
System.out.println("Parent的构造方法");
}
}
结果输出:
Parent的静态块
Child的静态块
Parent的构造块
Parent的构造方法
Child的构造块
Child的构造方法
Tips:构造代码块优先于构造方法执行,且优先于属性初始化之前执行
@PostConstruct
是对象的属性都初始化ok了之后
才去执行的(注意你new的话,@PostConstruct
方法是不会执行的,他是Spring给与的支持哦~)
值得注意的是,此处子类没有显示调用super(),但父类的构造还是执行了的。但是,但是,但是,如果构造快为有参构造,请记得显示调用super方法,否则父类是不能被初始化的。如果子类的构造器没有显示地调用超类的构造器,则将自动调用超类默认(没有参数) 的构造器。如果超类没有不带参数的构造器,并且在子类的构造器又没有显式地调用超类的其他构造器,则 java 编译器将报告错误~
public class StaticTest {
public static void main(String[] args) {
staticFunction();
}
// 静态变量(有实例化的过程,这就是本题的重点)
static StaticTest st = new StaticTest();
static {
//System.out.println(b); // 编译报错:因为b在构造代码块后边,此处不能引用。因此Java代码是从上到下的顺序
System.out.println("1");
}
{
System.out.println("2");
}
StaticTest() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}
public static void staticFunction() {
System.out.println("4");
}
// 这两个变量写在最后面
int a = 110;
static int b = 112;
}
输出:
2
3
a=110,b=0
1
4
答案五花八门,真正能答对这道题的小伙伴少之又少。从结果中,这里先给你扔个结论:
new StaticTest()
,从而打印:2a=110,b=0
st
变量还没初始化完呢,所以b此时值为0staticFunction
,打印:4从该结果你应该能知道:static变量可不是100%一定在实例变量之前被赋值(初始化哦~),比如本例的b就在a之后初始化了
类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载。
只有在准备阶段和初始化阶段才会涉及
类变量的初始化和赋值
,因此只针对这两个阶段进行分析;
类的准备阶段
:需要做是为类变量(static变量)分配内存并设置默认值
(注意此处都是先给默认值),因此类变量st为null、b为0;
需要注意的是,如果类变量是final的,编译时javac就会为它赋上值。因此上面如果我们这样写
static final int b=112
它哪怕在准备阶段,值就应该是112了
类的初始化阶段
:需要做的是执行类构造器
(请注意:这里不是指的构造函数
)。
类构造器:
编译器
收集所有静态语句块
和类变量
的赋值语句
,按语句在源码中的**顺序(请注意这三者是有序的)**合并生成类构造器
因此现在执行:st = new StaticTest()
.此时我们发现,就会进行对象的初始化了(看到没,这个时候b变量的赋值语句还没有执行哦~~~)
而对象初始化的顺序为:成员变量 -> 普通代码块 -> 构造函数,因此这一波过后:a=110了。 输出为:
2
3
a=110,b=0
需要注意的是,此时b仍然为0,并没有被赋值哦~
到此st = new StaticTest()
这句就执行结束了。继续执行类构造器
,显然就会执行static语句块了~~~输出1,最后调用静态方法,就输出4了 完美~
通过结果看,有点颠覆我们之前的认知。其实这是一个冷知识:
它的关键在于:
static StaticTest st = new StaticTest()
这句代码,内嵌的这个变量恰好是个静态成员
,而且是本类的实例
这就导致了这个有趣的现象:“实例初始化竟然出现在静态初始化之前”。
这里面我只做一小步变化:
static StaticTest st = new StaticTest()
改成
StaticTest st = new StaticTest()
或者改成:
static Object st = new Object();
最终输出结果就为(符合我们常识了吧,啊哈哈哈哈):
1
4
同名同类型
的静态
变量的时候结论就不用解释了:静态变量属于类的,和继承无关。
public class StaticDemo1 {
public static void main(String[] args) {
speak();
//StaticDemo1 t1 = new StaticDemo1();
//System.out.println(t1.i);
}
// 静态变量
static int i = 1;
// 静态方法
static void speak() {
System.out.println("a");
}
// 静态代码块
static {
i = i + 3;
System.out.println(i);
}
// 构造函数
public StaticDemo1() {
i = i + 5;
System.out.println(i);
}
}
输出:
4
a
4在a之前输出,证明:毕竟mian方法属于StaticDemo1
类的方法,所以会先执行此类的静态变量 + 静态代码块。
其它不变,改为这样:
public static void main(String[] args) {
StaticDemo1 t1 = new StaticDemo1();
System.out.println(t1.i);
speak();
}
4
9
9
a
System.out.println(t1.i)
直接输出,打印9(此时i的值是9)public static void main(String[] args) {
speak();
StaticDemo1 t1 = new StaticDemo1();
System.out.println(t1.i);
}
4
a
9
9
这个输出,在意料之中,不再解释喽。
这是一道面试题,考察的是:static块真正的执行时机。若想真正了解类的装载,请去了解JVM吧~
特别的,这里我介绍一下各种注解影响的执行顺序,如下代码:
@Component
public class InitBeanTest implements InitializingBean,ApplicationListener<ContextRefreshedEvent> {
@Autowired
DemoService demoService;
public InitBeanTest() {
System.err.println("----> InitSequenceBean: constructor: "+demoService);
}
@PostConstruct
public void postConstruct() {
System.err.println("----> InitSequenceBean: @PostConstruct: "+demoService);
}
@Override
public void afterPropertiesSet() throws Exception {
System.err.println("----> InitSequenceBean: afterPropertiesSet: "+demoService);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent arg0) {
System.err.println("----> InitSequenceBean: onApplicationEvent");
}
}
执行结果:
----> InitSequenceBean: constructor: null
----> InitSequenceBean: @PostConstruct: com.xxx.service.impl.DemoServiceImpl@40fe544
----> InitSequenceBean: afterPropertiesSet: com.xxx.service.impl.DemoServiceImpl@40fe544
----> InitSequenceBean: onApplicationEvent
根据代码演示,得出文字版结论:
父类构造函数
问题Java有个很有趣的现象:父类有N多个构造函数,子类如果只写一个的话那么子类最终就只有一个构造函数可用,因此子类在这方面要特别的注意喽。
默认情况下,子类在使用构造函数初始化时(不管是子类使用有参构造还是无参构造),默认情况下都会调用父类的无参构造函数(相当于调用了super()
)。看看下面几个变种如下:
// 父类木有空的构造 只有一个有参构造
class Parent{
private Integer id;
public Parent(Integer id){
System.out.println("this is parent cons...");
}
}
此时候我们发现发现如下三问题: 1、子类Child必须有对应的有参构造
2、super(id)必须显示的写出,否则编译不通过
3、原则上,子类的构造函数不能多于父类的
4、子类构造函数若多余父类(或者类型啥的和父类不匹配),需要显示的调用父类构造函数
结论:
先执行
父类的构造函数(super(xxx)必须放在第一行代码)super()
。若父类木有空构造,子类所有构造都必须显示调用super(xxx)·
据反馈,看了此篇文章后,很多小伙伴感觉自己学的是另外一个Java,其实这就是JavaSE,很多架构师认为,Java基础才是精华中的精华(一流),Spring才属于应用级别的技术(二流)。