记录《疯狂Java讲义精粹》划线内容及理解(3-4章)

第三章-流程控制和数组

3.3循环结构

for循环和while、do while循环不一样:由于while、do while循环的循环迭代语句紧跟着循环体,因此如果循环体不能完全执行,如使用continue语句来结束本次循环,则循环迭代语句不会被执行。但for循环的循环迭代语句并没有与循环体放在一起,因此不管是否使用continue语句来结束本次循环,循环迭代语句一样会获得执行。

for循环的循环迭代是不受循环控制语句干扰的


实际上,for循环允许同时指定多个初始化语句,循环条件也可以是一个包含逻辑运算符的表达式。


//省略了for循环三个部分,循环条件将一直为true
for (; ;)

非常简洁的死循环for


public class ForInsteadWhile{
public static void main(String[] args){
//把for循环的初始化条件提出来独立定义
int count= 0;
//for循环里只放循环条件
for( ; count< 10 ; ){
System. out. println(count);
//把循环迭代部分放在循环体之后定义count++;
}
System. out.println("循环结束!");
//此处将还可以访问count变量
}
}

把for循环的初始化语句放在循环之前定义还有一个作用:可以扩大初始化语句中所定义变量的作用域。在for循环里定义的变量,其作用域仅在该循环内有效,for循环终止以后,这些变量将不可被访问。如果需要在for循环以外的地方使用这些变量的值,就可以采用上面的做法。

3.4控制循环结构

break语句不仅可以结束其所在的循环,还可以直接结束其外层循环。此时需要在break后紧跟一个标签,这个标签用于标识一个外层循环。Java中的标签就是一个紧跟着英文冒号(:)的标识符。与其他语言不同的是,Java中的标签只有放在循环语句之前才有作用。

break可直接结束标识符所标识的循环

3.5数组类型

数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才可以使用。


数组的初始化有如下两种方式。

静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度。

动态初始化:初始化时程序员只指定数组长度,由系统为数组元素分配初始值。


执行动态初始化时,程序员只需指定数组的长度,即为每个数组元素指定所需的内存空间,系统将负责为这些数组元素分配初始值。指定初始值时,系统按如下规则分配初始值。

数组元素的类型是基本类型中的整数类型(byte、short、int和long),则数组元素的值是0。

数组元素的类型是基本类型中的浮点类型(float、double),则数组元素的值是0.0。

数组元素的类型是基本类型中的字符类型(char),则数组元素的值是'\u0000'。

 数组元素的类型是基本类型中的布尔类型(boolean),则数组元素的值是false。

数组元素的类型是引用类型(类、接口和数组),则数组元素的值是null。

3.6深入数组

数组是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的


如果需要访问下图所示堆内存中的数组元素,则程序中只能通过p[index]的形式实现。也就是说,数组引用变量是访问堆内存中数组元素的根本方式。


为什么有栈内存和堆内存之分?

答:当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁。因此,所有在方法中定义的局部变量都是放在栈内存中的;

当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(在方法的参数传递时很常见),则这个对象依然不会被销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收器才会在合适的时候回收它。


定义并初始化一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身。


当我们看一个数组时,一定要把数组看成两个部分:一部分是数组引用,也就是在代码中定义的数组引用变量;还有一部分是实际的数组对象,这部分是在堆内存里运行的,通常无法直接访问它,只能通过数组引用变量来访问。


学生提问:我是否可以让图3.13中灰色覆盖的数组元素再次指向另一个数组?这样不就可以扩展成三维数组吗?甚至扩展成更多维的数组?

答:不能!至少在这个程序中不能。因为Java是强类型语言,当我们定义a数组时,已经确定了a数组的数组元素是int[]类型,则a[0]数组的数组元素只能是int类型,所以灰色覆盖的数组元素只能存储int类型的变量。对于其他弱类型语言,例如JavaScript和Ruby等,确实可以把一维数组无限扩展,扩展成二维数组、三维数组……如果想在Java语言中实现这种可无限扩展的数组,则可以定义一个Object[]类型的数组,这个数组的元素是Object类型,因此可以再次指向一个Object[]类型的数组,这样就可以从一维数组扩展到二维数组、三维数组……

