第7章 面向对象进阶
本章读者可以学到如下实例:
- 实例059 经理与员工的差异
- 实例060 重写父类中的方法
- 实例061 计算几何图形的面积
- 实例062 简单的汽车销售商场
- 实例063 使用Comparable接口自定义排序
- 实例064 策略模式的简单应用
- 实例065 适配器模式的简单应用
- 实例066 普通内部类的简单应用
- 实例067 局部内部类的简单应用
- 实例068 匿名内部类的简单应用
- 实例069 静态内部类的简单应用
- 实例070 实例化Class类的几种方式
- 实例071 查看类的声明
- 实例072 查看类的成员
- 实例073 查看内部类信息
- 实例074 动态设置类的私有域
- 实例075 动态调用类中方法
- 实例076 动态实例化类
- 实例077 创建长度可变的数组
- 实例078 利用反射重写toString()方法
实例059 经理与员工的差异
实例说明
对于在同一家公司工作的经理和员工而言,两者是有很多共同点的。例如每个月都要发工资,但是经理在完成目标任务后,还会获得奖金。此时,利用员工类来编写经理类就会少写很多代码,利用继承技术可以让经理类使用员王类中定义的属性和方法。本实例将通过继承演示经理与员工的差异。实例的运行效果如图7.1所示。
实现过程
Employee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.mingrisoft;
import java.util.Date;
public class Employee { private String name; private double salary; private Date birthday;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
public Date getBirthday() { return birthday; }
public void setBirthday(Date birthday) { this.birthday = birthday; }
}
|
Manager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.mingrisoft;
public class Manager extends Employee { private double bonus;
public double getBonus() { return bonus; }
public void setBonus(double bonus) { this.bonus = bonus; } }
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.mingrisoft;
import java.util.Date;
public class Test { public static void main(String[] args) { Employee employee = new Employee(); employee.setName("Java"); employee.setSalary(100); employee.setBirthday(new Date()); System.out.println("员工的姓名:" + employee.getName()); System.out.println("员工的工资:" + employee.getSalary()); System.out.println("员工的生日:" + employee.getBirthday()); Manager manager = new Manager(); manager.setName("HelloWorld"); manager.setSalary(3000); manager.setBirthday(new Date()); manager.setBonus(2000); System.out.println("经理的姓名:" + manager.getName()); System.out.println("经理的工资:" + manager.getSalary()); System.out.println("经理的生日:" + manager.getBirthday()); System.out.println("经理的奖金:" + manager.getBonus()); } }
|
运行效果
1 2 3 4 5 6 7
| 员工的姓名:Java 员工的工资:100.0 员工的生日:Sun Sep 20 15:49:47 CST 2020 经理的姓名:HelloWorld 经理的工资:3000.0 经理的生日:Sun Sep 20 15:49:47 CST 2020 经理的奖金:2000.0
|
可以看到,经理类通过继承得到了员工类的name,salary,birthday
这三个属性。
指点迷津
在Manager
类中并未定义姓名等域,然而却可以使用,这就是继承的好处。
脚下留神
虽然使用继承能少写很多代码,但是不要滥用继承。在使用继承前,需要考虑一下两者之间是否真的是“is-a
”的关系,这是继承的重要特征。本实例中,经理显然是员工,所以可以用继承。另外,子类也可以成为其他类的父类,这样就构成了一棵继承树
技术要点
在面向对象程序设计中,继承是其基本特性之一。在Java
中如果想表明类A继承了类B,可以使用下面的语法定义类A:
1 2 3
| public class A extends B{ }
|
类A称为子类、派生类或孩子类,
类B称为超类、基类或父类。
尽管类B是一个超类,但是并不意味着类B比类A有更多的功能。相反,类A比类B拥有的功能更加丰富。
指点迷津
在继承树中,从下往上越来越抽象,从上往下越来越具体。
实例060 重写父类中的方法
实例说明
在继承了一个类之后,也就可以使用父类中定义的方法。然而,父类中的方法可能并不完全适用于子类。此时,如果不想定义新的方法,则可以重写父类中的方法。本实例将演示如何重写父类中的方法,实例的运行效果如图72所示。
实现过程
Employee.java
1 2 3 4 5 6 7
| package com.mingrisoft; public class Employee { public String getInfo() { return "父类:我是 牛逼上天公司 的员工!"; } }
|
Manager.java
1 2 3 4 5 6 7 8
| package com.mingrisoft; public class Manager extends Employee { @Override public String getInfo() { return "子类:我是 牛逼上天公司 的经理!"; } }
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.mingrisoft;
public class Test { public static void main(String[] args) { Employee employee = new Employee(); System.out.println(employee.getInfo()); Manager manager = new Manager(); System.out.println(manager.getInfo()); } }
|
运行效果
1 2
| 父类:我是 牛逼上天公司 的员工! 子类:我是 牛逼上天公司 的经理!
|
指点迷津
在JavaSE5.0
版中新增了注解功能,@Override
是最常用的注解之一。**@Override
注解只能应用在方法上,可以测试该方法是否重写了父类中的方法,如果没有则会在编译时报错。使用该注解可以很妤地避免重写时发生的各种问题,因此推荐读者使用**。
技术要点
本实例主要应用的是方法的重写。方法的重写(Overriding
)只能发生在存在继承关系的类中。
重写方法需要注意以下几点:
- 重写方法与原来方法签名要相同,即方法名称和参数(包括顺序)要相同。
- 重写方法的可见性不能小于原来的方法
- 重写方法抛出异常的范围不能大于原来方法抛出异常的范围
实例061 计算几何图形的面积
实例说明
对于每个几何图形而言,都有一些共同的属性,如名字和面积等,而其计算面积的方法却各不相同。为了简化开发,本实例将定义一个超类来实现输出名字的方法,并使用抽象方法来计算面积。实例的运行效果如图7.3所示。
实现过程
1 2 3 4 5 6 7 8 9
| package com.mingrisoft; public abstract class Shape { public String getName() { return this.getClass().getSimpleName(); } public abstract double getArea(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.mingrisoft; public class Rectangle extends Shape { private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double getArea() { return length * width; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.mingrisoft; public class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double getArea() { return Math.PI * Math.pow(radius, 2); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.mingrisoft;
public class Test { public static void main(String[] args) { Shape shape = null; Circle circle = new Circle(1); shape = circle; System.out.print("图形的名称是:" + shape.getName()); System.out.println("\t图形的面积是:" + shape.getArea()); Rectangle rectangle = new Rectangle(1, 1); shape = rectangle; System.out.print("图形的名称是:" + shape.getName()); System.out.println("\t图形的面积是:" + shape.getArea()); } }
|
运行效果
1 2
| 图形的名称是:Circle 图形的面积是:3.141592653589793 图形的名称是:Rectangle 图形的面积是:1.0
|
总结
指点迷津
在抽象类中,可以定义抽象方法(使用abstract
修饰的方法),也可以定义普通方法。
- 包含抽象方法的类必须是抽象类,
- 而抽象类不是必须包含抽象方法。
对于抽象方法而言,仅定义一个声明即可,即抽象方法是没有方法体的。
技术要点
本实例应用的主要技术就是抽象类。下面对抽象类进行介绍。在设计类的过程中,通常会将一些类所具有的公共属性和方法移到超类中,这样就不必重复定义了。然而这些类的超类却经常没有实际意义。通常将它设置成抽象的,这样可以避免创建该类的对象。声明一个最简单的抽象类的代码如下:
1
| public abstract class Shape{}
|
脚下留神
抽象类是不能直接实例化的,如果要获得该类的实例可以使用静态方法创建其实现类对象.
实例062 简单的汽车销售商场
实例说明
当顾客在商场购物时,卖家需要根据顾客的需求提取商品。对于汽车销售商场也是如此。用户需要先指定购买的车型,然后商家去提取该车型的汽车。本实例将实现一个简单的汽车销售商场,用来演示多态的用法。实例的运行效果如图74所示。
实现过程
(2)在com.mingrisoft
包中新建一个抽象类,名称为Car
,在该类中定义一个抽象方法getInfo
关键代码如下:
1 2 3 4 5
| package com.mingrisoft; public abstract class Car { public abstract String getInfo(); }
|
(3)在com.mingrisoft
包中再创建一个名称为BMW
的类,该类继承自Car
并实现其getInfo()
方法。关键代码如下
1 2 3 4 5 6 7 8
| package com.mingrisoft; public class BMW extends Car { @Override public String getInfo() { return "BMW"; } }
|
(4)在com.mingrisoft
包中再创建一个名称为Benz
的类,该类继承自Car
并实现其getInfo()
方法。关键代码如下:
1 2 3 4 5 6 7 8
| package com.mingrisoft; public class Benz extends Car { @Override public String getInfo() { return "Benz"; } }
|
(5)在com.mingrisoft
包中再创建一个名称为CarFactory
的类,该类定义了一个静态方法getCar
,它可以根据用户指定的车型来创建对象。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.mingrisoft; public class CarFactory { public static Car getCar(String name) { if (name.equalsIgnoreCase("BMW")) { return new BMW(); } else if (name.equalsIgnoreCase("Benz")) { return new Benz(); } else { return null; } } }
|
(6)在com.mingrisoft
包中再创建一个名称为Customer
-的类,用来进行测试。在main()
方法中,根据用户的需要提取了不同的汽车。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.mingrisoft; public class Customer { public static void main(String[] args) { System.out.println("顾客要购买BMW:"); Car bmw = CarFactory.getCar("BMW"); System.out.println("提取汽车:" + bmw.getInfo()); System.out.println("顾客要购买Benz:"); Car benz = CarFactory.getCar("Benz"); System.out.println("提取汽车:" + benz.getInfo()); } }
|
运行效果:
1 2 3 4
| 顾客要购买BMW: 提取汽车:BMW 顾客要购买Benz: 提取汽车:Benz
|
技术要点
本实例应用的主要技术就是面向对象程序设计中的多态。多态是面向对象程序设计的基本特性之一。
使用多态的好处就是可以屏蔽对象之间的差异,从而增强了软件的护展性和重用性。
Java
的多态主要是通过重写父类(或接口)中的方法来实现的。
对于香蕉、桔子等水果而言,人们通常关心其能吃的特性。如果分别说香蕉能吃、桔子能吃,则当再增加新的水果种类,如菠萝时还要写个菠萝能吃,这是非常麻烦的。使用多态的话可以写成水果能吃,当需要用到具体的水果时,系统会自动帮忙替换,从而简化开发。
实例063 使用Comparable接口自定义排序
实例说明
默认情况下,保存在List
集合中的数组是不进行排序的,不过可以通过使用Comparable
接口自定义排序规则并自动排序。本实例将介绍如何使用Comparable
接口自定义排序规则并自动排序。实例的运行效果如图75所示
实现过程
Employee.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.mingrisoft;
public class Employee implements Comparable<Employee> {
private int id; private String name; private int age;
public Employee(int id, String name, int age) { this.id = id; this.name = name; this.age = age; }
@Override public int compareTo(Employee o) { if (this.id > o.id) { return -1; } else if (this.id < o.id) { return 1; } return 0; }
@Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("员工的编号:" + id + ", "); sb.append("员工的姓名:" + name + ", "); sb.append("员工的年龄:" + age); return sb.toString(); } }
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.mingrisoft;
import java.util.ArrayList; import java.util.Collections; import java.util.List;
public class Test { public static void main(String[] args) { List<Employee> list = new ArrayList<Employee>(); list.add(new Employee(1, "Java", 23)); list.add(new Employee(3, "Java", 21)); list.add(new Employee(2, "Java", 22)); System.out.println("排序前:"); for (Employee employee : list) { System.out.println(" " + employee); } System.out.println("排序后:"); Collections.sort(list); for (Employee employee : list) { System.out.println(" " + employee); } } }
|
运行效果
1 2 3 4 5 6 7 8
| 排序前: 员工的编号:1, 员工的姓名:Java, 员工的年龄:23 员工的编号:3, 员工的姓名:Java, 员工的年龄:21 员工的编号:2, 员工的姓名:Java, 员工的年龄:22 排序后: 员工的编号:3, 员工的姓名:Java, 员工的年龄:21 员工的编号:2, 员工的姓名:Java, 员工的年龄:22 员工的编号:1, 员工的姓名:Java, 员工的年龄:23
|
可以看到,调用了Collections.sort(list);
后,list
集合里的元素都按编号进行降序排序。
技术要点
本实例主要应用java.lang
包中的Comparable
接口来实现自定义排序规则。Comparable
接口用于强行对实现它的每个类的对象进行整体排序。在实现该接口的类中,必须实现该接口中定义的compareTo()
方法,用于指定排序规则。Comparable
接口的定义如下:
1 2 3
| pubic interface Comparable<t>{ int compareTo(T other); }
|
如果一个类要实现这个接口,可以使用如下语句声明:
1 2 3
| public class Employee implements Comparable<Employee>{
}
|
在Employee
中必须实现接口中定义的compareTo()
方法。实现该方法后,
- 如果将该对象保存到列表中,那么可以通过执行
Collections.Sort()
方法进行自动排序;
- 如果保存到数组中,那么可以通过执行
Array.sort()
方法进行自动排序。
指点迷津
如果不想实现在接口中定义的方法,则可以将类声明为抽象类,将接口中定义的方法声明为抽象方法。
实例064 策略模式的简单应用
实例说明
在使用图像处理软件处理图片后,需要选择种格式进行保存,然而各种格式在底层实现的算法并不相同,这刚好适合策略模式。本实例将演示如何使用策略模式与简单工厂模式组合进行实例开发。
实现过程
(2)在com.mingrisoft
包中编写接口ImageSaver
,在该接曰中定义save
方法。关键代码如下:
1 2 3 4 5 6
| package com.mingrisoft;
public interface ImageSaver { void save(); }
|
(3)在com.mingrisoft
包中再编写类GIFSaver
,该类实现了ImageSaver
接曰。在实现save
方法时将图片保存为GIF
格式,关键代码如下:
1 2 3 4 5 6 7 8 9
| package com.mingrisoft;
public class GIFSaver implements ImageSaver { @Override public void save() { System.out.println("将图片保存成GIF格式"); } }
|
指点迷津:对于将图片保存成其他格式与存储为GIF
格式类似,这里就不再赘述。
(4)在com.mingrisoft
包中再编写类TypeChooser
,该类根据用户提供的图片类型来选择合适的图片存储方式。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.mingrisoft;
public class TypeChooser { public static ImageSaver getSaver(String type) { if (type.equalsIgnoreCase("GIF")) { return new GIFSaver(); } else if (type.equalsIgnoreCase("JPEG")) { return new JPEGSaver(); } else if (type.equalsIgnoreCase("PNG")) { return new PNGSaver(); } else { return null; } } }
|
指点迷津:此处使用了简单工厂模式,根据描述图片类型的字符串创建相应的图片保存类的对象
(5)在com.mingrisoft
包中再编写类User
,该类模拟用户的操作,为类型选择器提供图片的类型。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.mingrisoft; public class User { public static void main(String[] args) { System.out.print("用户选择了GIF\t格式:"); ImageSaver saver = TypeChooser.getSaver("GIF"); saver.save(); System.out.print("用户选择了JPEG\t格式:"); saver = TypeChooser.getSaver("JPEG"); saver.save(); System.out.print("用户选择了PNG\t格式:"); saver = TypeChooser.getSaver("PNG"); saver.save(); } }
|
运行效果
1 2 3
| 用户选择了GIF 格式:将图片保存成GIF格式 用户选择了JPEG 格式:将图片保存成JPG格式 用户选择了PNG 格式:将图片保存成PNG格式
|
技术要点
本实例应用的最重要的技术就是策略模式。
对于策略模式而言,需要定义一个接口或者抽象类来表示各种策略的抽象,这样就可以使用多态来让虚拟机选择不同的实现类。然后让每种具体的策略来实现这个接口或继承抽象类,并为其中定义的方法提供具体的实现。
由于在选择适当的策略上有些不方便,需要不断地判断需要的类型,因此用简单工厂方法来实现判断过程。
实例065 适配器模式的简单应用
实例说明
对于刚从工厂生产出来的商品,有些功能并不能完全满足用户的需要。因此,用户通常会对其进行一定的改装工作。
本实例将为普通的汽车增加GPS
定位功能,借此演示适配器模式的用法。实例的运行效果如图77所示。
实现过程
(2)在com.mingrisoft
包中编写类Car
,在该类中,首先定义两个属性,一个是name
,表示汽车的名字;另一个是speed
,表示汽车的速度。并为其提供getXXX()
和setXXX()
方法,然后通过重写toString()
方法来方便输出Car
对象。关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.mingrisoft; public class Car { private String name; private double speed; public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSpeed() { return speed; }
public void setSpeed(double speed) { this.speed = speed; }
@Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("车名:" + name + ", "); sb.append("速度:" + speed + "千米/小时"); return sb.toString(); } }
|
(3)在com.mingrisoft
包中再编写接口GPS
,该接口中定义了geoLocation()
方法,用来确定汽车的位置。关键代码如下:
1 2 3 4 5 6 7 8
| package com.mingrisoft;
import java.awt.Point;
public interface GPS { Point getLocation(); }
|
(4)在com.mingrisoft
包中再编写类GPSCar
,该类继承Car
并实现GPS
接口。在该类中首先实现geoLocation
方法,用于实现确定汽车位置的功能,然后重写toString
方法方便输出GPSCar
对象。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.mingrisoft;
import java.awt.Point;
public class GPSCar extends Car implements GPS { @Override public Point getLocation() { Point point = new Point(); point.setLocation(super.getSpeed(), super.getSpeed()); return point; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()); sb.append(", 坐标:(" + getLocation().x + ", " + getLocation().y + ")"); return sb.toString(); } }
|
指点迷津:可以使用super
关键字调用父类中定义的方法。
(5)在com.mingrisoft
包中再编写类Test
进行测试。在该类中,分别创建Car
和GPSCar
对象,并对其初始化,然后输出这两个对象。关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.mingrisoft; public class Test { public static void main(String[] args) { System.out.println("自定义普通的汽车:"); Car car = new Car(); car.setName("Adui"); car.setSpeed(60); System.out.println(car); System.out.println("自定义GPS汽车:"); GPSCar gpsCar = new GPSCar(); gpsCar.setName("Audi"); gpsCar.setSpeed(60); System.out.println(gpsCar); } }
|
运行效果:
1 2 3 4 5
| 自定义普通的汽车: 车名:Adui, 速度:60.0千米/小时 自定义GPS汽车: 车名:Audi, 速度:60.0千米/小时, 坐标:(60, 60)
|
技术要点
适配器模式可以在符合OCP
原则(开闭原则)的基础上,为类增加新的功能。该模式涉及的角色主要有以下3个:
- 目标角色:就是期待得到的接口,如本实例的
GPS
接口。
- 源角色:需要被增加功能的类或接口,如本实例的
Car
类。
- 适配器角色:新创建的类,在源角色的基础上实现了目标角色,如本实例的
GPSCar
类。
关于各个类的继承(实现)关系如图7.8所示。