最复习的时候发现内部类已经有部分知识开始遗忘了。所以特地抽出时间学习内部类。
1.为什么需要使用内部类
大部分的时候,类被定义成一个独立的程序单元。内部类就是把一个类放在另一个类的内部,包含内部类的类也被称为外部类。
内部类主要有以下作用:
a.内部类提供更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类.
b.内部类成员可以直接访问外部类私有数据,因为内部类被当做其外部类成员,同一个类的成员之间可以相互访问.但外部类不能访问内部类的实现细节,例如内部类的成员变量.
c.匿名内部类适合用于创建那些仅需要使用一次的类
内部类和外部类主要有两种区别
a.内部类比外部类多三个修饰符,private 、protected、static
b.非静态内部类不能拥有成员变量
如下是非静态内部类的使用
package innerclass;
public class Cow {
private double weight;
public Cow() {}
public Cow(double weight) {
this.weight = weight;
}
private class CowLeg{
private double length;
private String color;
public CowLeg() {}
public CowLeg(double length,String color) {
this.length = length;
this.color = color;
}
public void info() {
System.out.println("color:"+color+" length:"+length+" weight:"+weight);
}
}
public void test() {
CowLeg cl = new CowLeg(1.12,"black and white");
cl.info();
}
public static void main(String[] args) {
Cow cow = new Cow(378.9);
cow.test();
}
}
当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;如果不存在,则在该方法所在的内部类查找是否存在该名字的成员变量,如果存在则使用;如果还是找不到,那就查找该内部类的外部类是否存在该变量,存在则使用;
另外,非静态内部类里不能有静态方法、静态成员变量、静态初始块,例如下面都是错的
package innerclass;
public class InnerNoStatic {
private class InnerClass{
static {
System.out.println("");
}
private static int inProp;
private static void test() {}
}
}
2.静态内部类
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。
静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员。
如下是错误的
package innerclass;
public class StaticInnerClassTest {
private int prop1 = 5;
private static int prop2 = 5;
static class StaticInnerClass{
private static int age;
public void accessOuterProp() {
System.out.println(prop1);
}
}
}
输出prop1是错误的,不能通过编译.
除此之外Java还允许在接口里定义内部类,接口里默认是public static 修饰的,也就是说接口里是静态内部类
如果省略接口内部类访问控制符,则默认是public
3.使用内部类
分三种情况讨论内部类的用法
1.在外部类内部使用内部类:
在外部类内部使用内部类和平常使用普通的类没有什么区别,唯一的区别就是不要在外部类的静态成员(包括静态方法和静态初始块)中使用非静态内部类。
2.在外部类以外使用非静态内部类
如果希望在外部类以外的地方访问内部类(包括静态和非静态两种),则内部类不能使用private访问控制权限。
a.省略了访问控制符的内部类,只能被外部类或处于同一个包中的其他类访问。
b.使用了protected修饰的内部类,可被与外部类处于同一包中的其他类和外部类的子类所访问。
c.public修饰的内部类,可以在任何地方呗访问。
变量语法的格式如下:
OuterClass.InnerClass varName
由于非静态内部类的对象必须寄生在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。
OuterInstance.new InnerConstructor()
3.在外部类以外使用静态内部类
因为静态内部类是外部类类相关的,因此创建静态内部类对象是无需创建外部类对象。在外部类以外的地方创建静态内部类实例的如法如下:
new OuterClass.InnerConstructor();
4.局部内部类
如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。
如下是一个局部内部类的简单用法
package innerclass;
public class LocalInnerClass {
public static void main(String[] args) {
class InnerBase{
int a;
}
class InnerSub extends InnerBase{
int b;
}
InnerSub is = new InnerSub();
is.a = 5;
is.b = 8;
}
}
实际上局部内部类是一个非常“鸡肋”的语法,现实中很少去使用。
5.Java8改进的匿名内部类
匿名内部类适合创建那些要一次使用的类
定义匿名内部类的语法如下:
new 实现接口() | 父类构造器(实参列表)
{
//匿名内部类实体成分
}
从上面定义可以看出匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类和继承一个接口。
匿名内部类还有如下两条规则:
1.匿名内部类不能是抽象类,这个就不说了为什么了。。。
2.匿名内部类不能定义构造器。由于匿名内部类没名字啊,哥哥姐姐们,所以肯定不能有构造器啊。
如下是一个匿名内部类的简单用法:
package innerclass;
interface Product{
public double getPrice();
public String getName();
}
public class AnnotmousTest {
public void test(Product p) {
System.out.println("buy a :" + p.getName() +" cost " + p.getPrice());
}
public static void main(String[] args) {
AnnotmousTest ta = new AnnotmousTest();
ta.test(new Product() {
@Override
public double getPrice() {
// TODO Auto-generated method stub
return 567;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return "hahaa";
}
});
}
}
当创建匿名内部类时就必须实现接口或抽象父类里的所有抽象方法。
在Java8之前,Java要求被局部内部类,匿名内部类访问的局部变量必须使用final修饰,从Java8这个就被取消了,如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final.
如下
package innerclass;
interface A{
void test();
}
public class ATest {
public static void main(String[] args) {
int age = 8;
A a = new A() {
@Override
public void test() {
// TODO Auto-generated method stub
System.out.println(age);
}
};
a.test();
//加了下面将会导致编译错误
//age = 2;
}
}
6.Java8新增的Lambda表达式
Lambda表达式将代码块作为方法参数(很大原因在于优化匿名内部类)
我们看个例子:
// Java 8之前:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Before Java8, too much code for too little to do");
}
}).start();
//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
可以看出,lambda表达式简化了匿名内部类,不需要指出重写的名字,也不需给出重写的方法的返回值类型--只要给出重写的方法括号以及括号里的形参列表即可。
Lambda由三部分组成:
1.形参列表。如果形参列表只有一个参数,甚至连形参列表的圆括号也可以省略。
2.箭头。
3.代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号,Lambda代码块只有一条return语句,甚至可以省略return关键字。如果代码块只有一条语句,Lambda表达式会自动返回这条表达式的值。
如下是Lambda表达式的
package innerclass;
interface Eatable {
void taste();
}
interface Flyable {
void fly(String weather);
}
interface Addable {
int add(int a, int b);
}
public class LambdaTest {
public void eat(Eatable e) {
System.out.println(e);
e.taste();
}
public void drive(Flyable f) {
System.out.println("I drive " + f);
f.fly("好天气");
}
public void test(Addable add) {
System.out.println("The sum of five add three is " + add.add(5, 3));
}
public static void main(String[] args) {
LambdaTest lt = new LambdaTest();
lt.eat(() -> System.out.println("hahaha"));
lt.drive(weather ->{
System.out.println("the wheather is " + weather);
});
lt.test((a,b) -> a + b);
}
}
7.Lambda表达式与函数式接口
Lambda表达式的类型,也被称为“目标类型”,Lambda表达式的目标类型必须是函数式接口。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。
(Java8有专门的注解@FunctionalInterface来声明,如果不是函数式接口就会报错。)
package innerclass;
public class Test {
public static void main(String[] args) {
Runnable r = () ->{
System.out.println("hahaa");
};
}
}
从上面的讲解,和上面的例子可以看出Lambda有如下两个限制。
a.Lambda表达式的目标类型必须是明确的函数式接口
b.Lambda表达式只能为函数式接口创建对象
package innerclass;
public class Test {
public static void main(String[] args) {
//false example
Object obj = () -> System.out.println("hahah");;
}
}
如上这是错误实例,因为Lambda表达式只能为函数式接口创建对象。
为了保证Lambda表达式的目标类型是一个明确的函数式接口,有三种做法
a.将表达式赋值给函数式接口类型的变量
b.将表达式作为函数式接口类型的参数传给某方法
c.对表达式进行强转
如下例子
Object obj = (Runnable) () -> System.out.println("hahah");