牛刀小试 - 详解Java中的接口与内部类的使用

一、接口

  • 接口的理解

Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现;

也就是说,接口自身自提供方法的基本声明,而不提供方法体;接口中声明的方法只能被实现该接口的子类所具体实现。

接口是Java中另一种非常重要的结构。因为Java不支持多继承,某种程度来说这也造成了一定的局限性。

所以接口允许多实现的特点弥补了类不能多继承的缺点。通常通过继承和接口的双重设计,可以既保持类的数据安全也变相实现了多继承。

  • 接口的特点
  1. 使用关键字"interface"声明一个接口;使用关键字"implements"声明一个类实现一个接口。
  2. 与类的权限限制相同,接口的也只能被声明为“public”或默认修饰符。
  3. 接口当中声明的变量被自动的设置为public、static、final,也就是说在接口声明的变量实际都会被隐式的提升为"公有的静态常量"
  4. 接口中声明的方法都是抽象的,并且都被自动的设置为public。
  5. 接口自身不能被构造实例化,但可以通过实现该接口的类进行实例化。
  6. 实现接口的类如果不是抽象类,那么该类就必须对接口中的方法进行实现。
  7. 接口与接口之间也可以实现继承关系。子接口除拥有父接口的所有方法声明外,还可以定义新的方法声明。
package com.tsr.j2seoverstudy.interface_demo;

//访问修饰符只能为public或默认访问修饰符
public interface InterfaceDemo {
	// 会被隐式的提升为:public static final int VAR = 50;
	int VAR = 50;

	// public void methodDemo();
	void methodDemo();//方法必须是抽象的
}

class Test implements InterfaceDemo{

	public static void main(String[] args) {
		InterfaceDemo in = new InterfaceDemo();//compile exception

		InterfaceDemo in = new Test();//但可以声明接口类型的变量,并通过实现该接口的类来进行实例化
	}

	@Override
	public void methodDemo() {
	}

}  

根据接口的特点,实际上我们可以看到:接口实际上更像是在声明一种规范,相当于实现定义了程序的一种框架。例如,作为一种遥控汽车的设计者。你可能需要提供一些规范给这些遥控汽车的生产厂商们,让他们按照你的设计规范来生产遥控汽车。

package com.tsr.j2seoverstudy.interface_demo;

public interface Moveable {
	void turnLeft();

	void turnRight();

	void stop();
}

这是你提供的让玩具汽车具备可移动性(moveable)的接口,生产厂商必须按照该接口的规范进行实现,才能然小汽车成功的move起来。

  • 接口与抽象类的区别
  1. 一个类可以实现多个接口,但只能继承一个抽象类。
  2. 抽象类中可以存在非抽象方法,但接口中声明的方法必须是抽象的。
  3. 抽象类中的方法可以是任何的访问权限,但接口中的方法都是public权限的。
  4. 抽象类中可以存在自己定义的任何类型的实例域(变量等),但接口中的域都是公有、静态、最终的。

而抽象类和接口最大的相同之处,可能就在于:都是对其体系中的对象,不断的进行向上抽取而来的共有特性。它们都可以用于多态的实现。

二、内部类

1、什么是内部类?

顾名思义,内部类就指定义在另一个类的内部当中的类。我们知道一个Java所编写的基本体现形式就是一个类,而一个类的结构通常是由域(静态域、实例域)和方法构成的。而有时候一个类中还有另一种构成部分,就是内部类。

2、为什么使用内部类?

关于这点,《Java2学习指南》中这样说:你是一个OO程序猿,因此知道为了代码的重用性和灵活性(可扩展性),需要将类保持足够的专用性。也就是说,一个类只应该具有该类对象需要执行的代码;而任何其它操作,都应该放在更适合这些工作的其它的类当中。但是!有时候会出现当前类当中需要的某个操作,应当放在另一个单独的特殊类中更为合适,因为要保持类足够的专用性;但不巧的是,这些操作又与当前的类有着密切联系(例如会使用到当前类当中的成员(包括私有成员)等等)。正是这一类的情况,促使了内部类的诞生。

而更具体的来说,之所以使用内部类的原因,通常主要在于:

  • 在内部类当中可以访问该类定义所在的作用域当中的任何数据,包括私有数据。(这是因为内部类会隐式的持有所在外部类的对象引用:“外部类名.this”)
package com.tsr.j2seoverstudy.base;

public class Outer {
	private int num = 5;

	private class Inner {
		void printOuterNum() {
			/*
			 * 1.验证了内部类可以访问其定义所在的作用域当中的任何属性,包括被声明为私有的属性。
			 * 2.之所以内部类能访问外部类的实例属性,是因为其隐式的持有了外部类的对象:外部类类名.this
			 */
			System.out.println(num);
			System.out.println(Outer.this.num);
		}
	}
}

