在之前所讲解的类结构都是包含有:普通属性、常量(全局常量)、构造方法、抽象方法。但是如果说现在在设计一个类的时候发现这个类的内部只存在有全局常量和抽象方法的话,则这个类就能将其定义为接口。
接口就是全局常量和抽象方法的集合。在Java里面使用interface关键字可以进行接口的定义。
范例:定义接口
interface A { // 定义的是接口 public static final String MSG = "Hello World ." ; public abstract void fun() ; // 抽象方法 } |
定义的关键字是有区别的,但是其它的常量和抽象方法与之前没有区别。但是对于接口也可以发现,里面包含有抽象方法,所以一定无法直接使用关键字new进行接口对象的实例化,那么接口的使用原则如下:
· 接口必须有子类,子类使用implements进行接口实现,一个子类可以同时实现多个接口;
· 接口的子类(如果不是抽象类),则一定要覆写接口之中的全部抽象方法;
· 接口对象可以利用子类的向上转型,进行实例化操作。
范例:使用接口
interface A { // 定义的是接口 public static final String MSG = "Hello World ." ; public abstract void fun() ; // 抽象方法 } interface B { public abstract void print() ; } class X implements A , B { // X类同时实现了A、B两个接口 public void fun() { System.out.println("HELLO") ; } public void print() { System.out.println(MSG) ; } } public class TestDemo { public static void main(String args[]) { A a = new X() ; // 子类为父接口实例化 B b = new X() ; // 子类为父接口实例化 a.fun() ; b.print() ; } } |
现在已经学习了普通类、抽象类、接口三个概念,那么一定要记住的是,如果说现在一个类即要继承抽象类又要实现接口的话,则应该先继承(extends 父类)后实现(implenents 接口 , …);
范例:让子类继承抽象类和实现接口
interface A { // 定义的是接口 public static final String MSG = "Hello World ." ; public abstract void fun() ; // 抽象方法 } interface B { public abstract void print() ; } abstract class C { // 抽象类 public abstract void get() ; // 抽象方法 } class X extends C implements A , B { // X类同时实现了A、B两个接口 public void fun() { System.out.println("HELLO") ; } public void print() { System.out.println(MSG) ; } public void get() { System.out.println("你好。") ; } } public class TestDemo { public static void main(String args[]) { A a = new X() ; // 子类为父接口实例化 B b = new X() ; // 子类为父接口实例化 C c = new X() ; // 子类为父抽象类实例化 a.fun() ; b.print() ; c.get() ; } } |
清楚了接口的基本概念之后下面还需要有几个说明。
说明一:接口之中的全部方法访问权限都是public,不管写与不写都一样,在以后的工作之中,大部分的方法规范都是由接口规定的,所以这才有了只要是方法大部分都使用public定义的原因;
说明二:在接口里面既然全部都是由抽象方法和全局常量所组成,所以在定义的时候以下两种定义方式相同。
interface A { // 定义的是接口 public static final String MSG = "Hello World ." ; public abstract void fun() ; // 抽象方法 } |
interface A { // 定义的是接口 String MSG = "Hello World ." ; void fun() ; // 抽象方法 } |
但是很多时候为了防止一些初级人员理解错误,所以在定义接口的时候往往都加上public。
说明三:一般在定义接口的时候定义方法是比较多的。而定义全局常量相对少一些。
说明四:一个抽象类可以使用implements实现接口
范例:抽象类实现接口
interface A { // 定义的是接口 String MSG = "Hello World ." ; public abstract void fun() ; // 抽象方法 } interface B { public abstract void print() ; } abstract class C implements A,B { // 抽象类 public abstract void get() ; // 抽象方法 } class X extends C { public void fun() { System.out.println("HELLO") ; } public void print() { System.out.println(MSG) ; } public void get() { System.out.println("你好。") ; } } |
但是一个接口却不能够继承一个抽象类,可是一个接口却可以使用extends关键字继承多个父接口。
范例:接口的多继承
interface A { public void printA() ; } interface B { public void printB() ; } interface C extends A,B { // C继承A、B两个接口 public void printC() ; } class X implements C { // X要覆写三个抽象方法 public void printA() {} public void printB() {} public void printC() {} } |
说明五:任何内部的结构实际上都不受定义的限制,所以在一个接口里面也可以继续定义内部接口或内部抽象类,而且使用static定义的内部接口就是一个外部接口。
interface A { static interface B { // 内部接口加上static后属于外部接口 public void fun() ; } } class X implements A.B { // 实现内部接口 public void fun() { System.out.println("Hello World.") ; } } public class TestDemo { public static void main(String args[]) { A.B b = new X() ; b.fun() ; } } |
在实际之中,接口有三大核心功能:
· 使用接口定义规范标准;
· 接口表示一种能力;
· 将服务器的远程视图暴露给客户端。
“接口”这一名词是到处可见,例如:插座、交流语言、USB。那么下面通过程序模拟这样一个概念,打印机、U盘都可以插在电脑的USB接口上使用,而使用的规则假设就只有两个:工作、退出,那么这个时候设计如下。
两个无关的类之间的连接一定依靠接口来完成,由接口定义出操作的标准。
范例:定义接口标准
interface USB { public void work() ; // USB设备工作 public void exit() ; // 退出USB设备 } |
范例:定义电脑
class Computer { // 电脑 public void plugin(USB usb) { // 接收USB设备 usb.work() ; // 调用usb接口方法,这个方法是由子类实现的 usb.exit() ; // 完事后退出 } } |
范例:定义USB设备
class Flash implements USB { // U盘要实现USB标准 public void work() { System.out.println("U盘开始实现拷贝操作。") ; } public void exit() { System.out.println("U盘结束操作。") ; } } class Print implements USB { public void work() { System.out.println("打印机开始进行文档打印。") ; } public void exit() { System.out.println("打印机打印完毕,结束工作。") ; } } |
范例:测试程序
public class TestDemo { public static void main(String args[]) { Computer com = new Computer() ; com.plugin(new Flash()) ; com.plugin(new Print()) ; }} |
而在此工程之中,发现电脑和打印机还是两个彼此独立的类,没有直接的定义联系,但是有标准联系。
而这样的形式在生活中随处可见:
· 例如:高速上不允许非机动车行驶;
· 例如:小谢子病危了,打算吃个水果;
· 例如:本商场不允许宠物入内。
接口(和抽象类)就是对类的再次抽象,但是以接口为主(抽象类具备单继承局限,而接口没有)。
那么下面编写以下的一段程序。本程序没有语法错误,但是有结构错误。
interface Fruit { // 水果 public void eat() ; // 吃 } class Apple implements Fruit { public void eat() { System.out.println("吃苹果。") ; } } class Orange implements Fruit { public void eat() { System.out.println("吃橘子。") ; } } public class TestDemo { public static void main(String args[]) { Fruit f = new Apple() ; f.eat() ; } } |
本程序如果真的在实际之中使用,有可能出现什么问题?
本程序在主方法(客户端)直接使用了关键字new实例化了接口的子类对象,那么就表示这个时候在客户端,Fruit接口就与Apple子类绑定在一起了,如果要更改子类,则需要修改客户端代码,而这种形式是不可能使用的。最好的设计,后面可以动,但是客户端操作不要动。
即:“A è B”,属于点对点的操作就会造成扩展上的麻烦,那么可以借鉴JVM的操作原理(程序 è JVM è 操作系统,A è C è B),这样的做法反而更容易进行程序的维护。也就是说最好加入一个第三方。
范例:修改程序
interface Fruit { // 水果 public void eat() ; // 吃 } class Apple implements Fruit { public void eat() { System.out.println("吃苹果。") ; } } class Orange implements Fruit { public void eat() { System.out.println("吃橘子。") ; } } class Factory { public static Fruit getInstance(String className) { if ("apple".equals(className)) { return new Apple() ; } if ("orange".equals(className)) { return new Orange() ; } return null ; } } public class TestDemo { public static void main(String args[]) { Fruit f = Factory.getInstance(args[0]) ; f.eat() ; } } |
当现在修改使用子类的时候,客户端没有进行任何的修改。而如果说此时Fruit要扩充子类的话,那么只需要修改Factory类即可,即:某一局部的修改不影响其它程序组成部分。
范例:实现程序
interface Network { // 网络连接 public void browse(String url) ; } class RealNetwork implements Network { public void browse(String url) { System.out.println("访问站点:" + url) ; } } class ProxyNetwork implements Network { private Network net ; // 保存真正的上网连接 public ProxyNetwork(Network net) { this.net = net ; } public boolean check(String url) { if (url.contains("whitehouse")) { System.out.println("警告:访问非法网站。") ; return false ; } return true ; } public void browse(String url) { if (this.check(url)) { // 通过检查了 this.net.browse(url) ; } } public void record(String url){ System.out.println("上网记录:" + url) ; } } public class TestDemo { public static void main(String args[]) { Network nw = new ProxyNetwork(new RealNetwork()) ; nw.browse("www.weibo.com") ; nw.browse("www.whitehouse.com") ; } } |
在本结构之中,发现一个接口定义了两个子类,一个子类完成核心的真实业务主题,而另一个子类要为核心业务提供所有的辅助功能。
抽象类和接口是整个项目之中最大的组成灵魂。那么下面针对于这两者的区别做一个说明。
No. |
区别 |
抽象类 |
接口 |
1 |
关键字 |
abstract class 类名称 {} |
interface 接口名称{} |
2 |
组成 |
普通方法、构造方法、抽象方法、常量、变量 |
全局常量、抽象方法 |
3 |
权限 |
各类权限 |
只能是public权限 |
4 |
使用 |
子类使用extends只能够继承一个父类 |
子类使用implements可以实现多个接口 |
5 |
关系 |
抽象类可以实现多个接口 |
接口不能够继承抽象类,但是可以继承多个接口,实现接口多继承 |
6 |
设计模式 |
模板设计模式 |
工厂设计模式、代理设计模式 |
7 |
局限 |
单继承局限 |
无局限 |
通过以上的分析可以发现,在项目之中是使用抽象类还是使用接口其实都一样,可是由于接口没有抽象类的单继承局限,那么在整个开发之中,如果发现抽象类和接口都可以使用时,优先使用接口,避免单继承局限。
到现在为止所有Java程序结构就讲解完整了,包含单元:接口、抽象类、类、对象、继承。
匿名内部类是在抽象类和接口基础之上建立起来的一种结构,必须以抽象类和接口为主,那么下面首先来分析一下匿名内部类的产生原因。
范例:观察问题
interface A { public void fun() ; // 抽象方法 } class B implements A { public void fun() { System.out.println("Hello World .") ; } } public class TestDemo { public static void main(String args[]) { A a = new B() ; a.fun() ; } } |
本程序为A接口定义了一个B子类,而后在主方法之中,通过子类向上转型实现了接口对象的实例化操作。但是下面来思考一个问题:如果说现在的子类B只使用一次,那么还有必要单独定义一个类吗?
范例:利用匿名内部类解决
interface A { public void fun() ; // 抽象方法 } public class TestDemo { public static void main(String args[]) { A a = new A(){ public void fun() { System.out.println("Hello World .") ; } } ; a.fun() ; } } |
对于匿名内部类,在日后一定会出现,但是慢慢习惯。
在整个Java设计过程之中,实际上已经很好的考虑到了参数的统一问题,毕竟,在整个Java的环境下,类至少有几十亿个,那么在进行语言设计时就必须考虑这几十亿个类的参数统一问题,所以才提出了Object的概念。
在每定义一个类的时候,如果此类没有明确的继承一个类,则将默认继承Object类,即:所有的类都是Object子类,那么以下两种类的定义结构完全相同。
class Person { } |
class Person extends Object { // 默认隐含Object } |
范例:利用Object类接收所有类对象
class Person extends Object { // 默认隐含Object } public class TestDemo { public static void main(String args[]) { Object objA = new Person() ; Object objB = "HELLO" ; System.out.println(objA) ; // Person@64ea66 System.out.println(objB) ; // HELLO } } |
在Object类里面定义了许多的方法,那么其中在用户定义简单Java类的时候从标准来讲应该覆写Object类之中的三个方法:
· 取得对象信息:public String toString(),代替之前的getInfo();
· 对象比较:public boolean equals(Object obj);
· 对象哈希码:public int hashCode()。
既然所有的类都是Object子类,那么就证明,Object之中所有的方法每一个类都会继承。当用户输出对象的时候,实际上都会默认调用toString()方法,如果子类没有覆写此方法,则会调用Object类之中提供的toString(),而此方法的默认实现是输出对象的公共属性 —— 对象地址数据。
class Person { // 默认隐含Object } public class TestDemo { public static void main(String args[]) { Person per = new Person() ; System.out.println(per) ; System.out.println(per.toString()) ; } } |
那么一般可以由用户自己进行toString()方法的覆写。
范例:覆写toString()方法
class Person { // 默认隐含Object private String name ; private int age ; public Person(String name,int age) { this.name = name ; this.age = age ; } public String toString() { return "姓名:" + this.name + ",年龄:" + this.age ; } } public class TestDemo { public static void main(String args[]) { Person per = new Person("张三",20) ; System.out.println(per) ; System.out.println(per.toString()) ; } } |
从今天开始getInfo()方法不需要再写了,写toString()就完了。
在学习String类的时候就已经接触到了equals()方法,是进行字符串相等比较的。因为String类的equals()方法就是覆写Object类的equals()方法而来。所以严格来讲,对象比较的方法不是之前的compare(),而应该换成equals()。
class Person { // 默认隐含Object private String name ; private int age ; public Person(String name,int age) { this.name = name ; this.age = age ; } public boolean equals(Object obj) { if (obj == null) { return false ; } if (this == obj) { return true ; } if (!(obj instanceof Person)) { // 不是本类实例 return false ; } Person person = (Person) obj ; // 向下转型 if (this.name.equals(person.name) && this.age == person.age) { return true ; } return false ; } public String toString() { return "姓名:" + this.name + ",年龄:" + this.age ; } } public class TestDemo { public static void main(String args[]) { Person perA = new Person("张三",20) ; Person perB = new Person("张三",20) ; System.out.println(perA.equals("GZB")) ; } } |
以后都使用equals()方法实现操作。
通过之前的分析已经清楚的知道了Object类是所有类的父类,那么一定可以接收所有的类对象,可是在Java设计的过程之中,考虑到参数的统一问题,让Object类的功能扩大了,即:使用Object可以接收数组或者是接口对象。
范例:使用Object接收数组
public class TestDemo { public static void main(String args[]) { Object obj = new int [] {3,2,1} ; if (obj instanceof int[]) { // 是int数组 int result [] = (int []) obj ; for (int x = 0 ; x < result.length ; x ++) { System.out.print(result[x] + "、") ; } } } } |
范例:利用Object接收接口对象
interface A { } class B implements A { public String toString() { return "HELLO" ; } } public class TestDemo { public static void main(String args[]) { A a = new B() ; // 接口实例化 Object obj = a ; // 使用Object接收接口对象 A temp = (A) obj ; // 向下转型 System.out.println(temp) ; } } |
所有的引用类型都可以使用Object接收,以后就可以利用Object作为参数的统一了。
在Java之中一直提倡一个概念:一切皆对象,那么现在结合Object的特点实际上就非常容易理解此概念的意义所在了(只有在全部都是对象时Object才可以达到统一参数的目的)。但是在这之中就出现了一个不和谐的地方:基本数据类型不是对象,所以最早的时候部分人就开始将基本数据类型做一个简单的包装,类似于如下的结构。
class Int { // 定义一个类 private int num ; // 保存基本数据类型 public Int(int num) { this.num = num ; // 保存数据 } public int intValue() { // 取出包装的数据 return this.num ; } } public class TestDemo { public static void main(String args[]) { int x = 10 ; // 基本类型 Int temp = new Int(x) ; // 将基本数据类型变为对象 Object obj = temp ; // 对象可以使用Object接收 // 假设中间经过了许多其它操作 Int result = (Int) obj ; // Object变为包装类对象 int y = result.intValue() ; // 从类之中取出包装的int型数据 System.out.println(y * 2) ; } } |
以上的代码非常的简单,但是既然我们都想到了此问题,作为软件的开发者也一定想得到。那么为了方便开发,专门针对于八种基本数据类型给出相应的系统包装类:int(Integer)、char(Character)、byte(Byte)、short(Short)、long(Long)、float(Float)、double(Double)、boolean(Boolean)。
但是以上的八种包装类也分为两组:
· 对象型包装类(直接继承Object类):Boolean、Character。
· 数值型包装类(继承了Number类):Byte、Short、Integer、Long、Float、Double;
Number本身是一个抽象类,在此类之中提供有如下的几个方法:byteValue()、shortValue()、longValue()、intValue()、doubleValue()、floatValue(),这六个方法就是从包装类里面取出被包装的基本数据。
现在对于数据就提供有基本类型和与之对应的包装类型,而这两者的转换定义如下:
· 装箱操作:将基本数据类型变为包装类对象,一般都利用各个包装类的构造方法进行;
· 拆箱操作:将包装类之中所包含的基本类型取出,一般都使用Number类定义的方法。
范例:以int和Integer为例
public class TestDemo { public static void main(String args[]) { Integer obj = new Integer(10) ; // 装箱 int result = obj.intValue() ; // 拆箱 System.out.println(result * 2) ; } } |
范例:以double和Double为例
public class TestDemo { public static void main(String args[]) { Double obj = new Double(10.1) ; // 装箱 double result = obj.doubleValue() ; // 拆箱 System.out.println(result * 2) ; } } |
Integer和Double都属于数值型的包装类,而对于包装类还存在有对象型。
范例:以boolean和Boolean为例
public class TestDemo { public static void main(String args[]) { Boolean obj = new Boolean(true) ; // 装箱 boolean result = obj.booleanValue() ; // 拆箱 System.out.println(result) ; } } |
以上的代码都是在JDK 1.5之前使用的,一切的操作必须手工的装箱和拆箱,但是从JDK 1.5之后开始增加了自动装箱和自动拆箱的功能,同时所有的包装类对象也可以直接进行各种数学计算。
范例:自动装箱与自动拆箱
public class TestDemo { public static void main(String args[]) { Integer obj = 10 ; // 自动装箱 int result = obj ; // 自动拆箱 System.out.println(result * ++ obj) ; } } |
在日后的所有开发之中没有必要再去调用构造或者是调用xxxValue()方法进行操作了。那么既然此时提供有自动的装箱操作,则意味着基本数据类型可以直接被Object接收。
范例:使用Object接收double型数据
public class TestDemo { public static void main(String args[]) { Object obj = 10.2 ; // double ==> 装箱为Double ==> Object double num = (Double) obj ; // Object ==> 向下转为Double ==> 拆箱为double System.out.println(num * 2) ; } } |
Object很无敌了,可以接收所有的参数了。
在所有的系统之中,用户输入的数据永远都使用String表示,例如:如果用户输入了年龄,则应该是字符串的数据,那么现在就必须提供有一个方案可以将字符串变为指定的基本数据类型,而这一操作就由包装类提供了:
· Integer类:public static int parseInt(String s);
· Double类:public static double parseDouble(String s);
· Boolean类:public static boolean parseBoolean(String s)。
范例:将String变为int型数据
public class TestDemo { public static void main(String args[]) { String str = "110" ; // 字符串 int num = Integer.parseInt(str) ; // String变为int System.out.println(num * 2) ; } } |
但是请一定要记住,在这种转换过程之中,字符串必须全部由数字所组成,如果里面出现了任何的非数字,则会出现“NumberFormatException”错误信息。
范例:将String变为double型数据
public class TestDemo { public static void main(String args[]) { String str = "111.11" ; // 字符串 double num = Double.parseDouble(str) ; // String变为double System.out.println(num * 2) ; } } |
范例:将String变为boolean
public class TestDemo { public static void main(String args[]) { String str = "true" ; // 字符串 boolean num = Boolean.parseBoolean(str) ; // String变为boolean System.out.println(num) ; } } |
在此类转换过程之中,如果字符串不是true或者是false,那么统一都转为false。
疑问?现在既然提供有字符串变为基本数据类型的操作,那么如何将基本数据类型变为字符串呢?
· 方式一:任何的基本数据类型加上空字符串就是字符串数据。
public class TestDemo { public static void main(String args[]) { int num = 100 ; String str = num +"" ; System.out.println(str + "," + str.length()) ; } } |
但是此类的操作会产生垃圾,所以一般不去使用。
· 方式二:在String类之中提供了一系列的valueOf()方法可以使用;
public class TestDemo { public static void main(String args[]) { int num = 100 ; String str = String.valueOf(num) ; System.out.println(str + "," + str.length()) ; } } |
此类操作是日后开发之中最为常见的一种操作形式,一定要掌握。
在现实生活之中经常会遇见如下的几种情况:
· 在一片森林之中有多种树木;
· 在商场之中有多种商品;
· 在一个停车场里停放着多种车辆,例如:卡车、轿车、摩托车、自行车。
下面模拟以上的一个场景。现在有间超市,在超市之中提供有多种商品,现在要求实现商品的上架销售和下架的功能,同时可以根据关键字查询出商品的信息。本程序只要求描述出类的结构即可。
范例:定义商品标准
interface Goods { // 商品 public String getName() ; public double getPrice() ; } |
范例:定义超市类
class SuperMarket { // 超市 private Link goods ; // 所有的商品 public SuperMarket() { this.goods = new Link() ; // 准备出存放商品的空间 } public void insert(Goods g) { // 增加商品 this.goods.add(g) ; // 保存数据到链表 } public void delete(Goods g) { // 下架 this.goods.remove(g) ; // 从链表删除数据 } public Link search(String keyWord) { Link result = new Link() ; Object obj [] = this.goods.toArray() ; for (int x = 0 ; x < obj.length ; x ++) { Goods gd = (Goods) obj[x] ; if (gd.getName().contains(keyWord)) { // 符合查找条件 result.add(gd) ; // 保存商品查询结果 } } return result ; } public Link getGoods() { // 取得本超市之中的所有商品 return this.goods ; } } |
范例:定义商品数据
class Cup implements Goods { private String name ; private double price ; public Cup(){} public Cup(String name,double price){ this.name = name ; this.price = price ; } public String getName() { return this.name ; } public double getPrice() { return this.price ; } public boolean equals(Object obj) { if (obj == null) { return false ; } if (this == obj) { return true ; } if (!(obj instanceof Cup)) { return false ; } Cup cup = (Cup) obj ; if (this.name.equals(cup.name) && this.price == cup.price) { return true ; } return false ; } public String toString() { return "【杯子】名称 = " + this.name + ",价格:" + this.price ; } } |
class Computer implements Goods { private String name ; private double price ; public Computer(){} public Computer(String name,double price){ this.name = name ; this.price = price ; } public String getName() { return this.name ; } public double getPrice() { return this.price ; } public boolean equals(Object obj) { if (obj == null) { return false ; } if (this == obj) { return true ; } if (!(obj instanceof Computer)) { return false ; } Computer com = (Computer) obj ; if (this.name.equals(com.name) && this.price == com.price) { return true ; } return false ; } public String toString() { return "【电脑】名称 = " + this.name + ",价格:" + this.price ; } } |
此时电脑和杯子没有本质的联系,但是他们都属于商品。
public class TestDemo { public static void main(String args[]) { SuperMarket market = new SuperMarket() ; market.insert(new Cup("卡通杯",10.0)) ; market.insert(new Cup("保温杯",20.0)) ; market.insert(new Cup("冷水杯",32.0)) ; market.insert(new Computer("限量版卡通电脑",3200.0)) ; market.insert(new Computer("殴打杯纪念电脑",99200.0)) ; market.insert(new Computer("不怕水的电脑",39200.0)) ; market.delete(new Cup("卡通杯",10.0)) ; Object obj [] = market.search("卡通").toArray() ; for (int x = 0 ; x < obj.length ; x ++) { System.out.println(obj[x]) ; } } } |
本程序就是一个典型的面向接口的编程,不同类之间依靠接口进行连接。