第9章 抽象类与接口

本章需要掌握抽象类的定义和适用,并认真理解抽象类的组成特点,掌握包装类的特点,并且可以利用包装类实现字符串与基本数据类型间的转换处理,掌握接口的定义与使用,理解接口设计的目的,掌握工厂模式设计、代理模式的使用,理解泛型的作用以及相关的定义语法。
        抽象类与接口是在面向对象设计中最为重要的一个中间环节,利用抽象类与接口可以有效的拆分大型系统,避免产生耦合问题。本章将针对抽象类与接口的概念进行阐述。

9.1 抽象类

面向对象程序设计中,类继承的主要作用是扩充已有类的功能,子类可以根据自己的需要选择是否要覆写父类中的方法,所以一个设计完善的父类是无法对子类做出任何强制性的覆写约定。为了解决这样的设计问题,提出了抽象类的概念,抽象类与普通类相比唯一增加的就是抽象方法的定义,同事抽象类在使用时要求必须被子类所继承,并且子类必须覆写全部抽象方法。‘提示’:关于类继承的使用,普通类是指一个设计完善的类,这个类可以直接产生实例化对象并且调用类中的属性或方法;而抽象类最大的特点是必须有子类,并且无法直接进行对象实例化操作。在实际项目的开发中,很少会去继承设计完善的泪,大多都会考虑继承抽象类。

9.1.1 抽象类基本定义

抽象类需要使用abstract class 进行定义,并且在一个抽象类中也可以利用abstract关键自定义若干个抽象方法,这样抽象类的子类就必须在继承抽象类是强制覆写全部抽象方法。

范例:定义抽象类

abstract class Message
{
private String type;
public abstract String getConnectInfo();
public void setType(String type){this.type=type;}
public String getType()
{
return this.type;
}
}

本程序使用abstract关键字分别定义了抽象方法与抽象类,在定义抽象方法的时候只需定义方法名称而不需要定义方法体{},同时也可以发现,抽象类的定义就是在普通类的基础上追加了抽象方法的结构。
        抽象类并不是一个完整的类,对于抽象类的使用需要按照以下原则进行。
        抽象类必须提供子类,子类使用extends继承一个抽象类。
        抽象类的子类(不是抽象类)一定要覆写抽象类中的全部抽象方法。
        抽象类的对象实例化可以利用对象多态性通过子类向上周转性的方式完成。

范例:使用抽象类

abstract class Message
{
private String type;
public abstract String getConnectInfo();
public void setType(String type)
{
this.type=type;
}
public String getType()
{
return this.type;
}


}

class DatabaseMessage extends Message
{
@Override
public String getConnectInfo()
{
return ""+super.getType();
}
}

public class JavaDemo
{
public static void main(String args[])
{
Message msg=new DatabaseMessage();
msg.setType("AAAA");
System.out.println(msg.getConnectInfo());
}
}

本程序利用extends关键字定义了Message抽象类的子类DatabaseMessage,并且在DatabaseMessage子类中按照要求覆写了getConnectInfo()抽象方法,在主类中利用对象向上转型原则,通过子类实例化了Message类对象,这样当调用getConnectInfo()方法时执行的就是子类覆写的方法体。
提示:抽象类的实际使用:
抽象类最大的特点就是无法自己直接进行对象实例化操作,在实际项目开发中,抽象类的主要目的是进行过度操作使用。当你使用抽象类进行开发的时候,往往都是在你设计中需要类继承问题时所带来的代码重复处理。

9.1.1 抽象类相关说明

在面向对象设计中,抽象类是一个重要的组成结构,除了其基本的使用形式意外,还有以下急死俺注意事项。
(1)抽象类必须由子类继承,所以定义时不允许使用final关键字定义抽象类或者抽象方法。

范例:错误的抽象类定义
abstract final class Message
{
public final abstract String getConnectInfo();
}

(2)抽象类中可以定义成员属性与普通方法,为了可以为抽象类中的成员属性初始化,可以再抽象类中提供构造方法。子类在继承抽象类时会默认调用弗雷德无参构造,如果抽象类没有提供无参构造方法,则子类必须通过super()的形势调用指定参数的构造方法。

范例:抽象类中定义构造方法

abstract class Message
{
private String type;
public Messag(String type)
{
this.type=type;
}
public abstract String getConnectInfo();
}

class DatabaseMessage extends Message
{
public DatabaseMessage(String type)
{
super(type);
}
@override 
public String getConnectInfo()
{
return ""+super.getType();
}
}

public class JavaDemo
{
public static void main(String args[])
{
Message msg=new DatabaseMessage();
msg.setType("AAAA");
System.out.println(msg.getConnectInfo());
}
}

本程序利用extends关键字定义了Message抽象类的子类DatabaseMessage,并且在DatabaseMessage子类中按照要求覆写了getConnectInfo()抽象方法,在主类中利用对象的向上转型原则,通过子类实例化了Message类的对象,这样当调用getConnectInfo()方法时的就是子类多覆写的方法体。

提示:抽象类的实际使用
抽象类最大的特点就是无法自己直接进行对象实例化操作,所以在实际项目开发中,抽象类的主要目的是进行过滤操作使用。当你要使用抽象类进行开发的时候,往往都是在你设计中需要解决类继承所带来的代码重复处理。

9.1.2 抽象类相关说明

在面向对象设计中,抽象类是一个重要的组成结构,除了其基本的使用形式之外,还有以下急死俺注意事项。
(1)抽象类必须由子类继承,所以在定义时不允许使用final关键字定义抽象类或抽象方法。

范例:错误的抽象类定义
abstract finale class Message{
public finale abstract String getConnectInfo();
}
(2)抽象类中可以定义成员属性与普通方法,为了可以为抽象类中的成员属性初始化,可以再抽象类中提供构造方法。子类在继承抽象类时会默认调用父类的无参构造,如果抽象类没有提供无参构造方法,则子类必须通过super()的形势调用指定参数的构造方法。