可以看到上面例子中虽然外部类“Outer”中的变量“num”被声明为私有的,但定义在“Outer”当中的内部类仍然可以访问到该成员变量。

  • 内部类能够针对于存在同一个包下的其他类,将自身隐藏起来。简单的来说,该好处就是带来更完善的类的封装性。
package com.tsr.j2seoverstudy.base;

class Outer {
	private int num = 5;

	// 暴露给别人的方法接口
	public void exposeMethod() {
		Inner in = new Inner();
		System.out.println(in.doSomeThingSecret());
	}

	private class Inner {
		int doSomeThingSecret() {
			// 封装一些你不想暴露给其它人任何细节的方法
			System.out.println("隐蔽的方法,叼!");
			return num;
		}
	}
}

/*
 * 程序输出结果为:
 * 隐蔽的方法,叼!
 * 5
 */
public class Test{
    public static void main(String[] args) {
		Outer out = new Outer();
		out.exposeMethod();
	}
}

通过该例子我们可以看到通过内部类实现带来的严谨的封装性。我们通过内部类“Inner”的方法“doSomeThingSecret”完成了一系列“秘密的操作”。

但我们提供给它人使用时,暴露给使用者的细节只有外部类当中的一个方法“exposeMethod”。这就很好的达到了我们的目的,隐藏不想让别人知道的操作。

我们知道通常来讲,类的访问权限只能被修饰为public 或 默认的。这就意味着即使我们选择相对较小的访问权限:默认的包访问权限。

那么我们定义的该类当中的实现细节,也会暴露给位于同一个包中的其它类。

而内部类允许被声明为pirvate。这意味着:其它类甚至连我们定义了这样的一个类都不知道,就更不用提该类当中的实现细节了。

  • 通过匿名内部类能够更为便捷的定义回调函数。以Java中的多线程机制为例:

如果不使用内部类,那么我们的实现方式就应该如同:

package com.tsr.j2seoverstudy.base;

public class InnerDemo {

	public static void main(String[] args) {
		Assignment assignment = new Assignment();
		Thread t = new Thread(assignment);
		t.start();
	}
}

class Assignment implements Runnable{

	@Override
	public void run() {
		System.out.println("线程任务");

	}

}

而通过匿名内部类,我们可以将实现简化为:

package com.tsr.j2seoverstudy.base;

public class InnerDemo {

	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {
				System.out.println("线程任务");

			}
		});
		t.start();
	}
}

与我在之前的回顾中说到的一样,“匿名”其实很好理解,直接的理解就是没有名字。Java中的标示符就是名字。

所以,在第一种实现方式里,定义线程任务类的类名标示符“Assignment ”就是该线程任务类的类名。

而当我们将线程的任务通过第二种方式实现时,我们发现该其直接被作为参数传递给Thread类的构造函数当中。

没有相关的类名标示符,那么这个线程任务类就是没有名字的,所以被称为“匿名”。

注:第三种使用方式应该是实际开发中最常用的。前两种情况我个人在工作里很少用到,但很多知名的书中都介绍了,所以不妨也作为一种了解,总会有用得上的时候。

创建内部类对象的方式

在上面我们说过了,内部类当中是隐式的持有一个其所属外部类的对象引用的。

由此就不能想象,一个内部类的对象创建肯定是依赖于其所属的外部类的。

换句话说,要像创建使用一个内部类的对象,前提是必须先获取到其所属外部类的对象。

内部类的创建方式大概也就是两种情况:

  • 在其所属外部类当中,创建该内部类对象:这种情况与平常创建对象的方式没有任何不同,也就是ClassName clazz = new ClassName();这样的方式。
  • 在其所属外部类之外的类中创建内部类对象:因为我们说过了内部类对象的创建依赖于其所属外部类,所以这是的创建方式为:Outer.Inner in = new Outer().new Inner()。

我们还是通过一道以前看见过的面试题,来更直观的了解内部类的对象创建:

/*
 * 题目:
 * 1. public class Outer{
 * 2. public void someOuterMethod() {
 * 3. // Line 3
 * 4. }
 * 5. public class Inner{}
 * 6. public static void main( String[] args ) {
 * 7. Outer o = new Outer();
 * 8. // Line 8
 * 9. }
 * 10. }
 *
 * Which instantiates an instance of Inner?
 * A. new Inner(); // At line 3
 * B. new Inner(); // At line 8
 * C. new o.Inner(); // At line 8
 * D. new Outer.Inner(); // At line 8

 */

其实很简单,只要牢记我们上面说的两种创建情况就OK了。归纳来讲:在创建内部类对象之前,必须先构造其外部类的对象。