第四章-面向对象(上)

类可被认为是一种自定义的数据类型,可以使用类来定义变量,所有使用类定义的变量都是引用变量,它们将会引用到类的对象


面向对象的三大特征:封装、继承和多态

Java提供了private、protected和public三个访问控制修饰符来实现良好的封装,提供了extends关键字来让子类继承父类


初始化块总在构造器执行之前被调用。

静态初始化块用于初始化类,在类初始化阶段被执行。

4.1类和对象

面向对象的程序设计过程中有两个重要概念:类(class)和对象(object,也被称为实例,instance),其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体


Java语言里定义类的简单语法如下:

[修饰符] class类名{
零到多个构造器定义..
零到多个Field. . .
零到多个方法...}

在上面的语法格式中,修饰符可以是public、final、abstract,或者完全省略这三个修饰符,类名只要是一个合法的标识符即可,


对一个类定义而言,可以包含三种最常见的成员:构造器、Field和方法,三种成员都可以定义零个或多个

除了这三种外,其实一个类中还可以包含初始化块


static修饰的成员不能访问没有static修饰的成员。

也就是说,静态成员不能直接访问非静态成员。

关于这个问题后面会有详细解释

Field用于定义该类或该类的实例所包含的状态数据,

构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建实例

通常无法创建,也就是说还有别的途径可以,我猜像这个途径就是反射


static修饰的成员表明它属于这个类本身,而不属于该类的单个实例,因为通常把static修饰的Field和方法也称为类Field、类方法


值得指出的是,构造器既不能定义返回值类型,也不能使用void定义构造器没有返回值。如果为构造器定义了返回值类型,或使用void声明构造器没有返回值,编译时不会出错,但Java会把这个所谓的构造器当成方法来处理。

如果将一个类的构造器(构造方法)的命名前加上返回值,则编译器会把这个构造器当作普通方法来处理


构造器不是没有返回值吗?为什么不能用void修饰呢?

答:简单地说,这是Java的语法规定。实际上,类的构造器是有返回值的,当我们用new关键字来调用构造器时,构造器返回该类的实例,可以把这个类的实例当成构造器的返回值,因此构造器的返回值类型总是当前类,无须定义返回值类型。但必须注意:不能在构造器里显式使用return来返回当前类的对象,因为构造器的返回值是隐式的。

构造器的返回值,是一个在堆内存中的对象实例的地址?


如果访问权限允许,类里定义的方法和Field都可以通过类或实例来调用。

类或实例访问方法或Field的语法是:类.Field|方法,或者实例.Field|方法,

在这种方式中,类或实例是主调者,用于访问该类或该实例的指定Field或方法。

static修饰的方法和Field,既可通过类来调用,也可通过实例来调用;

没有使用static修饰的普通方法和Field,只可通过实例来调用。

书中多次强调静态的成员变量和方法不要用实例作为主调,尽量使用类名.Field|方法

这种方式调用


当一个对象被创建成功以后,这个对象将保存在堆内存中,Java程序不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象


不管是数组还是对象,当程序访问引用变量的Field或方法时,实际上是访问该引用变量所引用的数组、对象的Field或方法。


如果堆内存里的对象没有任何变量指向该对象,那么程序将无法再访问该对象,这个对象也就变成了垃圾,Java的垃圾回收机制将回收该对象,释放该对象所占的内存区。

这也是使强引用被垃圾回收的途径


this可以代表任何对象,当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的对象只能是当前类;只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this就代表谁。


Java允许对象的一个成员直接调用另一个成员,可以省略this前缀。也就是说,将上面的run方法改为如下形式也完全正确。

也就是说,在同类中的field和方法,可以直接调用,无需使用this前缀


static修饰的方法中不能使用this引用。由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员,因此Java语法规定:静态成员不能直接访问非静态成员。

由static修饰的方法和field是属于类而不是实例的,所以可通过类名.属性/方法名直接调用,

this是代表对象的,当this出现在方法体中,他所代表的对象不确定,但是类型确定就是当前类,当这个方法被调用时this代表的对象才确定,

由此可得this是面向对象的,

所以在static修饰的方法中使用this是没有意义,因为this不能指向合适的对象(一个是类,一个是类的实例-对象),当static方法中不能使用this调用同类非static方法时,得出结论,静态(static)方法不能访问非静态方法

如果确实需要在静态方法中调用普通方法,那么可以通过new一个对象来调用


来看一个使用上面知识的小例子:


当我们做一个较小的类时,一个类中同时包含了一些业务方法和main函数,

根据我们的经验,需要new一个当前类对象来调用,和main处在同类下的方法,

但根据我们上面学到的知识,同类下的方法可以直接调用,那为什么还要new对象,直接调用方法不就得了

原因在于main方法使用static修饰,静态方法内调用普通方法,需要new一个对象,这和我们的经验,以及上面的知识点吻合


一般来说,如果调用static修饰的成员(包括方法、Field)时省略了前面的主调,那么默认使用该类作为主调;如果调用没有static修饰的成员(包括方法、Field)时省略了前面的主调,那么默认使用this作为主调。


Java有一个让人极易“混淆”的语法,它允许使用对象来调用static修饰的Field、方法,但实际上这是不应该的。

可以用对象来调用static的方法和field,但不推荐这样做,直接用类名去调用更好

2022.08.02更新

 

4.2方法详解

同一个类的一个方法调用另外一个方法时,如果被调方法是普通方法,则默认使用this作为调用者;如果被调方法是静态方法,则默认使用类作为调用者。也就是说,表面上看起来某些方法可以被独立执行,但实际上还是使用this或者类来作为调用者。

Java里的方法不能独立存在,它必须属于一个类或一个对象,因此方法也不能像c语言的函数那样被独立执行,执行方法时必须使用对象来作为调用者


Java语言里方法的所属性主要体现在如下几个方面。

  • 方法不能独立定义,方法只能在类体里定义。
  • 从逻辑意义上来看,方法要么属于该类本身,要么属于该类的一个对象。
  •  永远不能独立执行方法,执行方法必须使用类或对象作为调用者。

Java里方法的参数传递方式只有一种:值传递。所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。


Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(...),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。


//以可变个数形参来定义方法
public static void test(int a , String... books);
//下面采用数组形参来定义方法
public static void test(int a , String[] books);

这两种形式都包含了一个名为books的形参,在两个方法的方法体内都可以把books当成数组处理。

虽然功能相同,但是可变形参传入参数时更加简洁,可直接写为一个数组赋值形式


数组形式的形参可以处于形参列表的任意位置,但个数可变的形参只能处于形参列表的最后。也就是说,一个方法中最多只能有一个长度可变的形参。

4.2.4递归方法

一个方法体内调用它自身,被称为方法递归。方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。


例如有如下数学题。已知有一个数列:f(0)=1,f(1)=4,f(n+2)=2*f(n+1)+f(n),其中n是大于0的整数,求f(10)的值。

……

如果把上面数学题改为如此。已知有一个数列:f(20) = 1,f(21)=4,f(n +2) = 2 *f(n+1)+f(n),其中n是大于0的整数,求f(10)的值。

已知条件小于需求值,f(n+2)=2*f(n+1)+f(n)

已知条件大于需求值,f(n)=f(n+2)-2*f(n+1)

对于不同的初始条件,对递归公式移项改变


如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。

4.3成员变量和局部变量

一个类在使用之前要经过类加载、类验证、类准备、类解析、类初始化等几个阶段


形参的作用域是整个方法体内有效,而且形参也无须显式初始化,形参的初始化在调用该方法时由系统完成,形参的值由方法的调用者负责指定。


在同一个类里,成员变量的作用范围是整个类内有效,一个类里不能定义两个同名的成员变量,即使一个是类Field,一个是实例Field也不行;

一个方法里不能定义两个同名的局部变量,即使一个是方法局部变量,一个是代码块局部变量或者形参也不行。

Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例Field)或类名(对于类Field)作为调用者来限定访问成员变量。

代码块就是使用{}(花括号)包起来的一段代码


笔者建议读者,当程序需要访问类Field时,尽量使用类作为主调,而不要使用对象作为主调,这样可以避免程序产生歧义,提高程序的可读性。

由static修饰的field称为类成员变量,作者多次强调调用这种类型的变量/方法时用类名作为主调而不是实例


局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。


栈内存中的变量无须系统垃圾回收,往往随方法或代码块的运行结束而结束。


即使在程序中使用局部变量,也应该尽可能地缩小局部变量的作用范围,局部变量的作用范围越小,它在内存里停留的时间就越短,程序运行性能就越好。

4.4隐藏和封装

封装(Encapsulation)是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。

在mybatis中做dao层时,就是对类的封装


对一个类或对象实现良好的封装,可以实现以下目的。

 隐藏类的实现细节。

 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对Field的不合理访问。

可进行数据检查,从而有利于保证对象信息的完整性。

便于修改,提高代码的可维护性。

封装的好处,

1隐藏细节,2逻辑控制,限制非法访问,3数据控制,4提高代码维护性

面向对象有三大特点,封装,继承,多态

那么其实封装和继承是相互矛盾的,因为继承会破坏一个类的封装,


图中4个访问控制级别中的default并没有对应的访问控制符,当不使用任何访问控制符来修饰类或类成员时,系统默认使用该访问控制级别。


访问控制级别表


外部类只能有两种访问控制级别:public和默认,


如果一个Java源文件里定义的所有类都没有使用public修饰,则这个Java源文件的文件名可以是一切合法的文件名;但如果一个Java源文件里定义了一个public修饰的类,则这个源文件的文件名必须与public修饰的类的类名相同。


如果一个Java类的每个Field都被使用private修饰,并为每个Field都提供了public修饰setter和getter方法,那么这个类就是一个符合JavaBean规范的类

2022.08.02更新

 

4.4.3 package、import和import static 

Java引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。


位于包中的类,在文件系统中也必须有与包名层次相同的目录结构。


Java的包机制需要两个方面保证:① 源文件里使用package语句指定包名;② class文件必须放在对应的路径下。


在实际企业开发中,我们还会在org.crazyit包下以项目名建立子包;如果该项目足够大,则还会在项目名子包下以模块名来建立模块子包;如果该模块下还包括多种类型的组件,则还会建立对应的子包。假设有一个eLearning系统,对于该系统下学生模块的DAO组件,则通常会放在org.crazyit.elearning.student.dao包下,其中elearning是项目名,student是模块名,dao用于组织一类组件。

所有的一切操作都是有原因的,项目中这样放文件是为了保证清晰的模块结构,提供足够的命名空间以保证相同类名的命名空间不冲突


如果父包中的类需要使用子包中的类,则必须使用子包的全名,而不能省略父包部分。


如果需要使用不同包中的其他类时,总是需要使用该类的全名,这是一件很烦琐的事情。为了简化编程,Java引入了import关键字,import可以向某个Java文件中导入指定包层次下某个类或全部类,import语句应该出现在package语句(如果有的话)之后、类定义之前。


Java默认为所有源文件导入java.lang包下的所有类,因此前面在Java程序中使用String、System类时都无须使用import语句来导入这些类


在一些极端的情况下,import语句也帮不了我们,我们只能在源文件中使用类全名。

导入的两个包有相同的类名,这种情况下创建对象只能用包级关系.类名


使用import可以省略写包名;而使用import static则可以连类名都省略。

在java1.5之后引入的静态导入,这里说明省略是在程序中调用时的省略,而不是import中的省略

具体说明一下这个点

在jdk1.5时引入了一个新的引入包关键字  import static (只能导入静态成员) 
使用时区别于普通的import    包名.类名.*/静态成员名(方法或变量)
如果使用这种方式导入,则在程序中连类名都可省略,直接只用方法名调用
 ArrayUtil.printArray(a);          改为   printArray(a)

当然,这种方式也是有缺点的,

1.可读性下降

2.如果导入的两个类中有重名的静态成员,编译器会报错                                                        

4.5深入构造器

构造器是一个特殊的方法,这个特殊方法用于创建实例时执行初始化。构造器是创建对象的重要途径(即使使用工厂模式、反射等方式创建对象,其实质依然是依赖于构造器),因此,Java类必须包含一个或一个以上的构造器


学生提问:构造器是创建Java对象的途径,是不是说构造器完全负责创建Java对象?

答:不是!构造器是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也确实返回了该类的对象,但这个对象并不是完全由构造器负责创建的。实际上,当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象已经产生了——这些操作在构造器执行之前就都完成了。也就是说,当系统开始执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用类型的变量,从而让外部程序可以访问该对象。


使用this调用另一个重载的构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句

4.6 类的继承

Java的继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类被称为父类,有的也称其为基类、超类。


Java的子类不能获得父类的构造器。


Java类只能有一个直接父类,实际上,Java类可以有无限多个间接父类。


如果定义一个Java类时并未显式指定这个类的直接父类,则这个类默认扩展java.lang.Object类。因此,java.lang.Object类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的Java对象都可调用java.lang.Object类所定义的实例方法。


这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。

可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。

方法的重写要遵循“两同两小一大”规则,

“两同”即方法名相同、形参列表相同;

“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,

子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;

“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。


如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。

如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。

如果子类中定义了一个与父类private方法具有相同的方法名、相同的形参列表、相同的返回值类型的方法,依然不是重写,只是在子类中重新定义了一个新方法。


方法重载和方法重写在英语中分别是overload和override,经常看到有些初学者或一些低水平的公司喜欢询问重载和重写的区别?

其实把重载和重写放在一起比较本身没有太大的意义,

因为重载主要发生在同一个类的多个同名方法之间,

而重写发生在子类和父类的同名方法之间。

它们之间的联系很少,除了二者都是发生在方法之间,并要求方法名相同之外,没有太大的相似之处。

当然,父类方法和子类方法之间也可能发生重载,因为子类会获得父类方法,如果子类定义了一个与父类方法有相同的方法名,但参数列表不同的方法,就会形成父类方法和子类方法的重载。


如果子类里没有包含和父类同名的Field,那么在子类实例方法中访问该Field时,则无须显式使用super或父类名作为调用者。

如果在某个方法中访问名为a的Field,但没有显式指定调用者,则系统查找a的顺序为:

(1)查找该方法中是否有名为a的局部变量;

(2)查找当前类中是否包含名为a的Field;

(3)查找a的直接父类中是否包含名为a的Field,依次上溯a的所有父类,直到java.lang.Object类,如果最终不能找到名为a的Field,则系统出现编译错误。


当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,

即使子类定义了与父类中同名的实例变量。

也就是说,当系统创建一个Java对象时,如果该Java类有两个父类(一个直接父类A,一个间接父类B),

假设A类中定义了2个实例变量,B类中定义了3个实例变量,当前类中定义了2个实例变量,那么这个Java对象将会保存2+3+2个实例变量。

如果在子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量。

注意不是完全覆盖,因此系统在创建子类对象时,依然会为父类中定义的、被隐藏的变量分配内存空间。

2022.08.02更新

 

 

 

4.6.4调用父类构造器

在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用来完成。


使用super调用父类构造器也必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现。

4.7多态

Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。


因为子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型(upcasting),向上转型由系统自动完成。


相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。

