单片机与物联网技术应用实战教程
上QQ阅读APP看书,第一时间看更新

6.2 Java面向对象

6.2.1 类

1.定义类

在客观世界中,人们总是把某些具有相同特征和行为的事物归为一类,面向对象程序设计中的“类”也与此相似。Java是面向对象的程序设计语言,类是面向对象的重要内容,类创建出对象。类是面向对象程序设计的核心概念之一,一个用户自定义的类就是一个新的数据类型。我们可以将类认为是一种自定义的数据类型,可以使用类来定义变量,所有使用类定义的变量都是引用变量。也就是说所有类是引用数据类型。

978-7-111-59182-5-Part01-266.jpg

图6-1 类与对象关系

面向对象的程序设计过程中有两个重要概念,一个是类(class),一个是对象(ob-ject,也被称为实例)。其中类是某一批对象的抽象,它是概念性质的存在,而对象才是一个具体存在的实体。类是相同或相似的各类事物间共同特性的一种抽象。即类是数据和对数据进行操作方法的集合体。在设计类时,应该抓住类是对象抽象的要点,抽取对象或实例的属性和行为,要看所设计类的用途。类和对象的关系如图6-1所示。

Java语言提供了对创建类和创建对象简单的语法支持。

定义类的简单语法如下:

978-7-111-59182-5-Part01-267.jpg

在上面的语法格式中,修饰符可以是public、final,或者完全省略这两个修饰符。按照规定,Java语言中的所有类名都以大写字母开头,并且类名中包含的每个单词的首字母都大写(如Sample Class Name)。Java类名是一种标识符,即由字母、数字、下划线(_)和美元符(978-7-111-59182-5-Part01-268.jpg)组成的字符串。不能以数字开头,中间不能有空格。例如,World1_value,978-7-111-59182-5-Part01-269.jpgvalue和Come2,都是有效的标识符;而4come和inputcome都不是有效地标识符。在Java中大写字母和小写字母是不同的,例如a1和A1是两个不同的标识符。

对一个定义类来说,包含的最常见的部分是构造器、属性、方法三个部分,都可以定义零个或多个。如果三部分都定义了零个,则定义了一个空类型,这三个部分合起来就是类体。Java规定每个类声明的体都必须以左花括号({)开头,并以一个相应的右花括号(})结束类的声明。其中类体中的构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建实例。因此,为避免出现构造器丢失的情况,Java语言提供了一种特殊的功能:如果程序员没有为一个类编写构造器,系统会自动为该类提供一个默认的构造器。一旦用户为类编辑了构造器,则系统将不会为该类提供一个构造器。

2.定义属性

正如现实生活中每个对象都有其特殊的属性(如大小、名字、颜色),在Java面向对象程序设计中,每个对象也有相应的特性和特征,称其为属性。属性用于定义该对象的类或者该类的实例所包含的数据。

定义属性的语法格式如下:

[修饰符]属性类型属性名[=默认值]

属性语法格式的详细说明如下:

1)修饰符:修饰符可以省略,也可以是public、protected、private、static、final,其中pub-lic、protected、private三个最多只能出现其中之一,可以与static、final组合起来修饰属性。

2)属性类型:属性类型可以是Java语言允许的任何数据类型,包括基本类型和引用类型。

3)属性名:属性名只要是一个合法的标识符即可,但如果从程序的可读性角度来看,属性名应该由一个或多个有意义的单词连缀而成。

4)默认值:定义属性还可以定义一个可选的默认值。

3.定义方法

现实生活中,具体事物都有行为,例如球可以滚动、弹跃、膨胀、收缩,婴儿会哭、睡觉、爬行、走路,汽车可以刹车、加速、减速、改变档位。这些行为抽象起来都是对象的动作。而方法就是描述对象的动作,即表示客观事物的动态特性(对数据的操作),描述这个对象“做什么”。方法用于定义该类或该类的实例行为特征或功能实现。

在Java中,必须通过方法才能完成对类和对象属性的操作。方法只能在类的内部声明并加以实现。一般在类体中声明属性之后再声明方法。

定义方法的语法格式如下:

978-7-111-59182-5-Part01-270.jpg

方法语法格式的详细说明如下:

1)修饰符。方法的修饰符可以分为存取权限修饰词、方法存在性修饰词、方法操作相关修饰词。存取权限修饰词用来控制此方法对于其他类的可存取关系,方法存在性修饰词是方法的声明与存在方面所具备的特性,方法操作相关修饰词是方法本身操作方面的特性,以及方法之间操作的相关性。修饰词可以省略,也可以是public、protected、private、static、final、abstract,其中public、protected、private三个最多只能出现其中之一,abstract和final最多只能出现其中之一,但是它们可以与static组合起来修饰方法。

2)方法返回值类型。方法执行后可能会返回某些执行的结果,而这些执行结果可以让调用这个方法的程序利用。方法返回值类型可以是一般的原始数据类型,或者是某个对象类。如果声明了方法返回值类型,则方法体内必须有一个有效地return语句,该语句返回一个变量或者一个表达式,这个变量或表达式必须与此处声明的类型匹配。例如,对于返回值类型声明为int的方法,必须在方法主体内加入“return整数变量或值”来返回方法执行的结果。除此之外,如果一个方法没有返回值,必须使用void来声明没有返回值,表示返回值类型为空。

3)方法名称。方法名称的命名规则与属性命名规则基本相同,同样尽量使用有意义的单词,通常建议方法名称以英文中的动词开头。

4)形式参数。形式参数用于定义该方法可以接受的参数,形式参数由一到多组“参数类型形参名”组成,多个参数之间以英文逗号(,)隔开,形参类型和形参名之间用空格隔开。形式参数的格式为:

<参数类型1><参数名1>,<参数类型2><参数名2>,…

5)方法主体。方法主体里多条可执行性语句之间有严格的执行顺序,排在方法主体前面的语句先执行,排在方法主体后面的语句后执行。

6.2.2 对象

很多编程爱好者,包括有过很多年编程经验的人,对于对象的概念都是很模糊的,如果将对象的概念与现实生活中的实物相比,就会发现对象其实是很好理解的。

对象就是实际生活中的事物,可以说一切事物都是对象,在现实生活中时时刻刻都接触到对象这个概念,例如桌子、椅子、电脑、电视机、空调等。这些实物都可以说是对象。

抽象来讲,对象是系统中用来描述客观事物的一个实体,是构成系统的基本单位。一个对象由一组属性和对属性进行操作的一组方法组成。从更抽象的角度来看,对象是问题域或现实中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用,它是一组属性和有权对这些属性进行操作的一组方法的封装体。客观世界是由对象和对象之间的联系组成的。

1.对象创建

用简单数据类型来说,有了int类型还不行,程序中能用的是int类型的变量,并且必须给变量赋值后才具有意义。同样,定义了类只是定义了数据类型,要想使用,还必须用该类型声明相应的变量,并给变量赋一个具体的值,这一过程称为对象创建。

创建对象的语法格式如下:

类名对象名=new类名([参数列表]);

例如:String s1=new String(“hello”);

在这个例子中先给对象命名为s1,并且声明s1是属于字符串类型的对象,最后把“hello”这个字符串类型对象的内存地址赋给这个对象,并且初始化。以后要操纵这个对象,只要操纵这个s1对象就可以了。

2.对象引用

通过对象创建之后,如何引用对象呢?比如通过Student类创建xiaofang这个对象,那么如何访问xiaofang的name、sex和number呢?这个过程称为对象引用。对象引用就是通过访问对象变量或调用对象方法。在Java中,运用运算符“.”可以实现对象变量或调用对象方法。

引用对象的语法格式如下:

引用对象的属性:对象.属性

调用对象的方法:对象.成员方法([参数])

比如xiaofang.number表示访问xiaofang的属性number。

下面来看一个对象引用的实例

978-7-111-59182-5-Part01-271.jpg

978-7-111-59182-5-Part01-272.jpg

在这段Java面对对象程序设计中,学习者一定要学会一点,首先看主程序,也就是“public staticvoidmain(Stringargs[]){}”。在这个主程序中看到了两句代码,一句是对象初始化语句,一句是利用对象引用对象方法的语句。这样学习起来比较方便,很快就能看出这个程序要干什么,再去细看方法究竟是干什么的。

3.对象比较和销毁

在Java语言中,对象比较主要运用“==”运算符和equal()函数进行比较。用“==”运算符比较对象时,只要两个对象相等即返回true,不同返回false。

不过这两个符号其实现的机制不同。或者说,对于两个相同的对象,如果利用它们来进行比较,往往会有不同的结果。例如,分别定义了3个String对象