范例:抽象类中定义构造方法

abstract class Message
{
public String type;
public Message(String type)
{
this.type=type;
}
public abstract String getConnectInfo()
}

class DatavaseMessage extends Message
{
public DatabaseMessage(String type)
{
super(type);
}

@Override
public String getConnectInfo
{
return super.getType();
}
}

本程序在Message抽象类中定义了一个单参构造方法,由于父类没有提供无参构造,所以DatabaseMessage子类的构造方法中就必须通过super(type)语句形式明确调用父类构造。
(3)抽象类中允许没有抽象方法,即使没有抽象方法,也无法直接使用关键字new直接实例化抽象类对象。

范例:定义没有抽象方法的抽象类
abstract class Message
{}
public class JavaDemo
{
public static void main(String args[])
{
Message msg=new Message();
}
}

本程序定义了没有抽象方法的抽象类,而通过编译的结果可以发现,即便没有抽象方法,抽象类也无法直接使用关键字new实例化对象。
(4)抽象类中可以提供static方法,并且该类方法不受到抽象类实例化对象的限制。

范例:在抽象类中定义static方法

abstract class Message
{
public sbstract String getInfo();
public static Message getInstance();
{
return new DatabaseMessage();
}
}

class DatabaseMessage extends Message
{
@Override
public String getInfo()
{
return "AAA";
}
}

public class JavaDemo
{
public static void main(String args[])
{
Message msg=Message.getInstance();
System.out.println(msg.getInfo());
}
}

本成都在抽象类中定义有static方法,此方法的主要目的是返回Message类实例,这时在主类中将通过静态方法获取Message类对象并且实现getInfo()方法调用。

9.1.3 模板设计模式

类的作用是对一类事物的共性进行抽象,而从这几层次来讲,抽象类的设计比普通类的设计级别药膏,即抽象类史载类级别的进一步抽象,例如,有以下三类实物
机器人类:补充能量(eat)+工作(work);
人类:吃饭(eat)+睡觉(sleep)+工作(work);
猪类:吃饭(eat)+睡觉(sleep)
现在给出的这三类事物都有各自的描述范围,但是这3类事物也有公共的行为方法可以进行抽象,这时就可以利用抽象类的结构进行着3类事物的行为控制,结构如图所示

范例:设计实现
abstract class Action
{
public static finale int EAT=1;
public static finale int SLEEP=5;
public finale int WORK=10;
public void command(int code)
{
switch (code)
{
case EAT:
{
this.eat();
break;
}
case SLEEP:
{
this.sleep();
break;
}
case WORK:
{
this.work();
break;
}
case EAT+SLEEP+WORK:
{
this.eat();
this.sleep();
this.work();
break;
}
}
}
public abstract void eat();
public abstract void sleep();
public abstract void work();
}
class Robot extends Action
{
@Overide
public void eat()
{
System.out.println("机器人需要接通电源充电");
}
@Overide
public void sleep(){}
@Overide
public void work()
{
Sytsem.out.println("机器人按照固定的套路进行工作");
}
}

class Person extends Action
{
@Overide public void eat()
{
System.out.println("饿的时候安静坐下吃饭");
}
@Overide void sleep()
{
System.out.println("安静的躺下,慢慢的睡着,而后做着美梦");
}
@Overide public void work()
{
System.out.println("人类是高级恼类工作过,在工作中不断学习与成长");
}
}

class Pig extends Action
{
@Overide
public void eat()
{
System.out.printl("吃食槽中饲料");
}
@Overide
public void sleep()
{
System.out.println("倒地就睡");
}
@Overide
public void work(){}
}

public class JavaDemo
{
public static void main(String args[])
{
Action robotAction=new Robot();
Action personAction=new Person();
Action pigAction=new Pig();
System.out.println("机器人行为");
robot.Action.comman(Action.SLEEP);
robotAction.commman(Action.WORK);
System.out.println("人类行为");
personAction.command(Action.SLEEP+Action.EAT+Action.WORK);
System.out.pritnln("猪类行为");
pigAction.work();
pigAction.eat();
}
}

本程序为行为抽象类定义了3个子类,在3个子类中会根据各自的需要进行方法的覆写,对于暂时不需要的功能,本程序直接以空方法体的方式进行实现。

9.2 包装类

Java是一门面向对象的编程语言,所有的设计都围绕着对象这一核心概念展开的,但与这一设计有所谓背的就是基本数据类型(byte,short,int long,float,double,char,boolean),所以为了符合这一特点可以利用类的结构对基本数据类型进行包装。

范例:实现基本数据类型包装
class Int
{
private int data;
public Int(int data)
{
this.data=data;
}
public int intValue()
{
return this.data;
}
}

public class JavaDemo
{
public static void main(String args[])
{
Object obj=new int(10);//装箱操作:将基本数据类型保存在包装类中
int x=((Int)obj).intValue();//拆箱操作,从包装类对象中获取基本数据类型。
System.out.println(x+2);

}
}

本程序定义了一个int包装类,并且在类中存储有int数据信息,利用这样的包装处理就可以使用Object类来进行基本数据来行的接受,从而实现参数的完全统一处理。
        基本数据类型进行包装处理后可以像对象一样进行引用传递,同时也可以使用object类来进行接收,所以面对这样的设计缺陷,Java也有自己的解决方案,为此专门设计了8个包装类:byte(Byte)、short(Short)、int(Interger)、long(Long)、float(Float)、double(Double)、boolean(Boolean)、char(Character)

通过上图可以发现,对于包装类可以分为以下两种内省
对象型包装类(Object直接子类):Boolean、Character
数值型包装类(Number直接子类):Byte、Short、Integer、Long、Float、Double
Number描述的是数值型包装类,此类是一个抽象类,并且在子类中提供有如下表的这些方法可以将包装类中包装的基本数据直接取出:

