Java面向对象(下)
本文介绍Java的继承( Inheritance])、封装(Encapsulation)、多态(Polymorphism)。
封装
封装把一个对象的部分成员变量私有化,隐藏类的细节,同时提供--些可以被外界访问的成员变量的方法,如果成员变量不想被外界访问,我们大可不必提供方法给外界访问。
封装的目的是:增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员。
封装的基本要求是:把所有的成员变量私有化,对每个成员变量提供getter和setter方法,如果有一个带参的构造函数的话,那一定要写一个不带参的构造函数。在开发的时候经常要对已经编写的类进行测试,所以在有的时候还有重写toString方法,但这不是必须的。
继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。被继承的类称为“基类”、“父类”或“超类”;通过继承创建的新类称为“子类”或“派生类”。
构造方法、私有成员变量和方法、静态成员变量和方法不能被继承。
1.子类拥有父类非private 的成员变量和方法。
2.子类可以拥有自己成员变量和方法,即子类可以对父类进行扩展。
3.子类可以用自己的方式实现父类的方法。
多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
Java实现多态有三个必要条件:
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
多态的实现方式:
(1)基于继承实现的多态
基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。
(2)基于接口实现的多态
继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。
在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,
能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。
Jave类的继承和多态思维导图
封装
简单地说,Java的封装就是在设计类时用好访问修饰符(public、protected、private)控制方法、属性等成员的访问权限。
在设计类时,我们需要根据需求合理地使用这些访问修饰符,将类的一些成员标记为private,即私有成员,以控制其访问权限,防止外部对象直接访问,从而保证数据的安全性。同时,类也可以提供公开的方法(例如getter和setter方法),以供外部对象访问和修改私有成员,从而实现对外暴露必要的接口。这里的“接口”【注】指的是类对外暴露的方法和属性,包括公共方法和属性等。
【注:接口(interface)有多种含义
1.在编程中,将类对外暴露的公共方法和属性等成员称为接口(interface),目的是使其能被内部修改而不影响外界其他实体与其交互沟通的方式;
2.在编程中,只包含方法声明但没有具体实现的接口,需要使用关键字"interface"来定义;
3.计算机网络中,用于描述不同系统之间的连接点或共享的边界,例如网络接口、API接口等。】
封装的特点:
只能通过规定的方法访问数据。
隐藏类的实例细节,方便修改和实现。
Java封装类实现途径:
(1)修改成员变量的可见性来限制对成员变量的访问,一般设为 private。
(2)为每个成员变量创建一对赋值和取值 (setter & getter)方法,一般设为 public,用于成员变量的读写。
(3)在赋值和取值方法中,加入成员变量控制语句(对成员变量值的合法性进行判断)。如在读取成员变量的方法中,添加对成员变量读取的限制。
在此需要请回顾一下,Java 支持访问权限修饰符有4 种:default (即默认,什么也不写)、private 、public、protected。在前文中已介绍,在此不多说了。
下面给出一个简单示例。封装之前:成员变量都可以直接访问和修改
class Person{
String name;
int age;
}
在此例中,其成员的访问权限修饰符是default (即缺省、默认,什么也不写),因此,其成员变量都可以直接访问和修改。
封装之后:
class Person{
//成员变量私有化,使得其他对象不能直接访问
private String name;
private int age;
//参数及方法内定义的变量是局部变量
public void setName(String name){
this.name = name;}
public String getName(){
return name;}
}
示例
下面示例,以一个员工类的封装为例介绍封装过程。包括两个文件:员工mployee.java文件和测试用EmployeeTest.java文件
Employee.java文件包含员工类Employee主要成员变量有姓名、年龄、联系电话和家庭住址。代码如下:
//Employee类
public class Employee {
private String name; // 姓名
private int age; // 年龄
private String phone; // 联系电话
private String address; // 家庭住址
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 对年龄进行限制
if (age < 18 || age > 40) {
System.out.println("年龄必须在18到40之间!");
this.age = 20; // 默认年龄
} else {
this.age = age;
}
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
其中,如代码所示,使用 private 关键字修饰成员变量,这就意味着除了 Employee 类本身外,其他任何类都不可以访问这些成员变量。但是,可以通过这些成员变量的 setXxx() 方法来对其进行赋值,通过 getXxx() 方法来访问这些成员变量。
其中 setAge() 方法,首先对用户传递过来的参数 age 进行判断,如果 age 的值不在 18 到 40 之间,则将 Employee 类的 age 成员变量值设置为 20,否则为传递过来的参数值。
EmployeeTest.java文件包含测试类 EmployeeTest,代码如下:
//EmployeeTest类
public class EmployeeTest {
public static void main(String args[]){
Employee people = new Employee();
people.setName("李明");
people.setAge(35);
people.setPhone("13612345678");
people.setAddress("某某省某某市");
System.out.println("姓名:" + people.getName());
System.out.println("年龄:" + people.getAge());
System.out.println("电话:" + people.getPhone());
System.out.println("家庭住址:" + people.getAddress());
}
}
在该类的 main() 方法中调用 Employee 类的setXxx() 方法对其相应的成员变量进行赋值,并调用 getXxx() 方法访问成员变量。
运行该示例,输出结果如下:
姓名:李明
年龄:35
电话:13612345678
家庭住址:某某省某某市
继承
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,类的继承格式:
class 父类名称 {
}
class 子类名称 extends 父类名称 {
}
父类名称也称为基类名称,Java 的继承通过 extends 关键字来实现。
【Java 与 C++ 定义继承类的方式十分相似。Java 用关键字 extends 代替了 C++ 中的冒号(:)。在 Java 中,所有的继承都是公有继承, 而没有 C++ 中的私有继承和保护继承。】
类的继承不改变类成员的访问权限,也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性,并且子类不能获得父类的构造方法。
使用继承的注意:子类一般比父类包含更多的成员变量和方法;父类中的 private 成员在子类中是不可见的,因此在子类中不能直接使用它们。
继承可以理解为一个类从另一个类获取方法和成员变量的过程。如果类B继承于类A,那么B就拥有A的方法和成员变量。
在Java中,父类和继承父类的子类可以保存在同一个文件中,也可以分别保存成不同的文件。
通常情况下,我们建议将每个类保存到单独的文件中,这样可以使代码更易于阅读和维护。具体来说,我们会将父类定义到一个文件中,并且将其所有的子类都定义到另外的文件中。
但是,在小型的应用程序中,将多个类定义在同一个文件中可能会更加方便,因为它可以减少文件数量,使代码更具有紧凑性。所以,根据需要和实际情况,可以灵活选择是否将父类和子类保存到同一个文件中。
当你在同一个源文件中定义一个父类和其继承子类时,需要注意以下几点:
父类必须先于其子类进行定义;
子类的定义需要使用关键字"extends"来指明其继承自哪个父类;
如果父类和子类都是public类,则源文件的名称必须与父类的名称相同。
假设我们已经定义了一个类 People:
class People{
String name;
int age;
int height;
void say(){
System.out.println("我的名字是 " + name + ",年龄是 " + age + ",身高是 " + height);
}
}
现在需要定义一个类 Teacher,它也有 name、age、height 成员变量和 say() 方法,另外还需要增加 school、seniority、subject 成员变量和 lecturing() 方法,怎么办呢?我们要重新定义一个类吗?
完全没必要,可以先继承 People 类的成员,再增加自己的成员即可,例如:
class Teacher extends People{
String school; // 所在学校
String subject; // 学科
int seniority; // 教龄
// 覆盖 People 类中的 say() 方法
void say(){
System.out.println("我叫" + name + ",在" + school + "教" + subject + ",有" + seniority + "年教龄");
}
void lecturing(){
System.out.println("我已经" + age + "岁了,依然站在讲台上讲课");
}
}
说明
name 和 age 变量虽然没有在 Teacher 中定义,但是已在 People 中定义,可以直接拿来用。
Teacher 是 People 的子类,People 是Teacher 类的父类。
子类可以覆盖父类的方法。
子类可以继承父类除private以为的所有的成员。
构造方法不能被继承。
对上面的代码进行整理为完整的代码如下:
public class DemoA {
public static void main(String[] args) {
Teacher t = new Teacher();
t.name = "汤姆";
t.age = 60;
t.school = "某某学校";
t.subject = "Java程序设计";
t.seniority = 12;
t.say();
t.lecturing();
}
}
class People{
String name;
int age;
int height;
void say(){
System.out.println("我的名字是 " + name + ",年龄是 " + age + ",身高是 " + height);
}
}
class Teacher extends People{
String school; // 所在学校
String subject; // 学科
int seniority; // 教龄
// 覆盖 People 类中的 say() 方法
void say(){
System.out.println("我叫" + name + ",在" + school + "教" + subject + ",有" + seniority + "年教龄");
}
void lecturing(){
System.out.println("我已经" + age + "岁了,依然站在讲台上讲课");
}
}
运行结果如下:
我叫汤姆,在某某学校教Java程序设计,有12年教龄
我已经60岁了,依然站在讲台上讲课
Java类的单继承性:Java 允许一个类仅能继承一个其它类,即一个类只能有一个父类,这个限制被称做单继承性。但接口(interface)允许多继承。
一个类只能有一个直接父类,但是它可以有多个间接的父类。例如:
从图 1 中可以看出,三角形、四边形和五边形的直接父类是多边形类,它们的间接父类是图形类。图形类、多边形类和三角形、四边形、五边形类形成了一个继承的分支。在这个分支上,位于下层的子类会继承上层所有直接或间接父类的成员变量和方法。如果两个类不在同一个继承树分支上,就不会存在继承关系,例如多边形类和直线。
如果定义一个 Java 类时并未显式指定这个类的直接父类,则这个类默认继承 java.lang.Object 类。因此,java.lang.Object 类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的 Java 对象都可调用 java.lang.Object 类所定义的实例方法。
在继承中需要注意的几种情况:
☆Java成员变量的隐藏(成员变量的覆盖)
当子类中声明的成员变量名字和从父类继承过来的成员变量名字相同时,子类就会使用自己声明的成员变量,隐藏从父类继承过来的成员变量。
Java成员变量的隐藏特点:
1)子类对象和子类自己定义的方法,在操作与父类同名的成员变量时,使用的是子类声明的成员变量,而不是父类的。例如:
本例有两个类文件:父文件variableHidden.java代码如下:
//父类
public class variableHidden {
//父类成员变量
double number = 456.456 ;
}
子类文件variableHidden_Test.java代码如下:
//子类
public class variableHidden_Test extends variableHidden{
double number = 11.11;
//方法中操作 number 成员变量,此时使用的number是子类自己声明的number
void Operation(int x){
number += x;
System.out.println("成员变量number的值:"+number);
}
public static void main(String[] args) {
//子类对象操作成员变量number
variableHidden_Test vht = new variableHidden_Test();
vht.number = 111.111;
System.out.println("子类对象操作成员变量number的值:"+vht.number);
//子类对象操作方法
vht.Operation(100);
}
}
输出如下:
子类对象操作成员变量number的值:111.111
成员变量number的值:211.111
2)子类继承父类的方法中,所操作的成员变量一定是被子类继承或隐藏的成员变量,而不是子类自己声明的成员变量。例如:
本例有两个类文件:父文件variableHidden.java代码如下:
定义两个类文件:父文件variableHidden2.java代码如下:
//父类
public class variableHidden2 {
//父类成员变量
double number = 456.456 ;
//父类的方法,方法中操作自己的成员变量
void Fa(){
System.out.println("父类方法,操作自己的成员变量number:"+number);
}
}
子类文件variableHidden_Test2.java代码如下:
//子类
public class variableHidden_Test2 extends variableHidden2{
//对父类的成员变量进行隐藏
double number = 11.11;
//方法中操作 number 成员变量
void Operation(int x){
number += x;
System.out.println("成员变量number的值:"+number);
}
public static void main(String[] args) {
//子类对象操作成员变量number
variableHidden_Test2 vht = new variableHidden_Test2();
vht.number = 111.111;
System.out.println("子类对象操作成员变量number的值:"+vht.number);
//子类对象操作自己的方法
vht.Operation(100);
//子类对象操作父类的方法,方法中操作的是父类的成员变量
vht.Fa();
}
}
输出如下:
子类对象操作成员变量number的值:111.111
成员变量number的值:211.111
父类方法,操作自己的成员变量number:456.456
☆Java方法的重写(方法的覆盖)和方法的重载
方法的重写和重载是体现继承特性的重要方面,理解了方法的重写和重载,可以为以后学习多态打下基础。在子类本身的方法中,如果有和所继承的方法具有相同的名称,便构成了方法的重写。重写的特点就是能够为各个子类定义所特有的行为。方法的重载就是指在一个类中,存在两个或者两个以上的具有相同名称,不同参数的方法。这样做就可以让程序员不必为同一个操作的不同变体而设置多个不同的方法名称。
先介绍方法的重写
Java方法重写:
1、子类通过重写可以隐藏从父类继承过来的方法,方法重写(override)也称为方法覆盖。
2、子类一旦重写了父类的方法,那父类被重写的方法就会被隐藏;子类通过方法的重写可以把父类的状态和行为改变为自身的状态和行为。
3、子类在重写父类的方法时,不允许降低方法的访问权限,但可以提高方法的访问权限。
方法重写的语法规则:
1、子类中定义一个方法,这个方法的类型和父类的方法的类型是一致的,
2、而且方法的名字,参数个数,参数类型也要和父类的方法一致;
3、只有方法体是可以不一致的;
例如
定义两个类文件:父文件methodRewrite.java代码如下:
//父类methodRewrite
public class methodRewrite {
//定义一个父类方法
void Method(int x,int y){
int sum = x * y ;
System.out.println("我是父类的方法,输出x * y = "+sum);
}
}
子类文件子文件methodRewrite_Test.java代码如下:
//子类methodRewrite_Test
public class methodRewrite_Test extends methodRewrite {
//对继承的父类方法进行重写
void Method(int x,int y){
System.out.println("我重写了父类的Method方法");
}
public static void main(String[] args) {
methodRewrite_Test mrt = new methodRewrite_Test();
mrt.Method(11,111);
}
}
输出如下:
我重写了父类的Method方法
super关键字
重写方法时既可以操作继承的成员变量,调用继承的方法,也可以操作子类新声明的成员变量,调用新定义的其他方法,但无法使用被子类隐藏的成员变量和方法;当子类想要使用被隐藏的方法或成员变量时,怎么办?必须使用关键字 super。
例如,定义两个类文件:父文件methodRewrite2.java代码如下:
//父类methodRewrite2
public class methodRewrite2{
//创建父类成员变量 和 方法
int numberFa = 11;
void methodFa(int x,int y){
int sum = x+y;
System.out.println("我是父类的方法,计算 x+y ="+sum);
}
//被子类重写方法
void Fa(int x,int y,int z){
int muti = x*y*z;
System.out.println("我是父类的方法,计算 x*y*z ="+muti);
}
//被子类隐藏的成员变量
int Fs = 100 ;
}
子文件methodRewrite_Test2.java代码如下:
//子类methodRewrite_Test2
public class methodRewrite_Test2 extends methodRewrite2{
//子类新声明的成员变量和方法
int numberCh = 111 ;
void methodCh(double x,double y){
double div = x/y;
System.out.println("我是子类的方法,计算 x/y ="+div);
}
//隐藏父类的成员变量Fs
int Fs = 159 ;
//方法重写,在方法中操作继承的成员变量,调用继承的方法,操作自己新声明的成员变量和方法
void Fa(int x,int y,int z){
//操作继承的成员变量,调用继承的方法
numberFa = 789;
System.out.println("numberFa= "+numberFa);
methodFa(111,111);
//操作自己新声明的成员变量和方法
numberCh = 456 ;
System.out.println("numberCh= "+numberCh);
methodCh(888,222);
//但无法使用被子类隐藏的成员变量和方法;
Fs = 77 ; //此时的Fs是子类自己声明的成员变量,而父类的成员变量Fs已经被隐藏了
System.out.println("Fs= "+Fs);
//使用关键字 super 调用被子类隐藏的成员变量
System.out.println("super.Fs= "+super.Fs);
int sub = x-y-z;
System.out.println("我重写了父类的方法,计算 x-y-z ="+sub);
}
public static void main(String[] args) {
//子类对象
methodRewrite_Test2 mrt = new methodRewrite_Test2();
//调用继承父类的成员变量和方法;
System.out.println("我调用了父类的成员变量numberFa:"+mrt.numberFa);
mrt.methodFa(123,1234);
//调用子类自己的成员变量和方法;
System.out.println("子类自己的成员变量numberCh:"+mrt.numberCh);
mrt.methodCh(100,2);
//调用子类重写的方法
mrt.Fa(100,10,10);
}
}
输出如下:
我调用了父类的成员变量numberFa:11
我是父类的方法,计算 x+y =1357
子类自己的成员变量numberCh:111
我是子类的方法,计算 x/y =50.0
numberFa= 789
我是父类的方法,计算 x+y =222
numberCh= 456
我是子类的方法,计算 x/y =4.0
Fs= 77
super.Fs= 100
我重写了父类的方法,计算 x-y-z =80
super 关键字的功能:
1)访问父类的成员方法和变量。种情况见前例。
2)在子类的构造方法中显式的调用父类构造方法。
下面介绍使用super调用父类构造方法
super 关键字可以在子类的构造方法中显式地调用父类的构造方法,基本格式如下:
super(parameter-list);
其中,parameter-list 指定了父类构造方法中的所有参数。super( ) 必须是在子类构造方法的方法体的第一行。如:
public class apple extends fruits
{
public apple(int price)
{
super(price);
super.var = value;
super.method(paraList);
}
}
下面给出一个比较完整的例子,包含两个类文件:父文件superKeyword.java代码如下:
//父类superKeyword
public class superKeyword {
//父类的成员变量
int numberFa;
//无参数构造方法
superKeyword(){
System.out.println("系统默认调用了父类无参数构造方法 ");
}
superKeyword(int numberFa){
this.numberFa = numberFa;
System.out.println("父类的手机号码: "+numberFa);
}
}
子类文件子文件superKeyword_Test .java代码如下:
//子类superKeyword_Test
public class superKeyword_Test extends superKeyword {
//成员变量
int age,number;
double Fraction;
//没有super();的情况时,系统默认该方法有 super(),此时就调用了父类没有参数的构造方法
superKeyword_Test(int age,int number){
this.age = age;
this.number = number;
System.out.println("子类的年龄:"+age+" 子类的手机号码: "+number+"\n");
}
//有super();的情况时,系统就调用父类相应参数的构造方法
superKeyword_Test(double Fraction, int number) {
super(117330111);
this.Fraction = Fraction;
this.number = number;
System.out.println("子类的分数:"+Fraction+" 子类的手机号码: "+number);
}
public static void main(String[] args){
//调用没有super()的构造方法
superKeyword_Test skt = new superKeyword_Test(18,117120);
//调用有super()的构造方法
superKeyword_Test skt1 = new superKeyword_Test(111.11,117110);
}
}
输出结果:
系统默认调用了父类无参数构造方法
子类的年龄:18 子类的手机号码: 117120
父类的手机号码: 117330111
子类的分数:111.11 子类的手机号码: 117110
super 关键字的用法小结如下:
super.父类成员变量名:调用父类中的成员变量
super.父类方法名:调用父类中的方法
super():调用父类的无参构造方法
super(参数):调用父类的有参构造方法。
在重写方法时,需要遵循下面的规则:
☆参数列表必须完全与被重写的方法参数列表相同。
☆返回的类型必须与被重写的方法的返回类型相同(Java1.5 版本之前返回值类型必须一样,之后的 Java 版本放宽了限制,返回值类型必须小于或者等于父类方法的返回值类型)。
☆访问权限不能比父类中被重写方法的访问权限更低(public>protected>default>private)。
☆重写方法一定不能抛出新的检査异常或者比被重写方法声明更加宽泛的检査型异常。例如,父类的一个方法声明了一个检査异常 IOException,在重写这个方法时就不能抛出 Exception,只能拋出 IOException 的子类异常,可以抛出非检査异常。
☆父类的成员方法只能被它的子类重写。
☆声明为 final 的方法不能被重写。
☆声明为 static 的方法不能被重写,但是能够再次声明。静态方法就是被修饰为了static类型的方法,在类里声明具有唯一性,不是通过类的实例化而存在的,而是通过类的建立而存在,可以理解为用关键字new创建对象了,就是把这个对象实例化了。Java中的静态方法可以被继承,但是不可以被重写。
☆构造方法不能被重写。
☆子类和父类在同一个包中时,子类可以重写父类的所有方法,除了声明为 private 和 final 的方法。
☆子类和父类不在同一个包中时,子类只能重写父类的声明为 public 和 protected 的非 final 方法。
☆如果不能继承一个方法,则不能重写这个方法。
☆另外,重写的方法可以使用 @Override 注解来标识。
方法的重载
方法重载(overload)指一个类中可以有多个方法具有相同的名字,但是这些方法的参数必须不同;参数不同指:
(1)、参数的个数不同 ;
(2)、参数个数相同,但是对于的某个参数的类型不同 ;
例如:
void test ( int x ){ }
void test ( int x,int y ){ }
void test (double x,int y ){ }
方法重载的要求是两同一不同:同一个类中方法名相同,参数列表不同。至于方法的其他部分,如方法返回值类型、修饰符等,与方法重载没有任何关系。
使用方法重载其实就是避免出现繁多的方法名,有些方法的功能是相似的,如果重新建立一个方法,重新取个方法名称,会降低程序可读性。
例如:
在比较数值时,数值的个数和类型是不固定的,可能是两个 int 类型的数值,也可能是两个 double 类型的数值,或者是两个 double、一个 int 类型的数值;在这种情况下就可以使用方法的重载来实现数值之间的比较功能。代码如下:
public class OverLoading {
public void max(int a, int b) {
// 含有两个int类型参数的方法
System.out.println(a > b ? a : b);
}
public void max(double a, double b) {
// 含有两个double类型参数的方法
System.out.println(a > b ? a : b);
}
public void max(double a, double b, int c) {
// 含有两个double类型参数和一个int类型参数的方法
double max = (double) (a > b ? a : b);
System.out.println(c > max ? c : max);
}
public static void main(String[] args) {
OverLoading ol = new OverLoading();
System.out.println("1 与 5 比较,较大的是:");
ol.max(1, 5);
System.out.println("5.205 与 5.8 比较,较大的是:");
ol.max(5.205, 5.8);
System.out.println("2.15、0.05、58 中,较大的是:");
ol.max(2.15, 0.05, 58);
}
}
虽然 3 个 max() 方法的方法名相同,但因为它们的形参列表不同,所以系统可以正常区分出这 3 个方法。当运行时,Java 虚拟机会根据传递过来的不同参数来调用不同的方法。运行结果如下:
1 与 5 比较,较大的是:
5
5.205 与 5.8 比较,较大的是:
5.8
2.15、0.05、58 中,较大的是:
58.0
final关键字
final关键字有最终、不变的意思,可以修饰成员变量,也可以修饰方法和类,而且会对类的继承产生很大的影响。通过final关键字的修饰可以改变其特性。
有三种使用方法
final在类之前,表示该类不能被继承。
final在方法之前,防止该方法被覆盖。
final在变量之前,定义一个常量。
通过extends关键字可以实现类的继承。但在实际应用中,出于某种考虑,当创建一个类时,希望该类永不需要做任何变动,或者出于安全因素,不希望它有任何子类,这时可以使用final关键字。在类的定义时,使用final修饰符,意味着这个类不能再作为父类派生出其他的子类,这样的类通常称为最终类。
如:
用final修饰符定义的常量不能修改
例、
public class ConstantTest{
public static void main(String[] args){
final int i1 = 10;
//下句提示出错
i1=i1+1;
System.out.println(i1);
int i2 = 10;
//下句可以
i2=i2+1;
System.out.println(i2);
}
}
用final修饰符定义最终方法,该方法不能被覆盖
class A{
final void a(){
……
}
……
}
用final修饰的类,该类不能被继承。
abstract关键字
abstract关键字是表示抽象的意思。
在Java里,抽象类不能被实例化;抽象类里最少要含有一个抽象方法,让它的子类去实现这个抽象方法,抽象类里也可以定义一些方法。
下例父类Bike被修饰为abstract,因此它是一个抽象类,其子类RacingCycleA可以实现抽象类的抽象方法。
//bike类所描述的是一个自行车,被申明为abstract抽象的
abstract class bike
{
public String name = "抽象类的成员变量";
public String getMessage()
{
//返回bile类里的成员变量name的值
return name;
}
//注意这个public类型的方法getMes(),被修饰为了abstract
abstract public String getMes();
}
//racing_cycle类所描述的是公路赛车,继承与bike类,bike类是抽象了,并实现了相应的抽象方法
public class racingCycleA extends bike
{
//实现了父类bike的抽象方法getMes()
public String getMes()
{
return getMessage();
}
public static void main(String args[])
{
racingCycleA rc = new racingCycleA();
//打印并显示运行结果
System.out.println(rc.getMes());
}
}
运行输出结果是:
抽象类的成员变量
多态
多态性是面向对象编程的一个重要特征,它是指在父类中定义的成员变量和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个成员变量或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
下面通过一个例子来演示重写如何实现多态性。
1)创建 Figure 类,在该类中首先定义存储二维对象的尺寸,然后定义有两个参数的构造方法,最后添加 area() 方法,该方法计算对象的面积。代码如下:
// Figure类
public class Figure {
double dim1;
double dim2;
Figure(double d1, double d2) {
// 有参的构造方法
this.dim1 = d1;
this.dim2 = d2;
}
double area() {
// 用于计算对象的面积
System.out.println("父类中计算对象面积的方法,没有实际意义,需要在子类中重写。");
return 0;
}
}
2)创建继承自 Figure 类的 Rectangle 子类,该类调用父类的构造方法,并且重写父类中的 area() 方法。代码如下:
// Rectangle子类
public class Rectangle extends Figure {
Rectangle(double d1, double d2) {
super(d1, d2);
}
double area() {
System.out.println("长方形的面积:");
return super.dim1 * super.dim2;
}
}
3)创建继承自 Figure 类的 Triangle 子类,该类与 Rectangle 相似。代码如下:
// Triangle子类
public class Triangle extends Figure {
Triangle(double d1, double d2) {
super(d1, d2);
}
double area() {
System.out.println("三角形的面积:");
return super.dim1 * super.dim2 / 2;
}
}
4)创建 RunTest 运行测试类,在该类的 main() 方法中首先声明 Figure 类的变量 figure,然后分别为 figure 变量指定不同的对象,并调用这些对象的 area() 方法。代码如下:
//RunTest运行测试类
public class RunTest {
public static void main(String[] args) {
Figure figure; // 声明Figure类的变量
figure = new Rectangle(9, 9);
System.out.println(figure.area());
System.out.println("===============================");
figure = new Triangle(6, 8);
System.out.println(figure.area());
System.out.println("===============================");
figure = new Figure(10, 10);
System.out.println(figure.area());
}
}
从上述代码可以发现,无论 figure 变量的对象是 Rectangle 还是 Triangle,它们都是 Figure 类的子类,因此可以向上转型为该类,从而实现多态。输出结果如下:
长方形的面积:
81.0
===============================
三角形的面积:
24.0
===============================
父类中计算对象面积的方法,没有实际意义,需要在子类中重写。
0.0
面向对象设计的几个基本原则
这些原则是在许多设计中总结出的指导性原则,并不是任何设计都必须要遵守的硬性规定。
☆在设计模式中,使用简单的 UML类图可以简洁地表达一个模式中类之间的关系。
☆面向抽象原则的核心思想是:在设计一个类时,不让该类面向具体的类,而是面向抽象类或接口
☆开-闭原则的本质是指当一个设计中增加新的模块时,不需要修改现有的模块。
☆“多用组合、少用继承”原则的目的是减少类之间的强耦合关系。
☆“高内聚-低耦合”原则的目的是尽量不要让一个类含有太多的其他类的实例的引用,便于类的维护。
关于这些,在此仅提及,想深入了解请参考其他文献。