Stringa=new String("welcome");//创建一个对象a

Stringb=new String("welcome");//创建一个对象b

Stringc=a;//创建一个对象,并将对象a地址赋值给c

以上3个对象,内容是一样的。但是如果利用“==”和equal函数来比较,往往会有不同的结果。

当用运算符“==”时,a==b,返回结果是false,说明他们是两个不同的对象;当用equal函数时,返回值是true。

其实不难理解,对象a和对象b两个对象虽然内容相同,但是其在内存中分配的地址不同,也就是同一个模具出来的外观看起来相同的不一样的盒子。而对象a和对象c虽然对象名称不同,但是在内存中的地址却是相同的。所以利用运算符“==”返回值为false,而用equal函数时,返回值是true。运算符“==”是用来比较内存中的地址是否相同,而equal只比较其内容,即使地址不同,但内容相同,equal返回值就为true。

Java堆是一个运行时数据区,对象从中分配空间。Java虚拟机(JVM)的堆中存储着正在运行的应用程序所建立的所有对象,这些对象通过new或newarray等指令建立,但是它们不需要程序代码来显式释放,而是由垃圾回收器负责释放的。

垃圾回收器是Java平台中用得最频繁的对象销毁方法。垃圾回收器会全程侦测Java应用程序的运行情况。一旦发现有些对象成为垃圾时,垃圾回收器就会销毁这些情况,并释放这些对象所占用的内存空间。通常情况下,如果程序发现以下两种情况时,系统会以为这些对象是需要被销毁的垃圾对象。①将一个NULL值赋值给对象。如用户先建立了一个对象a。对象用完了之后,再利用赋值语句,将NULL值赋值给这个对象a。此时这个对象与内存中对象的存储地址之间就失去了联系,此时内存中的这个对象就似乎成为了一个无主的对象,就会被垃圾回收器销毁。②对象超出了作用范围时,就会被认为是垃圾对象,被垃圾回收器回收并释放内存。

6.2.3 方法

1.方法的所属性

会遇到相同功能的代码写了很多次的情况,以后程序中再使用需要再重复编写。万一遗漏或者修改错误一处,则程序将会无法运行。那么代码能不能只写一遍,而在多处使用呢?如果可以,那么修改代码时只需修改一处即可,代码的可维护性会大大提高,这就用到了方法。需要说明的是Java中的方法必须定义在类中。

在Java中,最基本的方法的定义格式如下:

978-7-111-59182-5-Part01-273.jpg

定义方法后,调用方法时使用“对象名.方法名()”。例如:

978-7-111-59182-5-Part01-274.jpg

在实际应用中方法还可以带参数,即在实际的操作过程中还可以给方法传递一些参数,让其根据参数的不同完成不同的工作。带参数的方法基本格式如下:

978-7-111-59182-5-Part01-275.jpg

978-7-111-59182-5-Part01-276.jpg

2.传递方法参数

在方法的调用过程中,需要将实际参数传递到方法中,在Java中实参和形参之间的传递是如何进行的呢?在Java中参数的传递采用的是“值”传递的方法,但是值传递也分为两种方式。

方式一:基本数据类型:数值传递

基本数据类型的参数传递的是变量的值。例如:

978-7-111-59182-5-Part01-277.jpg

分析程序:执行语句“b.black(a);”时,将a变量的值10复制一份给了black中的形参a,然后执行a+1的操作,black()方法调用结束,形参a的作用域结束,并不会对main中的a产生影响,所以结果a还是最初的值10。

方式二:引用数据类型:地址传递

引用数据类型的参数传递的是变量所引用对象的首地址。例如:

978-7-111-59182-5-Part01-278.jpg

978-7-111-59182-5-Part01-279.jpg

运行程序,运行结果为

Mike'sageis:21

分析程序:在执行“test.changeAge(Mike);”语句时,将Mike变量所引用的地址复制一份给形参people,Mike所引用的值是内存People对象的内存首地址。这时changeAge()方法中的people变量引用的对象是内存中People对象,修改People对象的age变量,但是main中的Mike变量没有变化,但Mike变量所引用的People对象的age已经改变。所以结果为21。

3.构造方法

对象的初始化工作是非常重要的,为防止未对对象进行初始化就直接调用对象的操作,只需要将对象初始化工作的代码写在构造方法中即可。在Java中,通过new创建一个类的实例,通过调用构造方法执行初始化操作。

