一、前言
本来想看完书再整理下自己的笔记的,可是书才看了一半发现笔记有点多,有点乱,就先整理一份吧,顺便复习下前面的知识,之后的再补上。
真的感觉,看书是个好习惯啊,难怪人家说“书籍是人类进步的阶梯”。之前学知识,喜欢网上找份教程,看点视频,照着做呗,秉着”我做过的东西反正别人肯定玩过“的观念,一通乱学,学的又多又杂,现在细细想来,很多东西我只是学到了它的形,却没有学到它的神,只是在抄别人的代码。为什么这么做?这么写是出于什么考虑?我都一脸懵懂!而现在我喜欢看书,花时间来沉淀自己的知识,与大家共勉!另外,不推荐看第四版翻译的《Thinking in Java》,讲的太拗口了。或者说翻译的太拗口了。简直像谷歌的一键翻译成中文......
二、面向对象
1、对象存储到什么地方?
- 寄存器。这是最快的存储区,寄存器的数量及其有限,由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何现象。
- 栈。栈指针向下移动则分配新的内存,向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。基本数据类型和对象引用存储在其中。
- 堆。一种通用性的内存池,用于存放所有的java对象,堆不同于栈的好处在于:编译器不需要知道从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多少时间。
- 静态存储。静态存储里存放 程序运行时一直存放的数据。Static 关键字的对象存放在其中。static局部变量在所处模块在初次运行时进行初始化工作,且只操作一次。
- 常量存储。常量值通常直接存放在程序代码内部,这样做是安全的,因为他们永远不会被 改变。
- 非RAM存储。比如流对象和持久化对象,发送给另一台机器或者存放在磁盘上。
如果类的某个成员是基本数据类型,即使没有进行初始化,Java也会确保他获得一个默认值。
boolean false
char null
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
char null
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
3、在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那些变量仍会在调用任何方法之前得到初始化—— 甚至在构建器调用之前。
static 初始化只有在必要的时候才会进行。如果不创建一个 Table 对象,而且永远都不引用 Table.b1 或Table.b2,那么 static Bowl b1 和 b2 永远都不会创建。然而,只有在创建了第一个 Table 对象之后(或者发生了第一次 static 访问),它们才会创建。在那以后, static 对象不会重新初始化。
初始化的顺序是首先 static(如果它们尚未由前一次对象创建过程初始化),接着是非static 对象。
类的加载机制:1、虚拟机在首次加载Java类时,先在栈上为变量分配空间
2、接着将静态块、静态变量、静态方法 加载进静态方法区中
3、接着为变量在堆中开辟一个空间。
4、加载非静态块、非静态方法、非静态变量并初始化工作。
5、构造器工作,如果有父类,则父类按上述流程保证被加载。
6、将引用覆值给变量。
4、类的设计中为什么要控制对成员的访问?
1、防止用户接触那些他们不应碰的工具。对于数据类型的内部机制,那些工具是必需的。但它们并不属于用户接口的一部分,用户不必用它来解决自己的特定问题。比如private的方法,有些是不希望被用户使用的。
2、允许类设计者改变类的内部工作机制,同时不必担心它会对客户程序员产生什么影响。
3、防止类的成员被用户随意修改,比如用户通过 类名.成员名 就可以改变类的属性值。
5、为什么优先使用组合而非继承?
1、继承破坏封装性。基类的很多内部细节都是对派生类可见的,因此这种复用是“白箱复用”;
2、如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性;
3、组成不会强迫我们的程序设计进入继承的分级结构中。同时,组成显得更加灵活,因为可以动态选择一种类型(以及行为),而继承要求在编译期间准确地知道一种类型。因此降低了应用的灵活性。
6、一条常规的设计准则是:用继承表达行为间的差异,并用成员变量表达状态的变化。
7、为什么要向上转型?
好处:向上转型主要是为了用变量来接收不同的子类对象,调用方法的时候传参父类对象,可以调用子类中不同的重写方法,实现不同的效果。
坏处:屏蔽了子类中新增的变量和方法。
8、static关键字 强调他们只有一个,而final 关键字强调他们是一个常数。不能由于某样东西的属性是final,就认定他的值在编译的时候已经知道,其实final的值是进行类对象初始化的时候确定的。
9、final 关键字 修饰方法 的好处?
1、为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
2、将一个方法设成 final 后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个 final 方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。 Java 编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个 final 方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
10、为什么要把方法定义成抽象或者定义接口?
1、它能为不同的子类型或者实现类作出不同的表示。它为我们建立了一种基本形式,使我们能定义在所有衍生类里“通用”的一些东西。比如 子类继承了父类,虽然如果方法名与父类相同,但自变量或参数不同,就会出现重载现象,那或许并非我们所愿意的。所以好一点的方法,就是在父类或者接口中建立一种标准。接口定义了类与类之间的协议。
2、还有就是父类中的方法实现其实很冗余,基本用不到
11、接口也包含了基本数据类型的数据成员,但他们都默认是static 和 final,必须获得初始化(所以可以借助此性质定义需要的“枚举值”,通过接口名.变量名来访问)。接口只提供一种形式,并不提供实施的细节。
12、接口当作方法的形参的时候,传入的不仅可以是接口的实现类,也可以是实现类的子类。
13、利用继承技术,可方便地为一个接口添加新的方法声明,也可以将几个接口合并成一个新接口。在这两种情况下,最终都得到一个新接口。接口的继承实际上是一个接口功能增加的过程,有些应用只需要简单的接口。通过继承,可以在简单的接口上得到适合自己需要的复杂接口。
14、可以在一个类的内部定义新的类,称为内部类。内部类是一种非常有用的特性,因为他允许你把一些逻辑相关的类组织在一起,并控制在内部类的可视性。不仅这样,当你将内部类向上转型为其基类,尤其是转型为其一个接口的时候,它就有了用武之地。
15、如果想生成内部类(public)的一个对象,必须将那个对象的类型设为"外部类名.内部类名"。
Parcel11.Contents c = p.new Contents();
如果内部类是private或者protected的 时候又该怎么办呢?那就只能提供一个public的get()方法去得到这个内部类,尤其是向上转型或者实现接口的时候特别有用。
16、为什么需要定义内部类?
1、我们准备实现某种形式的接口,使自己能够创建和返回一个引用。
2、要解决一个复杂的问题,并希望创建一个类,用来辅助自己的程序方案。同时不愿意把它公开。
3、实现多重继承,由于可以实现多个接口,但只能继承一个类,如果想要同时继承两个类药怎么办呢?就可以使用内部类来实现。
如果我只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?
如果这能满足你的需求,那么你就应该这么做。
那么内部类实现一个接口与外围类实现一个接口有什么区别呢?
你不是总能享用到接口带来的方便,有时候你需要与接口的实现进行交互,每个内部类都能独立的继承一个接口的实现,所以无论外围类是否已经继承了某个接口。对于内部类都没有影响、
17、局部内部类:在方法的作用域中创建一个完整的类,一般表现为实现接口或向上转型,返回该接口或基类的引用。
嵌套类:如果你不需要内部类对象与其外围对象之间的联系,那你可以将内部类声明为static。
18、匿名内部类
1、如果是接口,相当于在内部返回了一个接口的实现类,并且实现方式是在类的内部进行的;
2、如果是普通类,匿名类相当于继承了父类,是一个子类,并可以重写父类的方法。
3、需要特别注意的是,匿名类没有名字,不能拥有一个构造器。如果想为匿名类初始化,让匿名类获得一个初始化值,或者说,想使用匿名内部类外部的一个对象,则编译器要求外部对象为final属性,否则在运行期间会报错。
public class Parcel8 { // Argument must be final to use inside // anonymous inner class: public Destination dest(final String dest) {//这个dest必须设置为final return new Destination() { private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel8 p = new Parcel8(); Destination d = p.dest("Tanzania"); } }
19、接口可以嵌套子在类或其他接口中。并且接口可以在类中设置成private。
20、一个内部类被嵌套多少层并不重要——他能透明的访问它所嵌入的外围类的所有成员。
21、内部类的继承:如果你想继承一个内部类,由于内部类依赖于外部类的实例,所以你必须要调用外部类的构造器才能编译成功。
public class C { public class D{ } } public class E extends C.D { public E(C c){ c.super(); } }
22、内部类的重载:如果子类中定义了一个类名和父类一样的内部类,实际上,内部类并没有发生什么特别的变化。两个内部类是完全独立的实体。
23、为什么普通内部类的的成员不能设置成 static ?
首先,尽管是内部类,他也是外部类的一个成员,是类实例的一部分。而静态的变量是类的一部分和实例无关 你若声明一个成员内部类 让他成为主类的实例一部分 然后又想在内部类声明和实例无关的静态的东西 你让JVM情何以堪啊 。
24、局部内部类和匿名内部类有什么区别?
实际上,它们有一样的行为和能力。唯一的区别就是局部内部类具有一个已命名的构造器。而匿名类只能用于实例初始化。
25、方法调用绑定
前期绑定:面向过程的语言中不需要选择就默认的绑定方式。
后期绑定:也叫动态绑定,编译器不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。Java中除了static 和 final的方法,其他所有的方法都是后期绑定。
26、一个类继承了父类并实现了接口,如果接口中的方法名和父类的方法名相同则默认不实现接口中的方法,而采用父类的方法实现。
27、实际上,内部类的一个实例初始化模块就是一个匿名内部类的构造器。初次之外,内部类拥有对封装类所有元素的访问权限。也就是说,我们可以在封装类中新建一个辅助类或实现一个工具类的接口帮助我们完成对封装类的一些增删查改的操作。
28、设计构造器时一个特别有效的规则是:用尽可能简单的方法使对象进入就绪状态;如果可能,避免调用任何方法。在构造器内唯一能够安全调用的是在基础类中具有final 属性的那些方法(也适用于 private方法,它们自动具有 final 属性)。这里有一个特殊的情景就是,子类实例化必须先调用父类的构造器,如果父类的构造器中调用了子类的重写方法怎么办?程序不会报错,子类方法中的成员变量采用成员默认值,因为,子类还没有实例化。
29、泛型类或者泛型方法中,不接受 8 种基本数据类型。
三、集合
1、为容纳一组对象,最适宜的选择应当是数组,但是数组也有他明显的缺点,即容量有限。而且假如容纳的是一系列基本数据类型,更是必须采用数组。集合实际容纳的类型为Object的引用,这当然包括一切的java对象,因为Object是一切对象的基类。当然并不包括基本数据类型,因为它们并不是从“任何东西”继承来的。
2、Iterator接口的remove方法将会删除上次调用next方法时返回的元素。集合也有一个remove的方法,适用于List 和 Set ,boolean remove(Object)。
![](https://yqfile.alicdn.com/img_67e7fa5c1db297febf159753a1998ca3.png?x-oss-process=image/resize,w_1400/format,webp)
3、Listiterator和Iterator 的区别?
一.相同点
都是迭代器,当需要对集合中元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用。
二.不同点
1.使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。
2.ListIterator有add方法,可以向List中添加对象,而Iterator不能。
3.ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。
4.ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
5.都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。
4、Set集合都不能保存重复的数据,即使是TreeSet也只是对不重复的数据进行排序罢了。对于TreeSet或者HashSet来说,在进行add()以及contain()操作时,HashSet显然要比ArraySet出色的多,而且性能明显与元素的多寡关系不大。一般在元素比较少,而且要进来遍历查询的时候,TreeSet会稍微快一些。
5、HashSet 与TreeSet和LinkedHashSet的区别?
HaseSet:
1、不能保证元素的排列顺序,顺序有可能发生变化。
2、不是同步的。
3、集合元素可以为null,但只能放入一个null。
4、当向HashSet集合中存入一个元素时,会首先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。如果该HashSet集合中已经有这个对象的hashCode值,则调用该对象的equal()方法,返回true,就不插入,返回false,就另外找一个hashCode存储。
TreeSet:
TreeSet底层是一个红黑二叉树结构,其中元是不可重复的。TreeSet类型是J2SE中唯一可实现自动排序的类型。TreeSet支持两种排序方式,自然排序和定制排序。向treeSet中加入的应该是同一个类的对象。向TreeSet插入基本数据类型时,Java已经定义好了CompareTo(Object obj)方法,采用自然排序,默认升序。如果放入的是自定义的对象类需要实现Comparable接口并重写compareTo()方法,相等返回0,大于返回正数,小于返回负数。同时treeSet的插入比较也是通过compareTo()或者equal()方法来比较插入的。
LinkedHashSet:
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素,也就是说它是按插入时的顺序排序的。
6、Set的性能方面:HashSet的性能总是比TreeSet的好(特别是最常用的添加和查询元素操作)。
TreeSet存在的唯一原因是:他可以维护元素的排序状态。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好。
7、HashMap、TreeMap 的区别?
1、HashMap是基于散列表实现的,时间复杂度能达到O(1),是无序的。
2、TreeMap基于红黑树(一种自平衡二叉查找书)实现的,时间复杂度平均能达到O(log n),是有序的。
8、HashMap、HashTable的区别?
1、HashMap几乎可以等价于HashTable,都实现了Map接口。
2、HashMap是非同步的,线程不安全的。而HashTable是同步的,线程安全的。当然我们也可以手动进行HashMap同步(Collections.synchronizeMap(hashMap))
3、HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
4、哈希值的使用不同,HashTable直接使用对象的hashCode。
9、LinkedHashMap和hashMap的区别?
HashMap 是无序的,HashMap 在 put 的时候是根据 key 的 hashcode 进行 hash 然后放入对应的地方。所以在按照一定顺序 put 进 HashMap 中,然后遍历出 HashMap 的顺序跟 put 的顺序不同(除非在 put 的时候 key 已经按照 hashcode 排序号了,这种几率非常小)
LinkedHashMap 和hashMap最大的不同在于,LinkedHashMap维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。怎么理解呢?就是LinkedHashMap是实现有序的HashMap,他在根据hashcode进行存储的同时,维护者一张记录插入顺序的表。
10、LinkedHashMap:为了提高速度,LinkedHashMap散列化所以的元素,但是在遍历”键值对“时,却有以元素的插入顺序返回”键值对“,即迭代结果显示的是你插入的顺序。此外可以在构造器中设定LinkedHashMap,使之采用基于访问的LRU(最近最少使用)算法,于是没有被访问过的元素就会出现在队列的前面。
LinkedHashMap linkedHashMap=new LinkedHashMap(16,0.75f,true); public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } 参数说明: 1. initialCapacity 初始容量大小,使用无参构造方法时,此值默认是16 2. loadFactor 加载因子,使用无参构造方法时,此值默认是 0.75f 3. accessOrder false: 基于插入顺序 true: 基于访问顺序
11、WeakHashMap
WeakHashMap 用弱引用承载映射键,这使得应用程序不再使用键对象时它们可以被垃圾收集,get() 实现可以根据 WeakReference.get() 是否返回 null 来区分死的映射和活的映射。但是这只是防止 Map 的内存消耗在应用程序的生命周期中不断增加所需要做的工作的一半,还需要做一些工作以便在键对象被收集后从 Map 中删除死项。否则,Map 会充满对应于死键的项。虽然这对于应用程序是不可见的,但是它仍然会造成应用程序耗尽内存。
引用队列是垃圾收集器向应用程序返回关于对象生命周期的信息的主要方法。弱引用有个构造函数取引用队列作为参数。如果用关联的引用队列创建弱引用,在弱引用对象成为 GC 候选对象时,这个引用对象就在引用清除后加入到引用队列中(具体参考上文软引用示例)。
WeakHashMap 有一个名为 expungeStaleEntries() 的私有方法,大多数 Map 操作中会调用它,它去掉引用队列中所有失效的引用,并删除关联的映射。
12、Map的性能方面来:对于Map的put()和get(),hashMap和hashTable都要优于TreeMap,而hashTable要多花一些开销在线程同步上,所以hashMap的性能又稍优于hashTable。
而对于Map的遍历来说,TreeMap的性能要稍优于hashMap和hashTable。
如果需要创建大量Map,TreeMap、hashMap的性能要优于hashTable。
综合考虑,平常使用hashMap()就已经能够满足大部分需求了。
13、对于Arrays和Collections来说,若在执行一次 binarySearch()之前不调用 sort(),便会发生不可预测的行为,其中甚至包括无限循环。排序遵守的是字典排序,亦即大写字母在字符集中位于小写字母的前面。
14、Comparator和Comparable的区别?
集合或者数组要实现自动排序功能,比如TreeSet、TreeMap、Arrays.sort()、Collections.sort(),有两种方式。
第一种:比较的类型内部实现了Comparable接口重写了compareTo()方法。
第二种:自己新建了一个比较类实现了Comparator接口,重写了compare方法。
15、使 Collection 或 Map 不可修改?
Collections.unmodifiableCollection(c);
Collections.unmodifiableList(a);
Collections.unmodifiableSet(s);
Collections.unmodifiableMap(m);
16、使Collection 或 Map 的同步?
Collections.synchronizedCollection(new ArrayList());
Collections.synchronizedList(new ArrayList());
Collections.synchronizedSet(new HashSet());
Collections.synchronizedMap(new HashMap());
17、
填充数组:Java中标准类库Arrays也有fill()的方法,但是他作用有限。只能用一个值填充各个位置,对于保存对象的数组,就是复制同一个引用进行填充。
复制数组:System.arraycopy(源数组,源数组下标开始,目标数组,目标数组下标开始,长度)。当然,对数组的任何越界操作都会导致异常。
填充集合:Collections.fill(list,"Hello")此方法的作用有限,只能替换已经在List中存在的元素,并不能增加新元素。
18、方法keySet()方法由Map的“键”组成的Set。类似的方法还有values,他返回一个Collection,包含Map的所有值("键“必须是唯一的,而“值可以重复”),由于Collection背后是由Map支持的,所以对Collection的任何改动都会影响到Map。
19、新程序中不应该使用过时的Vector、HashTable和Stack。
四、异常
1、异常情形是指:阻止当前方法或作用域继续执行的问题。异常情形和普通问题不一样,普通问题指在编写代码的时候就已经提示的错误。当异常出现的时候,将使用new在堆上创建异常对象,当前的执行路径被终止,并且在当前环境中弹出异常对象的引用,由异常处理机制接管程序。
2、异常处理的好处?
异常处理是Java中唯一正式的错误报告机制,并且通过编译器强制执行,它能使错误代码变得更有条理,而且把“描述做什么事的代码”和“出了问题怎么办的代码”相分离。异常处理是被设计用来处理一些烦人的运行期错误,这些错误是由你的代码控制能力之外的因素导致的。
3、异常处理理论上有两种基本模型。一种是终止模型: 一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。一种是恢复模型:异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。(现在基本都使用终止模型)
4、异常类的信息方法
String getMessage(); 获取详细信息
String getLocalizedMessage(); 获取本地语言表述的详细信息
String toString() ; 返回简单描述,要是有详细信息的话,也会包括在内
void printStackTrace(java.io.printWriter); 打印Throwable 和 Throwable的调用栈轨迹。调用栈显示了"把你带到异常抛出地点"的方法调用序列、
Throwable fillInStackTrace(); 用于在Throwable对象的内部记录栈框架的当前状态
5、异常链:你如果想要在捕获一个异常然后抛出另一个异常,并且希望把原始异常的信息保存下来,这就称为"异常链"。在Throwable的子类中,只有三种基本的异常类提供了带cause的构造器。他们是Error、Exception以及RuntimeException。如果你要把其他类型的异常链接起来,你应该用initCause()方法而不是构造器。
public Throwable initCause(Throwable cause)
将此 throwable 的 cause 初始化为指定值。(该 Cause 是导致抛出此 throwable 的throwable。)
6、自定义异常类:需要继承Exception类。Exception继承自Throwable,Throwable可以分为两种类型:Error用来表示你不用关心的编译期和系统错误;Exception是可以被抛出的基本类型。
7、finally:对于一些代码,你可能希望无论try块中的异常是否抛出,他们都能得到执行。通常适用于内存回收之外的情况。
五、其他
1、 Java包装类只能做一件事,就是将其初始化为某个值,然后读取这个值。也就是说,一旦创建了包装类的对象,就没有办法改变他的值。
2、怎么理解TCP是可靠的协议,而HTTP基于IP/TCP协议,却是不可靠的?
1、首先IP 是网络层的协议,确认source和target的IP地址。
2、TCP是传输层的传输协议,传输基于三次握手过程提供可靠的传输协议。
3、HTTP是应用层协议,它只负责把服务器的资源反馈到客户端。
4、怎么理解三个协议之间的合作呢,IP提供源地址和目标地址,TCP建立可靠的通道,最后由HTTP来负责传输内容,如果一个会话需要多个层级的连接,会造成延迟大、资源浪费,所以如果一个层级已经提供了可靠连接,则其它层级完全没有必要连接,只需要交流信息即可,比如这里的HTTP。