0%

Java-接口和内部类

接口 — Interface

定义:
接口是对类一组需求的描述,实现接口的类要遵从接口的描述的定义。
接口中的所有方法自动地属于public,所有声明方法不必提供关键字
不过在实现接口的时候,必须将方法声明为public,否则编译器会给出试图提供更弱的访问权限信息

通常类实现一个接口,通常需要2个步骤
1.让类声明为实现给定的接口
2.对接口中的方法进行定义

java十万个为什么

为什么接口内无法定义静态方法?

内部类 — inner class

内部类(inner class)是定义在另一个类中的类,其实也可以区分为外部类和内部类,这2者的关系,
我个人的理解就好像是火车车厢火车车厢内的旅客关系,火车先组装好,再把旅客放上去,旅客在车厢内,肯定是很自然拿到车厢的信息。
举个例子:自然拿到车厢的班次,车厢内座位。

内部类的方法可以访问该类定义所在的作用域中的数据,包括私有数据。  
内部类可以对同一个包中的其他类隐藏起来。  
当想定义一个回调函数并且不想编写大量的代码的时候,可以使用匿名类内部类对比。

课外补习

嵌套是一种类与类之间的关系,而不是对象之间的关系。
嵌套类有2个好处:命名控制访问控制

java 十万个为什么

为什么java内部类可以访问外部对象

java的内部类对象有一个隐式引用,实例化该内部类对象的外部对象,通过这个指针,可以访问到外部类的全部状态。
内部类的对象总有一个隐式的引用,它指向它的外部类对象。

总共有多少种内部类

  • 4种内部类
  • 成员内部类
  • 局部内部类
  • 静态内部类
  • 匿名内部类

    内部类的共性

1.内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号。
内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。
2.内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员的变量。

局部内部类

1.局部类不能用public和private访问说明符进行声明。
2.他的作用域被限定在这声明这个局部类的块中。
3.局部内部类中不可定义静态变量,可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的。

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
public class Outer2 {
private int s = 100;
private int out_i = 1;
public void f(final int k){
final int s = 200;
int i = 1;
final int j = 10;
class Inner{ //定义在方法内部
int s = 300;//可以定义与外部类同名的变量
//static int m = 20;//不可以定义静态变量
Inner(int k){
inner_f(k);
}
int inner_i = 100;
void inner_f(int k){
System.out.println(out_i);//如果内部类没有与外部类同名的变量,在内部类中可以直接访问外部类的实例变量
System.out.println(k);//可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的
// System.out.println(i);
System.out.println(s);//如果内部类中有与外部类同名的变量,直接用变量名访问的是内部类的变量
System.out.println(this.s);//用"this.变量名" 访问的也是内部类变量
System.out.println(Outer2.this.s);//用外部"外部类类名.this.变量名" 访问的是外部类变量
}
}
new Inner(k);
}

public static void main(String[] args) {
//访问局部内部类必须先有外部类对象
Outer2 out = new Outer2();
out.f(3);
}

}

静态内部类

我们在上文提到这个观点:

内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员的变量。

这里解释为什么,内部类是静态,那么它就是类级别的成员,根据类的加载机制(加载顺序),就不在依赖对象而存在。所以静态内部类,不能访问外部类非静态成员。

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
public class Outer3 {

private static int i = 1;
private int j = 10;
public static void outer_f1(){

}
public void outer_f2(){

}

// 静态内部类可以用public,protect,private修饰
static class Inner {
static int inner_i = 100;
int inner_j = 200;

static void inner_f1() {
System.out.println("Outer.i " + i);//静态内部类只能访问外部类的静态成员
}

void inner_f2() {
// System.out.println("Outer.i"+j);//静态内部类不能访问外部类的非静态成员
// outer_f2();//包括非静态变量和非静态方法
}
}

public void outer_f3(){
System.out.println(Inner.inner_i);
Inner.inner_f1();
// 外部类访问内部类的非静态成员:实例化内部类即可
Inner inner = new Inner();
inner.inner_f2();
}

public static void main(String[] args) {
new Outer().outer_f3();
}

}