构造方法的语法格式为

Fruitc=new Fruit();

构造方法的特点只要体现在:

1)无返回值,无void。

2)方法名与类名相同。

3)仅在创建对象new时调用。

例如:

978-7-111-59182-5-Part01-280.jpg

978-7-111-59182-5-Part01-281.jpg

程序运行结果为

978-7-111-59182-5-Part01-282.jpg

注意:

1)当一个类的对象在创建时,构造方法会被自动调用,可以在构造方法中加入初始化代码。

2)在对象的生命周期中构造方法只会调用一次。

3)一个类中如果没有定义构造方法,Java编译器会自动为该类生成一个默认的构造方法。默认的构造方法的参数列表即方法体均为空。因此,在实例化没有定义构造方法的类的对象时可以写成。

类名对象名=new类名();

4)只要类中有显示声明的构造方法,Java编译就不产生默认的构造方法。

5)在一个类中可以定义多个构造方法,但构造方法的参数列表不能相同。

6.2.4 继承

1.父类和子类

面向对象思想的第二大特征就是继承。继承就是实现类的重用、软件复用的重要手段。子类通过继承自动拥有父类的非私有的属性和方法,即继承父类的特征和能力。通俗来讲,“龙生龙、凤生凤、老鼠生儿会打洞”就是继承。子类不必重复书写父类中的属性和方法,而只需对父类已有的属性和方法进行修改或扩充,以满足子类更特殊的需求。

继承是通过extends关键字实现的,继承的基本语法:

978-7-111-59182-5-Part01-283.jpg

通过继承子类自动拥有父类的允许访问的所有成员(public,protected,默认访问权限)。但需注意的是,Java只需单继承,即一个子类只能有一个父类。final修饰的类不能被继承,表示最终类。类的继承具有传递性。即A继承B,B继承C,则A也继承了C。C也是A的父类。下面是一个继承示例。

978-7-111-59182-5-Part01-284.jpg

2.调用父类的构造方法

实例化子类对象时,会先调用父类的构造方法。调用格式为super(参数列表)。如果子类的构造方法没有显示调用父类的构造方法,则编译器会自动加上super()。此时若父类中没有无参数的构造方法,则编译器会报错。

用super语句调用父类的构造方法时,必须遵循以下语法规则。

(1)在子类的构造方法中,不能直接通过父类方法名调用父类的构造方法,而是要使用super语句。

(2)假如在子类的构造方法中super语句,它必须作为构造方法的第一条语句。

例如:

978-7-111-59182-5-Part01-285.jpg

需要注意的是同一个构造方法中不能同时使用this()和super()。

3.访问父类的属性方法

当super用于访问父类的属性方法时,使用的语法格式如下:

super.属性

super.方法()

例如,可以在子类中通过下面的方式来调用父类中的方法:

super.getname()

注意:父类的属性或方法必须是那些protected(受保护)或者public(公共)等可以让子类访问的属性方法。

4.多重次继承

在Java中多重次继承指的是一个类可以继承另外一个类,而另外一个类又可以继承其他的类,比如A类继承B类,B类又继承C类,这就是Java中的多重次继承。

需要注意的是,Java中有多重次继承,但却没有多继承的概念,一个类有且仅有一个父类,这是Java单继承的局限性。Java中通过实现接口来达到多继承的功能。一个类只能继承一个类,但是却可以实现多个接口。常常使用继承单个类和集成多个接口的方式实现类的多重次继承。以下是多重次继承的示例。

978-7-111-59182-5-Part01-286.jpg

前面说到子类只能继承一个父类,也就是说单一继承,但是在Java中可以实现多个接口,曲折地实现多重次继承。

6.2.5 多态

1.多态的基本概念

多态是面向对象的重要概念之一,简单地讲,多态是指一个事物在不同情况下呈现出不同的形态。

多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用,在编程时并不确定,而是在程序运行时才确定。

继承为多态做了铺垫,从同一个父类派生的多个不同子类可以被当成父类对待,可对这些不同的类型进行相同的处理,由于多态性,子类对象响应同一方法的行为是不同的。把不同的子类对象都当成父类来看,可以屏蔽不同子类对象之间的差异,写出通俗的编程,以适应需求的不断变化。

在Java中,多态性主要体现在两个方面:由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(运行时多态)。

