java 基础

本文详细解析了Java中类的初始化顺序,包括静态变量、静态初始化块、成员变量、初始化块及构造函数的加载顺序。并通过具体示例阐述了String类型在内存中的存储结构、字符串池的工作原理及其与享元模式的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类的初始化顺序

  在Java中,类里面可能包含:静态变量,静态初始化块,成员变量,初始化块,构造函数。在类之间可能存在着继承关系,那么当我们实例化一个对象时,上述各部分的加载顺序是怎样的?

  首先来看代码:

复制代码
 1 class Parent
 2 {
 3     public static StaticVarible staticVarible= new StaticVarible("父类-静态变量1");    
 4     public StaticVarible instVarible= new StaticVarible("父类-成员变量1");
 5     
 6     static
 7     {
 8         System.out.println("父类-静态块");
 9     }
10     
11     {
12         System.out.println("父类-初始化块");
13     }
14     
15     public static StaticVarible staticVarible2= new StaticVarible("父类-静态变量2");    
16     public StaticVarible instVarible2= new StaticVarible("父类-成员变量2");
17     
18     public Parent()
19     {
20         System.out.println("父类-实例构造函数");
21     }
22 }
23 
24 class Child extends Parent
25 {
26     public static StaticVarible staticVarible= new StaticVarible("子类-静态变量1");    
27     public StaticVarible instVarible= new StaticVarible("子类-成员变量1");
28     
29     static
30     {
31         System.out.println("子类-静态块");
32     }
33     
34     public Child()
35     {
36         System.out.println("子类-实例构造函数");
37     }
38     
39     {
40         System.out.println("子类-初始化块");
41     }
42     
43     public static StaticVarible staticVarible2= new StaticVarible("子类-静态变量2");    
44     public StaticVarible instVarible2= new StaticVarible("子类-成员变量2");
45     
46     
47 }
48 
49 class StaticVarible
50 {
51     public StaticVarible(String info)
52     {
53         System.out.println(info);
54     }
55 }
复制代码

  然后执行下面的语句:

1 Child child = new Child();

  输出结果如下:

复制代码
父类-静态变量1
父类-静态块
父类-静态变量2
子类-静态变量1
子类-静态块
子类-静态变量2
父类-成员变量1
父类-初始化块
父类-成员变量2
父类-实例构造函数
子类-成员变量1
子类-初始化块
子类-成员变量2
子类-实例构造函数
复制代码

  结论  

  从上述结果可以看出,在实例化一个对象时,各部分的加载顺序如下:

  父类静态成员/父类静态初始化块 -> 子类静态成员/子类静态初始化块 -> 父类成员变量/父类初始化块 -> 父类构造函数 -> 子类成员变量/子类初始化块 -> 子类构造函数

  和String相关的一些事儿

  首先,我们聊一聊Java中堆和栈的事儿。

  • 栈:存放基本类型,包括char/byte/short/int/long/float/double/boolean
  • 堆:存放引用类型,同时一般会在栈中保留一个指向它的指针,垃圾回收判断一个对象是否可以回收,就是判断栈中是否有指针指向堆中的对象。

  String作为一种特殊的数据类型,它不完全等同于基本类型,也不是全部的引用类型,许多面试题都有它的身影。

  String类型变量的存储结构

  String的存储结构分为两部分,我们以String a = "abc";为例,描述String类型的存储方式:

  1)在栈中创建一个char数组,值分为是'a','b','c'。

  2)在堆中创建一个String对象。

  Java中的字符串池

  为了节省空间和资源,JVM会维护一个字符串池,或者说会缓存一部分曾经出现过的字符串。

  例如下面的代码:

1 String v1 = "ab";
2 String v2 = "ab";

  实际上,v1==v2,因为JVM在v1声明后,已经对“ab”进行了缓存。

  那么JVM对字符串进行缓存的依据是什么?我们来看下面的代码,非常有意思:

