java笔记 - Go语言中文社区

java笔记


java笔记第一天


==equals

  1. ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中存储的引用地址是否相同。
  2. equals方法
    1.Object的原生方法比较的是是否指向同一引用对象,即栈中的存储的引用地址是否相等,所以和==的结果一样。
    2.而String,Integer、Date等类重写了equals方法,比较的是当a,b是同一类对象,并且属性值相等,即堆中的内容相等,就返回true,并不一定是同一个对象。
Stirng s1="abc";
String s2=new String("abc");

s1 == s2 ;      //false 
s1.equals(s2);    //true
  1. 基本数据类型没有equals方法,byte,short,char,int,long,float,double,boolean

  2. Object本身的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同,而String,Integer等类重写了equals方法,所以==比较的结果不一样。

  3. 当使用String str2="abc"时,程序会首先在字符串池中寻找相同的对象,如果已经有一个str1创建, 则str2str1的引用相等。而只有引号内包含文本创建对象才会将创建的对象放入到字符串池,而String str=new String("abc")创建的对象是不放在字符串池中的。

    String str1 = "ab";
    String str2 = "cd";

    String str3 = str1+str2; //这种创建方式是不放入字符串池的,和str4的原理一样

    String str4 = str1+"cd"; //这种创建方式是不放入字符串池的。这里实际上创建一个StringBulider对象
    然后调用StringBulider.append()方法,然后调用StringBulider.toString()方法。所以和和str7不相等(==)



    String str5 = "ab"+str2; //这种创建方式是不放入字符串池的.

    String str6 = "ab"+"cd"; //这种创建方式是放入字符串池的,这种情况实际上是创建了1个对象,即"abcd"1个对象,编译阶段会直接合成一个字符串。

    String str7 = "abcd";
    //str6 与str7都指向了常量池中同一引用地址。

    System.out.println(str6==str7); //返回ture


    final String fstr="ab";
    final String fstr2 = "cd";
    String str8=fstr+"cd";
    Strins str9 = fstr + fstr2;       //此时str9 == str7 也返回true
  对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的,所以str8和
  str7 指向常量池中相同位置,所以相等(==)

  final修饰的变量,只有定义时指定了初始值,并且被赋值时,只是简单的算术运算和字符串连接,没有访问普通变量,没有调用方法,此时该变量在编译期间就确定值了,类似于宏变量,使用时直接进行常量替换。

  下面这种情况就不相等

  final String str ;   //定义时未赋值,下面在赋值
  str = "ab";
  String str10 = str + "cd";   //str10 == str7 返回false;str此时不会进行常量替换

如果两个string对象,equals()相等,则他们的hashcode()也相等

String s1="abc";  String ss="abc"  //因为java常量池中不会存在两个相同的字符串,所以这两个相同

Sring s2=new String("abc");//或者Sring s2=new String(s1);
此时,s1,s2的堆中的值相等,但栈中的引用值不同,所以(s1==s2)==>false,(s1.equals(s2))==>true

使用new关键字一定会产生一个对象abc,和上面的"abc"不同,同时这个对象存储在堆中。所以上面产生两个对象
一个是存储在栈中的s2,一个是保存在堆中的abc,但是java不存在两个完全一样的字符串对象,所以对象abc
是引用常量字符串中的"abc",即s2-->abc对象--->常量池中的abc(见下方解释)


String s1=new String("abc");
String s2=new String("abc");
//new 出来的两个对象,==比较的是栈中存储的引用地址,肯定不等。
(s1==s2)==>false,(s1.equals(s2))==>true

String s1="abc";
String s2="abc";
(s1==s2)==>true,(s1.equals(s2))==>true

equals() 和hashcode()

  1. equals()相等的两个对象,hashcode()一定相等;
  2. equals()不相等的两个对象,hashcode()可能相等,也可能不等。
  3. 反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
  4. 两个对象相比较,hashcode()相当于字典中索引(比如A),而equals()相当于该索引下的单词,(比如AB,AC)
  5. 通过字面量赋值创建字符串时,会优先在常量池中查找是否已经存在相同的字符串,倘若已经存在,栈中的引用直接指向该字符串;倘若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
  6. Object的hashcode()方法是根据对象地址计算的(栈地址),所以两个对象的地址不同,hashcode也不同。equals()也比较的是栈地址
  7. String类的hashcode()方法重写了hashcode(),根据字符串的内容来返回hashcode(),所以相同的字符串有相同的hashcode
System.out.println("abc".hashCode());
System.out.println("abc".hashCode());
System.out.println(new String("abc").hashCode());
System.out.println(new String("abc").hashCode());