1)编译时多态。在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同静态确定调用相应的方法。

2)运行时多态。如果子类重写了父类的方法,此时方法调用的原则是系统根据调用该方法的实例,来决定调用哪个方法。如果子类重写了父类的方法,则运行时Java调用子类的方法。如果子类继承了父类的方法,则Java调用父类的方法。

2.多态的使用

多态的使用分为向上转型和向下转型。

1)向上转型 子类对象既能作为自身类型使用,又可以作为其父类型使用,这种把某个对象视为其父类型的做法就是向上转型。下面是一个向上转型的程序。

978-7-111-59182-5-Part01-287.jpg

向上转型,子类Man可以作为父类People类型使用。即子类转换为父类。

2)向下转型。把父类的引用向下转换为子类型引用,成为向下转型。在向下转型时,必须强制进行。例如:

Manager manager=newEmployee();

Employee employee=(Employee)manager;//父类强制类型转换为子类型

注意:多态的条件一是必须有继承,二是方法的重载。转型是在继承的基础上而言的,继承是面向对象语言中,代码重复的一种机制,通过继承,子类可以拥有父类的功能,如果父类不能满足当前子类的需求,子类可以重新父类的方法加以扩展。

6.2.6 修饰符

对于Java修饰符来讲大致可以分为三类:类修饰符、字段修饰符和方法修饰符。而根据功能的不同可以分为以下几种:

1)public(公共访问控制符),指定该变量为公共类型,它可以被任何对象的方法访问。

2)private(私有访问控制符),只能在当前类中访问,而不能被类外部的任何内容访问,一般修饰不开放给外部使用的内容。

3)protected(保护访问控制符),一般称作继承权限,使用protected修饰的内容可以被同一个包中的类访问,也可以在不同包内部的子类中访问,一般用于修饰只开放给子类的属性、方法和构造方法。

4)default(声明成员变量为默认类型),如果不给成员变量添加任何修饰符,表示这个成员变量被修饰为default类型。在同一个包里的类或子类是能够访问的,相当于public类型,但是在不同包里的类或者子类没有继承该成员变量,是访问不到它的。

权限访问修饰符见表6-5。

6-5 权限访问修饰符

978-7-111-59182-5-Part01-288.jpg

1.Final修饰符

Final的意思是不可改变的,决定性的。Final可以用以修饰类、字段、方法。但是修饰类后类不能被扩展(extends),也就是不能被子类继承。如果当这个类不需要拥有子类时候,类的实现细节不允许改变,并且确信这个类不会再被扩展,那么就设计为Final类。修饰字段后字段的值不能被改变,因此如果有Final修饰字段,就要对字段进行手动初始化。修饰方法后该方法不能被改变。如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为Final方法。使用Final方法的原因有二:第一,防止任何继承类用以修改其意义和实现将它的方法锁定。第二,编译器在遇到调用Final方法时候大大提高执行效率,使它具有高效性。例如:

1)编译无法通过的例子:

978-7-111-59182-5-Part01-289.jpg

978-7-111-59182-5-Part01-290.jpg

2)就可以正常编译的例子(去掉Final):

978-7-111-59182-5-Part01-291.jpg

2.Static修饰符

大家都知道,可以基于一个类创建多个该类的对象而每个对象都拥有自己的成员并相互独立,然而在某些时候,我们更希望该类的所有对象共享一个成员,此时需要用到Static来解决这一问题。

Static的意思是“全局”或者“静态”,它用来修饰内部类、方法、字段。修饰属于外部类而不属于外部类的某个实例的内部类。修饰字段和方法都是属于类而不属于类的实例字段和方法。当static被修饰符修饰时,有很多不同,例如被public修饰时其成员变量和成员方法本质是全局变量和全局方法。声明它类的对象时,是类的所有实例共享同一个static变量;当static变量前被private修饰,表示这个变量可以在类的静态代码块中或者类的其他静态成员方法中使用,但是不能在其他类中通过类名来直接引用。static前面加上其他访问权限关键字的效果也以此类推。

因为static代表静态的意思,所以它修饰的成员变量和成员方法习惯上称为静态变量和静态方法,当然也可以直接通过类名来访问,访问语法为:

类名.静态方法名(参数列表...)

类名.静态变量名

static的变量引用方法如下:

1)通过对象定位变量。假设已经定义好了一个static类,此时定义类中的static变量程序如下:

