面向对象概述

面向对象有三大特性:

  • 封装。只对外提供接口由外界访问。
  • 继承。
  • 多态。多态的三个条件:继承,重写,以及向上转型(父类引用指向子类对象)。

访问权限修饰符

private:除了自己没有其他任何类可以访问。
protected:同一包下的,以及自己的自类可以访问。
default:同一包下面的。
public:任何类都可以访问。

重写与重载

重写:override,重新实现父类的方法。发生在运行期,动态分派。
重载:方法名称相同,参数不同。返回值类型不同,参数类型相同不算是重载。发生在编译器,静态分派。

抽象类和接口

抽象类

使用abstract关键字声明,且抽象类至少应该包含一个抽象方法(只有声明没有实现)。

抽象类不可以被实例化

抽象方法不能是private,这样方法就不能继承了。抽象类不能是final的。

接口

在JDK8之前,没有任何方法的实现。从JDK8之后,接口也可以有默认方法的实现

接口的成员都只能是public的。

接口的字段是public static final的,因此,接口字段一旦定义就不可以改了。

但是,在JDK1.8之后,interface加了一些新的特性。

  • 可以添加静态方法。
1
2
3
4
5
6
7
public interface TestInterface {

public static String sayHello() {
return "Hello world!";
}

}
  • 可以为接口方法提供一个默认实现。使用default标记。

    1
    2
    3
    4
    5
    6
    7
    8
    interface MyInterface{
    String myNewName(String newName);

    default String myOldName(){
    return "chao";
    }

    }

区别

  • 类只可以继承一个抽象类,却可以实现多个接口。
  • 接口字段是static final的,不可以再去修改的,而抽象类却没有这种要求。
  • 接口成员是public的,抽象类却没有这种要求。

super

关于构造方法

当new一个子类的时候,一定会先调用父类的构造方法,然后再去调用子类的构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Parent {
public Parent(){
System.out.println("parent");
}
}

public class Son extends Parent {
public Son(){
System.out.println("son");
}
}

Son son = new Son();
/*
输出:parent
son
也就是说。当构造一个子类的时候,一定会先调用父类的构造方法。默认是调用父类无参的构造函数,若是想调用其它构造函数,可以通过super关键字实现。
*/

既然创建子类对象的时候,一定会先调用父类的构造方法,那么是否创建了父类呢?

答案是没有。

title

只是创建了一个子类对象,this完全引用这个子类对象,super引用子类可以继承的成员变量以及方法

super作用

  • 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super 函数。

  • 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。

内部类

在一个类中再定义一个类。

内部类的好处

  • 内部类可以访问外部类的所有成员,包括private。

    为什么内部类可以随意访问外部类的成员?

    持有引用。当外部类的对象创建了一个内部类的对象时,内部类对象必定会秘密捕获一个指向外部类对象的引用,然后访问外部类的成员时,就是用那个引用来选择外围类的成员的。当然这些编辑器已经帮我们处理了。

  • 内部类可以对外隐藏。

  • 可以实现多重继承。

    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
    37
    38
    39
    40
    //类一
    public class ClassA {
    public String name(){
    return "liutao";
    }
    public String doSomeThing(){
    // doSomeThing
    }
    }
    //类二
    public class ClassB {
    public int age(){
    return 25;
    }
    }

    //类三
    public class MainExample{
    private class Test1 extends ClassA{
    public String name(){
    return super.name();
    }
    }
    private class Test2 extends ClassB{
    public int age(){
    return super.age();
    }
    }
    public String name(){
    return new Test1().name();
    }
    public int age(){
    return new Test2().age();
    }
    public static void main(String args[]){
    MainExample mi=new MainExample();
    System.out.println("姓名:"+mi.name());
    System.out.println("年龄:"+mi.age());
    }
    }

内部类与外部类的关系

  • 对于非静态内部类,内部类的创建依赖外部类的实例对象,在没有外部类实例之前是无法创建内部类的。先有外部类对象,再有内部类对象。
  • 对于静态内部类,内部类并不依赖于外部类对象的创建,static依赖于类本身,并不依赖类实例对象。
  • 普通内部类不可以有静态成员,因为普通内部类需要依赖于外部对象而存在,需要outer.new InnerClass();,他是与对象相关的。
  • 静态可以访问静态的,不可以访问非静态的;非静态静态和非静态都可以访问。所以,静态内部类不可以访问外部类非静态成员。