//以上四个的hashcode都相等
  1. 参考下面集合中判断两个对象相等。
    String s1 = "abc";  
    String s2 = new String("abc");
    s2 = s2.intern(); 此时`s1==s2`返回true,

    `intern()`会先检查字符串池,如果存在,就返回池里的字符串的引用;如果不存在,该方法会 把"abc"添加到字符串池中,然后再返回它的引用。

    当且仅当s1.equals(s2)返回true时, s1.intern()==s2.intern()才返回true
  1. String str=new String("abc") 创建了两个对象,String的构造器:public String(String original) { //other code ... } ,我们正是使用new调用了String类的上面那个构造器方法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正是"abc"。一个在常量池中,一个在堆中。

String对象

字符串常用方法

  1. charAt()----返回指定索引处的字符串
  2. concat()--将一个字符串追加到另一个字符串的末尾
  3. equalseIgnoseCase() 判断两个字符串的相等性,忽略大小写
  4. length() 返回字符串中的字符个数
  5. replace()用新字符代替指定的字符
  6. substring() 返回字符串的一部分
  7. toLowerCase()将字符串中的大写字符转换成小写字符返回
  8. toString() 返回字符串的值
  9. toUpperCase() 将字符串中的小写字符转换成大写字符返回
  10. trim() 删除字符串前后的空格
  11. splite() 将字符串按照指定的规则拆分成字符串数组
  12. toCharArray() 字符串转换为一个字符数组
  13. indexOf() 返回字符串第一次出现的索引
//字符串和字符数组互相转换
  String str1 = "hello" ;                  // 定义字符串
  char c[] = str1.toCharArray() ;      // 将一个字符串变为字符数组
 
  String str2 = new String(c) ;  // 将全部的字符数组变为String
  String str3 = new String(c,0,3) ;   // 将部分字符数组变为String
  1. valueOf() 基本类型转换为字符串
valueOf 是静态方法,源码如下:
   public static String valueOf(Object obj) { 
    return (obj == null) ? "null" : obj.toString();
 }

所以使用obj.toString()方法必须不为null,valueOf可以使用null,但返回的是字符串"null"
  1. s1.compareTo(s2),按照字典顺序比较两个字符串 区分大小写;compareToIgnoreCase不区分大小写
s1.compareTo(s2) 相等时返回 0 
不想等是有两种情况 长度不同和对应索引处字符不同 

1. 对应索引的值不同时,则返回此索引处的ascii码差值 即charAt(k) - charAt(k)

2. 长度不同时,则短字符是长字符的开头,返回长度差 即length() - length()
  1. Object toString()
  • String 与其他类型数据进行连接操作时(+),将自动调用该对象类的toString()方法。
  • Object 类的toString()默认返回的是一个类的类名+"@"+此对象的hashcode(16进制)。

获取系统当前时间 的毫秒数

  • long time = System.currentTimeMillis();

StringBuffer (线程安全)

  • StringBuffer 对其自身修改时,不会创建新的字符串对象(Sring对象修改,都会创建一个新的对象)
  • StringBuffer 不能使用"="初始化,必须创建实例初始化
  • 主要操作为append() 和 insert(offset,str),reverse()

StringBuilder (线程不安全) (StringBuffer和Stringbuilder内部使用的是字符串数组存储)

  • 比StringBuffer速度快,也常用于append() 和 insert(),reverse()操作

字符串拼接 +, concat, append

    • 速度最慢
虽然编译器对+进行了优化,它是使用StringBuilder的append()方法来进行处理的,但是使用append后使用了
toString(),即 str+='b',等价于 str=new StringBuilder(str).append(b).toString(),所以变慢的原因是
new StringBuilder()和 toString()
  • concat() 速度次之
源码中最后使用了return new String() ,所以变慢
  • append()速度最快
最后返回的是本身,没有创建新的对象

判断字符串是否为空

null不是对象,""空字符串是对象,所以null没有空间,空字符串有空间
equals()用于比较对象,而null不是对象,所以null用等号=
所以当 str=null,下面比较是错误的
if(str.equals("")||str==null){}             //null不能调用equals方法,所以会报空指针异常

正确的为if(str==null ||str.equals(""))

所以判断字符串为空,首先判断是否为null,所以高效的写法为
if(str==null || str.length()<=0){} 

不为空
if(str1!=null || str.length()!=0){} 

集合 Java的容器类主要由两个接口派生而出:Collection和Map。集合中的元素类型都是Object

  • List 关注事物的索引列表,有序集合
  • Set 关注事物的唯一性,无序集合
  • Quene 关注事物被处理时的顺序
  • Map 关注事物的映射和键值的唯一性

Collection接口 Collection是容器层次结构中根接口。而Collections是一个提供一些处理容器类静态方法的类

Collection接口是 Set 、List 和 Queue 接口的父接口,提供了多数集合常用的方法声明,Iterator是Collection的父接口

AbstractCollection实现了Collection接口, AbstractList和AbstractSet都继承于AbstractCollection,并且AbstractList实现了List接口
AbstractSet实现了Set接口。具体的List实现类继承于AbstractList,而Set的实现类则继承于AbstractSet

  1. add(E e) 将指定对象添加到集合中,addAll(Collection c) 将指定集合的所有元素添加进来
  2. remove(Object o) 将指定的对象从集合中移除,移除成功返回true,不成功返回false
  3. contains(Object o) 查看该集合中是否包含指定的对象,包含返回true,不包含返回flase
  4. size()返回集合中存放的对象的个数。返回值为int
  5. clear()移除该集合中的所有对象,清空该集合。
  6. iterator()返回一个包含所有对象的iterator对象,用来循环遍历
  7. toArray()返回一个包含所有对象的数组,类型是Object
  8. toArray(T[] t)返回一个包含所有对象的指定类型的数组
  9. isEmpty() 判断集合是否有元素

Collections 工具类 提供的一些方法

  • void reverse(List list) //反转元素
  • void shuffle(List list) //随机排序
  • void sort(List list) //升序排序
  • int binarySearch(List list,Object key) //二分查找
  • Object max(),min() //排序后的最大值

将集合中不安全的集合包装成线程同步的集合

Collection c=Collections.synchronizedCollection(new ArrayList());

List list=Collections.synchronizedList(new ArrayList());

Set s=Collections.synchronizedSet(new HashSet());

Map m=Collections.synchronizedMap(new HashMap());

Iterator接口,Iterator是Collection的父接口,ListIterator专门用于遍历List

  • 常用方法
  1. boolean hasNext() //是否存在访问的元素
  2. next() //访问下一个元素
  3. void remove() //删除上次访问的元素,必须先访问后执行,如果没调用next(),则不合法
  • 迭代器访问Collction集合元素时,集合里的元素不能被改变,只是把值传给了迭代变量
  • 正确的顺序
Iterator it=c.iterator();
while(it.hasNext()){
    it.next();
    it.remove();
}
  • ListIterator 可以向前和向后遍历
void add(E e)    //将元素插入List
boolean hasPrevious()
E previous()      //返回前一个元素

List接口

List特有方法

List 一个有序的序列,元素可以重复,每一个元素都有一个索引,关心的是索引,与其他集合相比,List特有的就是和索引相关的一些方法:get(int index) 、
set(index,Object elem) 用elem替换指定位置元素,并返回旧元素
add(int index,Object o) 、 indexOf(Object o)第一次出现元素o的位置。

List判断对象相等的标准是 只要通过equals()方法返回true即可
  • List接口实现类
  1. ArrayList 可以将它理解成一个可增长的数组(初始容量是10),顺序存储,它提供快速迭代和快速随机访问的能力。线程不安全,可以存null
继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
有个数组成员变量 Object[] elementData 用于保存ArrayList中的元素,当容量超过时,设置新的容量为 (原始容量*3)/2+1 

E set()     //设置位置上的元素,并且返回旧元素
void trimToSize()  //将容量设置为实际元素的个数
void clear()    //清空ArrayList,将全部的元素设为null
Object clone()   //克隆ArrayList,内部是将ArrayList拷贝到一个新的ArrayList中,并返回

//源码 

// 将e添加到ArrayList的指定位置
public void add(int index, E element) {
   if (index > size || index < 0)
       throw new IndexOutOfBoundsException(
       "Index: "+index+", Size: "+size);

   ensureCapacity(size+1);  // Increments modCount!!
   //将数组容量增大一位,然后等价于从index开始数组元素每个后移一位

   System.arraycopy(elementData, index, elementData, index + 1,size - index);    
   elementData[index] = element;    //然后设置index位置新值
   size++;
}

ArrayList遍历

  • Iterator遍历
Iterator iter=list.iterator();
while(iter.hasNext()){
  iter.next();
}
  • 通过索引值随机访问
int size=list.size();
for(int i=0;i<size;++i){
  list.get(i);
}
  • foreach
for(Interger i : list){
  value=i;
}

ConcurrentModificationException异常 当遍历List的时候同时对其修改(增、删),就会抛出此异常

  • ArrayList的iterator()方法是在父类AbstractList中实现的,而AbstractList的iterator()方法,返回一个Itr类对象,此类是AbstractList的成员内部类,实现了Iterator接口。
public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        } 
    }
}

