Java 内部类综述

摘要:html

  多重继承指的是一个类能够同时从多于一个的父类那里继承行为和特征,然而咱们知道Java为了保证数据安全,它只容许单继承。但有时候,咱们确实是须要实现多重继承,并且现实生活中也真正地存在这样的状况,好比遗传:咱们即继承了父亲的行为和特征也继承了母亲的行为和特征。可幸的是,Java 提供了两种方式让咱们曲折地来实现多重继承:接口和内部类。事实上,实现多重继承是内部类的一个极其重要的应用。除此以外,内部类还能够很好的实现隐藏(例如,私有成员内部类)。内部类共有四种类型,即成员内部类、静态内部类、局部内部类和匿名内部类。特别地,java

  • 成员内部类:成员内部类是外围类的一个成员,是 依附于外围类的,因此,只有先建立了外围类对象才可以建立内部类对象。也正是因为这个缘由,成员内部类也不能含有 static 的变量和方法;android

  • 静态内部类:静态内部类,就是修饰为 static 的内部类,该内部类对象 不依赖于外部类对象,就是说咱们能够直接建立内部类对象,但其只能够直接访问外部类的全部静态成员和静态方法;express

  • 局部内部类:局部内部类和成员内部类同样被编译,只是它的 做用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效;编程

  • 匿名内部类:定义匿名内部类的前提是,内部类必需要继承一个类或者实现接口,格式为 new 父类或者接口(){定义子类的内容(如函数等)}。也就是说,匿名内部类最终提供给咱们的是一个匿名子类的对象。安全

一. 内部类概述

一、 内部类基础less

  内部类指的是在一个类的内部所定义的类,类名不须要和源文件名相同。内部类是一个编译时的概念,一旦编译成功,内部类和外部类就会成为两个彻底不一样的类。例如,对于一个名为 Outer 的外部类和在其内部定义的名为 Inner 的内部类,在编译完成后,会出现 Outer.class 和 Outer$inner.class 两个类。所以,内部类的成员变量/方法名能够和外部类的相同。内部类能够是静态static的,也可用 public,default,protected 和 private 修饰。 特别地,关于 Java源文件名与类名的关系( java源文件名的命名与内部类无关,如下3条规则中所涉及的类和接口均指的是外部类/接口),须要符合下面三条规则:ide

  • 若是java源文件包含public类(public接口),则源文件名必须与public类名(public接口名)相同。 
      一个java源文件中,若是有public类或public接口,那么就只能有一个public类或一个public接口,不能有多个public的类或接口。固然,一个java源文件中能够有多个包可见的类或接口,即默认访问权限修饰符(类名前没有访问权限修饰符)。public类(接口) 与 包可见的类(接口)在文件中的顺序能够随意,即public类(接口)能够不在第一个的位置。函数

  • 若是java源文件不包含public类(public接口),则java源文件名没有限制。 
      只要符合文件名的命名规范就能够,能够不与文件中任一个类或接口同名,固然,也能够与其中之一同名。学习

  • 类和接口的命名不能冲突。 
      同一个包中的任何一个类或接口的命名都不能相同。不一样包中的类或接口的命名能够相同,由于经过包能够把它们区分开来。


二、 内部类的做用

使用内部类能够给咱们带来如下优势:

  • 内部类能够很好的实现隐藏(通常的非内部类,是不容许有 private 与 protected 权限的,但内部类能够);

  • 内部类拥有外围类的全部元素的访问权限;

  • 能够实现多重继承;

  • 能够避免修改接口而实现同一个类中两种同名方法的调用。


1)内部类能够很好的实现隐藏

  平时咱们对类的访问权限,都是经过类前面的访问修饰符来限制的,通常的非内部类,是不容许有 private 与 protected 权限的,但内部类能够,因此咱们能经过内部类来隐藏咱们的信息。能够看下面的例子:

//测试接口 public interface InterfaceTest { public void test(); } //外部类 public class Example { //内部类 private class InnerClass implements InterfaceTest{ @Override public void test() { System.out.println("I am Rico."); } } //外部类方法 public InterfaceTest getInnerInstance(){ return new InnerClass(); } } //客户端 public class Client { public static void main(String[] args) { Example ex = new Example(); InterfaceTest test = ex.getInnerInstance(); test.test(); } }/* Output: I am Rico. *///:~ 

  对客户端而言,咱们能够经过 Example 的getInnerInstance()方法获得一个InterfaceTest 实例,但咱们并不知道这个实例是如何实现的,也感觉不到对应的具体实现类的存在。因为 InnerClass 是 private 的,因此,咱们若是不看源代码的话,连实现这个接口的具体类的名字都看不到,因此说内部类能够很好的实现隐藏。


2)内部类拥有外围类的全部元素的访问权限

//外部类 public class Example { private String name = "example"; //内部类 private class Inner{ public Inner(){ System.out.println(name); // 访问外部类的私有属性 } } //外部类方法 public Inner getInnerInstance() { return new Inner(); } } //客户端 public class Client { public static void main(String[] args) { Example ex = new Example(); ex.getInnerInstance(); } }/* Output: example *///:~ 

  name 这个成员变量是在Example里面定义的私有变量,这个变量在内部类中能够被无条件地访问。

  

3)能够实现多重继承

  对多重继承而言,能够这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。内部类使得Java的继承机制更加完善,是内部类存在的最大理由。Java中的类只能继承一个类,它的多重继承在咱们没有学习内部类以前是用接口来实现的。但使用接口有时候有不少不方便的地方,好比,咱们实现一个接口就必须实现它里面的全部方法;而内部类可使咱们的类继承多个具体类或抽象类,规避接口的限制性。看下面的例子:

//父类Example1 public class Example1 { public String name() { return "rico"; } } //父类Example2 public class Example2 { public int age() { return 25; } } //实现多重继承的效果 public class MainExample { //内部类Test1继承类Example1 private class Test1 extends Example1 { public String name() { return super.name(); } } //内部类Test2继承类Example2 private class Test2 extends Example2 { public int age() { return super.age(); } } public String name() { return new Test1().name(); } public int age() { return new Test2().age(); } public static void main(String args[]) { MainExample mexam = new MainExample(); System.out.println("姓名:" + mexam.name()); System.out.println("年龄:" + mexam.age()); } }/* Output: 姓名:rico 年龄:25 *///:~ 

  注意到类 MainExample,在这个类中,包含两个内部类 Test1 和 Test2。其中,类Test1继承了类Example1,类Test2继承了类Example2。这样,类MainExample 就拥有了 类Example1 和 类Example2 的方法,也就间接地实现了多继承。


4) 避免修改接口而实现同一个类中两种同名方法的调用

  考虑这样一种情形,一个类要继承一个类,还要实现一个接口,但是它所继承的类和接口里面有两个相同的方法(方法签名一致),那么咱们该怎么区分它们呢?这就须要使用内部类了。例如,

//Test 所实现的接口 public interface InterfaceTest { public void test(); } //Test 所实现的类 public class MyTest { public void test(){ System.out.println("MyTest"); } } //不使用内部类的情形 public class Test extends MyTest implements InterfaceTest{ public void test(){ System.out.println("Test"); } }

  此时,Test中的 test() 方法是属于覆盖 MyTest 的 test() 方法呢,仍是实现 InterfaceTest 中的 test() 方法呢?咱们怎么能调到 MyTest 这里的方法?显然这是很差区分的。而咱们若是用内部类就很好解决这一问题了。看下面代码:

//Test 所实现的接口 public interface InterfaceTest { public void test(); } //Test 所实现的类 public class MyTest { public void test(){ System.out.println("MyTest"); } } //使用内部类的情形 public class AnotherTest extends MyTest { private class InnerTest implements InterfaceTest { @Override public void test() { System.out.println("InterfaceTest"); } } public InterfaceTest getCallbackReference() { return new InnerTest(); } public static void main(String[] args) { AnotherTest aTest = new AnotherTest(); aTest.test(); // 调用类MyTest 的 test() 方法 aTest.getCallbackReference().test(); // 调用InterfaceTest接口中的 test() 方法 } }

  经过使用内部类来实现接口,就不会与外围类所继承的同名方法冲突了。


三、 内部类的种类

  在Java中,内部类的使用共有两种状况:

  (1) 在类中定义一个类(成员内部类,静态内部类); 
  (2) 在方法中定义一个类(局部内部类,匿名内部类)。


二. 成员内部类

一、定义与原理

  成员内部类是最普通的内部类,它是外围类的一个成员,在实际使用中,通常将其可见性设为 private。成员内部类是依附于外围类的,因此,只有先建立了外围类对象才可以建立内部类对象。也正是因为这个缘由,成员内部类也不能含有 static 的变量和方法,看下面例子:

public class Outter { private class Inner { private final static int x=1; // OK /* compile errors for below declaration * "The field x cannot be declared static in a non-static inner type, * unless initialized with a constant expression" */ final static Inner a = new Inner(); // Error static Inner a1=new Inner(); // Error static int y; // Error } }

  若是上面的代码编译无误, 那么咱们就能够直接经过 Outter.Inner.a 拿到内部类Inner的实例。 因为内部类的实例必定要绑定到一个外部类的实例的,因此矛盾。所以,成员内部类不能含有 static 变量/方法。此外,成员内部类与 static 的关系还包括:

  • 包含 static final 域,但该域的初始化必须是一个常量表达式;
  • 内部类能够继承含有static成员的类。

二、交互

 成员内部类与外部类的交互关系为:

  • 成员内部类能够直接访问外部类的全部成员和方法,即便是 private 的;
  • 外部类须要经过内部类的对象访问内部类的全部成员变量/方法。
//外部类 class Out { private int age = 12; private String name = "rico"; //内部类 class In { private String name = "livia"; public void print() { String name = "tom"; System.out.println(age); System.out.println(Out.this.name); System.out.println(this.name); System.out.println(name); } } // 推荐使用getxxx()来获取成员内部类的对象 public In getInnerClass(){ return new In(); } } public class Demo { public static void main(String[] args) { Out.In in = new Out().new In(); // 片断 1 in.print(); //或者采用注释内两种方式访问 /* * 片断 2 Out out = new Out(); out.getInnerClass().print(); // 推荐使用外部类getxxx()获取成员内部类对象 Out.In in = out.new In(); in.print(); */ } }/* Output: 12 rico livia tom *///:~ 

 对于代码片断 1和2,能够用来生成内部类的对象,这种方法存在两个小知识点须要注意:

1) 开头的 Out 是为了标明须要生成的内部类对象在哪一个外部类当中; 
2) 必须先有外部类的对象才能生成内部类的对象。


所以,成员内部类,外部类和客户端之间的交互关系为:

  • 在成员内部类使用外部类对象时,使用 outer.this 来表示外部类对象;
  • 在外部类中使用内部类对象时,须要先进行建立内部类对象;
  • 在客户端建立内部类对象时,须要先建立外部类对象。

特别地,对于成员内部类对象的获取,外部类通常应提供相应的 getxxx() 方法。


三、私有成员内部类

  若是一个成员内部类只但愿被外部类操做,那么可使用 private 将其声明私有内部类。例如,

class Out {
    private int age = 12; private class In { public void print() { System.out.println(age); } } public void outPrint() { new In().print(); } } public class Demo { public static void main(String[] args) { /* * 此方法无效 Out.In in = new Out().new In(); in.print(); */ Out out = new Out(); out.outPrint(); } }/* Output: 12 *///:~ 

  在上面的代码中,咱们必须在Out类里面生成In类的对象进行操做,而没法再使用Out.In in = new Out().new In() 生成内部类的对象。也就是说,此时的内部类只对外部类是可见的,其余类根本不知道该内部类的存在。


三. 静态内部类