9.2.1 装箱与拆箱

基本数据类型的包装类都是为了基本数据类型转换为对象引用而提供的,这样对于基本数据类型与包装类之间就有了以下的转换操作关系。
数据装箱:将基本数据类型保存在包装类中,一般可以利用包装类的构造方法瓦城。
Interger类:public Interger(int Value).
Double类:public Doule(double value);
Boolean类:public Boolean(boolean value);
数据拆箱:从包装类中获取基本数据类型。
数值型包装类已经由Number类定义了拆箱的方法。
Boolean型:public boolean booleanValue();
范例:以int和Interger为例实现转换
public class JavaDemo
{
public static void main(String args[])
{
Interger obj=new Interger(10);
int num=obj.intValue();
System.out.println(num*num);
}
}

本程序利用Interger类提供的构造方法将基本数据类型数字10装箱,使基本数据类型成为类对象,随后可以利用Number类提供的intValue()方法从包装类中获取保存的int数据。
范例:以double和Double为例实现转换
public class JavaDemo
{
public static void main(String args[])
{
Double obj=new Double(10.1);
double sum=obj.doubleValue();
System.out.println(num*num);
}
}

本程序利用Interger类提供的构造方法将基本数据类型数字10装箱,但基本数据类型成为类对象,随后可以利用Nunber类提供的intValue()方法从包装类中获取保存的int数据。

范例:以double和Double为例实现转换。
public class JavaDemo
{
public static void main(String args[])
{
Double obj=new Double(10.1);
double sum=obj.doubleValue();
System.out.println(num*num);
}
}
本程序利用Double类的构造方法与Number类的doubleValue()方法,实现了浮点型数据的装箱与拆箱操作。
范例:以boolean和Boolean为例实现转换
public class JavaDemo
{
public static void main(String args[])
{
Boolean obj=new Boolean(true);
boolean flag=obj.booleanValue();
System.out.println(flag);
}
}

本程序通过Boolean类的构造方法包装了基本数据类型的内容,并且利用Boolean类中提供的booleanValue()方法实现数据的拆箱操作。
        以上操作是在JDK1.5之前所进行的必须的操作,但是在JDK1.5之后,Java提供了自动装箱和拆箱机制,并且包装类的对象可以自动进行数学计算了。
注意:关于手动装箱的操作问题
从JDK1.5之后开始提供了自动装箱,但是从JDK1.9之后开始发现包装类的构造方法已经出现了过期的声明。
范例:观察Interger、Boolean类的构造方法
 

interger类构造方法@Deprecated(since="9")
public Interger(int value){this.value=value;}
Boolean类构造方法@Deprecated(since="9")
public Boolean(boolean value){this.value=value;}

通过@Deprecated注解中的since属性可以发现,从JDK1.9之后开始不建议继续使用该构造方法,那就意味着该方法在后续地JDK中有可能被取消。因此,在以后多编写的代码中,对于基本数据来兴转包装类的操作都建议通过自动装箱机制实现。

范例:以int和Interger为例实现自动装箱以及拆箱操作。
public class JavaDemo
{
public static void main(String args[])
{
Interger x=new Interger(10);
Interger y=10;
Interger z=10;
System.out.println(x==y);
System.out.println(x==z);
System.out.println(z==y);
System.out.println(x.quels(y));
}
}
执行结果false、false、true、true。
通过本程序,读者一定要记住,在以后使用包装类操作的时候,都要注意数据相等比较的问题,即“==”和equals()的区别。
        另外还需要提醒读者的是,在使用Interger自动装箱实现包装类对象实例化操作中,如果所赋值的内容在-128-127则可以自动实现已有堆内存的引用,可以使用“==”比较,如果不在此范围内,那么就必须依靠equals()来比较。

范例:Interger自动装箱与相等判断
public class JavaDemo
{
Interger numA1=100;
Interger numA2=100;
Ssytem.out.println(numA1==numA2);
Interger numB1=130;
Interger numB2=130;
System.out.println(numB1==numB2);
System.out.println(numB1.equals(numB2));
}

执行结果true、false、true,此时由于设置的内容超过了-128-127的范围,所以通过“==”比较时返回的就是false,那么只能够利用equals()实现相等的比较了。

9.2.2 数据类型转换

在项目编写中往往需要提供有交互是的运行环境,即根据用户输入内容的不同来进行不同的处理,但是在Java程序中所有输入的内容都会利用String类来描述,所以就需要通过包装类来实现各自不同类型的转换,以Interger、Double、Boolean为例,这几个类中都会提供有相应的静态方法实现转换。
Interger类:public static int parseInt(String s);
Double类:public static double parseDouble(String s);
Bolean类:public static boolean parseBoolean(String s);
提示:Character类没有提供转换方法
Character这个包装类中并没有提供一个类似的parseCharacter(),因为字符串String类中提供了一个charAt()方法,可以取得制定索引的字符,而且一个自负的长度就是一位。

范例:将字符串变为int型数据。
public class JavaDemo
{
String str="123";
String str=String.valueOf(num);
System.out.println(str.length());
}

本程序利用Interger()类中提供的parseInt()方法将一个由数字所组成的字符串实现了转型操作,但是在这类转型中,要求字符串必须由纯数字组成,如果有非数字存在,改代码将会引发异常。

范例:将字符串变为boolean型数据
public class JavaDemo
{
public static void main(String args[])
{
String strA="true";
boolean flagA=Boolean.parseBoolean(strA);
System.out.println(flagA);
String strB="AAA";
boolean flagB=Boolean.parseBoolean(strB);
System.out.println(flagB);
}
}
执行结果true、false
 

本程序定义了两个字符串并利用Boolean.parseBoolean()方法实现转换,在转换过程中,如果字符串的组成是true或false则可以按照要求转换,如果不是,则为了避免程序出错,会统一转换为false。提示:基本数据类型为String型,通过基本数据类型包装类可以实现String与基本数据类型之间的转换,但是反过来,入股想要将基本数据类变为String类对象的形势则可以采用以下两种方式完成。

范例:连接空字符串实现转换

public class JavaDemo
{
public static void main(String args[])
{
int num=100;
String str=num+"";
System.out.println(str.length());
}
}

本程序利用空字符串的形势,利用字符串链接的处理操作将int型转变为了String型,但是这类的转换由于西药单独生目标感字符串常量,所以会有垃圾产生。

转换房事:利用String类中提供的valueOf()方法转换,该方法定义如下。
转换方法:public static String valueOf(数据类型 变量),该方法被重载多次。

范例:利用valueOf()方法转换

public class JavaDemo
{
public static void main(String args[])
{
int num=100;
String str=String.valueof(num);
System.out.println(str.length());
}
}

9.3 接口

接口在实际开发中是由一种比抽象类更为重要的结构组成,接口的主要特点在于其用于定义开发标准,同时接口在JDK1.8之后也发生了重大的变革,本章将为读者讲解接口的基本使用以及扩展定义。

9.3.1 接口基本定义

在Java中接口属于一种特殊的类,需要通过interface关键字进行定义,在接口中可以定义全局常量,抽象方法(必须是public访问权限),default方法以及static方法。

范例:定义标准接口
由于类名称与接口名称的定义要求相同,所以为了区分出接口,往往会在接口名称前假如字母I(interface的缩写)
interface IMessage
{
public static finale String INFO="AAAA";
public abstact String getInfo();
}

本程序定义了一个IMessage接口,由于接口中存在抽象方法,所以无法直接实例化,其使用原则如下。
        接口需要被子类实现,子类利用implements关键字可以实现多个父接口。
子类如果不是抽象类,那么一定会覆写接口中的全部抽象方法。
接口对象可以利用子类对象的向上转型进行实例化。
        提示:关于extends、implements关键字的顺序
子类可以继承父类也可以实现父接口,其基本的语法如下。
class 子类[extends 父类][implements 接口1,接口2...]{}
如果出现混合应用,则要先继承(extends)再实现(implements)的顺序完成,同时一定要记住,子类接口的最大特点在于可以同时实现多个父接口,而每一个子类只能通过extends继承一个父类。

范例:使用接口
interface IMessage
{
public static final String INFO="AAA";
public abstract String getInfo();

}
class MessageImplimplements IMessage
{
@Override
public String getInfo()
{
return "AAA";
}
}

public class JavaDemo
{
public static void main(String args[])
{
IMessage msg=new MessageImpl();
System.out.println(msg.getInfo());
}
}

本程序在MessageImpl子类上实现了两个父接口,结构如图所示,这样就必须同时覆写两个父接口中的抽象方法,所以MessageImpl是IMessage接口、IChannel接口和Object类的3个类的实例

范例:观察接口实例转换

public class JavaDemo
{
public static void main(String args[])
{
IMessage msg=new MessageImpl();
Object obj=msg;
IChannel channel=(IChannel)obj;
System.out.println(channel.connect());
}
}
如果现在没有MessageImpl子类,那么IMessage接口、IChannel接口、Object类三者之间是没有任何关系的,但是由于MessageImpl同时实现了这些接口并默认继承了Object父类,所以该实例就可以进行任意父接口的转型。
提示:关于接口的简化定义
在进行接口定义时,对于全局常量和抽象方法可以按照以下的形势进行简化定义
 

完整定义interface IMessage{public static finale String Info="AAA";
public abstract String getInfo();}
简化定义interface IMessage{String INFO="AAA",String getInfo();}

以上两种IMessage接口的定义作用完全相同,但是从世纪的开发来讲,在接口定义抽象方法时建议保留public声明,这样的接口会更加清楚。
        在面向对象设计中,抽象类也是必不可少的一种结构,利用抽象类可以实现一些公共方法的定义。可以利用extends先继承父类再利用implements实现若干父接口的顺序完成子类定义。

在面向对象设计中,抽象类也是必不可少的一种结构,利用抽象类可以实现一些公共方法的定义。可以利用extends先继承父类,再利用implements实现若干父接口的顺序完成了子类定义。
范例:子类继承抽象类同时实现接口
interface IMessage{
public static finael String INFO="AAA";
public abstract String getInfo();
}

interface IChannel
{
public abstract boolean getDatabaseConnection();
}
class MessageImpl extends DatabaseAbstract implements IMessage,IChannel
{
@Override
public String getInfo()
{
if(this.connect())
{
if(this.getDatabaseConnection())
{
return "AAA";
}else
{
return 数据库消息无法访问;
}
}

return "默认消息"+IMessage.INFO;
}
@Override
public boolean connect()
{
return true;
}
@Override
public boolean getDatabaseConnection()
{
return true;
}
}

public class JavaDemo
{
public static void main(String args[])
{
IMessage msg=new MessageImpl();
System.out.println(msg.getInfo());
}
}

本程序在定义MessageImpl子类时继承了DatabaseAbstract抽象类,同时实现了IMessage、IChannel两个付接口,并且在getInfo()方法中进行接口方法的调用整合,需要注意的是在定义抽象类的过程中所有的抽象方法必须有abstract关键字定义。
        在Java中的extends关键字除了具有类继承的作用外,也可以在接口上使用以实现接口的继承关系,并且可以同时实现多个父接口。

