JAVA//继承

1. 类、超类和子类

在这里插入图片描述
JAVA使用extends替代了C++中的:

在Java 中, 所有的继承都是公有继承, 而没有C++ 中的私有继承和保护继承。

父类 == 超类 == 基类
子类 == 派生类 == 孩子类

1.1 覆盖方法

如果在子类中定义了一个与超类签名相同的方法, 那么子类中的这个方法就覆盖了超类中的这个相同签名的方法。(签名=方法名字+参数列表,返回类型不是签名的一部分),如果覆盖后修改了返回类型,称这两个方法具有可协变的返回类型。

子类的方法不能够直接地访问超类的私有域。 必须借助超类的公共接口才能访问。

可用super关键字访问父类接口,super只是一个指示编译器调用超类方法的特殊关键字,与this原理不同。
可使用super.调用超类公共方法。

1.2 子类构造器

由于子类的构造器不能访问超类的私有域, 所以必须利用超类的构造器对这部分私有域进行初始化。
我们可以通过super 实现对超类构造器的调用。使用super 调用构造器的语句必须是子类构造器的第一条语句
如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器, 并且在子类的构造器中又没有显式地调用超类的其他构造器’则Java 编译器将报告错误。
在这里插入图片描述
关键字this 有两个用途: 一是引用隐式参数, 二是调用该类其他的构造器, 同样,super 关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。

在调用构造器的时候, 这两个关键字的使用方式很相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。

一个对象变量可以指示多种实际类型的现象被称为多态。在运行时能够自动地选择调用哪个方法的现象称为动态绑定( dynamic binding)。

在Java 中, 不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如
果不希望让一个方法具有虚拟特征, 可以将它标记为final。

1.3 继承层次

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

1.4 多态

程序中出现超类对象的任何地方都可以用子类对象置换**。可以将一个子类的对象赋给超类变量**。(JAVA的对象变量事实上是指向对象的关系,类似指针和引用的结合)

在Java 中, 子类数组的引用可以转换成超类数组的引用, 而不需要采用强制类型转换。
在这里插入图片描述

1.5 方法调用

如果是private 方法、static 方法、final方法、构造器,那么编译器将会准确知道该调用哪个方法,称为静态绑定。因为这些方法无法被子类覆盖

而如果调用的方法依赖于隐式参数的实际类型,则会采用动态绑定。虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。

每次调用方法都要进行搜索,时间开销相当大。因此, 虚拟机预先为每个类创建了一个方法表( method table), 其中列出了所有方法(包括继承的)的签名和实际调用的方法。这样一来,在真正调用方法的时候, 虚拟机根据签名查找这个表就行了。

super关键字会让编译器对隐式参数超类的方法表进行搜索

在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。

1.6 阻止继承:final类和方法

将方法或类声明为final 主要目的是: 确保它们不会在子类中改变语义

将类声明为final:不允许定义该类的子类。
将方法声明为final:不允许子类自定义该方法。

域也可以被声明为final。对于final 域来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动地成为final ,而不包括域。

如果一个方法没有被覆盖并且很短, 编译器就能够对它进行优化处理, 这个过程为称为内联( inlining ),内联的好处:CPU 在处理调用方法的指令时, 使用的分支转移会扰乱预取指令的策略。直接将函数体展开就无需调用方法了。

1.7 强制类型转换

强制类型转换一般格式如下:

target_type t=(target_type) s;

对象引用的转换语法与数值表达式的类型转换一样。如下:
在这里插入图片描述
进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后, 使用对象的全部功能。比如将指向子类对象的基类对象引用暂时转化为子类对象引用,可以访问子类的独有方法或域。

可将子类引用赋给超类变量编译器是允许的。但将一个超类的引用赋给一个子类变量, 必须进行类型转换(不一定能成功), 这样才能够通过运行时的检査。

在进行类型转换之前, 先查看一下是否能够成功地转换。这个过程简单地使用instanceof 操作符就可以实现。
instanceof用法如下:

boolean b = object instanceof class

类的实例包括本身的实例,以及所有直接或间接子类的实例。即当Object是class或者它的子类的实例时,返回true。

也就是说,对于强制类型转换:
•只能在继承层次内进行类型转换。
•在将超类转换成子类之前,应该使用instanceof 进行检查

java的这种类型转换处理过程有些像C++的dynamic_cast 操作。
在这里插入图片描述
它们之间只有一点重要的区别: 当类型转换失败时, **Java 不会生成一个null 对象,而是抛出一个异常。**从这个意义上讲, 有点像C++ 中的引用转换。

1.8 抽象类

使用abstract关键字声明的抽象方法无需具体实现**。包含一个或多个抽象方法的类本身必须被声明为抽象的。抽象方法充当着占位的角色,
在这里插入图片描述
除了抽象方法之外, 抽象类还
可以包含具体数据和具体方法**。例如,Person 类还保存着姓名和一个返回姓名的具体方法。

