java面试整理(三)—— 内部类、静态内部类、成员内部类、局部内部类、匿名内部类
知识总结
内部类
- 外部类只能是public或者默认,不可以是protected之类
- 内部类是一个编译是的概念,一旦编译成功,就会成为完全不同的两个类,分别为outer.class和outer$inner.class类。内部类的成员变量/方法名可以和外部类的相同。
- 可以很好的实现隐藏
- 可是实现多重继承
- 可以避免修改接口而实现同一个类中两种同名方法的调用。
静态内部类
- 静态内部类与非静态内部类的区别在于:静态内部类没有了指向外部的引用。
- 在静态内部类new一个外部类的对象,可以访问外部的非公开的(private 修饰的)成员变量与方法(jdk1.8测试)
- 静态内部类中可以定义静态或者非静态的成员
- 在外部类中,创建的静态内部类对象可以直接访问对象中的所有成员变量和方法,无论是否是私有的。
非静态内部类
- 非静态内部类能访问外部类的一切成员, 包括私有成员。
- 外部类虽然不能直接访问内部类的成员, 但是可以通过内部类的实例访问内部类的私有成员。
- 非静态内部类可以获得外部类的引用从而实现回调。
- 非静态内部类不能有静态成员,因为内部类需要先创建了外部类,才能创建它自己的对象。但是其可以使用 static final 定义常量
成员内部类
- 成员内部类可以直接访问外部类的所有成员变量、方法,但是外部类如果想要调用成员内部类中所有成员变量或方法则需要成员内部类的对象来获取
- 相同名称成员变量作用域重合时的调用,使用Outer.this.num调用外部类成员变量,this.num,调用内部类成员变量,num调用方法中的局部变量。若不重合,则直接使用成员变量调用即可。
局部内部类
- 局部内部类不能被public、private、static修饰;
- 局部内部类中不可定义静态变量或方法,当访问外部方法的局部变量(即方法内的变量)时,变量必须是final修饰的的(jdk1.8环境下可以不显式的加final,但是我们不能对变量进行修改操作,因为它是默认final型的)。
- 在外部类中不能创建局部内部类的实例
- 创建局部内部类的实例只能在包含他的方法中;
- 外部类不能访问局部内部类,只能在方法体中访问局部内部类,且访问必须在内部类定义之后。
匿名内部类
- 为了免去给内部类命名,或者只想使用一次,就可以选择使用匿名内部类。
- 于匿名内部类没有自己的名字,所以其在实例化时,必须继承一个父类(可以是一个抽象类)或者实现一个接口,但是也只能是二者选其一,而不能即继承父类,又实现接口。而且其继承和实现都是隐式的,不是显式的使用extends和implement关键字来继承和实现的。
- 由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法。
- 匿名内部类不能定义任何静态成员、方法。
- 局部内部类的所有限制同样对匿名内部类生效。
- 匿名内部类访问的外部类成员变量或成员方法必须用final修饰;jdk1.8可以不需要显式的声明
内部类
又称之为嵌套类,是在类中在定义另外一个类。内部类几乎可以处于一个类内部任何位置,可以与实例变量处于同一级,或处于方法之内,甚至是一个表达式的一部分。
外部类:包含内部类的类
内部类的名字不允许与外部类的名字相同。因为在外部引用它时必须给出带有外部类名的完整名称(外部类名.内部类名)
外部类只能是public或者默认,不可以是protected之类;内部类访问控制符均可,也可使用abstract或者final修饰。
内部类是一个编译是的概念,一旦编译成功,就会成为完全不同的两个类,分别为outer.class和outer$inner.class类。所以内部类的成员变量/方法名可以和外部类的相同。
内部类关系
内部类作用
1.内部类可以很好的实现隐藏
2.非静态内部类拥有外围类的所有元素的访问权限 (private修饰也能访问)
3.可是实现多重继承 (让多个内部类分别继承多个其他类,使外部类可以同时获取多个其他类的属性)
4.可以避免修改接口而实现同一个类中两种同名方法的调用。(外部类继承,让内部类实现接口)
静态内部类
静态内部类与非静态内部类的区别在于:静态内部类没有了指向外部的引用。静态内部类不与外部类有关系,即静态内部类只是将类放在外部类中,但是在虚拟机编译之后,其是两个完全不同的两个类,就和两个普通类一样,但又有所不同(可以看下面的),静态内部类中不能直接调用外部类的成员变量或方法。而非静态内部类则不同,其和外部类是有联系的,其可以直接调用外部类的方法和成员变量,即使外部类使用private修饰的变量或方法也一样能够调用。
若想在静态内部类中调用外部类的成员变量或方法,只能在静态内部类new一个外部类的对象,就像普通类调用对象一样。和普通的两个类不同的是,这种方法,可以访问外部的非公开的(private 修饰的)成员变量与方法。(jdk1.8测试)
创建静态内部类对象可以不依靠与外部类对象来实现:
outClass.staticInnerClass obj = new outClass.staticInnerClass();
- 1
以下皆是在jdk1.8环境下测试
静态内部类中可以定义静态或者非静态的成员
静态内部类中可以直接访问外部类的静态成员变量或者方法,无论是否公开
同理
在外部类中调用静态内部类,只需要直接new innerClass()即可,
在外部类中,创建的静态内部类对象可以直接访问对象中的所有成员变量和方法,无论是否是私有的。
可以参考以下代码:
public class test {
private String name = "test";
private static String na = "ss";
String test(){
StaticInerCls inn = new StaticInerCls();
return inn.name1;
}
static class StaticInerCls{
private String name1 = "innerName";
public String ss(){
test t = new test();
String s = t.name;
String ss = t.test();
String sss = na;
return sss;
}
}
}
public class testA {
public static void main(String[] args) {
System.out.println(s());
System.out.println(new test().test());
}
public static String s(){
test.StaticInerCls s = new test.StaticInerCls();
return s.ss();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
非静态内部类
非静态内部类才是真正意义上的内部类,其可以和外部类有直接联系。非静态内部类能访问外部类的一切成员, 包括私有成员。外部类虽然不能直接访问内部类的成员, 但是可以通过内部类的实例访问内部类的私有成员。非静态内部类可以获得外部类的引用从而实现回调。而且通过创建非静态内部类还可已让java实现真正的多继承!
非静态内部类不能有静态成员,因为内部类需要先创建了外部类,才能创建它自己的对象。但是其可以使用 static final 定义常量
常量池会维护这些常量。虽然非静态内部类不能脱离外部类这个上下文实例化,但是常量池使得final变量脱离了类实例化这个条件,编译期间便可确定。
成员内部类
成员内部类之所以定义为成员内部类,原因在于其定义的位置和普通外部类的成员变量和成员方法一样,把该内部类定义为外部类的一个成员,也即其可成员变量、成员方法是同一级的。形如:
public class OuterCls {
private String name;
public String getName(){
return name;
}
class InerCls{
private String name;
public String getName(){
return name;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
成员内部类可以直接访问外部类的所有成员变量、方法,但是外部类如果想要调用成员内部类中所有成员变量或方法则需要成员内部类的对象来获取
创建成员内部类对象:
OuterCls outerCls = new OuterCls();
OuterCls.InerCls inerCls = outerCls.new InerCls();
- 1
- 2
成员内部类中可以含有常量
private static final String TYPE = "tes";
- 1
内部类访问外部类成员变量
class Day10_11 {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.print();
}
}
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void print(){
int num = 30;
System.out.println(num);//本方法中的局部变量,
System.out.println(this.num);//内部类中的成员变量
System.out.println(Outer.this.num);//外部类中的成员变量,Outer是外部类名
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
以上调用是在相同名称成员变量作用域重合时的调用,若不重合,则直接使用成员变量调用即可。
局部内部类
指内部类定义在方法体内,在局部内部类前不加修饰符public或private,只能在该方法或条件的作用域内才能使用,退出这写作用域就无法引用。
局部内部类不能被public、private、static修饰;
局部内部类中不可定义静态变量或方法,当访问外部方法的局部变量(即方法内的变量)时,变量必须是final修饰的的(jdk1.8环境下可以不显式的加final,但是我们不能对变量进行修改操作,因为它是默认final型的)。
解析:这是作用域的问题。在方法method执行完成后,局部变量value就失效了,而在new Inner()产生的in对象还存在obj的引用,这样对象就访问了一个不存在的变量,是不允许的。iner还存在,在外面和后续调用该局部变量时,这个局部变量可能已经失效了。但为什么加上final就可以保证能访问呢?这里Java采用了一种copy local variable的方法实现,定义为final的变量,会拷贝一份存到局部内部类中,后续使用持续维护这个对象在生命周期内,所以可以继续访问。
在外部类中不能创建局部内部类的实例
创建局部内部类的实例只能在包含他的方法中;
外部类不能访问局部内部类,只能在方法体中访问局部内部类,且访问必须在内部类定义之后。
匿名内部类
为了免去给内部类命名,或者只想使用一次,就可以选择使用匿名内部类。其和局部内部类一样,也是定义在外部类的方法体内。
其创建方式如下格式:
new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}
- 1
- 2
- 3
- 4
从上面我们可以看出,由于匿名内部类没有自己的名字,所以其在实例化时,必须继承一个父类(可以是一个抽象类)或者实现一个接口,但是也只能是二者选其一,而不能即继承父类,又实现接口。而且其继承和实现都是隐式的,不是显式的使用extends和implement关键字来继承和实现的。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
在创建匿名内部类时,由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法。
可以改写父类中的方法,添加自定义方法。
我们常用的线程的方法一般就是使用匿名内部类,如:
public void countDown(){
new Thread(){
@Override
public void run() {
...
}
}.start();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,
匿名内部类不能定义任何静态成员、方法。
匿名内部类中的方法不能是抽象的,因为其本身不是抽象类;
匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
匿名内部类访问的外部类成员变量或成员方法必须用final修饰;jdk1.8可以不需要显式的声明
匿名内部类因为没有类名,可知匿名内部类不能定义构造器。其会借助与父类的默认构造器,那么怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。如:
public class OutClass {
public InnerClass getInnerClass(final int age,final String name){
return new InnerClass() {
int age_ ;
String name_;
//构造代码块完成初始化工作
{
if(0 < age && age < 200){
age_ = age;
name_ = name;
}
}
public String getName() {
return name_;
}
public int getAge() {
return age_;
}
};
}
public static void main(String[] args) {
OutClass out = new OutClass();
InnerClass inner_1 = out.getInnerClass(201, "chenssy");
System.out.println(inner_1.getName());
InnerClass inner_2 = out.getInnerClass(23, "chenssy");
System.out.println(inner_2.getName());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32