Itr类的成员变量
1. cursor     //表示下一个要访问的元素的索引
2. lastRet   //上一个访问元素的索引

3. exceptedModcount     //表示对ArrayList修改的次数的期望值,初始值为modCount,
modCount是AbstactList的成员变量,ArrayList每次调用add()和remove()时,都会对modCount进行加1

//Iterator的hasNext()方法
public boolean hasNext(){
  return cursor !=size();       //判断下一个访问元素的下标是否等于ArrayList大小,不等则还有元素访问。
}


//Iterator的next()
public E next() {
    checkForComodification();
 try {
    E next = get(cursor);      //通过cursor获得下一个要访问元素的下标
    lastRet = cursor++;        //把cursor赋值给lastRet,然后自增,初始时cursor=0,lastRet=-1,调用一次后cursor=1,lastRet=0
    return next;
 } catch (IndexOutOfBoundsException e) {
    checkForComodification();            //此方法会判断modCount和exceptedModCount是否相等,不相等则抛出异常,
                                         //此时modCount=0,exceptedModCount=0 
    throw new NoSuchElementException();         
 }
}

然后调用ArrayList的remove(),此方法会对modCount加1,size减1,对以上测试程序,对于iterator,exceptedModcount=0,cursor=1,lastRet=0
对于list ,modCount=1,size=0。然后循环再调用hahNext(),此时cursor=1,size=0,所以不相等,继续执行。而再调用iterator的next,会调用
checkForComodification(),此时不相等,就抛出异常了。即调用list.remove()会导致modCount与exceptedModCount不一致。
而迭代器同样有remove()方法,虽然此方法内部调用的list的remove,但是多了一个exceptedModCount=modCount,所以不会报错。所以迭代器中删除元素
需要调用iter.remove()。

fail-fast原理

  • 上述即使使用迭代器的remove()方法,在多线程中,当一个线程遍历,另一线程修改时,也会导致exceptedModCount和modCount不一致,抛出异常。
    解决方法为加锁,或者使用CopyOnWriteArrayList

  • 当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变即其它线程通过add、remove、clear等方法,改变了modCount的值;这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件

ArrayList 类似于顺序表,适合随机存取,set和get;而 LinkedList 类似于链表,适合插入和删除,add和remove,只需移动指针即可。

  1. LinkedList 中的元素之间是双链接的,当需要快速插入和删除时LinkedList成为List中的不二选择,元素可以为null。
  • LinkedList随机访问时,必须从首节点(或尾节点,索引小于列表长度一半时从队首开始遍历,反之从队尾)

  • LinkedList 继承于AbstractSequentialList类,AbstractSequentialList继承与AbstractList。实现了List,Deque等接口

AbstractSequentialList 实现了随机访问List的一些方法。get(int index)、set(int index,E element)、add(int index,E element)
和remove(int index)

LinkedList本质是双向循环链表 包含两个成员变量  header和size 
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry是双向循环链表数据结构,Entry包含成员变量 previous,next,element。
previous是当前节点的上一个节点,next是当前节点的下一个节点,element是当前节点所包含的值。

List遍历

  • foreach 遍历
  • Iterator 遍历
  • 下标遍历 list.size()

使用foreach结构的类对象必须实现了Iterable接口

Vector线程安全,因为都是同步方法

  • Stack()是vector的子类

Set接口(内部是map实现的) ,三个实现类都是线程不安全的

Set关心唯一性,它不允许重复。

HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。(内部是hashmap)
HashSet不是线程同步的。集合元素可以是null。HashSet判断两个元素相等的标准是equals()相等,并且
hashcode()返回值也相等。这两个有一个不相等,即是不重复。
如果两个对象的hashCode()返回值相等,但是equals()返回false,因为两个对象的hashcode值相同,HashSet将试图将他们保存在同一个位置,只能用链式结构保存对象,但是HashSet是根据hashcode定位元素,这样的话就会导致性能下降

HashSet内部有一个HashMap的成员变量,所有操作都是基于这个HashMap操作的。HashSet存储的实际是HashMap的key,所以不允许有重复元素,元素可以为null。

HashSet内部使用一个静态常量Object类变量PRESENT,作为HashMap的value,所以HashSet中存储的key的value值都为PRESENT

add(E e)方法
public boolean add(E e){
  return map.put(e,PRESENT) == null ;
}

添加成功返回true,否则为false

因为HashMap的put方法时,若已存在key,会用新value替换旧的value,但是key不会改变,并返回旧value,此时HashSet的add()方法便返回FALSE
而没有该key时,便添加该key,并返回null,而此时的add()返回true,添加成功

contains()方法 

public boolean contains(Object o) {
    return map.containsKey(o);        //调用HashMap的containsKey
    }


遍历方法
1. 迭代器遍历
for(Iterator iter=set.iterator();iter.hasNext();){
      iter.next();
}

2.toArray()转换为数组遍历
String[] strArr=(String[])set.toArray(new String[0]);
for(String str:strArr){
  syso(str);
} 

LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序,有序进行迭代遍历时可采用此类。

TreeSet 使用树结构实现(红黑树)当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。(自然顺序意思是某种和插入顺序无关,而是和元素本身的内容和特质有关的排序方式,譬如“abc”排在“abd”前面。)TreeSet判断对象相等标准是 两个对象通过compareTo(Object obj)方法比较是否返回0(内部是TreeMap)

Queue接口

 Queue接口继承Collection接口,用于保存将要执行的任务列表。一种队列则是双端队列,支持在头、尾两端插入和移除元素,主要包括:ArrayDeque、LinkedBlockingDeque、LinkedList。另一种是阻塞式队列,队列满了以后再插入元素则会抛出异常,主要包括ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。

LinkedList 同样实现了Queue接口,可以实现先进先出的队列。
PriorityQueue 用来创建自然排序的优先级队列。

Deque<E>接口

Deque接口是双端队列接口,继承自Queue接口。可以再两头插入和删除,所以相比Quene多了一些在两端操作的方法。比如addFirst(),addLast(),peekLast(),pollLast()等等

LinkedList 同样实现了Deque的接口

Map接口

Map是一个键值对的集合。也就是说,一个映射不能包含重复的键,每个键最多映射到一个值。关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。键不能重复,值可以重复。

HashMap,TreeMap继承于AbstractMap,AbstractMap实现了Map接口,Hashtable虽然继承于Dictionary,但实现了Map接口

HashMap 当需要键值对表示,又不关心顺序时可采用HashMap,HashMap中元素的排列顺序不固定,HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