范例:使用extends继承多个父接口
interface IMessage
{
public static finale String INFO="AAA";
public abstract String getInfo();

}
interface IChannel
{
public boolean connect();
}
extends在类集成商只能够继承一个父类,但是接口上可以继承多个
interface IService extends IMessage,IChannel
{
@Override
public String getInfo()
{
return IMessage.INFO;
}
@Override
public boolean connect()
{
return true;
}
@Override
public String service()
{
return "AAAA";
}
}
本程序在定义IService接口时,让其继承了IMessage、IChannel两个父接口,这样IService接口就有了两个父接口定义的所有抽象方法。
提示:接口的使用分析
接口时面向对象程序设计中最为重要的话题,在实际项目中的和信用处是实现方法名称的暴露与子类的隐藏;

对于实现子类的隐藏往往需要通过一些复杂的代码结构来实现,这些内容在本书后续都会有详细的讲解,而对于接口的学习读者应该先掌握其基本概念后在逐步深入。

9.3.2 接口定义加强

接口是从Java语言诞生之初所提出的设计结构,其最初的组成就是抽象方法与全局常量,但是随着技术的发展,在JDK1.8的时候接口中的组成除了提供全局常量与抽象方法之外,还可以使用default定义普通方法或者使用static定义静态方法。

范例:在接口中使用default定义普通方法
interface IMessage{
public String message();
public default boolean connect()
{
System.out.println("AAAA");
return true;
}
}

class MessageImpl implements IMesssage
{
public String message()
{
return "AAAA";
}
}

public class JavaDemo
{
public static void main(String args[])
{
IMessage msg=new MessageImpl();
if(msg.connect())
{
System.out.println(msg.message());
}
}
}
本程序在IMessage接口中利用default定义了普通方法,这样接口中的组成就不再只有抽象方法,同时这些default定义的普通方法也可以直接背子类继承。

提问:接口中定义普通方法有什么意义
在JDK1.8以前接口中的核心组成就是全局常量与抽象方法,为什么在JDK1.8之后却允许定义default方法了,这样做有什么意义嘛
回答:便于扩充接口功能,同时简化设计结构。
在设计中接口的主要功能是进行公共标准的定义,但是随着技术的发展,接口的设计也有可能得到更新,那么此时假设说有一个早期版本的接口,并且随着发展已经定义了大约1080个子类
如果使用上述的结构设计,那么一旦IMessage接口中追加一个新方法,并且所有的子类对于此方法实现完全相同时,按照JDK1.8以前的设计模式就需要修改所有定义的子类,重复复制实现方法,这样机会哦导致代码的可维护性降低。而在JDK1.8之前,为了解决这样的设计问题,往往会在接口和实现子类之间追加一个抽象类:
当采用如图的结构设计志宏,当接口在扩充公共方法时就不必修改所有的子类,只需修改抽象类即可,所以为了决绝这样的设计,在JDK1.8之后才提供有default方法的支持。同时需要提醒读者的是,如果子类发现付接口中公共的default方法功能不足时,可以进行覆写。
使用default定义的普通方法需要通过接口实例化对象才可以调用,而为了避免实例化对象的依赖,在接口中也可以使用static定义方法,此方法可以直接利用接口名称调用。

范例:在接口中定义static方法

interface IMessage
{
public String message();
public default boolean connect()
{
System.out.println("AAAA");
return true;
}
public static IMessage getInstance()
{
return new MessageImpl();
}
}

class MessageImpl implements IMessage
{
public String message()
{
if(this.connect())
{
return "AAAA";
}
return "消息没有发送";
}
}

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

本程序在IMessage接口中定义了一个static方法getInstance(),此方法可以直接被接口名称调用,主要作用是获取接口实例化对象。

提示:关于接口和抽象类
通过一系列的分析可以发现,接口中定义的default、static两类方法很大程度上与抽象类的作用有些重合,所以有些读者会认为开发中可以不再需要抽象类,对于公共方法只需通过default或static在接口中定义即可,实际上这是一种误区,对于JDK1.8后的接口功能补充,更偏向这只是一种修补的设计方案,即对于那些设计结构有缺陷的代码是一种补救措施。当有了自定义接口之后不要急于直接定义子类,中间最好设计一个过度的抽象口。

9.3.3 定义接口标准

对于接口而言,在开发中最为重要的应用就是进行标准的制定。实际上在日常的生活中也会见到许多关于接口的名词,例如,USB接口、PCI接口、鼠标接口等,那么这些接口实际上都是属于标准的定义与应用。
        以USB程序为例,计算机上可以插入各种USB设备,所以计算机上认识的只是USB标准,而不关心这个标准的具体实现子类,
范例:利用接口定义标准

interface IUSB
{
public boolean check();
pubic void work();
}
class Computer
{
public void plugin(IUSB usb)
{
if(usb.check())
usb.work();
else
{
System.out.println("硬件设备出现了问题")
}
}
}
class keyboard implements IUSB
{
public boolean check()
{
return true;
}
public void work()
{
System.out.pritnln("打开计算机在线学习");
}
}

class Print implements IUSB
{
public boolean check()
{
return false;
}
public void check()
{
System.out.println("AAAA");
}
}

public class JavaDemo
{
public static void main(Stringa args[])
{
Computer computer =new Computer();
computer.plugin(new Keyboard());
computer.plugin(new Print());
}
}

本程序首先定义了一个公共的IUSB结构标准,于是USB的具体实现子类与计算机类之间按照此标准进行操作。在主方法调用时,可以向Computer.plugin()方法中传递IUSB接口子类对象,并且按照既定的模型进行调用。

提示:对于标准的理解
在现实生活中标准的概念无处不在。例如,当一个人肚子饿了的时候,他可能会想到吃包子、吃面条,这些都有一个公共的标准:食物。再如,一个人需要称作交通工具去机场,那么这个人可能骑自行车,也有可能坐出租车,所以交通国剧也是一个标准。
        经过这样的分析可以发现,接口在整体设计上是针对该类的进一步抽象,而其涉及的层次也要高于抽象类。

9.3.4 工厂设计模式

