文章目录
一、java类的封装
封装就是将对象的属性和方法相结合,通过方法将对象的属性和实现细节保护起来,实现对象的属性隐藏。
做法就是:修改属性的可见性来限制对属性的访问,并为每个属性创建一对取值(getter)方法和赋值(setter)方法,用于对这些属性的访问。
实现封装的具体步骤如下:
- 修改属性的可见性来限制对属性的访问。
- 为每个属性创建一对赋值方法和取值方法,用于对这些属性的访问。
- 在赋值和取值方法中,加入对属性的存取限制。
例 1
下面以一个员工类的封装为例介绍封装过程。一个员工的主要属性有姓名、年龄、联系电话和家庭住址。假设员工类为 Employee,示例如下:
1. public class Employee
2. {
3. private String name; //姓名
4. private int age; //年龄
5. private String phone; //联系电话
6. private String address; //家庭住址
7. public String getName()
8. {
9. return name;
10. }
11. public void setName(String name)
12. {
13. this.name=name;
14. }
15. public int getAge()
16. {
17. return age;
18. }
19. public void setAge(int age)
20. {
21. //对年龄进行限制
22. if(age<18||age>40)
23. {
24. System.out.println("年龄必须在18到40之间!");
25. this.age=20; //默认年龄
26. }
27. else
28. {
29. this.age=age;
30. }
31. }
32. public String getPhone()
33. {
34. return phone;
35. }
36. public void setPhone(String phone)
37. {
38. this.phone=phone;
39. }
40. public String getAddress()
41. {
42. return address;
43. }
44. public void setAddress(String address)
45. {
46. this.address=address;
47. }
48. }
如上述代码所示,使用 private 关键字修饰属性,这就意味着除了 Employee 类本身外,其他任何类都不可以访问这些属性。但是,可以通过这些属性的 setXxx() 方法来对其进行赋值,通过 getXxx() 方法来访问这些属性。
在 age 属性的 setAge() 方法中,首先对用户传递过来的参数 age 进行判断,如果 age 的值不在 18 到 40 之间,则将 Employee 类的 age 属性值设置为 20,否则为传递过来的参数值。
编写测试类 EmployeeTest,在该类的 main() 方法中调用 Employee 属性的 setXxx() 方法对其相应的属性进行赋值,并调用 getXxx() 方法访问属性,代码如下:
1. public class EmployeeTest
2. {
3. public static void main(String[] args)
4. {
5. Employee people=new Employee();
6. people.setName("王丽丽");
7. people.setAge(35);
8. people.setPhone("13653835964");
9. people.setAddress("河北省石家庄市");
10. System.out.println("姓名:"+people. getName());
11. System.out.println("年龄:"+people.getAge());
12. System.out.println("电话:"+people.getPhone());
13. System.out.println("家庭住址:"+people.getAddress());
14. }
15. }
运行该示例,输出结果如下:
姓名:王丽丽
年龄:35
电话:13653835964
家庭住址:河北省石家庄市
通过封装,实现了对属性的数据访问限制,满足了年龄的条件。在属性的赋值方法中可以对属性进行限制操作,从而给类中的属性赋予合理的值, 并通过取值方法获取类中属性的值(也可以直接调用类中的属性名称来获取属性值)。
二、java简单的继承及单继承和多继承的区别
继承是代码复用的一种形式,即在具有包含关系的类中,从属类继承主类的全部属性和方法,从而减少了代码冗余,提高了程序运行效率。例如,一个矩形(Rectangle类)属于四边形(Quadrilateral),正方形、平行四边形和梯形同样都属于四边形。从类的角度来解释,可以说成 Rectangle 类是从 Quadrilateral 类继承而来的,其中 Quadrilateral 类是基类,Rectangle 类是派生类。
1.简单继承
Java 中类的继承是通过扩展其他类而形成新类来实现的,原来的类称为父类(super class)或基类,新类称为原来类的子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法,使得父类的基本特征可被所有子类的对象共享。
注意:类的继承并不改变类成员的访问权限。也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性。
类继承的定义格式如下:
1. class class_name extends extend_class
2. {
3. //类的主体
4. }
其中,class_name 表示子类(派生类)的名称;extend_class 表示父类(基类)的名称;extends 关键字直接跟在子类名之后,其后面是该类要继承的父类名称。例如:
1. public class Student extends Person{}
例 1
教师类和学生类可以由人类派生,他们具有共同的属性:姓名、年龄、性别、身份证号,而学生还具有学生号和所学专业两个属性,教师还具有教龄和所教专业两个属性。下面编写 Java 程序代码,使教师类和学生类都继承于人类,具体的实现步骤如下。
(1) 创建人类 People,并定义 name、age、sex、sn 属性,代码如下:
1. public class People
2. {
3. public String name; //姓名
4. public int age; //年龄
5. public String sex; //性别
6. public String sn; //身份证号
7. public People(String name,int age,String sex,String sn)
8. {
9. this.name=name;
10. this.age=age;
11. this.sex=sex;
12. this.sn=sn;
13. }
14. public String toString()
15. {
16. return"姓名:"+name+"\n年龄:"+age+"\n性别:"+sex+"\n身份证号:"+sn;
17. }
18. }
如上述代码,在 People 类中包含 4 个公有属性、一个构造方法和一个 toString() 方法。
(2) 创建 People 类的子类 Student 类,并定义 stuNo 和 department 属性,代码如下:
1. public class Student extends People
2. {
3. private String stuNo; //学号
4. private String department; //所学专业
5. public Student(String name,int age,String sex,String sn,String stuno,String department)
6. {
7. super(name,age,sex,sn); //调用父类中的构造方法
8. this.stuNo=stuno;
9. this.department=department;
10. }
11. public String toString()
12. {
13. return"姓名:"+name+"\n年龄:"+age+"\n性别:"+sex+"\n身份证号:"+sn+"\n学号:"+stuNo+"\n所学专业:"+department;
14. }
15. }
由于 Student 类继承自 People 类,因此,在 Student 类中同样具有 People 类的属性和方法,这里重写了父类中的 toString() 方法。
注意:如果在父类中存在有参的构造方法而并没有重载无参的构造方法,那么在子类中必须含有有参的构造方法,因为如果在子类中不含有构造方法,默认会调用父类中无参的构造方法,而在父类中并没有无参的构造方法,因此会出错。
(3) 创建 People 类的另一个子类 Teacher,并定义 tYear 和 tDept 属性,代码如下:
1. public class Teacher extends People
2. {
3. private int tYear; //教龄
4. private String tDept; //所教专业
5. public Teacher(String name,int age,String sex,String sn,int tYear,String tDept)
6. {
7. super(name,age,sex,sn); //调用父类中的构造方法
8. this.tYear=tYear;
9. this.tDept=tDept;
10. }
11. public String toString()
12. {
13. return"姓名:"+name+"\n年龄:"+age+"\n性别:"+sex+"\n身份证号:"+sn+"\n教龄:"+tYear+"\n所教专业:"+tDept;
14. }
15. }
Teacher 类与 Student 类相似,同样重写了父类中的 toString() 方法。
(4) 编写测试类 PeopleTest,在该类中创建 People 类的不同对象,分别调用它们的 toString() 方法,输出不同的信息。具体的代码如下:
1. public class PeopleTest
2. {
3. public static void main(String[] args)
4. {
5. //创建Student类对象
6. People stuPeople=new Student("王丽丽",23,"女","410521198902145589","00001","计算机应用与技术");
7. System.out.println("----------------学生信息---------------------");
8. System.out.println(stuPeople);
9.
10. //创建Teacher类对象
11. People teaPeople=new Teacher("张文",30,"男","410521198203128847",5,"计算机应用与技术");
12. System.out.println("----------------教师信息----------------------");
13. System.out.println(teaPeople);
14. }
15. }
运行程序,输出的结果如下:
----------------学生信息---------------------
姓名:王丽丽
年龄:23
性别:女
身份证号:410521198902145589
学号:00001
所学专业:计算机应用与技术
----------------教师信息----------------------
姓名:张文
年龄:30
性别:男
身份证号:410521198203128847
教龄:5
所教专业:计算机应用与技术
2.单继承
Java 不支持多继承,只允许一个类直接继承另一个类,即子类只能有一个父类,extends 关键字后面只能有一个类名。例如,如下代码会导致编译错误:
1. class Student extends Person,Person1,Person2{…}
2. class Student extends Person,extends Person1,extends Person2{…}
尽管一个类只能有一个直接的父类,但是它可以有多个间接的父类。例如,Student 类继承 Person 类,Person 类继承 Person1 类,Person1 类继承 Person2 类,那么 Person1 和 Person2 类是 Student 类的间接父类。下图展示了单继承的关系。
从图中可以看出,三角形、四边形和五边形的直接父类是多边形类,它们的间接父类是图形类。图形类、多边形类和三角形、四边形、五边形类形成了一个继承的分支。在这个分支上,位于下层的子类会继承上层所有直接或间接父类的属性和方法。如果两个类不在同一个继承树分支上,就不会存在继承关系,例如多边形类和直线。
提示:间接的继承关系就是分别继承、实现,例如:class Graph extends Polygon{…}、class Trigon extends Graph{…}。如果类声明头部没有定义任何 extends 子句,则该类隐含地继承自 java.lang.Object 类。
三、java的多态
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
格式说明:父类引用指向子类对象。
举个例子:
Figure figure=newRectangle(9,9);
例 1
下面通过一个例子来演示重写如何实现多态性。例子使用了类的继承和运行时多态机制,具体步骤如下。
(1) 创建 Figure 类,在该类中首先定义存储二维对象的尺寸,然后定义有两个参数的构造方法,最后添加 area() 方法,该方法计算对象的面积。代码如下:
1. public class Figure
2. {
3. double dim1;
4. double dim2;
5. Figure(double d1,double d2)
6. {
7. //有参的构造方法
8. this.dim1=d1;
9. this.dim2=d2;
10. }
11. double area()
12. {
13. //用于计算对象的面积
14. System.out.println("父类中计算对象面积的方法,没有实际意义,需要在子类中重写。");
15. return 0;
16. }
17. }
(2) 创建继承自 Figure 类的 Rectangle 子类,该类调用父类的构造方法,并且重写父类中的 area() 方法。代码如下:
1. public class Rectangle extends Figure
2. {
3. Rectangle(double d1,double d2)
4. {
5. super(d1,d2);
6. }
7. double area()
8. {
9. System.out.println("长方形的面积:");
10. return super.dim1*super.dim2;
11. }
12. }
(3) 创建继承自 Figure 类的 Triangle 子类,该类与 Rectangle 相似。代码如下:
1. public class Triangle extends Figure
2. {
3. Triangle(double d1,double d2)
4. {
5. super(d1,d2);
6. }
7. double area()
8. {
9. System.out.println("三角形的面积:");
10. return super.dim1*super.dim2/2;
11. }
12. }
(4) 创建 Test 测试类,在该类的 main() 方法中首先声明 Figure 类的变量 figure,然后分别为 figure 变量指定不同的对象,并调用这些对象的 area() 方法。代码如下:
2. {
3. public static void main(String[] args)
4. {
5. Figure figure; //声明Figure类的变量
6. figure=new Rectangle(9,9);
7. System.out.println(figure.area());
8. System.out.println("===============================");
9. figure=new Triangle(6,8);
10. System.out.println(figure.area());
11. System.out.println("===============================");
12. figure=new Figure(10,10);
13. System.out.println(figure.area());
14. }
15. }
从上述代码可以发现,无论 figure 变量的对象是 Rectangle 还是 Triangle,它们都是 Figure 类的子类,因此可以向上转型为该类,从而实现多态。
(5) 执行上述代码,输出结果如下:
长方形的面积:
81.0
===============================
三角形的面积:
24.0
===============================
父类中计算对象面积的方法,没有实际意义,需要在子类中重写。
0.0
其中父类引用figure在调用方法时,会先看自己有没有area()这个方法,如果没有area()方法,编译会报错,如果有这个方法,会执行子类重写后的方法,
口诀就是:编译看左边,运行看右边
就比如上图中:figure.area()
①编译会先看左边,figure接口中有没有area()方法,有,编译通过。
②运行结果看右边,是Rectangle对象,那么会执行Rectangle中重写的eat()方法,也就是“长方形的面积:81.0”。
1.多态的好处
多态的好处就是当父类类型作为方法形式参数时,更能体现出多态的扩展性与便利。
上面的父类Figure,用了多态,我们在创建对象的时候,只用创建一个父类的对象,然后指向其要建立的子类,然后调用其内部方法就行了,就不用每创建一个子类就去用子类的对象去建立对象了,直接用父类的引用去指向子类的实例就行了。
2.多态的弊端
弊端就是父类的引用,不能调用子类里面独有的方法,意识就是,父类里面有一个area()方法,子类里面也有一个area()方法,还有一个周长()方法,这时候,父类的引用不能调用这个周长()方法,因为他是子类独有的方法。
四、java的抽象类
如果一个类只定义了一个为所有子类共享的一般形式,至于细节则交给每一个子类去实现,这种类没有任何具体的实例,只具有一些抽象的概念,那么这样的类称为抽象类。
抽象类白话文说就是把一类事物共有的部分抽离出来为一个公共的类,然后具体的实现去交给继承的子类去做。
在面向对象领域,抽象类主要用来进行类型隐藏。比如,如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
在 Java 中抽象类的语法格式如下:
<abstract>class<class_name>
{
<abstract><type><method_name>(parameter-iist);
}
其中,abstract 表示该类或该方法是抽象的;class_name 表示抽象类的名称;method_name 表示抽象方法名称,如果在一个方法之前使用 abstract 来修饰,则说明该方法是抽象方法,不能有方法体;parameter-list 表示方法参数列表。
我们可以看到抽象方法使用 abstract 关键字声明,它没有方法体,而直接使用 ; 结尾。
注意:abstract 关键字只能用于普通方法,不能用于 static 方法或者构造方法中。在抽象类中必须包含至少一个抽象方法,并且所有抽象方法不能有具体的实现,而应在它们的子类中实现所有的抽象方法(要有方法体)。继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
包含一个或多个抽象方法的类必须通过在其 class 声明前添加 abstract 关键字将其声明为抽象类。因为一个抽象类不定义完整的实现,所以抽象类也就没有自己的对象。因此,任何使用 new 创建抽象类对象的尝试都会导致编译时错误。
例 1
不同几何图形的面积计算公式是不同的,但是它们具有的特性是相同的,都具有长和宽这两个属性,也都具有面积计算的方法。那么可以定义一个抽象类,在该抽象类中含有两个属性(width 和 height)和一个抽象方法 area(),具体步骤如下。
(1) 首先创建一个表示图形的抽象类 Shape,代码如下所示。
1. public abstract class Shape
2. {
3. public int width; //几何图形的长
4. public int height; //几何图形的宽
5. public Shape(int width,int height)
6. {
7. this.width=width;
8. this.height=height;
9. }
10. public abstract double area(); //定义抽象方法,计算面积
11. }
(2) 定义一个正方形类,该类继承自形状类 Shape,并重写了 area() 抽象方法。正方形类的代码如下:
1. public class Square extends Shape
2. {
3. public Square(int width,int height)
4. {
5. super(width,height);
6. }
7. //重写父类中的抽象方法,实现计算正方形面积的功能
8. @Override
9. public double area()
10. {
11. return width*height;
12. }
13. }
(3) 定义一个三角形类,该类与正方形类一样,需要继承形状类 Shape,并重写父类中的抽象方法 area()。三角形类的代码实现如下:
1. public class Triangle extends Shape
2. {
3. public Triangle(int width,int height)
4. {
5. super(width, height);
6. }
7. //重写父类中的抽象方法,实现计算三角形面积的功能
8. @Override
9. public double area()
10. {
11. return 0.5*width*height;
12. }
13. }
(4) 最后创建一个测试类,分别创建正方形类和三角形类的对象,并调用各类中的 area() 方法,打印出不同形状的几何图形的面积。测试类的代码如下:
1. public class ShapeTest
2. {
3. public static void main(String[] args)
4. {
5. Square square=new Square(5,4); //创建正方形类对象
6. System.out.println("正方形的面积为:"+square.area());
7. Triangle triangle=new Triangle(2,5); //创建三角形类对象
8. System.out.println("三角形的面积为:"+triangle.area());
9. }
10. }
在该程序中,创建了 4 个类,分别为图形类 Shape、正方形类 Square、三角形类 Triangle 和测试类 ShapeTest。其中图形类 Shape 是一个抽象类,创建了两个属性,分别为图形的长度和宽度,并通过构造方法 Shape() 给这两个属性赋值。
在 Shape 类的最后定义了一个抽象方 法 area(),用来计算图形的面积。在这里,Shape 类只是定义了计算图形面积的方法,而对于如何计算并没有任何限制。也可以这样理解,抽象类 Shape 仅定义了子类的一般形式。
正方形类 Square 继承抽象类 Shape,并实现了抽象方法 area()。三角形类 Triangle 的实现和正方形类相同,这里不再介绍。
在测试类 ShapeTest 的 main() 方法中,首先创建了正方形类和三角形类的实例化对象 square 和 triangle,然后分别调用 area() 方法实现了面积的计算功能。
(5) 运行该程序,输出的结果如下:
正方形的面积为:20.0
三角形的面积为:5.0
五、java的接口
在 Java 中,被关键字 interface 修饰的 class 就是一个接口。接口定义了一个行为协议,可以由类层次结构中任何位置的任何类实现。接口中定义了一组抽象方法,都没有具体实现,实现该接口的类必须实现该接口中定义的所有抽象方法。
为什么需要接口呢?
我们知道 Java 仅支持单继承,也就是说一个类只允许有一个直接父类,这样保证了数据的安全。Java 不支持下图所示的多继承:
接口就是为了解决 Java 单继承这个弊端而产生的,虽然一个类只能有一个直接父类,但是它可以实现多个接口,没有继承关系的类也可以实现相同的接口。继承和接口的双重设计既保持了类的数据安全也变相实现了多继承。
1.定义接口
接口继承和实现继承的规则不同,一个类只有一个直接父类,但可以实现多个接口。Java 接口本身没有任何实现,只描述 public 行为,因此 Java 接口比 Java 抽象类更抽象化。Java 接口的方法只能是抽象的和公开的,Java 接口不能有构造方法,Java 接口可以有 public、Static 和 final 属性。
接口把方法的特征和方法的实现分隔开来,这种分隔体现在接口常常代表一个角色,它包装与该角色相关的操作和属性,而实现这个接口的类便是扮演这个角色的演员。一个角色由不同的演员来演,而不同的演员之间除了扮演一个共同的角色之外,并不要求其他的共同之处。
接口对于其声明、变量和方法都做了许多限制,这些限制作为接口的特征归纳如下:
- 具有 public 访问控制符的接口,允许任何类使用;没有指定 public 的接口,其访问将局限于所属的包。
- 方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。
- 在 Java 接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。
- 接口没有构造方法,不能被实例化。例如:
1. public interface A
2. {
3. publicA(){…} //编译出错,接口不允许定义构造方法
4. }
- 一个接口不能够实现另一个接口,但它可以继承多个其他接口。子接口可以对父接口的方法和常量进行重写。例如:
1. public interface StudentInterface extends PeopleInterface
2. {
3. //接口 StudentInterface 继承 PeopleInterface
4. int age=25; //常量age重写父接口中的age常量
5. void getInfo(); //方法getInfo()重写父接口中的getInfo()方法
6. }
Java 接口的定义方式与类基本相同,不过接口定义使用的关键字是 interface,接口定义由接口声明和接口体两部分组成。语法格式如下:
[public] interface interface_name [extends interface1_name[, interface2_name,…]]
{
//接口体,其中可以包含定义常量和声明方法
[public] [static] [final] type constant_name=value; //定义常量
[public] [abstract] returnType method_name(parameter_list); //声明方法
}
其中,public 表示接口的修饰符,当没有修饰符时,则使用默认的修饰符,此时该接口的访问权限仅局限于所属的包;interfaCe_name 表示接口的名称,可以是任何有效的标识符;extends 表示接口的继承关系;interface1_name 表示要继承的接口名称;constant_name 表示变量名称,一般是 static 和 final 型的;returnType 表示方法的返回值类型;parameter_list 表示参数列表,在接口中的方法是没有方法体的。
提示:如果接口本身被定义为 public,则所有的方法和常量都是 public 型的。
例如,定义一个接口 MyInterface,并在该接口中声明常量和方法,如下:
1. public interface Mylnterface
2. { //接口myInterface
3. String name; //不合法,变量name必须初始化
4. int age=20; //合法,等同于 public static final int age=20;
5. void getInfo(); //方法声明,等同于 public abstract void getInfo();
6. }
2.实现接口
接口被定义后,一个或者多个类都可以实现该接口,这需要在实现接口的类的定义中包含 implements 子句,然后实现由接口定义的方法。实现接口的一般形式如下:
<public> class <class_name> [extends superclass_name] [implements interface[, interface…]]
{
//主体
}
如果一个类实现多个接口,这些接口需要使用逗号分隔。如果一个类实现两个声明了同样方法的接口,那么相同的方法将被其中任一个接口使用。实现接口的方法必须声明为 public,而且实现方法的类型必须严格与接口定义中指定的类型相匹配。
例 1
在程序的开发中,需要完成两个数的求和运算和比较运算功能的类非常多。那么可以定义一个接口来将类似功能组织在一起。下面创建一个示例,具体介绍接口的实现方式。
(1) 创建一个名称为 IMath 的接口,代码如下:
1. public interface IMath
2. {
3. public int sum(); //完成两个数的相加
4. public int maxNum(int a,int b); //获取较大的数
5. }
(2) 定义一个 MathClass 类并实现 IMath 接口,MathClass 类实现代码如下:
1. public class MathClass implements IMath
2. {
3. private int num1; //第 1 个操作数
4. private int num2; //第 2 个操作数
5. public MathClass(int num1,int num2)
6. {
7. //构造方法
8. this.num1=num1;
9. this.num2=num2;
10. }
11. //实现接口中的求和方法
12. public int sum()
13. {
14. return num1+num2;
15. }
16. //实现接口中的获取较大数的方法
17. public int maxNum(int a,int b)
18. {
19. if(a>=b)
20. {
21. return a;
22. }
23. else
24. {
25. return b;
26. }
27. }
28. }
在实现类中,所有的方法都使用了 public 访问修饰符声明。无论何时实现一个由接口定义的方法,它都必须实现为 public,因为接口中的所有成员都显式声明为 public。
(3) 最后创建测试类 NumTest,实例化接口的实现类 MathClass,调用该类中的方法并输出结果。该类内容如下:
1. public class NumTest
2. {
3. public static void main(String[] args)
4. {
5. //创建实现类的对象
6. MathClass calc=new MathClass(100, 300);
7. System.out.println("100 和 300 相加结果是:"+calc.sum());
8. System.out.println("100 比较 300,哪个大:"+calc.maxNum(100, 300));
9. }
10. }
程序运行结果如下所示。
100 和 300 相加结果是:400
100 比较 300,哪个大:300
在该程序中,首先定义了一个 IMath 的接口,在该接口中只声明了两个未实现的方法,这两个方法需要在接口的实现类中实现。在实现类 MathClass 中定义了两个私有的属性,并赋予两个属性初始值,同时创建了该类的构造方法。因为该类实现了 MathClass 接口,因此必须实现接口中的方法。在最后的测试类中,需要创建实现类对象,然后通过实现类对象调用实现类中的方法。
六、java的接口和抽象类的区别
从前面对面向对象的设计原则的讲解,读者可以了解到,其实所有的设计原则和设计模式都离不开抽象,因为只有抽象才能实现上述设计原则和设计模式。
在 Java 中,针对抽象有两种实现方式:一种是接口,一种是抽象类。很多读者对这两种实现方式比较困惑,到底是使用接口,还是使用抽象类呢?对于它们的选择甚至反映出对问题领域本质的理解,对设计意图的理解是否正确、合理?
在面向对象的设计思想中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有描绘一个具体的对象,那么这样的类就是抽象类,抽象类是对那些看上去不同,但是本质上相同的具体概念的抽象,正是因为抽象的概念在问题领域没有对应的具体概念,所以抽象类是不能够实例化的。
1.基本语法区别
在 Java 中,接口和抽象类的定义语法是不一样的。这里以动物类为例来说明,其中定义接口的示意代码如下:
1. public interface Animal
2. {
3. //所有动物都会吃
4. public void eat();
5.
6. //所有动物都会飞
7. public void fly();
8. }
定义抽象类的示意代码如下:
1. public abstract class Animal
2. {
3. //所有动物都会吃
4. public abstract void eat();
5.
6. //所有动物都会飞
7. public void fly(){};
8. }
可以看到,在接口内只能是功能的定义,而抽象类中则可以包括功能的定义和功能的实现。在接口中,所有的属性肯定是 public、static 和 final,所有的方法都是 abstract,所以可以默认不写上述标识符;在抽象类中,既可以包含抽象的定义,也可以包含具体的实现方法。
在具体的实现类上,接口和抽象类的实现类定义方式也是不一样的,其中接口实现类的示意代码如下:
1. public class concreteAnimal implements Animal
2. {
3. //所有动物都会吃
4. public void eat(){}
5.
6. //所有动物都会飞
7. public void fly(){}
8. }
抽象类的实现类示意代码如下:
1. public class concreteAnimal extends Animal
2. {
3. //所有动物都会吃
4. public void eat(){}
5.
6. //所有动物都会飞
7. public void fly(){}
8. }
可以看到,在接口的实现类中使用 implements 关键字;而在抽象类的实现类中,则使用 extends 关键字。一个接口的实现类可以实现多个接口,而一个抽象类的实现类则只能实现一个抽象类。
1. 接口的方法默认是 public ,所有方法在接口中不能有实现(JDK1.8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法;
2. 接口中除了 static 、final 变量,不能有其他变量,而抽象类可以;
3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口;
4. 接口方法默认修饰符是 public ,抽象方法可以有 public 、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰!);
5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为(行为就是方法的意思)的抽象,是一种行为的规范。这一条非常不错,我们都知道,抽象类是把所有类的共性抽象出来一个类,让其他类去继承,所以说是对类的抽象,而接口里面写的是方法,是把所有共性的方法抽象出来,让其他的类去实现这些方法,所有说接口是对行为的抽象,是方法的抽象。
七、java的内部类
这个东西我在书籍开发中还没见过,先学习记录一下,有用到再细看吧,哈哈哈哈
在一个类内部的类,我们称之为内部类。内部类可以很好地实现隐藏,一般的非内部类是不允许有 private 与 protected 权限的,但内部类可以。内部类拥有外围类的所有元素的访问权限。
内部类可以分为:实例内部类、静态内部类和成员内部类,每种内部类都有它特定的一些特点,本节先详细介绍一些和内部类相关的知识。
在类 A 中定义类 B,那么类 B 就是内部类,也称为嵌套类,相对而言,类 A 就是外部类。如果有多层嵌套,例如类 A 中有内部类 B,而类 B 中还有内部类 C,那么通常将最外层的类称为顶层类(或者顶级类)。
内部类也可以分为多种形式,与变量非常类似,如图 1 所示。
内部类的特点如下:
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的 .class 文件,但是前面冠以外部类的类名和 $ 符号。
- 内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否为 private 的。
- 内部类声明成静态的,就不能随便访问外部类的成员变量,仍然是只能访问外部类的静态成员变量。
【例1】内部类的使用方法非常简单,例如下面的代码演示了内部类最简单的应用。
1. public class Test
2. {
3. public class InnerClass
4. {
5. public int getSum(int x,int y)
6. {
7. return x+y;
8. }
9. }
10. public static void main(String[] args)
11. {
12. Test.InnerClass ti=new Test().new InnerClass();
13. int i=ti.getSum(2/3);
14. System.out.println(i); //输出5
15. }
16. }
有关内部类的说明有如下几点:
- 外部类只有两种访问级别:public 和默认;内部类则有4种访问级别:public、protected、 private 和默认。
- 在外部类中可以直接通过内部类的类名访问内部类。 InnerClass ic=new InnerClassf); //InnerClass为内部类的类名
- 在外部类以外的其他类中则需要通过内部类的完整类名访问内部类。 Test.InnerClass ti=newTest().new InnerClass(); //Test.innerClass是内部类的完整类名
- 内部类与外部类不能重名。
提示:内部类的很多访问规则可以参考变量和方法。
1.Java的局部内部类
局部内部类是指在一个方法中定义的内部类。示例代码如下:
1. public class Test
2. {
3. public void method()
4. {
5. class Inner
6. {
7. //局部内部类
8. }
9. }
10. }
局部内部类有如下特点。
(1) 局部内部类与局部变量一样,不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。
(2) 局部内部类只在当前方法中有效。
1. public class Test
2. {
3. Inner i=new Inner(); //编译出错
4. Test.Inner ti=new Test.Inner(); //编译出错
5. Test.Inner ti2=new Test().new Inner(); //编译出错
6. public void method()
7. {
8. class Inner{}
9. Inner i=new Inner();
10. }
11. }
(3) 局部内部类中不能定义 static 成员。
(4) 局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符(public、 private 和 protected)和 static 修饰符修饰。
(5) 在局部内部类中可以访问外部类的所有成员。
(6) 在局部内部类中只可以访问当前方法中 final 类型的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用 .this. 的形式访问外部类中的成员。
1. public class Test
2. {
3. int a=0;
4. int d=0;
5. public void method()
6. {
7. int b=0;
8. final int c=0;
9. final int d=10;
10. class Inner
11. {
12. a2=a; //访问外部类中的成员
13. //intb2=b; //编译出错
14. int c2=c; //访问方法中的成员
15. int d2=d; //访问方法中的成员
16. int d3=Test.this.d; //访问外部类中的成员
17. }
18. Inner i=new Inner();
19. System.out.println(i.d2); //输出10
20. System.out.printIn(i.d3); //输出0
21. }
22. public static void main(String[] args)
23. {
24. Test t=new Test();
25. t.method();
26. }
27. }
2.Java的实例内部类
实例内部类是指没有用 static 修饰的内部类。示例代码如下:
1. public class Outer
2. {
3. class Inner
4. {
5. //实例内部类
6. }
7. }
上述示例中的 Inner 类就是实例内部类。实例内部类有如下特点。
(1) 在外部类的静态方法和外部类以外的其他类中,必须通过外部类的实例创建内部类的实例。
1. public class Outer
2. {
3. class Inner1{}
4. Inner1 i=new Inner1(); //不需要创建外部类实例
5. public void method1()
6. {
7. Inner1 i=new Inner1(); //不需要创建外部类实例
8. }
9. public static void method2()
10. {
11. Inner1 i=new Outer().new inner1(); //需要创建外部类实例
12. }
13. class Inner2
14. {
15. Inner1 i=new Inner1(); //不需要创建外部类实例
16. }
17. }
18. class OtherClass
19. {
20. Outer.Inner i=new Outer().new Inner(); //需要创建外部类实例
21. }
(2) 在实例内部类中,可以访问外部类的所有成员。
1. public class Outer
2. {
3. public int a=100;
4. static int b=100;
5. final int c=100;
6. private int d=100;
7. public String method1()
8. {
9. return "实例方法1";
10. }
11. public static String method2()
12. {
13. return "静态方法2";
14. }
15. class Inner
16. {
17. int a2=a+1; //访问public的a
18. int b2=b+1; //访问static的b
19. int c2=c+1; //访问final的c
20. int d2=d+1; //访问private的d
21. String str1=method1(); //访问实例方法method1
22. String str2=method2(); //访问静态方法method2
23. }
24. public static void main(String[] args)
25. {
26. Inner i=new Outer().new Inner(); //创建内部类实例
27. System.out.println(i.a2); //输出101
28. System.out.println(i.b2); //输出101
29. System.out.println(i.c2); //输出101
30. System.out.println(i.d2); //输出101
31. System.out.println(i.strl); //输出实例方法1
32. System.out.println(i.str2); //输出静态方法2
33. }
34. }
提示:如果有多层嵌套,则内部类可以访问所有外部类的成员。
(3) 在外部类中不能直接访问内部类的成员,而必须通过内部类的实例去访问。如果类 A 包含内部类 B,类 B 中包含内部类 C,则在类 A 中不能直接访问类 C,而应该通过类 B 的实例去访问类 C。
(4) 外部类实例与内部类实例是一对多的关系,也就是说一个内部类实例只对应一个外部类实例,而一个外部类实例则可以对应多个内部类实例。
如果实例内部类 B 与外部类 A 包含有同名的成员 t,则在类 B 中 t 和 this.t 都表示 B 中的成员 t,而 A.this.t 表示 A 中的成员 t。
1. public class Outer
2. {
3. int a=10;
4. class Inner
5. {
6. int a=20;
7. int b1=a;
8. int b2=this.a;
9. int b3=Outer.this.a;
10. }
11. public static void main(String[] args)
12. {
13. Inner i=new Outer().new Inner();
14. System.out.println(i.b1); //输出20
15. System.out.println(i.b2); //输出20
16. System.out.println(i.b3); //输出10
17. }
18. }
(5) 在实例内部类中不能定义 static 成员,除非同时使用 final 和 static 修饰。
3.Java的静态内部类
静态内部类是指使用 static 修饰的内部类。示例代码如下:
1. public class Outer
2. {
3. static class Inner
4. {
5. //静态内部类
6. }
7. }
上述示例中的 Inner 类就是静态内部类。静态内部类有如下特点。
(1) 在创建静态内部类的实例时,不需要创建外部类的实例。
1. public class Outer
2. {
3. static class Inner{}
4. }
5. class OtherClass
6. {
7. Outer.Inner oi=new Outer.Inner();
8. }
(2) 静态内部类中可以定义静态成员和实例成员。外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例。
1. public class Outer
2. {
3. static class Inner
4. {
5. int a=0; //实例变量a
6. static int b=0; //静态变量 b
7. }
8. }
9. class OtherClass
10. {
11. Outer.Inner oi=new Outer.Inner();
12. int a2=oi.a; //访问实例成员
13. int b2=Outer.Inner.b; //访问静态成员
14. }
(3) 静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。
1. public class Outer
2. {
3. int a=0; //实例变量
4. static int b=0; //静态变量
5. static class Inner
6. {
7. Outer o=new Outer;
8. int a2=o.a; //访问实例变量
9. int b2=b; //访问静态变量
10. }
11. }
4.Java的匿名内部类
匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。其语法形式如下:
1. new<类或接口>()
2. {
3. //类的主体
4. };
这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。
匿名类有两种实现方式:
- 继承一个类,重写其方法。
- 实现一个接口(可以是多个),实现其方法。
下面通过代码来说明。
1. public class Out
2. {
3. void show()
4. {
5. System.out.println("调用 Out 类的 show() 方法");
6. }
7. }
8. public class TestAnonymousInterClass
9. {
10. //在这个方法中构造一个匿名内部类
11. private void show()
12. {
13. Out anonyInter=new Out()
14. {
15. //获取匿名内部类的实例
16. void show()
17. {
18. System.out.println("调用匿名类中的 show() 方法");
19. }
20. };
21. anonyInter.show();
22. }
23. public static void main(String[] args)
24. {
25. TestAnonymousInterClass test=new TestAnonymousInterClass();
26. test.show();
27. }
28. }
程序的输出结果如下:
调用匿名类中的 show() 方法
从输出结果可以看出,匿名内部类有自己的实现。
提示:匿名内部类实现一个接口的方式与实现一个类的方式相同,这里不再赘述。
匿名类有如下特点。
(1) 匿名类和局部内部类一样,可以访问外部类的所有成员。如果匿名类位于一个方法中,则匿名类只能访问方法中 final 类型的局部变量和参数。
1. public static void main(String[] args)
2. {
3. int a=10;
4. final int b=10;
5. Out anonyInter=new Out()
6. {
7. void show()
8. {
9. //System.out.println("调用了匿名类的 show() 方法"+a); //编译出错
10. System.out.println("调用了匿名类的 show() 方法"+b); //编译通过
11. }
12. };
13. anonyInter.show();
14. }
(2) 匿名类中允许使用非静态代码块进行成员初始化操作。
1. Out anonyInter=new Out()
2. {
3. int i;
4. { //非静态代码块
5. i=10; //成员初始化
6. }
7. public void show()
8. {
9. System.out.println("调用了匿名类的 show() 方法"+i);
10. }
11. };
(3) 匿名类的非静态代码块会在父类的构造方法之后被执行。
5.Java内部类的作用
多重继承指的是一个类可以同时从多于一个的父类那里继承行为和特征,然而我们知道 Java 为了保证数据安全,只允许单继承。
有些时候我们会认为如果系统中需要使用多重继承,那往往都是糟糕的设想,这时开发人员往往需要思考的不是怎么使用多重继承,而是他的设计是否存在问题。但是,有时候开发人员确实需要实现多重继承,而且现实生活中真正地存在这样的情况,例如遗传,我们既继承了父亲的行为和特征,也继承了母亲的行为和特征。
Java 提供的两种方法让我们实现多重继承:接口和内部类。
例 1
本节我们以生活中常见的遗传例子进行介绍,如儿子(或者女儿)是如何利用多重继承来继承父亲和母亲的优良基因的。
(1) 创建 Father 类,在该类中添加 strong() 方法。代码如下:
1. public class Father
2. {
3. public int strong()
4. { //强壮指数
5. return 9;
6. }
7. }
(2) 创建 Mother 类,在该类中添加 kind() 方法。代码如下:
1. public class Mother
2. {
3. public int kind()
4. { //友好指数
5. return 8;
6. }
7. }
(3) 重点在于儿子类的实现,创建 Son 类,在该类中通过内部类实现多重继承。代码如下:
1. public class Son
2. {
3. //内部类继承Father类
4. class Father_1 extends Father
5. {
6. public int strong()
7. {
8. return super.strong()+1;
9. }
10. }
11. class Mother_1 extends Mother
12. {
13. public int kind()
14. {
15. return super.kind()-2;
16. }
17. }
18. public int getStrong()
19. {
20. return new Father_1().strong();
21. }
22. public int getKind()
23. {
24. return new Mother_1().kind();
25. }
26. }
上述代码定义两个内部类,这两个内部类分别继承 Father(父亲)类和 Mother(母亲)类,且都可以获取各自父类的行为。这是内部类一个很重要的特性:内部类可以继承一个与外部类无关的类,从而保证内部类的独立性。正是基于这一点,多重继承才会成为可能。
(4) 创建 Test 类进行测试,在 main() 方法中实例化 Son 类的对象,然后分别调用该对象的 getStrong() 方法和 getKind() 方法。代码如下:
1. public class Test
2. {
3. public static void main(String[] args)
4. {
5. Son son=new Son();
6. System.out.println("Son 的强壮指数:"+son.getStrong());
7. System.out.println("Son 的友好指数:"+son.getKind());
8. }
9. }
执行上述代码,输出结果如下:
Son 的强壮指数:10
Son 的友好指数:6
从实现代码和输出结果可以发现,儿子继承父类,变得比父亲更加强壮;同时也继承了母类,只不过友好指数下降。
八、Java的super关键字
由于子类不能继承父类的构造方法,因此,要调用父类的构造方法,必须在子类的构造方法体的第一行使用 super() 方法。该方法会调用父类相应的构造方法来完成子类对象的初始化工作。
在以下情况下需要使用 super 关键字:
- 在类的构造方法中,通过 super 语句调用该类的父类的构造方法。
- 在子类中访问父类中的成员。
1.使用 super 调用父类的构造方法
子类可以通过 super 关键字来调用一个由父类定义的构造方法,格式如下:
super(parameter-list);
其中,parameter-list 指定了父类中构造方法所需的所有参数。super() 必须是在子类构造方法的主体第一行。
例如,在 Person 类中指定了两个构造方法。示例代码如下:
1. public People(String name,int age,String sex,String sn)
2. {
3. this.name=name;
4. this.age=age;
5. this.sex=sex;
6. this.sn=sn;
7. }
8. public People(String name,String sn)
9. {
10. this.name=name;
11. this.sn=sn;
12. }
那么,Student 类继承了 Person 类,就可以使用 super 语句来定义 Student 类的构造方法。示例代码如下:
1. public Student(String name,int age,String sex,String sn,String stuno,String department)
2. {
3. super(name,age,sex,sn); //调用父类中含有4个参数的构造方法
4. this.stuNo=stuno;
5. this.department=department;
6. }
7. public Student(String name,String sn,String stuNo)
8. {
9. super(name,sn); //调用父类中含有两个参数的构造方法
10. this.stuNo=stuNo;
11. }
从上述 Student 类构造方法代码可以看出,super 用来直接调用父类中的构造方法,使用它可以使书写代码更简洁方便。
2.使用 super 访问父类成员
使用 super 访问父类中的成员与 this 关键字的使用相似,只不过它引用的是子类的父类,
基本形式如下:
super.member
其中,member 是父类中的方法或属性名称。这种形式多用于子类的成员名隐藏了父类中的同名成员的情况。
例 1
在 Animal 类和 Cat 类中分别定义了 public 类型的 name 属性和 private 类型的 name 属性,并且 Cat 类继承 Animal 类。那么,我们可以在 Cat 类中通过 super 关键字来访问父类 Animal 中的 name 属性,通过 this 关键字来访问本类中的 name 属性,如下面的代码:
1. //父类Animal的定义
2. public class Animal
3. {
4. public String name; //动物名字
5. }
6. //子类Cat的定义
7. public class Cat extends Animal
8. {
9. private String name; //名字
10. public Cat(String aname,String dname)
11. {
12. super.name=aname; //通过super关键字来访问父类中的name属性
13. this.name=dname; //通过this关键字来访问本类中的name属性
14. }
15. public String toString()
16. {
17. return"我是"+super.name+",我的名字叫"+this.name;
18. }
19. public static void main(String[] args)
20. {
21. Animal cat=new Cat("动物","喵星人");
22. System.out.println(cat);
23. }
24. }
上述代码演示了使用 super 实现子类的成员名隐藏父类中同名成员的情况。尽管 Cat 类中的属性 name 隐藏了 Animal 类中的 name 属性,但是 super 允许访问父类中的 name 属性。另外,super 还可以用于调用被子类隐藏的方法。
运行程序,输出结果如下:
我是动物,我的名字叫喵星人
九、Java的方法重写和方法重载
1.Java的方法重写
在子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖。
在重写方法时,需要遵循下面的规则:
- 参数列表必须完全与被重写的方法参数列表相同,否则不能称其为重写。
- 返回的类型必须与被重写的方法的返回类型相同,否则不能称其为重写。
- 访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)。
- 重写方法一定不能抛出新的检査异常或者比被重写方法声明更加宽泛的检査型异常。例女口,父类的一个方法声明了一个检査异常 IOException,在重写这个方法时就不能抛出 Exception,只能拋出 IOException 的子类异常,可以抛出非检査异常。
例 1
每种动物都有名字和年龄属性,但是喜欢吃的食物是不同的,比如狗喜欢吃骨头、猫喜欢吃鱼等,因此每种动物的介绍方式是不一样的。
下面编写 Java 程序,在父类 Animal 中定义 getInfo() 方法,并在子类 Cat 中重写该方法, 实现猫的介绍方式。父类 Animal 的代码如下:
1. public class Animal
2. {
3. public String name; //名字
4. public int age; //年龄
5. public Animal(String name,int age)
6. {
7. this.name=name;
8. this.age=age;
9. }
10. public String getInfo()
11. {
12. return"我叫"+name+",今年"+age+"岁了。";
13. }
14. }
子类 Cat 的代码如下:
1. public class Cat extends Animal
2. {
3. private String hobby;
4. public Cat(String name,int age,String hobby)
5. {
6. super(name,age);
7. this.hobby=hobby;
8. }
9. public String getInfo()
10. {
11. return"喵!大家好!我叫"+this.name+",我今年"+this.age+"岁了,我爱吃"+hobby+"。";
12. }
13. public static void main(String[] args)
14. {
15. Animal animal=new Cat("小白",2,"鱼");
16. System.out.println(animal.getInfo());
17. }
18. }
如上述代码,在 Animal 类中定义了一个返回值类型为 String、名称为 getInfo() 的方法,而 Cat 类继承自该类,因此 Cat 类同样含有与 Animal 类中相同的 getInfo() 方法。但是我们在 Cat 类中又重新定义了一个 getInfo() 方法,即重写了父类中的 getInfo() 方法。
在 main() 方法中,创建了 Cat 类的对象 animal,并调用了 getInfo() 方法。输出的结果如下:
喵!大家好!我叫小白,我今年2岁了,我爱吃鱼。
如果子类中创建了一个成员变量,而该变量的类型和名称都与父类中的同名成员变量相同,我们则称作变量隐藏。
2.Java的方法重载
方法重载是 Java 多态性的表现。在 Java 语言中,同一个类中的两个或多个方法可以共享同一个名称,只要它们的参数声明不同即可,这种情况被称为方法重载。方法重载有两种情况:普通方法的重载和构造方法的重载。
例如,在 JDK 的 java.io.PrintStream 中定义了十多个同名的 println() 方法。
1. public void printin(int i){…}
2. public void println(double d){…}
3. public void println(String s){…}
这些方法完成的功能类似,都是格式化输出。根据参数的不同来区分它们,以进行不同的格式化处理和输出。它们之间就构成了方法的重载。实际调用时,根据实参的类型来决定调用哪一个方法。例如:
1. System.out.println(102); //调用println(int i)方法
2. System.out.println(102.25); //调用println(double d)方法
3. System.out.println("价格为 102.25"); //调用println(String s)方法
方法重载时必须要注意以下两点:
- 重载方法的参数列表必须和被重载的方法不同,并且这种不同必须足以清楚地确定要调用哪一个方法。
- 重载方法的返回值类型可以和被重载的方法相同,也可以不同,但是只有返回值类型不同不能表示为重载。
例 1
在比较数值时,数值的个数和类型是不固定的,可能是两个 int 类型的数值,也可能是两个 double 类型的数值,或者是两个 double、一个 int 类型的数值;在这种情况下就可以使用方法的重载来实现数值之间的比较功能。具体实现代码如下:
1. public class Overloading
2. {
3. public void max(int a,int b)
4. {
5. //含有两个int类型参数的方法
6. System.out.println(a>b?a:b);
7. }
8. public void max(double a,double b)
9. {
10. //含有两个double类型参数的方法
11. System.out.println(a>b?a:b);
12. }
13. public void max(double a,double b,int c)
14. {
15. //含有两个double类型参数和一个int类型参数的方法
16. double max=(double)(a>b?a:b);
17. System.out.println(c>max?c:max);
18. public static void main(String[] args)
19. {
20. OverLoading ol=new OverLoading();
21. System.out.println("1 与 5 比较,较大的是:");
22. ol.max(1,5);
23. System.out.println("5.205 与 5.8 比较,较大的是:");
24. ol.max(5.205, 5.8);
25. System.out.println("2.15、0.05、58 中,较大的是:");
26. ol.max(2.15,0.05,58);
27. }
28. }
29. }
如上述代码,在 OverLoading 类中定义了 3 个相同名称的方法,其参数列表是不相同的。当运行时,Java 虚拟机会根据传递过来的不同参数来调用不同的方法。运行结果如下:
1 与 5 比较,较大的是:
5
5.205 与 5.8 比较,较大的是:
5.8
2.15、0.05、58 中,较大的是:
58.0
十、Java对象类型转换和强制对象类型转换
Java 语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。如果把引用类型转换为子类类型,则称为向下转型;如果把引用类型转换为父类类型,则称为向上转型。
1.对象类型转换
例如,Creature 类表示生物类,Animal 类表示动物类,该类对应的子类有 Dog 类,使用对象类型表示如下:
1. Animal animal=new Dog();
2. Dogdog=(Dog)animal; //向下转型,把Animal类型转换为Dog类型
3. Creature creature=animal; //向上转型,把Animal类型转换为Creature类型
例 1
下面通过具体的示例演示对象类型的转换。例如,父类 Animal 和子类 Cat 中都定义了实例变量 name、静态变量 staticName、实例方法 eat() 和静态方法 staticEat()。此外,子类 Cat 中还定义了实例变量 str 和实例方法 dogMethod()。
父类 Animal 的代码如下:
1. public class Animal
2. {
3. public String name="Animal:动物";
4. public static String staticName="Animal:可爱的动物";
5. public void eat()
6. {
7. System.out.println("Animal:吃饭");
8. }
9. public static void staticEat()
10. {
11. System.out.println("Animal:动物在吃饭");
12. }
13. }
子类 Cat 的代码如下:
1. public class Cat extends Animal
2. {
3. public String name="Cat:猫";
4. public String str="Cat:可爱的小猫";
5. public static String staticName="Dog:我是喵星人";
6. public void eat()
7. {
8. System.out.println("Cat:吃饭");
9. }
10. public static void staticEat()
11. {
12. System.out.println("Cat:猫在吃饭");
13. }
14. public void eatMethod()
15. {
16. System.out.println("Cat:猫喜欢吃鱼");
17. }
18. public static void main(String[] args)
19. {
20. Animal animal=new Cat();
21. Cat cat=(Cat)animal; //向下转型
22. System.out.println(animal.name); //输出Animal类的name变量
23. System.out.println(animal.staticName); // 输出Animal类的staticName变量
24. animal.eat(); //输出Cat类的eat()方法
25. animal.staticEat(); //输出Animal类的staticEat()方法
26. System.out.println(cat.str); //调用Cat类的str变量
27. cat.eatMethod(); //调用Cat类的eatMethod()方法
28. }
29. }
通过引用类型变量来访问所引用对象的属性和方法时,Java 虚拟机将采用以下绑定规则:
- 实例方法与引用变量实际引用的对象的方法进行绑定,这种绑定属于动态绑定,因为是在运行时由 Java 虚拟机动态决定的。例如,animal.eat() 是将 eat() 方法与 Cat 类绑定。
- 静态方法与引用变量所声明的类型的方法绑定,这种绑定属于静态绑定,因为是在编译阶段已经做了绑定。例如,animal.staticEat() 是将 staticEat() 方法与 Animal 类进行绑定。
- 成员变量(包括静态变量和实例变量)与引用变量所声明的类型的成员变量绑定,这种绑定属于静态绑定,因为在编译阶段已经做了绑定。例如, animal.name 和 animal.staticName 都是与 Animal 类进行绑定。
对于 Cat 类,运行时将会输出如下结果:
Animal:动物
Animal:可爱的动物
Cat:吃饭
Animal:动物在吃饭
Cat:可爱的小猫
Cat:猫喜欢吃鱼
2.强制对象类型转换
Java 编译器允许在具有直接或间接继承关系的类之间进行类型转换。对于向下转型,必须进行强制类型转换;对于向上转型,不必使用强制类型转换。
例如,对于一个引用类型的变量,Java 编译器按照它声明的类型来处理。如果使用 animal 调用 str 和 eatMethod() 方法将会出错,如下:
1. animal.str=""; //编译出错,提示Animal类中没有str属性
2. animal.eatMethod(); //编译出错,提示Animal类中没有eatMethod()方法
如果要访问 Cat 类的成员,必须通过强制类型转换,如下:
1. ((Cat)animal).str=""; //编译成功
2. ((Cat)animal).eatMethod(); //编译成功
把 Animal 对象类型强制转换为 Cat 对象类型,这时上面两句编译成功。对于如下语句,由于使用了强制类型转换,所以也会编译成功,例如:
1. Cat cat=(Cat)animal; //编译成功,将Animal对象类型强制转换为Cat对象类型
子类的对象可以转换成父类类型,而父类的对象实际上无法转换为子类类型。因为通俗地讲,父类拥有的成员子类肯定也有,而子类拥有的成员,父类不一定有。因此,对于向上转型,不必使用强制类型转换。例如:
1. Cat cat=new Cat();
2. Animal animal=cat; //向上转型,不必使用强制类型转换
如果两种类型之间没有继承关系,那么将不允许进行类型转换。例如:
1. Dog dog=new Dog();
2. Cat cat=(Cat)dog; //编译出错,不允许把Dog对象类型转换为Cat对象类型
总结
各位小主,如果感觉有一点点帮助到你的话,麻烦留下赞赞一枚哦~
版权归原作者 秋之叶落殇 所有, 如有侵权,请联系我们删除。