内部类

Posted daidai66

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内部类相关的知识,希望对你有一定的参考价值。

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类

而且内部类因为在外部类的里面,因此可以直接访问外部类的私有成员

根据内部类声明的位置(如同变量的分类),我们可以分为:

(1)成员内部类:

  • 静态成员内部类

  • 非静态成员内部类

(2)局部内部类

  • 有名字的局部内部类

  • 匿名的内部类

静态内部类

  1 【修饰符】 class 外部类{
  2     【其他修饰符】 static class 内部类{
  3     }
  4 }

静态内部类的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

    • 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员

    • 可以使用abstract修饰,因此它也可以被其他类继承

    • 可以使用final修饰,表示不能被继承

    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号

  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

    • 外部类只允许public或缺省的

  • 只可以在静态内部类中使用外部类的静态成员

    • 在静态内部类中不能使用外部类的非静态成员哦

  • 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象

  • 如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别

  1 public class TestInner{
  2     public static void main(String[] args){
  3     	Outer.Inner in= new Outer.Inner();
  4     	in.inMethod();
  5 
  6     	Outer.Inner.inTest();
  7 
  8         Outer.Inner.inFun(3);
  9     }
 10 }
 11 
 12 class Outer{
 13 	private static int a = 1;
 14 	private int b = 2;
 15 	protected static class Inner{
 16 		static int d = 4;//可以
 17 		void inMethod(){
 18 			System.out.println("out.a = " + a);
 19 //			System.out.println("out.b = " + b);//错误的
 20 		}
 21 		static void inTest(){
 22 			System.out.println("out.a = " + a);
 23 		}
 24         static void inFun(int a){
 25 			System.out.println("out.a = " + Outer.a);
 26             System.out.println("local.a = " + a);
 27 		}
 28 	}
 29 }

严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry

非静态成员内部类

  1 【修饰符】 class 外部类{
  2     【修饰符】 class 内部类{
  3     }
  4 }

非静态内部类的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

    • 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量

    • 可以使用abstract修饰,因此它也可以被其他类继承

    • 可以使用final修饰,表示不能被继承

    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号

  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

    • 外部类只允许public或缺省的

  • 还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的

  • 在外部类的静态成员中不可以使用非静态内部类哦

    • 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样

  • 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象

    • 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象

  1 public class TestInner{
  2     public static void main(String[] args){
  3     	Outer out = new Outer();
  4     	Outer.Inner in= out.new Inner();
  5     	in.inMethod();
  6 
  7     	Outer.Inner inner = out.getInner();
  8     	inner.inMethod();
  9     }
 10 }
 11 class Father{
 12 	protected static int c = 3;
 13 }
 14 class Outer{
 15 	private static int a = 1;
 16 	private int b = 2;
 17 	protected class Inner extends Father{
 18 //		static int d = 4;//错误
 19 		int b = 5;
 20 		void inMethod(){
 21 			System.out.println("out.a = " + a);
 22 			System.out.println("out.b = " + Outer.this.b);
 23 			System.out.println("in.b = " + b);
 24 			System.out.println("father.c = " + c);
 25 		}
 26 	}
 27 
 28 	public static void outMethod(){
 29 //		Inner in = new Inner();//错误的
 30 	}
 31 	public Inner getInner(){
 32 		return new Inner();
 33 	}
 34 }

局部内部类

  1 【修饰符】 class 外部类{
  2     【修饰符】 返回值类型  方法名(【形参列表】){
  3final/abstractclass 内部类{
  4     	}
  5     }
  6 }

局部内部类的特点:

  • 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构

    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

    • 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量

    • 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承

    • 可以使用final修饰,表示不能被继承

    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号

      • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类

  • 和成员内部类不同的是,它前面不能有权限修饰符等

  • 局部内部类如同局部变量一样,有作用域

  • 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法

  • 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量

    • JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final

  1 class Outer{
  2 	private static int a = 1;
  3 	private int b = 2;
  4 
  5 	public static void outMethod(){
  6 		final int c = 3;
  7 		class Inner{
  8 			public void inMethod(){
  9 				System.out.println("out.a = " + a);
 10 //				System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
 11 				System.out.println("out.local.c = " + c);
 12 			}
 13 		}
 14 
 15 		Inner in = new Inner();
 16 		in.inMethod();
 17 	}
 18 
 19 	public void outTest(){
 20 		final int c = 3;
 21 		class Inner{
 22 			public void inMethod(){
 23 				System.out.println("out.a = " + a);
 24 				System.out.println("out.b = " + b);//可以,因为outTest是非静态的
 25 				System.out.println("method.c = " + c);
 26 			}
 27 		}
 28 
 29 		Inner in = new Inner();
 30 		in.inMethod();
 31 	}
 32 
 33 }