接口在实际开发过程中主要的特点是进行标准的定义,而标准的定义是一个灵活的概念,也就是说标准不应该与具体的子类固定在一起。为了解决代码的耦合问题,在开发中针对接口对象的获得,往往会通过工厂模式来完成

其结构如图。
范例:工厂模式简单实现

interface IFood
{
pubic void eat();
}
class Bread implements IFood
{
public void eat()
{
System.out.println("吃面包。");
}
}

class Milk implements IFood

{
public void eat()
{
System.out.println("喝牛奶");
}
}

class Factory
{
public static IFood getInstance(String className)
{
if("bread".equals(className))
return new Bread();
else if("milk".equals(className))
return new Milk();
else
return null;
}
}

public class JavaDemo
{
public static void main(String args[])
{
IFood food=Factory.getInstance("bread");
}
}

本程序定义了一个Factory工厂类,并且在此类中提供有一个静态方法可以用于返回IFood接口实例化对象,这样在主类(客户端)调用时不在需要关注具体的IFood接口子类,只需传入指定的类型标记就可以获取接口对象。

提示:可以利用初始化参数动态传递
对于以上的工厂设计模式,如果现在利用初始化执行参数的模式,也可以动态的在朱磊中进行IFood不同子类的更换。

范例:修改程序主类
public class JavaDemo
{
public static void main(String args[])
{
//通过初始化参数进行指定子类的标记接受
//要使用Bread子类,执行命令:java JavaDemo bread;
IFood food=Factory.getInstance(args[0]);
food.eat();
}
}

利用这样的设计就可以在不需要明确知道子类的情况下使用接口,但是对于当前的设计本身也存在以下的几个问题。
问题1:没当IFood接口扩充子类时都需要修改Factory工厂类,这样一旦子类很多,此工厂类的代码必定造成大量重复。
问题2:如果现在有若干个接口都需要通过Factory类获取实例时,对于Factory类需要追加大量重复逻辑的static方法。
问题3:一个项目中可能只需要使用某个接口的子类,而这个子类往往可以利用配置文件的形势来定义,修改时也可以通过配置文件的修改来更换子类,而这个子类往往可以利用配置文件的形式来定义,修改时也可以利用配置文件的修改而更换子类,所以这种固定标记的做法会造成代码结构的混乱。
        对于以上的问题(工厂模式与下一节要讲解的代理模式会存在相似的问题)。

9.3.5 代理设计模式

代理设计也是在Java开发中使用较多的一种设计模式,是指用一个代理主题来操作真实主题,真实主题执行具体的业务操作,而代理主题负责其他相关业务的处理。简单理解,就是说你如果现在肚子饿了,那么肯定要吃饭,而如果你只会吃饭而不会做饭的话,那么就需要去饭店吃饭,而由饭店为你吃饭的业务作各种辅助操作(例如,购买食材,处理食材,烹饪没事,收拾餐具等),而你负责关键的一部吃就可以了
不管是代理操作还是真是操作,其共同的目的就是为吃饭服务,所以用户关心的只是如何吃到饭,至于里面是如何操作的用户并不关心,于是可以得到以下结果
范例:实现代理设计模式

interface IEat
{
public void get();
}
class EatReal implements IEat
{
public void get()
{
System.out.println("真实主题得到一份食物");
}
}

class EatProxy implements IEat
{
private IEat eat;
public EatProxy(IEat eat)
{
this.eat=eat;
}
public void get()
{
this.prepare();
this.eat.get();
this.clear();
}
public void prepare()
{
System.out.println("代理主题 1 精心购买食材");
System.out.println("代理主题 2 小心购买食材");
}
public void clear()
{
System.out.println("[代理主题]3 收拾碗筷");
}
}

public class JavaDemo
{
public static void main(String args[])
{
IEat eat=new EatProxy(new EatReal());
eat.get();
}
}

程序执行结果
代理主题:1精心购买食材
代理主题2.小心地处理食材
代理主题3得到一份食物,而后开始品尝美味。
代理主题3收拾碗筷。

本程序为一个IEat接口定义了两个子类:真实主题类(EatReal)和代理主题类(EatProxy),真实主题类只有在代理类提供支持的情况下才可以正常完成核心业务。但是对于主类(客户端)而言,其所关注的只是IEat执行标准,而具体使用哪一个子类并不需要关注。


提示:代理设计模式结合工厂设计模式

在本程序所讲解的实现代码中,为了方便读者理解程序,直接在主类上实例化了子类,这种操作严格意义来讲不符合设计要求的。所有自定义的接口对象都应该通过工厂类获得,修改后的逻辑代码如上图。

范例:修改代理模式代码
class Factory
{
public static IEat getInstance()
{
return new EatProxy(new EatReal())
}
}

public class JavaDemo
{
public static void main(String args[])
{
IEat eat=Factory.getInstance();
eat.get();
}
}

9.3.6 抽象类与接口区别

抽象类和接口是项目开发设计中的两个重要设计环节,为了帮助读者更好的理解两这之间的关系,进行了如下对比:
通过上面的分析可以得出结论:在开发中,抽象类和接口实际上都是可以使用,具体使用那个没有明确的限制,二抽象类有一个最大的缺点--一个子类只能够继承一个抽象类,即存在单继承的限制,所以当遇到抽象类和接口都可以使用的情况下,优先考虑接口,避免单继承局限。

提问:概念太多了,该如何使用
前面已经学习过的概念有对象、类、抽象类、接口、继承、实现等,这些概念都属于什么样的关系呢,在开发中,又该如何使用这些概念呢。

回答:接口是在类之上的标准

