读《Java核心技术 卷I》有感之第5章 继承

写在前面:最近拘泥于研究生公司这边的一些收尾工作,以及相关的论文工作。说实话,感觉没啥效率,因为说穿了就是来回在一些无所谓的事情中做来做去。你说它重要吧,它确实重要;你说它有趣吧,它确实是相当无聊。总结来说,编码水平和理论基础水平不是一回事,我很喜欢看许多理论基础相关的书籍和描述,但是似乎对实际编码过程感觉没有那么有趣。哎,想想以后的搬砖生活,不得不反思自己所做事情的关键所在,以及如何让自己在多种压力的环境下尽可能的活的开心,并能尽可能高效的完成自己的任务。

5.1 类、超类和子类

 其实想优雅的写一个类感觉好难,我觉得还是看别人写的少了,或者说设计模式运用的太差了。目前我在实际工作中所写的相关类往往都感觉写的不好,总觉得很多可以完善和优化的地方,但是每次都事与愿违。。。感觉很头疼,其实我很喜欢追求代码“优雅”的体验,但是目前的实力真的有些不允许。

5.1.1 定义子类

 java的继承都是公有继承,同时都是使用extends表示继承,而不是用“:”符号。顺便一提我这时候才发现java的类定义时最后不需要写“};”的";"符号,这样还是挺好的。

public class Manager extends Employee
{
	...
}

 这里提到了一个挺重用的类设计概念:将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。这段时间在写AGV与ROS通讯时就大量使用了这种方法,将所有的节点操作放置在超类中,而子类的主要作用是将与对应的按钮操作,同时为其赋予对应按钮所有的独特参数。

5.1.2 覆盖方法

 override在C++与java中都有所运用,其特色是子类定义一个与父类同名同参的方法时将会覆盖掉父类的同名同参方法。java在这里使用一个叫super的概念来给予子类调用父类方法的途径,这里类似于C++在子类中通过(父类名::方法名)方式调用。

super.method == FatherClass::method
5.1.3 子类构造器

 这里是表达java可以使用父类的构造器,具体方式如下:

public Manager(String name, double salary, ...)
{
	super(name, ...);
	salary = 0;
}

 这里顺便也提到了多态的概念:一个对象变量可以指示多种实际类型的现象(说穿了就是使用继承类时的方法多重),同时还有所谓的动态绑定概念:在运行时能够自动选择调用哪个方法的现象

5.1.4 继承层次

 继承层次表示由一个公共超类派生出来的所有类的集合,而在这个层次中,从某个特定的类到其祖先的路径被称为该类的继承链

5.1.5 多态
  • 关键点1:程序中任何出现超类对象的地方都可以通过子类对象进行置换。
  • 关键点2:超类对象最好不要使用子类进行置换。
5.1.6 理解方法调用
  • 关键点1:重载解析即表示查找方法名与参数类型完全匹配的那个方法。
  • 关键点2:方法的名字和参数列表称为方法的签名,这个就是方法用来表征自己的方式。
  • 关键点3:静态绑定即表示private方法、static方法以及final方法或者构造器的调用。
  • 关键点4:JVM会为每个类建立方法表,这些方法表将会联系到对应方法的代码段,或者联系到其超类中方法表的对应位置。
5.1.7 阻止继承:final类和方法

 final类即不允许被扩展的类,也就是无法被继承;final方法及无法被扩展的方法,既无法被其子类的方法所覆盖。

5.1.8 强制类型转换
  • 方法1:C的强制转换int a = (int)b;
  • 方法2:首先运行检查操作符instanceof,比如if(a instanceof b),这个操作符会判断a是否能够转换为b,是则返回true否则false。
5.1.9 抽象类
  • 关键点1:abstract关键字作用于函数,表示该函数为抽象函数如:
public abstract String getDescription();
virtual string getDescription() = 0; //这里等同于C++中的纯虚函数
  • 关键点2:abstract关键字作用域类,表示该类为抽象基类
