第 10章 类结构扩展

本章需要掌握包的主要作用与包的使用,理解jar文件的主要作用与创建命令,掌握java中4种访问控制权限,并且可以深刻理解面向对象封装性在Java中的实现,掌握构造方法私有化的意义,深刻理解单例设计模式与多例设计模式的作用,理解美剧的主要作用于定义形式。
        面向对象中的核心组成是类与接口,在项目中会利用包进行一组相关类的管理,这样适合于程序代码的部分更新,也更符合面向对象封装性的概念,同时合理的使用封装也可以方便地实现实例化对象数量的控制,本章将讲解类结构的一些扩展特性。

10.1 包

在Java中,可以将一个大型项目中的类类分别独立出来,并分门别类的存在文件里,在将这一些文件一起编译执行。

10.1.1 包的定义

在Java程序中,包主要的目的是可以将不同功能的文件进行分割。在之前的代码开发中,所有的程序都保存在同一个文件中,这样所带来的问题是如果有同名文件,那么会发生覆盖问题,因为在同一个目录中不允许有重名文件,而在不同的目录下可以有重名文件。所谓的包实际上是指文件夹,在Java中可以使用package定义包名称,此语句必须编写在源代码的首行。

范例:定义包

package cn.demo
public class Hello
{
public static void main(String args[]){System.out.println("AAAA");}
}
本程序将Hello类放在一个自定义的包中,这样一来在程序编译后就必须将*.class文件保存在指定的目录中,但是手工建立程序包目录非常麻烦,此时最好的做法就是进行打包编译处理:javac -d .Hello.java,参数作用如下。
-d表示要生成目录,而目录的结构就是package定义的结构。
.:表示在当前所在的目录中生成程序类文件。
在程序打包编译后会有相应的包结构存在,而是用java命令执行程序时,需要编写上完整的“包类名称”,例如,以上范例的执行命令:java cn.mldn.demo.hello;
提示:项目中必须提供包
在实际项目编写开发过程中,所有的程序类都必须放在一个包中,并且往往要设计一个总包名称和子包名称。在进行包名称命名时所有的字母要求小写。

10.1.2 包的导入

利用包的定义可以将不同功能的泪保存在不同的包中以实现分模块开发的需求,但是不同包中的类彼此之间一定存在相互调用的关系,那么此时就需要使用import一句来导入被调用的其他包中的程序类。

范例:定义一个程序类cn.util.Message,这个类负责获取消息数据

package cn.util.Message
public class Message{public String getContent(){return "AAA";}}
本程序定义了一个Messsage类,由于此类需要被其他类所引用,所以首先编译程序(javac -d .Message.java)
注意:定义时必须使用public class声明
如果一个包中的类要想被其他包中的类所使用,那么这个类一定要定义为public class,而不能使用class声明。因为class声明的类只能够在同一个包中使用,这一点在访问权限中有详细说明。
总结:关于public class和class类的区别
public class :文件名称和类名称保持一致,在一个*.java文件中只能存在一个public class定义,如果一个类要想被外部的包所访问必须定义public。
class:文件名称可以和类名称不一致,在一个*.java中可以同时存在多个class定义,并且编译完成之后会形成多个*.class文件,使用class定义的类只能够在一个包中访问,不同包无法访问。
范例:定义一个测试类使用Message类:cn.util.Message(),引用Message类
package cn.test
import cn.util.Message;
public class TestMessage
{
public static void main(String args[])
{
Message msg=new Message();
System.out.println(msg.getContent());
}
}

本程序实现了不同包中类的引用,在TestMessage类中需要使用import语句导入指定的类,这样就可以直接实例化类对象并且进行相应的调用。
提问:如何理解编译顺序
在以上程序中由于TestMessage类需要实例化Message类对象,所以import进行了指定类的导入,同时需要先编译Message.java,再编译TestMessage.java,如果类文件很多,这样的编译顺序是不是过于繁琐了。
回答:可以使用*.java自动编译
在开发中如果所有的程序源代码都按照顺序编译,这实在是一件可怕的事情,为了解决这样的问题,在Java中可以采用*.java的匹配模式进行编译,对于以上程序,可以直接使用javac -d *.java由JDK帮助开发者区分调用顺序并自动编译。

在进行不同包导入时,除了使用import 包.类名称的形式外,还可以使用import 包.*的通配符形式自动进行录入处理。
提示:import包.*的导入模式不影响程序性能。
在Java中无论是使用import 包.*导入或者单独导入,从实际操作性能上来讲是没有任何区别的,因为即使使用了“*”也表示只导入所需要的类,不需要的并不导入。
范例:使用自动导入处理,修改cn.test.TestMessage类
package cn.test
import cn.util.*
{
public class TestMessage
{
Message msg=new Message();
System.out.println(msg.getContent());
}
}

本程序在编写import语句时使用包.*的形式进行自动导入配置,这样在TestMessage引用cn.util包中的多个类的时候就可以减少import语句的编写数量。
提问:不同包的相同类写入
包的本质是目录,不同的目录可以存放相同的类名称,这样一来,如果说此时有两个类cn.util.Message、org.demo.Message,其结构如下
 

cn.util.Messageorg.demo.Message
package cn.util;
public class Message{
public String getContent(){return "AAAA";}
}
package org.demo;
public class Message{public String getInfo(){return "AAAA";}}

那么在TestMessage类中,如果需要同时导入这两个包,并且直接实例化Message类对象并调用方法时,这时会发生什么。
回答:同时采用import包.*导入不同包时相同的类名称产生冲突,这就需要在使用时写上类全名
在开发中为了防止不同包的重名类的相互影响,往往在使用类时写上类的完整名称,例如,当前的TestMessage类中需要实例化的是cn.util.Message类对象,就必须编写上这个类的完整名称。

范例:明确指明要使用的类
package cn.test
import cn.util.*
import org.*
public class TestMessage
{
public static void main(String args[])
{
cn.util.Message msg=new cn.util.Message();
System.out.println(msg.getContent());
}
}

本程序由于TestMessage类中所导入的两个包(cn.util和org.demo)都有Message类,为了防止名称引用不明确的编译错误,就必须明确地写上要实例化对象类的完整名称。

10.1.3 静态导入

当一个类中的全部组成方法都是static时,就可以利用JDK1.5后提供的新机制进行静态导入操作。

范例:定义一个由静态方法组成的类

package cn.util;
public class MyMath
{
public static int add(int ...args)
{
int sum=0;
for(int temp:args){sum+=temp;}
return sum;
}
public static int sub(int x,int y){return x-y;}

本程序提供的方法全部是static型,按照传统的导入形式,需要先使用import导入指定类,随后在利用类名称进行调用。但是在静态导入中泽可以直接采用import static 包.类的形势进行静态方法导入。
范例:使用静态导入
package cn.test;
import static cn.util.MyMath.*;
public class TestMath
{
public static void main(String args[])
{
System.out.println(add(10,10,10));
System.out.println(sub(30.20));
}
}

利用静态导入的优点在于不同的静态方法就好像在主类中定义一样,不需要根据类名称就可以直接进行调用。

10.1.4 jar文件

jar(Java Archive,Java归档文件)是一种Java给出的压缩格式文件,即可以将*.class文件以*.jar压缩包的方式给用户,这样方便程序的维护。如果要使用jar的话,可以直接利用JDK给出的jar命令完成,如果要确定使用参数则可以输入命令:jar --help,查看相关参数,在世纪开发中,Jav最常用的3个参数如下
-c:创建一个新的文件
-v:生成标准的压缩信息
-f:由用户自己制定一个*.jar的文件名称。

范例:定义一个类,随后将其打包为jar文件
package cn.util
public class Message
{
public String getContent();{return "AAAA";}
}

源代码首先编译为*.class文件后才可以打包为*.jar文件,可以按照以下步骤进行
对程序打包编译:javac -d .Message.java.
此时会形成cn的包,包里有相应的子包与*.class文件,将其打包为AA.jar:jar -cvf AA.jar cn.
每一个*.jar文件都是一个独立的程序路径,如果想要在Java程序中使用此路径,则必须通过CLASSPATH进行配置。
SET CLASSPATH=.;d:\AA.jar;
范例:编写测试类,引入AA.jar中的Message类

package cn.test
public class TestMessage{
cn.util.Message ms=new cn.util.Message();

}

此时程序可以直接引用*.jar文件中的程序类使用。
提示:错误的CLASSPATH属性配置
在引用*.jar文件的过程中,CLASSPATH环境属性是一个重要选项,如果没有正确的配置,则在程序使用时会出现Exception in thread "main" java.lang.NoClassDefFoundError:cn/util/Message异常信息。

10.1.5 系统常用包

Java语言最大的特点是提供了大量的开发支持,尤其是经过了那么多年的发展,几乎只要想做的技术,Java都可以完成了,而且提供了大量的开发包支撑,对于Java SE也提供了一些常用的系统包。
 

No.包名称作用
1java.lang基本包,像String这样的类都保存在此包中,在JDK1.0的时候如果想编写程序,必须手动导入此包,但之后JDK解决了此问题,此包围自动导入
2java.lang.reflect反射机智的包,是java.lang的子包,在Java反射机制中将进行介绍
3java.util工具包,一些常用的类库、日期操作都在此包中。如果掌握精通此包,则可以更好的理解各种设计思路
4java.text提供一些文本的处理类库
5java.sql数据库操作的包,提供了各种数据库操作的类和接口
6java.net完成网络编程
7java.io输入、输出处理
8java.awt包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类用来构建和管理应用程序的图形用户界面(GUI)
9javax.swing此包用于建立图形用户界面,次保重的组件相对于java.awt而言是轻量级组件
10java.applet小应用程序开发包

表中的包只是Java开发过程中很小一部分,而随着读者开发经验的提升,对这些开发包也会慢慢积累,当积累到一定程度后就可以开始编写实际程序了。

提示:JDK1.9之后的变化
在JDK1.9之前的版本中实际上提供的是一个所有类的*.jar文件(rt.jar,tools.jar),在传统的开发中只要启动了Java虚拟机,就需要加载这些类文件。
在JDK1.9之后提供了一个模块化的设计,将原本要加载的一个*.jar文件变成了若干个模块文件,这样在启动的时候可以根据程序加载指定的模块(模块中有包),以提高启动速度

10.2 访问控制权限

对于封装性实际上在之前讲解了一个private,而如果想要完整掌握封装性,必须结合4种访问权限来看,而这4种访问控制权限的定义如下表:
 

No范围privatedefaultprotectedpublic
1同一包的同一类1111
2同一包的不同类111
3不同包的子类11
4不同包的非子类1

对于private、default、public的相关特点在之前的讲解中已经通过举例进行了详细说明,本次的重点在于讲解protected权限。该权限的主要特点:允许本包以及不同包的子类进行访问。
对于private、default、public的相关特点在之前的讲解中就已经通过举例进行了详细的说明,本次的重点在于讲解protected权限。该权限的主要特点:允许本包以及不同包的子类进行访问。
范例:定义cn.a.Messsage类,并且在此类中直接访问protedcted属性
package cn.b;
import cn.a.Message;
public class NetMessage extends Messsage{
public void priint(){System.out.println(super.info);}
}

此时Message父类与NetMessage子类不在同一个包中,同时在NetMessage子类中利用super关键字访问了父类中的protected属性。

范例:编写测试类
package cn.test;
import cn.b.*;
public class TestMessage{
public static void main(String args[])
{
new NetMesssage().print();
}
}
 

本程序通过cn.test.TestMessage中直接实例化了子类对象实例,所以输出了Message父类中protected成员属性内容。如果此时尝试在TestMessage中直接访问Message中的info成员属性,则会在编译时提示protedted权限访问错误。

提示:关于访问控制权限的使用
对于访问控制权限,初学者把握一下的原则即可。
属性声明以priavte为主。
方法声明以public为主
对于封装性实际上是有3种表示方式:private,default,protected;

10.3 构造方法私有化

在类结构中每当使用关键字new都会调用构造方法并实例化新对象,然而在设计中,也可以利用构造方法私有化形式来实现实例化对象的控制,本节姜维读者分析构造私有化的相关案例。

10.3.1 单例设计模式

单例设计模式是指在整个系统中一个类只允许提供一个实例化对象,为实现此要求就可以通过private进行构造方法的封装,这样该类将无法在类的外部利用关键字new实例化新的对象。同时为了方便使用本类的方法,则可以在内部提供一个全局实例化对象供用户使用。

范例:单例设计模式
package cn.demo;
class Singleton
{
private static final Singleton INSTANCE=new Singleton();
private Singleton(){}
public static Singleton getInstance(){return INSTANCE;}
public void print(){System.out.println("AAA");}
}

public class JavaDemo
{
public static void main(String args[])
{
Singleton instance=null;
instance=Singleton.getInstance();
instance.print();
}
}
本程序将Singleton类的构造方法进行了private私有化封装,这样讲无法在类外部通过关键字new实例化对象。同时为了方便使用Singleton类对象,在类内部提供了公共的INSTANCE对象作为本类成员属性,并利用static方法可以直接获取本类实例以实现相关方法调用。
        对于单例模式也分为两种:饿汉式单例模式和懒汉单例模式,两种单例模式的主要差别是在于对对象进行实例化的时机,在之前所讲解的单例模式中,可以发现类中定义成员属性时就直接进行了对象实例化处理,这种结构就属于饿汉模式。而懒汉模式是在第一次使用类的时候进行实例化。

范例:定义懒汉单例设计模式

package cn.demo;
class Singleton
{
public static Singleton instance;
private Singleton(){}
public static Singleton getInstance()
{
if(instance==null){instance=new Singleton();}return instance;
}
public void print(){System.out.println("AAA");}
}
public class JavaDemo
{
public static void main(String args[])
{
Singleton instance=null;
instance=Singleton.getInstance();
instance().print();
}
}

本程序在Singleton内部定义instance成员属性时并没有进行对象实例化,而是在第一次调用getInstance()的时候才进行了对象实例化处理,这样可以节约程序启动时的资源。

10.3.2 多例设计模式

单例设计模式只留有一个类的实例化对象,而多里模式会定义出多个对象。例如,定义一个表示星期的操作类,这个类的对象有七个实例化对象(星期一到星期天);定义一个表示性别得嘞,有两个实例化的塑像(男,女);定义一个表示颜色基色的操作类,有三个实例化对象(红、绿、蓝)。这种情况下,类似这样的类就不应该由用户无限制的去床罩实例化对象,应该只使用有限的几个,这个就属于多例设计模式。

范例:实现多例设计模式

package cn.demo;
class Color
{
private static final Color RED=new Color("红色");
private static final Color GREEN=new Color("绿色");
private static final ColorBLUE=new Color("蓝色");
private String title;
private Color(String title){this.title=title;}
public static Color getInstance(String color)
{
switch(color)
{
case "red":return RED;case "green";return GREEN;case "blue":return BLUE;default return null; 
}

}

public String toString(){return this.title;}
}

public class JavaDemo
{
public static void main(String args[])
{
Color c=Color.getInstance("green");System.out.println(c);
}
}

由于需要控制实例化对象的产生个数,所以本程序将构造方法进行私有化定义后在内部提供了3个实例化对象,为了方便外部类使用,可以通过getInstance()方法利用对象标记获得实例化对象。

10.4 枚举

Java语言从设计之初并没有提供枚举的概念,所以开发者不得不使用多例模式来代替枚举的解决方案,而从JDK1.5开始,Java支持了枚举结构的定义,通过枚举可以简化多例模式的实现。

10.4.1 定义枚举类

JDK1.5开始,Java提供了一个新的关键字:enum,利用此关键字可以实现枚举类型的定义,利用枚举可以简化多例模式的定义。

范例:定义枚举类型

package cn.demo;
enum Color{RED,GREEN,BLUE;}

public class JavaDemo
{
public static void main(String args[])
{
for(Color c:Color.values()){System.out.print(c+".");}
}
}

本程序利用values()方法获取了Color中的全部枚举类型,随后利用foreach循环获取每一个对象斌进行输出。

之所以采用枚举来代替多里模式的一个很重要的原因在于,可以直接在swicth语句中进行枚举对象类型判断。

范例:在switch中判断枚举类型

package cn.demo;
enum Color{
RED,GREEN,BLUE;
}

public class JavaDemo
{
public static void main(String args[])
{
Color c=Color.RED;
switch(c)
{
case RED:System.out.println("红色");break;
case GREEN:System.out.println("绿色");break;
case BLUE:System.out.println("蓝色");break;
}
}
}
本程序直接在swicth中可以实现枚举对象的实例,而如果使用多例设计模式,则只能通过大量的if语句的判断来进行内容的匹配与结果输出。
提示:关于swicth允许操作的数据类型
switch中支持判断的数据类型,随着JDK版本的升级也越来越完善。
在JDK1.5之前,只支持Int或Char型数据。
在JDK1.5之后,增加Enum型数据。
在JDK1.7之后,增加String型数据。

10.4.2 Enum类

枚举并不是一个新的类型,他只是提供了一种更为方便的结构。严格来讲,每一个使用enum定义的嘞实际上都属于一个类继承了Enum父类而已,而java.lang.Enum类定义如下
public abstract class Enum<E extends Enum<E>>
        extends Object implements Comparable<E>,Serializable{}
在Enum类中定义其可以支持的泛型上限,同时在Enum类中提供如下常用方法
 

No方法名称类型描述
1protected Enum(String name,int ordinal)构造传入名字和序号
2public finale String name()普通获得对象名字
3public finale int ordinal()普通获得对象序号

范例:观察enum关键字与Enum类之间的联系
package cn.demo;
enum Coloe{RED,GREEN,BLUE;}
public class JavaDemo
{
public static void main(String args[])
{
for(Color c:Color.values()){System.out.println(c.ordinal()+""+c.name();)}
}
}

本程序每输出一个枚举类对象时都调用了Enum类中定义的ordinal()与name()方法来获取相应的信息,所以可以证明,enum定义的枚举类将默认继承Enum父类。

10.4.3 定义枚举结构

在枚举类中除了可以定义若干个实例化对象之外,也可以像普通类那样定义成员属性、构造方法、普通方法,但是需要记住的是,枚举的本质是属于多例设计模式,所以构造方法不允许使用public进行定义。如果类中没有提供无参构造方法,则必须在定义每一个枚举对象时明确传入参数内容。

范例:在枚举类中定义成员属性和方法

package cn.demo;
enum Coloe
{
RED("红色"),GREEN("绿色"),BLUE("蓝色");
private String title;
private Color(String title){this.title=title;}
@Override
public String toString(){return this.title;}
}

public class JavaDemo
{
public static void main(String args[])
{
for(Color c:Color.values()){System.out.println(c.ordinal();+""+c.name()+c);}
}
}

本程序通过枚举结构定义了构造方法并且覆写了Object类中的toString()方法,可以发现在Java中已经将枚举结构的功能进行了扩大,使其与类结构更加贴近。

范例:通过枚举类实现接口

package cn.demo;
interface IMessage{public String getMessage();}
enum Color implements IMessage

{
RED("红色"),GREEN("绿色"),BLUE("蓝色");
private String title;
private Color(String title){this.title=title;}
@Override
public String toString(){return this.title;}
@Override public String getMessage(){return this.title;}
}

public class JavaDemo
{
public static void main(String args[])
{
IMessage msg=Color.RED;
System.out.println(msg.getMessage());
}
}

本程序让枚举类实现了IMessage接口,这样就需要在枚举类中覆写接口中的抽象方法,由于Color是IMessage接口子类,所以每一个枚举对象都可以通过对象的向上转型实现IMessage接口对象实例化。
        枚举还有一个特别的功能就是可以直接进行抽象方法的定义,此时可以在每一个枚举对象中分别实现此抽象方法。

范例:在枚举中定义抽象方法

package cn.demo;
enum Color
{
RED("红色")
{
@Overide
public String getMessage(){return "red"+this;}
},GRENN("绿色"){@Overide public String getMessage(){return "BLUE"+this;}},BLUE("蓝色")
{
@Override 
public String getMessage(){return "BLUE"+this;}
};
 

private String title;
private Coloe(String title){this.title=titlle;}
@Override
public String toString(){return this.title;}
public abstract String getMessage();
}

本程序在枚举应用中利用abstract关键字定义了一个抽象方法,这样就必须在每一个枚举类对象中分别覆写此抽象方法。

10.4.4 枚举应用案例

枚举主要是定义了实例化对象的适用范围,同时枚举类型也可以作为成员属性类型。例如,现在定义一个Person类,里面需要提供有性别属性,二性别肯定不希望用户随意输入,所以使用枚举类型最为合适。

package cn.demo;
enum Sex
{
MALE("男"),FEMALE("女");
private String title;
private Sex(String title){this,title=titlle;}
@Overide
public String toString(){return this.title;}
}

class Person
{
private String name;
private int age;
private Sex sex;
public Person(String name,int age,Sex sex){this.name=name;this.age=age;this.sex=sex;}
public String toString(){return ""+this.name+this.age+this.sex;}
}

public class JavaDemo
{
public static void main(String args[])
{
...
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值