给Java_献给Java初学者

本文深入探讨了Java中的对象与基本类型的区别,包括堆栈内存管理、自动包装(Autoboxing)、==与equals()的使用以及赋值操作的细节。通过实例解析了Integer对象在不同情况下的内存行为,揭示了Java在内存分配上的优化策略,并提醒开发者注意潜在的陷阱和理解误区。

对象与基本类型

几乎所有Java初学者都被告知,在Java里一切都被视为对象(Object),操纵对象的表示符实际上时对象的一个引用(Reference)。例如

String str;//注意!此处创建了一个引用,而非对象

str=new String(“Hello”);//这里创建了一个String对象并与str相关联

通常用new操作符来创建一个新对象,并存储在堆里面。【注】具体内容可以参看堆与栈

程序设计中有一系列小的、简单的变量(笔者是这样认为的),将它们存储在堆里往往并不是很高效,因此对于这些基本类型,Java创建一个并非引用的“自动”的变量,并将其值存储在栈中。【注】具体内容可以参看 Java堆与栈

Java确定了每种基本类型所占存储空间的大小,并且不随计算机硬件架构的变化而变化,因此他们更具可移植性。Java基本类型及其包装器的具体信息参见下表:

基本类型

大小

最小值

最大值

包装器类型

boolean

Boolean

char

16-bit

Unicode o

Unicode 264-1

Character

byte

8 bits

-128

+127

Byte

short

16 bits

-215

+215-1

Short

int

32 bits

-231

+231-1

Integer

long

64 bits

-263

+263-1

Long

float

32 bits

IEEE754

IEEE754

Float

double

64 bits

IEEE754

IEEE754

Double

void

Void

基本类型具有的包装器类型使得可以在堆中创建一个非基本对象用来表示对应的基本类型,例如

Character ch=new Character(‘c’);

Java SE5的自动包装功能将自动的将基本类型转换为包装器类型:

Character ch=‘c’;//Autoboxing

可以看到Java SE5的自动包装功能为我们提供了很大的方便(泛型中你会看到它的优势),然而如果你不了解它的工作原理则会让你陷入意想不到的困惑和麻烦之中。请看下面的例子:

publicstaticvoidmain(String args[])

{

Integer i1=127;

Integer i2=127;

System.out.println(i1==i2);

i1=128;

i2=128;

System.out.println(i1==i2);

}/*Output:

* true

* false*/

有些令人惊讶,两个范例语法完全一样,只不过改个数值而已,结果却相反。这是因为在自动包装时对于从–128到127之间的值,它们被包装为Integer对象后,会存在内存中被重用,而其它的值,被包装后的Integer对象并不会被重用,即相当于每次包装时都新建一个Integer对象。

然而下面的输出却是ture,这是因为你用了n1=n2,n1只是n2的别名而已。

在看一个例子:

publicstaticvoidmain(String args[])

{

Integer i1=12;

Integer i2=12;

System.out.println(i1==i2);

i1=newInteger(13);

i2=newInteger(13);

System.out.println(i1==i2);

i1=i2=newInteger(14);

System.out.println(i1==i2);

}/*Output:

* true

* false

* true*/

天呐!数字13仿佛被诅咒了一般,==对它的判断似乎失去了效用!事实上可怜的Java一无所知,如果你对输出的结果感到疑惑,说明你对==和Java的存储还不是很了解。

==与equals()

如果知道下面的规则,你对上面例子的输出结果或许就不会那么疑惑了:

1.对于基本类型,==将比较两边的值是否相等;

2.对于对象,==则比较对象的是否指向同一个对象。

如果你仍然对输出结果不甚明白,那么别急,关于Java堆与栈的介绍将会让你明了,但是在这里我还要添加关于equals()的介绍。

或许你已经被告知,要想比较对象的实际内容是否相同必须使用所有对象都适用的特殊方法equals(),例如:

Integer n1=new Integer(13);

Integer n2=new Integer(13);

System.out.println(n1.equals(n2));//true

这看上去的确很简单,但是下面的例子中equals()又要让你失望和疑惑了:

public class Value { public int i;}

Value v1=new Value();

Value v2=new Value();

v1.i=v2.i=13;

System.out.println(v1.equals(v2));//false

哦,它输出了false!好吧,让我们看看equals()方法的原型:

publicbooleanequals(Object obj) {return(this==obj);

}

再让我们看看Integer中的equals()方法:

publicbooleanequals(Object obj) {if(objinstanceofInteger) {returnvalue==((Integer)obj).intValue();

}returnfalse;

}

OK!一切都清楚了,继承自Object中的equals()方法与==没什么两样,只是Integer类对equals()方法进行了重写而已,这样我们很容易能写出令上面例子中System.out.println(v1.equals(v2));语句输出true的方法

。Java堆与栈

Java的堆是一个位于随机访问存储器(RAM)的运行时数据区。通常使用new操作符在堆中创建对象,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

Java的栈也位于RAM,它的存取速度比堆要快,仅次于寄存器且据可以共享,主要存放一些基本类型的变量和对象的引用;但存在于栈中的数据大小与生存期必须是确定的,缺乏灵活性。

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

int a = 3;

int b = 3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,由于在栈中查找到3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。

这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

好了,我们再来看上面的例子:

Integer i1 = 12;

Integer i2 = 12;

System.out.println(i1==i2); //true

由于是自动包装,对于从–128到127之间的值,它们被包装为Integer对象后,会存在内存中被重用,因此输出的是true;

i1=new Integer(13);

i2=new Integer(13);

System.out.println(i1==i2);//flase

由于使用的是new操作符,而不是自动包装功能,Java在堆里面创建了两个Integer对象,分别与i1和i2关联,由于==对于对象比较的是引用,所以输出是false;

然而下面的语句中实际上只创建了一个对象,这里又出现的别名的现象

i1=i2=new Integer(14);

System.out.println(i1==i2);

因此用第一种方式创建多个int,在内存中其实只存在一个对象而已.这种写法有利与节省内存空间.同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于Integer i = new Integer (int);的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。

好了,这样以来相信下面的程序也不会为我们带来太多的疑惑了:

publicstaticvoidmain(String args[])

{

Integer i1=newInteger(13);

Integer i2=newInteger(13);inti3=13;

System.out.println(i1==i2);

System.out.println(i2==i3);

System.out.println(i3==i1);

}/*Output

* false

* true

* true*/

赋值

赋值操作符“=”对我们来说是再熟悉不过的了。它取右边的值(右值)复制给左边的值(左值),左值必须是一个明确的已命名的变量。例如:

Integer n1=new Integer(13);

Integer n2=new Integer(14);

n2=n1;//别名现象

n2=11;

System.out.println(n1);//11

了解了Java的存储分配就不难知道,对于基本类型复制的是值,对于对象复制的则是对象的引用。

进行赋值的时候Java编译器会自动检查左值和右值的类型是否匹配,必要的时候会洗的进行类型转换。

一方面Java中允许我们显式的进行类型转换,例如:

int i=(int)1.7;//i的值1,如例子所示将浮点类型转化为整型是Java总是对数字执行截尾而非舍入

另一方面,编译器会自动进行类型的提升,例如:

int i=13;

long l=i;

Java的自动提升确实提供了一定的便利,然而更多的是让我们陷入一种迷茫之中,例如:

short s=1;

s=s+1;

运行的时候编译器会提示“可能损失精度”。首先在s=s+1;语句中由于Java将整型常量1默认为int类型,编译器会自动将s提升为int与1相加之后返回int类型,而s为short类型,则需要进行窄化转换,并造成“可能损失精度“。

然而下面的语句却能顺利的通过:

short s=1;

s+=1;

许多程序员都会认为E1 op = E2只是E1 = E1 op E2)的简写方式,而事实上中讲到,复合赋值E1 op= E2等价于简单赋值E1 = (T)((E1)op(E2)),其中T是E1的类型,除非E1只被计算一次(参见《Java语言规范)。可以看到复合赋值帮我们进行了显式的类型转换。

再来看下面的例子,

publicstaticvoidmain(String args[])

{inta=3;intb=7;

a^=b^=a^=b;

System.out.println("a="+a+"b="+b);

}/*Output:

* a=0 b=3*/

如果你了解C语言,那么上述表达式是交换两个int类型数据的方法之一,然而这在Java中却不管用,得到的结果却是a=0 b=3;

