软构实验lab2过程遇到的几个问题及其学习理解

一、mutable和immutable

首先java有两种数据类型:基本数据类型和引用数据类型。

  • 基本数据类型包括八种:byte,short,int,long,char,boolean,float,double
  • 其余的都是引用数据类型。

引用数据类型的对象其实很像c语言中的指针,它本身存储着一个指针,指向一个空间,该空间存储实际要用的内容。

而不可变类型(immutable type)说明上述的指针所指向的空间内容不能被改变,这样的引用类型就是不可变类型。

而可变类型(mutable type)相反,空间的内容可以改变。

但是无论可变还是不可变类型,其指针所指向的空间是可以变的。

经典例子是String是不可变类型,而StringBuilder是可变类型。

那要改变不可变类型怎么办?只能根据原空间内容,新建一个空间,在该空间上写入新的内容,然后让该不可变的引用类型对象(实际上是指针)指向这个新的空间。旧的空间会自动被回收掉。

针对这次实验,自己设计的类中,Edge是不可变类型,Vertex是可变类型。在ConcreteEdgesGraph中的set方法内,要改动其中的一个Edge,只能返回新的Edge。而在ConcreteVerticesGraph中就可以直接改Vertex的内容,因为Vertex被设计为mutable类型,会附带设计一些方法去改变其内容。

二、使用final和其他安全性

注意到这次实验中的类的私有变量都使用了final类型。使用了final类型的变量意味着其值不可被改变。如果是基本数据类型,则无论如何都不可被改变,该变量就当是一个常量了。若是引用数据类型,则所要引用哪个空间是不可改变的,但空间内的内容不受final约束,即如果是mutable则空间内容可以改变,若是immutable则不可。

还有一个要注意的类安全问题是:不要轻易返回引用类型的私有变量。道理很简单,引用类型对象就是指针,返回指针后外部就可以获取其指向的空间,从而会在类不知情的情况下改动空间内容,影响到类。

之所以这个问题需要注意,是因为java的指针使用并不明显,例如一个数列List对象,很容易就把它当作一个完整的数列而不是指针。返回时想当然地直接返回,以为返回的只是它的空间内容。

三、拷贝

拷贝分为深拷贝和浅拷贝。

浅拷贝就是直接赋值这个对象,注意对于引用数据类型,浅拷贝返回的是该对象的指针。

深拷贝拷贝一个引用数据类型对象时,返回的就是空间的内容相同,但空间(或指针值)不同的一个对象。

深拷贝就用于解决上面第二点中的安全问题。例如想要返回类内的一个List,但又不想后续类内的List的空间内容被改变。就可以用深拷贝,返回一个新的指针(引用数据类型对象,即一个新的List),而新的List和类内的List的空间内容相同。

深拷贝的简单方式,以上面深拷贝List来返回为例,用return new List<>(list);即可。其中list就是类内的List变量,该语句返回list的深拷贝。

还要注意一下,超类(一切类的父类)Object的clone方法是浅拷贝,即如果使用list.clone(),则返回的就是list本身(这个指针)。

四、equals和==

==永远比较的只是对象本身。基本数据类型间使用==正常理解。但引用数据类型使用==,比较的是它们本身,即比较的是指针,而不是它们所指向的空间。下面是一个例子。

s1和s2所指向的"abc",是代码中的常量,编译时会当成一个地址用,所以s1和s2所指向的地址相同(前面说到,String是引用类型,当作指针来看)。而s3开辟了一个新空间,并以"abc"为空间内容,返回的是一个新地址。

现说说Object.equals方法,通常情况下equals内部使用的就是==。

但是String类型应该对equals进行了重写(@Override),所以使用String.equals比较的是字符串是否相同,而不是两个String所指向的地址是否相同。

这里equals的应用是,当Graph从String变成了泛型<L>后,由于在编写String版本时,使用了String.equals进行比较。那么到了泛型的Graph,虽然还能适用于大部分类,但例如一些自定义类,可能需要重写一下equals方法。

例如后面使用了Person作为Graph的L。Graph用在FriendshipGraph中,根据FriendshiGraph的规约,其图内部不能有名字相同的两个节点。而名字是String,包装在Person类内。

在Graph中,会调用Person.equals来,试图以此来满足图内不能有名字相同的两点这条规约。而如果Person.equals未被重写,则比较的是两个Person的地址,而不是Person内的那个String。这样的话,创建两个Person:

Person p1=new Person("somebody");

Person p2=new Person("somebody");

这两个Person类的名字相同,都是somebody,应该是不满足上述的规约的。但由于p1和p2的值实际是不同的,因此会被Graph当作“名字”不同的两个节点输入。

因此要重写.euqals。

五、静态方法使用泛型

一开始我写了一个这样的(简化后的)静态方法,报错了

 而静态方法使用泛型的正确姿势很简单,(此处)在static后面接上<L>即可。修改以及使用例子如下。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值