所以当在外部类中创建内部类对象,因为外部类自身持有对象引用:this。所以可以直接创建内部类。

而在外部类之外创建内部类对象,则就需要先new Outer()创建得到外部类对象,再创建内部类对象。

由此我们来分别看该题目当中的4个答案:

  • A答案放在程序的第三行。是在其所属外部类当中的实例方法中创建内部类对象,因为实例方法持有外部类对象引用this,所以可以直接创建。则A答案合法。
  • B答案放在程序的第八行。虽然是在内部类本身创建内部类对象,但因为代码是位于静态方法当中,所以并不持有其外部类对象。所以B是非法的。
  • C答案放在程序的第八行。代码虽然位于静态方法中,但因为之前已经创建了外部类对象“o”,所以再通过“o”创建内部类对象的方式是行得通的的。但要注意的是这种方式的正确使用形式应当是“o.Inner()”而非“new o.Inner()”。所以C也是非法的。
  • D答案放在程序的第八行。乍看之下,十分完美。但要注意的是其使用的是“new Outer.”,而非通过new关键字调用类构造器创建对象的正确方式“new Outer().”。所以D自然也是非法的。

由此可以得出结论,合法的内部类实例声明方式只有:A。

局部内部类

局部内部类是内部类之中一种稍显特殊的使用方式。顾名思义,与“局部变量”相同,也就是被定义在方法或代码块当中的内部类。

关于局部内部类的使用,我觉得需要掌握的主要只有三点:

第一、与其它的局部成员一样,局部类的有效范围被限定在包含的代码块中,一旦超出该范围,该局部内部类就不能被访问了。

第二、局部内部类不能被访问修饰符修饰,也就是说不能使用private、protected、public任一修饰符。因为作用域已经被限定在了当前所属的局部块中。

第三、这通常也是使用局部内部类的最常见原因。你可能也注意到了,普通的内部类虽然能访问任何其所属外部类的成员;但其所属外部类定义的方法当中的局部变量是访问不到的,使用局部内部类就可以解决这一问题。但必须谨记的是:被局部内部类访问的变量必须被修饰为final。

public class PartInnerDemo {
    int num_1 = 10;

	public void method(){
		final int num_2 = 5;
		class PartInner{
		   private void InnerMethod(){
			  System.out.println(num_1+num_2);
		   }
		}
	}
}

静态内部类(嵌套类)

静态内部类可以说是内部类当中的一朵奇葩。开个玩笑,之所以这样说是因为静态内部类是比较特殊的一种内部类。

它的特性更像是一种嵌套类,而非内部类。因为我们前面说过了一般内部类当中,都会隐式的持有一个其所属外部类的对象引用。而静态内部类则不会。

除此之外,在任何非静态内部类当中,都不能存在静态数据。所以,如果想在内部类中声明静态数据,那么这个内部类也必须被声明为静态的。

当然,静态类中除了静态数据,也可以声明实例数据。不同之处在于:

如果要在之外使用静态内部类当中的静态数据,那么可以直接通过该内部类的:类名.静态成员名的方式。

而如果要只用该静态内部类当中的实例成员,那么就必须如同其他非静态内部类一样,先创建该内部类的对象。

但同时需要注意,静态内部类的对象创建与一般的内部类又有所不同,因为我们知道静态内部类自身是不持有外部类的对象引用的,所以它不依赖于外部类的对象。简单的说,我们可以认为静态内部类自身也就是所属外部类的一个静态成员,所以其对象创建的方式为:Outer.Inner in = new Outer.Inner();

public class StaticInner {

	void test() {
		int num_1 = Inner.num_1;
		//
		Inner in = new Inner();
		int num = in.num;
	}

	private static class Inner {
		int num = 5;
		static int num_1 = 10;
	}

        public static void main(String[] args) {
        StaticInner.Inner in = new StaticInner.Inner();
        }
 }

说到这里,想起另外一个问题。这个问题在初学Java时一直没能想清楚原因:

public class Test {

	public static void main(String[] args) {
		class Inner{
			String name;
			Inner(String s){
				name = s;
			}
		}

		Inner o = new Inner("test");
	}
}

我们在前面说过,所有非静态的内部类的实例化工作,都依赖于其外部类的对象实例化。

但在这段代码中,我们对于定义的局部内部类“Inner”的对象创建却没有依赖于其所在外部类。

这应当是因为:因为该局部内部类被定义在一个静态方法中,静态方法中不会持有其所属的类的对象引用this。

也就是说,定义在静态方法当中的局部内部类遭受与静态方法同样的限制:不能访问任何其所属外部类的非静态成员。

而内部类之所以持有其外部类对象引用的目的在于:可以访问其所属外部类的所有实例成员。

那么既然现在我已经被限制成不能访问实例成员了,自然也就不必依赖于外部类的对象了。