匿名内部类

1、引入

当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?

(1)编写类,继承这个父类或实现这个接口

(2)重写父类或父接口的方法

(3)创建这个子类或实现类的对象

  1 public interface Runnable{
  2     public abstract void run();
  3 }
  1 //声明接口实现类
  2 public class MyRunnable implements Runnable{
  3     public void run(){
  4         while(true){
  5             System.out.println("大家注意安全");
  6             try
  7             	Thread.sleep(1000);
  8             }catch(Exception e){
  9             }
 10         }
 11     }
 12 }
  1 public class Test{
  2     public static void main(String[] args){
  3         //如果MyRunnable类只是在这里使用一次,并且只创建它的一个对象
  4         //分开两个.java源文件,反而不好维护
  5         Runnable target = new MyRunnable();
  6         Thread t = new Thread("安全提示线程",target);
  7         t.start();
  8     }
  9 }

因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题

可以修改为如下形式:

  1 public class Test{
  2     public static void main(String[] args){
  3         //MyRunnable类只是在这里使用一次,并且只创建它的一个对象,那么这些写代码更紧凑,更好维护
  4         Runnable target = new Runnable(){
  5             public void run(){
  6                 while(true){
  7                     System.out.println("大家注意安全");
  8                     try
  9                         Thread.sleep(1000);
 10                     }catch(Exception e){
 11                     }
 12                 }
 13             }
 14         };
 15         Thread t = new Thread("安全提示线程",target);
 16         t.start();
 17     }
 18 }

2、语法格式

  1 new 父类(【实参列表】){
  2     重写方法...
  3 }
  4 //()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
  1 new 父接口(){
  2     重写方法...
  3 }
  4 //()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造

匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象

匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:

  • 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态

  • 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final

3、使用方式一:匿名内部类的对象直接调用方法

  1 interface A{
  2 	void a();
  3 }
  4 public class Test{
  5     public static void main(String[] args){
  6     	new A(){
  7 			@Override
  8 			public void a() {
  9 				System.out.println("aaaa");
 10 			}
 11     	}.a();
 12     }
 13 }
  1 class B{
  2 	public void b(){
  3 		System.out.println("bbbb");
  4 	}
  5 }
  6 public class Test{
  7     public static void main(String[] args){
  8     	new B(){
  9     		public void b(){
 10     			System.out.println("ccccc");
 11     		}
 12     	}.b();
 13 
 14     }
 15 }

4、使用方式二:通过父类或父接口的变量多态引用匿名内部类的对象

  1 interface A{
  2 	void a();
  3 }
  4 public class Test{
  5     public static void main(String[] args){
  6     	A obj = new A(){
  7 			@Override
  8 			public void a() {
  9 				System.out.println("aaaa");
 10 			}
 11     	};
 12     	obj.a();
 13     }
 14 }
  1 class B{
  2 	public void b(){
  3 		System.out.println("bbbb");
  4 	}
  5 }
  6 public class Test{
  7     public static void main(String[] args){
  8     	B obj = new B(){
  9     		public void b(){
 10     			System.out.println("ccccc");
 11     		}
 12     	};
 13     	obj.b();
 14     }
 15 }

5、使用方式三:匿名内部类的对象作为实参

  1 interface A{
  2 	void method();
  3 }
  4 public class Test{
  5     public static void test(A a){
  6     	a.method();
  7     }
  8 
  9     public static void main(String[] args){
 10     	test(new A(){
 11 
 12 			@Override
 13 			public void method() {
 14 				System.out.println("aaaa");
 15 			}
 16 
 17     	});
 18     }
 19 }

以上是关于内部类的主要内容,如果未能解决你的问题,请参考以下文章

片段 - 全局视图变量与本地和内部类侦听器和内存泄漏

为啥片段类应该是公开的?

ForegroundService没有从片段开始?

在内部片段类中使用ListView

自定义无内存泄漏的Handler内部类

底部导航 如何从片段内部更改片段