兴趣新闻
a)文件名不包括域名、路径和URL参数,例如http://www.rs.com/n.op/q/rs?id=1中的文件名是rs。
b)部分URL可能没有文件名,例如http://www.abc.com/,这类统计为“空文件名”。
c)出现在不同URL中的相同文件名视为同一文件名,例如http://www.ceshi.com/hi.php
和ftp://ftp.cdef.com/hi.php为同一文件名
文件内容示例如下:
http://www.test.com/abc/de/fg.php?id=1&url=http://www.test.com/index.html
http://www.ceshi.com/hi.jsp
ftp://ftp.ceshi.com/hi.jsp
http://www.hello.com/cw/hi.jsp?k=8
http://www.hi.com/jk/l.html?id=1&s=a.html
http://www.rs.com/n.op/q/rs?id=1
http://www.abc.com/
package bai;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
public class Test {
public static Set<String> readFile(){
Set<String> set = new HashSet<String>();
String _str;
try {
File f=new File("d:\\test.txt");
BufferedReader reader = new BufferedReader(new FileReader(f));
String line;
while((line=reader.readLine())!=null){
_str = reads(line);
if(!_str.equals("")&&_str!=null){
set.add(_str);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return set;
}
public static String reads(String str){
int _len = str.lastIndexOf("?");
if(_len>0){
str = str.substring(0,_len);
}
return str.substring(str.lastIndexOf("/")+1,str.length()).trim();
}
public static void main(String args[]){
for(String str:Test.readFile()){
System.out.println(str);
}
System.out.println(Test.readFile().size());
}
}
剩下估计也就一块了,sso(单点登陆)。
需求吗?
1。用户信息实现可扩展性(这个可以搞定用一个用户扩展表就可以了)
2。用户要实现可以分组,这就必要要引如组的概念;
3。还要实现角色自己个东西,(有点不明白)
4。还要对我们所有的url资源实现访问控制(Acegi做到了这一点,而且还可以对service的ap的method的执行实现控制,简直令我高兴死了,这样就可以从根本上实现权力动作的分发)。
这几者之间的逻辑关系到现在我还没有搞明白!希望得到高人指点。
前几天 断断续续的上网查了一下关于sso的开源代码,结果发觉国内外基本都yale的cas 实现,cas只是听过,从来找不到合适的资料,自然无从下手了。
就在这一切都无法下手的时候,看了看五一买的《Spring in action》书,里边还真有关于这个问题在spring的介绍和解释,发觉spring的Acegi对yale的cas进行了应用,而且也是用IoC来实现配置,感觉真好呆了。
马上就用google 找Acegi的入门事例,结果还真找到了一个,特别简单,但是解释却特别的好,只讲指定的文件copy到tomcat下就,执行一下数据的sql语句就可以用了, 这是个学习计算机以来,应用第三方事例中唯一一个一次测试通过的。
今天算是立项吧!计划花两个星期弄一个可以简单应用的sso,接下来再扩展了,再优化。
这个星期的任务是把需要理解和解决的技术上基本问题给pass掉。今天基本任务完成了,事例的数据结构搞明白了。
明天是搞明白这个事例的在bean配置中的关系。实现密码加密。
ISBusinessExample16.java
package com.openv.spring;
public interface ISBusinessExample16 {
public String getStr(String args);
}
SBusinessExample16.java
package com.openv.spring;
public class SBusinessExample16 implements ISBusinessExample16 {
public String getStr(String args) {
return "Hello," + args + "!";
}
}
appcontext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="sbe16"
class="com.openv.spring.SBusinessExample16"/>
</beans>
ISBusinessExample16.java
package com.openv.spring;
public interface ISBusinessExample16 {
public String getStr(String args);
}
SBusinessExample16Home.java
package com.openv.spring;
mport javax.ejb.EJBHome;
public interface SBusinessExample16Home extends EJBHome {
public SBusinessExample16Remote create() throws javax.ejb.CreateException,
java.rmi.RemoteException;
}
SBusinessExample16Remote.java
package com.openv.spring;
public interface SBusinessExample16Remote extends EJBObject {
public String getStr(String args) throws java.rmi.RemoteException;
}
SBusinessExample16Bean.java
package com.openv.spring;
import javax.ejb.CreateException;
import org.springframework.ejb.support.AbstractStatelessSessionBean;
public class SBusinessExample16Bean extends AbstractStatelessSessionBean
implements ISBusinessExample16 {
private ISBusinessExample16 sbe;
protected void onEjbCreate() throws CreateException {
sbe = (ISBusinessExample16) getBeanFactory().getBean("sbe16");
}
public String getStr(String args) {
return sbe.getStr(args);
}
}
ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" version="2.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">
<display-name>SBusinessExample16Bean</display-name>
<enterprise-beans>
<session>
<ejb-name>SBusinessExample16Bean</ejb-name>
<home>com.openv.spring.SBusinessExample16Home</home>
<remote>com.openv.spring.SBusinessExample16Remote</remote>
<ejb-class>com.openv.spring.SBusinessExample16Bean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<env-entry>
<env-entry-name>ejb/BeanFactoryPath</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>appcontext.xml</env-entry-value>
</env-entry>
</session>
</enterprise-beans>
</ejb-jar>
jboss.xml
<!DOCTYPE jboss PUBLIC
"-//JBoss//DTD JBOSS 4.0//EN"
"http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
<jboss>
<enterprise-beans>
<session>
<ejb-name>SBusinessExample16Bean</ejb-name>
<jndi-name>SBusinessExample16Bean</jndi-name>
</session>
</enterprise-beans>
</jboss>
SBusinessExample16BeanTest.java 测试类
package com.openv.spring;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import junit.framework.TestCase;
public class SBusinessExample16BeanTest extends TestCase {
protected com.openv.spring.SBusinessExample16Home home;
protected Context getInitialContext() throws Exception {
Hashtable props = new Hashtable();
props.put(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
props.put(Context.URL_PKG_PREFIXES,
"org.jboss.naming:org.jnp.interfaces");
props.put(Context.PROVIDER_URL, "jnp://localhost:1099");
Context ctx = new InitialContext(props);
return ctx;
}
protected com.openv.spring.SBusinessExample16Home getHome()
throws Exception {
Context ctx = this.getInitialContext();
Object o = ctx.lookup("SBusinessExample16Bean");
com.openv.spring.SBusinessExample16Home intf =
(com.openv.spring.SBusinessExample16Home) PortableRemoteObject
.narrow(o, com.openv.spring.SBusinessExample16Home.class);
return intf;
}
public static void main(String[] args) {
junit.swingui.TestRunner.run(SBusinessExample16BeanTest.class);
}
public SBusinessExample16BeanTest(String arg0) {
super(arg0);
}
protected void setUp() throws Exception {
super.setUp();
this.home = this.getHome();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testGetStr() {
com.openv.spring.SBusinessExample16Remote instance;
String result;
try{
String param0 = "xxxxx";
instance=this.home.create();
result =instance.getStr(param0);
System.out.println(result);
}
catch(javax.ejb.CreateException c)
{
c.printStackTrace();
}
catch(java.rmi.RemoteException e)
{
e.printStackTrace();
}
}
}
appcontextclient.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="sbe16client"
class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
<property name="jndiName">
<value>SBusinessExample16Bean</value>
</property>
<property name="resourceRef">
<value>false</value>
</property>
<property name="jndiTemplate">
<ref local="jndiTemplate"></ref>
</property>
<property name="businessInterface">
<value>com.openv.spring.ISBusinessExample16</value>
</property>
</bean>
<bean id="jndiTemplate"
class="org.springframework.jndi.JndiTemplate">
<property name="environment">
<props>
<prop key="java.naming.factory.initial">
org.jnp.interfaces.NamingContextFactory
</prop>
<prop key="java.naming.provider.url">
jnp://localhost:1099
</prop>
<prop key="java.naming.factory.url.pkgs">
org.jboss.naming:org.jnp.interfaces
</prop>
</props>
</property>
</bean>
</beans>
SBusinessExample16Client.java 客服端
package com.openv.spring;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jndi.JndiObjectFactoryBean;
public class SBusinessExample16Client {
protected static final Log log = LogFactory.getLog(SBusinessExample16Client.class);
public static void client() {
Resource resource = new ClassPathResource("/com/openv/spring/appcontextclient.xml");
BeanFactory factory = new XmlBeanFactory(resource);
ISBusinessExample16 se = (ISBusinessExample16) factory.getBean("sbe16client");
log.info(se.getStr("nimda"));
}
}
SBusinessExample16ClientTest.java 客端测试类
package com.openv.spring;
import junit.framework.TestCase;
public class SBusinessExample16ClientTest extends TestCase {
p ublic static void main(String[] args) {
}
public SBusinessExample16ClientTest(String arg0) {
super(arg0);
}
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testClient() {
SBusinessExample16Client t=new SBusinessExample16Client();
t.client();
}
}
运行 环境 jboss4.0x ecilpse3.spring junit;
在这个EJB中,
SBusinessExample16BeanTest测试
client端测试,结果是成功的。
Clien是远程调用;
由于只有一台电脑,同时也只能一个jvm中运行
假如我要在两台机器上运行的的话 a 做服务器(jboss) b做客服机;
ISBusinessExample16 接口定义是错误的,因为在b机器的jvm中没有这个接口的存在,是是还要象数据库驱动一样打包后加载到b机器里边呀!
<?xml version="1.0"?>
<project name="haohao" default="run" basedir=".">
<path id="lib">
<fileset dir="WEB-INF/lib/jakarta-commons">
<include name="commons-logging.jar" />
</fileset>
<fileset dir="lib/hibernate">
<include name="*.jar" />
</fileset>
<fileset dir="lib">
<include name="*.jar" />
</fileset>
<fileset dir="lib">
<include name="spring.jar" />
</fileset>
<fileset dir="lib">
<include name="*.dtd" />
</fileset>
</path>
<target name="run" depends="compile" description="Run HelloWorldClient">
<java classname="haohao.HelloWorldClient" fork="yes">
<classpath refid="lib"/>
<classpath path="WEB-INF/classes"/>
</java>
</target>
<target name="compile" depends="prepare">
<javac destdir="WEB-INF/classes" deprecation="false" optimize="false" failonerror="true">
<src path="WEB-INF/src"/>
<classpath refid="lib"/>
</javac>
</target>
<target name="prepare">
<delete dir="WEB-INF/classes" />
<mkdir dir="WEB-INF/classes" />
<copy todir="WEB-INF/classes">
<fileset dir="WEB-INF/src">
<include name="*.xml"/>
</fileset>
</copy>
</target>
</project>
ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Datasource that works in any application server
You could easily use J2EE data source instead if this were
running inside of a J2EE container.
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://localhost/haohao</value></property>
<property name="username"><value>root</value></property>
<property name="password"><value>weiwei</value></property>
</bean>
<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<!-- Must references all OR mapping files. -->
<property name="mappingResources">
<list>
<value>User.hbm.xml</value>
</list>
</property>
<!-- Set the type of database; changing this one property will port this to Oracle,
MS SQL etc. -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
</props>
</property>
</bean>
<!-- Pass the session factory to our UserDAO -->
<bean id="userDAOTarget" class="haohao.UserDAOImpl">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
<bean id="userDAO"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref local="transactionManager"/></property>
<property name="target"><ref local="userDAOTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="addUser">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- Pass the session factory to our UserDAO
<bean id="userDAO" class="haohao.UserDAOImpl">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
-->
</beans>
User.java
package haohao;
import java.util.ArrayList;
import java.util.List;
public class User {
public long id ;
public String name ;
public long getId() {
return id;
}
public void setId(long id) {
this.id =id ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
}
User.java
package haohao;
import java.util.List;
public interface UserDAO {
public List getUsers();
public void addUser(User user);
public User findUserByName(String name);
public void removeUser(String name);
}
}
UserDAOImpl.java
package haohao;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.Query;
import org.springframework.orm.hibernate.HibernateCallback;
import org.springframework.orm.hibernate.support.HibernateDaoSupport;
public class UserDAOImpl extends HibernateDaoSupport implements UserDAO{
public List getUsers(){
return getHibernateTemplate().findByNamedQuery("");
}
public User findUserByName(String name) {
return (User) getHibernateTemplate().find("from User u where u.name=?",name).get(0);
}
public void addUser(User user){
getHibernateTemplate().save(user);
}
public void removeUser (String name) {
User user = findUserByName(name);
this.getHibernateTemplate().delete(name);
}
}
package haohao;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class HelloWorldClient{
protected static final Log log=LogFactory.getLog(HelloWorldClient.class);
public static UserDAO userDAO;
/*static {
ApplicationContextFactory.init("haohao/src/applicationContext.xml");
} */
public static void main(String[] args){
Resource resource=new ClassPathResource("applicationContext.xml");
BeanFactory factory=new XmlBeanFactory( resource);
userDAO=(UserDAO)factory.getBean("userDAO");
User user = userDAO.findUserByName("haohao");
String name = user.getName();
log.info(name);
}
}
编译通过.提示说我的sessionfactroy不能创建设.
我弄了整整两天没有通过
方法(如字符串转换) 和域(常量)的归属
创建包装对象来保存值。如用Hashtable存储数值?
(2) 类型层次short和byte没有相应类
(3) 构造器
据基本类型值创建对象:Character(char)
对String解码以创建对象
(4) 基本方法
toString
typeValue:产生基本类型值,如Character.charValue
equals:比较相同类型类对象是否相等
hashCode:哈希码。
14.2 Boolean
valueOf方法和解码字符串的构造器都把"true"(而不论大小写)理解为true;任何其它字符串都是false。
14.3 Character
(1) 常量
MIN_VALUE=’\u0000’ MAX_VALUE=’\uffff’
MIN/MAX_RADIX: 在基数转换(单一字符数字和整数值之间)方法中允许的最小/大基数值。基数:2-36;值大于9的数字是A(a)到Z(z)。
(2) 方法
public static int digit(char ch, int radix):用指定数制返回ch的数值。若字符是无效数字,返回-1。
public static char forDigit(int digit, int radix): 返回指定数制下指定数字的字符值。如果在该数制下数字无效,返回字符\u0000。
public static boolean isDefined(char ch): Unicode字符
public static boolean isLowerCase(char ch) :
public static boolean isUpperCase(char ch)
public static boolean isTitleCase(char ch):标题体字母
public static boolean isDigit(char ch):
public static boolean isLetter(char ch):字母
public static boolean isLetterOrDigit(char ch)
public static boolean isJavaLetter(char ch):ch可作为Java标识符的第一个字符,即字母、'_'或'$' 。
public static boolean isJavaLetterOrDigit(char ch):如果ch可以在Java标识符的第一个字符后出现则返回true。
public static boolean isSpace(char ch): 空格,即 ' '、'\t'、'\n'、'\f'或'\r'
public static char toLowerCase(char ch): 返回ch相应的小写体字符。若没有相应的小写体,则返回ch。
public static char toUpperCase(char ch): 返回ch相应的大写体字符。若没有相应的大写体,则返回ch。
public static char toTitleCase(char ch):返回ch相应的标题体字符,若没有,返回toUpperCase(ch)的结果。
public static int digit(char ch, radix): 返回给定数制下ch对应的数值,如数制无效或ch无效返回-1。
public static char forDigit(int digit, radix): 返回在给定数制下digit对应的字符。若digit大于radix或radix超出了范围,则返回-1。
14.4 Number: 抽象类
(1) 抽象方法: 返回数字类型对象的值:
public int intValue()
public long longValue()
public float floatValue()
public double doubleValue()
(2) 方法和常量:
toString(type)/valueOf(String)
static final常量MIN_VALUE和MAX_VALUE。
14.5 Integer
public static int parseInt(String str, int radix) throws NumberFormatException: 指定数制下str的整数值,如不能将串分析成int,NumberFormatException。
public static int parseInt(String s) throws NumberFormatException: 等价于parseInt(str, 10)。
public static Integer valueOf(String s, int radix) throws NumberFormatException: 与parseInt(str, radix)相似,只是返回Integer对象而不是int。
public static String toString(int i, int radix): 指定数制下i的String对象,缺省toString方法假设数制为10。
静态方法toHexString、toOctalString和toBinaryString分别将int参数转换成相应的16/8/2进制串。
14.6 Long
public static long parseLong(String str, int radix) throws NumberFormatException:指定数制下str的long值
public static long parseLong(String str) throws NumberFormatException 等价于parseLong(str, 10)。
public static long valueOf(String str, int radix) throws NumberFormatException : 与parseLong(str, radix)相似,只是返回一个Long对象而不是long。
public static String toString(long l, int radix): 指定数制下表示l的String对象,缺省toString数制为10。
toHexString、toOctalString和toBinaryString分别将一个long参数转换成相应的16进制、8进制或2进制串。
14.7 Float/Double
(1) 常量:
public final static float POSITIVE_INFINITY: +∞值。
public final static float NEGATIVE_INFINITY: -∞值。
public final static float NaN: 非数。提供得到NaN值的工具,并非用于测试。测试用isNaN方法。
(2) 方法
public static boolean isNaN(float val)
public boolean isNaN(): 当前对象
public static boolean isInfinite(float val): 正/负无穷
public boolean isInfinite()
public static int floatToIntBits(float value): 对一个float值返回一个int型的字位表示。
符号(31),阶(30-23), 尾数(22-0)
正无穷大:0x7f 80 00 00
负无穷大:0xff 80 00 00
NaN: 0x7f c0 00 00
public static float intBitsToFloat(int bits): 返回与给定字位表示相应的float。
public static long doubleToLongBits(double value): 对一个double值返回一个long型的字位表示。
符号(63),阶(62-52), 尾数(51-0)
正无穷大:0x7f f0 00 00 00 00 00 00L
负无穷大:0xff f0 00 00 00 00 00 00L
NaN: 0x7f f8 00 00 00 00 00 00L
public static double longBitsToDouble(long bits): 返回与给定字位表示相应的double。
每个类和接口都有Class对象,用于基本查询,并可创建新对象。
指定类型对象的创建和使用特殊方法的类加载,如通过网络。
(1) 方法
static Class forName(String className) throws ClassNotFoundException: 根据calssName提供的类全名返回class对象
ClassLoader getClassLoader ():若没有,返回null
Class[] getInterfaces():返回当前类/接口所实现/继承的若干接口。若没有,返回数组的长度为0。
String getName():返回当前类的全名。
Class getSuperclass():返回父类。若当前类为Object类或接口,返回null
boolean isInterface():
Object newInstance() throws InstantiationException, IllegalAccessException: 为当前类创建实例.
String toString():class/interface开头。
(2) 例1: 输出特定类型所属的层次(递归)。
public class TypeDesc {
public java.io.PrintStream out = System.out;
private static String[]
basic = { "class", "interface" },
extended = { "extends", "implements" };
public void printType(Class type, int depth) {
if (type == null) return // Object's supertype is null
for (int i = 0; i < depth; i++)
out.print(" ");
String[] labels = (depth == 0 ? basic : extended);
out.print(labels[type.isInterface() ? 1 : 0] + " ");
out.println(type.getName());
// print out an interfaces this class implements
Class[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; i++)
printType(interfaces, depth + 1);
// recurse on the superclass
printType(type.getSuperclass(), depth + 1);
}
public static void main(String[] args) {
TypeDesc desc = new TypeDesc();
for (int i = 0; i < args.length; i++) {
try { desc.printType(Class.forName(args), 0);
} catch (ClassNotFoundException e) {
System.err.print(e); // report the error
}
}
}
}
SampleOutput: class java.util.Hashtable
implements java.lang.Cloneable
extends java.lang.Object
extends java.util.Dictionary
extends java.lang.Object
(3) 例2: 动态选择排序算法
static double[] testData = { 0.3, 1.3e-2, 7.9, 3.17, };
public static void main(String[] args) {
try {
for (int arg = 0; arg < args.length; arg++) {
String name = args[arg];
Class classFor = Class.forName(name);
SortDouble sorter
= (SortDouble)classFor.newInstance();
SortMetrics metrics
= sorter.sort(testData);
System.out.println(name + ": " + metrics);
for (int i = 0; i < testData.length; i++)
System.out.println("" + testData);
}
} catch (Exception e)
System.err.print(e); // report the error
}
}
14.9 Class的加载
(1) 类的加载:装入程序运行时需要的代码。
通常做法:用“类路径”机制搜索。
问题:运行时动态加载编译后的类文件?如果代码不是文件又如何?
(2) 方法
protected final Class defineClass(byte data[], int offset, int length): data转换成Class的一个实例。
protected final Class findSystemClass(String name): 根据name在需要时加载系统类(无需加载器)
protected abstract Class loadClass(String name, boolean resolve):根据name对类进行解析。若resolve为true,则该方法将调用方法resolveClass。
protected final void resolveClass(Class c):在创建实例前用来解释它,使得所有该类引用的类也都被加载。
(3) 例1: 网络下载后加载
ClassLoader loader = new NetworkClassLoader(host,port);
Object main = loader.loadClass(“main”,true).newInstance();
……
public class NetworkClassLoader {
String host; int Port;
Hashtable cache = new Hashable();
private byte loadClassData(String name)[] {
// load the class data from the connection,…
}
public synchronized Class loadClass(String name, boolean resolve){
Class c = cache.get(name);
if (c==null) {
byte data[]=loadClassData(name);
c=defineClass(data,0,data.length);
cache.put(name,c);
}
if (resolve) resolveClass( c );
return c;
}
}
(4) 例2: 动态加载类文件
编制游戏能让游戏者写类并使用任何他们所选择的策略来玩游戏。提供一个抽象的Player类,游戏者将扩展该类实现他们的策略。
public class Game {
static public void main(String[] args) {
String name; // the class name
while((name = getNextPlayer()) != nulll) {
try {
PlayerLoader loader = new PlayerLoader();
Class
classOf = loader.loadClass(name,true);
Player
player = (Player)classOf.newInstance();
Game game = new Game();
player.play(game);
game.reportScore(name);
} catch (Exception e) {
reportException(name,e);
}
}
}
}
//PlayerLoader类扩展了ClasssLoader以实现loadClass
class PlayerLoader extends ClassLoader {
private Hashtable Classes = new Hashtable();
public Class loadClass(String name, loolean resolve)
throws ClassNotFoundException
{ try {
Class newClass = (Class)Classes.get(name);
if (newClass == null) { // not yet defined
try { // check if system class
newClass = findSystemClass(name);
if (newClass != null)
return newClass;
} catch (ClassNotFoundException e) {
; // keep on looking
}
// class not found -- need to load it
byte[] buf = bytesForClass(name);
newClass = defineClass(buf, 0, buf.length);
Classes.put(name, newClass);
}
if (resolve)
resolveClass(newClass);
return newClass;
} catch (IOException e) {
throw new ClassNotFoundException(e.toString());
}
}
// ... byteForClass() and any other methods ...
protectd byte[] bytesForClass(String name) //undefined
throws IOException, ClassNotFoundException
{
FileInputStream in = streamFor(name);
int length = in.available(); // get byte count
if (length == 0)
throws new ClassNotFoundException(name);
byte[] buf = new byte[length];
in.read(buf); // read the bytes
return buf;
}
}
运行期类型鉴定(RTTI)的概念初看非常简单――手上只有基础类型的一个句柄时,利用它判断一个对象的正确类型。
然而,对RTTI的需要暴露出了面向对象设计许多有趣(而且经常是令人困惑的)的问题,并把程序的构造问题正式摆上了桌面。
本章将讨论如何利用Java在运行期间查找对象和类信息。这主要采取两种形式:一种是“传统”RTTI,它假定我们已在编译和运行期拥有所有类型;另一种是Java1.1特有的“反射”机制,利用它可在运行期独立查找类信息。首先讨论“传统”的RTTI,再讨论反射问题。
11.1 对RTTI的需要
请考虑下面这个熟悉的类结构例子,它利用了多形性。常规类型是Shape类,而特别衍生出来的类型是Circle,Square和Triangle。
516页图
这是一个典型的类结构示意图,基础类位于顶部,衍生类向下延展。面向对象编程的基本目标是用大量代码控制基础类型(这里是Shape)的句柄,所以假如决定添加一个新类(比如Rhomboid,从Shape衍生),从而对程序进行扩展,那么不会影响到原来的代码。在这个例子中,Shape接口中的动态绑定方法是draw(),所以客户程序员要做的是通过一个普通Shape句柄调用draw()。draw()在所有衍生类里都会被覆盖。而且由于它是一个动态绑定方法,所以即使通过一个普通的Shape句柄调用它,也有表现出正确的行为。这正是多形性的作用。
所以,我们一般创建一个特定的对象(Circle,Square,或者Triangle),把它上溯造型到一个Shape(忽略对象的特殊类型),以后便在程序的剩余部分使用匿名Shape句柄。
作为对多形性和上溯造型的一个简要回顾,可以象下面这样为上述例子编码(若执行这个程序时出现困难,请参考第3章3.1.2小节“赋值”):
516-517页程序
基础类可编码成一个interface(接口)、一个abstract(抽象)类或者一个普通类。由于Shape没有真正的成员(亦即有定义的成员),而且并不在意我们创建了一个纯粹的Shape对象,所以最适合和最灵活的表达方式便是用一个接口。而且由于不必设置所有那些abstract关键字,所以整个代码也显得更为清爽。
每个衍生类都覆盖了基础类draw方法,所以具有不同的行为。在main()中创建了特定类型的Shape,然后将其添加到一个Vector。这里正是上溯造型发生的地方,因为Vector只容纳了对象。由于Java中的所有东西(除基本数据类型外)都是对象,所以Vector也能容纳Shape对象。但在上溯造型至Object的过程中,任何特殊的信息都会丢失,其中甚至包括对象是几何形状这一事实。对Vector来说,它们只是Object。
用nextElement()将一个元素从Vector提取出来的时候,情况变得稍微有些复杂。由于Vector只容纳Object,所以nextElement()会自然地产生一个Object句柄。但我们知道它实际是个Shape句柄,而且希望将Shape消息发给那个对象。所以需要用传统的"(Shape)"方式造型成一个Shape。这是RTTI最基本的形式,因为在Java中,所有造型都会在运行期间得到检查,以确保其正确性。那正是RTTI的意义所在:在运行期,对象的类型会得到鉴定。
在目前这种情况下,RTTI造型只实现了一部分:Object造型成Shape,而不是造型成Circle,Square或者Triangle。那是由于我们目前能够肯定的唯一事实就是Vector里充斥着几何形状,而不知它们的具体类别。在编译期间,我们肯定的依据是我们自己的规则;而在编译期间,却是通过造型来肯定这一点。
现在的局面会由多形性控制,而且会为Shape调用适当的方法,以便判断句柄到底是提供Circle,Square,还是提供给Triangle。而且在一般情况下,必须保证采用多形性方案。因为我们希望自己的代码尽可能少知道一些与对象的具体类型有关的情况,只将注意力放在某一类对象(这里是Shape)的常规信息上。只有这样,我们的代码才更易实现、理解以及修改。所以说多形性是面向对象程序设计的一个常规目标。
然而,若碰到一个特殊的程序设计问题,只有在知道常规句柄的确切类型后,才能最容易地解决这个问题,这个时候又该怎么办呢?举个例子来说,我们有时候想让自己的用户将某一具体类型的几何形状(如三角形)全都变成紫色,以便突出显示它们,并快速找出这一类型的所有形状。此时便要用到RTTI技术,用它查询某个Shape句柄引用的准确类型是什么。
11.1.1 Class对象
为理解RTTI在Java里如何工作,首先必须了解类型信息在运行期是如何表示的。这时要用到一个名为“Class对象”的特殊形式的对象,其中包含了与类有关的信息(有时也把它叫作“元类”)。事实上,我们要用Class对象创建属于某个类的全部“常规”或“普通”对象。
对于作为程序一部分的每个类,它们都有一个Class对象。换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当地说,是保存在一个完全同名的.class文件中)。在运行期,一旦我们想生成那个类的一个对象,用于执行程序的Java虚拟机(JVM)首先就会检查那个类型的Class对象是否已经载入。若尚未载入,JVM就会查找同名的.class文件,并将其载入。所以Java程序启动时并不是完全载入的,这一点与许多传统语言都不同。
一旦那个类型的Class对象进入内存,就用它创建那一类型的所有对象。
若这种说法多少让你产生了一点儿迷惑,或者并没有真正理解它,下面这个示范程序或许能提供进一步的帮助:
519-520页程序
对每个类来说(Candy,Gum和Cookie),它们都有一个static从句,用于在类首次载入时执行。相应的信息会打印出来,告诉我们载入是什么时候进行的。在main()中,对象的创建代码位于打印语句之间,以便侦测载入时间。
特别有趣的一行是:
Class.forName("Gum");
该方法是Class(即全部Class所从属的)的一个static成员。而Class对象和其他任何对象都是类似的,所以能够获取和控制它的一个句柄(装载模块就是干这件事的)。为获得Class的一个句柄,一个办法是使用forName()。它的作用是取得包含了目标类文本名字的一个String(注意拼写和大小写)。最后返回的是一个Class句柄。
该程序在某个JVM中的输出如下:
520页中程序
可以看到,每个Class只有在它需要的时候才会载入,而static初始化工作是在类载入时执行的。
非常有趣的是,另一个JVM的输出变成了另一个样子:
520页下程序
看来JVM通过检查main()中的代码,已经预测到了对Candy和Cookie的需要,但却看不到Gum,因为它是通过对forName()的一个调用创建的,而不是通过更典型的new调用。尽管这个JVM也达到了我们希望的效果,因为确实会在我们需要之前载入那些类,但却不能肯定这儿展示的行为百分之百正确。
1. 类标记
在Java 1.1中,可以采用第二种方式来产生Class对象的句柄:使用“类标记”。对上述程序来说,看起来就象下面这样:
Gum.class;
这样做不仅更加简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以执行的效率也会更高。
类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据类型的封装器类,它还存在一个名为TYPE的标准字段。TYPE字段的作用是为相关的基本数据类型产生Class对象的一个句柄,如下所示:
……等价于……
521页表格略
11.1.2 造型前的检查
迄今为止,我们已知的RTTI形式包括:
(1) 经典造型,如"(Shape)",它用RTTI确保造型的正确性,并在遇到一个失败的造型后产生一个ClassCastException违例。
(2) 代表对象类型的Class对象。可查询Class对象,获取有用的运行期资料。
在C++中,经典的"(Shape)"造型并不执行RTTI。它只是简单地告诉编译器将对象当作新类型处理。而Java要执行类型检查,这通常叫作“类型安全”的下溯造型。之所以叫“下溯造型”,是由于类分层结构的历史排列方式造成的。若将一个Circle(圆)造型到一个Shape(几何形状),就叫做上溯造型,因为圆只是几何形状的一个子集。反之,若将Shape造型至Circle,就叫做下溯造型。然而,尽管我们明确知道Circle也是一个Shape,所以编译器能够自动上溯造型,但却不能保证一个Shape肯定是一个Circle。因此,编译器不允许自动下溯造型,除非明确指定一次这样的造型。
RTTI在Java中存在三种形式。关键字instanceof告诉我们对象是不是一个特定类型的实例(Instance即“实例”)。它会返回一个布尔值,以便以问题的形式使用,就象下面这样:
if(x instanceof Dog)
((Dog)x).bark();
将x造型至一个Dog前,上面的if语句会检查对象x是否从属于Dog类。进行造型前,如果没有其他信息可以告诉自己对象的类型,那么instanceof的使用是非常重要的――否则会得到一个ClassCastException违例。
我们最一般的做法是查找一种类型(比如要变成紫色的三角形),但下面这个程序却演示了如何用instanceof标记出所有对象。
522-524页程序
在Java 1.0中,对instanceof有一个比较小的限制:只可将其与一个已命名的类型比较,不能同Class对象作对比。在上述例子中,大家可能觉得将所有那些instanceof表达式写出来是件很麻烦的事情。实际情况正是这样。但在Java 1.0中,没有办法让这一工作自动进行――不能创建Class的一个Vector,再将其与之比较。大家最终会意识到,如编写了数量众多的instanceof表达式,整个设计都可能出现问题。
当然,这个例子只是一个构想――最好在每个类型里添加一个static数据成员,然后在构建器中令其增值,以便跟踪计数。编写程序时,大家可能想象自己拥有类的源码控制权,能够自由改动它。但由于实际情况并非总是这样,所以RTTI显得特别方便。
1. 使用类标记
PetCount.java示例可用Java 1.1的类标记重写一遍。得到的结果显得更加明确易懂:
524-526页程序
在这里,typenames(类型名)数组已被删除,改为从Class对象里获取类型名称。注意为此而额外做的工作:例如,类名不是Getbil,而是c11.petcount2.Getbil,其中已包含了包的名字。也要注意系统是能够区分类和接口的。
也可以看到,petTypes的创建模块不需要用一个try块包围起来,因为它会在编译期得到检查,不会象Class.forName()那样“掷”出任何违例。
Pet动态创建好以后,可以看到随机数字已得到了限制,位于1和petTypes.length之间,而且不包括零。那是由于零代表的是Pet.class,而且一个普通的Pet对象可能不会有人感兴趣。然而,由于Pet.class是petTypes的一部分,所以所有Pet(宠物)都会算入计数中。
2. 动态的instanceof
Java 1.1为Class类添加了isInstance方法。利用它可以动态调用instanceof运算符。而在Java 1.0中,只能静态地调用它(就象前面指出的那样)。因此,所有那些烦人的instanceof语句都可以从PetCount例子中删去了。如下所示:
526-528页程序
可以看到,Java 1.1的isInstance()方法已取消了对instanceof表达式的需要。此外,这也意味着一旦要求添加新类型宠物,只需简单地改变petTypes数组即可;毋需改动程序剩余的部分(但在使用instanceof时却是必需的)。
11.2 RTTI语法
Java用Class对象实现自己的RTTI功能――即便我们要做的只是象造型那样的一些工作。Class类也提供了其他大量方式,以方便我们使用RTTI。
首先必须获得指向适当Class对象的的一个句柄。就象前例演示的那样,一个办法是用一个字串以及Class.forName()方法。这是非常方便的,因为不需要那种类型的一个对象来获取Class句柄。然而,对于自己感兴趣的类型,如果已有了它的一个对象,那么为了取得Class句柄,可调用属于Object根类一部分的一个方法:getClass()。它的作用是返回一个特定的Class句柄,用来表示对象的实际类型。Class提供了几个有趣且较为有用的方法,从下例即可看出:
528-529页程序
从中可以看出,class FancyToy相当复杂,因为它从Toy中继承,并实现了HasBatteries,Waterproof以及ShootsThings的接口。在main()中创建了一个Class句柄,并用位于相应try块内的forName()初始化成FancyToy。
Class.getInterfaces方法会返回Class对象的一个数组,用于表示包含在Class对象内的接口。
若有一个Class对象,也可以用getSuperclass()查询该对象的直接基础类是什么。当然,这种做会返回一个Class句柄,可用它作进一步的查询。这意味着在运行期的时候,完全有机会调查到对象的完整层次结构。
若从表面看,Class的newInstance()方法似乎是克隆(clone())一个对象的另一种手段。但两者是有区别的。利用newInstance(),我们可在没有现成对象供“克隆”的情况下新建一个对象。就象上面的程序演示的那样,当时没有Toy对象,只有cy――即y的Class对象的一个句柄。利用它可以实现“虚拟构建器”。换言之,我们表达:“尽管我不知道你的准确类型是什么,但请你无论如何都正确地创建自己。”在上述例子中,cy只是一个Class句柄,编译期间并不知道进一步的类型信息。一旦新建了一个实例后,可以得到Object句柄。但那个句柄指向一个Toy对象。当然,如果要将除Object能够接收的其他任何消息发出去,首先必须进行一些调查研究,再进行造型。除此以外,用newInstance()创建的类必须有一个默认构建器。没有办法用newInstance()创建拥有非默认构建器的对象,所以在Java 1.0中可能存在一些限制。然而,Java 1.1的“反射”API(下一节讨论)却允许我们动态地使用类里的任何构建器。
程序中的最后一个方法是printInfo(),它取得一个Class句柄,通过getName()获得它的名字,并用interface()调查它是不是一个接口。
该程序的输出如下:
530页程序
所以利用Class对象,我们几乎能将一个对象的祖宗十八代都调查出来。
11.3 反射:运行期类信息
如果不知道一个对象的准确类型,RTTI会帮助我们调查。但却有一个限制:类型必须是在编译期间已知的,否则就不能用RTTI调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道RTTI要处理的所有类。
从表面看,这似乎并不是一个很大的限制,但假若得到的是一个不在自己程序空间内的对象的句柄,这时又会怎样呢?事实上,对象的类即使在编译期间也不可由我们的程序使用。例如,假设我们从磁盘或者网络获得一系列字节,而且被告知那些字节代表一个类。由于编译器在编译代码时并不知道那个类的情况,所以怎样才能顺利地使用这个类呢?
在传统的程序设计环境中,出现这种情况的概率或许很小。但当我们转移到一个规模更大的编程世界中,却必须对这个问题加以高度重视。第一个要注意的是基于组件的程序设计。在这种环境下,我们用“快速应用开发”(RAD)模型来构建程序项目。RAD一般是在应用程序构建工具中内建的。这是编制程序的一种可视途径(在屏幕上以窗体的形式出现)。可将代表不同组件的图标拖曳到窗体中。随后,通过设定这些组件的属性或者值,进行正确的配置。设计期间的配置要求任何组件都是可以“例示”的(即可以自由获得它们的实例)。这些组件也要揭示出自己的一部分内容,允许程序员读取和设置各种值。此外,用于控制GUI事件的组件必须揭示出与相应的方法有关的信息,以便RAD环境帮助程序员用自己的代码覆盖这些由事件驱动的方法。“反射”提供了一种特殊的机制,可以侦测可用的方法,并产生方法名。通过Java Beans(第13章将详细介绍),Java 1.1为这种基于组件的程序设计提供了一个基础结构。
在运行期查询类信息的另一个原动力是通过网络创建与执行位于远程系统上的对象。这就叫作“远程方法调用”(RMI),它允许Java程序(版本1.1以上)使用由多台机器发布或分布的对象。这种对象的分布可能是由多方面的原因引起的:可能要做一件计算密集型的工作,想对它进行分割,让处于空闲状态的其他机器分担部分工作,从而加快处理进度。某些情况下,可能需要将用于控制特定类型任务(比如多层客户/服务器架构中的“运作规则”)的代码放置在一台特殊的机器上,使这台机器成为对那些行动进行描述的一个通用储藏所。而且可以方便地修改这个场所,使其对系统内的所有方面产生影响(这是一种特别有用的设计思路,因为机器是独立存在的,所以能轻易修改软件!)。分布式计算也能更充分地发挥某些专用硬件的作用,它们特别擅长执行一些特定的任务――例如矩阵逆转――但对常规编程来说却显得太夸张或者太昂贵了。
在Java 1.1中,Class类(本章前面已有详细论述)得到了扩展,可以支持“反射”的概念。针对Field,Method以及Constructor类(每个都实现了Memberinterface――成员接口),它们都新增了一个库:java.lang.reflect。这些类型的对象都是JVM在运行期创建的,用于代表未知类里对应的成员。这样便可用构建器创建新对象,用get()和set()方法读取和修改与Field对象关联的字段,以及用invoke()方法调用与Method对象关联的方法。此外,我们可调用方法getFields(),getMethods(),getConstructors(),分别返回用于表示字段、方法以及构建器的对象数组(在联机文档中,还可找到与Class类有关的更多的资料)。因此,匿名对象的类信息可在运行期被完整的揭露出来,而在编译期间不需要知道任何东西。
大家要认识的很重要的一点是“反射”并没有什么神奇的地方。通过“反射”同一个未知类型的对象打交道时,JVM只是简单地检查那个对象,并调查它从属于哪个特定的类(就象以前的RTTI那样)。但在这之后,在我们做其他任何事情之前,Class对象必须载入。因此,用于那种特定类型的.class文件必须能由JVM调用(要么在本地机器内,要么可以通过网络取得)。所以RTTI和“反射”之间唯一的区别就是对RTTI来说,编译器会在编译期打开和检查.class文件。换句话说,我们可以用“普通”方式调用一个对象的所有方法;但对“反射”来说,.class文件在编译期间是不可使用的,而是由运行期环境打开和检查。
11.3.1 一个类方法提取器
很少需要直接使用反射工具;之所以在语言中提供它们,仅仅是为了支持其他Java特性,比如对象序列化(第10章介绍)、Java Beans以及RMI(本章后面介绍)。但是,我们许多时候仍然需要动态提取与一个类有关的资料。其中特别有用的工具便是一个类方法提取器。正如前面指出的那样,若检视类定义源码或者联机文档,只能看到在那个类定义中被定义或覆盖的方法,基础类那里还有大量资料拿不到。幸运的是,“反射”做到了这一点,可用它写一个简单的工具,令其自动展示整个接口。下面便是具体的程序:
533-534页程序
Class方法getMethods()和getConstructors()可以分别返回Method和Constructor的一个数组。每个类都提供了进一步的方法,可解析出它们所代表的方法的名字、参数以及返回值。但也可以象这样一样只使用toString(),生成一个含有完整方法签名的字串。代码剩余的部分只是用于提取命令行信息,判断特定的签名是否与我们的目标字串相符(使用indexOf()),并打印出结果。
这里便用到了“反射”技术,因为由Class.forName()产生的结果不能在编译期间获知,所以所有方法签名信息都会在运行期间提取。若研究一下联机文档中关于“反射”(Reflection)的那部分文字,就会发现它已提供了足够多的支持,可对一个编译期完全未知的对象进行实际的设置以及发出方法调用。同样地,这也属于几乎完全不用我们操心的一个步骤――Java自己会利用这种支持,所以程序设计环境能够控制Java Beans――但它无论如何都是非常有趣的。
一个有趣的试验是运行java ShowMehods ShowMethods。这样做可得到一个列表,其中包括一个public默认构建器,尽管我们在代码中看见并没有定义一个构建器。我们看到的是由编译器自动合成的那一个构建器。如果随之将ShowMethods设为一个非public类(即换成“友好”类),合成的默认构建器便不会在输出结果中出现。合成的默认构建器会自动获得与类一样的访问权限。
ShowMethods的输出仍然有些“不爽”。例如,下面是通过调用java ShowMethods java.lang.String得到的输出结果的一部分:
534-535页程序
若能去掉象java.lang这样的限定词,结果显然会更令人满意。有鉴于此,可引入上一章介绍的StreamTokenizer类,解决这个问题:
535-537页程序
ShowMethodsClean方法非常接近前一个ShowMethods,只是它取得了Method和Constructor数组,并将它们转换成单个String数组。随后,每个这样的String对象都在StripQualifiers.Strip()里“过”一遍,删除所有方法限定词。正如大家看到的那样,此时用到了StreamTokenizer和String来完成这个工作。
假如记不得一个类是否有一个特定的方法,而且不想在联机文档里逐步检查类结构,或者不知道那个类是否能对某个对象(如Color对象)做某件事情,该工具便可节省大量编程时间。
第17章提供了这个程序的一个GUI版本,可在自己写代码的时候运行它,以便快速查找需要的东西。
11.4 总结
利用RTTI可根据一个匿名的基础类句柄调查出类型信息。但正是由于这个原因,新手们极易误用它,因为有些时候多形性方法便足够了。对那些以前习惯程序化编程的人来说,极易将他们的程序组织成一系列switch语句。他们可能用RTTI做到这一点,从而在代码开发和维护中损失多形性技术的重要价值。Java的要求是让我们尽可能地采用多形性,只有在极特别的情况下才使用RTTI。
但为了利用多形性,要求我们拥有对基础类定义的控制权,因为有些时候在程序范围之内,可能发现基础类并未包括我们想要的方法。若基础类来自一个库,或者由别的什么东西控制着,RTTI便是一种很好的解决方案:可继承一个新类型,然后添加自己的额外方法。在代码的其他地方,可以侦测自己的特定类型,并调用那个特殊的方法。这样做不会破坏多形性以及程序的扩展能力,因为新类型的添加不要求查找程序中的switch语句。但在需要新特性的主体中添加新代码时,就必须用RTTI侦测自己特定的类型。
从某个特定类的利益的角度出发,在基础类里加入一个特性后,可能意味着从那个基础类衍生的其他所有类都必须获得一些无意义的“鸡肋”。这使得接口变得含义模糊。若有人从那个基础类继承,且必须覆盖抽象方法,这一现象便会使他们陷入困扰。比如现在用一个类结构来表示乐器(Instrument)。假定我们想清洁管弦乐队中所有适当乐器的通气音栓(Spit Valve),此时的一个办法是在基础类Instrument中置入一个ClearSpitValve()方法。但这样做会造成一个误区,因为它暗示着打击乐器和电子乐器中也有音栓。针对这种情况,RTTI提供了一个更合理的解决方案,可将方法置入特定的类中(此时是Wind,即“通气口”)――这样做是可行的。但事实上一种更合理的方案是将prepareInstrument()置入基础类中。初学者刚开始时往往看不到这一点,一般会认定自己必须使用RTTI。
最后,RTTI有时能解决效率问题。若代码大量运用了多形性,但其中的一个对象在执行效率上很有问题,便可用RTTI找出那个类型,然后写一段适当的代码,改进其效率。
11.5 练习
(1) 写一个方法,向它传递一个对象,循环打印出对象层次结构中的所有类。
(2) 在ToyTest.java中,将Toy的默认构建器标记成注释信息,解释随之发生的事情。
(3) 新建一种类型的集合,令其使用一个Vector。捕获置入其中的第一个对象的类型,然后从那时起只允许用户插入那种类型的对象。
(4) 写一个程序,判断一个Char数组属于基本数据类型,还是一个真正的对象。
(5) 根据本章的说明,实现clearSpitValve()。
(6) 实现本章介绍的rotate(Shape)方法,令其检查是否已经旋转了一个圆(若已旋转,就不再执行旋转操作)。
--------------------------------------------------------------------------------
英文版主页 | 中文版主页 | 详细目录 | 关于译者
澄清Java(一)----接口与继承
Bromon原创 请尊重版权
计算机学院研二的兄弟与我讨论Java,一见面,几个问题全是关于接口,接口有什么用?为什么要用接口?什么时候该使用接口?很庆幸他们不是问我Java如何连接SQL Server,或者是如何开发J2EE应用,这类问题有杀伤力,避之则吉。今年计算机学院本科有个毕业设计课题是做J2ME,选这个题目的学生在5月末都还在苦着脸研究java.util.*这个包,这个这个……唉。
大多数人认为,接口的意义在于顶替多重继承。众所周知Java没有c++那样多重继承的机制,但是却能够实作多个接口。其实这样做是很牵强的,接口和继承是完全不同的东西,接口没有能力代替多重继承,也没有这个义务。接口的作用,一言以蔽之,就是标志类的类别(type of class)。把不同类型的类归于不同的接口,可以更好的管理他们。OO的精髓,我以为,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、java、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象。(cowboy的名言是“抽象就是抽去像的部分”,看似调侃,实乃至理)。
设计模式中最基础的是工厂模式(Factory),在我最近的一个很简单的应用中,我想尽量的让我的程序能够在多个数据库间移植,当然,这涉及很多问题,单是如何兼容不同DBMS的SQL就让人头痛。我们不妨先把问题简单化,只考虑如何连接不同的数据库。
假设我有很多个类,分别是Mysql.java、SQLServer.java、Oracle.java、DB2.java,他们分别连接不同的数据库,统一返回一个Connection对象,并且都有一个close方法,用于关闭连接。只需要针对你的DBMS,选择不同的类,就可以用了,但是我的用户他会使用什么数据库?我不知道,我希望的是尽量少的修改代码,就能满足他的需要。我可以抽象如下接口:
package org.bromon.test;
public interface DB
{
java.sql.Connection openDB(String url,String user,String password);
void close();
}
这个接口只定义两个方法,没有任何有实际意义的代码,具体的代码由实作这个接口的类来给出,比如Mysql.java:
Package org.bromon.test;
import java.sql.*;
public class Mysql implements DB
{
private String url=”jdbc:mysql:localhost:3306/test”;
private String user=”root”;
private String password=””;
private Connection conn;
public Connection openDB(url,user,password)
{
//连接数据库的代码
}
public void close()
{
//关闭数据库
}
}
类似的当然还有Oracle.java等等,接口DB给这些类归了个类,在应用程序中我们这样定义对象:
org.bromon.test.DB myDB;
使用myDB来操作数据库,就可以不用管实际上我所使用的是哪个类,这就是所谓的“开-闭”原则。但是问题在于接口是不能实例化的,myDB=new DB(),这样的代码是绝对错误的,我们只能myDB=new Mysql()或者myDB=new Oracle()。麻烦了,我还是需要指定具体实例化的是哪个类,用了接口跟没用一样。所以我们需要一个工厂:
package org.bromon.test;
public class DBFactory
{
public static DB Connection getConn()
{
Return(new Mysql());
}
}
所以实例化的代码变成:myDB=DBFactory.getConn();
这就是23种模式中最基础的普通工厂(Factory),工厂类负责具体实例化哪个类,而其他的程序逻辑都是针对DB这个接口进行操作,这就是“针对接口编程”。责任都被推卸给工厂类了,当然你也可以继续定义工厂接口,继续把责任上抛,这就演变成抽象工厂(Abstract Factory)。
整个过程中接口不负责任何具体操作,其他的程序要连接数据库的话,只需要构造一个DB对象就OK,而不管工厂类如何变化。这就是接口的意义----抽象。
继承的概念不用多说,很好理解。为什么要继承呢?因为你想重用代码?这绝对不是理由,继承的意义也在于抽象,而不是代码重用。如果对象A有一个run()方法,对象B也想有这个方法,所以有人就Class B extends A。这是不经大脑的做法。如果在B中实例化一个A,调用A的Run()方法,是不是可以达到同样的目的?如下:
Class B
{
A a=new A();
a.run();
}
这就是利用类的聚合来重用代码,是委派模式的雏形,是GoF一贯倡导的做法。
那么继承的意义何在?其实这是历史原因造成的,最开始的OO语言只有继承,没有接口,所以只能以继承来实现抽象,请一定注意,继承的本意在于抽象,而非代码重用(虽然继承也有这个作用),这是很多Java烂书最严重的错误之一,它们所造成的阴影,我至今还没有完全摆脱,坏书害人啊,尤其是入门类的,流毒太大。什么时候应该使用继承?只在抽象类中使用,其他情况下尽量不使用。抽象类也是不能实例化的,它仅仅提供一个模版而已,这就很能说明问题。
软件开发的万恶之源,一是重复代码而不是重用代码,二是烂用继承,尤以c++程序员为甚。Java中取缔多重继承,目的就是制止烂用继承,实是非常明智的做法,不过很多人都不理解。Java能够更好的体现设计,这是让我入迷的原因之一。