public abstract class Person{}
//C++中没有显示声明抽象基类的方式我记得,其默认是有纯虚函数的类即为抽象基类
  • 关键点3:代码撰写时尽量将通用的方法和域放置在超类中
  • 关键点4:继承抽象基类的子类未全部定义所有纯虚函数则该子类仍为抽象类,若全部定义完成则该子类不为抽象类,即可被用于定义实例。
5.1.10 受保护访问

 Java的protected标识符没有C++中的厉害:

  • public:仅对本类可见;
  • priavte:对所有类可见;
  • protected:对本包和所有子类可见;(这里的本包可见表明了安全性较差)

5.2 Object类

 Object类是Java中所有类的超类,这里很类似于Qt中的QObject类作为所有Qt相关类的超类一样(其实应该说Qt参考了Java的)。这个类可以引用任何类型的对象(这里说明只有数值int、字符char以及布尔类型boolean这样的基本类型不是对象,其他的预定义类之类的都是对象。而Java中所谓的对象,就是对C++中指针的一种包装形态,其本质就是在用指针罢了)

5.2.1 equals方法

 书中给出了这个方法的基本原理,也就是判断过程,这里用形如a.equals(b)的方式来理解:

  • 1.判断a与b两者是否引用同一对象(其实就是判断两指针的地址是否相同);
  • 2.判断b是否为null(其实就是判断b是否为nullptr);
  • 3.判断a与b两者是否为同一个相同类的类型(借助getClass()方法来实现);
  • 4.判断a与b两者的状态是否相同(状态就是类内部的所有成员变量数值)。
5.2.2 相等测试与继承

 书这里之后的两页相当有意思,主要探讨了equals方法的五种独特特性:

  • 自反性:对于任何非空引用,x.equals(x)都应该返回true;
  • 对称性:对于任何引用x与y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true;
  • 传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true;
  • 一致性:如果x与y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
  • 对于任意非空引用x,x.equals(null)应该返回false。

 这里有一个很有意思的地方,在定义子类的equals方式时,首先要调用父类的equals方法,即确定父类的成员相同时再判断子类的。(说到这里,发现Java对于类相等的判断不同之处就在于此,C++中会采用重载“= =”运算符这样的操作来实现,即鼓励程序员通过这种方式来进行判断。而Java则是选择每个类定义自身的equals函数,而弱化“= =”符号的权限,使等号判断只能单纯的判断指针地址的相等)。
 与此同时,作者也在这疯狂探讨equals方法相对于instanceof操作符的优势,我个人比较赞同作者的观点,既然要判断两对象相等那就应该保持完全相等,走后门的相等可以单独写函数实现。不过这里equals方法与重载“==”操作符时的写法其实很类似,不同书中给出的写法更严谨,具体如下:

public class Employee
{
	...
	//1.将形参命名为otherObject,这里主要是一个直译的名字问题
	public boolean equals(Object otherObject)
	{
		//2.检查this与otherObject的对象是否相等(==用来判断指针值,或者判断null值相等)
		if(this == otherObject)
			return true;
		//3.检查otherObject是否为null,用于实现对当前类非null时对null的独特判断方式
		if(otherObject == null)
			return false;
		//4.比较this与otherObject是否为同一个类,实现对称性
		if(this.getClass() != otherObject.getClass())
			return false;
		//这里书中提到如果子类都具有统一的语义则可以使用instanceof操作符,不过既然不知道语义是否被修改,那么一直使用getClass它不香吗
		//5.将otherObject转换为与this相应的类类型变量,并判断两者中所有域是否相等
		//(其中基本类型采用==操作符比较,对象采用equals进行比较)
		Employee other = (Employee)otherObject;
		return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay);
	}
}
5.2.3 hashCode方法

 散列码是用来表示当前对象独特性的一个整型值(所以总是要和equals一致,两个都是表示一个对象的独特性的),与其相关有几点如下:

  • 字符串的散列码是由内容导出的,所以内容相同的两个字符串变量的散列码相同(从底层上来说,字符串属于常量,内容相同底层地址确实也相同);
  • 字符串缓冲有不同的散列码,因为对应的StringBuffer类没有定义hashCode方法,所以使用的Object方法中的hashCode方法,而这个方法是通过对象存储地址来实现计算的;
  • 如果重新定义了equals方法,那么就必须重新定义hashCode方法(这估计涉及到散列表插入的原理);
  • hashCode方法必须与equals方法保持一致,即如果x与y的equals结果相同,那么就必须保证x与y的hashCode函数结果也保持一致。