总结一下。当引用变量和实例对象的类型不一致时,会出现多态如test p= new test1();

这时p调用父类没被重写的方法时,执行父类定义的方法内容(被继承了),

调用父类被子类重写的方法时,执行子类的方法内容,

另外,p引用变量不能调用子类独有的方法,这个就是多态,

另外成员变量没有多态,无论怎么调用成员变量,都表现为父类定义时的状态

2022.08.02  晚上七点,佩罗西马上访台,会爆发战争吗

我做了一张新的思维导图,更加清晰的说明多态

4.7.2 引用变量的强制类型转换

引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,

即使它实际所引用的对象确实包含该方法。

如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助于类型转换运算符。


引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。


考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换


instanceof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),

它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。

如果是,则返回true,否则返回false。

在使用instanceof运算符时需要注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。


instanceof运算符的作用是:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码更加健壮。

4.8继承与组合

为了保证父类有良好的封装性,不会被子类随意改变,设计父类通常应该遵循如下规则。

尽量隐藏父类的内部数据。尽量把父类的所有Field都设置成private访问类型,不要让子类直接访问父类的Field。

不要让子类可以随意访问、修改父类的方法。父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法;

如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符(该修饰符后面会有更详细的介绍)来修饰该方法;

如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以使用protected来修饰该方法。

 尽量不要在父类构造器中调用将要被子类重写的方法。


如果想把某些类设置成最终类,即不能被当成父类,则可以使用final修饰这个类,

例如JDK提供的java.lang.String类和java.lang.System类。

除此之外,使用private修饰这个类的所有构造器,从而保证子类无法调用该类的构造器,也就无法继承该类。

对于把所有的构造器都使用private修饰的父类而言,可另外提供一个静态方法,用于创建该类的实例。


继承是对已有的类做一番改造,以此获得一个特殊的版本。简而言之,就是将一个较为抽象的类改造成能适用于某些特定需求的类。

反之,如果两个类之间有明确的整体、部分的关系,此时就应该采用组合关系来实现复用,

继承要表达的是一种“是(is-a)”的关系,而组合表达的是“有(has-a)”的关系。

4.9 初始化块

初始化块是Java类里可出现的第4种成员(前面依次有Field、方法和构造器),

一个类里可以有多个初始化块,相同类型的初始化块之间有顺序:

前面定义的初始化块先执行,后面定义的初始化块后执行。

初始化块的语法格式如下:

[修饰符]{

//初始化块的可执行代码

……

}

初始化块的修饰符只能是static,使用static修饰的初始化块被称为静态初始化块。

初始化块里的代码可以包含任何可执行性语句,包括定义局部变量、调用其他对象的方法,以及使用分支、循环语句等。


初始化块只在创建Java对象时隐式执行,而且在执行构造器之前执行。


当Java创建一个对象时,系统先为该对象的所有实例Field分配内存(前提是该类已经被加载过了),接着程序开始对这些实例变量执行初始化,其初始化顺序是:先执行初始化块或声明Field时指定的初始值,再执行构造器里指定的初始值。


创建一个Java对象时,不仅会执行该类的普通初始化块和构造器,而且系统会一直上溯到java.lang.Object类,

先执行java.lang.Object类的初始化块,开始执行java.lang.Object的构造器,依次向下执行其父类的初始化块,开始执行其父类的构造器……最后才执行该类的初始化块和构造器,


静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此静态初始化块总是比普通初始化块先执行。


静态初始化块不能访问非静态成员,包括不能访问实例Field和实例方法。


静态初始化块和声明静态Field时所指定的初始值都是该类的初始化代码,它们的执行顺序与源程序中的排列顺序相同。

初始化块的执行顺序,首先从上到下执行父类到子类的所有静态初始化块,再从上到下执行每个类的初始化块和构造方法,一个类中的静态成员变量和静态初始块的执行顺序则是按顺序执行

后言:路漫漫其修远兮,吾将上下而求索

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值