2018.06.05 重新梳理下java基础,熟悉的章节不记录了。
定义:为了让方法名相同而形参不同的构造器同时存在,即同名不同参。
区分:通过参数类型的差异。(注意:不要用返回值区分重载方法 )
类型提升(向上提升):
int — long — float — double
byte — short — int
char — int
窄化转换:和向上提升反过来,注意先考虑降到byte再考虑char
1、this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。
2、static方法就是没有this的方法,可以在没有创建任何对象前提下,仅仅通过类本身来调用static方法,很像全局方法。
垃圾回收器只知道释放那些经由new分配的内存。
(1)对象可能不被垃圾回收。
(2)垃圾回收不等于“析构”。
(3)垃圾回收只与内存有关(使用垃圾回收器的唯一原因就是回收程序不再使用的内存)。
1、finalize()的用途
(1)finalize()只处理一个特殊情况:通过某种创建对象方式以外的方式为对象分配了存储空间。
finalize()用法比较像C中调用了malloc()来分配存储空间,且必须调用free()才能使空间得到释放,否则造成内存泄漏,那么finalize()就是需要在本地方法中调用它,即调用C++的方法。
本地方法:就是在java中调用非java代码的方式。(面试题常考)
不应该将finalize()作为统一的清理方法,因为它可能不被执行,这是一个陷阱。
(2)finalize()还有一个有趣的用法,它并不依赖于每次都要对finalize()进行调用,这就是对象“终结条件”的验证。
System.gc() 用于强制进行终结动作。
2、垃圾回收如何工作(面试经典题)
Java虚拟机采用一种自适应的垃圾回收技术。
要是没有新垃圾及产生,就会转换到 "标记——清扫"工作模式。
"标记——清扫"所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出存活的对象,给对象一个标记。全部标记工作完成后,清理动作才会开始。
“停止——复制”的回收动作不是在后台运行的,它发生时,程序将会被暂停。它将所有活对象从旧堆复制到新堆,然后再释放旧堆中的对象所占内存。
如果所有对象都很稳定,垃圾回收器的效率降低,就切换到“标记——清扫”方式,同样,Java虚拟机会追踪“标记——清扫”的效果,如果堆空间出现很多碎片,就会切换回“停止——复制”方式。
Java虚拟机中有很多附加技术提升速度,比如“即时”编译器技术(JIT)。这种技术将程序全部或部分翻译成本地机器码。
1、初始化成员
type | value |
---|---|
boolean | false |
char | [ ] |
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0 |
double | 0.0 |
2、静态数据初始化
静态数据只占用一份存储区域。静态初始化只有在必要时候进行。只有在第一个类型对象创建(或第一次访问静态数据)的时候,他们才会被初始化。此后,静态对象不会再被初始化。(在构造器之前)
初始化顺序:
静态对象(只一次)——> 非静态对象
3、数组初始化
(1)注意数组不能直接赋值给另一个数组,要想复制数组,要么将元素遍历一个个赋值,要么用深拷贝(List、Map都是这样)。
int[] a1 = { 1,2,3,4,5 };
int[] a2;
a2 = a1;
这样是不行的,a2只是a1的一个引用a2的值改变相当于a1改变。
(2)可变参数列表
所谓可变参数列表,可以理解为函数的参数列表中某类型的数量是不确定的。类似于C中的varargs和python中的List。
enum的一些特性:
1、toString() 函数可以方便的显示某个实例的名字。
2、ordinal() 函数可以显示某个特定enum常量的申明顺序。
3、枚举类型可以配合switch case使用。
第2、3点在Android中用handler传递消息有很多的应用。
当创建一个导出类对象时,该对象包含了一个基类子对象。当初始化时,构造器的调用遵循由内到外的顺序,默认情况下调用基类的无参构造器,基类构造器都有参时,可以用super(“参数”)显式的调用基类构造器。
就是使用JavaBean的原理
关键字protected表明,就类用户而言,它是private的,就 继承自此类的导出类 或者 其它位于同一个包的类来说,它是可以访问的。
1、final数据:
(1)典型的定义常量:
public static final int Value = 66;
定义为public,则可以被用于包之外;定义为static,则强调只有一份;定义为final,则说明它是一个常量。
(2)定义为static和不定义为static区别
private final int i4 = rand.nextInt(20);
static final int i5 = rand.nextInt(20);
i5的值不可以通过创建第二个对象加以改变,因为它是static的,在装载时已被初始化。
(3)final引用的意义
不能因为一个变量是final的,就认为无法改变它的值。由于它是一个引用,final意味着无法将该变量再次指向另一个新的对象。
2、final参数
在函数参数列表中的final参数,在函数内无法修改它。
3、final方法
使用场景:
1、把方法锁定,防止继承类修改,覆盖它。
2、提高效率。(逐渐淘汰)
4、final类
使用场景:不打算集成该类,对该类的设计永不需要做任何变动,或出于安全考虑,不希望它有子类。
“封装”:利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。(例:JavaBean)
“继承”:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
“多态”:消除类型之间的耦合关系。将改变的事物与未变的事物分离开来
子类对象的引用向上转型为基类,传递到相应方法中。
多态的运作方式:只写一个方法,这个方法仅接收基类作为参数,也就是说,不管导出类的存在,编写的代码只是与基类打交道。
1、绑定
将一个方法调用同一个方法主体关联起来被称作绑定。
为什么需要?因为Java中所有方法都是通过动态绑定实现多态。
2、产生正确的行为
发送消息给某个对象,让该对象去断定应该做什么事。
向上转型就是这句代码:
Shape s = new Circle();
3、可扩展性
所有方法只与基类接口通信,这样的程序可扩展,因为可以从通用的基类继承出新的数据类型,从而新添一些功能。
为什么编译器强制必须调用构造器,没有指定基类某个构造器,也要调用默认构造器?(构造器的作用)
基类构造器用来对自己的元素进行初始化,因此必须令所有过早起都得到调用,否则不可能正确构造对象。
1、构造器的调用顺序
基类构造器 -> 成员的初始化方法 -> 子类构造器
2、继承与清理
在销毁时,需要显式的调用基类的dispose()方法,销毁的顺序和初始化相反,包括字段的销毁顺序和申明的顺序相反。
3、构造器内部的多态方法行为(会存在潜在的bug)
在初始化时,基类构造器中调用子类中覆盖的方法,此时子类的成员变量未赋值,如果此时对其操作可能会产生意想之外的结果。所以应该避免这样做。在构造器内唯一能够安全调用的事基类中的final方法。(P163的例子很能说明问题)
4、用继承进行设计
原则:(1)组合更加灵活,首选组合。
(2)用继承表达行为间的差异,用字段表达状态上的变化。
例子:
import static net.mindview.util.Print.*;
class Actor {
public void act() {}
}
class HappyActor extends Actor {
public void act() { print("HappyActor"); }
}
class SadActor extends Actor {
public void act() { print("SadActor"); }
}
class Stage {
private Actor actor = new HappyActor();
public void change() { actor = new SadActor(); }
public void performPlay() { actor.act(); }
}
public class Transmogrify {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
} /* Output:
HappyActor
SadActor
5、向下转型与运行时类型识别
使用场景:
对于子类扩展基类接口的情况,向上转型后不能调用子类的不同于基类的新方法。
这样需要用到向下转型,在Java中,所有的转型都会对其进行检查。称为“运行时类型识别”(RTTT)如果转型正确,则转型成功;如果所转类型不是正确的类型,则转型失败,返回ClassCastException异常。
例子:
class Useful {
public void f() {}
public void g() {}
}
class MoreUseful extends Useful {
public void f() {}
public void g() {}
public void u() {}
public void v() {}
public void w() {}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f();
x[1].g();
// Compile time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown
}
}
1、包含抽象方法的类叫抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。
2、如果从一个抽象类继承,并想创建该新类的对象,那么必须为基类中的所有抽象方法提供方法定义。
3、可以创建一个没有任何抽象方法的抽象类。(什么情景下出现这种情况?)
interface这个关键字替代class关键字,产生了一个完全抽象的类。接口只提供形式,未提供任何具体实现。
接口被用了建立类与类之间的协议。接口也可以包含域,但是这些域隐式的是static和final的。因此,其中定义的成员变量,是static&final的。
接口与抽象类的区别:
a.继承抽象类的子类们的本质都是相似的,它们体现的是一种 “is-a" 的关系,就像动物中的猫和狗;而对于接口的继承更多的是一种行为的相似,是一种 “like-a” 的关系,比如飞机和鸟,它们都具有飞的行为,却并不需要在本质上相似。
b.抽象类可以拥有任意范围的成员数据,既可以是抽象,也可以是非抽象;但是接口,所有的方法必须是抽象的,所有的成员变量必须是 public static final的,某种程度上来说,接口是对抽象类的一种抽象。
c.一个类只能继承一个抽象类,但却可以实现多个接口。
d. 抽象的子类可以选择性实现其基类的抽象方法 接口的子类必须实现
使用情况:
a.抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高
b.抽象类可以有具体的方法 和属性, 接口只能有抽象方法和不可变常量
c.抽象类主要用来抽象类别,接口主要用来抽象功能.
d.抽象类中,且不包含任何实现,派生类必须覆盖它们。接口中所有方法都必须是未实现的。
e.接口是设计的结果 ,抽象类是重构的结果
1、策略模式:创建一个能够根据所传递的参数对象的不同而具有不同行为的方法。
2、适配器模式:适配器中的代码将接受你说拥有的接口,并产生你所需要的接口。说白了就是桥接两个接口,用与接口之间的转换。
将接口从具体实现中解耦使得接口可以应用于多种不同的具体实现,因此代码更具有可复用性。
多重继承使用接口的核心原因:为了能够向上转型为多个基类型。
通过继承,可以在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。
为什么使用接口而不是类?
因为在适配器模式中,我们可以在任何现有类之上添加新的接口,所以这意味着让方法接受借口类型,是一种让任何类都可以对该方法进行适配的方式。
比如Scanner类。Scanner构造器接受一个Readable接口,如果创建了一个新类,并且想让Scanner可以作用它,就让它成为Readable,即实现Readable接口,这个接口只要求实现read()方法,新类实现这个方法就ok。·
对消费者传递一个工厂1对象,产生工厂1的产品,调用产品1的方法。
对消费者传递一个工厂2对象,产生工厂2的产品,调用产品2的方法。
import static net.mindview.util.Print.*;
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
Implementation1() {} // Package access
public void method1() {print("Implementation1 method1");}
public void method2() {print("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory {
public Service getService() {
return new Implementation1();
}
}
class Implementation2 implements Service {
Implementation2() {} // Package access
public void method1() {print("Implementation2 method1");}
public void method2() {print("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory {
public Service getService() {
return new Implementation2();
}
}
public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(new Implementation1Factory());
// Implementations are completely interchangeable:
serviceConsumer(new Implementation2Factory());
}
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
内部类是嵌在外部类内部的类。
如果想从外部类的非静态方法创建一个内部类对象,一定要用这样调用:OuterClassName . InnerClassName
Parcel2 q = new Parcel2();
Parcel2.Contents c = q.contents();
内部类拥有其外围类的所有元素的访问权。
一个应用到迭代器模式的例子:
interface Selector{
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] items;
private int next = 0;
public Sequence(int size){
items = new Object[size];
}
public void add(Object x){
if ( next < items.length){
items[next++] = x;
}
}
private class SequenceSelector implements Selector{
private int i = 0;
@Override
public boolean end() {
return i == items.length;//内部类可以访问成员
}
@Override
public Object current() {
return items[i];
}
@Override
public void next() {
if ( i < items.length){
i++;
}
}
}
public Selector selector(){
return new SequenceSelector();
}
public static void main(String[] arg0){
Sequence sequence = new Sequence(10);
for (int i = 0;i < 10;i++){
sequence.add(i);
}
Selector selector = sequence.selector();//通过调用selector()获取一个自己定义的Selector
while ( !selector.end()){
System.out.println(selector.current() + " ");
selector.next();
}
}
}
可以在一个方法或者任意的作用域内定义内部类,这样做有两个理由:
1、实现了某类型的接口,于是可以创建并返回对其的引用。
2、要解决一个复杂的问题,想创建一个类来辅助解决方案,但是不希望这个类是公用的。
eg1:在方法内定义内部类
public Destination destination(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
eg2:在作用域内定义内部类
public class Parcel6{
private void internalTracking( boolean b){
if (b){
class TrackingSlip{
private String id;
TrackingSlip(String s){
id = s;
}
String getSlipe(){
return id;
}
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlipe();
System.out.println(s);
}
//在if外面用是无效的,超出了作用域范围
//! TrackingSlip ts = new TrackingSlip("x");
}
public void track(){
internalTracking(true);
}
public static void main(String[] arg0){
Parcel6 parcel6 = new Parcel6();
parcel6.track();
}
}
注意点:
1、如果定义一个匿名内部类,它需要使用外部定义的参数,那么次参数引用需要是final的。
public class Parcel9 {
// Argument must be final to use inside
// anonymous inner class:
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
}
2、如果这个参数并未直接被直接使用在匿名内部类内部(如被基类构造器使用),那么就不需要是final的。
abstract class Base {
public Base(int i) {
print("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{ print("Inside instance initializer"); }
public void f() {
print("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
上例中解决了一个问题:虽然匿名内部类不能有构造器,因为连名字都没有,但可以先实例初始化,就能达到为匿名内部类创建一个构造器的效果。
3、可以更好的应用在工厂模式中
1 、将内部类声明为static,则内部类对象与其外围类对象之间不会有联系,这就叫做嵌套类。
嵌套类意味着:
1)、要创建嵌套类的对象,不需要其外围类的对象。
2)、不能从嵌套类的对象中访问非静态的外围对象。
2、接口内部的类
接口中的任何类自动是public和static的。
如果想创建某些公共代码,使得它们可以被某个接口的所有不同实现所公用,那么使用接口内部的嵌套类会显得很方便。
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
3 、为什么需要内部类
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果使用内部类,可以获得一些特别的特性:
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
创建内部类对象的时刻并不依赖于外围类对象的创建。
内部类没有令人迷惑的“is-a”关系,它就是一个独立的实体。
所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法
有两篇博文写的非常好,不懂的时候可以反复看:
1、https://blog.csdn.net/Allen_Zhao_2012/article/details/8056665
2、https://blog.csdn.net/xiaanming/article/details/8703708
class WithInner{
class Inner(){}
}
public class InheritInner extends WithInner.Inner{
InheritInner(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
InheritInner 只继承内部类而不是外部类。但是当要生成一个构造器时,不能只是传递一个指向外围类对象的引用。
使用局部内部类而不用匿名内部类的理由:
1、需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化;
2、需要不止一个该内部类对象。
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1)Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照队列规则来确定对象产生的顺序。
2)Map。一组成对的"键值对"对象,允许使用键值来查找值。映射表允许我们使用另一个对象查找某个对象,它也被称为“关联数组”。
1、添加一组元素
方法1:Collection.addAll()方法运行起来很快,而且构建一个不包含元素的Collection,然后调用Collection.addAll()这种方式很方便,因此它是首选方式。
Collection collection = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
Integer[] moreInts = { 6, 7, 8, 9, 10 };
collection.addAll(Arrays.asList(moreInts));
方法2:Collection.addAll()只能接受另一个Collection对象作为参数,不如Arrays.asList()和Collections.addAll()灵活,这两个方法采样的都是可变参数列表。(注意Collection和Collections)
Collections.addAll(collection, 11, 12, 13, 14, 15);
2、各类容器的特点
Collection在每个槽中只能保存一个元素。此类容器包括:List,它以特定的顺序保存一组元素;
Set,元素不能重复;
Queue,一端插入对象,另一端移除对象。
Map在每个槽内保存两个对象,即键和与之相关联的值。
Collection打印出来的内容用[ ]括住,Map打印出来的内容用{ }括住。
对于Set:
HashSet:获取元素方式最快(原理后续章节会讲);
TreeSet:按照比较结果的升序保存对象;
LinkedHashSet:按照被添加的顺序保存对象。
对于Map:
HashMap:提供了最快的查找技术;
TreeMap:按照比较结果升序保存键;
LinkedHashMap:按照插入顺序保存键,同时还保留了HashMap的查询速度。
两种类型的List:
ArrayList:它长于随机访问元素,但是在List的中间插入和移除元素时较慢;
LinkedList:它提供了代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢。
LinkedList在执行某些操作时比ArrayList更高效,但是在随机访问操作方面却要逊色一些。
LinkedList还添加了可以作为栈,队列,双端队列的方法。可以使用它们的方法。
indexOf()方法返回索引,如果重复元素存在,再添加一个相同的元素,调用该方法返回“-1”(表示未找到它),此时调用remove()返回false。
subList()方法允许很容易的从较大的列表中创建一个片段,顺序并不影响containsAll()的判断结果。
retainAll()保留两List的交集。
removeAll()也是基于equals()方法的。
迭代器(Iterator,一种设计模式)是一个对象,它的工作是遍历并选择序列中的对象,迭代器通常被称为轻量级对象,创建它们的代价小。
Java的Iterator只能单向移动,用来:
1、使用方法Iterator()要求容器返回一个Iterator。Iterator准备好返回序列的第一个元素。
2、使用next()获得序列中的下一个元素。
3、使用hasNext()检查序列中是否还有元素。
4、使用remove()将迭代器新近返回的元素删除。
在remove()之前必须先调用next()。
“栈”通常是指“后进先出”(LIFO)的容器。
可以直接将LinkedList当做栈使用。
public class Stack {
private LinkedList storage = new LinkedList();
public void push(T v) { storage.addFirst(v); }
public T peek() { return storage.getFirst(); }
public T pop() { return storage.removeFirst(); }
public boolean empty() { return storage.isEmpty(); }
public String toString() { return storage.toString(); }
}
Push()接受T类型对象,peek()和pop()返回T类型对象。
peek()方法将提供栈顶元素,但并不将其从栈顶移除。
pop()将移除并返回栈顶元素。
用Stack实现:
import net.mindview.util.*;
public class StackTest {
public static void main(String[] args) {
Stack stack = new Stack();
for(String s : "My dog has fleas".split(" "))
stack.push(s);
while(!stack.empty())
System.out.print(stack.pop() + " ");
}
} /* Output:
fleas has dog My
注意显示的导入import net.mindview.util.Stack; 而不是jiava.util.Stack。
如果想在自己的代码里使用Stack类,在创建实例时,需要完整的指定包名,否则可能会和java.util包中的Stack发生冲突。
特点:
1、Set不保存重复元素。
2、HashSet使用了散列(散列概念后续章节有),因此输出的顺序没有规律。
3、若想对结果排序,用TreeSet。
HashSet、TreeSet、LinkedHashSet
HashSet使用了散列,HashSet所维护的顺序与TreeSet和LinkedHashSet都不一样,因为他们的实现具有不同的元素存储方式。TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数。LinkedHashList因为查询速度的原因也使用了散列,但是看起来是使用了链表来维护元素的插入顺序。
Map的get方法返回该键对应的值。如果没有则返回null。
Map的put方法放入键值对。
Map的contansKey()返回是否含有这个键,containsValue()返回是否含有这个值。
KeySet()方法产生所有健组成的Set,可用于遍历。
Map有四种取值方法,网上可以查到。
队列是一个典型的先进先出(FIFO)的容器。队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要。因为它们可以安全的将对象从一个任务传输给另一个任务。
offer()是与Queue相关的方法之一,他在允许的情况下,将一个元素插入到队尾,或者返回false。
peek()和element()都将在布衣橱的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常。
poll()和remove()方法将一出并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。
作者曰:理解了边界所在,你才能成为程序高手。因为只有知道了某个技术不能做到什么,你才能更好地做到所能做的(部分原因是,不必浪费时间在死胡同里乱转)。
一、元组
实现的功能:仅一次方法调用就返回多个对象
元组(tuple)将一组对象直接打包存储于其中的一个单一对象。这个容器允许读取其中的元素,但不允许向其中存放新的对象。
public class TwoTuple{
public final A first;
public final B second;
public TwoTuple(A a,B b){
first = a;
second = b;
}
public String toString(){
return "(" + first + "," + second + ")";
}
}
该例子的安全性:
1、客户端程序读取first和second对象,可以任意使用,但由于声明为final,因此无法对first和second赋值
2、若想要使用不同元素的元组,强制要求他们另外创建一个新的TwoTuple对象。
二、泛型接口
1、泛型接口可应用于生成器,这是一种专门负责创建对象的类,一个生成器只定义一个方法,该方法用以产生新的对象。比如next()方法。如下:
public interface Generator{
T next();
}
2、泛型的一个局限性:基本类型无法作为类型参数。比如int类型变量的类型参数为“Integer”。
3、可用于匿名内部类或内部类
这一章后面太细太复杂 不看了
---------分割线2018.10.31--------
突然发现草稿箱还有笔记。哎。。。好久没用java了,以后有时间再把后续章节的内容整理好。