5.2.4 toString方法

 这个函数说穿了就是把对应类中的所有参数名字与值全部变成String类型展示出来,换句话说就是便于获取当前类中的所有参数信息。

5.3 泛型数组列表

 首先讲了Java中最棒的数组变量大小设计,这相对于C++来说这简直是太棒了!

int size = ...;
Employee[] staff = new Employee[size];

 然后讲了Java的动态数组ArrayList,并提到了两个方法:

  • void ensureCapacity(int capacity):填充数组之间的容量capacity扩张(哦我的上帝!像极了std::vector);
  • void trimToSize():将当前的容量capacity调整为当前size大小(哦我的上帝!像极了std::vector)。

 这里还提到了ArrayList没有重载[]运算符(哦我的上帝!这可太蠢了),同时表示将ArrayList类进行=操作符赋值时会导致两个ArrayList使用的都是同一个内存的ArrayList(毕竟指针传递,问题不大)

5.3.1 访问数组列表元素

 主要讲了ArrayList的几个关键函数,从这几个关键函数我看出了这个动态数组设计的还是有点蠢的(index必须保证在size() - 1到0之间):

  • void set(int index, E obj):index位置设成obj(有点蠢);
  • E get(int index):获取index位置的元素值(还是有点蠢);
  • void add(int index, E obj):在index位置插入元素obj,之前在index位置的元素后向后移(像极了以前抄别人作业,硬是非得改几个名词,你说这里用insert不比这个add通俗易懂的多么)
  • E remove(int index):删除index位置的元素,其后面的元素将会向前移动。

 Java的动态数组看来和C++一样,理论上都是适合读取数据,不适合插入与删除数据。不过不了解Java的链表的底层数据结构不清楚,反正std::list就是一场灾难。

5.3.2 类型化与原始数组列表的不兼容性

 这里讲了一个update()函数,没太能搞明白想表达意思,应该是说在类里面自定义一个update函数便于操作某些现有数组?

5.4 对象包装器与自动装箱

 这里涉及到Java的特色操作了,即每个基本类型都对应一个特有的类,这些类往往被称为包装器,这些包装器都是final类型的类,即无法被继承。这些包装器的名字都相当典型:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean,其中前六个都是派生于公共的超类Number。然后这些包装类的值是不可变动的,一旦构造则无法被修改。

  • 自动装箱:比如数组列表ArrayList< Integer >,在定义时不能使用基本类型作为类型参数,必须使用包装器,在add时却可以直接add(3)这样的操作,因为编译器内部会变成add(Integer.valueOf(3))这样的代码。
  • 自动拆箱:情况同上,无非就是直接对于get(i)这样的操作时会变成get(i).intValue()这样的代码。
  • 所谓的箱与基本类型理解:Java的基本类型如int、short这样的类型是沿用C++中的基本类型,所以这些值在创建时都会直接存储在栈上(当然字符串常量那种类型还是在.rodata段上,不赘述),而当使用Integer这样的包装器后其实已经相当于new了空间,所以此时的对象已经成为了指针类型并指向堆上的一块内存区域,所以对Integer变量之间进行“==”操作符将无法实现操作,只能通过equals方法进行判断了。

5.5 参数数量可变的方法

 Java通过类似Object … args这样的形参描述表达可以接受任意数量的Object 对象,这种情况类似于传递了一个Object[]数组。(说实话我不太喜欢这个功能,我个人认为必须保证形参的数据结构确定性,不然编码过程会很头疼)

