当前位置:课程学习>>第四章 面向对象基础>>文本学习>>知识点二
一、类的定义
类是对象的抽象,它的定义包括两个部分:类头和类体,类头是对类的声明,类体则是包含类中成员和方法的声明。类定义的具体格式为:
[修饰符] class 类名 [extends 父类名] [implements 接口列表] //类头
{ //类体
成员变量的声明及初始化;
方法声明及方法体;
}
(1) [ ] 内为可选项(如无特别声明,以后的[ ]内的均为可选项)。
(2) 修饰符用来说明该类的所具有的性质,它包括了访问控制符(public或缺省为空)和类型说明符(abstract抽象类、final最终类)
(3) class为类定义的关键字;类名由用户自定,但必须符合Java中标识符的命名规则,一般最好能够体现出该类的功能或作用。
(4) extends 父类名:用来说明当前类是该父类的子类,实现类的继承。如果声明继承,则必须保证该父类是存在的,而且是可访问的。
(5) implements 接口列表:用来说明当前类实现了哪些接口,这是Java语言用来实现多重继承的一种特殊机制,并通过接口来增强类的功能。
【例4.1】类的定义:坐标点类。
1 //Point.java
2 public class Point
3 {
4 int x;
5 int y;
6 public void drawPoint()
7 {
8 System.out.println("x="+x+",y="+y);
9 }
10 }
二、成员
在类中成员变量是表示类的状态或属性的,一个类可以没有成员,也可以拥有多个成员。类中的成员既可以是基本数据类型的变量,也可以是数组或其他类的对象等复杂数据类型。它的定义格式为:
[修饰符] 基本数据类型 成员名 [=初始值]; //基本数据类型的成员
[修饰符] 类名 对象名 [=new 构造方法([实际参数列表])]; //复杂数据类型的成员
(1) 修饰符:用来修饰成员的修饰符比较多,它包括了访问控制符(public、private、protected和缺省为空)、static静态、final常量、volatile易失、transient暂时。这些修饰符一般都可以同时用来修饰成员,个别的例外。
(2) 若使用其他类的对象作为当前类的成员变量时,必须要求其他的类是存在且可访问的。
【例4.2】圆的定义。
1 //Circle.java
2 public class Circle
3 {
4 Point point=new Point();
5 int radius=0;
6 double length;
7 double area;
8 }
三、方法
1. 方法的定义
类中的方法是用来说明类所具有的行为或功能,一个类可以没有方法,也可以有多个方法。方法是由一段实现某些操作的代码构成。方法定义也包含两个部分:方法的声明和方法体,其语法结构为:
[修饰符] 返回值类型 方法名( [参数列表]) [ throws 异常列表]
{
局部变量的声明
语句序列
}
(1) 修饰符:用来修饰方法的修饰符比较多,它包括了访问控制符(public、private、protected和缺省为空)、static静态、final常量、abstract抽象、synchronized同步、native本地。方法的修饰符有些和成员的一样,但需要注意不相同的那些修饰符。
(2) 返回值:返回值可以是任意的Java数据类型,既包括基本数据类型,也包括类、数组、接口等引用类型。需要在方法体中使用return语句来返回相应类型的值,要求类型必须和声明的一样。若返回一个类的对象,可以是该类的子类对象。也可以没有任何返回值,则使用void来声明。
(3) 方法的参数:其类型可以是基本数据类型,也可以是引用类型。若是基本数据类型的参数,方法中的实参与形参之间是值传递,即在实际的方法调用时,实参传递给形参具体的值,而不是地址,这样在方法体中对形参的任何改变都不会影响到实参,形参和实参在内存空间中是两个独立的地址空间。若是引用类型,则是地址传递,实参传递给形参的是实参自己在内存空间中的地址,这样在方法体中对形参的改变都会影响到实参。
【例4.3】值传递和地址传递。
1 //ValueAndAddressTransfer.java
2 class Point
3 {
4 int x=10;
5 int y=20;
6 }
7 public class ValueAndAddressTransfer
8 {
9 int a=3;
10 int b=4;
11 Point point;
12 void valueTransfer(int x,int y)
13 {
14 x=a;
15 y=b;
16 }
17 void addressTransfer(Point p)
18 {
19 p.x=a;
20 p.y=b;
21 }
22 public static void main(String[] args)
23 {
24 int value1=5;
25 int value2=6;
26 ValueAndAddressTransfer test=new ValueAndAddressTransfer();
27 System.out.println("initialization:value1="+value1+",value2="+value2);
28 test.valueTransfer(value1,value2);
29 System.out.println("transfer:value1="+value1+",value2="+value2);
30 Point ptest=new Point();
31 System.out.println("initialization:ptest.x="+ptest.x +",ptest.y="+ptest.y);
32 test.addressTransfer(ptest);
33 System.out.println("transfer:ptest.x="+ptest.x +",ptest.y="+ptest.y);
34 }
35 }
其运行结果如图4.2所示。
图4.2值传递和地址传递演示程序的运行结果
从结果可以看出,int型的参数传递过去的是具体的值,因此即使在方法中对其进行了修改(第14行、第15行),但不会影响实参,所以输出结果依旧是原来的值。而对于引用类型的参数传递的是地址,因此在方法对其的修改(第19行、第20行),实际上是对该引用的地址中的值进行了修改,因此在实际输出时实参的值已经被修改掉了,变为了方法修改后的值。
(4) throws 异常列表:用以声明该方法中要抛出的异常列表(第5章异常处理的内容)。
2. 方法的重载(overloading)
方法的重载是实现多态性的一种典型方式。它是指同一个类中的多个方法具有相同的方法名,但具有不同的参数列表,即参数的数量不同或参数类型不同。每个重载方法的参数的类型或数量必须是不同的。虽然每个重载方法可以有不同的返回类型,但是不能通过方法的返回值来区分。重载的作用在于它允许相关的方法可以使用同一个名字来访问,而且每个方法都能够执行想要的任何动作,重载方法之间一般都是关联的,但不是必须的。
【例4.4】方法的重载。
1 //OverloadDemo.java
2 class Overload
3 {
4 void test()
5 {
6 System.out.println("No parameters");
7 }
8 void test(int a)
9 {
10 System.out.println("int a=" + a);
11 }
12 void test(int a,int b)
13 {
14 System.out.println("int a=" + a + ",b=" + b);
15 }
16 double test(double a)
17 {
18 System.out.println("double a=" + a);
19 return a*a;
20 }
21 }
22 public class OverloadDemo
23 {
24 public static void main(String[] args)
25 {
26 Overload ob = new Overload();
27 double result;
28 ob.test();
29 ob.test(10);
30 ob.test(10,20);
31 result = ob.test(12.5);
32 System.out.println("Result of ob.test(12.5): " + result);
33 }
34 }
程序的运行结果为:
No parameters
int a=10
int a=10,b=20
double a=12.5
Result of ob.test(12.5):156.25
从上述程序可见,test()被重载了四次。第一个没有参数,第二个有一个整型int参数,第三个有两个整型参数,第四个有一个double 型参数。由于重载不受方法的返回类型的影响,第四个test()方法也返回了一个和重载没有因果关系的值。
当一个重载的方法被调用时,Java是通过匹配参数来决定调用哪个方法,但是,这种匹配并不总是精确的。在一些情况下,Java的自动类型转换也适用于重载方法。
【例4.5】方法重载中的自动类型转换。
1 //OverloadDemo2.java
2 class Overload
3 {
4 void test()
5 {
6 System.out.println("No parameters");
7 }
8 void test(int a,int b)
9 {
10 System.out.println("int a=" + a + ",b=" + b);
11 }
12 void test(double a)
13 {
14 System.out.println("double a=" + a);
15 }
16 }
17 public class OverloadDemo2
18 {
19 public static void main(String[] args)
20 {
21 Overload ob = new Overload();
22 int i=88;
23 ob.test();
24 ob.test(10,20);
25 ob.test(i);
26 ob.test(12.5);
27 }
28 }
程序的运行结果为:
No parameters
int a=10,b=20
double a=88.0
double a=12.5
上面这个程序中并没有定义test(int)方法。因此当在程序中第25行带整型参数调用test(int)方法时,并不能在类中找到和它完全匹配的方法。但是Java可以自动地将整数转换为double型。因此,在test(int) 找不到以后,Java 将i扩大到double 型,然后调用test(double)。当然,如果定义了test(int) ,当然就会调用test(int) 而不会调用test(double)。只有在找不到精确匹配时,Java的自动转换才会起作用。
3. 构造方法
在Java中每个类都有自己的构造方法。构造方法是类中一种特殊的方法,它主要作用是用来创建对象,完成对象的初始化工作。构造方法的特殊性体现在它与众不同的特点:
(1) 构造方法的方法名和类名完全相同。
(2) 构造方法没有返回值,在声明的时候也不需要使用void来声明(注意:要是人为的加上void来声明也不会出现任何错误)。
(3) 构造方法的调用是在使用new运算符创建对象时由系统自动调用,用户不能显式调用。
(4) 如果在程序中没有定义构造方法,则在运行时系统会自动为该类创建一个默认的构造方法,它是一个没有任何内容且没有任何参数的构造方法。如果在Java程序中创建了构造方法,即使没有定义默认构造方法,Java系统也不会再自动创建默认构造方法,此时创建对象时需要注意不要使用程序中不存在的构造方法来创建对象,特别是使用默认构造方法。
【例4.6】构造方法的创建和使用。
1 //Mankind.java
2 public class Mankind
3 {
4 String name;
5 int age;
6 char sex;
7 double salary;
8 Mankind()
9 {
10 System.out.println("Output of constructor");
11 name="zhang san";
12 age=18;
13 sex='男';
14 salary=0;
15 }
16 void print()
17 {
18 System.out.println("name is "+name+",age is "+age+",sex is "+sex+",salary is "+salary+"元");
19 }
20 public static void main(String[] args)
21 {
22 Mankind onePerson=new Mankind();
23 System.out.println("After create object");
24 onePerson.print();
25 //onePerson.Mankind();
26 }
27 }
第22行创建onePerson对象时会自动调用构造方法Mankind()方法,并对成员变量进行相应的初始化操作。这样在第24行输出时就会输出初始化后的值
程序的运行结果为:
Output of constructor
After create object
name is zhang san,age is 18,sex is 男,salary is 0.0元
在程序中如果用户通过第25行的语句形式来直接调用构造方法,在编译的时候就会出现如下的错误提示,如图4.3所示。
【例4.7】默认构造方法的不当使用。
1 //MankindTest.java
2 public class MankindTest
3 {
4 String name;
5 int age;
6 MankindTest(String n)
7 {
8 name=n;
9 }
10 MankindTest(String n,int a)
11 {
12 name=n;
13 age=a;
14 }
15 void print()
16 {
17 System.out.println("name is "+name+",age is "+age);
18 }
19 public static void main(String[] args)
20 {
21 System.out.println("Create object");
22 MankindTest onePerson=new MankindTest();
23 MankindTest twoPerson=new MankindTest("zhang san");
24 MankindTest threePerson=new MankindTest("li si",20);
25 }
26 };
程序中定义了两个构造方法:第6行和第10行,这两个构造方法均是有参数的构造方法,这里没有默认的构造方法(没有参数的构造方法),此时Java不会自动去创建默认的构造方法。因此在第22行使用默认的构造方法去创建对象时就找不到相应的构造方法了,编译就会出现错误,如图4.4所示。更改的方法就是把第22行注释掉即可。
图4.4 用户自己调用构造方法时出现的错误提示
构造方法同其他普通方法一样也可以被重载,通过重载在一个类中定义多个不同的构造方法,用以实现不同形式的初始化操作。
【例4.8】构造方法的重载。
1 //MankindTest2.java
2 public class MankindTest2
3 {
4 String name;
5 int age;
6 char sex;
7 double salary;
8 MankindTest2()
9 {
10
11 }
12 MankindTest2(String n)
13 {
14 name=n;
15 age=10;
16 sex='男';
17 salary=0;
18 }
19 MankindTest2(String n,int a,char s,double sa)
20 {
21 name=n;
22 age=a;
23 sex=s;
24 salary=sa;
25 }
26 void print()
27 {
28 System.out.println("name is "+name+",age is "+age+",sex is "+sex+",salary is "+salary+"元");
29 }
30 public static void main(String[] args)
31 {
32 MankindTest2 onePerson=new MankindTest2();
33 onePerson.print();
34 MankindTest2 twoPerson=new MankindTest2("zhang san");
35 twoPerson.print();
36 MankindTest2 threePerson=new MankindTest2("li si",20,'男',2000.0);
37 threePerson.print();
38 }
39 }
程序的输出结果为:
name is null,age is 0,sex is ,salary is 0.0元
name is zhang san,age is 10,sex is 男,salary is 0.0元
name is li si,age is 20,sex is 男,salary is 2000.0元
四、对象的创建和使用
1. 创建对象
在类定义好后,可以使用类来创建对象来访问类中的成员、调用类中的方法。对象的创建有两种方法:一是使用new运算符直接来创建,另外就是使用赋值语句,利用已有的对象来赋值。
使用new运算符来创建对象有如下两种格式,这两种格式是等价的。
(1) 类名 对象名; //声明对象,创建对象的引用
对象名=new 构造方法([实参]); //创建对象,分配内存并进行初始化
(2) 类名 对象名=new 构造方法([实参]);
2. 使用对象中的成员与方法
对象一旦创建后,就可以使用分量运算符.来访问对象中的成员、调用对象中的方法,格式为:
对象名.成员名 //访问对象中非private的成员
对象名.方法名([实参]) //调用对象中的方法
注意:如果类中的成员被private修饰符修饰了,则不能直接使用对象名.成员名的形式来访问,而只能通过对象中的相应方法来访问和修改。
同一个类可以创建多个对象,这些对象之间是相互独立的,也就是说在处在不同的内存空间中。
【例4.9】对象的创建和使用。
1 //ObjectTest.java
2 public class ObjectTest
3 {
4 String name;
5 int age;
6 void print()
7 {
8 System.out.println("object info:name is "+name+",age is "+age);
9 }
10 public static void main(String[] args)
11 {
12 ObjectTest one=new ObjectTest();
13 ObjectTest two=new ObjectTest();
14 one.name="zhang san";
15 one.age=19;
16 two.name="li si";
17 two.age=63;
18 one.print();
19 two.print();
20 }
21 }
这里使用类ObjectTest创建了两个对象(第12行和第13行)one和two,它们的任何操作都是独立的,不会影响到另外的对象。程序的运行结果为:
object info:name is zhang san,age is19
object info:name is li si,age is 63
3. 成员的初始化
(1) 在使用类来创建对象时,如果该类中的成员是基本数据类型的成员,若没有显式地赋值,则在创建对象时会被自动初始化,它们的值就是各个数据类型的默认值。
【例4.10】成员变量的自动初始化。
1 //DefaultValue.java
2 public class DefaultValue
3 {
4 boolean b;
5 int i;
6 float f;
7 double d;
8 char c;
9 void print()
10 {
11 System.out.println("b is "+b);
12 System.out.println("i is "+i);
13 System.out.println("f is "+f);
14 System.out.println("d is "+d);
15 System.out.println("c is "+c);
16 }
17 public static void main(String[] args)
18 {
19 DefaultValue one=new DefaultValue();
20 one.print();
21 }
22 }
程序的输出结果为(c的输出实际是个空格):
b is false
i is 0
f is 0.0
d is 0.0
c is
(2) 如果类中的成员是其他类的对象时,若只使用“类名 对象名;”语句了,则只会声明一个对象,创建该对象的引用,系统不会为该对象分配任何内存空间,因此如果直接输出的话,是一个null值。如果使用new运算符,则会创建,直接输出时会输出该对象的哈希值,格式为:类名@对象的哈希值。对象的哈希值可以看作JVM中的虚拟地址,由JVM负责映射到实际的内存地址。
【例4.11】引用
1 //ObjectReference.java
2 class Reference
3 {
4 int a;
5 }
6 public class ObjectReference
7 {
8 Reference one;
9 Reference two=new Reference();
10 void print()
11 {
12 System.out.println("one object is "+one);
13 System.out.println("two object is "+two);
14 }
15 public static void main(String[] args)
16 {
17 ObjectReference test=new ObjectReference();
18 test.print();
19 }
20 }
程序的输出结果如图4.5所示:
图4.5 引用和对象的输出
(3) 如果不是类中的成员,如main()方法,不管是基本数据类型还是其他类型,Java系统不会为其进行自动的初始化,需要显式地进行赋值或者初始化,否
则在编译的时候就会提示错误,不能编译通过。
【例4.12】变量的显式赋值。
1 //NoInitValue.java
2 public class NoInitValue
3 {
4 public static void main(String[] args)
5 {
6 int i;
7 NoInitValue one;
8 NoInitValue two=new NoInitValue();
9 System.out.println("i is "+i);
10 System.out.println("one is "+one);
11 System.out.println("two is "+two);
12 }
13 }
在编译该程序的时候就会有如图4.6所示的错误提示。
图4.6 未初始化变量时的错误提示
要想修正错误,必须对变量i显式的赋值,如int i=10;同时要创建one对象,如NoInitValue one=new NoInitValue();
4. 对象间的赋值
创建对象除了使用new运算符外还可以利用其他对象直接赋值来创建一个对象,不过对象间的赋值与基本数据类型的变量赋值完全不同。基本数据类型变量间的赋值传递的是具体的数值,而对象间赋值传递的则是对象的地址,如果这两个对象赋值前均已经创建了,那么赋值后这两个对象都指向同一个地址,两个对象就变为一个对象了,另一个对象会被Java的垃圾收集机制自动回收了。
【例4.13】对象间的赋值。
1 //ObjectEvaluate.java
2 class Test
3 {
4
5 }
6 public class ObjectEvaluate
7 {
8 int a;
9 Test t1;
10 Test t2=new Test();
11 public static void main(String[] args)
12 {
13 ObjectEvaluate one=new ObjectEvaluate();
14 ObjectEvaluate two=new ObjectEvaluate();
15 System.out.println("1 t1 is "+one.t1+",t2 is "+one.t2);
16 Test t3=new Test();
17 one.t1=t3;
18 System.out.println("2 t1 is "+one.t1+",t2 is "+one.t2);
19 one.a=3;
20 two.a=4;
21 System.out.println("3 one.a is "+one.a+",two.a is "+two.a);
22 one=two;
23 System.out.println("4 one.a is "+one.a+",two.a is "+two.a);
24 one.a=5;
25 System.out.println("5 one.a is "+one.a+",two.a is "+two.a);
26 two.a=6;
27 System.out.println("6 one.a is "+one.a+",two.a is "+two.a);
28 }
29 }
程序的输出结果如图4.7所示。
图4.7 对象间赋值的运行结果
ObjectEvaluate类中的t1成员只是被声明,没有创建,因此在输出时(第15行)只能输出null,t2成员被创建了,所以有具体的输出值。t1在通过赋值语句(第17行)后就有值了,实际上就是被创建了。对于对象one和two在执行第22行语句赋值前,相互之间都是独立的,一旦执行了赋值语句(第22行)后,这两个对象实际上就变成了一个对象,只不过有两个名字而已,所以不管对哪个对象的操作都会影响到另外一个对象。
提示:Java中命名规范是类名的每个单词的首字母大写,而成员名、方法名、对象名的首个单词首字母小写,其后每个单词首字母大写。