内部类
1 概述
1、什么是内部类?
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
2、为什么要声明内部类呢?
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。
而且内部类因为在外部类的里面,因此可以直接访问外部类的私有成员。
3、内部类都有哪些形式?
根据内部类声明的位置(如同变量的分类),我们可以分为:
(1)成员内部类:
-
静态成员内部类
-
非静态成员内部类
(2)局部内部类
-
有名字的局部内部类
-
匿名的内部类
2 静态内部类
语法格式:
【修饰符】 class 外部类{
【其他修饰符】 static class 内部类{
}
}
静态内部类的特点:
-
和其他类一样,它只是定义在外部类中的另一个完整的类结构
-
可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
-
可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
-
可以使用abstract修饰,因此它也可以被其他类继承
-
可以使用final修饰,表示不能被继承
-
编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
-
-
和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
-
外部类只允许public或缺省的
-
-
只可以在静态内部类中使用外部类的静态成员
-
在静态内部类中不能使用外部类的非静态成员哦
-
-
在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象
-
如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别
示例代码:
public class TestInner{
public static void main(String[] args){
Outer.Inner in= new Outer.Inner();
in.inMethod();
Outer.Inner.inTest();
Outer.Inner.inFun(3);
}
}
class Outer{
private static int a = 1;
private int b = 2;
protected static class Inner{
static int d = 4;//可以
void inMethod(){
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的
}
static void inTest(){
System.out.println("out.a = " + a);
}
static void inFun(int a){
System.out.println("out.a = " + Outer.a);
System.out.println("local.a = " + a);
}
}
}
其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry。
3 非静态成员内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 class 内部类{
}
}
非静态内部类的特点:
-
和其他类一样,它只是定义在外部类中的另一个完整的类结构
-
可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
-
可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量。
-
可以使用abstract修饰,因此它也可以被其他类继承
-
可以使用final修饰,表示不能被继承
-
编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
-
-
和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
-
外部类只允许public或缺省的
-
-
还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的
-
在外部类的静态成员中不可以使用非静态内部类哦
-
就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
-
-
在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象
-
因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象
-
示例代码:
public class TestInner{
public static void main(String[] args){
Outer out = new Outer();
Outer.Inner in= out.new Inner();
in.inMethod();
Outer.Inner inner = out.getInner();
inner.inMethod();
}
}
class Father{
protected static int c = 3;
}
class Outer{
private static int a = 1;
private int b = 2;
protected class Inner extends Father{
// static int d = 4;//错误
int b = 5;
void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + Outer.this.b);
System.out.println("in.b = " + b);
System.out.println("father.c = " + c);
}
}
public static void outMethod(){
// Inner in = new Inner();//错误的
}
public Inner getInner(){
return new Inner();
}
}
练习1:语法练习题
声明一个身体Body类,包含一个私有的boolean类型的属性live,初始化为true,表示活着。属性私有化,提供get/set方法。
声明一个身体Body的内部类Heart,包含void beat()方法,当live为true时,打印“心脏在跳动”,否则打印“心脏停止跳动"。因为Heart只为外部类Body服务,而又具有自己的方法,属性等,而且这里应该是有Body实体存在的情况下才能有Heart实体,所以这里把Heart声明为非静态内部类。
声明一个测试类,在测试类的主方法中,创建身体和心脏的对象,调用心脏对象的beat()方法,然后调用身体对象的setLive()方法,设置为false后,再调用心脏对象的beat()方法查看结果。
public class Person {
private boolean live = true;
class Heart {
public void beat() {
// 直接访问外部类成员
if (live) {
System.out.println("心脏在跳动");
} else {
System.out.println("心脏不跳了");
}
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
}
public class InnerDemo {
public static void main(String[] args) {
// 创建外部类对象
Person p = new Person();
// 创建内部类对象
Heart heart = p.new Heart();
// 调用内部类方法
heart.beat();
// 调用外部类方法
p.setLive(false);
// 调用内部类方法
heart.beat();
}
}
输出结果:
心脏在跳动
心脏不跳了
或
public class Beatable{//可跳动的
public abstract void beat();
}
public class Person {
private boolean live = true;
private Heart heart = new Heart();
private class Heart implements Beatable{
public void jump() {
// 直接访问外部类成员
if (live) {
System.out.println("心脏在跳动");
} else {
System.out.println("心脏不跳了");
}
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
public Beatable getHeart(){
return heart;
}
}
public class InnerDemo {
public static void main(String[] args) {
// 创建外部类对象
Person p = new Person();
// 获取内部类对象
Beatable heart = p.getHeart();
// 调用内部类方法
heart.beat();
// 调用外部类方法
p.setLive(false);
// 调用内部类方法
heart.beat();
}
}
输出结果:
心脏在跳动
心脏不跳了
练习2:简单面试题
判断如下代码的运行结果:
public class Test{
public Test(){
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
s2.a = 20;
Test.Inner s3 = new Test.Inner();
System.out.println(s3.a);
}
class Inner{
public int a = 5;
}
public static void main(String[] args) {
Test t = new Test();
Inner r = t.new Inner();
System.out.println(r.a);
}
}
练习3:高难面试题
代码填空题:
public class TestInner{
public static void main(String[] args){
Outer.Inner in = new Sub();
in.method();//输出 hello inner
}
}
class Outer {
abstract class Inner{
abstract void method();
}
}
class Sub ________(1)__________{
______(2)多行代码_______________
}
参考答案:
public class TestInner{
public static void main(String[] args){
Outer.Inner in = new Sub();
in.method();//输出 hello inner
}
}
class Outer {
abstract class Inner{
abstract void method();
}
}
class Sub extends Outer.Inner{
static Outer out = new Outer();
Sub(){
out.super();
}
4 局部内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【final/abstract】 class 内部类{
}
}
}
局部内部类的特点:
-
和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
-
可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
-
可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量
-
可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
-
可以使用final修饰,表示不能被继承
-
编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
-
这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
-
-
-
和成员内部类不同的是,它前面不能有权限修饰符等
-
局部内部类如同局部变量一样,有作用域
-
局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法
-
局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
-
JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
-
示例代码:
class Outer{
private static int a = 1;
private int b = 2;
public static void outMethod(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
System.out.println("out.local.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
public void outTest(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + b);//可以,因为outTest是飞静态的
System.out.println("method.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
}
思考
为什么在局部内部类中使用外部类方法的局部变量要加final呢?
public class TestInner{
public static void main(String[] args) {
A obj = Outer.method();
//因为如果c不是final的,那么method方法执行完,method的栈空间就释放了,那么c也就消失了
obj.a();//这里打印c就没有中可取了,所以把c声明为常量,存储在方法区中
}
}
interface A{
void a();
}
class Outer{
public static A method(){
final int c = 3;
class Sub implements A{
5 匿名内部类
1、引入
当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?
(1)编写类,继承这个父类或实现这个接口
(2)重写父类或父接口的方法
(3)创建这个子类或实现类的对象
例如:
public interface Runnable{
public abstract void run();
}
//声明接口实现类
public class MyRunnable implements Runnable{
public void run(){
while(true){
System.out.println("大家注意安全");
try
Thread.sleep(1000);
}catch(Exception e){
}
}
}
}
public class Test{
public static void main(String[] args){
//如果MyRunnable类只是在这里使用一次,并且只创建它的一个对象
//分开两个.java源文件,反而不好维护
Runnable target = new MyRunnable();
Thread t = new Thread("安全提示线程",target);
t.start();
}
}
这里,因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。
可以修改为如下形式:
public class Test{
public static void main(String[] args){
//MyRunnable类只是在这里使用一次,并且只创建它的一个对象,那么这些写代码更紧凑,更好维护
Runnable target = new Runnable(){
public void run(){
while(true){
System.out.println("大家注意安全");
try
Thread.sleep(1000);
}catch(Exception e){
}
}
}
};
Thread t = new Thread("安全提示线程",target);
t.start();
}
}
2、语法格式
new 父类(【实参列表】){
重写方法...
}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
new 父接口(){
重写方法...
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。
注意:
匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:
-
在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
-
在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final
思考:这个对象能做什么呢?
答:(1)调用某个方法(2)赋值给父类/父接口的变量,通过多态引用使用这个对象(3)作为某个方法调用的实参
3、使用方式一:匿名内部类的对象直接调用方法
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
class B{
public void b(){
System.out.println("bbbb");
}
}
public class Test{
public static void main(String[] args){
new B(){
public void b(){
System.out.println("ccccc");
}
}.b();
}
}
4、使用方式二:通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
class B{
public void b(){
System.out.println("bbbb");
}
}
public class Test{
public static void main(String[] args){
B obj = new B(){
public void b(){
System.out.println("ccccc");
}
};
obj.b();
}
}
5、使用方式三:匿名内部类的对象作为实参
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
6、练习
练习1
声明一个Employee员工类,包含编号、姓名、薪资,
声明一个测试类,在main中,创建Employee[]数组,长度为5,显示原来顺序结果
调用java.util.Arrays数组工具类的排序方法public static void sort(Object[] a, Comparator c)对数组的元素进行排序,用匿名内部类的对象给c形参传入按照薪资比较大小的定制比较器对象。并显示排序后结果
调用java.util.Arrays数组工具类的排序方法public static void sort(Object[] a, Comparator c)对数组的元素进行排序,用匿名内部类的对象给c形参传入按照编号比较大小的定制比较器对象。并显示排序后结果
员工类示例代码:
class Employee{
private int id;
private String name;
private double salary;
public Employee(int id, String name, double salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
public Employee() {
super();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
测试类:
public class TestInner {
public static void main(String[] args) {
Employee[] arr = new Employee[5];
arr[0] = new Employee(1,"张三",13000);
arr[1] = new Employee(3,"王五",14000);
arr[2] = new Employee(2,"李四",13000);
arr[3] = new Employee(4,"赵六",7000);
arr[4] = new Employee(5,"钱七",9000);
//原顺序
System.out.println("员工列表:");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
Arrays.sort(arr, new Comparator() {