精华内容
下载资源
问答
  • https://zhidao.baidu.com/question/312033233.html?qbl=relate_question_5&word=java%D6%D0%B5%C4%B1%E4%C1%BF%C3%FB%BF%C9%D2%D4%B6%AF%CC%AC%C2%F0
    展开全文
  • Java集合面试题

    万次阅读 多人点赞 2019-06-25 14:46:19
    一个集合代表一组对象,这些对象即为它的元素。Java 平台不提供这个接口任何直接的实现。 Set ,是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。 List ,是一个有序...

    Java集合面试题

    Java 集合框架的基础接口有哪些?

    • Collection ,为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java 平台不提供这个接口任何直接的实现。
      • Set ,是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。
      • List ,是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List 更像长度动态变换的数组。
    • Map ,是一个将 key 映射到 value 的对象。一个 Map 不能包含重复的 key,每个 key 最多只能映射一个 value 。
    • 一些其它的接口有 Queue、Dequeue、SortedSet、SortedMap 和 ListIterator 。

    ? 为何 Collection 不从 Cloneable 和 Serializable 接口继承?

    Collection 接口指定一组对象,对象即为它的元素。

    • 如何维护这些元素由 Collection 的具体实现决定。例如,一些如 List 的 Collection 实现允许重复的元素,而其它的如 Set 就不允许。
    • 很多 Collection 实现有一个公有的 clone 方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为 Collection 是一个抽象表现,重要的是实现。

    当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制,特定的实现应该决定它是否可以被克隆和序列化

    为何 Map 接口不继承 Collection 接口?

    尽管 Map 接口和它的实现也是集合框架的一部分,但 Map 不是集合,集合也不是 Map。因此,Map 继承 Collection 毫无意义,反之亦然。

    如果 Map 继承 Collection 接口,那么元素去哪儿?Map 包含 key-value 对,它提供抽取 key 或 value 列表集合( Collection )的方法,但是它不适合“一组对象”规范。

    ? 为何 Map 接口不继承 Collection 接口?

    尽管 Map 接口和它的实现也是集合框架的一部分,但 Map 不是集合,集合也不是 Map。因此,Map 继承 Collection 毫无意义,反之亦然。

    如果 Map 继承 Collection 接口,那么元素去哪儿?Map 包含 key-value 对,它提供抽取 key 或 value 列表集合( Collection )的方法,但是它不适合“一组对象”规范。

    ? Collection 和 Collections 的区别?

    • Collection ,是集合类的上级接口,继承与他的接口主要有 Set 和List 。
    • Collections ,是针对集合类的一个工具类,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

    ? 集合框架里实现的通用算法有哪些?

    Java 集合框架提供常用的算法实现,比如排序和搜索。

    Collections类包含这些方法实现。大部分算法是操作 List 的,但一部分对所有类型的集合都是可用的。部分算法有排序、搜索、混编、最大最小值。

    ? 集合框架底层数据结构总结

    1)List

    • ArrayList :Object 数组。
    • Vector :Object 数组。
    • LinkedList :双向链表(JDK6 之前为循环链表,JDK7 取消了循环)。

    2)Map

    • HashMap :
      • JDK8 之前,HashMap 由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。
      • JDK8 以后,在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8 )时,将链表转化为红黑树,以减少搜索时间。
    • LinkedHashMap :LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》
    • Hashtable :数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。
    • TreeMap :红黑树(自平衡的排序二叉树)。

    3)Set

    • HashSet :无序,唯一,基于 HashMap 实现的,底层采用 HashMap 来保存元素。
    • LinkedHashSet :LinkedHashSet 继承自 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的。
    • TreeSet :有序,唯一,红黑树(自平衡的排序二叉树)。

    什么是迭代器(Iterator)?

    Iterator 接口,提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的 #remove(Object Obj) 方法删除,可以通过迭代器的 #remove() 方法删除。

    ? Iterator 和 ListIterator 的区别是什么?

    • Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List。
    • Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。
    • ListIterator 实现了 Iterator 接口,并包含其他的功能。比如:增加元素,替换元素,获取前一个和后一个元素的索引等等。

    ? 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

    差别在于 ConcurrentModification 异常:

    • 快速失败:当你在迭代一个集合的时候,如果有另一个线程正在修改你正在访问的那个集合时,就会抛出一个 ConcurrentModification 异常。 在 java.util 包下的都是快速失败。
    • 安全失败:你在迭代的时候会去底层集合做一个拷贝,所以你在修改上层集合的时候是不会受影响的,不会抛出 ConcurrentModification 异常。在 java.util.concurrent 包下的全是安全失败的。

    ? 如何删除 List 中的某个元素?

    有两种方式,分别如下:

    • 方式一,使用 Iterator ,顺序向下,如果找到元素,则使用 remove 方法进行移除。
    • 方式二,倒序遍历 List ,如果找到元素,则使用 remove 方法进行移除。

    ? Enumeration 和 Iterator 接口有什么不同?

    • Enumeration 跟 Iterator 相比较快两倍,而且占用更少的内存。
    • 但是,Iterator 相对于 Enumeration 更安全,因为其他线程不能修改当前迭代器遍历的集合对象。同时,Iterators 允许调用者从底层集合中移除元素,这些 Enumerations 都没法完成。

    对于很多胖友,可能并未使用过 Enumeration 类,所以可以看看 《Java Enumeration 接口》 文章。

    ? 为何 Iterator 接口没有具体的实现?

    Iterator 接口,定义了遍历集合的方法,但它的实现则是集合实现类的责任。每个能够返回用于遍历的 Iterator 的集合类都有它自己的 Iterator 实现内部类。

    这就允许集合类去选择迭代器是 fail-fast 还是 fail-safe 的。比如,ArrayList 迭代器是 fail-fast 的,而 CopyOnWriteArrayList 迭代器是 fail-safe 的。

    Comparable 和 Comparator 的区别?

    • Comparable 接口,在 java.lang 包下,用于当前对象和其它对象的比较,所以它有一个 #compareTo(Object obj) 方法用来排序,该方法只有一个参数。
    • Comparator 接口,在 java.util 包下,用于传入的两个对象的比较,所以它有一个 #compare(Object obj1, Object obj2) 方法用来排序,该方法有两个参数。

    详细的,可以看看 《Java 自定义比较器》 文章,重点是如何自己实现 Comparable 和 Comparator 的方法。

    ? compareTo 方法的返回值表示的意思?

    • 大于 0 ,表示对象大于参数对象。
    • 小于 0 ,表示对象小于参数对象
    • 等于 0 ,表示两者相等。

    ? 如何对 Object 的 List 排序?

    • Object[] 数组进行排序时,我们可以用 Arrays#sort(...) 方法。
    • List<Object> 数组进行排序时,我们可以用 Collections#sort(...) 方法。

    有哪些关于 Java 集合框架的最佳实践?

    • 基于应用的需求来选择使用正确类型的集合,这对性能来说是非常重要的。例如,如果元素的大小是固定的,并且知道优先级,我们将会使用一个 Array ,而不是 ArrayList 。
    • 一些集合类允许我们指定他们的初始容量。因此,如果我们知道存储数据的大概数值,就可以避免重散列或者大小的调整。
    • 总是使用泛型来保证类型安全,可靠性和健壮性。同时,使用泛型还可以避免运行时的 ClassCastException 异常。
    • 在 Map 中使用 JDK 提供的不可变类作为一个 key,这样可以避免 hashcode 的实现和我们自定义类的 equals 方法。
    • 应该依照接口而不是实现来编程。
    • 返回零长度的集合或者数组,而不是返回一个 null ,这样可以防止底层集合是空的。

    区别

    List 和 Set 区别?

    List,Set 都是继承自 Collection 接口。

    • List 特点:元素有放入顺序,元素可重复。
    • Set 特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉。

    注意:元素虽然无放入顺序,但是元素在 Set 中的位置是有该元素的 hashcode 决定的,其位置其实是固定的。

    另外 List 支持 for 循环,也就是通过下标来遍历,也可以用迭代器,但是 Set 只能用迭代,因为他无序,无法用下标来取得想要的值。

    Set 和 List 对比:

    • Set:检索元素效率高,删除和插入效率低,插入和删除不会引起元素位置改变。
    • List:和数组类似,List 可以动态增长,查找元素效率低,插入删除元素效率,因为可能会引起其他元素位置改变。

    List 和 Map 区别?

    • List 是对象集合,允许对象重复。
    • Map 是键值对的集合,不允许 key 重复。

    Array 和 ArrayList 有何区别?什么时候更适合用 Array?

    • Array 可以容纳基本类型和对象,而 ArrayList 只能容纳对象。
    • Array 是指定大小的,而 ArrayList 大小是固定的,可自动扩容。
    • Array 没有提供 ArrayList 那么多功能,比如 addAll、removeAll 和 iterator 等。

    尽管 ArrayList 明显是更好的选择,但也有些时候 Array 比较好用,比如下面的三种情况。

    • 1、如果列表的大小已经指定,大部分情况下是存储和遍历它们
    • 2、对于遍历基本数据类型,尽管 Collections 使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。
    • 3、如果你要使用多维数组,使用 [][] 比 List 会方便。

    ArrayList 与 LinkedList 区别?

    ? ArrayList

    • 优点:ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
    • 缺点:因为地址连续,ArrayList 要移动数据,所以插入和删除操作效率比较低。

    ? LinkedList

    • 优点:LinkedList 基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。对于新增和删除操作 add 和 remove ,LinedList 比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。
    • 缺点:因为 LinkedList 要移动指针,所以查询操作性能比较低。

    ? 适用场景分析

    • 当需要对数据进行对随机访问的情况下,选用 ArrayList 。

    • 当需要对数据进行多次增加删除修改时,采用 LinkedList 。

      如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList 。

    • 当然,绝大数业务的场景下,使用 ArrayList 就够了。主要是,注意好避免 ArrayList 的扩容,以及非顺序的插入。

    ? ArrayList 是如何扩容的?

    直接看 《ArrayList 动态扩容详解》 文章,很详细。主要结论如下:

    • 如果通过无参构造的话,初始数组容量为 0 ,当真正对数组进行添加时,才真正分配容量。每次按照 1.5 倍(位运算)的比率通过 copeOf 的方式扩容。
    • 在 JKD6 中实现是,如果通过无参构造的话,初始数组容量为10,每次通过 copeOf 的方式扩容后容量为原来的 1.5 倍。

    重点是 1.5 倍扩容,这是和 HashMap 2 倍扩容不同的地方。

    ArrayList 集合加入 1 万条数据,应该怎么提高效率?

    ArrayList 的默认初始容量为 10 ,要插入大量数据的时候需要不断扩容,而扩容是非常影响性能的。因此,现在明确了 10 万条数据了,我们可以直接在初始化的时候就设置 ArrayList 的容量!

    这样就可以提高效率了~

    ArrayList 与 Vector 区别?

    ArrayList 和 Vector 都是用数组实现的,主要有这么三个区别:

    • 1、Vector 是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果,而 ArrayList 不是。这个可以从源码中看出,Vector 类中的方法很多有 synchronized 进行修饰,这样就导致了 Vector 在效率上无法与 ArrayList 相比。

      Vector 是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。

    • 2、两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同。

    • 3、Vector 可以设置增长因子,而 ArrayList 不可以。

    适用场景分析:

    • 1、Vector 是线程同步的,所以它也是线程安全的,而 ArrayList 是线程无需同步的,是不安全的。如果不考虑到线程的安全因素,一般用 ArrayList 效率比较高。

      实际场景下,如果需要多线程访问安全的数组,使用 CopyOnWriteArrayList 。

    • 2、如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用 Vector 有一定的优势。

      这种情况下,使用 LinkedList 更合适。

    HashMap 和 Hashtable 的区别?

    Hashtable 是在 Java 1.0 的时候创建的,而集合的统一规范命名是在后来的 Java2.0 开始约定的,而当时其他一部分集合类的发布构成了新的集合框架。

    • Hashtable 继承 Dictionary ,HashMap 继承的是 Java2 出现的 Map 接口。
    • 2、HashMap 去掉了 Hashtable 的 contains 方法,但是加上了 containsValue 和 containsKey 方法。
    • 3、HashMap 允许空键值,而 Hashtable 不允许。
    • 【重点】4、HashTable 是同步的,而 HashMap 是非同步的,效率上比 HashTable 要高。也因此,HashMap 更适合于单线程环境,而 HashTable 适合于多线程环境。
    • 5、HashMap 的迭代器(Iterator)是 fail-fast 迭代器,HashTable的 enumerator 迭代器不是 fail-fast 的。
    • 6、HashTable 中数组默认大小是 11 ,扩容方法是 old * 2 + 1 ,HashMap 默认大小是 16 ,扩容每次为 2 的指数大小。

    一般现在不建议用 HashTable 。主要原因是两点:

    • 一是,HashTable 是遗留类,内部实现很多没优化和冗余。
    • 二是,即使在多线程环境下,现在也有同步的 ConcurrentHashMap 替代,没有必要因为是多线程而用 Hashtable 。

    ? Hashtable 的 #size() 方法中明明只有一条语句 “return count;” ,为什么还要做同步?

    同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访问。所以,这样就有问题了,可能线程 A 在执行 Hashtable 的 put 方法添加数据,线程 B 则可以正常调用 #size() 方法读取 Hashtable 中当前元素的个数,那读取到的值可能不是最新的,可能线程 A 添加了完了数据,但是没有对 count++ ,线程 B 就已经读取 count 了,那么对于线程 B 来说读取到的 count 一定是不准确的。

    而给 #size() 方法加了同步之后,意味着线程 B 调用 #size() 方法只有在线程 A 调用 put 方法完毕之后才可以调用,这样就保证了线程安全性

    HashSet 和 HashMap 的区别?

    • Set 是线性结构,值不能重复。HashSet 是 Set 的 hash 实现,HashSet 中值不能重复是用 HashMap 的 key 来实现的。

    • Map 是键值对映射,可以空键空值。HashMap 是 Map 的 hash 实现,key 的唯一性是通过 key 值 hashcode 的唯一来确定,value 值是则是链表结构。

      因为不同的 key 值,可能有相同的 hashcode ,所以 value 值需要是链表结构。

    他们的共同点都是 hash 算法实现的唯一性,他们都不能持有基本类型,只能持有对象。

    为了更好的性能,Netty 自己实现了 key 为基本类型的 HashMap ,例如 IntObjectHashMap

    HashSet 和 TreeSet 的区别?

    • HashSet 是用一个 hash 表来实现的,因此,它的元素是无序的。添加,删除和 HashSet 包括的方法的持续时间复杂度是 O(1)
    • TreeSet 是用一个树形结构实现的,因此,它是有序的。添加,删除和 TreeSet 包含的方法的持续时间复杂度是 O(logn)

    ? 如何决定选用 HashMap 还是 TreeMap?

    • 对于在 Map 中插入、删除和定位元素这类操作,HashMap 是最好的选择。
    • 然而,假如你需要对一个有序的 key 集合进行遍历, TreeMap 是更好的选择。

    基于你的 collection 的大小,也许向 HashMap 中添加元素会更快,再将 HashMap 换为 TreeMap 进行有序 key 的遍历。

    HashMap 和 ConcurrentHashMap 的区别?

    ConcurrentHashMap 是线程安全的 HashMap 的实现。主要区别如下:

    • 1、ConcurrentHashMap 对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用 lock 锁进行保护,相对 于Hashtable 的 syn 关键字锁的粒度更精细了一些,并发性能更好。而 HashMap 没有锁机制,不是线程安全的。

      JDK8 之后,ConcurrentHashMap 启用了一种全新的方式实现,利用 CAS 算法。

    • 2、HashMap 的键值对允许有 null ,但是 ConCurrentHashMap 都不允许。

    队列和栈是什么,列出它们的区别?

    栈和队列两者都被用来预存储数据。

    • java.util.Queue 是一个接口,它的实现类在Java并发包中。队列允许先进先出(FIFO)检索元素,但并非总是这样。Deque 接口允许从两端检索元素。
    • 栈与队列很相似,但它允许对元素进行后进先出(LIFO)进行检索。
      • Stack 是一个扩展自 Vector 的类,而 Queue 是一个接口。

    原理

    HashMap 的工作原理是什么?

    我们知道在 Java 中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap 也是如此。实际上 HashMap 是一个**“链表散列”**。

    HashMap 是基于 hashing 的原理。

    HashMap 图解

    • 我们使用 #put(key, value) 方法来存储对象到 HashMap 中,使用 get(key) 方法从 HashMap 中获取对象。
    • 当我们给 #put(key, value) 方法传递键和值时,我们先对键调用 #hashCode() 方法,返回的 hashCode 用于找到 bucket 位置来储存 Entry 对象。

    ? 当两个对象的 hashCode 相同会发生什么?

    因为 hashcode 相同,所以它们的 bucket 位置相同,“碰撞”会发生。

    因为 HashMap 使用链表存储对象,这个 Entry(包含有键值对的 Map.Entry 对象)会存储在链表中。

    ? hashCode 和 equals 方法有何重要性?

    HashMap 使用 key 对象的 #hashCode()#equals(Object obj) 方法去决定 key-value 对的索引。当我们试着从 HashMap 中获取值的时候,这些方法也会被用到。

    • 如果这两个方法没有被正确地实现,在这种情况下,两个不同 Key 也许会产生相同的 #hashCode()#equals(Object obj) 输出,HashMap 将会认为它们是相同的,然后覆盖它们,而非把它们存储到不同的地方。

    同样的,所有不允许存储重复数据的集合类都使用 #hashCode()#equals(Object obj) 去查找重复,所以正确实现它们非常重要。#hashCode()#equals(Object obj) 方法的实现,应该遵循以下规则:

    • 如果 o1.equals(o2) ,那么 o1.hashCode() == o2.hashCode() 总是为 true 的。
    • 如果 o1.hashCode() == o2.hashCode() ,并不意味 o1.equals(o2) 会为 true

    ? HashMap 默认容量是多少?

    默认容量都是 16 ,负载因子是 0.75 。就是当 HashMap 填充了 75% 的 busket 是就会扩容,最小的可能性是(16 * 0.75 = 12),一般为原内存的 2 倍。

    ? 有哪些顺序的 HashMap 实现类?

    • LinkedHashMap ,是基于元素进入集合的顺序或者被访问的先后顺序排序。
    • TreeMap ,是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)。

    ? 我们能否使用任何类作为 Map 的 key?

    我们可以使用任何类作为 Map 的 key ,然而在使用它们之前,需要考虑以下几点:

    • 1、如果类重写了 equals 方法,它也应该重写 hashcode 方法。

    • 2、类的所有实例需要遵循与 equals 和 hashcode 相关的规则。

    • 3、如果一个类没有使用 equals ,你不应该在 hashcode 中使用它。

    • 4、用户自定义 key 类的最佳实践是使之为不可变的,这样,hashcode 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashcode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。

      比如,我有一个 类MyKey ,在 HashMap 中使用它。代码如下:

      //传递给MyKey的name参数被用于equals()和hashCode()中
      MyKey key = new MyKey('Pankaj'); //assume hashCode=1234
      myHashMap.put(key, 'Value');
      // 以下的代码会改变key的hashCode()和equals()值
      key.setName('Amit'); //assume new hashCode=7890
      //下面会返回null,因为HashMap会尝试查找存储同样索引的key,而key已被改变了,匹配失败,返回null
      myHashMap.get(new MyKey('Pankaj'));
      
      
      • 那就是为何 String 和 Integer 被作为 HashMap 的 key 大量使用。

    ? HashMap 的长度为什么是 2 的幂次方?

    为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。

    这个算法应该如何设计呢?我们首先可能会想到采用 % 取余的操作来实现。但是,重点来了:

    • 取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash % length == hash & (length - 1) 的前提是 length 是 2 的 n 次方;)。
    • 并且,采用二进制位操作 &,相对于 % 能够提高运算效率,

    这就解释了 HashMap 的长度为什么是 2 的幂次方。

    HashSet 的工作原理是什么?

    HashSet 是构建在 HashMap 之上的 Set hashing 实现类。让我们直接撸下源码,代码如下:

    // HashSet.java
    
    private transient HashMap<E,Object> map;
    
    private static final Object PRESENT = new Object();
    
    
    • map 属性,当我们创建一个 HashMap 对象时,其内部也会创建一个 map 对象。后续 HashSet 所有的操作,实际都是基于这个 map 之上的封装。

    • PRESENT 静态属性,所有 map 中 KEY 对应的值,都是它,避免重复创建。

    • OK ,再来看一眼 add 方法,代码如下:

      // HashSet.java
      
      public boolean add(E e) {
          return map.put(e, PRESENT) == null;
      }
      
      
      • 是不是一目了然。

    ? HashSet 如何检查重复?

    艿艿:正如我们上面看到 HashSet 的实现原理,我们自然可以推导出,HashMap 也是如何检查重复滴。

    如下摘取自 《Head First Java》 第二版:

    当你把对象加入 HashSet 时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较。

    • 如果没有相符的 hashcode ,HashSet会假设对象没有重复出现。
    • 但是如果发现有相同 hashcode 值的对象,这时会调用 equals 方法来检查 hashcode 相等的对象是否真的相同。
      • 如果两者相同,HashSet 就不会让加入操作成功。
      • 如果两者不同,HashSet 就会让加入操作成功

    【详细可以查看java基础系列】

    展开全文
  • javaScript学习笔记(一)js基础

    万次阅读 多人点赞 2018-09-21 10:07:18
    ================================= 公众号 关注一波 (一叶知秋博客) 不定期分享视频资料 一、简介 1、概述: ...JavaScript是目前web开发中不可缺少的脚本语言,js不需要编译即可运行,运行在客户端,...Java...

     

                                          公众号  关注一波  不定期分享视频资料

     

                                                                   

    一、简介

    1、概述:

    JavaScript是目前web开发中不可缺少的脚本语言,js不需要编译即可运行,运行在客户端,需要通过浏览器来解析执行JavaScript代码。

    诞生于1995年,当时的主要目的是验证表单的数据是否合法

    JavaScript本来应该叫Livescript,但是在发布前夕,想搭上当时超热的java顺风车,临时把名字改为了JavaScript。(也就是说js跟java没有关系,当时只是想借助java的名气)。

    js组成部分:

    (1)核心(ECMAScript):这一部分主要是js的基本语法。

    (2)BOM:Brower Object Model(浏览器对象模型),主要是获取浏览器信息或操作浏览器的,例如:浏览器的前进与后退、浏览器弹出提示框、浏览器地址栏输入网址跳转等操作等。

    (3)DOM:Document Object Model(文档对象模型),此处的文档暂且理解为html,html加载到浏览器的内存中,可以使用js的DOM技术对内存中的html节点进行修改,用户从浏览器看到的是js动态修改后的页面。(增删改查)

    2、特点:

    1. 交互性(它可以做的就是信息的动态交互)
    2. 安全性(不允许直接访问本地硬盘)
    3. 跨平台性(只要是可以解析js的浏览器都可以执行,和平台无关)

    3、和Java区别:

    4、作用

    avaScript 被用来改进设计、验证表单、检测浏览器、创建cookies,等等。JavaScript 是因特网上最流行的脚本语言,并且可在所有主要的浏览器中运行,比如: Internet Explorer、 Maxthon、Mozilla、Firefox、Netscape、Chrome和 Opera等。

    在目前学习阶段只要记住最常用的二个:(1)运态修改html及css代码 (2)验证表单

    5、书写位置

    5.1、内嵌式:

    理论上js可以书写在页面的任意位置。

    <script>

    alert("内嵌式")

    </script>

    5.2、外链式:

    首先新建一个文件类型为.js的文件,然后在该文件中写js语句,通过script标签对引入到html页面中。

    <script src="js文件路径地址">这里不能写js语句</script>

    5.3、行内式:

    直接书写在标签身上,是一个简写的事件,所以又称之为事件属性。     onclick单击事件

    <input type="button" value="点我呀!" onclick="alert('点我干啥!^6^');">
    <button onclick="alert('恭喜你,中 500 万.');">点我呀!</button>
    

    6、注释:

    单行注释:		//	注释语句		快捷键ctrl+/
    多行注释:		/* 注释语句 */    快捷键ctrl+shift+/   
    注意:多行注释相互不能嵌套使用,只能在多行注释里面使用单行注释!
    

    7、数据类型:

    Js中的数据类型:
     

    数值型:number(凡是数字都是数值型,不区分整数和小数)
    字符串:string(凡是引号包裹起来的内容全部都是字符串)
    布尔:boolean(true、false)
    对象类型:object(特殊取值null)
    未定义型:undefined
    

    对比java中的数据类型:

    整数:byte short int long
    小数:float double
    字符:char 
    布尔:boolean
    字符串:String

    8、变量:

    8.1、定义:就是存放数据的、内疗可以存储任意数据

    8.2、声明变量:
     

    var 变量名称 = 存储的数据;   	(variable 变量)

    8.3、变量命名规范:

    1.	只能由字母、数字、_(下划线)、$(美元符号)组成。
    2.	不能以数字开头。
    3.	命名中不能出现-(js会理解成减号进行减法的操作),不能和关键字冲突。
    

    js是弱类型语言,不重视类型的定义,但js会根据为变量赋值的情况自定判断该变量是何种类型:

    数值型:var i = 1;	var d = 2.35;
    字符串:var str = "用心学习";
    布尔型:var b = true;
    

    对比Java:java是强类型的语言,注重类型的定义,Java定义类型如下:

    整型:int i = 1;
    浮点型:double d = 2.35;
    字符串:String str = “用心学习”;
    布尔型:boolean b = true;
    

    总结:js中变量的定义。只要加一个var就行。java则使用什么类型的变量就要定义什么类型的。

    9、检测数据类型:

    typeof(value); 或者typeof value;     返回这个变量的类型. 
    说明 : 同一个变量, 可以进行不同类型的数据赋值.
    
    <script type="text/javascript">
        
        var a;
        alert(typeof a);  // undefined
    
        a = 998;
        alert(typeof a); // number
    
        a = "用心学习";
        alert(typeof a); // string
    
        a = true;
        alert(typeof a); // boolean
        
    </script>
    

    10、算术运算符

    +	-	*	/	%	++	--

    注意:

    1.	由于js中的小数和整数都是number类型,不存在类似整数除以整数还是整数的结论。
    2.	字符串和其他的数据使用+号运算,会连接成一个新的字符串。
    3.	字符串使用除了+以外的运算符:如果字符串本身是一个数字,那么会自动转成number进行运算
    ,否则就会返回一个NaN的结果,表示这不是一个数字。NaN:not a number
    
    <script>
    
        alert(1234 / 1000 * 1000); // 1234
      
        var s = "12";
         s -= 10;
        alert(s);  // 2
     
        var s = "aa";
        s -= 10;
        alert(s);  // NaN       Not a Number 不是一个数字
    
        var s = "12";
        s += 10;
        alert(s);       // 1210 
    
    </script>
    

    11、关系(比较)运算符

    >		>=		<		<=  	!=		
    ==	等于(只比较内容)	===	恒等于(比较内容的同时还要比较数据类型)
    注意:关系运算符返回的结果只有两个:true / false
    
    <script>
    
        // 请问1 : 3 > 5, 结果为 ?
        alert(3 > 5);   // false
    
        // 请问2 : “22” == 22  结果为 ?
        alert("22" == 22); // true  (仅仅判断数值)
    
        // 请问3 : “22” === 22  结果为 ?
        alert("22" === 22);  // false  (恒等于, 数值和类型都要相等)
        
    </script>
    

    12、逻辑运算符

    &&	 	与		true&&false		====>false
    || 		或		true||false			====>true
    ! 		非		!true				====>false
    false(理解):false,  0,  null,  undefined 
    true(理解):true, 非0,  非null,  非undefined
    
    针对 && 顺口溜: 找第一个出现的假值. (一假即假)
    针对 || 顺口溜: 找第一个出现的真值. (一真即真)

    演示一:

    <script>
    
        // 短路与 (一假即假)
        // 口诀 : 找第一个为假的值.
    
        // 请问1 :  8 < 7 && 3 < 4, 结果为 ?
        alert(8 < 7 && 3 < 4);  // false
    
        // 请问2 :  -2 && 6 + 6 && null 结果为 ?
        alert(-2 && 6 + 6 && null); // null
    
        // 请问3 :  1 + 1 && 0 && 5  结果为 ?
        alert(1 + 1 && 0 && 5); // 0
    
    </script>
    

    演示二:

    <script>
    
        // 短路或 : 一真即真.
        // 口诀 : 找第一个为真的值.
    
        // 请问1 :  0 || 23 结果为 ?
        alert(0 || 23); // 23
    
        // 请问2 :  0 || false || true  结果为 ?
        alert(0 || false || true); // true
    
        // 请问3 :  null || 10 < 8 || 10 + 10结果为 ?
        alert(null || 10 < 8 || 10 + 10);  // 20
    
        // 请问4 :  null || 10 < 8 || false结果为 ?
        alert(null || 10 < 8 || false); // false
        
    </script>
    

    13、三元运算符:

    条件?表达式1:表达式2
    如果条件为true,返回表达式1的结果
    如果条件为false,返回表达式2的结果
    

    演示:

    <script>
    
        // 请问1 :  3 ? “aaa” : “bbb” 结果为 ?
        alert(3 ? "aaa" : "bbb");       // aaa
    
        // 请问2 :  0 ? “ccc” : “ddd”  结果为 ?
        alert(0 ? "ccc" : "ddd");       // ddd
        
    </script>
    

    14、if条件语句

    这个和Java中if语句一样。

    演示:

    <script>
    
          var score = 59;
    
          if (score >= 90) {
              alert("优秀");
          } else if (score >= 80) {
              alert("良好");
          } else if (score >= 60) {
              alert("及格");
          } else {
              alert("不及格");
          }
    
      </script>
    

    15、switch分支结构

    这个和java中switch结构一样。只是Java中表达式为:常量 整型(去long)、字符、字符串

    演示:

    <script>
    
          var score = 59;
    
          // 需求 : 将需要一个整型数值, 不想要小数点.
          // window 对象的 parseInt 方法.
          score = window.parseInt(score / 10 + "");
          // alert(score);
    
          switch (score) {
              case 10:
              case 9:
                  alert("优秀!");
                  break;
              case 8:
                  alert("良好!");
                  break;
              case 7:
              case 6:
                  alert("及格!");
                  break;
              default:
                  alert("不及格!");
                  break;
          }
    
      </script>
    

    16、循环结构 while、do-while. for;

    while(循环条件){循环体;}
    do{循环体;}while(循环条件);
    for(循环变量赋初值;循环条件;循环变量增值){循环语句;}
    console.log(...); 以日志的形式在控制台输出结果!
    

    演示:

    <script>
    
        // 需求 : 统计 1~100 之间能够被3和7整除的数字个数
    
        var count = 0;
    
        // 1. 遍历 1~100 之间的所有整型数值
        for (var i = 1; i <= 100; i++) {
    
            // 2. 判断
            if (i % 3 == 0 && i % 7 == 0) {
                // alert(i);
                console.log(i);
                // 3. 累加个数
                count++;
            }
        }
    
        // 4. 查看结果
        // alert(count);
        console.log(count);
    
    

    console.log显示如下:

    17、循环嵌套

    演示案例9*9乘法表

     <style>
            table {
                /* 将 table 表格的线变成了细线 */
                border-collapse: collapse;
                /*color: red;*/
                border-color: red;
            }
        </style>
    
        <script>
    
            // 需求 : 九九乘法口诀表
            document.write("<table border='1px solid red' cellspacing='0' cellpadding='8px'>");
            document.write("<caption>九九乘法口诀表</caption>");
            for (var i = 1; i <= 9; i++) {
                document.write("<tr>");
                for (var j = 1; j <= i; j++) {
                    document.write("<td>");
                    document.write(j + "*" + i + "=" + (j*i) + "&nbsp;&nbsp;&nbsp;&nbsp;");
                    document.write("</td>");
                }
                document.write("</tr>");
            }
            document.write("</table>");
    
        </script>
    
    

    18、自定义函数

    函数是命名的独立的语句段,这个语句段可以被当作一个整体来引用和执行:

    格式:

    function 函数名(形式参数){函数体}
    调用函数:函数名(实际参数);
    

    18.1、函数只有被调用后才会执行

    18.2、如果函数需要返回值、直接使用return 返回、不会像java一样要考虑返回值的类型

    <script type="text/javascript">
    
        // 定义一个函数 : function
        function demo2() {
            return 666;
        }
    
        // 调用函数 :
        alert(demo2());
    
    </script>
    

    18.3、如果函数需要传递参数、不需要指定参数的类型、直接使用变量即可

    <script type="text/javascript">
    
        // 定义一个函数 : function
        function demo3(a, b) {
            return a + b;
        }
    
        // 调用函数 :
        alert(demo3(10, 20));//显示30
    
    </script>
    

    18.4、js中出现二个重名的函数名、后者会把前面的覆盖掉

    对比java、java有重载(同名不同参)、重写(同名同参同返回值类型、方法体不一样)

    演示:

    <script type="text/javascript">
    
        // 定义一个函数 : function
        function demo4(a, b) {
            alert("调用1...");
        }
    
       function demo4() {
           alert("调用2...");
       }
    
        demo4(10, 20);
        demo4();
    
    </script>
    

    会显示二次下面的图片:

    19、匿名函数

    匿名函数是没有名字的函数

    function(形式参数){函数体}
    调用方式:将匿名函数赋值给一个变量,通过变量名调用函数
    定义函数并赋值给变量:var fn = function(形式参数){函数体}
    调用函数:fn(实际参数);
    

    演示:

    <script type="text/javascript">
    
        // 匿名函数 : 没有名称的函数
        var func = function(i, u) {
            alert(i + " love " + u);
        }
    
        // 调用函数 :
       func("柳岩", "小白");//显示柳岩love小白
    
    </script>
    

    20、案例-轮播图

    说明1 : script 标签需要放在 body 标签之后.
    
    说明2 : window.setInterval(“字符串函数名称()”, 时间毫秒数);
    
    说明3 : window.setInterval(函数名称, 时间毫秒数);
    
    说明4 : window.setInterval(匿名函数, 时间毫秒数);            推荐使用
    <head>
        <meta charset="UTF-8">
        <title>轮播图</title>
    
        <style>
    
            div {
                width: 80%;
                margin: 50px auto;
            }
    
            img {
                width: 100%;
            }
    
        </style>
    
    </head>
    <body>
        <div class="container">
            <img src="../img/01.jpg" alt="图片">
        </div>
    </body>
    

    实现一:

    <script>
    
        // 需求 : 动态获取页面中的 img 标签, 然后修改 img 标签的 src 属性.
        // 1. 获取 img 标签
        var img = document.getElementById("img");
        // alert(img);
    
        // 定义一个变量
        var count = 1;
    
        // 1.2 定义一个函数
        function changeImageSrc() {
            count++;
            img.src = "../img/0"+count+".jpg";
    
            // 判断
            if (count == 8) {
                count = 0;
            }
        }
    
        // 2. 循环切换图片
        // window.setInterval(函数, 时间毫秒); 在指定的时间毫秒间隔, 不断调用第一个参数传入的函数.
        // 调用方式一 :
        // window.setInterval("changeImageSrc()", 1000);
        // 调用方式二 :
        window.setInterval(changeImageSrc, 1000);
    
    </script>
    

    实现二:

    <script>
    
        // 需求 : 动态获取页面中的 img 标签, 然后修改 img 标签的 src 属性.
        // 1. 获取 img 标签
        var img = document.getElementById("img");
        // alert(img);
    
        // 定义一个变量
        var count = 1;
    
        // 2. 循环切换图片
        // window.setInterval(匿名函数, 时间毫秒); 在指定的时间毫秒间隔, 不断调用第一个参数传入的匿名函数.
        window.setInterval(function() {
            count++;
            img.src = "../img/0"+count+".jpg";
    
            // 判断
            if (count == 8) {
                count = 0;
            }
        }, 1000);
    
    </script>
    

    21、js事件

    21.1、事件概述:

    事件三要素:
    1.	事件源:被监听的html元素(就是这个事件加给谁),就是某个(某些)html标签
    2.	事件类型:某类动作,例如点击事件,移入移除事件,敲击键盘事件等
    3.	执行指令:事件触发后需要执行的代码,一般使用函数进行封装
    语法格式:事件源.事件类型=执行指令
    

    21.2、常用的事件:

    案例:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>事件</title>
    
        <script>
            // 窗口 : window 对象提供了一个事件类型  onload 页面加载完成事件.
            // 事件源 : window    事件类型 : 页面加载完成事件 (onload)  执行指令: 就是赋值的 function 函数.
            window.onload = function () {
    
                // 获取页面的 btn 按钮
                var btn = document.getElementById("btn");
                // alert(btn);
    
                // 给 btn 按钮绑定一个事件 (单击事件 onclick)
                // 事件源 : btn按钮    事件类型 : 单击事件 (onclick)  执行指令: 就是赋值的 function 函数.
                btn.onclick = function () {
                    alert("恭喜你, 中了 500 万!");
                }
            }
    
        </script>
    
    </head>
    <body>
        <button id="btn">按钮</button>
    </body>
    </html>
    

    到此基础结束。

     

    ==============《轮播图图片路径问题》================

     

                                                           =================================

                        公众号  关注一波  (一叶知秋博客)不定期分享视频资料

                                                           

     

     

    展开全文
  • 如何正确的创建和销毁Java对象

    万次阅读 2017-06-15 18:24:16
    Java是一门强大的高级语言。在学习了其基础知识后,我们仍需要理解其深刻的内涵。接下来,我们会以《Effective Java》一书做为Java进阶学习的载体,对Java进行一个系统的、全新的认识。...第一章:创建和销毁对象

    Java是一门强大的高级语言。在学习了其基础知识后,我们仍需要理解其深刻的内涵。接下来,我们会以《Effective Java》一书做为Java进阶学习的载体,对Java进行一个系统的、全新的认识。接下来,就让我们来感受Java高深的内涵吧。


    第一章:创建和销毁对象


    第1条:考虑用静态工厂方法代替构造器

      对于类而言,为了让客户端获取它自身的一个实例,我们最常用的方法就是提供一个公有的构造器了。但是,其实还有一种好方法(它应该在每个程序员的工具箱中占有一席之地),即是类提供一个公有的静态工场方法。(一个返回类的实例的静态方法,并不同于设计模式中的工厂方法模式
    提供静态工厂方法而不是公有构造器有以下几大优势
      
      1. 静态工厂方法有名称而构造器没有自己的名称。
       如果构造器的参数本身没有确切地描述正被返回的对象,那么就会出现客户无法判断所拿到的东西是什么的尴尬局面了。举个栗子:构造器BigInteger(int,int,Random)返回的BigInteger可能为素数,如果用名为BigInteger,probablePrime的静态工厂方法来表示则更为清楚。(1.4发行版最后就增加了这个方法。)
       还有一种情况,就是当需要构建的内容一个构造器不够使用时,我们往往会提供两个构造器,这其实是很不好的方法,因为客户根本就不知道该用哪个构造器,调用错误的构造器时,就会出现数据出错。因此,可以使用静态工厂方法,并且为静态工厂方法选择能够突出其作用的名称。
       
      2.不必多次创建一个新对象
       这样不可变类就可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。举个栗子:Boolean.valueOf(boolean)。它从来不创建对象!
       另外,还有一个小优点。静态工厂方法能够为重复的调用返回相同的对象,可严格控制实例的存在
       
      3.可以返回原返回类型的任何子类型的对象
       使用静态工厂方法时,要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象。客户端永远不需要关心他们从工厂方法中得到的对象的类,只需要关心是某个类的某个子类即可。
       
      4.创建参数化类型实例时,使代码变得更加简洁
       在调用参数化类构造器时,即使类型参数很明显,还是必须声明。
       以创建一个Map类为例:

    Map<String,List<String>> m=new HashMap<String,List<String>>();

       参数少的时候还好,随着类型参数越变越长,这一说明也就变得异常痛苦。而静态工厂方法就可以解决这个问题(类型推导)

    public static <K,V> HashMap<K,V> newInstance(){
            return new HashMap<K,V>();
    }
    //调用
    Map<String,List<String>> m =HashMap.newInstance();
    //Java1.6还没有这种工厂方法,但你可以把这种方法放在你的工具类中。

      静态工厂方法的缺点:
      
      1.类如果不含公有的或者受保护的构造器,就不能被子类化
      
      2.它与其他的静态方法实际没有任何差别
       没有任何差别意味着什么呢?就是说,对于提供了静态工厂方法而不是构造器的类来说,没有像构造器那样在API文档中明确标识出来,因此,想要查明如何实例化一个类是很困难的。那该怎么办呢?其实还是有方法的,你可以通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯即可。

    第2条:遇到多个构造器参数时考虑使用构建器

      上面讲到了静态工厂类,静态工厂类与构造器相比好处还是蛮多的,但是,两者有个共同的局限性:他们都不能很好地拓展到大量的可选参数。
      解决方法:
      
      1、我们一般想到的就是采用重叠构造器模式(可自行百度)

    //演示调用代码
    NutritionFacts cocaCola=new NutritionFacts(240,15,6,7,0,5,6);

      当有许多参数时,重叠构造器模式会让客户端代码很难编写,且难以阅读
      
      
      2、JavaBean模式

    //演示调用代码
    NutritionFacts cocaCola=new NutritionFacts();
    cocaCola.setServingSize(240);
    cocaCola.setServings(8);
    cocaCola.setCalories(100);

      JavaBean模式也有很严重的缺点。因为构造过程被分到了几个调用中,所以构造过程中不能保证JavaBean的一致性。如果使用处于不一致状态的对象,会导致失败,且调试起来十分困难。另外,JavaBean模式阻止了把类做成不可变得可能,所以需要我们再费力气去确保其线程安全。(手动“冻结”对象)但是手动的方法十分笨拙,在实践中很少使用。
      
      幸亏,还有第三种方法。
      3、Builder模式
       原理:不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,设置每个相关的可选参数。最后调用无参的build方法生成不可变对象。弥补了前两种方法的不足。
       代码如下:

    public class NutritionFacts{
        private final int servingSize;
        private final int servings;
        private final int fat;
        public static class Builder{
            //Required parameters
            private final int servingSize;
            private final int servings;
    
    
            private int fat=0;
            public Builder(int servingSize,int servings){
                this.servingSize=servingSize;
                this.servings=servings;
            }
            public Builder fat(int val){
                fat=val;
                return this;
            }
    
            public NutritionFacts builder(){
                return new NutritionFacts(this);
            }
        }
        private NutritionFacts(Builder builder){
            servingSize=builder.servingSize;
            servings=builder.servings;
            fat=builder.fat;
        }
    }
    //实验调用代码
    NutritionFacts cocaCola=new NutritionFacts.Builder(200,8).fat(60).builder();
    //创建成功  

      于构造器相比,builder的优势在于可以有多个可变(varargs)参数。构造器则只能有一个可变参数。又因为builder利用单独的方法来设置每个参数,要多少个就有多少个,要怎么调整就怎么调整。
      总之,如果类的构造器或者静态工厂中具有4个以上参数,设计这种类时就首选Builder模式,特别是当大多数参数都是可选的的时候。
      

    第3条:用私有构造器或者枚举类型强化Singleton属性

       
       Singleton指仅被实例化一次的类。通常代表如窗口管理器或文件系统等本质上唯一的系统组件(使类成为Singleton会使它的客户端测试变得很困难,因为无法给Singleton替换模拟实现,除非它实现一个充当其类型的接口
       Java1.5发行前有两种方法实现Singleton。

    第一种:公有静态成员为final域

    public class Lin{
        public static final Lin INSTANCE =new Lin();//使用final修饰,清楚地表明了这个类是单例
        private Lin(){...}
        public void leaveTheBuilding(){...}
    }

      由于缺少了公有的或者受保护的构造器,所以保证了Lin的全局唯一性:一旦Lin类被实例化,只会存在一个Lin实例,客户端的任何行为都不会改变这一点。但要注意:享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。(若要抵御这种攻击,则可以修改构造器,在要创建第二个实例时抛出异常)

    第二种:公有成员是静态方法

    public class Lin{
        private static final Lin INSTANCE = new Lin();
        private Lin(){...}
        public static Lin getInstance(){return INSTANCE;}
    
        public void leaveTheBuilding(){...}
    }

      对于所有调用都会返回同一个对象引用,所以保证了只有一个Lin实例。
      
      以上两种方法皆是把构造器保持为私有的,并导出公有的静态成员变量,以便允许客户端访问该类的唯一实例

      但如今,以上的公有域方法在性能上不再有优势,因为现在的JVM实现几乎能将静态工厂方法的调用内联化。(是不是很奔溃,讲了那么久博主你居然跟我说上面的方法不再有优势,那有卵用?)别着急~凡是都要知道了它的内涵之后才能真正理解它高效运行的原理嘛。接下来就来介绍实现Singleton的最佳方法了

    第三种:编写一个包含单个元素的枚举类型

    public enum Lin{
        INSTANCE;
    
        public void leaveTheBuilding(){...}
    } 

      无偿地提供了序列化机制,即使是在面对复杂的序列化或者反射机制的时候也能绝对防止多次实例化。

    第4条:通过私有构造器强化不可实例的能力

    public class UtilityClass{
      private UtilityClass(){
           throw new AssertionError();
         }
         ...//Remainder omitted
         

      这种习惯用法有一点副作用,它使得一个类不能被子类化。子类就没有可访问的超类构造器可调用了

    第5条:避免创建不必要对象

      伴侣。最好是一直陪伴的同一个,而不是在寂寞时随便约炮找对象。而对象,也最好是能够重用的,而不是每次需要时就创建一个相同功能的新对象。

      如果对象是不可变的,它就始终可以被重用。

      Don’t do this!

    String s=new String("new");

      这样如果循环的话,会创建无数个不必要的实例

      正确打开方式

    String s="reuse";

      对于已知不会被修改的可变对象也可以进行重用

    class Person{
        private final Date birthDate;//出生就确定了,所以不可修改
    
        private static final Date BOOM_START;//final证明已被当做常量对待
        private static final Date BOOM_END;//同上
    
        static{//初始化时建立,因此之后都是不可变类
          Calender gmtCal=Calender .getInstance(TimeZone.getTimeZone("GMT"));
          gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
          BOOM_START=gmtCal.getTime();
          gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
          BOOM_END=gmtCal.getTime();
        }
    
        public boolean isBabyBoomer(){
            return  birthDate.compareTo(BOOM_START) >= 0 &&
                    birthDate.compareTo(BOOM_END) < 0 ;
        }
    }

      以上的这种方法,在多次调用时的速度会大大提高(因为已经是拿创建好的东西来重用)。

      通过此点以上的判断,我们知道了正确第使用对象的重要性。通过上面的重用方法,有的人可能会想到对象池。难道上述那些对象都适合放在对象池吗?其实不然。

      通过维护自己的对象池来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。真正正确使用对象池的典型对象就是数据库连接池。建立数据库连接的代价非常昂贵,再加上数据库许可可能限制你使用一定的数量连接,所以重用这些对象非常有意义。但一般而言,维护自己的对象池必定会把代码弄得很乱,也增加内存占用,损害性能。另外,现代的JVM实现具有高度优化的垃圾回收器,所以性能很容易能超过轻量级对象池性能。所以,一般都用重量级对象池。
      

    第6条:消除过期的对象引用

      
      还记得我们这一章的标题叫什么吗?(前面讲了太多静态构造器的内容,估计大家都忘了这一章的标题了。)对,就是创建和销毁对象。
      
      今天,我们就来讲讲过期对象引用的消除。

      由于Java有着强大的垃圾回收功能,我们可能会觉得,咦,我们现在就不再需要考虑内存管理的事了,真爽!但,其实不然。看下下面这个例子你就知道了

    public class Stack{
        private Object[] elements;
        private int Size = 0;
        private static final int DEFAULT_SIZE = 16;
    
        public Stack(){
            elements = new Object[DEFAULT_SIZE];
        }
    
        public void push(Object e){
            ensureCapacity();
            elements[Size++] = e;
        }
    
        public Object pop(){
            if(size==0)
                throw new = EmptyStackException();
            return elements[--Size];
        }
    
        private void ensureCapacity(){
            if(elements.length==Size)
                elements=Array.copyOf(elements,2*Size+1);
        }
    }

      上面程序中并无明显错误,运行也一切正常。但程序中隐藏着一个问题:内存泄漏。随着垃圾回收器活动的增加,内存占用的增加,程序性能会不断地降低。极端情况下还有可能导致磁盘交换或程序失败。

      我们来看看这个问题是怎么出现的:
      我们要知道,如果一个栈先是增长,然后再收缩。那么,从栈中弹出的对象其实是不会被当做垃圾回收的,即使栈程序不再引用这些对象。为什么呢?因为啊,栈内部维护着对这些对象的过期引用(永远不会被解除的引用)。而且,如果一个对象引用被无意识地保留起来了,那么垃圾回收机制不但不会处理这个对象,而且也不会处理被这个对象所引用的其他所有对象!对性能的影响得多大,想想就知道了……

      如何解决这个问题呢?其实很简单,我们只要在pop函数中做如下修改

            Object result = elements[--Size];
            elements[Size] = null;
            return result;

      这叫做清空过期引用。原理解析:数组活动区域的元素是已经分配了的,而数组其余部分的元素则是自由的。但是垃圾回收器并不知道这一点;对于垃圾回收器而言,elements中所有对象引用都同等有效。只有我们知道数组的非活动部分是不重要的,所以可以手动清空。

      以上是内存泄漏的一种常见来源。另外两个常见的泄漏来源是缓存、监视器和其他回调。这两种情况的解决方法则是使用WeakHashMap中的键来保存。由于比较少用到,所以我们就不展开细讲了。有遇到是再Google一下。

    第7条:避免使用终结方法

      
      首先需要强调:终结方法通常是不可预测的,也是很危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定,降低性能,以及可移植性问题。
      其次,再来说说终结方法的好处:两种合法用途

      1、当对象所有者忘记调用显示终止方法时,可以充当“安全网”  

    以下是显示终止方法代码:

    Foo foo = new foo(...);
    try{
        ...
    }finally{
        foo.terminate();//显示终结方法!
    }
    

      如果客户端无法调用以上方法来正确结束操作时,“安全网”就起了作用。

      2、终止非关键的本地资源


      如果不是以上的两种用途,则不要使用终结方法。原因:Java语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行!当一个程序终止时,某些已经无法访问对象上的终结方法依旧没有被执行,这种情况是存在的!所以,慎用终结方法!

    总结

      在这一章中,我们讲了对象的创建和销毁习惯,在什么时候应该使用哪种方式创建和销毁对象,哪种方式是安全的,哪种方式应该尽量避免使用……让我们对对象这一概念有了更清晰的认识,由于对象是Java的核心内容,所以,如何正确地创建和销毁就显得至关的重要了。

      下一节,我们将会继续学习Java编程的另一个核心——方法

    展开全文
  • 0)Service/DAO层方法命名规约 1)获取单个对象的方法用get做前缀。 2)获取多个对象的方法用list做前缀。 3)获取统计值的方法用count做前缀。 4)插入的方法用save(推荐)或insert做前缀。 5)删除的方法用remove...
  • Java中一切皆是对象(Object),并且所有对象都是由它们的类(Class)指定的。所以每一个对象都有一个到java.lang.Class(用于描述对象的结构)的实例的引用。 Person boss = new Person(); ...
  • java面向对象

    万次阅读 多人点赞 2018-08-21 16:51:59
    包括面向对象概念、类与对象的关系、封装、构造函数、this关键字、static关键字、单例设计模式、继承、多态、内部类、异常、包等java基础知识。 1、面向对象 面向对象是相对面向过程而言 面向对象和面向过程都是...
  • 小甲鱼零基础入门学习python笔记

    万次阅读 多人点赞 2019-08-14 11:06:30
    插曲之字符串 •到目前为止,我们所认知的字符串就是引号内的一切东西,我们也把字符串叫做文本,文本和数字是截然不同的,咱看例子:>>>5+8 >>> '5'+'8' •要告诉Python你在创建一个字符串,就要在字符两边加上...
  • %d = %02d " %(i, j, i*j)) print(s) 三:enumerate()函数和下标元素循环示例 Python语言中的for循环直接迭代对象集合中的元素,如果需要在循环中使用索引下标访问集合元素,则可以使用内置的enumerate()函数 ...
  • C语言

    万次阅读 多人点赞 2019-12-18 23:01:50
    多态性是指同一个操作可以是不同对象的行为。操作—对象。 C语言 1.源程序的扩展名为.c,目标程序的扩展名为.obj,可执行程序的扩展名为.exe(每个后缀为.c的C语言都可以单独进行编译)(C语言编译程序把.c编译成.obj...
  • js面试题

    千次阅读 多人点赞 2019-04-09 19:42:32
    JavaScript 的组成 JavaScript 由以下三部分...BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法 JS 的基本数据类型和引用数据类型 基本数据类型:undefined、null、boolean、number、string、s...
  • //在HBase中管理、访问表需要先创建HBaseAdmin对象 //Connection connection = ConnectionFactory.createConnection(conf); //HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); HBaseAdmin admin =...
  • 具有与java语言类似的语法,一种网页编程语言 操作方向: —操作DOM元素(html元素) —操作BOM元素(浏览器对象) 2)js特点: 1.解释执行:不需要预编译,逐行执行 2.基于对象:内置了大量的现成对象 ...
  • 前端面试题

    万次阅读 多人点赞 2019-08-08 11:49:01
    Javascript创建对象的几种方式? 73 iframe的优缺点? 75 请你谈谈Cookie的弊端? 75 js延迟加载的方式有哪些? 76 documen.write和 innerHTML 的区别? 76 哪些操作会造成内存泄漏? 76 判断一个字符串...
  • C#语言

    千次阅读 2019-11-08 10:54:13
    ​ 在C#中有三种循环Java中的循环是一样的,但是在C#中的foreach循环结构和Java中的不太一样。 (1)while循环 while(循环条件){ //循环操作 } (2)do-while循环 do{ //循环操作 }while(循环条件); ...
  • Java命名规范

    千次阅读 2017-04-20 15:43:40
    最近,阿里巴巴发布了《阿里巴巴Java开发手册》,总结了阿里人多年一线实战中积累的研发流程规范,这些流程规范在一定程度上能够保证最终的项目交付质量,通过限制开发人员的编程风格、实现方式来避免研发人员在实践...
  • 测试开发笔记

    万次阅读 多人点赞 2019-11-14 17:11:58
    内部测试 测试阶段 测试对象 测试方法 测试目的 经济价值 优点 缺点 必要性 资源 系统测试 system testing(ST) 整个系统 (整个产品) 黑盒测试 验证产品是否符合需求规格说明书 能够保证产品以较高的的质量尽早的...
  • Java文件名及其他命名规则

    千次阅读 2015-05-28 16:09:45
    这是博主的原帖... http://bbs.csdn.net/topics/70157841  关于JAVA源文件命名的问题 ...Java文件命名java程序是由类组成的; java应用程序必须有一个包含main方法的public类,main方
  • Java命名规范+常量定义方法

    千次阅读 2018-12-12 22:05:33
    但是由于Java面向对象编程的特性,每一名Java程序员都 可以编写属于自己的Java包,为了保障每个Java命名的唯一性,在最新的Java编程规范中,要求程序员在自己定义的包的名称之前加上唯一的前缀。 由于互联网上的域...
  • JavaWeb

    千次阅读 多人点赞 2017-12-26 09:09:24
    创建:从客户端向服务器发送请求,name服务器就会创建一个request对象。 销毁:服务器为这次请求做出了响应之后,服务器就会销毁request对象。 作用范围:一次请求。 ServletConfig对象(了解) ServletContext对象...
  • Java面向对象(知识点整理)

    万次阅读 多人点赞 2021-02-22 08:53:55
    文章目录面向对象类与对象匿名对象创建对象的内存分析栈(stack)堆(heap)方法区PC寄存器本地方法栈内部类成员内部类局部内部类匿名内部类静态内部类包装类拆箱和装箱操作字符串转换基本数据类型和包装类型的区别...
  • C#基础教程-c#实例教程,适合初学者

    万次阅读 多人点赞 2016-08-22 11:13:24
     完全面向对象:不象C++语言,即支持面向过程程序设计,又支持面向对象程序设计,C#语言是完全面向对象的,在C#中不再存在全局函数、全局变量,所有的函数、变量和常量都必须定义在类中,避免了命名冲突。...
  • JVM中对象创建与内存分配机制--JVM系列(3)

    万次阅读 热门讨论 2020-11-12 10:52:38
    类加载检查: 当JVM遇到一条创建对象的指令时(例如:利用反射创建对象、调用new语句创建对象、调用对象的clone方法创建对象、利用反序列法手段创建对象),首先会去检查该指令的参数能否在常量池中定位到该对象对应...
  • Python入门

    千次阅读 多人点赞 2017-12-13 15:05:02
    tuple是另外一种有序的列表,跟List的不同之处在于Tuple一旦创建完毕,就不能修改了。 构建tuple是是通过()把所有元素都括起来。 tuple没有append()、insert()、pop()方法,已经不可以替换原有元素。 ...
  • Java条件对象(Condition)

    千次阅读 2018-05-26 17:02:23
    摘要:通过之前讨论的锁对象,我们知道了,由于线程按照时间片调度,所以使用锁对象来在多线程共享资源时保护未执行完成的线程安全。那么,我们再来考虑这样一种情况:如果我的线程执行过程中因为没有满足一些必要的条件...
  • 8、Java基础---创建日期类

    千次阅读 2019-09-15 19:39:51
    如果将类命名为Day, 只考虑字段的话,可以像下面这样进行声明;类Day的内容如图所示: 将所有的字段都设为私有(private) 外部访问可以通过构造函数和方法间接执行。 二、构造函数和方法 构造函数是在创建实例时...
  • Java命名规范

    千次阅读 2014-04-22 11:38:28
    接下来,我们一起了解下Java命名规范    变量名    1.普通变量命名应该采用首字母小写,其他字母首字母大写的方式。  2.final static变量的名字应该都大写,并且指出完整含义。如果一个常量名称由多个单词...
  • 所有对象实例和数组都在堆上分配内存空间。栈:线程私有,每个线程都会创建一个虚拟机栈,生命周期与线程相同。每个方法被执行的时候就会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。一...
  • java变量命名规范

    千次阅读 2013-12-25 13:49:43
    天才意识到在java语言里面,进行编程,命名确实很重要,因为有时实在是不知道该起什么名字,可以参照一下...但是由于Java面向对象编程的特性,每一名Java程序员都 可以编写属于自己的Java包,为了保障每个Java命名

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 79,379
精华内容 31,751
关键字:

java循环创建不同命名的对象

java 订阅