注意:生成(new)一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:
Outer.Inner in=new Outer.Inner();
而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类。静态内部类不可用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
41
42
43
44
45
46
47
public class StaticInnerClassTest
{
public static void main(String[] args) {

double[] d = new double[20];
for (int i= 0;i<d.length;i++)
d[i] = 100 * Math.random();
ArrayAlg.Pair p = ArrayAlg.minmax(d);
System.out.println("min = " + p.getFirst());
System.out.println("min = " + p.getSecond());
}
}

class ArrayAlg
{
public static class Pair
{
private double first;
private double second;

public Pair(double first, double second) {
this.first = first;
this.second = second;
}

public double getFirst() {
return first;
}

public double getSecond() {
return second;
}
}

public static Pair minmax(double[] values){

double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;

for (double v : values){
if (min > v);
if (max < v);
}
return new Pair(min,max);
}

}

匿名内部类

先提出问题?

匿名内部类的使用、匿名内部类要注意的事项、如何初始化匿名内部类、匿名内部类使用的形参为何要为final

1
2
3
4
new 父类构造器(参数列表)|实现接口()    
{
//匿名内部类的类体部分
}

我们看到使用匿名内部类我们必须继承一个父类或者是实现一个接口,仅能继承一个父类或者是一个接口。
没有class的关键字,这是因为匿名类是直接使用new来生成一个对象的引用。当然是隐式的。

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
public abstract class Bird {  
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public abstract int fly();
}

public class Test {

public void test(Bird bird){
System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
}

public static void main(String[] args) {
Test test = new Test();
test.test(new Bird() {

public int fly() {
return 10000;
}

public String getName() {
return "大雁";
}
});
}
}

Output:
大雁能够飞 10000米

对于这段匿名内部类代码其实是可以拆分为如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
public class WildGoose extends Bird{  
public int fly() {
return 10000;
}

public String getName() {
return "大雁";
}
}

WildGoose wildGoose = new WildGoose();
test.test(wildGoose);

在这里系统会创建一个继承自Bird类的匿名类的对象,该对象转型为对Bird类型的引用。
对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用。对于上面的实例,如果我们需要对test()方法里面内部类进行多次使用,建议重新定义类,而不是使用匿名内部类。

使用匿名内部类时,我们必须是继承一个类或者是实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
匿名类是不能定义构造函数的。
匿名内部类中不能存在任何静态成员变量和静态方法
匿名内部类为局部内部类,所以局部内部类的所有限制对匿名类产生有效
匿名类不能是抽象,因为他必须实现继承的类或者是实现接口的所有抽象方法

使用的形参为何要final?

我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final
当所在的方法的形参需要被内部类里面使用时,该形参必须为final。

案例

1
2
3
4
5
6
7
8
9
public class OuterClass {  
public void display(final String name,String age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}

从上面代码中看好像name参数应该是被内部类直接调用?
其实不然,在java编译之后实际的操作如下:

1
2
3
4
5
6
7
8
9
10
public class OuterClass$InnerClass {  
public InnerClass(String name,String age){
this.InnerClass$name = name;
this.InnerClass$age = age;
}

public void display(){
System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
}
}

所以从上面代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份,自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数。
直到这里还没有解释为什么是final?在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。

简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。
故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。

匿名内部类初始化

我们一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。
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
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());
}
}

类加载顺序:有继承关系的加载顺序

参考文章(CSDN)-类加载的顺序

1.首先加载父类的静态字段或者静态语句块

2.子类的静态字段或静态语句块

3.父类普通变量以及语句块

4.父类构造方法被加载

5.子类变量或者语句块被加载

6.子类构造方法被加载

类加载顺序:关键点

  • 静态代码块(只加载一次)
  • 构造方法(创建一个实例就加载一次)
  • 静态方法,调用的时候才会加载,不调用的时候不会加载
  • 静态语句块和静态变量被初始化的顺序与代码先后顺序有关