Hashtable 注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。

LinkedHashMap 是HashMap的子类,定义了当前对象的上一个和下一个引用,在hash表基础上形成双向循环链表,当需要键值对,并且关心插入顺序时可采用它。

LinkeedHashMap默认采用的按照put()顺序排序,新添加元素都会添加指链表末尾。
 public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)此构造方法可以设置按照访问顺序排序,即最近访问的会
 从原位置删除,添加至链表末尾。所以第一个节点是最少访问的节点。

LinkedHashMap采用双向循环链表将所有Entry连接起来,这样遍历时,就不用循环table数组,只要遍历循环链表即可。

1.添加元素 

LinkedHashMap并没有重写HashMap的put方法,而是重写了若key存在时调用的recordAccess方法,和key不存在直接插入的addEntry()和createEntry()方法。当按照访问顺序排序时,这几个重写方法会使最近访问的元素移至链表末尾。所以就算不使用get(),再次put()已经put()过的元素,也会使顺序改变。并且如果实现LRU算法时,要重写removeEldestEntry()方法,该方法在addEntry()中被调用,意味着超过一定大小时,就将最近未访问的节点删除掉,即第一个节点

2. 访问元素 
LinkedHashMap重写了get()方法,调用该方法时,如果是按照访问顺序排序,也会调用recordAccess()方法,也会将元素移至链表末尾,改变顺序。

3. LinkedHashMap实现LRU
首先,当accessOrder为true时,才会开启按访问顺序排序的模式,才能用来实现LRU算法。我们可以看到,无论是put方法还是get方法,都会导致目标Entry成为最近访问的Entry,因此便把该Entry加入到了双向链表的末尾(get方法通过调用recordAccess方法来实现,put方法在覆盖已有key的情况下,也是通过调用recordAccess方法来实现,在插入新的Entry时,则是通过createEntry中的addBefore方法来实现),这样便把最近使用了的Entry放入到了双向链表的后面,多次操作后,双向链表前面的Entry便是最近没有使用的,这样当节点个数满的时候,删除的最前面的Entry(head后面的那个Entry)便是最近最少使用的Entry。

TreeMap 当需要键值对,并关心元素的自然排序时可采用它,继承于AbstractMap,且实现了NavigableMap接口因此,TreeMap中的内容是“有序的键值对。

在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。TreeMap的key必须实现Comparable接口,并且key是同一类的对象

Map接口的常用方法如下表所示:

put(K key, V value) 向集合中添加指定的键值对,若已存在则覆盖,并返回旧值,若键值之前不存在,则返回null
putAll(Map t) 把一个Map中的所有键值对添加到该集合
containsKey(Object key) 如果包含该键,则返回true
containsValue(Object value) 如果包含该值,则返回true,通过判断equals()返回true
get(Object key) 根据键,返回相应的值对象
keySet() 将该集合中的所有键以Set集合形式返回
values() 将该集合中所有的值以Collection形式返回
remove(Object key) 如果存在指定的键,则移除该键值对,返回键所对应的值,如果不存在则返回null
clear() 移除Map中的所有键值对,或者说就是清空集合
isEmpty() 查看Map中是否存在键值对
size() 查看集合中包含键值对的个数,返回int类型
Set entrySet() 返回键值对视图

entrySet 返回key-value的Set<Map.Entry<k,v>>集合
keySet 返回key的Set集合
values 返回value的Collection集合

HashMap

  1. HashMap key 和 value都可以为null
  2. HashMap有一个Entry的内部类,是hash表的数据结构,用来存储key-value对
  3. HashMap 有一个叫做table的Entry数组的成员变量,数组的索引作为hash插在的索引值,并且指向了存储key-value的链表
  4. key的hashcode()方法用来寻找Entry对象所在的bucket
  5. 如果两个key有相同的hash值,则他们会放在一个桶里,并且按照头插法插入链表
  6. key的equal()方法来判断两个就算key相同,是否为同一元素,从而确保key的唯一性
  7. value对象的hashcode()和equals()并没有什么作用
//成员变量 
1. table 一个Entry[]数组,Entry是一个单向链表,哈希表的key-value对都存储在Entry数组中
2. size  HashMap的大小,保存key-value键值对的数量
3. threshold  用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
4. loadFactor就是加载因子。 
5. modCount是用来实现fail-fast机制的。

构造函数

  1. HashMap中有两个重要的参数,Capacity和负载因子(Load factor),Capacity就是table的大小,默认为16(2的倍数);loadFactor默认值为0.75;
    还有一个阈值(threshold),等于Capacity * loadFactor,所以默认为12,当存储容量超过阈值,就调整table大小为当前大小的2倍
HashMap有4个默认构造函数,此时会初始化table数组
1. 
// 默认构造函数。
 public HashMap() {
 }

2. 
// 指定“容量大小”和“加载因子”的构造函数
public HashMap(int initialCapacity, float loadFactor) {

//最大容量为2^30,超过这个容量,将被这个最大值替换。并且容量必须是2的倍数,就算不是,也会扩容至2的倍数
//比如设置初始值为17,会扩容32;初始值为15,会扩容16

3. public HashMap(Map<? extends K, ? extends V> m) {
  // 使用迭代法,将形参的元素加入到HashMap中

  for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
            Map.Entry<? extends K, ? extends V> e = i.next();
             putForCreate(e.getKey(), e.getValue());
         }
}

put(key,value)方法

1. 若key为null,则调用putForNullkey(value),遍历table,若存在key为null,则用 新value 替换 旧value,返回旧value。否则key为null的hash
值为0,存放再table[0]中,返回

2. 若key不为null,则计算key的hash值,通过hash值得到key在table中的索引

3. 遍历此索引下table[index]的链表,如果发现此链表中存在 键值的hash值和key的hash值相等,并且 key值相等(==比较相等,即栈地址相等) 或者key.equals(k)返回true,
即已存在key对应的值,则用value替换旧value,并返回旧value,保证key的唯一性

4. 若不存在key对应的值,在插入之前调用addEntry()先判断HashMap中size大小,如果size大于threshold,则扩容table.length*2两倍。
然后调用createEntry(),新建一个key-value节点,按照头插法插入链表。然后返回null

put()时涉及到的问题 hash算法 扩容

  • hash算法
1. 先调用hash()方法,此方法根据key的hashcode进行二次hash,防止hash冲突

2. 根据hash()得到的hash值,然后调用indexFor()计算在table中的索引。
 static int indexFor(int h, int length) {
        return h & (length-1);
    }

此处的 h & (length-1) 等价于 h % length,但是 & 比 % 更高效。因为length总是等于2^n。
因为偶数的最低位为0,奇数的最低位为1。假设length不是偶数,假设为15,length-1=14;这样在和14进行&运算的时候,最低位永远是0,那么0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的,空间减少,进一步增加碰撞几率,这样就会导致查询速度慢 

