您的位置:首页 > 编程语言 > Java开发

Java基础之面向对象的概念 继承---组合----枚举类

2014-09-07 19:20 453 查看
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关键字),但因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: