背景:Java中“一切皆是对象”,为什么还有非对象的“==”?
在Java语言假设我们只进行OOP,所以Java代码都是由一个接着一个的类组成的。那么,对象之间比较,用equals()就可以了。
可为什么“==”在代码随处可见呢?
Java是基于C++的,相比与C++,Java是一种更“纯粹”的面向对象程序设计语言。C++和Java都是混合/杂合型语言。
但是,Java的设计者认为这种杂合性并不像在C++中那么重要。
Java编程思想
要想搞清楚这些,需要先拆解一下:
equals()和“==”的区别
equals():在Java中每个对象都默认继承了Object.equals()方法,equals()用来比较两个对象是否内容相同。默认是通过比较两个对象的引用是否相同来判定。
==是Java语言中的关系操作符之一,用于比较操作数是否相等。
==用于对象时,比较的就是对象的引用是否相同。也就是Object.equals()的默认实现
公众号:的数字化之路Java中什么时候用==,什么时候用equals
上面的介绍中,出现了新词:“操作数”。这个在Java中出现的频次并不高的“操作数”与对象、基本类型是什么关系呢?
操作数与基本类型的关系
操作符作用于操作数,生成一个新值。
几乎所有的操作符只能操作“基本类型”。
例外的操作符是“=”,“==”和“!=”,这些操作符能操作所有的对象(这个令人困惑的点,也是这篇文章出现的原因)。
String类支持“+”和“+=”。如果==用于两个对象,则比较这两个对象的引用,如果相等则表示这两个对象是同一个对象。Java编程思想
小结:所有的基本类型都是操作数。对象也可以是操作数。实际上在最底层,Java中的数据是通过使用操作符来操作的。也包含比较对象的内容(状态)。
Java已经是更“纯粹”的面向对象语言了,为什么还有基本类型?
什么是“面向对象”?
这个“坑”有点大。要聊“面向对象”,“面向过程”是避不开的。
什么是面向过程?系统是物理世界的映射。物理世界中任何事物都有其生命周期,生命周期按时间推进的特性形成了面向过程。
用面向过程的方式来思考现实世界人与人之间的分工的时候,就会遇到很大的困难。比如,流程的每个节点是分散在不同的角色身上的。用面向过程的方式来思考流程时,不同角色的界限就没有了,他们的职责和权利就会淹没在流程中。如果完全采用面向过程的方式,经常会搞得一团糟,一旦需求有任何变化,就会使得所有人都疲于应对。
什么是面向对象?生命周期中还有另外一个特性,即在事物生命周期推进的过程中,生命周期的主体是不变的,这个事物所有的生命周期活动都作用并积累在该事物的本身,如果把“事物”替换为“对象”,就是一个对象的生命周期活动就是该对象的行为,也就是方法;一个对象行为的结果是该对象的状态,也就是数据。
可见,面向过程和面向对象这是软件世界对物理世界让两种理解,就像“盲人摸🐘”一样。两者既不相同、也不对立,而是各有各的特点,反映的是同一个事物的不同侧面,用一个更恰当的词语来说就是“非一非二”:两者既不是同一个,也不是两个不同的东西,而是同一个事物的两个侧面,不可分割。
如何不可分割呢?
先“拆”:一个生命周期拆分之后,会形成多个子生命周期。也就是说,一个对象拆分之后,会形成多个对象,每个对象都具备自己生命周期的所有活动,这就是对象的拆分。
后“合”:生命周期的拆分,在软件中表达为对象的拆分。拆分之后的合并,则是通过面向过程的代码来串联,形成访问流程,也就是访问生命周期。最终还是完整地完成了原有主体的所有生命周期活动。
先“拆”后“合”的好处:拆分之后,不同的业务人员可以分别关注不同的生命周期,并行地推进用户的访问生命周期;不同的代码人员也可以分别关注不同的生命周期,并行地编写代码,从而提升代码产出的效率,使得不同的代码人员之间不会互相干扰。这就是因为“内聚”而带来的“松耦合”。
面向对象思想和语言的出现,使得用对象来模拟这个世界的每个独立生命周期成为了可能,让软件可以更加自然、轻松地模拟现实世界。
上面的解读是不是信息密度太大?来看看ChatGPT3.5的回答:
面向对象,全称是面向对象编程(Object-Oriented, OOP),是一种编程思想。
它主要基于对象的概念,将现实世界中的事物看做是对象,并且通过对象之间的交互来完成任务。
面向对象的面向是指OOP的三大特性:
封装(encapsulation)、
继承(inheritance)、
多态(polymorphism)。ChatGPT3.5
封装和面向对象的内聚有关系。
继承和多态,更多的是指事物的共性。
封装是指将对象的状态和行为封装在一个单元内,对外部世界隐藏内部的实现细节,只暴露必要的接口。这种做法可以提高代码的可维护性和可复用性,同时也保证了数据的安全性。
继承是指一个类可以从另一个类中继承属性和方法,并且可以在此基础上扩展自己的功能。继承可以减少代码的重复性,提高代码的复用性。
多态是指同一个类的不同实例或者不同类之间可以表现出不同的行为,实现方式主要有方法重载和方法覆盖等。多态可以增强程序的灵活性和扩展性,让代码更具有可读性和可维护性。 因此,面向对象的“面向”是指在OOP中,封装、继承和多态等特性的应用和实现。
ChatGPT3.5
总结一下,OOP是“是对一个主体活动生命周期的拆解策略,通过一个个对象来封装对象在过程中不同活动时的行为和状态(数据),通过行为和数据的内聚,来实现构建更大的软件规模和提升研发效率的效果” 。
所以,上面的问题实际是在问:“基本类型”如何提升了软件研发效率?
“基本类型”如何提升了软件研发效率?
软件系统的性能维度
OOP的理念很好,支持OOP的语言也很多,要是基于OOP思想使用Java语言开发出来的系统运行慢或太消耗资源,就无法商用更无法普及。
因此 ,在程序设计中经常用到一系列类型,它们需要特殊对待。可以把这引起类型想像成“基本类型”。之所以特殊对待,是因为new将对象存储在“堆【Heap】”里,故用new创建一个对象---特别是小的、简单的变更,往往不是很高效。
因此对于这些类型,Java采用与C和C++相同的办法。也就是,不用new来创建变量,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并存于“栈【Stack】”中,因此更加高效。
以Java语言为例:JVM虚拟机运行时数据区
Java对象的一个行为(方法)执行时,数据在JVM中的存放情况:
软件系统的规模提升维度
可读性、可理解性不高,这个软件的可维护性就不好。一个维护困难的系统必然会慢慢“腐烂”掉,表现出来的就是“改不动了”。
OOP再好,要是代码的可读性和可理解性不好,对初学者不友好,会导致学习门槛高,上手困难,也会间接影响这门语言的普及。用的人少了,生态就难以建立。没有生态,这门语言会慢慢沦落为一个小众的“玩具”语言。
可读性、可理解性:Don't make me think
同样的加减乘除,基于基本类型的代码:
int i=1+1;
int j=i-1;
int k=i*j;
int h=k/2;
同样的加减乘除,基于对象行为的代码:
BigDecimal oData1 = new BigDecimal("20.120");
BigDecimal oData2 = new BigDecimal("20.111");
BigDecimal addResult = oData1.add(oData2);
BigDecimal subtractResult = oData1.subtract(oData2);
BigDecimal multiplyResult = oData1.multiply(oData2);
BigDecimal divideResult = oData1.divide(oData2, 3, RoundingMode.HALF_UP);
如上,那种代码的可读性、可理解性更好, 不言自明。
小结:
面向对象与“==”解决了软件开发过程中不同层面的问题。用一个更恰当的词语来说就是“非一非二”:两者既不是同一个,也不是两个不同的东西,而是同一个事物的两个侧面。
面向对象是对一个主体生命周期的拆解策略,通过一个个对象的一层层封装来实现构建更大的软件规模和提升研发效率的效果。正所谓“诚以兵虽众多。而层层管辖。分数既明。则如身使臂。如臂使指。左右运掉。仍如一人。”
"=="操作符是具体代码层面的。在这个层面,重点要考虑的是性能、效率、易用性、可读性、可理解性。+、-、*、/、=、== 是跨语言和种族的,用起来自然而言,当然是首选了。
另外,就Java语言而言,支持OOP语言的很多,在做取舍时肯定是演化优于一步到位:先火了再说。那就怎么方便、怎么能快速占领市场就怎么来了。