即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化。也就是说, 如果将一个类声明为abstract , 就不能创建这个类的对象。但是可以定义一个抽象类的对象变量, 但是它只能引用非抽象子类的对象。

可以通过抽象类对象变量访问非抽象子类的方法。前提是在抽象类内声明了该方法(不要求定义)。

1.9 受保护访问

如果超类中的某些方法允许被子类访问, 或允许子类的方法访问超类的某个域。为此, 需要将这些方法或域声明为protected。

由于其他程序员可以由超类再派生出新类,并访问其中的受保护域,这会破坏封装性。所以,要谨慎使用protected域。
与之相比,protected方法更加实用,如果需要限制某个方法的使用, 就可以将它声明为protected。这表明子类(可能很熟悉祖先类)得到信任, 可以正确地使用这个方法, 而其他类则不行

事实上,Java 中的受保护部分对所有子类及同一个包中的所有其他类都可见。这与c++ 中的保护机制稍有不同, Java 中的protected 概念要比C++ 中的安全性差。

2. Object:所有类的超类

在Java 中每个类都是由它扩展而来的。但是并不需要这样写:
在这里插入图片描述
如果没有明确地指出超类,Object 就被认为是这个类的超类

可以使用Object 类型的变量引用任何类型的对象:
在这里插入图片描述
当然, Object 类型的变量只能用于作为各种值的通用持有者。要想对其中的内容进行具体的操作, 还需要清楚对象的原始类型, 并进行相应的类型转换
在这里插入图片描述
在Java 中, 只有基本类型( primitive types ) 不是对象, 例如, 数值、字符和布尔类型的值都不是对象。
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object 类。

在C++ 中没有所有类的根类, 不过, 每个指针都可以转换成void* 指针。

2.1 equals方法

Object 类中的equals 方法用于检测一个对象是否等于另外一个对象。在Object 类中,这个方法将判断两个对象是否具有相同的引用。这种比较很多时候用处不大,更多时候,需要比较的是两个对象的状态是否相等。

Objects.equals 方法。如果两个参数都为null,Objects.equals(a,b) 调用将返回true ; 如果其中一个参数为null ,则返回false ; 否则, 如果两个参数都不为null, 则调用a.equals(b)。

可以使用@Override 对覆盖超类的方法进行标记,如果标记了@Override的方法却没有覆盖超类的方法,就会报错。

2.2 HashCode

散列码( hash code ) 是由对象导出的一个整型值,没有规律,不同对象具有不同散列码。

hashCode 方法定义在Object 类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。没有自定义hashCode方法的类就会返回该默认散列码。

如果重新定义了equals方法,就必须重新定义hashCode方法,以便用户可以将对象插人到散列表中。
Equals 与hashCode 的定义必须一致:如果x.equals(y) 返回true, 那么x.hashCode( ) 就必须与y.hashCode( ) 具有相同的值

hashCode 方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。

2.3 toString方法

用于返回表示对象值的字符串。

绝大多数(但不是全部)的toString 方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值。如:
在这里插入图片描述
只要对象与一个字符串通过操作符“ +” 连接起来。Java 编译就会自动地调用toString 方法,以便获得这个对象的字符串描述。

调用System.out.println(object)方法,println 方法就会直接地调用x.toString()

在这里插入图片描述

3. 泛型数组列表

Java允许在运行时确定数组的大小:
在这里插入图片描述
若想要更灵活的数组,可以使用ArrayLsit。

ArrayList 是一个采用类型参数的泛型类。
在这里插入图片描述
对上述省略类型的写法,如果赋值给一个变量, 或传递到某个方法, 或者从某个方法返回, 编译器会检査这个变量、参数或方法的泛型类型, 然后将这个类型放在<>中。

使用add添加:
在这里插入图片描述
调用add 且内部数组已经满了, 数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中

还可以使用带索引的add:
在这里插入图片描述
新元素插入在下标为n的元素之后。

可以删除元素:
在这里插入图片描述
删除下标为n的元素。

使用ensureCapacity,为数组列表声明保存若干元素的潜力,在不超过这个声明值时,即使内部数组容量不够,使用add也不会导致重新分配空间
在这里插入图片描述
或使用:
在这里插入图片描述
使用size返回实际元素数目,等价于a.length:
在这里插入图片描述
一旦能够确认数组列表的大小不再发生变化, 就可以调用trimToSize 方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。
一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储块,所以应该在确认不会添加任何元素时, 再调用trimToSize。

在这里插入图片描述

3.1 访问数组列表元素

使用get获取元素(i是下标):
在这里插入图片描述
使用add 方法为数组添加新元素, 而不要使用set 方法, 它只能替换数组中已经存在的元素内容