而当lenth为偶数,假设为16,length-1=15=1111; 2^n-1 得到的数二进制低位全为1, 在进行&时,得到的值总是和原来的hash值相同,这样的话只有hash
值相同的,才会放入同一个链表中,不同的hash值发生碰撞的几率较小,提高查询效率。

所以对于任意对象,只要它的hashCode()值相等,则hash()方法计算出来的hash码值也相等,而在计算索引时,hash值相等的会存入同一个链表。
所以当两个对象(key)的hashCode()值相等,它们就会被存入同一个链表中。
  • 扩容(rehash)
1.8以下版本,1.8加入了红黑树 

1. 调用resize(newCapacity),传入新的容量,然后会初始化一个新的大Entry数组,Entry[] newTable = new Entry[newCapacity]; 

2. 调用transfer(newTable)方法,传入新数组。遍历旧的table数组,加入新的数组中

for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
   Entry<K,V> e = src[j];             //取得旧Entry数组的每个元素
   if (e != null) {
       src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
       do {
           Entry<K,V> next = e.next;
           int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
           e.next = newTable[i]; //头插法插入新的链表
           newTable[i] = e;      //将元素放在数组上
           e = next;             //访问下一个Entry链上的元素
       } while (e != null);
   }
}

因为重新放入新数组中也是使用头插法,所以之前顺序为s1->s2->s3,扩容之后就为s3->s2->s1 

3. 再将table指向新的newTable,修改阈值

//扩容的过程中,在多线程中链表出现循环链,可能造成死循环,详见印象笔记

get(key)方法

1. 若key为null,调用getForNullKey()方法,在table[0]处查找,找到则返回value,否则返回null

2. 若key不为null,则调用hash(key.hashcode())得到key的hash码,然后调用indexFor(),计算在table中的索引,然后遍历索引下的单链表,
若有元素和key的hash值相同,并且key值相等或者key.equals(k)返回true,则找到,返回value,否则返回null

remove(key) 删除键值为key的元素

1. 调用removeEntryForKey(key),若key为null,则key的hash值为0,否则调用hash()计算hash码,然后调用indexFor()计算索引,遍历该索引下的
链表。若为第一个元素,则table[i]指向下一个元素,否则就是执行单链表的删除,并返回该Entry对象。

2. 若该Entry对象为null,则未找到该元素,返回null,否则返回删除的value

使用自定义对象作为HashMap的key,一定要重写hashCode()和equals(),否则该key在外部被改变,hashCode()即被改变,再get()会返回null

1. 重写hashCode()是为了对同一个key,能得到相同的hashCode(),就可以定位到相应的key上

2. 重写equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样才真正地获得了这个key所对应的这个键值对。

containsKey(key)

1. public boolean containsKey(Object key) {
    return getEntry(key) != null;
}

2. 调用getEntry(),getEntry()作用就是返回返回“键为key”的键值对。HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值,然后计算索引,如果存在key对应的元素,则返回该Entry对象,否则返回null

containsValue(value)

1. 若value为null,则调用containsNullValue(),containsNullValue()两层循环每个索引下的链表,判断是否有value=null的,若有则返回true,否则返回null。

2. 若value不为null,也是双重循环遍历,判断value.equals(e.value),若有则返回true,否则返回null。

Set<Map.Entry<k,v>> entryset values() keySet()三者类似

1. 内部有一个实现Iterator接口的类HashIterator(),当我们通过entrySet()获取到的Iterator的next()方法去遍历HashMap时,实际上调用的是 此类的nextEntry()方法。而nextEntry()的实现方式,先遍历Entry(根据Entry在table中的序号,从小到大的遍历);然后对每个Entry(即每个单向链表)逐个遍历

Map遍历
Map遍历有总计四种方式,方法有两种:(Entry是Map的静态内部类)

  1. 一类是基于map的Entry;map.entrySet();
  2. 一类是基于map的key;map.keySet()
    访问方式也有两种:
  3. 利用迭代器Iterator
  4. 利用foreach循环
  • Map的内部类Entry,Entry包含如下三个方法
  • K getKey() //返回Entry里包含的key值
  • V getValue() //返回Entry里包含的value值

HashMap和Hashtbale区别

  • Hashtable是同步方法,所以是线程安全的;HashMap不是线程安全的
  • HashMap的可以有一个key为null,value为多个null;而hashtable不允许key和value为null,否则会抛出NullPointerException
  • HashMap继承于AbstractMap,AbstractMap实现了Map接口,Hashtable虽然继承于Dictionary,但实现了Map接口
  • HashMap的table数组初始容量是16,Hashtable初始容量是11,填充因子默认都是0.75;HashMap扩容是当前容量的2倍,Hashtable扩容是capaciy*2+1
  • Hash计算方式不同,Hashtable是直接对table的length求余(hash&0x7fffffff)%length;HashMap是 h & length-1

集合中判断两个对象相等

  1. 判断两个对象的hashCode是否相等
    如果不相等,认为两个对象也不相等,完毕
    如果相等,转入2)
    (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。)
  2. 判断两个对象用equals运算是否相等
    如果不相等,认为两个对象也不相等
    如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)


    hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率

Comparable 与 Comparator

  • Comparator位于包java.util下,而Comparable位于包java.lang下

  • Comparator的重写方法为public int compare(T1,T2){}; Comparable的重写方法为 public int compareTo(Object o){}

  • 若要对一个类或者集合排序,就要实现Comparable接口,重写compareTo()方法,该类就成为可以比较的类。或者在外部实现Comparator接口,重写compare
    方法,这样就不用修改原类的代码,就可以比较了。

  • 如 String、Integer 自己就可以完成比较大小操作,已经实现了Comparable接口

  • 当调用数组排序 Arrays.sort(Object[] arr)时,默认调用的就是compareTo()方法,所以该类必须实现Comparable接口,而
    Arrays.sort(T [],Comparator)。就必须传入一个实现Comparator接口的对象

  • 同样的Collections.sort(List<T> ) ,List中的元素必须实现Comparable接口;Collections.sort(List<T>,Comparator)就必须传入一个实现Comparator接口的对象,然后即可对list进行排序

HashMap按照value排序

  • TreeMap可以实现按照key排序,当想使用value排序时,就先转换为List,然后调用Collections.sort()排序
Map<String,Interger> map = new HashMap<>();
List<Map.entry<String,Interger>> li = new ArrayList<>(map.entrySet());

Collections.sort(l, new Comparator<Map.Entry<String, Integer>>() {    
    public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {    
          return (o2.getValue() - o1.getValue());    
      }    
}); 

数组

  1. System.arraycopy(srcArray,srcPos,destArray,desPos,int length)数组拷贝
