Java基础之面向对象的概念 继承---组合----枚举类
本章作为面向对象概念的最后一篇,但是作为一名java程序员在面向对象的这条路上还很长。
一、继承与组合简介
继承是实现类重用的重要手段,但是继承的最大缺点是破坏了封装性,组合也是实现类重用的重要手段之一,这种方式则能提供更好的封装性。本章的上半部分将讲解继承和组合的区别和联系。
1.1使用继承应该注意的地方。
子类继承父类之后可以访问父类的变量和方法,因此严重破坏了父类的封装性,封装性是指:每个类都应该封装它内部的信息和实现细节,而只暴漏必要的方法给其他类使用。
子类访问父类的变量和方法的同时也造成了子类和父类的严重耦合。
为了解决这类的问题,我们应从父类的设计入手,有如下规则
1.尽量隐藏父类的内部数据,就是将父类的变量设置为private修饰。子类不能够直接访问。
2.不要让子类可以随意访问,修改父类的方法。可以使用访问修饰符(public protected private ),final修饰符进行方法的访问权限设定,来达到不同的访问需求。
3.尽量不要在父类中调用将被子类重写的方法,
4.如果将父类设计为不可继承的状态,则需要使用Final修饰,或者使用private修饰这个类的所有构造函数,则可以保证子类无法调用父类的构造函数,子类就无法继承该类。对于该类创建对象的问题,可以创建一个静态方法,返回该类的实例。
实例代码1:
package cn.com.basicFour;
/**
* @author fcs
* 2014年8月28日
* 说明:子类重写的方法出现在父类的构造器中引发错误
*/
class Base{
public Base(){
test();
}
public void test(){
System.out.println("我是父类中将被子类重写的方法。。。。");
}
}
public class Sub extends Base {
public static void main(String[] args) {
Sub sub = new Sub();
}
private String name;
//父类中的构造方法调用的是子类重写的方法,但是name的长度是一个null,运行报错。
public void test(){
System.out.println("我是子类重写的方法。。。。name的长度: "+name.length());
}
}
那么什么时候需要继承呢?
说明:不仅需要保证子类是一种特殊的父类。而且需要下面两个条件之一
1.子类需要额外增加属性,而不仅仅是属性值的改变。
2.子类需要增加自己独有的行为方式,(包括增加新的方法或重写父类的方法)
上面说明了继承的注意事项,如果只是考虑到继承的复用性,那也可以使用组合实现。
组合仅需将对象引用置于新类中即可
实例代码2:简单的组合演示
package cn.com.basicFour;
class Animal{
private void best(){
System.out.println("心脏跳动。。。");
}
public void breath(){
best();
System.out.println("戏一口气。。。。吐一口气。。。");
}
}
class Bird{
//将Animal类嵌入可以继承的子类中
private Animal a;
public Bird(Animal animal){
this.a = animal;
}
//重新定义自己的方法
public void breath(){
//直接使用Animal提供的方法。
a.breath();
}
public void fly(){
System.out.println("我在天空飞翔。。。。。");
}
}
class Wolf{
//将Animal类嵌入可以继承的子类中
private Animal a;
public Wolf(Animal animal){
this.a = animal;
}
//重新定义自己的方法
public void breath(){
//直接使用Animal提供的方法。
a.breath();
}
public void run(){
System.out.println("我在陆地上奔跑。。。。。");
}
}
public class CompositeTest {
public static void main(String[] args) {
//此时需要显示的创建被嵌入的对象
Animal al = new Animal();
Bird bird = new Bird(al);
bird.fly();bird.breath();
Wolf wolf = new Wolf(al);
wolf.breath();
wolf.run();
}
}
结论:
继承是对已有的类进行改造,来达到某些特定的需求。
如果两个类之间有明确的整体和部分的关系,可以使用组合关系实现复用。
继承要表达的思想是一种“is-A”关系,组合要表达的思想是“has-A”关系。
一、枚举类
说明:在某些情况下,一个类的对象是有限而且固定的。在java里被称为枚举类。
1.1枚举类说明:
1. Java5 新增了一个enum关键字(与class ,interface地位相同),用以定义枚举类。
2.枚举类是一种特殊的类,可以有自己的属性,方法,可以实现一个或者多个接口,也可以定义自己的构造函数,
3.一个java源文件最多只能定义一个public 访问权限的枚举类,该源文件名必须和该枚举类名相同。
4.枚举类与普通类的区别
1.使用ENUM定义的枚举类默认继承了java.long.Enum类,
而不是继承Object类,java.long.Enum类实现了java.long.Serializable和java.long.Comparable接口。、
2.使用ENUM定义的非抽象的枚举类默认会使用Final修饰,因此枚举类不能派生子类。
3.枚举类的构造函数只能使用private访问修饰符。
4.枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远都不能产生实例,系统会自动为这些实例添加public static Final修饰。
1.2手动实现枚举类
设计方式
1.通过private将构造函数隐藏起来。
2.将这个类的所有可能的实例使用public static Final修饰。
3.可以提供一些静态方法,允许其他程序根据特定的参数来获取与之匹配的实例。
实例代码3:
package cn.com.basicFour;
/**
*
* @author fcs
* 2014年8月28日
* 说明:手动实现枚举类
* 该类是一个不可变类
*/
public class Season {
private final String name;
private final String desc;
public Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public static final Season SPRING = new Season("春天","出游踏春");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season FALL = new Season("秋天","秋风飒飒");
public static final Season WINTER = new Season("冬天","围炉赏雪");
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
//静态工厂方法实现返回season的实例
public static Season getSeason(int seasonNum){
switch(seasonNum){
case 1:
return SPRING;
case 2:
return SUMMER;
case 3:
return FALL;
case 4:
return WINTER;
default:
return null;
}
}
}
public class SeasonTest {
public SeasonTest(Season season){
System.out.println(season.getName()+" 是一个"+season.getDesc()+"的季节。。。。");
}
public static void main(String[] args) {
//直接使用Season的FALL常量代表一个Season实例
new SeasonTest(Season.FALL);
}
}
说明:使用枚举类可以使程序更加健壮,避免创建对象的随意性。
可以使用这种方式:静态常量
Public static final int SEASON_SPRING = 1;
....
这种方式存在一些问题:
1.类型不安全,因为这些常量可以相加。
2.没有命名空间,容易与其他常量或者变量混淆。
3.打印输出的意义不明确。1指的是什么
实例代码4:
//枚举类演示
public enum Gender {
MALE,FEMALE;
private String name; //定义public修饰的变量
public void setName(String name){
switch(this){
case MALE:
if(name.equals("男")){
this.name = name;
}else{
System.out.println("参数错误。。。。。");
}
break;
case FEMALE:
if(name.equals("女")){
this.name = name;
}else{
System.out.println("参数错误。。。。。。");
}
break;
}
}
public String getName(){
return this.name;
}
}
package cn.com.basicFour;
public class GenderTest {
public static void main(String[] args) {
Gender g = Enum.valueOf(Gender.class, "FEMALE");
g.setName("女");
System.out.println(g+"代表 : "+g.getName());
g.setName("男");
System.out.println(g+"代表: "+g.getName());
}
}
说明:应该将上面的程序设计为不可变类。将所有的变量都使用final修饰符来修饰。
应该使用构造函数指定初始值,列出枚举值时就必须对应的传入参数。
实例代码5:
//枚举类与构造函数
public enum Gender2 {
//枚举类必须使用对应的构造函数实现
MALE("男"),FEMALE("女");
private final String name;
private Gender2(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
1.3实现接口的枚举类
实例代码6:
public enum Gender3 implements GenderDesc{
//;//必须带上这个分号,当没有枚举类型时。
MALE("男"){ //这种方式相当于一个匿名内部子类
public void info() {
System.out.println("这个枚举值代表男性。。。。。");
}
},FEMALE("女"){
public void info() {
System.out.println("这个枚举值代表女性。。。。");
}
};
public void info() {
System.out.println("这是用于定义性别的枚举类。。。。。");
}
private final String name;
private Gender3(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
注意:非抽象的枚举类才是默认使用final修饰的,对于一个抽象的枚举类而言,只要包含了抽象方法就是抽象枚举类,系统默认使用abstract修饰,而不是final修饰。
编译后程序会生成三个class文件:Gender.class,Gender$1.class,Gender%2.class
1.3包含抽象方法的枚举类
package cn.com.basicFour;
/**
*
* @author fcs
* 2014年8月28日
* 说明:正常的枚举计算
*/
public enum Operation {
PLUS,MINS,TIMES,DIVIDE;
//为枚举类定义一个方法,用于实现不同的运算
double eval(double x,double y){
switch(this){
case PLUS:
return x+y;
case MINS:
return x - y;
case TIMES:
return x * y;
case DIVIDE:
return x / y;
default: return 0;
}
}
public static void main(String[] args) {
System.out.println(Operation.PLUS.eval(23,22.1));
System.out.println(Operation.MINS.eval(2233,22.1));
System.out.println(Operation.TIMES.eval(2,22.1));
System.out.println(Operation.DIVIDE.eval(22,22.1));
}
}
带抽象方法的枚举类
package cn.com.basicFour;
/**
* @author fcs
* 2014年8月28日
* 说明:带抽象类的枚举
* ,每个枚举值都必须实现该抽象类
*/
public enum Operation2 {
PLUS{
@Override
public double eval(double x, double y) {
return x+y;
}
},
MINES{
@Override
public double eval(double x, double y) {
return x - y;
}
},
TIMES{
@Override
public double eval(double x, double y) {
return x * y;
}
},
DIVIDES{
@Override
public double eval(double x, double y) {
return x / y;
}
};
public abstract double eval(double x,double y);
public static void main(String[] args) {
System.out.println(Operation.PLUS.eval(23,22.1));
System.out.println(Operation.MINS.eval(2233,22.1));
System.out.println(Operation.TIMES.eval(2,22.1));
System.out.println(Operation.DIVIDE.eval(22,22.1));
}
}
说明:
枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动回为它添加abstract关键字),但因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误