978-7-111-59182-5-Part01-292.jpg

978-7-111-59182-5-Part01-293.jpg

由上例我们可以很简洁地看出来:只是定义了一个类却创建了两个对象,而且两个对象只是占据一个存储空间,也同样是共享的,所以拥有“20”一个同样的值。

2)通过类名直接引用 直接通过类名也可以直接引用static变量,但只可以用于静态成员。首先是通过类名调用,一方面强调了变量的结构,另一方面也方便了编译器进行优化。例如,语句StaticTestu++引用static修饰符。

3.Abstract修饰符

abstract是抽象的意思,用来修饰类和方法。类为抽象类,这个类将不能生成对象实例,但可以作为对象变量声明的类型,也就是编译时类型,抽象类就相当于一个类的半成品,需要子类继承并覆盖其中的抽象方法;方法为抽象方法,也就是只有声明(定义)而没有实现。需要子类继承实现(覆盖),也就是说必须在其子类中实现,除非子类本身也是抽象类。

注意:有抽象方法的类一定是抽象类。但是抽象类中不一定都是抽象方法;abstract修饰符在修饰类时必须放在类名前。

Static、private、final和abstract都不能放在一起。因为static是可以覆盖的,但是在调用时会调用编译时类型的方法,因为调用的是父类的方法,而父类的方法又是抽象的方法,又不能够调用;private是不能够继承到子类,所以也就不能覆盖;final是不可以在它的子类中覆盖。所以修饰符Static、private、final和abstract之间是不能放在一起的。

例如:

978-7-111-59182-5-Part01-294.jpg

6.2.7 接口

在生活中经常会使用到接口:例如手机充电接口,计算机USB接口等。当使用USB接口,同一类型手机不用担心接口型号和计算机或者充电的接口是否匹配,直接可以使用。同样,对于学习Java接口也是如此,Java程序设计中的接口是在程序中预设一个虚拟的接口,这个接口可以和编写程序实现更好的衔接,它只会定义方法名却没有方法体,指明接口定义了一个类该做什么,却没有说如何去做。接口在Java中具有重要的意义,它可以理解为一种特殊的类(但不是类),里面全部都是由全局常量和公共的抽象方法组成,所以接口只包含常量和方法的定义,而没有实现变量和方法。因此,可以直接定义接口类型的参数方法,并把代码应用于实现接口的所有类中。

1.接口定义

在Java中,定义接口必须使用interface关键字,接口定义分为两个部分:接口声明和接口体。其中接口体有常量定义和方法定义两个部分组成。接口定义的语法格式:

978-7-111-59182-5-Part01-295.jpg

例如:

978-7-111-59182-5-Part01-296.jpg

在此段代码中只是定义了使用方法的名称,并没有真正地实现这个方法。而且可以看出修饰符可省略,但是省略则是使用默认的访问权限。

而修饰符的作用则是用于指定接口的访问权限,可以放置:public;abstract;static;strictfp。其中,public属于一个接口的内部类在默认情况下使用的,用于指定接口的访问权限;static属于一个接口的内部类在默认情况下使用的,用于指定接口的访问权限;abstract每个接口隐式的修饰abstract接口,所以不该在程序中使用;strictfp:strictfloatingpoint,定义为strictfp接口声明内的所有浮点运算都显式的进行严格的浮点运算。接口中声明的所有嵌套类型隐式的都是strict-fp。对于接口名,必须选定参数,用于指定接口名称,接口名必须是合法的Java标识符,并首字母大写。当使用到extends时父接口名为必选参数。

2.应用接口

通过类才可以使得接口实现一定的作用,使其执行一定的功能。以下给出类实现接口的语法。

978-7-111-59182-5-Part01-297.jpg

例如:

978-7-111-59182-5-Part01-298.jpg

接口中声明的变量会自动成为类变量,不需要加上static和final修饰词。以下两个步骤可以让一个类实现一个接口:

1)把类声明为实现给定的接口,用implement。

2)对接口中的所有方法进行定义。

其中,implement用于指定该类实现的是哪些接口。

其接口列表位必选参数。当接口列表存在多个接口名时,各个接口名之间用逗号分割。在该类中实现接口时,方法的名字,返回值类型,参数的个数及类型必须同接口中的完全一致,并实现该接口中的所有方法。如果没有实现接口声明的所有方法,就必须把该类声明为abstract,否则编译器会报错。