srcArray - 源数组。
srcPos - 源数组中的起始位置。
destArray - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。 
  1. Arrays.copyOf() //拷贝数组
int []a={1,2,3,4,5};

int [] b=Arrays.copyOf(a,a.length);

如果第二个变量大于源数组的长度,则超出值默认为0
  1. Arrays.asList()
Arrays.asList() 返回一个List,但是该List不支持add和remove操作,其内部返回一个ArrayList对象
但是此ArrayList并不是 java.util.ArrayList, 而是Arrays的内部类。

Arrays.asList()最好传递的是引用类型,并且使用此方法后,数组就和链表链接在一起,修改其中
任何一个,另外一个也会变化。

  • public Stack( ) 创建一个空栈,只有这个构造函数,Stack继承Vector,Vector继承AbstractList
Stack<Interger> sta=new Stack<Interger>();
  • 常用方法
1. public boolean empty()       //栈是否为空,因为栈实现了Collection接口,所以可用isEmpty()

2. public E pop()              //出栈

3. public E push(E item)       //进栈

4. public E peek()             //查看栈顶元素,但不移除

5. int search(Object o )       //返回对象在栈中的位置,以1为基数

java正则表达式

  1. String 类中的正则方法
1. boolean matches(reger)               //验证字符串是否匹配
str.matches("/w+");

2. String replaceAll(regex,replacement)     //用replacement替换全部匹配的字符,replace没有正则参数

     //去掉前后空格方法一 
    String regex = "^\[(.*)\]$"; 
    String s1 = str.replaceAll(regex, "$1"); 
    //方法二,注:replace方法无正则匹配 
    String regex = "^\[|\]$"; 
    String s1 = str.replaceAll(regex, ""); 

3. String[] split()   //根据正则才拆分字符串,结果为一个字符串数组

 String str = "asfasf.sdfsaf.sdfsdfas.asdfasfdasfd.wrqwrwqer.asfsafasf.safgfdgdsg";
        String[] strs = str.split("\.");
        for (String s : strs){
            System.out.println(s);
        } 
  1. 正则类
  • Pattern 没有公共构造方法,必须调用静态方法 Pattern.complie(pattern)创建对象
  • Mathcher 返回正则匹配的结果,通常用于匹配多个结果

Pattern主要方法

  • Pattern.matches(regex,string) 等价于str.matches(regex),也等价于matcher对象的matches()方法,(没有参数)。
boolean matches = Pattern.matches(pattern, text);    //静态方法
  • 对象方法 split(text)
String patternString = "sep";

Pattern pattern = Pattern.compile(patternString);

String[] split = pattern.split(text);

System.out.println("split.length = " + split.length);

for(String element : split){
    System.out.println("element = " + element);
}

Mathcher用法

  • 创建Mathcher对象
     String text = "This order was placed for QT3000! OK?";
      String pattern = "(.*)(\d+)(.*)";

      // 创建 Pattern 对象
      Pattern pat = Pattern.compile(pattern);

      // 现在创建 matcher 对象
      Matcher mat = pat.matcher(text);
  • 方法用例
1. boolean matches = mat.matches();           //检测结果是否匹配,等同于以上两个方法


2. 查找所有匹配到的结果

String text    =
        "This is the text which is to be searched " +
        "for occurrences of the word 'is'.";
String patternString = "is";
Pattern pattern = Pattern.compile(patternString);      //先创建Pattern对象
Matcher matcher = pattern.matcher(text);               //用Pattern对象方法创建Matcher对象
int count = 0;
while(matcher.find()) {
    count++;
    System.out.println("found: " + count + " : "  + matcher.start() + " - " + matcher.end());
}

find() 方法返回第一个是否匹配,之后每次调用 find() 都会返回下一个。

start()和end()返回每次匹配的字串在整个文本中的开始和结束位置。end返回的是字符串末尾的后一位,多一位

3. group获得分组结果

String text    =  "John writes about this, and John writes about that," +
                        " and John writes about everything. "  ;
String patternString1 = "(John)";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
    System.out.println("found: " + matcher.group());    //因为只有一个分组
}

//多个分组
String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;
String patternString1 = "(John) (.+?) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);
while(matcher.find()) {
    System.out.println("found: " + matcher.group(1) +
                       " "       + matcher.group(2));
}

3. 替换,等价于String类的replaceAll()方法

replaceAll(replacement) 和 replaceFirst() 方法可以用于替换Matcher搜索字符串中的一部分。replaceAll() 方法替换全部匹配的正则表达式,replaceFirst() 只替换第一个匹配的。

在处理之前,Matcher 会先重置。所以这里的匹配表达式从文本开头开始计算。即后面继续替换,也是从头开始

String text    =
          "John writes about this, and John Doe writes about that," +
                  " and John Wayne writes about everything."
        ;
String patternString1 = "((John) (.+?)) ";
Pattern pattern = Pattern.compile(patternString1);
Matcher matcher = pattern.matcher(text);

String replaceAll = matcher.replaceAll("Joe Blocks ");
System.out.println("replaceAll   = " + replaceAll);
  • str.matches()和Pattern.matches()以及mat.matches() 只会当字符串全部匹配的时候,才返回true
String text="abcd";
String pattern="abc" ,此时使用以上三个方法,都为false,因为不是完全全部匹配
  • mat.find() 可以在随意位置匹配,但是如果之前已经使用过find(),需要用mat.reset()重置匹配

  • mat.group() 返回匹配到的子字符串,等价于 str.substring(mat.start(),mat.end())

while(m.find()) { 
     System.out.println(m.group());   //m.group()等价于m.group(0)返回整个匹配结果 
     System.out.print("start:"+m.start()); 
     System.out.println(" end:"+m.end()); 
} 
  • mat.group(int i) 专用于分组操作,用于返回分组结果,即正则中()中匹配的结果,mat.groupCount()用于返回有多少组,即多少个(),无分组的话返回0
String text="BBC ABCDAB ABCDABCDABDE";
String pattern="(AB)CD";
while(mat.find()) {
            System.out.println(mat.groupCount());   //返回1
            System.out.println(mat.group(1));
            System.out.println("found: " + count + " : "  + mat.start() + "-" + mat.end());
        }


mat.group()会返回 所有ABCD 
mat.group(1)返回所有AB

try..catch

  • try块是必须的,catch块和finally块是可选的,但是必须至少出现一个,可以有多个catch块。一个catch块可以捕获多个异常,用|分割
  • 通常不要在finally块中使用return,throw,一旦使用了,将会导致try块,catch块中的return、throw语句失效。当try块,catch中遇到return,throw时
    程序不会结束,而是查看是否有finally块,若有,则执行finally块中代码,然后再返回try块,catch执行return,throw。
    若finally块中也有return,throw,则程序结束,不会再返回try块,catch执行
  • 所以不管try块,catch执行什么的代码,finally块总会被执行。除非try块,catch调用了退出虚拟机的方法(System.exit(1))