一、定义与原理

   静态内部类,就是修饰为 static 的内部类,该内部类对象不依赖于外部类对象,就是说咱们能够直接建立内部类对象。看下面例子: 
   
            静态内部类.png-29.5kB


二、交互

 静态内部类与外部类的交互关系为:

  • 静态内部类能够直接访问外部类的全部静态成员和静态方法,即便是 private 的;
  • 外部类能够经过内部类对象访问内部类的实例成员变量/方法;对于内部类的静态域/方法,外部类能够经过内部类类名访问。

三、成员内部类和静态内部类的区别

 成员内部类和静态内部类之间的不一样点包括:

  • 静态内部类对象的建立不依赖外部类的实例,但成员内部类对象的建立须要依赖外部类的实例;

  • 成员内部类可以访问外部类的静态和非静态成员,静态内部类不能访问外部类的非静态成员;


四. 局部内部类

一、定义与原理

   有这样一种内部类,它是嵌套在方法和做用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想建立一个类来辅助咱们的解决方案,但又不但愿这个类是公共可用的,因此就产生了局部内部类。局部内部类和成员内部类同样被编译,只是它的做用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

// 例 1:定义于方法内部 public class Parcel4 { public Destination destination(String s) { class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } return new PDestination(s); } public static void main(String[] args) { Parcel4 p = new Parcel4(); Destination d = p.destination("Tasmania"); } } 
// 例 2:定义于做用域内部 public class Parcel5 { private void internalTracking(boolean b) { if (b) { class TrackingSlip { private String id; TrackingSlip(String s) { id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip("slip"); String s = ts.getSlip(); } } public void track() { internalTracking(true); } public static void main(String[] args) { Parcel5 p = new Parcel5(); p.track(); } } 

二、final 参数

   对于final参数,如果将引用类型参数声明为final,咱们没法在方法中更改参数引用所指向的对象;如果将基本类型参数声明为final,咱们能够读参数,但却没法修改参数(这一特性主要用来向局部内部类和匿名内部类传递数据)。

  若是定义一个局部内部类,而且但愿它的方法能够直接使用外部定义的数据,那么咱们必须将这些数据设为是 final 的;特别地,若是只是局部内部类的构造器须要使用外部参数,那么这些外部参数就不必设置为 final,例如: 
      局部内部类.png-66.9kB


五. 匿名内部类

   有时候我为了免去给内部类命名,便倾向于使用匿名内部类,由于它没有名字。匿名内部类的使用须要注意如下几个地方:

  • 匿名内部类是没有访问修饰符的;

  • 匿名内部类是没有构造方法的 (由于匿名内部类连名字都没有)

  • 定义匿名内部类的前提是,内部类必须是继承一个类或者实现接口,格式为 new 父类或者接口(){子类的内容(如函数等)}。也就是说,匿名内部类最终提供给咱们的是一个匿名子类的对象,例如:

// 例 1 abstract class AbsDemo { abstract void show(); } public class Outer { int x=3; public void function()//可调用函数 { new AbsDwmo()//匿名内部类 { void show() { System.out.println("x==="+x); } void abc() { System.out.println("haha"); } }.abc(); //匿名内部类调用函数,匿名内部类方法只能调用一次 } }
// 例 2 interface Inner { //注释后,编译时提示类Inner找不到 String getName(); } public class Outer { public Inner getInner(final String name, String city) { return new Inner() { private String nameStr = name; public String getName() { return nameStr; } }; } public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); System.out.println(inner instanceof Inner); //匿名内部类实质上是一个匿名子类的对象 } /* Output: Inner true *///:~ } 

  • 若匿名内部类 (匿名内部类没有构造方法) 须要直接使用其所在的外部类方法的参数时,该形参必须为 final 的;若是匿名内部类没有直接使用其所在的外部类方法的参数时,那么该参数就没必要为final 的,例如:
// 情形 1:匿名内部类直接使用其所在的外部类方法的参数 name public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); } public Inner getInner(final String name, String city) { // 形参 name 被设为 final return new Inner() { private String nameStr = name; // OK private String cityStr = city; // Error: 形参 city 未被设为 final public String getName() { return nameStr; } }; } } // 情形 2:匿名内部类没有直接使用其所在的外部类方法的参数 public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); } //注意这里的形参city,因为它没有被匿名内部类直接使用,而是被抽象类Inner的构造函数所使用,因此没必要定义为final public Inner getInner(String name, String city) { return new Inner(name, city) { // OK,形参 name 和 city 没有被匿名内部类直接使用 private String nameStr = name; public String getName() { return nameStr; } }; } } abstract class Inner { Inner(String name, String city) { System.out.println(city); } abstract String getName(); } 

  从上述代码中能够看到,当匿名内部类直接使用其所在的外部类方法的参数时,那么这些参数必须被设为 final的。为何呢?本文所引用到的一篇文章是这样解释的:

  “这是一个编译器设计的问题,若是你了解java的编译原理的话很容易理解。首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。当外部类传的参数被内部类调用时,从java程序的角度来看是直接的调用,例如:

public void dosome(final String a,final int b){ class Dosome{ public void dosome(){ System.out.println(a+b) } }; Dosome some=new Dosome(); some.dosome(); } 

  从代码来看,好像是内部类直接调用的a参数和b参数,可是实际上不是,在java编译器编译之后实际的操做代码是:

class Outer$Dosome{  
  public Dosome(final String a,final int b){ this.Dosome$a=a; this.Dosome$b=b; } public void dosome(){ System.out.println(this.Dosome$a+this.Dosome$b); } }

  从以上代码来看,内部类并非直接调用方法传进来的参数,而是内部类将传进来的参数经过本身的构造器备份到了本身的内部,本身内部的方法调用的实际是本身的属性而不是外部类方法的参数。这样就很容易理解为何要用final了,由于二者从外表看起来是同一个东西,实际上却不是这样,若是内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,由于从编程人员的角度来看他们是同一个东西,若是编程人员在程序设计的时候在内部类中改掉参数的值,可是外部调用的时候又发现值其实没有被改掉,这就让人很是的难以理解和接受,为了不这种尴尬的问题存在,因此编译器设计人员把内部类可以使用的参数设定为必须是final来规避这种莫名其妙错误的存在。”


  以上关于匿名内部类的每一个例子使用的都是默认无参构造函数,下面咱们介绍 带参数构造函数的匿名内部类

public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); } public Inner getInner(final String name, String city) { return new Inner(name, city) { //匿名内部类 private String nameStr = name; public String getName() { return nameStr; } }; } } abstract class Inner { Inner(String name, String city) { // 带有参数的构造函数 System.out.println(city); } abstract String getName(); } 

  特别地,匿名内部类经过实例初始化 (实例语句块主要用于匿名内部类中),能够达到相似构造器的效果,以下:

public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); System.out.println(inner.getProvince()); } public Inner getInner(final String name, final String city) { return new Inner() { private String nameStr = name; private String province; // 实例初始化 { if (city.equals("gz")) { province = "gd"; }else { province = ""; } } public String getName() { return nameStr; } public String getProvince() { return province; } }; } } 

六. 内部类的继承

  内部类的继承,是指内部类被继承,普通类 extents 内部类。而这时候代码上要有点特别处理,具体看如下例子: 
              内部类继承.png-48.5kB

  能够看到,子类的构造函数里面要使用父类的外部类对象.super() [成员内部类对象的建立依赖于外部类对象];而这个外部类对象须要从外面建立并传给形参。


引用

谈谈Java的匿名内部类 
java源文件名与类名的关系 
Java内部类的做用 
Java内部类的使用小结 
Java中普通内部类为什么不能有static数据和static字段,也不能包含嵌套类。 
java提升篇(八)—-详解内部类 
【解惑】领略Java内部类的“内部” 
java提升篇(九)—–实现多重继承

相关文章
相关标签/搜索