为了更好的说明给出的集中结构的关系,下面通过一个简短的分析完成
如果现在要想定义一个动物,男动物肯定是一个公共标准,而这个公共标准就可以通过接口来完成。在动物中又分为两类:哺乳动物和孪生动物,而这个标准属于对动物标准进一步细化,应该称为字标准,所以此种关系可以使用接口的集成来表示。
        而哺乳动物又可以继续划分为人、狗、猫等不同的类型,由于这些类型不表示具体的实物标准,所以可以使用抽象类来进行表示
        如果要表示出工人或者学生这样的概念,则肯定是一个具体的定义,则实用类的方式,然而每一个学生或每一个工人都是具体的,那么就通过对象来表示,

在途中可以发现,在所有的设计中,接口应该睡最先被设计出来的,所以在项目开发中,以接口设计最为重要。

9.4 泛型

泛型是JDK15之后所提供的新技术特性,利用泛型的特征可以方便的避免对象强制转型所带来的安全隐患问题。

9.4.1 泛型问题的引出

在Java语言中,为了方便接收参数类型的统一,提供了一个核心类Object,利用此类对象可以接收所有类型的数据(包括基本数据类型与引用数据类型)。但是由于其所描述的数据范围过大,所以在实际使用中就会出现传入数据类型错误,从而引发ClassCastException异常。例如,现在要设计一个可以描述坐标点Point(包括x与y坐标信息),对于坐标点允许保存3类数据。
        整形数据:x=10,y=20.
        浮点型数据:x=10.1,y=20.9;
字符串型数据:x=东经120,y=北纬30.
于是在设计Point类的时候就需要去考虑x和y属性的具体类型,这个类型要求可以保存以上3类数据,很明显,最为原始的做法就是利用Object类来进行定义,这是因为存在以下的转换关系。
整形数据:基本数据类型-包装为Interger类对象-自动向上转型为Object。

范例:定义Point坐标点类
class Point
{
private Object x;
private Object y;
public void setX(Object x){this.x=x;}
public void setY(Object y){this.y=y;}
public Object getX(){return this.x;}
public Object getY(){return this.y;}
}

Point类中的x与y属性都采用了Object作为存储类型,这样就可以接受任意的数据类型,于是此时就有可能产生两种情况。
情况1:使用者按照统一的数据类型设置坐标内容,并且利用向下转型获取坐标原始数据。
public class JavaDemo
{
public static void main(String args[])
{
Point point=new Point();
point.setX(10);
point.setY(20);
int x=(Interger)point.getX();
int y=(Interger)point.getY();
System.out.println(""+x+y);
}
}

本程序利用基本数据类型自动装箱包装类对象的特点向Point类对象中传入x与y两个坐标信息,摈弃给在获取坐标原始数据时,也依据设置的数据类型进行强制性的想吓唬在那行,所以可以得到正确的执行结果。

情况2:使用者没有按照统一的数据类型设置坐标内容,读取数据时使用了错误的类型进行强制转换
public class JavaDemo
{
public static void main(String args[])
{
Point point=new Point();
point.setX(10);
point.setY("北纬20度");
int x=(Integer)point.getX();
int y=(Interger)point.getY();
}
}

执行后会引发异常
本程序在设置Point类坐标数据时采用了不同的数据类型,所以在获取原始数据是就会出现程序运行的异常,即这类错误并不会在编译的时候告诉开发者,而是在执行的过程中才会产生隐患。而造成此问题的核心原因就是Object类型能够接受的数据范围过大。

9.4.2 泛型基本定义

如果想要在解决项目中可能出现的ClassCastException安全隐患,最为核心的防范就是避免强制性的进行对象向下转型的操作,所以泛型设计的核心思想在于:类中的属性或方法的参数与返回值的类型采用动态标记,在对象实例化的时候动态配置要使用的数据类型。

注意:泛型只允许设置引用数据类型。
泛型在类上标记处后,需要通过实例化对象进行类型的设置,而所设置的类型只能够是引用数据类型。如果要设置基本数据类型,则必须采用包装类的形势,这也是为什么JDK1.5之后要引入包装类对象的自动装箱与自动拆箱机制的原因。

范例:在类定义上使用泛型

class Point<T>
{
private T x;
private T y;
public void setX(T x);
{this.x=x;}
public void setY(T y){this.y=y};
public T getX(){return this.x;}
public T getY(){return this.y;}
}

public class JavaDemo
{
public static void main(String args[])
{
Point<Interger>point =new Point<Interger>();
point.setX(10);
point.setY(20);
int x=point.getX();
int y=point.getY();

}
}

在本程序中实例化Point类对象时所辖用的泛型类型设置为了Interger,这样一来当前Point类对象中的x,y属性类型就是Interger,对应方法参数和返回值也都是Interger,这样不仅可以在编译的时候明确知道数据类型的错误,也避免了对象向下转型的操作。

提示:JDK1.5和JDK1.7定义泛型的时候是稍微有些区别的
JDK1.5是最早引入泛型的版本,而在JDK1.7之后为了方便开发有对泛型操作进行了简化
范例:JDK1.5的声明泛型对象操作
Point<String>point=new Point<String>();
以上是 JDK1.5的语法,在声明和实例化对象的时候必须同时设置好泛型类型。
范例:JDK1.7之后的简化
Point<String> point =new Point<>();
这个时候实例化对象时的泛型类型就通过实例时的泛型类型来定义了
在泛型类使用中,JDK考虑到了最初开发则会的使用习惯,允许开发者在实例化对象时不设置泛型类型,这样在程序编译时就会出现相应的警告信息,同时为了保证程序不出错,未设置的泛型类型将使用Object作为默认类型。

范例:观察默认类型

public class JavaDemo
{
public static void main(String args[])
{
Point point=new Point();
point.setX(10);
point.setY(20);
int x=(Interger)point.getX();
int y=(Interger)point.getY();

}
}