异常

  • java的异常父类为Throwable,继承的有两大类Error,Exception。Error程序无法处理和捕获,Exception又分为IOException和RuntimeException

  • IOException必须try..catch,而RuntimeException(运行时异常)包括常见的空指针,数组越界,类型转换异常,无需try..catch

  • 异常捕获时先捕获小异常,再捕获大异常。

  • throws抛出异常时,当前方法不知如何处理,然后就交给上一级处理,如果main方法也不知如何处理,也throws抛出异常,然后交给JVM处理

throws用来抛出异常类,用在方法上 void test() throws IOException{}。throws可以抛出多个异常类,用,分开
使用throws抛出异常就无需try..catch。然后一个方法throws异常,则调用该方法时,要么放在try..catch中,要么放在另一个throws方法中。

当有子类方法重写父类的throws异常时,子类抛出的异常必须是父类抛出异常的子类或者相同
  • throw程序自行抛出异常,throw抛出的是异常实例,放在方法内
void test(){
  throw new IOException();
}

如果throw抛出的异常,不是RuntimeException异常,则该throw语句必须要么处在try块里,显示捕获异常;要么放在一个带throws方法里,由调用者处理。

面向对象

  1. 类的初始化过程
  • 初始化 父类 的静态变量和静态代码块(也叫类初始化块)

  • 初始化 子类 的静态变量和静态代码块

  • 初始化 父类 的普通成员变量和普通代码块,再执行父类的构造方法

  • 初始化 子类 的普通成员变量和普通代码块,再执行子类的构造方法

  • 初始化块经过编译实际上会被还原到每个构造器中,且位于构造器代码的最前面

  • 普通话初始化块负责对对象进行初始化,静态初始化块负责对类进行初始化

  • 只有在第一次创建对象的时候才会创建静态初始化块,此后会一直存在,所以后面创建对象时无需再类初始化

  • 而普通初始化块,每次创建对象都会执行。等同于构造方法

class Father{
    int i=5;
     // 静态变量
     public static String p_StaticField = "父类--静态变量";
     // 变量
     public String p_Field = "父类--变量";
    
     // 静态初始化块
     static {
      System.out.println(p_StaticField);
      System.out.println("父类--静态初始化块");
     }
     // 初始化块
     {
      System.out.println(p_Field);
      System.out.println("父类--初始化块");
     }
     //父类构造方法
    Father(){
        System.out.println("Father"+this.i);
        test();
    }
    public void test(){
        System.out.println("father test"+this.i);
    }
}
public class Test extends Father {

    int i=50;
    
    // 静态变量
    public static String staticField = "子类静态变量";
    // 变量
    public String field = "子类变量";
    // 静态初始化块
    static {
    System.out.println(staticField);
    System.out.println("子类静态初始化块");
    }
    // 初始化块
    {
    System.out.println(field);
    System.out.println("初始化块");
    }
    
    //子类构造方法
    Test() {
    System.out.println("Son"+this.i);
    }
    public void test(){
        System.out.println("son test"+this.i);
    }

    public static void main(String[] args) {
       
        System.out.println("在我之前是啥");
        Test tt=new Test();
    }

}


//输出为

父类--静态变量
父类--静态初始化块
子类静态变量
子类静态初始化块

在我之前是啥
-----------
父类--变量
父类--初始化块
Father5
son test0               //此步为子类实例化父类,后面说明
子类变量
子类初始化块
  • 子类调用new 时,在调用子类构造函数之前,会先调用父类的无参构造函数,并且每一个构造函数都会调用。
    即隐式调用super()。若父类一个构造函数也没有,也会隐式调用。若父类只有含参的构造函数,没有无参的构造
    函数。则子类必须显示的调用父类的含参构造函数,即super(参数),且super必须位于第一句。
    子类通过this()调用子类本身的其他构造函数,但是不会和super()同时出现,因为调用构造函数必须是第一句。
    在静态方法中,不能使用this,this指向对象,static修饰的变量或方法优先于对象而存在。

  • 子类实例化父类对象 此对象调用的方法必须是父类拥有的,如果被子类重写,则调用的是子类的

Father fa=new Son();

当子类实例化父类对象时,该对象只能调用父类中定义的方法和属性。
因为属性不能重写,所以该对象访问的对象是父类的
因为静态方法也不能重写,所以访问的也是父类的,因为静态方法是类在加载时就被加载到内存中的方法,在整个运行过程中保持不变,因而不能重写。
非静态方法是在对象实例化时才单独申请内存空间,为每一个实例分配独立的运行内存,因而可以重写


因为非静态方法可以被重写(包括抽象方法),所以当调用方法时,原则是调用父类的方法,但是被子类重写了,就会调用子类的方法。产生多态或者动态绑定。 如果此方法没有被重写,则只会调用父类方法,不会调用子类独有方法。

重写原则 两同两小一大
1. 方法名相同,参数类型相同
2. 子类返回类型小于等于父类方法返回类型
3. 子类抛出异常小于等于父类抛出异常
4. 子类访问权限大于等于父类访问权限

class Father(){
    int i=5; 
    Father(){ 
        syso("father "+i);
        test();
    }

    pulic void test(){ 
        syso("father test"+this.i).
    }
}

class Son(){ 
    int i=50;

    Son(){ 
        syso("son "+this.i);
    }

    public void test(){ 
        syso("Son test"+this.i); 
    }

    pulic static void main(String[] args){ 
       Father fa=new Son();
       fa.i;
    }
}
// father 5
son test 0 
son 50 
5

//先调用父类构造方法,父类构造方法中,调用了test(),但是子类重写了此方法,所以调用的为子类的方法(多态),但是此时还没调用子类的构造方法,所以子类中i的值为0。
fa.i 调用父类的属性。

  • 接口
A) 接口没有提供构造方法(初始化块),抽象类中可以有构造方法(初始化块),接口是特殊的抽象类,接口和抽象类都不能实例化
B) 接口中的方法默认使用public、abstract修饰,因此不要企图使用proteced或是private修饰符去修饰方法,同时由于接口中的方法都是abstract的所以方法不能有具体实现,即方法后不能有{},抽象类中可以有实现过的方法。接口不能定义静态方法,抽象类可以定义静态方法。
C) 接口中的属性默认使用public、static、final修饰,因此不要企图使用proteced或是private修饰符去修饰属性,同时由于接口中的属性是public static final的那么定义属性时必须初始化,否则会报错。抽象类即可以定义普通成员变量也可以定义静态常量
D) 接口可以多继承

接口与实现类之间也存在多态关系,即可以用接口声明变量,然后实现类指向接口变量
  • 多态