3.接口继承

接口和接口之间也有继承的关系,当两个接口实现继承时,需要使用的关键词为extends。

对于接口的继承和继承类两者之间从表面来看,区别就是接口的下层类要实现(覆盖)接口中提到的所有方法,而继承类则不用。但是,实际上接口是一种方法继承,而类的继承则是包括了字段的继承,但实现类继承的多继承困难也就在于此:没有办法多个上层类之间的字段冲突,也无法确定调用的是哪个上层类的方法,在这时需要接口继承作用。首先,接口只是包含方法的定义,却没有实现,这样不同接口之间的矛盾得以解决,而接口中间不包含常量使得冲突的字段很大程度降低。所以,接口就是一种简化的多继承,而类的继承只可以用以单继承。

6.2.8 抽象类

通俗地讲抽象类就是普通类和接口的结合,因为抽象类可以像普通类那样在类中实现方法,也可以像接口一样,只声明,不实现。抽象类不可以被实例化,也就是说不可以使用new关键字来创建对象。使用抽象类的好处在于,当有的方法在父类中不想实现时可以不实现。

1.抽象类方法

抽象类的对象不能由抽象类直接创建,只可以通过抽象类派生出新的子类,再由其子类创建对象。也就是说只需要给一个模板,可依据模板来创建一个新的对象。

当一个类被声明为抽象类时,要在这个类前面加上修饰符abstract。

抽象类方法包括一般方法和抽象方法。一般方法需要抽象类中的成员直接继承,实例化子类后,通过子类调用。抽象方法是以abstract修饰的方法,这种方法只声明返回的数据类型、方法名称和所需的参数,没有方法体,该方法只需要声明而不需要实现。当一个方法为抽象方法时,子类要实现父类的所有抽象方法,如果没实现抽象方法,其子类即为抽象类,即声明为abstract。

2.抽象类语法

定义抽象方法需要在方法的声明处使用关键字abstract。以下是一个抽象方法的基本格式:

Abstract<方法返回值类型>方法名(参数列表)

其中,方法返回值类型和方法名为必选参数,方法值返回类型则是用于指定方法的返回值,类型若无,则使用关键字void来标识,而方法名则只要是合法的Java标识符即可。

例如,使用abstract class方式定义M:

978-7-111-59182-5-Part01-299.jpg

3.抽象类作用

了解Java中的抽象类,抽象类在编程中有哪些作用?或者说为什么Java中会存在抽象类?

在面向对象方法中,抽象类主要用来进行类型例举。为创建一个用于固定组行为描述的抽象描述,但相对于其他所创建的这组抽象描述却可以具备多种可以实现形式。这个抽象描述就是抽象类,而具体实现形式则是派生类。对于所创建的模块,由于模块依赖于一个固定形式的抽象体,所以它是不允许修改的。为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。而且抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同但本质上相同的具体概念的抽象。

6.2.9 内部类

在所有类中有一种类是“依附”外部类而存在的内部类,即嵌套类(inner class)。内部类是定义类的一种方式。它可以被定义在另外一个类和接口的内部,或者作为其成员的一部分而存在。内部类可以是静态,也可以用protected和private修饰(外部类只可以用public和默认包访问权限)。类似的,一个接口可以被定义在另一个类和接口的内部,或者作为其成员的一部分而存在,称为内部接口或者嵌套接口。

嵌套类或者嵌套接口所在的类就称为外部类(outer class)或者顶级类(top—level class)。嵌套类和嵌套接口合称为嵌套类型。而嵌套类型则是外部类型的一部分。

对于内部类来说可以分为两种:成员内部类和局部内部类。

(1)成员内部类

成员内部类就如同它名字一样:作为外部类的一个成员存在,在与外部类的属性和方法并列。不可使用static做限定词。

注意:成员内部类中不能定义静态变量,但可以访问外部类的所有成员,而且内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。

例如:

978-7-111-59182-5-Part01-300.jpg

(2)局部内部类

局部内部类在方法中定义的内部类,与局部变量类似,在局部内部类前不加修饰符public或private,其范围为定义它的代码块。

注意:局部内部类中不可定义静态变量,可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的。在类外不可直接生成局部内部类(保证局部内部类对外是不可见的)。要想使用局部内部类时需要生成对象,在方法中才能调用其局部内部类。通过内部类和接口达到一个强制的弱耦合,用局部内部类来实现接口,并在方法中返回接口类型,使局部内部类不可见,屏蔽实现类的可见性。