本程序在实例化Point类对象时没有设置泛型类型,所以将使用Object作为x,y属性以及方法参数和返回值的数据类型,这样在进行数据获取时就必须将bject对象实例强制转换为执行类型。之所以存在这样的设计,主要也是为了翻遍与旧版本JDK程序的衔接。

9.4.3 泛型通配符

利用泛型类在实例化对象时进行的动态类型匹配,虽然可以有效的解决对象向下转型的安全隐患,但是在程序中实例化泛型类对象时,不同泛型类型的对象之间彼此之间是无法进行引用传递的。
所以在进行泛型类型的引用对象是,为了可以适应所有本类的实例化对象,则可以在接受时使用“?”作为泛型通配使用,利用“?”表示的泛型类型只允许从对象中获取数据,而不允许修改数据。
范例:使用“?”接收数据

class Message<T>
{
private T content;
pubic void setContent(T content){this.content=content;}
public T getContent(){return this.content;}
}
public class JavaDemo
{
public static void main(String args[])
{
Message<String>msg=new Message<String>();
msg.setContent("AAA");
fun(msg);
}
public static void fun(Message<?>temp)
}

本程序在fun()方法的参数上使用Message<?>接收Message类的引用对象,由于通配符“?”的作用,所以该方法可以匹配任意的泛型类型(Message<String>或Message<Interger>)等都可以。

提问:如果不设置泛型类型或者设置泛型类型为Object可否解决上述问题
根据之前所讲解的泛型概念,如果此时在fun()方法上采用以下两类参数生命是否可以接收任意的泛型类型对象
形式1:public static void fun(Message<Object> temp){}
形式2:public static void fun(Message temp){}
回答:泛型需要考虑操作类型的统一性
首先需要清楚一个核心的问题,在面向对象程序设计中,Object类可以接收一切的数据类型,但是在泛型的概念中:Message<String>与Message<Object>属于两个不同类型的对象
如果采用形式1的方式定义参数,则表示fun()方法只能接受Message<Object>类型的引用
如果采用形式2的方式定义参数,不在fun()方法上设置泛型类型,实际上可以解决当前不同泛型类型的对象传递问题,但同时会有新的问题产生:允许随意修改数据。
范例:观察不设置泛型类型时的方法定义
public class JavaDemo
{
public static void main(String args[])
{
Message<String>msg=new Message<String>();
msg.setContent("AAAA");
fun(msg);
}
public static void fun(Message temp)
{
temp.setContent(18);
System.out.pritln(temp.getContent());
}
}

执行结果18:执行完程序可以发现,虽然通过不设置泛型的形势可以接收任意的泛型对象引用,但是无法对修改做出控制;而是用通配符“?”的泛型只允许获取,不允许修改。

通配符“?”除了可以匹配任意的泛型类型外,也可以通过泛型上限与下限的配置实现更加严格的类范围定义。
【类和方法】设置泛型的上限(?extends类):只能够使用当前类或当前类的子类设置泛型类型。?extends Number:可以设置Number或Number子类(例如,Intger,Double)
【方法】设置泛型的下限(?super类):只能够设置制定的类或指定类的父类
?super String:只能够设置String类或String的父类Object.

范例:设置泛型上限

class Message <T extends Number>
{
private T content;
public void setContent(T content){this.content=content;}
public T getContent(){return this.content;}
}

public class JavaDemo
{
public static void main(String args[])
{
Message<Interger>msg=new Message<Interger>();
msg.setContent(10);
fun(msg);
}
public static void fun(Message<?extends Number>temp){}
}

本程序在定义Message类与fun()方法接收参数时使用了泛型上限的设置,这样可以实例化的Messsage对象只允许使用Number或其子类作为泛型类型。
范例:设置泛型下限
class Message<T>
{
private T content;
public void setContent(T content){this.content=content;}
public T getContent(){return this.content;}
}

public class JavaDemo
{
public static void main(String args[])
{
Message<String>msg=new Message<String>();
msg.setContent("AAAA");
fun(msg);
}
}

public class JavaDemo
{
public static void main(String args[])
{
Message<String>msg=new Message<String>();
msg.setContent("AAAA");
fun(msg);
}
public static void fun(Message<? super String >temp){System.out.println(tmp.getContent());}

}
本程序在fun()方法上使用泛型下限设置了可以接收Message对象的泛型类型只能是String或其父类Object。

9.4.4 泛型接口

泛型除了可以定义在类上也可以定义在接口上,这样的结构称为泛型接口
范例:定义泛型接口
interface IMessage<T>
{
public String echo(T msg);
}
对于此时的IMessage泛型接口在进行子类定义时就有两种实现方式:在子类中继续声明泛型和子类中为父类设置泛型类型。
范例:定义泛型接口子类,在子类中继续声明泛型。

interface IMessage<T>{public String echo(T msg);}
class MessageImpl<S> implements IMessage<S>{public String echo(S t){return "echo"+t;}}
public class JavaDemo
{
public static void main(String args[])
{
IMessage<String>msg=new MessageImpl<String>();
System.out.println(msg.echo("AAAA"));
}
}

本程序定义MessageImpl子类时继续声明了一个泛型标记S,并且实例化MessageImpl子类对象时设置的泛型类型也会传递到IMessage接口中。

范例:定义子类,在子类中为IMessage设置泛型类型
interface IMessage<T>{public String echo(T msg);}
class MessageImpl implements IMessage<String>{public String echo(String t){return "+t;}}
public class JavaDemo{public static void main(String args[]){System.out.println(msg.echo("AAA"));}}

9.4.5 泛型方法

对于泛型,除了可以定义在类上之外,也可以在方法上进行定义,而在方法上定义泛型的时候,这个方法不一定非要在泛型类中定义。
范例:定义泛型方法
public class JavaDemo
{
public static void main(String args[])
{
Interger num[]=fun(1,2,3);
for(int temp:num){System.out.println(temp);}
}
public static<T> T[] fun(T...args){return args;}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值