面向对象编程(高级部分)
★抽象类
由于父类方法可能存在不确定性的问题(即只知道需要这个方法,但是没有具体的方法体),所以就要考虑将该方法设计为抽象(abstract)方法,所谓抽象方法就是没有实现的方法,即该方法没有方法体。
当一个类中存在抽象方法时,需要将该类声明为抽象(abstract)类。
一般来说,抽象类会被继承,由子类来实现具体的抽象方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.f.Chapter10.abstract_;
public class Abstract {
public static void main(String[] args) {
}
}
//由于该类中存在抽象方法 eat,所以该类需要为抽象类
abstract class Animal {
public String name;
//不同的动物吃的东西不一样,所以这里仅将 eat 方法定义为抽象方法
public abstract void eat();
}当父类的一些方法不能确定时,可以用 abstract 关键字来修饰该方法,这个方法就是抽象方法,用 abstract 来修饰该类就是抽象类。
用 abstract 关键字来修饰一个类时,这个类就叫抽象类。
1
2访问修饰符 abstract 类名{
}用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法。
访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类。
★抽象类使用细节
抽象类不能被实例化。
抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.f.Chapter10.abstract_;
public class AbstractDetail {
public static void main(String[] args) {
}
}
abstract class Animals {
private String name;
public void eat() {
}
}一旦一个类包含了 abstract 方法,那么这个类必须声明为 abstract 类。
abstract 只能修饰类和方法,不能修饰属性和其他的。
抽象类可以有任意成员(因为抽象类还是类),比如:非抽象方法、构造器、静态属性等等。
抽象方法不能有主体,即不能实现。
★如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract类。
考虑这么一种情况:抽象类 Animals 有一个抽象方法 eat,子类 Dogs 没有实现这个抽象方法,所以子类 Dogs 也为抽象类,那么 Dogs 的子类 Corgi 若要不为抽象类,则 Corgi 是否需要实现这个抽象方法呢?-> 答案是需要的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.f.Chapter10.abstract_;
public class AbstractDetail {
public static void main(String[] args) {
}
}
abstract class Animals {
private String name;
public abstract void eat();
}
abstract class Dogs extends Animals {
}
class Corgi extends Dogs{
public void eat() {}
}★抽象方法不能使用 private、final 和 static 来修饰,因为这些关键字都是和重写相违背的,即 “用 private、final 和 static 修饰的方法是不能被重写的”。
模板设计模式
考虑下面的场景:我们有多个类,各自完成不同的任务 job,要求计算不同任务的耗时。
为了方便起见,假设只有两个类 AA 和 BB,则它们的任务 job 和耗时可以用下面的代码来实现。
1
2
3
4
5
6
7
8
9
10package com.f.Chapter10.abstract_;
public class TemplateDesignMode {
public static void main(String[] args) {
AA aa = new AA();
aa.job();
BB bb = new BB();
bb.job();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.f.Chapter10.abstract_;
public class AA {
public void job() {
long start = System.currentTimeMillis();
long sum = 0;
for (int i = 0; i < 800000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("任务耗时" + (end - start) + "ms");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.f.Chapter10.abstract_;
public class BB {
public void job() {
long start = System.currentTimeMillis();
long sum = 0;
for (int i = 0; i < 80000; i++) {
sum *= i;
}
long end = System.currentTimeMillis();
System.out.println("任务耗时" + (end - start) + "ms");
}
}但是若类似的类有好多个,则它们的代码都有相同的重叠部分,造成代码的重复和冗余,为了提高代码的复用性,可以考虑把代码相同的部分抽取出来,做成一个公共的父类模板 Template,这些类都继承这个父类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.f.Chapter10.abstract_;
public abstract class Template {
public void calculateTime() {
long start = System.currentTimeMillis();
long sum = 0;
job(); //涉及到动态绑定:当调用对象方法的时候,该方法会和该对象内存地址/运行类型绑定;
long end = System.currentTimeMillis();
System.out.println("任务耗时" + (end - start) + "ms");
}
//这里,为了让 job 各不相同,应为抽象方法。实际操作发现不为抽象方法也无伤大雅
public abstract void job();
}这里的抽象类 job 由继承的子类来重写。
1
2
3
4
5
6
7
8
9
10
11
12package com.f.Chapter10.abstract_;
public class AA extends Template{
public void job() {
long sum = 0;
for (int i = 0; i < 800000; i++) {
sum += i;
}
}
}1
2
3
4
5
6
7
8
9
10
11
12package com.f.Chapter10.abstract_;
public class BB extends Template{
public void job() {
long sum = 0;
for (int i = 0; i < 80000; i++) {
sum *= i;
}
}
}1
2
3
4
5
6
7
8
9
10package com.f.Chapter10.abstract_;
public class TemplateDesignMode {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}由此,将相同的代码部分提取出来,做成公共的模板,提高了代码的复用性,称为模板设计模式。
★接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
接口的定义语法:
1
2
3
4
5
6
7
8
9
10interface 接口名{
//属性;
//方法(1.抽象方法;2.默认实现方法;3.静态方法);
}
class 类名 implements 接口{
//自己的属性;
//自己的方法;
//必须实现的接口的抽象方法;
}注意:
在 jdk 7.0 之前,接口里的所有方法都没有方法体;
在 jdk 8.0 之后,接口里可以有静态方法,默认方法,也就是说接口中可以有方法的具体体现;
注意:接口的默认方法要通过 “实现了接口的类的对象” 来调用,且默认方法可以被重写。
默认方法可以被重写,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
在接口中,抽象方法,可以省略 abstract。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20interface MyInterface {
//默认方法,jdk 8.0 之后
default public void myMethod(){
System.out.println("MyInterface...");
}
//静态方法,jdk 8.0 之后
public static void hi(){
System.out.println("hi...");
}
//在接口中,省略了 abstract 修饰的抽象方法
public void hello();
}
class A implements MyInterface {
//引用接口的类需要实现接口的抽象方法
public void hello() {
System.out.println("hello...");
}
}
★接口使用细节
接口不能被实例化;
接口中所有的方法都是 public 方法,接口中的抽象方法可以不用 abstract 修饰;
一个普通类实现(implements)接口,就必须将该接口的所有方法都实现,如果方法很多,则可以使用
alt+enter
来快速实现;抽象类实现接口时,可以不用实现接口的方法;
★一个类可以同时实现多个接口;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16interface IA{
void iaHi(); //修饰符public和abstract可以不写
}
interface IB{
void ibHello();
}
class AA implements IA,IB {
public void iaHi() {
}
public void ibHello() {
}
}接口中的属性,只能是
final
的,而且是public static final
修饰符,例如,int a = 1;
,实际上是public static final int a = 1;
(必须初始化);1
2
3interface IA{
int a = 1; //等价于 public static final int a = 1;
}**接口中属性的访问形式为:
接口名.属性名
**;1
2
3
4
5
6
7
8
9
10
11interface IA{
int a = 1;
void iaHi();
}
class AA implements IA {
private int aa = IA.a; //接口名.属性名
public void iaHi() {
}
}★接口不能继承其他的类,但是可以继承多个别的接口,如
interface IA extends IB,IC{}
;接口的修饰符只能是
public
和默认,这点和类的修饰符是一样的。对于接口中的属性,当类实现接口时,以下几种访问形式都是可以的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class InterfaceTest {
public static void main(String[] args) {
AA aa = new AA();
System.out.println(IA.a); //接口名.属性名
System.out.println(aa.a); //对象名.属性名
System.out.println(AA.a); //类名.属性名
}
}
interface IA{
int a = 1;
}
class AA implements IA {
}
★接口vs继承
当子类继承了父类时,就自动的拥有了父类的”功能“(属性和方法),如果子类需要扩展功能,就可以通过实现接口的方法来扩展。
实现接口是对 java 单继承机制的一种补充。
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
38public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey littleMonkey = new LittleMonkey("小猕");
littleMonkey.climbing(); //猴子小猕会爬树...
littleMonkey.swimming(); //小猕通过学习学会了游泳...
}
}
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void climbing() {
System.out.println("猴子" + this.name + "会爬树...");
}
}
interface Fishable {
void swimming();
}
class LittleMonkey extends Monkey implements Fishable {
public LittleMonkey(String name) {
super(name);
}
public void swimming() {
System.out.println(getName() + "通过学习学会了游泳...");
}
}接口和继承解决的问题不同:
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法;
继承的价值主要在于:解决代码的复用性和可维护性问题。
接口比继承更加灵活,继承是满足 is-a 的关系,而接口只需满足 like-a 的关系。
接口在一定程度上实现了代码的解耦(接口规范性 + 动态绑定机制)。
★接口多态特性
多态参数。在下面的代码中,对于
work
函数,形参是接口类型UsbInterface
,而实参第一次传入的是phone
,第二次传入的是camera
,即为接口的多态参数特性,即可以接收 “实现了UsbInterface
接口的类的对象实例“。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
44package com.f.Chapter10.interface_;
public class Computer {
public static void work(UsbInterface usbInterface){
usbInterface.start();
usbInterface.stop();
}
public static void main(String[] args) {
Phone phone = new Phone();
Camera camera = new Camera();
work(phone);
work(camera);
}
}
interface UsbInterface{
void start();
void stop();
}
class Phone implements UsbInterface{
public void start() {
System.out.println("手机接入usb接口...");
}
public void stop() {
System.out.println("手机拔出usb接口...");
}
}
class Camera implements UsbInterface{
public void start() {
System.out.println("相机接入usb接口...");
}
public void stop() {
System.out.println("相机拔出usb接口...");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.f.Chapter10.interface_;
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口的多态体现
//接口类型的变量 ia 可以指向 实现了IA接口的类 的对象实例
IA ia = new Bicycle();
ia = new Car();
//继承的多态体现
//父类的变量 base 可以指向继承了父类的子类 Sub 的对象实例
Base base = new Sub();
}
}
interface IA { }
class Bicycle implements IA { }
class Car implements IA { }
class Base { }
class Sub extends Base { }多态传递。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.f.Chapter10.interface_;
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向 实现了该接口的类 的对象实例
IB ib = new Teacher();
//如果接口 IB 继承了 IA,那么 IA 的变量也可以指向 实现了接口IB的类 的对象实例
//亦即,实际上就相当于 Teacher 类也要实现 IA 接口
IA ia = new Teacher();
}
}
interface IA {}
interface IB extends IA {}
class Teacher implements IB {}
★内部类
一个类的内部又完整地嵌套了另一个类结构,被嵌套的类称为内部类(inter class),嵌套其他类的类称为外部类(outer class)。
内部类的基本语法:
1
2
3
4
5
6
7
8
9class Outer{ //外部类
class Inner{ //内部类
}
}
class Other{ //外部其他类
}内部类的最大特点就是可以直接访问外部类的私有属性,并且可以体现类与类之间的包含关系。
内部类可以分为四种:
- 定义在外部类的局部位置上(比如方法内):
- 局部内部类(有类名);
- 匿名内部类(没有类名);
- 定义在外部类的成员位置上:
- 成员内部类(没用 static 修饰);
- 静态内部类(用 static 修饰)。
- 定义在外部类的局部位置上(比如方法内):
局部内部类
局部内部类是定义在外部类的局部位置上,比如方法中或代码块中,并且有类名。
- 可以直接访问外部类的所有成员,包含私有的;
- 局部内部类不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以使用
final
修饰,因为局部变量也可以使用final
; - 作用域:仅仅在定义它的方法或代码块中;
- 局部内部类 —-访问—-> 外部类的成员,访问方式是直接访问;
- 外部类 —-访问—-> 局部内部类的成员,访问方式是先创建对象,再访问(注意:必须在作用域内);
- 外部其他类不能访问局部内部类,因为局部内部类的地位就是一个局部变量;
- 如果外部类和局部内部类的成员重名,那么局部内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.this.成员
)去访问。
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
32package com.f.Chapter10.innerclass;
public class LocalInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.foo();
System.out.println("outer的地址为:" + outer);
}
}
class Outer {
private int n1 = 100;
public void foo() {
//局部内部类定义在外部类的局部位置上,比如方法中
final class Inner {
private int n1 = 200;
public void f1() {
//局部内部类可以直接访问外部类的所有成员,包含私有的
//Outer.this 本质就是外部类的对象,即哪个Outer类的对象调用了foo方法,那么这个Outer.this就是哪个对象
System.out.println("类Outer的私有成员n1的值为:" + Outer.this.n1);
System.out.println("Outer.this的地址为:" + Outer.this);
System.out.println("类Inner的私有成员n1的值为:" + n1);
System.out.println("类Inner的私有成员n1的值为:" + this.n1);
}
}
//外部类访问局部内部类的成员的方式是先创建对象,再访问(注意:必须在作用域内)
Inner inner = new Inner();
inner.f1();
}
}
★★★匿名内部类
匿名内部类是定义在外部类的局部位置上,比如方法中或代码块中,并且没有类名。
本质是类;
是内部类;
该类没有名字(是系统分配名字);
同时还是一个对象。
语法:
1
2
3new 类/接口(参数列表){
//......
};
基于接口的匿名内部类:
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
46package com.f.Chapter10.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer {
private int n1 = 100;
public void method() {
//基于接口的匿名内部类
//1. 需求:想使用 IA 接口,并创建对象
//2. 传统方式是写一个类,实现该接口,再创建这个类的对象
//3. 但需求是 Tiger 类只使用一次,后面不再使用
//4. 为了简化开发,可以使用匿名内部类
//5. tiger 的编译类型? -> IA
//6. tiger 的运行类型? -> Outer$1
/*
我们看底层,会发现系统分配有类名 Outer$1
class Outer$1 implements IA {
@Override
public void cry(){
System.out.println("老虎叫唤...");
}
}
*/
//7. jdk底层在创建匿名内部类 Outer$1 后,就立马创建了 Outer$1 实例,并且把地址返回给了 tiger 变量
//8. 匿名内部类只使用一次,就不再使用(注意这是对匿名内部类来说的,而不是对创建好的实例来说的)
IA tiger = new IA() {
public void cry() {
System.out.println("老虎叫唤...");
}
};
tiger.cry();
tiger.cry();
System.out.println("tiger的运行类型是:" + tiger.getClass());
}
}
interface IA {
void cry();
}基于类的匿名内部类:
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
49
50
51
52package com.f.Chapter10.innerclass;
public class AnonymousInnerClass_ {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer {
private int n1 = 100;
public void method() {
//基于类的匿名内部类
//1. father01 的编译类型? -> Father
// father02 的编译类型? -> Father
//2. father01 的运行类型? -> Outer$1
// father02 的运行类型? -> Father
/*
我们看底层,会发现系统分配有类名 Outer$1
class Outer$1 extends Father {
@Override
public void test() {
System.out.println("匿名内部类重写Father类的test方法...");
}
}
*/
//3. jdk底层在创建匿名内部类 Outer$1 后,就立马创建了 Outer$1 实例,并且把地址返回给了 father01 变量
//4. 匿名内部类只使用一次,就不再使用(注意这是对匿名内部类来说的,而不是对创建好的实例来说的)
//5. 注意:("jack") 这个参数列表会传递给 Father 类的构造器
Father father01 = new Father("jack") {
public void test() {
System.out.println("匿名内部类重写Father类的test方法...");
}
};
System.out.println("father01的运行类型是:" + father01.getClass());
//Father father02 = new Father("tom");
//System.out.println("father02的运行类型是:" + father02.getClass());
father01.test();
}
}
class Father {
public Father(String name) {
System.out.println("Father类的构造器,接收到参数name=" + name);
}
public void test() {
System.out.println("Father类的test方法...");
}
}
★匿名内部类使用细节
- 匿名内部类的语法比较奇特,请注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征。
- 匿名内部类可以访问外部类的所有成员,包括私有的;
- 不能添加访问修饰符,因为它的地位就是一个局部变量;
- 作用域:仅仅在定义它的方法或代码块中;
- 匿名内部类 —-访问—-> 外部类的成员,访问方式是直接访问;
- 外部其他类不能访问匿名内部类;
- 如果外部类和匿名内部类的成员重名,那么匿名内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.this.成员
)去访问; - 匿名内部类涉及到:继承、多态、动态绑定、内部类。
★★★匿名内部类实践
当作实参直接传递,代码简洁高效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.f.Chapter10.innerclass;
public class AnonymousInnerClassExercise {
public static void main(String[] args) {
//匿名当作实参直接传递,代码简洁高效。
f1(new IA(){
public void show() {
System.out.println("匿名内部类的 show 方法...");
}
});
}
//静态方法,形参是接口类型 IA
public static void f1(IA ia) {
ia.show();
}
}
interface IA {
void show();
}如果不使用匿名内部类,则要先创建一个实现了
IA
接口的类,然后在main
函数中创建该类的对象,再把该对象作为参数传入f1
,如果只使用一次f1
,代码就会显得很繁琐。
成员内部类
成员内部类是定义在外部类的成员位置,并且没有
static
修饰。可以直接访问外部类的所有成员,包括私有的;
可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员;
注意:对于一个外部类,其访问修饰符只能是默认或者 public;
作用域:和外部类的其他成员一样,为整个外部类的类体;
成员内部类 —-访问—-> 外部类的成员,访问方式是直接访问;
外部类 —-访问—-> 成员内部类的成员,访问方式是先创建对象,再访问(注意:必须在外部类的类体内);
“ 在成员内部类中,say 方法的修饰符是 private,所以在外部其他类中,无法调用该方法,但是在外部类中是可以调用该方法的,如 f1 函数所示 ”。
外部其他类创建成员内部类的实例对象的方式有以下两种:
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
43package com.f.Chapter10.innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.f1();
//外部其他类访问成员内部类的成员的两种方式:
//第一种方式:outer.new Inner():相当于把new Inner()当作是outer成员
Outer.Inner inner01 = outer.new Inner();
Outer.Inner inner02 = new Outer().new Inner();
//第二种方式:在外部类中,编写一个方法,返回Inner对象实例
Outer.Inner inner03 = outer.getInnerInstance();
//注意:因为在成员内部类中,say 方法的修饰符是 private,所以在外部其他类中,无法调用该方法,
// 但是在外部类中是可以调用该方法的,如 f1 函数所示
}
}
class Outer { //外部类
private int n1 = 10;
public String name = "张三";
class Inner { //成员内部类
private void say() {
//可以直接访问外部类的所有成员,包括私有的
System.out.println("外部类Outer的n1 = " + Outer.this.n1);
System.out.println("外部类Outer的name = " + Outer.this.name);
}
}
public void f1(){
//使用成员内部类:创建成员内部类的对象,然后使用
Inner inner = new Inner();
inner.say();
}
//该方法返回成员内部类的实例对象
public Inner getInnerInstance(){
return new Inner();
}
}如果外部类和成员内部类的成员重名,那么成员内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.this.成员
)去访问。
静态内部类
静态内部类是定义在外部类的成员位置,并且用
static
修饰。可以直接访问外部类的所有静态成员,包括私有的,但不能直接访问非静态成员;
可以添加任意访问修饰符(public、protected、默认、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
44package com.f.Chapter10.innerclass;
public class StaticInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.hi();
//外部其他类访问静态内部类的成员的两种方式:
//第一种方式:new Outer.Inner():因为是静态内部类,所以可以直接用 Outer.Inner
Outer.Inner inner01 = new Outer.Inner();
inner01.say();
//第二种方式:在外部类中,编写一个方法,返回Inner对象实例
Outer.Inner inner02 = new Outer().getInnerInstance();
inner02.say();
}
}
class Outer {
private int n1 = 10;
private static String name = "张三";
public static class Inner {
private String name = "李四";
public void say() {
System.out.println("静态内部类的非静态变量name的值为:" + name);
System.out.println("外部类的静态变量name的值为:" + Outer.name);
System.out.println("=====================================");
//不能直接访问外部类的非静态成员
//System.out.println(n1); //无法访问
}
}
public void hi() {
System.out.print("我是类Outer的hi方法...\t");
Inner inner = new Inner();
inner.say();
}
public Inner getInnerInstance() {
return new Inner();
}
}如果外部类和静态内部类的成员重名,那么静态内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.成员
)去访问。注意:上面所说的成员,在外部类中一定是静态成员,但在静态内部类中,可以是静态的,也可以是非静态的。