《Java语言规范》描述到:操作符的操作数是从左向右求值的。为了求表达式x ^= expr的值,x的值是在计算expr之前被提取的,并且这两个值的异或结果被赋给变量x。因此Java中a^=b^=a^=b;的实际行为:

inttmp1=a ;//a在表达式中第一次出现inttmp2=b ;//b的第一次出现inttmp3=a^b ;//计算a ^ ba=tmp3 ;//最后一个赋值:存储a ^ b 到 ab=tmp2^tmp3 ;//第二个赋值:存储最初的a值到b中a=tmp1^b ;//第一个赋值:存储0到a中

参考资料

1.《Thinking in Java, Second Edition》,Bruce Eckel,Prentice Hall

2.《Java Puzzlers : Traps, Pitfalls, and Corner Cases》,Joshua Bloch,Neal Gafter,Addison Wesley/Pearson

3.《The Java Language Specification》,James Gosling,bill Joy,Guy Steele,Addison-WesleyProfessional

内容概要:本文详细介绍了一个基于MATLAB实现的SWT-SVM故障诊断分类预测项目,通过平稳小波变换(SWT)进行信号去噪与多尺度特征提取,结合支持向量机(SVM)实现机械设备故障的智能分类。项目涵盖从数据采集、预处理、SWT分解、特征提取与降维(如PCA)、模型训练与优化(含交叉验证、网格搜索、贝叶斯优化)、性能评估(混淆矩阵、ROC曲线、F1分数等)到结果可视化与GUI界面开发的完整流程。系统具备高可解释性、强鲁棒性和良好工程集成能力,适用于多行业设备健康监测,并提供完整的代码实现与部署方案。; 适合人群:具备一定MATLAB编程基础,熟悉信号处理与机器学习算法的高校研究生、科研人员及工业领域从事设备故障诊断、智能运维的工程师和技术人员。; 使用场景及目标:①应用于智能制造、风电、轨道交通、石化、航空航天等领域的设备故障早期检测与健康状态评估;②构建端到端的智能诊断pipeline,提升诊断准确率与自动化水平;③通过GUI交互界面实现数据导入、模型训练、实时预测与结果导出,服务于科研教学与工业实际部署。; 阅读建议:建议读者结合文中提供的完整MATLAB代码与GUI设计,逐步复现各模块功能,重点关注SWT参数选择、特征降维策略、SVM超参数优化及模型评估方法。在实践过程中调试信号处理流程与分类性能,深入理解算法原理与工程落地的关键环节。
【Copula光伏功率预测】基于单调广义学习系统(MBLS)和Copula理论的时空概率预测模型(Matlab代码实现)内容概要:本文介绍了一个基于单调广义学习系统(MBLS)和Copula理论的时空概率预测模型,用于光伏功率预测,并提供了Matlab代码实现。该模型结合了MBLS在函数逼近和学习能力方面的优势,以及Copula理论在处理多变量非高斯分布和捕捉变量间复杂相关性结构的能力,能够有效处理光伏出力的不确定性与时空相关性,从而提高预测精度和可靠性。此外,文档还列举了多个相关的科研方向和技术应用实例,如风电预测、虚拟电厂调度、风光制氢合成氨系统优化、多目标优化算法等,展示了其在电力系统、新能源、优化调度等多个领域的广泛应用前景。; 适合人群:具备一定编程基础,尤其是熟悉Matlab编程语言,从事新能源、电力系统、优化调度、机器学习等相关领域研究的科研人员和研究生。; 使用场景及目标:①应用于光伏发电功率的高精度时空概率预测,为电网调度、能源管理和市场交易提供决策支持;②作为研究Copula理论和MBLS算法在复杂非线性系统建模中应用的案例,促进相关算法的改进与创新;③结合文中提到的其他优化算法(如多目标优化、智能优化算法)和应用场景(如虚拟电厂、综合能源系统),构建更复杂的系统优化与决策模型。; 阅读建议:此资源不仅提供了具体的代码实现,还涵盖了丰富的科研背景和应用方向。建议读者在学习过程中,不仅要理解MBLS和Copula理论的核心思想与实现细节,还应结合文中提及的其他技术(如优化算法、深度学习模型)进行横向对比和综合应用,以拓宽研究视野。同时,鼓励读者基于提供的代码框架,针对具体问题进行参数调整和模型改进,通过实践加深对理论的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值