由于我的缘由,布衣博主的技术博文大半年来一直没时间更新(WHAT ? 这是啥理由),恍恍惚惚间,一年又是头,仍是得跳出来,给本身一个交代。java
编程日久,项目开发中最经常使用的技能大概就是 Ctrl+C 再 Ctrl+V 了,实实在在的代码搬运工。这很正常,人愈来愈堕,能用现成有的东西毫不会本身造;尤为上班这种食人俸禄的差事,要讲究效率和产出,拷贝修改,虽然低级,却彷佛是最高效的作法。不过久而久之的,从码农自身来看,对于本身能力的提高,技能体系的构建,确定是弊大于利的。因此,有时间仍是要沉下来,看书,钻研,学习新东西,与时同步。最近在看《Java 编程思想(第四版)》这本书,对博主这种对Java初通皮毛的人来讲,能同时兼顾基础和深度,受益颇多,也是对本身已有的Java知识进行了比较系统的梳理。固然,技术最重要的在于交流分享,因此,读书和实践过程当中布衣博主也会有一些本身的心得体会的分享出来,算是抛砖引玉吧。这篇先讲讲Java内部类相关的东西。都是结合书本内化而来的,有知识点,也有布衣博主本身的料。程序员
内部类,顾名思义,就是一个定义在已有的Java类内部的类,就像下面这样:编程
public class Outer { //外部类 class Inner{ //内部类 public void test(){ System.out.println("内部类方法"); } } }
而后呢?没有而后了,就先这样简洁直观的认识它吧,相信我,内部类原本就很简单,别搞得太复杂了。缓存
内部类既然是个Java类,天然是能够建立对象的。固然,这话不许确,往深了说,内部类也能够是抽象的,这确定是没法建立对象的——不过你若是要这样死究的话,我以为就有点太囿于语法了,对实际运用帮助不大。简单点的掌握内部类常见的普通状况:普通内部类、静态内部类以及匿名内部类足够了。下面分别介绍——框架
普通状况,或者说最典型的状况,就是一个Java类嵌在另外一个Java类中,造成了内、外的格局;外部类就是咱们普通的类,内部类也是普通的类,特性都知足Java类的特性,没什么特别的。惟一要说的,就是内部类对象的建立依赖于外部类对象。这很好理解,若是在外部类都没有建立对象的基础上,内部类建立的对象依附于哪一个实体呢?也能够说是皮之不存毛将焉附?因此,内部类的对象建立是这样的:
ide
public class InnerTest { public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.test(); //有了内部类对象你就能够像正常的类对象同样进行方法调用的骚操做了。 } }
不过,一般状况下,也不会这样去建立内部类对象,基于Java封装的特性,内部类做为一种封装的体现,一般会对调用者隐藏实现细节,而经过外部类的方法来返回内部类的对象实例。普通内部类虽然和外部类是两个类,可是由于处于一个类的内部,仍是有些不同的特性——它能够访问外部类的因此成员,包括私有的字段、方法等,这是由于内部类中隐含的持有对外部类的引用,经过这个引用,就能调用外部类的全部成员了。这有点相似于局部和全局的关系,内部类处于局部,可以直接调用做为全局的外部类的其它成员。说到这里,有个误区须要注意。常常看网上说法就说内部类对象能够访问外部类的成员,好像我建立的内部类对象能够直接调用外部类的成员同样,博主也差点就信了,代码尝试,大靠一声,怎么能访问的?因此,我以为,技术人员对文字的理解仍是有些不够严谨的地方,包括书本上的说法,或由于翻译的缘由,一些表达上并非那么贴切的,这个只能本身多去思辨实践了。这里,咱们先给外部类定义方法、字段等来完善一下:学习
public class Outer { private String str; public void buyi() {
System.out.println("外部类方法"); } class Inner { public void test() { buyi(); // ① str = "陈本布衣"; // ② System.out.println("内部类方法"); } } }
public class InnerTest { public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.test(); inner.str; // ③ buyi.buyi(); // ④ } }
上面代码中,① ②才是上文中说的,内部类能够访问外部类的全部成员的状况,③和④ 你会发现,是没法编译经过的。也就是说,正确的理解应该是内部类的内部能够经过隐含的外部类的引用去访问外部类的全部成员,而不是内部类对象能够访问外部类的成员,这是有本质区别的,固然你也不能拿着内部类对象的引用去访问外部类的成员,要搞清楚,这是毕竟是两个类,类的封装性决定了,一个类的对象是不能去访问另外一个类对象的非静态成员的。测试
以上所见都是看起来很正常的内部类,比较普通,其实内部类还有很复杂的状况,好比定义在方法内部、代码块内部的局部内部类,这里就不深究了。我以为,做为基础性掌握,没必要太追求全面了,先入门,用精,当你技术积累够了,不少东西也就通了。优化
Java中的静态,指全局的,好比静态方法、成员变量等,若是访问权限容许,你在任何地方都能都直接使用。未了解内部类以前,不知道你有没有想过,类可不能够也是静态呢?是的,类也能够是静态的,不过必须是内部类才行。静态内部类,也叫嵌套类,Java中标准类库中就有不少现成的应用,好比整形的包装器中用静态内部类来缓存[-128,127]之间的整数。嵌套类由于是静态的,很天然的要从静态方面去考虑与普通内部类的区别。这里用博主的理解来阐述一遍:this
① 相似于静态方法中你不能使用this关键字,于是嵌套类就失去了普通内部类中那个隐含对外部类的引用this,结果就是你不能在嵌套类中随意访问外部类的非静态成员了;
② 静态属性决定了,嵌套类更加独立于外部类,要建立嵌套类的对象彻底不依赖外部类的对象实体;
③ 静态属性决定了,它会在外部类加载的时候初始化。。。等等,博主差写高兴了,想固然的东西到底靠不靠谱呢?实践是永远是检验真理的惟一标准,上代码——
public class Outer { Outer() { System.out.println("默认构造器"); } static { System.out.println("外部静态块"); } static void outerstatic() { System.out.println("外部静态方法"); } static class Inner { public static String str = "陈本布衣"; static { System.out.println("嵌套类静态代码块"); } static void innerstatic() { System.out.println("嵌套类静态方法"); } public void test() { System.out.println("嵌套类非静态方法"); } } }
public class InnerTest { public static void main(String[] args) { Outer outer = new Outer(); // ① Outer.Inner inner = new Outer.Inner(); // ② Outer.Inner.innerstatic(); // ③ String s = Outer.Inner.str; // ④ } }
稍微有点Java基础的都清楚,静态成员是一个类中最早被初始化的部分,因此,若是咱们只经过 ① 建立外部类的对象,那么Outer类中的静态代码块确定会执行,控制台有相应的打印,那静态内部类会不会也被初始化呢? 测试结果是不会,这时候静态类有点像静态方法,你主动叫它,它就静静的待在哪里,不为所动! 单独执行 ② 你会发现,外部类静态块没有初始化,也便是静态内部类独立于外部类,它的初始化不会对外部类有任何影响;执行 ③ ④ 一样的,你会发现,只有在要使用类的内部属性的时候,代码块才会初始化,一样的初始化对外部类的初始化没有产生影响,就像外部类彻底不知道内部类在干什么同样。
这个时候咱们再去看包装器中关于缓存的静态内部类的使用就会更加透彻一些,以Integer包装器为例:
// 源码中的静态内部类 private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } // 源码中的装箱操做 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
代码中,只是经过静态内部类定义了缓存值的默认范围,在你的程序中,若是你没有主动的执行装箱或自动的执行装箱转换,静态内部类是不会加载进内存的,对内存没有任何占用;只有当你的代码有了装箱操做,才会对静态内部类产生调用,才会将该类加载进内存。从这个方面去想,Java的设计者经过对于代码的编写的优化是比我等菜鸟要高深那么一点点点点。。。。
继续顾名思义,匿名内部类,就是没有类名的内部类。缺乏了类名,也就意味着没有构造器,那怎么建立对象呢?只能直接new 它的类实体,能够这么说,匿名内部类是伴随着类定义的同时就必须被实例化的。咱们经过匿名内部类来建立线程就是很好的例子:
public class ThreadTest { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { } }); thread.start(); } }
(PS : 对Java8的Lambda表达式比较熟悉的老司机可能会像下面这样去写,看起来代码简洁逼格也挺高,不过说实话,这样写代码我看了内心是有句mmp不知当讲不当讲的,项目组的维护和沟通成本都很高。只能说,对于新技术仍是不要盲目的跟风吧。)
public class ThreadTest { public static void main(String[] args) { Thread thread = new Thread(() -> { }); thread.start(); } }
线程的建立应该是咱们比较常见的匿名内部类的应用了。不过上诉代码你若是不用匿名内部类,也能够先实现了接口,经过接口的实现再来建立对象,可是,何须舍近道而绕远路呢?这里也能够看出匿名内部类特色了,它帮咱们省去了单独实现接口再来建立对象的不少冗余的步骤。因此你该知道,匿名内部类本质上就是一种代码上的减省,实际上它仍是在遵循着Java实现(继承)后再建立对象的语法逻辑的,不信看下面的代码:
public class Model { private int i; public Model(int i) { this.i = i; } public int value() { return i; } @Override public String toString() { return "Model{" + "i=" + i + '}'; } }
public class Demo { public Model getModel(int var){ return new Model(var){ public int value(){ System.out.println("方法调用"); return super.value()*20; } }; } @Test public void test(){ Model model = getModel(3); System.out.println(model); //此时value的值仍是 3 ,由于匿名类中的方法还没被调用 System.out.println(model.value()); // 此时value被赋值为 60 } }
这段段代码说明两点:① 经过匿名内部类的方式返回Model对象的时候是有继承体系的,也即匿名实体是继承了Model类的,否则实体中的没法使用super调用父类方法;② 不仅是接口的实现能够用匿名内部类,普通类的继承也是能够的,只要知足继承体系便可。
内部类本人平时开发中用得也很少,单纯的从功能实现来说,漫长的码农生涯中彻底不用内部类也彻底无碍,可是Java的设计者们为何还要搞这套语法呢?追踪Java标准类库的一些源码你会发现,平时经常使用的容器类、整形包装器等都有大量使用内部类的场景;而平时引入的第三方类库、框架中的源码也有不少使用内部类的。没有对比就没有伤害,当你翻看本身的代码老是一坨一坨拼凑感极强,源码框架中的代码老是那么的优雅时,你还能说什么呢,还不能让你对代码编写质量,对内部类这种语法结构的使用多一份心思吗?尤为是在没有代码审查制度对代码质量也没有严格要求的公司,程序员的在代码上的任意发挥简直是程序bug之母,因此,像布衣博主同样,虽然对内部类在代码编写上的妙用,还体会得不够深入,但优化代码时,尝试着往源码的思路靠,样子走,总仍是有些益处的。