外部类、接口只能使用public、abstract、final修饰,不能使用private、protected、static修饰,就是说外部类、接口的访问权限只能是默认、public。
局部变量不能使用static修饰,但可以使用final修饰。
内部类提供了更好的封装,可以把内部类隐藏在外部类之内。
普通内部类
修饰符 class Outer{
修饰符 class Inner{
.......
}
}
普通内部类相当于外部类的一个成员,可以使用任何修饰成员的修饰符。内部类中可以有构造函数、初始化块、成员变量、成员方法。
内部类内(内部类成员级、内部类方法体中均)可以直接访问外部类的成员。
在外部类中访问内部类的成员:
外部类的成员方法中不能直接访问内部类中的成员,需要new Inner()创建内部类对象,再通过内部类对象访问内部类的成员。
在外部类外访问内部类的成员:
外部类类名.内部类名 变量名=外部类实例对象.new 内部类(参数表); //需要先创建内部类实例,再通过内部类实例访问内部类的成员
外部类类名.内部类名 变量名=new 外部类(参数表) .new 内部类(参数表); //内部类是public、要访问的内部类成员时public才可以访问
内部类的完整类名是:外部类类名.内部类类名,有时候还需要加前缀包名。
会编译为两个.class文件,内部类一个,外部类一个。
创建外部类对象时,在heap中会有两个对象,一个外部类的,一个内部类的,二者分开存储,但内部类中保存了对应的外部类的引用。
当外部类成员、内部类成员、内部内的局部变量同名时,在内部类的方法体中:
this 表示当前内部类对象
外部类类名.this 表示当前外部类对象
静态内部类
修饰符 class Outer{
static class Inner{
.......
}
}
该内部类属于外部类本身,不属于外部类的任何一个实例。静态内部类也是外部类的一个成员。
静态内部类中可以包含静态成员,也可以包含普通成员。static只是表示此内部类是外部类的类成员,并不是说此内部类只能包含静态成员。
静态内部类中只能访问外部类的静态成员,不能访问外部内的非静态成员,但可以访问内部类自身的所有成员。
在外部类中访问内部类的成员:
需要先 new 内部类类名(参数表) 创建内部类对象,再通过该对象来访问。
内部类的静态成员可通过内部类类名直接访问。
在外部类外访问内部类的成员:
外部类.内部类 变量名=new 外部类.内部类(参数表); //通过外部类类名直接调用内部类的构造函数(内部类使用static修饰),参数表是内部类构造函数的参数表
内部类的静态成员可直接通过 外部类类名.内部类类名.静态成员 调用。
外部类.内部类 也是一种数据类型,表示内部类。
局部内部类
定义在方法体中的内部类,和局部变量一个级别。
局部变量不能使用任何访问权限、static修饰,但可以使用final来修饰,所以局部内部类也不能使用这些来修饰,但局部内部类可以使用final来修饰。
局部内部类中的成员,和正常的成员一样,可以使用一切修饰符。
局部内部类的上一级程序单元是所在方法,不是类。
在局部内部类中,只能访问所在方法体中的局部变量(只能在成员一级访问,比如用来给内部类的成员变量赋值,不能在内部类的方法中访问),不能访问外部类的成员。
匿名内部类
new 接口名|父类构造器(实参表){
........
}
匿名内部类没有类名,只能使用一次。会在定义处创建该类的一个实例。
实质还是定义一个类,要实现一个接口,或继承+重写一个父类。但最多只能实现一个接口,或继承+重写一个父类。
因为没有类名,所以没有构造函数,但可以有初始化块。
如果匿名内部类是外部类的成员级,则内部类中可以访问外部类的成员,比如:
private ActionListener al=new ActionListener{
.............
}
如果匿名内部类是局部变量级的,则匿名内部类中不能访问外部内的成员,比如:
public void xxx(){
........
btn.addActionListener(new ActionListener{
.......
});
}
注意:
外部类成员级的内部类,在内部类的成员级、方法体内均可直接访问外部类的成员。
局部变量级的内部类,只能在内部类的成员级访问外部类的成员,在内部类的方法体内不能访问外部类的成员。
Lambda表达式
函数式接口:
只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能包括一个抽象方法。
Java8提供了@FunctionalInterface注解,该注解放在接口定义前面,表示该接口是函数式接口,编译时会检查该接口是否符合函数式接口的要求。
Lambda表达式可以实现函数式接口,并在实现处创建一个对象,和匿名内部类相似。
区别:匿名内部类可以实现任何接口、抽象类,但Lambda表达式只能实现函数式接口。Lambda表达式相当于一种特殊的匿名内部类,但Lambda表达式比匿名内部类更加简洁。
Lambda表达式只是语法糖,编译时会被替换为常规的代码。
Lambda表达式语法:
(形参表) -> {
.......//抽象方法的实现部分
}
无形参时,写为();只有一个形参时,可以缺省()。
代码块中只有一条语句时,可以缺省{};代码块中只有一条return语句时,可以缺省{}和return关键字(要缺省,必须同时缺省return关键字和{})。
参数表只写形参变量名,不写形参类型。
示例:
1 package test; 2 3 interface Interface1{ 4 //无形参、无返回值 5 void print(); 6 } 7 8 interface Interface2{ 9 //有形参 10 void print(String str); 11 } 12 13 interface Interface3 { 14 //有返回值 15 String getString(); 16 } 17 18 public class Test { 19 public static void main(String[] args) { 20 Interface1 interface1=()->{ 21 System.out.println("接口1 ok"); 22 }; 23 interface1.print(); 24 25 //只有一个形参时,可以缺省();代码块只有一条语句时,可以缺省{} 26 Interface2 interface2=str-> System.out.println(str); 27 interface2.print("接口2 ok"); 28 29 //方法体中只有一条return语句时,可以同时缺省{}和return关键字,要缺省就必须同时缺省这两部分 30 Interface3 interface3=()-> "接口3 ok"; 31 String str=interface3.getString(); 32 System.out.println(str); 33 } 34 }
Lambda表达式会在该处创建一个该接口的示例,并返回这个实例,即Lambda表达式的值是对应接口的实例。
可以将Lambda表达式作为实参传递给方法:
1 package test; 2 3 interface Interface3 { 4 //有返回值 5 String getString(); 6 } 7 8 public class Test { 9 public static void main(String[] args) { 10 //可以将Lambda表达式作为实参传递给方法。Lambda表达式的值是对应接口的实例,并不是所实现抽象方法的返回值String。Lambda表达式比匿名内部类更简洁。 11 printStr(()->"ok"); 12 } 13 14 public static void printStr(Interface3 interface3){ 15 System.out.println(interface3.getString()); 16 } 17 }
Java中的很多接口都是函数式接口,比如ActionListener接口、Runnable接口。
Lambda表达式可访问的变量和匿名内部类可访问的变量完全一致,但Lambda表达式不能调用接口中定义的默认方法,匿名内部类则可以。