多态三个条件 
1. 继承
2. 重写
3. 父类引用指向子类对象
  • 重载
    重载是指同一类中,方法名相同,参数个数或者类型不同,至于返回值类型,修饰符等没有关系。如果已经构成重载,则方法的返回值可以不同,如果两个方法的参数列表完全一样,不能设置返回值类型不同来实现重载,会提示已定义此方法。
  • Interger 类将-128~127之间的整数放入一个缓存数组中,所以在此范围的装箱,实际上引用的数组的元素。
    不在此范围的就会新建Integer实例
Interger ina=2;
Interger inb=2;
syso(ina == inb)       //true 

Interger ina=128;
Interger inb=128;
syso(ina == inb)     //false 

单例类

class Singleton{
    private static Singleton instance;  //使用当前类变量来缓存是否已创建实例,已创建则直接返回

    private Singleton(){}     //构造器使用private修饰,隐藏该构造器

    //提供一个静态方法,用于返回实例,并且保证只返回一个对象

    public static Singleton getInstance(){
        //如果instance为null,说明还没创建过该对象
        //如果不为null,表明已经创建该对象,直接返回该对象
        if(instance == null){
            instance =new Singleton();
        }
        return instance;
    }
}

public class SingletTest{
    public static void main(){
        //创建对象不能通过构造器,只能通过类静态方法创建
        Singleton s1=Singleton.getInstance();

        Singleton s2=Singleton.getInstance();
        syso(s2 == s2);         //会输出true,因为只会产生一个对象
    }
}

//上面是懒汉式,多线程调用会产生多个实例。下面是饿汉式
class Singleton{
    //static final修饰,类加载时就初始化
  private static final Singleton instance=new Singleton();

  private Singleton(){}
  public static Singleton getInstance(){
    return instance;
  }
}

//静态内部类 懒加载       因为静态内部类不会在类加载时加载
public class Singleton{ 
    private Singleton(){}
    private static class SingletonHolder{ 
        private static Singleton instance = new Singleton();     
    }
    public static getInstance(){ 
        return SingletonHolder.instance;
    }
}

final final修饰的变量必须显式初始化

static 和 final

  • satic 修饰的字段在类加载的过程中的准备阶段被初始化为0或null或默认值,而后在初始化阶段(调用<clinit>才会被设定值,如果没有设定值,就是默认值)

  • final修饰的字段在运行时被初始化

  • static final 修饰的字段没有默认值,必须显示的赋值。即编译期间放入常量池

  1. final修饰局部变量 可以在定义是指定默认值,也可以随后赋值,但只能赋值一次,修饰形参时,不能在方法中修改
public void test(final int a){
    a=5;   //错误,不能修改final形参
}
  1. final修饰成员变量
  • 类变量 必须定义时赋值,或者在静态初始化块中赋值
  • 实例变量 必须在定义时赋值,或者在非静态初始化块,或者构造函数中赋值
  1. final 修饰基本类型和引用类型的区别
  • 修饰基本类型,因为基本类型不能重新赋值,所以不能被改变
  • 修饰引用类型,对于引用变量保存的只是一个引用,final只能保证这个变量引用地址不会改变,即不会引用别的对象了。但是对象的值是可以改变的
final int[] arr={1,2,3,4};

Arrays.sort(arr);           //可以进行排序
arr[3]=5;       //可以进行赋值

arr=null;       //重新赋值,非法

fianl Person p=new Person();

p.setName();    //可以重新设置属性值


final String str1="abc" + 123;

final String str2="abc"+String.valueOf(123);

str2 调用了String类的方法,所以无法在编译时确定值,所以str1不等于str2
  • 如果final修饰的变量,访问了普通变量,调用了方法,则该变量就无法在编译时确定
final String str1="abc" + 123;

final String str2="abc"+String.valueOf(123);

str2 调用了String类的方法,所以无法在编译时确定值,所以str1不等于str2


String s1="abcd";
String s2="ab" + "cd";

s1 == s2  //true 

String s4="ab";
String s5="cd";

String s3=s4 + s5; 
s3==s1  //false      s3无法在编译时确定值,s4和s5只是普通变量,不回进行常亮替换,s4和s5加上final修饰符,s3即等于s1

  • fianl 修饰方法
    final修饰的方法子类无法重写,但是子类可以重新定义不加final修饰的同名方法。final方法无法重写,但是可以被重载。类的private方法被隐式定义为final方法

缓存类

class CacheImmutale{
    private  static int MAX_SIZE=10;
    //使用数组缓存已有的实例
    private  static CacheImmutale[] cache =new CacheImmutale[MAX_SIZE];

    private static int pos=0;  //记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例
    private final String name;

    private CacheImmutale(Strin name){//构造方法私有
        this.name=name;
    } 

    public String getName(){
        return name;
    }

    public static CacheImmutale valueOf(String name){
        //遍历已缓存的对象
        for(int i=0;i<MAX_SIZE;++i){
            //如果已有实例,则直接返回
            if(cache[i]!=null && cache[i].getName().equals(name)){
                return cache[i];
            }
        }

        if(pos==MAX_SIZE){     //如果缓存池已满
            cache[0]=new CacheImmutale(name);   //覆盖第一个对象
            pos=1;
        }else{
            cache[pos++]=new CacheImmutale(name);     //新创建的对象缓存起来
        }
    }

    public boolean equals(Object obj){
        if(this==obj){
            return true;
        }

        if(obj!=null && obj.getClass() == CacheImmutale.class){
            CacheImmutale ci=(CacheImmutale)obj;
            return this.name.equals(ci.getName());
        }
        return false;
    }

    public int hashcode(){
        return name.hashcode();
    }
}

CacheImmutale c1=CacheImmutale.valueOf("hello");
CacheImmutale c2=CacheImmutale.valueOf("hello");

syso(c1 == c2);     //true

Interger类就采用额上述相同的策略,如果采用new 创建Integer对象每次都返回新的对象,而采用valueOf()方法,则会缓存该对象

Interger i1=new Interger(6);
Interger i2=Interger.valueOf(6);

Interger i3=Interger.valueOf(6);

syso(i1 == i2)    //false 
syso(i2 == i3)    //true 


Interger i2=Interger.valueOf(128);

Interger i3=Interger.valueOf(128);
syso(i2 == i3)    //false        由于Integer值缓存-128~127之间的值,所以超过此范围不缓存

  • final类不能被继承

抽象类

  • abstract 和 fianl不能同时使用
  • abstract 和static不能同时修饰方法,因为通过类调用没有方法体的方法,会出错,但是可以同时修饰内部类
  • abstract方法必须被重写,所以private和abs
版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/4edc7e8f9c2f
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-08 23:03:34
  • 阅读 ( 1263 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