匿名内部类

关于匿名内部类,其实在上面说为什么使用内部类的三种原因时,已经说过了。

匿名内部类的定义格式通常为:

new SuperType(constuction parameters){

               //inner class method and data

}

对于匿名内部类,简单来说就是一种内部类的简写形式。

而必须注意的是,对于匿名内部类的使用,其前提是:该内部类必须是继承于一个外部类或者实现一个外部接口。

正如我们在上面说到的关于Java多线程。之所以能使用匿名内部类,是因为我们定义的匿名内部类实现了Runnable接口。

时间: 2024-08-28 02:13:05

牛刀小试 - 详解Java中的接口与内部类的使用的相关文章

详解Java中代码块和继承

本文发表于个人GitHub主页,原文请移步详解Java中代码块和继承 阅读. 概念 1.代码块 局部代码块 用于限定变量生命周期,及早释放,提高内存利用率 静态代码块 对类的数据进行初始化,仅仅只执行一次. 构造代码块 把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造代码块. 2.继承 继承是已有的类中派生出新的类,新的类能够吸收已有类的数据属性和行为,并能扩展新的功能. 代码块的执行顺序 public class Test {    public String name

【Java学习笔记之三十三】详解Java中try,catch,finally的用法及分析

这一篇我们将会介绍java中try,catch,finally的用法 以下先给出try,catch用法: try { //需要被检测的异常代码 } catch(Exception e) { //异常处理,即处理异常代码 } 代码区如果有错误,就会返回所写异常的处理. 首先要清楚,如果没有try的话,出现异常会导致程序崩溃.而try则可以保证程序的正常运行下去,比如说: try { int i = 1/0; } catch(Exception e) { ........ } 一个计算的话,如果除数

详解Java中的clone方法

转载自:http://blog.csdn.net/zhangjg_blog/article/details/18369201 Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有几种方式可以创建对象呢? 1 使用new操作符创建一个对象 2 使用clone方法复制一个对象 那么这两种方式有什么相同和不同呢? new操作符的本意是

详解Java中Map用法

Map以按键/数值对的形式存储数据,这里要特别说明( Map.Entry,是Map的内部类,它用来描述Map中的键/值对). Map是一个接口,我们平时多用它的实现类HashMap. 用例如下: public static void main(String args[]) { HashMap hashmap = new HashMap(); hashmap.put("Item0", "Value0"); hashmap.put("Item1",

详解Java中的访问控制修饰符(public, protected, default, private)

Java中的访问控制修饰符已经困惑笔者多时,其中较复杂的情况一直不能理解透彻.今天下定决心,系统.全面地研究Java中的访问控制修饰符的所有方面,并整理成这篇文章,希望有同样疑惑的读者读完后能有所收获.如果文章中出现错误,欢迎评论指出,共同交流~ 说在前面:这篇文章只研究Java中访问控制修饰符声明类的变量/方法的情况. 先抛出结论: * 成员变量/方法的访问权限 *                                        private        default  

详解Java中格式化日期的DateFormat与SimpleDateFormat类

DateFormat其本身是一个抽象类,SimpleDateFormat 类是DateFormat类的子类,一般情况下来讲DateFormat类很少会直接使用,而都使用SimpleDateFormat类完成,下面我们具体来看一下两个类的用法: DateFormat1. DateFormat 介绍DateFormat 的作用是 格式化并解析“日期/时间”.实际上,它是Date的格式化工具,它能帮助我们格式化Date,进而将Date转换成我们想要的String字符串供我们使用不过DateFormat

详解Java中的clone方法:原型模式

Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有几种方式可以创建对象呢? 1 使用new操作符创建一个对象2 使用clone方法复制一个对象 那么这两种方式有什么相同和不同呢? new操作符的本意是分配内存.程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间.分配完内存之

牛刀小试 - 详解Java多线程

线程与多线程的概念 关于线程与多线程的较详细的理解可以参考:线程的解释 和多线程的解释. 而我们要做的是,对其进行"精炼".我们每天都在和电脑.手机打交道,每天都在使用各种各样的应用软件. 打开上电脑的任务管理器,就可以看到有一项名为"进程"的栏目,点击到里面可能就会发现一系列熟悉的名称:QQ,360等等. 所以首先知道了,QQ.360之类的应用软件在计算机上被称为一个进程. 而一个应用程序都会有自己的功能,用以执行这些进程当中的个别功能的程序执行流就是所谓的线程.

详解java中clone方法

原文地址:http://leihuang.net/2014/11/14/java-clone/ In java, it essentially means the ability to create an object with similar state as the original object. 什么是clone 字典中的意思就是复制(强调跟原来的一模一样). *By default, java cloning is 'field by field copy' *.因为Object类不知