5.6 枚举类

 Java的枚举类很常规,都是public enum Size{ SMALL, LARGE, …}这样的表达方式。但是很骚气的是他可以在其中添加构造器、方法和域,通俗的理解来说就是枚举Size中的每个单独枚举比如SMALL以及LARGE这样的都是Size枚举的一个实例,然后通过定义Size的内部参数以及构造器等方式可以为SMALL以及LARGE这样的实例进行初始化,从而在对应不同的枚举时调用方法的结果都可以有所不同。这里记录一下:

public enum Size
{
	SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");

	private String abbreviation;
	private Size(String abbbreviation) {this.abbbreviation = abbbreviation;}
	public String getAbbbreviation( return abbbreviation;}
}

5.7 反射

 说实话我现阶段真的看不太懂反射,这里明确表示能够分析类能力的程序称之为反射(reflective),这里简单记录下书中描述的反射作用:

  • 在运行时分析类的能力;
  • 在运行时查看对象,例如,编写一个toString方法供所有类使用;
  • 实现通用的数组操作代码;
  • 利用Method对象,这个对象很像C++中的函数指针。
5.7.1 Class类

 保存对象运行时类型标识信息的类被称为Class,这个类实际上是一个泛型类,就像template< typename >这样的存在。其常用的方法是getName(),具体表现为【包名+类名】。

5.7.2 捕获异常

 两种异常类型:未检查异常与已检查异常,检测异常则为经典的try catch方式。

5.7.3 利用反射分析类的能力

 从这里开始打算把反射先放一放,暂时不太能具体理解这种方法的使用状态,简单记录下java.lang.reflect包中的三个类:

  • Field类:描述类的所有域;
  • Method类:描述的类的所有方法;
  • Constructor:描述类的所有构造器。

5.8 继承的设计技巧

  • 1.将公共操作和域放在超类:说穿了就是子类基本上都会用到的东西尽量放在超类中,最近写硬件相关的模板类时,就是把诸如connect()、disconnect()、write()、read()作为纯虚函数写在了基类之中,让子类去实现即可。
  • 2.不要使用受保护的域:书中表达的原因其实是Java自身对protected变量的保护性做的并不好,一是子类派生时可以直接通过代码访问protected变量(不过我在代码编写中使用protected变量就是为了让子类直接访问啊。。。可能我的理解与大佬不一样),二是因为同包中的其他类都可以访问类中的protected变量(我觉得这个才是主要原因)。
  • 3.使用继承实现“is-a”关系:继承其实说到底的根本目的是为了实现代码复用,但是代码复用时的关键是需要尽量保证被继承的代码要被尽可能的使用而不是继承后就是一坨废铜烂铁。换句话说,如果需要将一个东西作为a的子类就尽量保证is-a,否则建议重新写父类继承或者单独写。
  • 4.除非所有继承的方法都有意义,否则不要用继承:大意就是说,尽可能的保证父类中被继承的方法都是能够为子类所用的,否则就没有必要。
  • 5.在覆盖方法时,不要改变预期的行为:这里想表达的是在子类在覆盖父类的方法时(注意是覆盖,而不是实现纯虚函数这种情况),尽量不要偏离原本父类方法的本意,不然会造成歧义。
  • 6.使用多态,而非类型信息:这里的意思如果存在判断当前某个对象的状态而执行不同的操作时,应该将相关的操作统一写在这个对象的一个方法中,从而在调用时直接调用对应方法即可,而不是要做一系列的状态判断。就像下面这样:
if(x is of type1)
	action1(x);
else if(x is of type2)
	action2(x);
//将根据type的不同操作封装在action中,从而直接调用action即可:
x.action();
  • 7.不要过多地使用反射:大意就是反射操作很高端,在编写应用程序不容易发现错误,即编译器不是很好发现反射造成的错误,所以建议少使用。

后记:这一章主要讲了Java的类、继承和多态,也就是相关类设计者的基础知识,整体来看其实与C++的区别并不大,有些地方的改进很有特色(比如动态数组,类中的main函数等等),但是有的地方就改进的稀烂(比如ArrayList的那几个函数),但是总体而言还是感觉Java的编写方式其实更好操作一点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方寸间沧海桑田

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值