复制代码
 1 public class StringTest {
 2     public static final String constValue = "ab";
 3     public static final String staticValue;
 4     
 5     static
 6     {
 7         staticValue="ab";
 8     }
 9     
10     public static void main(String[] args)
11     {
12         String v1 = "ab";
13         String v2 = "ab";
14         System.out.println("v1 == v2 : " + (v1 == v2));
15         String v3 = new String("ab");
16         System.out.println("v1 == v3 : " + (v1 == v3));
17         String v4 = "abcd";
18         String v5 = "ab" + "cd";
19         System.out.println("v4 == v5 : " + (v4 == v5));
20         String v6 = v1 + "cd";
21         System.out.println("v4 == v6 : " + (v4 == v6));
22         String v7 = constValue + "cd";
23         System.out.println("v4 == v7 : " + (v4 == v7));
24         String v8 = staticValue + "cd";
25         System.out.println("v4 == v8 : " + (v4 == v8));
26         String v9 = v4.intern();
27         System.out.println("v4 == v9 :" + (v4 == v9));
28         String v10 = new String(new char[]{'a','b','c','d'});
29         String v11 = v10.intern();
30         System.out.println("v4 == v11 :" + (v4 == v11));
31         System.out.println("v10 == v11 :" + (v10 == v11));
32     }
33 }
复制代码

  请注意它的输出结果:

复制代码
v1 == v2 : true
v1 == v3 : false
v4 == v5 : true
v4 == v6 : false
v4 == v7 : true
v4 == v8 : false
v4 == v9 :true
v4 == v11 :true
v10 == v11 :false
复制代码

  我们会发现,并不是所有的判断都返回true,这似乎和我们上面的说法有矛盾了。其实不然,因为

  结论

  1. JVM只能缓存那些在编译时可以确定的常量,而非运行时常量。

    上述代码中的constValue属于编译时常量,而staticValue则属于运行时常量。

  2. 通过使用 new方式创建出来的字符串,JVM缓存的方式是不一样的。

    所以上述代码中,v1不等同于v3。

  String的这种设计属于享元模式吗?

  这个话题比较有意思,大部分讲设计模式的文章,在谈到享元时,一般就会拿String来做例子,但它属于享元模式吗?

  字符串与享元的关系,大家可以参考下面的文章:http://www.cnblogs.com/winter-cn/archive/2012/01/21/2328388.html

  字符串的反转输出

  这种情况下,一般会将字符串看做是字符数组,然后利用反转数组的方式来反转字符串。

  眼花缭乱的方法调用

  有继承关系结构中的方法调用

  继承是面向对象设计中的常见方式,它可以有效的实现”代码复用“,同时子类也有重写父类方法的自由,这就对到底是调用父类方法还是子类方法带来了麻烦。

  来看下面的代码:

复制代码
 1 public class PropertyTest {
 2 
 3     public static void main(String[] args)
 4     {
 5         ParentDef v1 = new ParentDef();
 6         ParentDef v2 = new ChildDef();
 7         ChildDef v3 = new ChildDef();
 8         System.out.println("=====v1=====");
 9         System.out.println("staticValue:" + v1.staticValue);
10         System.out.println("value:" + v1.value);
11         System.out.println("=====v2=====");
12         System.out.println("staticValue:" + v2.staticValue);
13         System.out.println("value:" + v2.value);
14         System.out.println("=====v3=====");
15         System.out.println("staticValue:" + v3.staticValue);
16         System.out.println("value:" + v3.value);
17     }
18 }
19 
20 class ParentDef
21 {
22     public static final String staticValue = "父类静态变量";
23     public String value = "父类实例变量";
24 }
25 
26 class ChildDef extends ParentDef
27 {
28     public static final String staticValue = "子类静态变量";
29     public String value = "子类实例变量";
30 }
复制代码

  输出结果如下:

复制代码
=====v1=====
staticValue:父类静态变量
value:父类实例变量
=====v2=====
staticValue:父类静态变量
value:父类实例变量
=====v3=====
staticValue:子类静态变量
value:子类实例变量
复制代码
  结论

  对于调用父类方法还是子类方法,只与变量的声明类型有关系,与实例化的类型没有关系。

  到底是值传递还是引用传递

  对于这个话题,我的观点是值传递,因为传递的都是存储在栈中的内容,无论是基本类型的值,还是指向堆中对象的指针,都是值而非引用。并且在值传递的过程中,JVM会将值复制一份,然后将复制后的值传递给调用方法。

  按照这种方式,我们来看下面的代码:

复制代码
 1 public class ParamTest {
 2 
 3     public void change(int value)
 4     {
 5         value = 10;
 6     }
 7     
 8     public void change(Value value)
 9     {
10         Value temp = new Value();
11         temp.value = 10;
12         value = temp;
13     }
14     
15     public void add(int value)
16     {
17         value += 10;
18     }
19     
20     public void add(Value value)
21     {
22         value.value += 10;
23     }
24     
25     public static void main(String[] args)
26     {
27         ParamTest test = new ParamTest();
28         Value value = new Value();
29         int v = 0;
30         System.out.println("v:" + v);
31         System.out.println("value.value:" + value.value);
32         System.out.println("=====change=====");
33         test.change(v);
34         test.change(value);
35         System.out.println("v:" + v);
36         System.out.println("value.value:" + value.value);
37         value = new Value();
38         v = 0;
39         System.out.println("=====add=====");
40         test.add(v);
41         test.add(value);
42         System.out.println("v:" + v);
43         System.out.println("value.value:" + value.value);
44     }
45 }
46 
47 class Value
48 {
49     public int value;
50 }
复制代码

  它的输出结果:

复制代码
v:0
value.value:0
=====change=====
v:0
value.value:0
=====add=====
v:0
value.value:10
复制代码

  我们看到,在调用change方法时,即使我们传递进去的是指向对象的指针,但最终对象的属性也没有变,这是因为在change方法体内,我们新建了一个对象,然后将”复制过的指向原对象的指针“指向了“新对象”,并且对新对象的属性进行了调整。但是“复制前的指向原对象的指针”依然是指向“原对象”,并且属性没有任何变化。

  final/finally/finalize的区别

  final可以修饰类、成员变量、方法以及方法参数。使用final修饰的类是不可以被继承的,使用final修饰的方法是不可以被重写的,使用final修饰的变量,只能被赋值一次。

  使用final声明变量的赋值时机:

  1)定义声明时赋值

  2)初始化块或静态初始化块中

  3)构造函数

  来看下面的代码:

复制代码
 1 class FinalTest
 2 {
 3     public static final String staticValue1 = "静态变量1";
 4     public static final String staticValue2;
 5     
 6     static
 7     {
 8         staticValue2 = "静态变量2";
 9     }
10     
11     public final String value1 = "实例变量1";
12     public final String value2;
13     public final String value3;
14     
15     {
16         value2 = "实例变量2";
17     }
18     
19     public FinalTest()
20     {
21         value3 = "实例变量3";
22     }
23 }
复制代码

  finally一般是和try...catch放在一起使用,主要用来释放一些资源。

  我们来看下面的代码:

复制代码
 1 public class FinallyTest {
 2 
 3     public static void main(String[] args)
 4     {
 5         finallyTest1();
 6         finallyTest2();
 7         finallyTest3();
 8     }
 9     
10     private static String finallyTest1()
11     {
12         try
13         {
14             throw new RuntimeException();
15         }
16         catch(Exception ex)
17         {
18             ex.printStackTrace();
19         }
20         finally
21         {
22             System.out.println("Finally语句被执行");
23         }
24         try
25         {
26             System.out.println("Hello World");
27             return "Hello World";
28         }
29         catch(Exception ex)
30         {
31             ex.printStackTrace();
32         }
33         finally
34         {
35             System.out.println("Finally语句被执行");
36         }
37         return null;
38     }
39     
40     private static void finallyTest2()
41     {
42         int i = 0;
43         for (i = 0; i < 3; i++)
44         {
45             try
46             {
47                 if (i == 2) break;
48                 System.out.println(i);
49             }
50             finally
51             {
52                 System.out.println("Finally语句被执行");
53             }
54         }
55     }
56     
57     private static Test finallyTest3()
58     {
59         try
60         {
61             return new Test();
62         }
63         finally
64         {
65             System.out.println("Finally语句被执行");
66         }
67     }
68 }
复制代码

  执行结果如下:

复制代码
java.lang.RuntimeException
    at sample.interview.FinallyTest.finallyTest1(FinallyTest.java:16)
    at sample.interview.FinallyTest.main(FinallyTest.java:7)
Finally语句被执行
Hello World
Finally语句被执行
0
Finally语句被执行
1
Finally语句被执行
Finally语句被执行
Test实例被创建
Finally语句被执行
复制代码

  注意在循环的过程中,对于某一次循环,即使调用了break或者continue,finally也会执行。

基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人和运动设备中的六轴姿态传感器,它集成了三轴陀螺仪和三轴加速度计。这款传感器能够实时监测并提供设备的角速度和线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp`和`MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口和函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式和I2C地址,`getAcceleration()`和`getGyroscope()`则分别用于获取加速度和角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()`和`getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准和滤波,以消除噪声和漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)和角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置和动态运动,而角速度数据则能反映物体转动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度和角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器时,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力和更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程和数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实时运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客和其他开源资源是学习和解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息和实践指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值