例如

978-7-111-59182-5-Part01-301.jpg

978-7-111-59182-5-Part01-302.jpg

6.2.10 多线程的编程

1.多线程的定义

线程(Thread)是程序中单一的顺序控制流程。在单个程序的同时运行多个线程完成不同工作的称为多线程。

在详细介绍线程之前,先了解进程的概念。进程是具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。简单地说,进程是程序在计算机上的一次执行活动。当你启动一个程序时,你就启动了一个进程。而什么叫多进程呢?多进程就是在操作系统中能同时运行多个任务(程序)。简单地说,在同一个时间内,一个计算机系统可以运行多个进程。比如,你在计算机上打开qq音乐听歌,同时打开网页下载网上的电视剧,又同时网上qq聊天。这些任务(程序)看起来都是同步的,相互没有干扰,互相独立执行。那么对于一个CPU而言,在某一时间上,只能执行一个程序。CPU运行的速度实在是太快了。在多个程序进行轮流执行。人眼是观察不出来的。

线程,被称为轻量级进程,是操作系统能够进行运算调度的最小单元。它是进程中一个单一顺序的控制流。所以一个进程包含至少有一个线程。多线程是进程中包含多个线程,并且在同一时间内,同时完成多项任务。多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用的效率来提高系统的效率。

线程与进程的关系和区别:进程包含线程,一个线程可以有多个线程。进程是系统进行资源分配和调度的基本单元。线程是进程的一个实体,是CPU调度和分派的基本单元。线程比进程更小,基本上不拥有资源,所以对它的调度所付出的开销就会小得多。线程与进程的区别:子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程都有自己的执行堆栈和程序计数器为其执行上下文,多线程主要是为了节约CPU时间,根据具体情况而定,线程的运行中需要使用计算机的内存资源和CPU。

2.多线程的创建

在Java中创建线程有两种方式。一种是通过继承Thread方式来实现,另一种是通过实现Runnable接口创建线程。

1)继承Thread方式创建线程。是通过一个类来继承Thread,然后这个类来重写Thread中的run方法,最后通过start方法来启动线程。并且此时这个类就是线程类。例如下面程序代码是如何通过该方式来创建线程的。

978-7-111-59182-5-Part01-303.jpg

978-7-111-59182-5-Part01-304.jpg

2)实现Runnable接口创建线程。是通过一个类先实现Runnable接口,然后这个类中重写Runnable中的run方法。接着在main中创建这个实例对象,并把这个实例当作Thread构造器的参数创建一个Thread的实例对象,最后调用Thread类的start方法开启线程并调用Runnable接口子类的方法创建线程。例如下面程序代码是如何通过该方式来创建线程的。

978-7-111-59182-5-Part01-305.jpg

3.线程同步

先了解同步这个概念,可能会认为同步就是一起动作,其实不是,“同”的意思指协同、互相配合的意思。在Java多线程中,线程同步就是当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。比如,就好比两个人不能同时上同一个厕所,只有当一个人上好厕所时,另一个人才能进去。而在之前,这个人不得不在外面等待。所以Java多线程中就是需要用线程同步技术来解决,为的是避免多个线程对同一资源的访问。

前面讲了线程同步这个概念,是为了避免多个线程对同一资源的访问。可以想象,在Java中给共享资源加一把锁,这把锁只有一把钥匙,哪个线程获取了这把钥匙,才有权访问该资源,这个锁就加在共享资源上。

Java语言中的synchronized关键字给共享资源加锁。接下来简单地介绍synchronized的用法。

用法1:synchronized可以放在方法名的前面表示该方法同步。

例如:

978-7-111-59182-5-Part01-306.jpg

用法2:synchronized可以放在对象的前面表示访问该对象,只能有一个同步。

例如:

978-7-111-59182-5-Part01-307.jpg

用法3:synchronized可以放在类名的前面表示该类所有方法同步。

例如:

978-7-111-59182-5-Part01-308.jpg

978-7-111-59182-5-Part01-309.jpg

978-7-111-59182-5-Part01-310.jpg

用法4:对某一代码使用,synchronized后跟括号,括号里是变量,一次只有一个线程进入代码块。例如:

978-7-111-59182-5-Part01-311.jpg