内部类创建

1
2
ClassOuter outer = new ClassOuter();
ClassOuter.InnerClass inner = outer.new InnerClass();

普通内部类

1
2
3
4
5
6
public class InnerClassTest {

public class InnerClassA {

}
}

内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段。

静态内部类

静态内部类就像外部类的一个静态成员一样,创建其对象无需依赖外部类对象(访问一个类的静态成员也无需依赖这个类的对象,因为它是独立于所有类的对象的)。但是于此同时,静态内部类中也无法访问外部类的非静态成员,因为外部类的非静态成员是属于每一个外部类对象的,而本身静态内部类就是独立外部类对象存在的,所以静态内部类不能访问外部类的非静态成员,而外部类依然可以访问静态内部类对象的所有访问权限的成员,这一点和普通内部类无异。

匿名内部类

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
37
38
39
40
41
42
43
44
45
46
47
48
public class InnerClassTest {

public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;

public InnerClassTest() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
}
// 自定义接口
interface OnClickListener {
void onClick(Object obj);
}

private void anonymousClassTest() {
// 在这个过程中会新建一个匿名内部类对象,
// 这个匿名内部类实现了 OnClickListener 接口并重写 onClick 方法
OnClickListener clickListener = new OnClickListener() {
// 可以在内部类中定义属性,但是只能在当前内部类中使用,
// 无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名,
// 也就无法创建匿名内部类的对象
int field = 1;

@Override
public void onClick(Object obj) {
System.out.println("对象 " + obj + " 被点击");
System.out.println("其外部类的 field1 字段的值为: " + field1);
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
};
// new Object() 过程会新建一个匿名内部类,继承于 Object 类,
// 并重写了 toString() 方法
clickListener.onClick(new Object() {
@Override
public String toString() {
return "obj1";
}
});
}

public static void main(String[] args) {
InnerClassTest outObj = new InnerClassTest();
outObj.anonymousClassTest();
}
}

匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,因为是匿名内部类,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。

当匿名内部类访问局部变量的时候,局部变量必须是final的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Button {
public void click(final int params){
//匿名内部类,实现的是ActionListener接口
new ActionListener(){
public void onAction(){
System.out.println("click action..." + params);
}
}.onAction();
}
//匿名内部类必须继承或实现一个已有的接口
public interface ActionListener{
public void onAction();
}

public static void main(String[] args) {
Button button=new Button();
button.click();
}
}

原因是:因为局部变量和匿名内部类的生命周期不同。

匿名内部类是创建后是存储在堆中的,而方法中的局部变量是存储在Java栈中,当方法执行完毕后,就进行退栈,同时局部变量也会消失。那么此时匿名内部类还有可能在堆中存储着,那么匿名内部类要到哪里去找这个局部变量呢?

为了解决这个问题编译器为自动地帮我们在匿名内部类中创建了一个局部变量的备份,也就是说即使方法执结束,匿名内部类中还有一个备份,自然就不怕找不到了。

但是问题又来了。如果局部变量中的a不停的在变化。那么岂不是也要让备份的a变量无时无刻的变化。为了保持局部变量与匿名内部类中备份域保持一致。编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。所以为什么匿名内部类应用外部方法的域必须是常量域的原因所在了。

特别注意:在Java8中已经去掉要对final的修饰限制,但其实只要在匿名内部类使用了,该变量还是会自动变为final类型(只能使用,不能赋值)。

内部类导致内存泄漏

  • 如果一个匿名内部类没有被任何引用持有,那么匿名内部类对象用完就有机会被回收。

  • 如果内部类仅仅只是在外部类中被引用,当外部类的不再被引用时,外部类和内部类就可以都被GC回收。

  • 如果当内部类的引用被外部类以外的其他类引用时,就会造成内部类和外部类无法被GC回收的情况,即使外部类没有被引用,因为内部类持有指向外部类的引用)。

所以,内部类内存泄露的原因就是有外部类以外的其他引用,这样内部类和外部类都无法被回收

可以通过使用静态内部类来解决。

分析内部类