使用set设置元素(i是下标):
在这里插入图片描述
使用toArray方法将数组列表元素拷贝到一个数组内(list为列表数组):
在这里插入图片描述

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

可以将类型化的数组列表传递给原始数组列表:
在这里插入图片描述
在这里插入图片描述
可以给updata传递类型化数组列表staff。

而将一个原始ArrayList 赋给一个类型化ArrayList 会得到一个警告:
在这里插入图片描述
即使将返回的原始ArrayList 使用强制类型转换为类型化ArrayList 也不能避免出现警告:
在这里插入图片描述
会得到另一个警告信息,指出类型转换有误。

鉴于兼容性的考虑, 编译器在对类型转换进行检査之后, 如果没有发现违反规则的现象, 就将所有的类型化数组列表转换成原始ArrayList 对象。在程序运行时, 所有的数组列表都是一样的, 即没有虚拟机中的类型参数。

如果确定这种赋值不会造成严重的后果,可以使用标注来标记这个变量能够接受类型转换, 如下所示:
在这里插入图片描述

4. 对象包装器与自动装箱

所有的基本类型都冇一个与之对应的类,Integer 类对应基本类型int,这些类称为包装器。

对象包装器类是不可变的, 即一旦构造了包装器, 就不允许更改包装在其中的值。同时, 对象包装器类还是final , 因此不能定义它们的子类

尖括号中的类型参数不允许是基本类型,所以想要定义基本类型的数组列表,需要使用包装器。
在这里插入图片描述
在这里插入图片描述
将基本类型赋值给对应的包装器,或是将包装器赋值给对应的基本类型时,会自动完成基本类型到包装器的自动装箱和拆箱
在这里插入图片描述
在这里插入图片描述

甚至在算术表达式中也能够自动地装箱和拆箱:
在这里插入图片描述
n=3完成装箱,n++先拆箱完成++,再装箱。

自动装箱规范要求boolean、byte、char<=127,以及介于-128 ~ 127 之间的short 和int 被包装到固定的对象中,也就是再这个范围内可以用==(== 运算符也可以应用于对象包装器对象, 只不过检测的是对象是否指向同一个存储区域)作用于包装器,来比较两个包装器对应的值是否相等。超出这个范围,就不一定能这样做了。

由于包装器类引用可以为null , 所以自动装箱有可能会抛出异常:
在这里插入图片描述

如果在一个条件表达式中混合使用Integer 和Double 类型, Integer 值就会拆箱,提升为double, 再装箱为Double:
在这里插入图片描述
装箱和拆箱是编译器认可的, 而不是虚拟机。编译器在生成类的字节码时, 插人必要的方法调用。虚拟机只是执行这些字节码。

可以将某些基本方法放置在包装器中, 例如, 将一个数字字符串转换成数值:
在这里插入图片描述

5. 参数数量可变的方法

以printf方法为例子:
在这里插入图片描述
printf方法接收两个参数,一个是格式字符串, 另一个是 Object[]数组, 其中保存着所有的参数(如果调用者提供的是整型数组或者其他基本类型的值, 自动装箱功能将把它们转换成对象 )

即参数可变的写法为:
type .. name。name就是一个type[]的数组。
可使用new type[ ] {instance1,instance2 …}显式的将一组实参传给可变参数。

6. 枚举类

枚举类型的声明实际是创建了一个类,并且实例化了若干对象。
在这里插入图片描述
因为枚举类型只有有限个对象,所以比较枚举类型是否相等直接使用==比较地址即可。

所有的枚举类型都是 Enum 类的子类。它们继承了这个类的许多方法。其中最有用的一个是 toString, 这个方法能够返回枚举常量名

toString 的逆方法是静态方法 valueOf,返回指定字符串名字的枚举值:
Size s = Enum.valueOf(Size.class,"SMALL"); 前述语句将s设置成 Size.SMALL。

每个枚举类型都有一个静态的 values 方法, 它将返回一个包含全部枚举值的数组。
Size[] values = Size.values() ;

ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数。 例如:Size.MEDIUM. ordinal() 返回 1。
在这里插入图片描述

7. 反射

能够分析类能力的程序称为反射。

7.1 Class类

在程序运行期间,Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。保存这些信息的类被称为Class。虚拟机利用运行时类型信息选择相应的方法执行。

一个Class 对象实际上表示的是一个类型, 虚拟机为每个类型管理一个Class 对象,而这个类型未必一定是一种类类型。如下:
在这里插入图片描述
每个类都从Object类继承了getClass方法,他会返回一个Class的实例。

一个Class 对象将表示一个特定类的属性,使用Class对象的getName方法,可以返回Class对象存储的类型信息。

类在一个包里,包的名字也作为类名的一部分

可以调用静态方法forName 获得类名对应的Class 对象
在这里插入图片描述
如果className不是类名或者接口名,则会返回一个checked exception ( 已检查异常)。

