文章目录
一,内部类
什么是内部类
- 定义在类内部的类就被称为内部类。外部类按常规的类访问方式使用内部类,唯一的差别是内部类可以访问外部类的所有方法与属性,包括私有方法与属性。
- 内部类是一个编译时的概念。外部类outer.java内定义了一个内部类inner,一旦编译成功,就会生成两个完全不同的.class文件,分别是outer.class和outer$inner.class。
为什么要设计内部类
- 内部类是为了更好的封装,把内部类封装在外部类里,不允许同包其他类访问
- 内部类中的属性和方法即使是外部类也不能直接访问,相反内部类可以直接访问外部类的属性和方法,即使private
- 实现多继承:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
- 匿名内部类用于实现回调
内部类分类
1,静态内部类
定义:定义在类内部的静态类
示例:
public class Out {
private static int a;
private int b;
public static class Inner {
public void print() {
System.out.println(a);
}
}
}
解析:Inner是静态内部类。静态内部类可以访问外部类所有静态变量和方法。静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
使用:
Out.Inner inner = new Out.Inner();
inner.print();
实现原理:
public class Out$Inner {
public Out$Inner() {
}
public void print() {
System.out.println(Out.access$000());
}
}
原理解析:
Out.java编译后会生成两个class文件,分别是Out.class和Out$Inner.class。因为这两个类处于同一个包下,所以静态内部类自然可以访问外部类的非私有成员。对外部类私有变量的访问则通过外部类的access$000()方法。
使用场景:
静态内部类与非静态内部类之间存在一个最大的区别:
非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。
它的创建是不需要依赖于外围类,
它不能使用任何外围类的非static成员变量和方法
2,成员内部类
定义:
定义在类内部的非静态类称为成员内部类
特点:
- 成员内部类可以访问外部类所有的变量和方法,包括静态和实例,私有和非私有。
- 和静态内部类不同的是,每一个成员内部类的实例都依赖一个外部类的实例(成员内部类是依附外部类而存在的)。其它类使用内部类必须要先创建一个外部类的实例。
- 内部类中的 this 指的是内部类的实例对象本身,如果要用外部类的实例对象就可以用类名 .this 的方式获得
- 成员内部类不能定义静态方法和变量(final修饰的除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。
3,匿名内部类
定义:
内部类的定义和声明写到一起时,就不用给这个类起个类名而是直接使用了,这种形式的内部类根本就没有类名,因此我们叫它匿名内部类
特点:
- 匿名内部类可以访问外部类所有的变量和方法。
- 匿名内部类不能有构造器(构造方法)
- 匿名内部类不可以是抽象类,抽象类不能创建实例
- 匿名内部类常用于回调函数,比如我们常用的绑定监听的时候。
- 一个类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的事先或是覆盖。
- 只是为了获得一个对象实例,不需要知道其实际类型。
- 类名没有意义,也就是不需要使用到
示例:
1,实现接口:
interfance Product
{
public double getPrice();
public String getName();
}
public class Anonymous
{
public void test (Product p){
System.out.println(p.getName()+"--------"+p.getPrice());
}
public static void main(String [ ] args ) {
Anonymous as= new Anonymous ();
as.test(new Product( )//此处实现接口并实现抽象方法{
public double getPrice( ){//实现方法
return 8888;
}
public String getName( )//实现方法 {
return "I can do it ";
}
});
}
}
第二种,继承抽象类
public abstract class A {
public void A(){
System.out.println("A");
}
}
public class Test {
public static void main(String[] args) {
//new出接口或者实现类
A a= new A (){
//实现接口里未实现的方法
public void A() {
System.out.println("匿名内部类");
}
};
a.A();
}
4,局部内部类
定义:
定义在外部类方法中的类,叫局部类
示例:
public class Out {
private static int a;
private int b;
public void test(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
}
public static void testStatic(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(a);
//定义在静态方法中的局部类不可以访问外部类的实例变量
//System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
}
}
特点:
局部类只能在定义该局部类的方法中使用。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。同时局部类还可以访问方法的参数和方法中的局部变量,这些参数和变量必须要声明为final的。否则会报错
Cannot refer to a non-final variable x inside an inner class defined in a different method
为什么局部类访问的变量需要final修饰
- 1,外部类方法结束时候,局部变量就会销毁,但内部类对象可能还存在,并指向一个不存在的局部变量
- 2,局部变量复制为内部类的成员变量时,必须保证两个变量一致。在内部类修改成员变量,方法中局部变量也会跟着改变。
- 将局部变量设置为final,这样编译器会将final局部变量"复制"作为局部内部类中的数据成员(且此时为常量)
使内部类无法去修改这个变量。保证复制的数据成员与原始变量一致。 - 应用场景说明:
局部内部类是嵌套在方法和作用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,但又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效
二,抽象类(abstract)
1,定义:
如果一个类没有包含足够多的信息来描述一个具体的对象,这样的类就是抽象类。抽象类用abstract修饰。
2,特点:
- 不能实例化,因为抽象类中含有无法具体实现的方法。
- 可在抽象类中定义公共成员变量、成员方法、构造方法等。
- 只要包含一个抽象方法的类,该类必须要定义成抽象类
- 如果子类继承于一个抽象类,则该子类可以有选择性决定是否覆写父类的抽象方法,如果子类没有实现父类的抽象方法,则必须将子类也定义为抽象类(抽象类可以继承抽象类)
3,目的
抽象类本质上是为了继承而存在,为子类提供一个公共特性的通用模板,是子类的抽象。
3,示例
abstract class A{//定义一个抽象类
public void fun(){//普通方法
System.out.println("存在方法体的方法");
}
public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰
}
//单继承
class B extends A{//B类是抽象类的子类,是一个普通类
@Override
public void print() {//强制要求覆写
System.out.println("Hello World !");
}
}
三,接口
1,定义
接口在java中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而继承接口的抽象方法
2,特点
(1)从定义上看,接口是个集合,并不是类。类描述了属性和方法,而接口只包含方法(未实现的方法),是抽象方法的集合。接口和抽象类一样不能被实例化,因为不是类。但是接口可以被实现(使用 implements 关键字)。实现某个接口的类必须在类中实现该接口的全部方法。虽然接口内的方法都是抽象的(和抽象方法很像,没有实现)但是不需要abstract关键字。
(2)接口没有构造方法(接口不是类)
(3)接口中的方法必须是抽象的(不能实现)
(4)接口中除了static、final变量,不能有其他变量
(5)接口支持多继承(一个类可以实现多个接口)
3,目的
提供一组抽象方法的集合,供子类实现
4,示例
interface Door{
void open ();
void close();
}
Public class BigDoor implements Door {
void open (){
System.out.println("BigDoor is opening...");
};
void close(){
System.out.println("BigDoor is closing...");
};
}
四,面试题
1,为什么使用内部类(文中有详解)
2,为什么局部类访问的变量需要final修饰(文中有详解)
3,抽象类和接口的不同
1、结构
抽象类中可以有自己的方法实现。也可以有抽象方法。接口只有抽象方法。
抽象类中有自己的成员变量,成员方法。接口只有常量和抽象方法。
抽象类可以用public,protected,private等修饰。接口只能用public修饰。
2、继承方式
子类使用extends关键字继承抽象类。子类可以选择性重写抽象类中需要使用的方法,如果子类没有实现抽象类中所有声明的方法的实现,则子类也是抽象类。
子类使用implements关键字实现接口。子类需要提供接口中所有声明的方法的实现。
3、构造方法
抽象类可以有构造方法,但接口没有构造方法。但抽象类的构造器不用于创造对象,而是让其子类调用这些构造器完成抽象类的初始化操作。
4、多/单继承
一个子类只能继承一个父类,但可以实现多个接口。
5、速度
抽象方法比接口速度快。接口需要时间去寻找在类中实现的方法,故速度较慢。
6、设计
抽象类是对事物的一种抽象,描述的是某一类特性的事物。表示 这个对象是什么。(is-a关系——强调所属关系)
接口是对行为功能的抽象,描述是否具备某种行为特征。表示 这个对象能做什么。(has-a关系——强调功能实现)
4,静态内部类设计意图,以及与非静态内部类的不同
- 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。所以它的创建是不需要依赖于外围类,
- 它不能使用任何外围类的非static成员变量和方法
5,静态属性,静态方法能否被继承和重写
对于非静态的属性和方法
- 对于非静态属性,子类可以继承父类的非静态属性。但是当子类和父类有相同的非静态属性时,并没有重写并覆盖父类的非静态属性,只是隐藏了父类的非静态属性。
- 对于非静态的方法,子类可以继承父类的非静态方法并可以重写覆盖父类的非静态属性方法。
对于静态的属性和方法 - 对于静态的属性,子类可以继承父类的静态属性。但是和非静态的属性一样,会被隐藏。
- 对于静态的方法,子类可以继承父类的静态方法。但是子类不可重写覆盖父类的静态方法,子类的同名静态方法会隐藏父类的静态方法。
原因解析:
- 静态方法和属性是属于类的,调用的时候直接通过类名.方法名即可,不需要继承机制也可以调用。如果子类里面定义了同样的静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"(hide)。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成。至于是否继承一说,子类是有“继承”静态方法和静态属性,但是跟实例方法和属性(实例变量)的继承是不一样的意思(这里要切记),静态的"继承",通过子类能调用相关的静态变量与静态方法。
- 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是静态变量与静态方法的“隐藏”是没有这个优先级之分的。
- 静态属性(静态变量)、静态方法无继承,但有隐藏,静态方法又不能被重写,因此不能实现多态。非静态方法(实例发方法)可以被继承和重写,因此可以实现多态。