1、抽象类与方法
前面讲多态的时候,就讲到了当Animal类 被Cat类所继承、也被Dog类所继承、假设再来Pig类继承、Bird类继承。。。
这些比动物类Animal 具体一点点的类都继承Animal类,Animal类里面有speak方法,然后每一个子类都重写这个方法。
当使用多态时,虽然接收的是Animal 类型的变量,但是new的确实一个个子类。执行的虽然是Animal类里面的speak方法名,但是执行的逻辑确是new出的子类中重写的speak方法逻辑。这个时候我们发现,Animal类里面这个speak的逻辑根本就不需要嘛。我们只是需要这个名字来统一规范,大家都重写它。
当一个类里面的方法直接不定义函数体时,这个方法就是抽象方法。含有抽象方法的类就是抽象类。
抽象类的出现就是我们实际写代码发现的规律。有些时候,子类抽取的公共方法到基类,基类只需要定义方法名即可,具体实现由子类重写就好了。这样自然而然就出现了抽象这个词了。抽象就是没有具体的实现的意思。
从以上可以得出,抽象方法必须要能被继承,不然没有意义!所以抽象方法不能是private、也不能是static 因为static是类的东西,也不能被继承。也不能和final同时修饰方法或者类,final修饰的类不能被继承,方法则不能被重写。
抽象的关键字是 abstract
abstract 只能修饰 类、方法。
public abstract class Animal {
public abstract void speak();
}
-
抽象类 可以没有抽象方法。
-
有抽象方法的类一定要用 abstract 修饰,即有抽象方法的类一定是抽象类。
我们猜测一下 abstract 干了些啥。
1、abstract 修饰方法,这样就告诉编译器 我是抽象的,你不要检查我的函数体。
2、abstract 修饰类,就是告诉编译器,我里面可以定义抽象方法,你不要阻止我定义抽象方法。我内部定义的没有函数体的方法,给我检查一下是否使用了abstract修饰。
3、abstract 修饰类,就是告诉编译器,这个类不可用new出来,即不能实例化对象。本身父类即现在的抽象类就不是用来new的,而是用来接收子类new出的对象的。
既然抽象类不能new对象,那么是否有构造器呢?
必须有啊,子类继承的时候得调用啊。不然怎么拷贝父类的东西。这里也证明了一点,调用构造器与实例化对象是2回事!
讲抽象,不讲多态就是耍流氓。
定义了抽象类,就像是定义了规则,标准。具体的实现是交给了继承它的子类。
继承它的子类又分2种。
1、只重写了部分的抽象方法。
只重写了部分的抽象方法的子类,说明里面还有抽象方法。这还是一个抽象类!不能实例化对象
2、全部重写了抽象方法。
全部重写抽象方法,这才不是抽象类了,才可以new 对象。而且为了体现抽象类的作用,得用多态写法,用抽象类接收。用自己的类接收,将失去抽象类定义的意义!但是是可以用自己的类型接收。抽象是由多态进一步演化而来的。
2、匿名类
前面讲了匿名对象,即new 一个对象,但是这个对象不赋值给 某个类类型的变量,即不取名字。
一般是用来做实参直接传递给方法。详情见匿名对象部分。
匿名类实际上差不多。也是类不取名字,直接用。见如下代码:
// 有一个抽象类 Animal
public abstract class Animal {
// 抽象方法 speak
public abstract void speak();
}
@Test
public void test1(){
// testFunc的参数 传递的是一个匿名类
testFunc(new Animal() {
@Override
public void speak() {
System.out.println("匿名类,使用");
}
});
}
// 方法,参数是 Animal类型
private void testFunc(Animal ani){
ani.speak();
}
上面的代码展示了匿名类的使用。
// 匿名类的定义
new Animal() {
@Override
public void speak() {
System.out.println("匿名类,使用");
}
}
Animal 类是一个抽象类,是不可以直接new 对象的。但是你看匿名类,它的使用形式确实有new Animal(),这是矛盾的吗?
不是的,因为你不要忽略了后面还有大括号。大括号里面有对抽象方法speak的重写。
什么时候会出现重写呢?子类啊,我们只讲过 子类继承父类,可以重新父类里面的方法。
所以这个匿名类实际上是 Animal的一个子类。为什么用new Animal(){...}这种样子呢?因为这是个匿名类,没法用new自己的名字这种方法创建对象。只能用父类的。而且子类new对象时,会隐含的调用super方法,即父类的构造方法。Animal()实际上是Animal的构造方法。
当使用父类的名字进行new对象时,必须后面跟上{},并在里面重写抽象方法。这样才是new的匿名子类。
匿名类个匿名方法一样,只能用一次,因为没有名字,后面就找不到了,会被垃圾回收。
3、模板方法 设计模式
这个设计模式其实我们已经推理过了。就是多态的应用。
抽象类作为父类,里面定义抽象方法。是所有子类需要实现的方法。父类中的方法实际上是定义标准,即所有子类重新的名必须和父类定义的可重写的方法名称一致。
抽象类并没有规定所有的方法都必须是抽象方法,甚至全部不是抽象方法都可以。有部分抽象方法也行。
当我们解决一个问题时,某些步骤需要根据具体情况而定,但是步骤名称是确定的。就可以定义为抽象方法,放子类去重写,完善逻辑。
public abstract class Animal {
// 抽象方法
public abstract void speak();
// 实际问题
public void spendTime(){
long start = System.currentTimeMillis();
speak();
long end = System.currentTimeMillis();
System.out.println("花费了:"+ (end - start)+"这么长的时间!");
}
}
// 一个非抽象子类
public class Dog extends Animal{
public String type = "Dog";
@Override
public void speak(){
System.out.println(this.type);
}
}
// 使用
@Test
public void test1(){
Animal dog = new Dog();
testFunc(dog);
}
private void testFunc(Animal ani){
ani.spendTime();
}
上面代码中,实际问题是 求 speak方法执行了多少时间。这个speak()得根据具体的 子类实现确定。但是整体步骤是确定了。即spendTime方法的逻辑是确定的。
将speak定义为抽象方法,有一个子类Dog继承这个Animal类,并实现这个speak方法。
当我们用多态写法 用父类接收new出来的Dog时,传递给testFunc。Func里面调用Animal里面的spendTime方法可以了,spendTime方法里面调用的speak方法实际上是Dog类里面重写的speak方法。这是以前讲的多态与方法重写。
这样与以前的区别是什么?
以前testFunc是直接调用的父类的抽象方法speak,现在是调用父类里面一个含有抽象方法的方法spendTime。就是在speak方法上增加了一点点逻辑,将抽象的speak方法包装了一下,变成了含有speak方法的spendTime方法。本质还是一样的。
这个模式其实就是多态的应用。
4、接口 interface
为什么需要接口这个东西?
JAVA里面的接口与类是同级别的东西。你有没有发现,抽象类的定义是里面的方法可以是抽象的,也可以是不抽象的,但是含有1个抽象方法的类必须是抽象类。这个抽象类里面的方法没有定义死。即有些方法可以是不抽象的!
接口就是升级一下。
1、所有方法都必须是抽象的!规定我这个类里面所有方法都是抽象的,即我不再提供具有函数体的方法了。这个类定义的方法都是一种标准和规范了。子类都得按照已经定义好的方法名称来重写。
2、接口还做了一个升级,继承接口的类必须全部重写里面的抽象方法。抽象类的子类是没有这个规定的。
3、类还有一个规定,类必须是单继承的。而接口突破了这个限制,可以多继承,或者说为了提供多继承的需求,又不与类冲突。将多继承的功能赋予了接口。
满足了这些条件,抽象类就升级改名叫接口了。
接口的所有方法都必须是抽象的吗?
在JAVA 8之前确实是这样的,jdk8之后接口也允许定义有函数体的方法了。但是这些有函数体的方法又限制,必须是默认方法或者静态方法。在JDK9 之后 接口又增加了可以定义私有方法的特性。
可见,语言是在发展中的,JAVA设计者一开始也不能完全的设计好一门语言。需要在实践中不断的改进。但是改进的前提是不能破坏原有的功能。这就是为什么以前规定 接口中的方法必须都是抽象的,而且必须都能被继承(实现)。而java9之后规定你要定义非抽象方法也可以,必须是默认方法、静态方法、私有方法。
分析一下:默认方法的由来。
public interface Demo1 {
// 首先是默认方法的定义 使用default
public default void method2(){
System.out.println("Hello");
}
}
前面分析到 接口中的方法最开始规定是必须都是抽象的,必须都能被继承,被重写的。而且要全部重写的。如果接口由于业务需求突然增加了1个抽象方法。那么继承(实现)它的很多类都会报错,因为还有一个抽象方法没有实现。
为了解决这个问题,出现了默认方法。这个默认方法就不强制要求需要子类去重写,因为自己有方法体。子类也是可以进行重写的。
Collection 接口里面就有 默认方法。如 stream、parallelStream、spliterator、removeIf
Collection 继承了Iterable接口,并且重写了 spliterator 方法。你没看出,接口也可以重写父接口的方法。前提是一定要定义为默认方法。因为静态方法不能被继承,只剩下默认方法可以有方法体。
静态方法:
静态方法就比较简单了,凡是静态static就代码归属权发生变化。这个方法将属于接口,而不是对象。也不能被继承(实现),只能通过接口名点的方式去调用。与类的静态方法也有点不一样。普通类是可以进行实例化的,所以可以通过类的对象去方法类的静态方法。但是接口不能实例化对象。只能通过接口名访问。
静态方法的出现也是实践中发现,有时候会有一些公共的方法。抽取到接口比较合适
私有方法:
暂时略过。属于JDK9的内容。本教程针对JDK8以及以下。
4.1 接口的属性
接口中的属性只能是全局常量,按照接口的出现来说,比抽象类抽象更彻底,必须被子类继承实现。自己本身不能实例化对象。
不能实例化对象,意味着接口中的属性不能被自己的对象调用。只能被子类的对象调用。所以接口中的属性必须能被继承,不能是private。自己不能实例化对象,接口想要自己用,就必须定义为static。
接口中的方法现在都变成了规范了。所以属性要么没有,要么也要变成一种规范。即定义了规范,你们只能用,不能改。也要定义为final
所以最开始的 接口属性定义为 public static final,翻译就是能被继承,通过接口名可以访问,子类只能用不能改
4.3 接口与类的探讨
4.3.1 接口有构造函数吗?
接口中是没有构造方法的。因为接口抽象的很彻底。不能实例化对象。而且支持了多继承。JAVA设计者将构造方法给拿去了。
但是子类还是要继承的,要重写抽象方法的,不是说子类会默认调用父类的构造方法吗?
是的,那是继承才会。现在既然没有构造方法了,子类继承接口也将不再叫做继承。而叫做实现了。
前面为了说明来由,将继承与实现同化了。事实上是有区别的。
子类继承接口改名叫做实现了!接口里面的继承都是指实现。类与接口的关系没有继承,只有实现!!!
原因是实现不会默认调用接口的构造方法,因为接口就没有构造方法!
4.3.2 接口与类的关系
继承的关键字是 extends 而实现叫做 implements
纠正:本篇文章里面,上面写的关于 类继承接口 的说法其实是不对,正确的应该是类实现接口。之所以说继承是为了和类的继承进行对比。事实上,接口的实现和类的继承区别就不大。继承会默认调用父类的构造方法,而实现不会。其余的都一样。
类与类的关系:继承、而且是单继承
类与接口的关系: 实现,是类实现接口。
接口与接口的关系:继承,而且可以多继承。
同种类型之间叫做继承,类与接口之间才叫实现。
接口的多实现:
这个没什么新意。本来就是要这样子的。在多态里面,父类被很多子类继承,每个子类重写父类方法。慢慢的发现父类的被继承方法不需要方法体,由此引出抽象方法,又引出抽象类。抽象类不够彻底。来了接口。
这个演化的过程只是父类的演化。实质上还有需要很多子类去继承的。当然,接口里面改叫实现了。
没有多个实现类去实现接口,就没有接口的多态。
注意: 继承和实现是可以同时存在的。
问题:当实现类继承的父类与实现的接口具有同名同参数的方法时,而实现类又没有重写这个方法,调用的是哪一个的呢?
当这个方法在接口中不是默认方法时,即JDK7之前的接口定义,是没有方法体的,是一个抽象方法。而父类中这个方法如果是抽象方法,继承这个方法一定要重写的,没有重写说明不是抽象方法。即这个方法是有方法体的。所以,这种情况下是调用的父类的方法。
JDK8增加了默认方法,但是也不能改变这种调用原则。看起来像是 类优先。
4.3.3 为什么接口要支持多继承(实现)?
先看看为什么类不支持多继承。假设支持。
一个类A同时继承B、C、D三个类。B、C、D 都有speak方法。
当A没有重写speak方法时,A实例化一个对象,调用speak方法,请问调用是哪一个类里面的speak方法呢?
是B还是C又或者是D呢?
这个问题是有算法解决的,但是很复杂。C++支持多继承,python也支持。可以去参考一下。
这里如果不能确定调用的是哪一个方法,就会带来执行结果的不确定性。JAVA将这种情况给禁止了。
那么,为什么接口又允许了呢?
最大的原因就是接口的方法抽象的很彻底。1.7之前的方法都必须是抽象的。没有函数体的。
实现类必须重写接口中的方法。
看这种情况:一个类A同时实现B、C、D三个接口,这三个接口都有speak抽象方法。
由于A必须重写speak,所以调用的一定是自己的逻辑。不存在类的多继承这种情况。
即使三个接口B、C、D的抽象方法重名也没关系,反正没有方法体,而且都会被1个同名方法重写。
这样接口的多继承变成了接口简单的抽象方法合并。多个同名抽象方法相当于1个生效。
JAVA8的接口支持默认方法带来了一些新的问题。
问题:当实现类实现的的B、C、D里面都有同名同参数的默认方法时,A实现类调用这个方法是调用哪一个接口的默认方法呢?
ans:接口的默认方法原本是不要求实现类一定要重写。但是这种情况,必须进行重写。不然就会冲突。不知道调哪一个接口的。
当重写了之后,就是调用自己重写的,而不是调用接口中的。
再深入一下:如果还继承了 E类,E里面也有同名同参数的这个方法,是如何调用的呢?
ans:此时反而简单了,见上面的类优先原则。是调用父类E里面的这个方法。而且子类不要求必须重写。
当然,实现类要是重写了,还是调用重写的方法。
4.4 接口的匿名实现类
这个没啥新东西了。
匿名还是用在作为参数传递的时候用。看下面例子。
// 定义 Animal 接口
public interface Animal {
// 定义 speak 抽象方法
public void speak();
}
@Test
public void test1(){
testFunc(new Animal() {
@Override
public void speak() {
System.out.println("匿名实现类");
}
});
}
private void testFunc(Animal ani){
ani.speak();
}
上面的代码与匿名类的实现其实没什么不同。都是一般用在作为方法的参数传递。接口就是new 接口名,加上一对大括号,大括号里面全部实现接口的抽象方法,实现的本质还是全部重写抽象方法。
对比一下抽象类的匿名子类:
// 有一个抽象类 Animal
public abstract class Animal {
// 抽象方法 speak
public abstract void speak();
}
@Test
public void test1(){
// testFunc的参数 传递的是一个匿名类
testFunc(new Animal() {
@Override
public void speak() {
System.out.println("匿名类,使用");
}
});
}
private void testFunc(Animal ani){
ani.speak();
}
看到没有,区别就是一个是接口一个是抽象类。能实现的功能都一样。
接口就是来源于抽象类。属于特殊的抽象类。
5、代理模式(Proxy)
代理模式:为其他对象提供一种代理,以控制对这个对象的访问。
上面这句话很抽象,你肯定不懂,肯定记不住。哈哈
不着急。
先看看模板方法的代码:
public abstract class Animal {
// 抽象方法
public abstract void speak();
// 实际问题
public void spendTime(){
long start = System.currentTimeMillis();
speak();
long end = System.currentTimeMillis();
System.out.println("花费了:"+ (end - start)+"这么长的时间!");
}
}
// 一个非抽象子类
public class Dog extends Animal{
public String type = "Dog";
@Override
public void speak(){
System.out.println(this.type);
}
}
// 使用
@Test
public void test1(){
Animal dog = new Dog();
testFunc(dog);
}
private void testFunc(Animal ani){
ani.spendTime();
}
这种模式 就是 我们实现在抽象方法上增加一点逻辑。事实上还是要调用这个抽象方法的。
再来看看代码模式的代码:这里写的是静态代理。动态代理需要反射知识。到时再补充。
// 定义一个 接口 里面有一个抽象方法。
public interface NetWork {
// browse 抽象方法
void browse();
}
// 定义被代理类,这个类是 接口的实现类
public class ServerImpl implements NetWork {
@Override
public void browse() {
System.out.println("Server实现类 实现browse方法");
}
}
// 定义代理类,这个类也是接口的实现类
public class ProxyServer implements NetWork {
// 接口类型的变量 接收的一定是 接口的实现类,因为接口不能实例化对象
private NetWork work;
@Override
public void browse() {
System.out.println("代理类实现方法");
check();
this.work.browse();
}
// 检查方法
public void check() {
System.out.println("检查方法");
}
public ProxyServer(NetWork work){
this.work = work;
}
}
// 测试使用
@Test
public void test1(){
// new的是代理类,传的参数是被代理类对象
ProxyServer proxyServer = new ProxyServer(new ServerImpl());
// 执行的是代理的方法。
proxyServer.browse();
}
以上代码中,先有一个接口NetWork,里面有一个browse方法。 被代理类 ServerImpl 必须实现这个方法,因为它要真正的干活。
而代理类 ProxyServer 也是 NetWork 的一个普通实现类。也必须实现 browse 方法。只不过,他实现的有所不同。他重写browse的逻辑是内部先执行了自己的check方法。然后执行了 被代理类的 browse。
@Override
public void browse() {
System.out.println("代理类实现方法");
check();
this.work.browse();
}
具体是 在构造函数中接收一个对象,用多态方式接收,即形参是接口类型。是接口类型的形参可以接收任意实现类的对象。也包括他自己哦。但是不能真的传递他自己,会死循环。接收到的对象保存到类属性work中。
然后在重新browse的时候,先执行需要增加的逻辑 check,再调用 接收到的对象的browse方法。就像你委托别人帮你买房,别人去买房的时候增加了看房,讨价还价等逻辑,再叫你自己去付款,然后他可能再继续 搞其他手续。
在测试使用中,我们看到,new的是代理类对象,传递的是被代理类的一个匿名对象,你也可以传递普通的有名对象。
然后执行的是代理类的browse方法。看起来是代理类在干活。实际上,代理类干的活里面包括了你要干的活。
和前面的模板方法都实现了在不确定实现的基础上增加确定的逻辑。
模板方法是直接在抽象类中干的,因为抽象类是可以有非抽象方法的。但是接口不允许。所以只能在某一个实现类里面干。这个实现类就是代理类。
下面再看一个基于 抽象类 的代理模式:
// 抽象 类Animal
public abstract class Animal {
// 抽象方法 speak
public abstract void speak();
}
// 子类 dog 继承 Animal
public class Dog extends Animal{
public String type = "Dog";
@Override
public void speak(){
System.out.println(this.type);
}
}
// 子类 代理类 继承 Animal
public class AbstractClassProxy extends Animal{
private final Animal ani;
@Override
public void speak() {
long start = System.currentTimeMillis();
ani.speak();
long end = System.currentTimeMillis();
System.out.println("花费了:"+ (end - start)+"这么长的时间!");
}
AbstractClassProxy(Animal ani){
this.ani = ani;
}
}
// 测试使用
@Test
public void test1(){
AbstractClassProxy proxy = new AbstractClassProxy(new Dog());
proxy.speak();
}
用抽象类来实现代理模式也是可以的。但是总感觉有点别扭。
因为Animal类的子类应该都是动物,这里搞了一个AbstractClassProxy 很怪异。因为AbstractClassProxy不是一种动物。这也就是大家普遍认为 类与子类之间要有一种关系。即子类要归属于父类的一种。而不是像这里的这样 谈不上联系。
对于这样 没有什么关系的继承最好使用 接口来做。因为接口不叫做继承了,改叫 实现。就没有了这种 is a关系。或者说弥补了 is a关系以外的关系。
6、工厂模式
当没有工厂是,创建者和调用者都在一起。
public interface Car {
// run 方法
void run();
}
public class AudiImpl implements Car {
@Override
public void run() {
System.out.println("奥迪跑");
}
}
public class BydImpl implements Car {
@Override
public void run() {
System.out.println("byd 在跑");
}
}
public class TestFunc {
@Test
void test1(){
AudiImpl audi = new AudiImpl();
BydImpl byd = new BydImpl();
audi.run();
byd.run();
}
}
如上面的代理,创建 audi 与byd 和他们调用都在一起。这是常规用法。
如果将创建对象与对象调用分开,那就是工厂模式了。
6.1 工厂方法模式
增加一个工厂接口,里面有一个抽象方法
public interface CarFactory {
// 获取Car
Car getCar();
}
现在提供具体的实现类。来创建不同的对象。
public class AudiFactoryImpl implements CarFactory {
// 注意 重写方法的返回值可以是 接口定义的返回值本身或者子类
// 这里直接写Car作为返回值也没有什么问题
@Override
public AudiImpl getCar() {
return new AudiImpl();
}
}
public class BydFactoryImpl implements CarFactory {
// 这里就是保留原来的返回值
@Override
public Car getCar() {
return new BydImpl();
}
}
使用:
AudiImpl audi = new AudiFactoryImpl().getCar();
Car byd = (BydImpl)new BydFactoryImpl().getCar();
这样还不是最完美的,因为没增加一种骑车,就需要增加一个具体的工厂实现类。当学习到反射时,就可以突破这个限制。
6.2 抽象工厂模式
这个就是比工厂方法模式在创建对象的复杂程度更高一些。
用意再用给客户端提供一个接口,可以创建多个产品族中的产品对象。
具体的讲解将在 设计模型 专栏讲解。
7、内部类
就是一个类声明在另一个类的内部。这与同一个文件写多个类是有区别的。
写在内部意味着 写在某个类定义的大括号里面。外面的类叫做外部类,里面的就是内部类。
为什么会出现这种东西呢?
就是我们定义属性时,简单的属性不能满足我们的需要。需要复杂的数据类型。
复杂的数据类型就是类啊。
其实内部类放在外面也是可以的。之所以放在内部,是因为这2个类关系密切。
public class InnerClassDemo {
private InnerC inner;
// 普通成员内部类
class InnerC {
}
// 静态成员内部类
static class StaticInnerC{
}
}
看上面的代码,定义了2个成员内部类,一个静态的,一个非静态的。还可以定义局部内部类。
public class InnerClassDemo {
private InnerC inner;
// 普通成员内部类
class InnerC {
}
// 静态成员内部类
public static class StaticInnerC{
}
public void method() {
// 局部内部类
class LocalInnerC{
}
}
{ // 代码块内部类 也是局部内部类
class CodeBlockInnerC {
}
}
}
一般用的多的是成员内部类。
从2方面分析成员内部类:
-
作为类的成员:能被 权限、static、final、abstract修饰。
4中权限都可以修饰。注意外部类只能是public 或者缺省
static、final修饰都是与属性、方法一致的。
-
作为一个类:具有所有类的特性。
内部类的例子:
例如:Integer 包装类里面就有 一个 IntegerCache 内部类。
7.1 如何实例化成员内部类?
如果要在外部实例化成员内部类,要求这个内部类外部可以访问。即类的权限为public,还得是静态的
InnerClassDemo.StaticInnerC staticInnerC = new InnerClassDemo.StaticInnerC();
对于非静态的内部类,需要先实例化外部类对象,再new出来。注意是 对象.new 内部类名称
InnerClassDemo innerClassDemo = new InnerClassDemo();
InnerClassDemo.InnerC innerC = innerClassDemo.new InnerC();
7.2 如何在成员内部类中区分调用外部类的结构?
public class InnerClassDemo {
private InnerC inner;
private String name = "外部类";
// 普通成员内部类
public class InnerC {
private String name = "普通内部类";
}
// 静态成员内部类
public static class StaticInnerC{
private String name = "静态内部类";
}
public void method() {
// 局部内部类
class LocalInnerC{
private String name = "方法局部内部类";
}
}
{ // 代码块内部类 也是局部内部类
class CodeBlockInnerC {
private String name = "代码块局部内部类";
}
}
}
上面展示了 所有类都有同名的name属性,来看看如何区分调用
public class InnerClassDemo {
private InnerC inner;
private String name = "外部类";
// 普通成员内部类
public class InnerC {
private String name = "普通内部类";
public void test(){
// 调用外部类的属性 name
System.out.println(InnerClassDemo.this.name);
}
// 测试
public void test(String name){
System.out.println(name); // 调用方法形参
System.out.println(this.name); // 调用本类的属性
System.out.println(InnerClassDemo.this.name); // 外部类的属性
}
}
// 静态成员内部类
public static class StaticInnerC{
private String name = "静态内部类";
}
public void method() {
// 局部内部类
class LocalInnerC{
private String name = "方法局部内部类";
}
}
{ // 代码块内部类 也是局部内部类
class CodeBlockInnerC {
private String name = "代码块局部内部类";
}
}
}
在 普通的背部类中测试了一下。this的用法还是不变。非静态结构还是不能调用静态结构。
内部类调用外部类属性要用 外部类名.this.属性名的方式
7.3 局部内部类的使用
局部内部类
public void method() {
// 局部内部类
class LocalInnerC{
private String name = "方法局部内部类";
}
}
上面这种使用方法很少见。一般是需要获取一个对象时才会用到。见下面的调用
// 获取一个实现了Comparable接口的类对象
// 方式1:
public Comparable getComparable1(){
// 创建一个Comparable接口的类, 这个类就是局部内部类
class MyComparable implements Comparable{
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();
}
// 方式2:
public Comparable getComparable2(){
// 匿名实现类 这也是内部类
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}
你会发现,匿名类可以替换有名类,本来就是内部用一次。所以方式2见的多。