鉴于历史原因,getName 方法在应用于数组类型的时候会返回一个很奇怪的名字。

newlnstance 方法对应C++ 中虚拟构造器的习惯用法。然而,C++ 中的虚拟构造器不是一种语言特性, 需要由专门的库支持。

7.2 捕获异常

抛出异常比终止程序要灵活得多,这是因为可以提供一个“ 捕获” 异常的处理器(handler ) 对异常情况进行处理

异常有两种类型: 未检查异常和已检查异常。对于已检查异常, 编译器将会检查是否提供了处理器。然而,有很多常见的异常, 例如, 访问null引用, 都属于未检查异常。编译器不会査看是否为这些错误提供了处理器。

异常处理格式如下:
在这里插入图片描述

7.3 利用反射分析类的能力

Class 类中的getFields、getMethods 和getConstructors 方法将分别返回类提供的public方法构造器数组, 其中包括超类的公有成员。

Class 类的getDeclareFields、getDeclareMethods getDeclaredConstructors 方法将分别返回类中声明的全部方法构造器, 其中包括私有和受保护成员,但不包括超类的成员。

/域对应Field对象,方法对应Method对象,构造器对应Constructor对象/

在java.lang.reflect 包中有三个类Field、Method 和Constructor 分别用于描述类的域、方法和构造器。

这三个类都有一个叫做getName 的方法, 用来返回项目的名称

Field类有一个getType 方法, 用来返回描述域所属类型的Class 对象

Method和Constructor 类有能够报告参数类型的方法。

Method 类有一个可以报告返回类型的方法。

这三个类还有一个叫做getModifiers 的方法, 它将返回一个整型数值, 用不同的位开关描述public 和static 这样的修饰符使用状况

可以利用java.lang.refleCt 包中的Modifier类的静态方法分析getModifiers 返回的整型数值。例如, 可以使用Modifier 类中的isPublic、isPrivate 或isFinal判断方法或构造器是否是public、private 或final。

还可以利用Modifier.toString 方法将修饰符打印出来

7.4 在运行时查看域的值

如果f 是一个Field 类型的对象(例如,通过getDeclaredFields 得到的对象),
obj 是某个包含f 域的类的对象,f.get(obj) 将返回一个对象,其值为obj 域的当前值,注意该域需要是可访问的,否则会抛出异常。如下:
在这里插入图片描述
get返回的是一个Object对象,其值为域的值。
Java 中数值类型不是对象。如果返回的是数值类型的域的值怎么办,反射机制将会自动地将这个域值打包到相应的对象包装器

setAccessible 方法是AccessibleObject 类中的一个方法, 它是Field、Method 和Constructor类的公共超类,可使用该方法覆盖访问控制
在这里插入图片描述
可用set覆盖Field对象所表示的域:

void set(Object obj ,Object newValue)

可使用toString查看任意对象的内部信息:

return new ObjectAnalyzer().toString(Object);

更常用的用法是用该方法定义自己的tostring方法:
在这里插入图片描述

7.5 使用反射编写泛型数组代码

如何编写一个通用的复制数组的函数?
在这里插入图片描述
在这里插入图片描述
这样做是有问题的:将一个Employee[] 临时地转换成Object[] 数组, 然后再把它转换回来是可以的,但一从开始就是Object[] 的数组却永远不能转换成Employe[]数组。

Array 类中的静态方法newlnstance,它能够构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度
也就是说,要创建一个和原数组一样的数组,需要知道数组元素类型和数组长度。
数组长度直接使用Aarray.getLenth(a)即可。
使用以下方法获取数组元素类型
1)获取a 数组的类对象,为何不获取对象数组?因为可能有元素不是对象的数组,获取类对象是最通用的。
2 ) 确认它是一个数组。
3 ) 使用Class 类只能定义表示数组的类对象)的getComponentType 方法确定数组对应的类型。

7.6 调用任何方法

在C 和C++ 中, 可以从函数指针执行任意函数。从表面上看, Java 没有提供方法指针。

与Field类使用get方法查看域类似,Method类也有一个invoke方法:
在这里插入图片描述
invoke 的参数和返回值必须是Object 类型的。第一个参数是隐式参数,即类方法里的this,后面的参数是显式参数。静态方法可以省略第一个参数,此时它被设置为null。
返回类型是基本类型, invoke 方法会返回其包装器类型

可使用Class 类的getDeclareMethods对返回的的Method 对象数组进行查找, 直到发现想要的方法为止。

也可使用Class 类的getMethod获取想要的方法:
Method getMethod(String name, Class… parameterTypes)
第一个参数是方法名,后面的参数是想要的方法的参数类型(因为可能有同名方法)。
在这里插入图片描述
除非所有继承的方法都有意义, 否则不要使用继承

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值