註解
一、什麼是註解
官方定義:
註解是一系列元數據,它提供數據用來解釋程序代碼,但是註解並非是所解釋的代碼本身的一部分。註解對於代碼的運行效果沒有直接影響。
註解本身不起作用,起作用的是註解解釋器,註解需要和反射一起使用才能發揮大的威力。
註解有許多用處,主要如下:
提供信息給編譯器:編譯器可以利用註解來探測錯誤和警告信息。
編譯階段時的處理:軟件工具可以用來利用註解信息來生成代碼、Html文檔或者做其它相應處理。
運行時的處理:某些註解可以在程序運行的時候接受代碼的提取
值得注意的是,註解不是代碼本身的一部分。
我們通俗一點理解就是:註解就相當於超市裏面商品的標籤,它不屬於商品,它只是爲商品做一些解釋說明,而註解就是爲我們的代碼作解釋說明,並不屬於我們代碼本身的部分。
二、註解的使用
JDK中提供的註解
JDK裏面提供的幾個註解
@Override: java.lang.Override 是一個標記類型註解,它被用作標註方法。它說明了被標註的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種註解在一個沒有覆蓋父類方法的方法時,java 編譯器將以一個編譯錯誤來警示。
@Deprecated:表示該方法已經過時了。(當方法或是類上面有@Deprecated註解時,說明該方法或是類都已經過期不能再用,但不影響以前項目使用,提醒你新替代待的方法或是類。如果程序員不小心使用了它的元素,那麼編譯器會發出警告信息。)
@SuppressWarnings:表示忽略指定警告,比如@SuppressWarnings(“Deprecation”)
@SafeVarargs:參數安全類型註解。它的目的是提醒開發者不要用參數做一些不安全的操作,它的存在會阻止編譯器產生 unchecked 這樣的警告。它是在 Java 1.7 的版本中加入的。
@FunctionalInterface:函數式接口註解,這個是 Java 1.8 版本引入的新特性。
元註解
java.lang.annotation 提供了四種元註解,專門註解其他的註解(在自定義註解的時候,需要使用到元註解)。
@Retention:定義該註解的生命週期。
RetentionPolicy.SOURCE:註解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視。
RetentionPolicy.CLASS:註解只被保留到編譯進行的時候,它並不會被加載到 JVM 中。
RetentionPolicy.RUNTIME:註解可以保留到程序運行的時候,它會被加載進入到 JVM 中,所以在程序運行時可以獲取到它們。
@Target:表示該註解用於什麼地方。默認值爲任何元素,表示該註解用於什麼地方。可用的ElementType 參數包括:
ElementType.CONSTRUCTOR: 用於描述構造器。
ElementType.FIELD: 成員變量、對象、屬性(包括enum實例)。
ElementType.LOCAL_VARIABLE: 用於描述局部變量。
ElementType.METHOD: 用於描述方法。
ElementType.PACKAGE: 用於描述包。
ElementType.PARAMETER: 用於描述參數。
ElementType.TYPE: 用於描述類、接口(包括註解類型) 或enum聲明。
@Documented:一個簡單的Annotations 標記註解,表示是否將註解信息添加在java 文檔中。
@Inherited :表示當前註解可以由子註解來繼承。
@Repeatable:是可重複的意思,Java 1.8 才加進來的,指的是註解的值可以同時取多個。
自定義註解
Annotation型定義爲@interface,所有的Annotation 會自動繼承java.lang.Annotation這一接口,並且不能再去繼承別的類或是接口。
參數成員只能用public 或默認這兩個訪問權修飾,可以用default設置默認值。也可以不定義成員。
參數成員只能用基本類型byte、short、char、int、long、float、double、boolean八種基本數據類型和String、Enum、Class、annotations等數據類型,以及這一些類型的數組。
要獲取類方法和字段的註解信息,必須通過Java的反射技術來獲取 Annotation 對象,因爲你除此之外沒有別的獲取註解對象的方法
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyAnnotation { int age() default 1; String name() default ""; } @MyAnnotation(age=26,name="bobo") public class People { }
註解經典運用
運行期利用反射可以獲取註解:詳情請移步喫透Java基礎六:反射
註解+反射 在數據庫框架方面的應用:
有一張用戶表,包含id name age gender 對每一個字段進行檢索並打印出Sql語句
1、自定義Table和Column註解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Table { String value(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Column { String value(); }
2、編寫User類
@Table("user") public class User { public User(String id,String name,String age,String gender){ this.id=id; this.name=name; this.age=age; this.gender=gender; } @Column("id") private String id; @Column("name") private String name; @Column("age") private String age; @Column("gender") private String gender; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } }
3、利用反射獲取註解信息,實現Sql語句查詢
public class MyTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { User user = new User("1", "bobo", "26", "男"); System.out.println(query(user)); } public static String query(User user) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { StringBuilder stringBuilder = new StringBuilder(); Class userClass = user.getClass(); //獲取表明 Annotation annotation = userClass.getAnnotation(Table.class); Table table = (Table) annotation; stringBuilder.append("select * from ").append(table.value()).append(" where 0=0"); //獲取字段信息 Field[] fields = userClass.getDeclaredFields(); for (Field field : fields) { //獲取列名 Column column = field.getAnnotation(Column.class); String columnName = column.value(); //獲取字段值 String filedName = field.getName(); String methodName = "get" + filedName.substring(0, 1).toUpperCase() + filedName.substring(1); Method method = userClass.getMethod(methodName, null); String value = (String) method.invoke(user, null); stringBuilder.append(" and " + columnName + " = " + value); } return stringBuilder.toString(); } }
運行輸出:
select * from user where 0=0 and id = 1 and name = bobo and age = 26 and gender = 男
枚舉
一:枚舉類
枚舉是JDK1.5添加的,在枚舉類型出來之前,我們都是以定義常量來代替,比如:
public class Date { public static final int ONE = 0; public static final int TWO = 1; public static final int THREE = 2; public static final int FOUR = 3; public static final int FIVE = 4; }
這種定義常量的方式也是有一些不足的,如int值相同的情況下,編譯期並不會提示我們的,所以很容易造成混淆。
定義枚舉類的一般語法:任意兩個枚舉成員不能具有相同的名稱,多個枚舉成員之間使用逗號分隔,枚舉表示的類型其取值是必須有限的,枚舉是枚舉類的實例對象,枚舉成員類是jvm在運行期創建,不能在外部創建實例對象。
public enum Date { ONE,TWO,THREE,FOUR,FIVE }
二:枚舉類原理
把剛纔定義的Date枚舉類編譯後再反編譯class文件來看一下:
可以看到,使用關鍵字enum定義的枚舉類型,在編譯期後,也將轉換成爲一個實實在在的類,而在該類中,會存在每個在枚舉類型中定義好變量的對應實例對象,如上述的ONE枚舉類型對應public static final Date ONE;,同時編譯器會爲該類創建兩個方法,分別是values()和valueOf()。
- 枚舉類實際上也是一個類,默認繼承於java.lang.Enum類,並且此類是final的,不可被繼承,又由於java是單繼承已經默認繼承Enum所以我們定義的Date枚舉類是不允許繼承其他類的。
- 默認生成私有構造函數,只能由虛擬機去創建,不允許外部直接創建枚舉類對象的。
- 定義的枚舉實際上是枚舉類型的常量。
java.lang.Enum源碼
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { //返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明 private final String name; public final String name() { return name; } //返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數爲零) private final int ordinal; public final int ordinal() { return ordinal; } //枚舉的構造方法,只能由編譯器調用 protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } //比較的是ordinal值 public final int compareTo(E o) { Enum<?> other = (Enum<?>)o; Enum<E> self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal;//根據ordinal值比較大小 } //返回與此枚舉常量的枚舉類型相對應的 Class 對象 @SuppressWarnings("unchecked") public final Class<E> getDeclaringClass() { //獲取class對象引用,getClass()是Object的方法 Class<?> clazz = getClass(); //獲取父類Class對象引用 Class<?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper; } //返回帶指定名稱的指定枚舉類型的枚舉常量 public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { //enumType.enumConstantDirectory()獲取到的是一個map集合,key值就是name值,value則是枚舉變量值 //enumConstantDirectory是class對象內部的方法,根據class對象獲取一個map集合的值 T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); } public String toString() { return name; } public final boolean equals(Object other) { return this==other; } public final int hashCode() { return super.hashCode(); } protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } protected final void finalize() { } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum"); } private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("can't deserialize enum"); } }
下面例子測試其方法:
public class MyTest { public static void main(String[] args) { Date[] dates = {Date.ONE, Date.TWO, Date.THREE, Date.FOUR, Date.FIVE}; for (Date date : dates) { System.out.println("name:" + date.name() + " ordinal:" + date.ordinal() + " class:" + date.getDeclaringClass()); } System.out.println("-----------------------------"); System.out.println("dates[0].compareTo(dates[1]):" + dates[0].compareTo(dates[1])); System.out.println("-----------------------------"); Date d1 = Date.valueOf(Date.class, dates[0].name()); Date d2 = Enum.valueOf(Date.class, dates[0].name()); System.out.println("d1:" + d1 + " d2:" + d2); } }
運行輸出:
name:ONE ordinal0 class:class Date name:TWO ordinal1 class:class Date name:THREE ordinal2 class:class Date name:FOUR ordinal3 class:class Date name:FIVE ordinal4 class:class Date ----------------------------- dates[0].compareTo(dates[1])-1 ----------------------------- d1:ONE d2:ONE
Values()方法與ValueOf()方法
values()方法和valueOf(String name)方法是編譯器生成的static方法,在Enum類中並沒出現values()方法,但valueOf()方法還是有出現的,只不過編譯器生成的valueOf()方法需傳遞一個name參數,而Enum自帶的靜態方法valueOf()則需要傳遞兩個方法,其實,編譯器生成的valueOf方法最終還是調用了Enum類的valueOf方法:
public class MyTest { public static void main(String[] args) { System.out.println(Arrays.toString(Date.values())); System.out.println(Date.valueOf("ONE")); } }
運行輸出:
[ONE, TWO, THREE, FOUR, FIVE]
ONE
values()方法的作用就是獲取枚舉類中的所有變量,並作爲數組返回。
valueOf(String name)方法與Enum類中的valueOf方法的作用類似根據名稱獲取枚舉變量。
三:枚舉類使用
enum 與 class、interface 具有相同地位,但是不能繼承與被繼承,也不能創建實例,只能由JVM去創建。
可以實現多個接口,也可以擁有構造器、成員方法、成員變量。
實現接口
public interface ClickListener { void onClick(); } public enum Date implements ClickListener{ ONE, TWO, THREE, FOUR, FIVE; @Override public void onClick() { } }
定義構造函數
注意:只能是私有的構造函數或默認
public enum Date{ ONE, TWO, THREE, FOUR, FIVE; private Date(){} }
定義成員方法和成員變量
public enum Date{ ONE, TWO, THREE, FOUR, FIVE; private String a; public String getContext(){ return Arrays.toString(values()); } }
EnumMap
java.util.EnumSet和java.util.EnumMap是兩個枚舉集合。EnumMap繼承了AbstractMap類,因此EnumMap具備一般map的使用方法,主要通過一個保存key的數組K[] keyUniverse和一個保存value的數組Object[] vals來操作。
源碼:
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable { //Class對象引用 private final Class<K> keyType; //存儲Key值的數組 private transient K[] keyUniverse; //存儲Value值的數組 private transient Object[] vals; //map的size private transient int size = 0; //空map private static final Enum<?>[] ZERO_LENGTH_ENUM_ARRAY = new Enum<?>[0]; //構造函數 public EnumMap(Class<K> keyType) { this.keyType = keyType; keyUniverse = getKeyUniverse(keyType); vals = new Object[keyUniverse.length]; } //返回枚舉數組 private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) { //最終調用到枚舉類型的values方法,values方法返回所有可能的枚舉值 return SharedSecrets.getJavaLangAccess().getEnumConstantsShared(keyType); } public V put(K key, V value) { typeCheck(key);//檢測key的類型 //獲取存放value值得數組下標 int index = key.ordinal(); //獲取舊值 Object oldValue = vals[index]; //設置value值 vals[index] = maskNull(value); if (oldValue == null) size++; return unmaskNull(oldValue);//返回舊值 } private void typeCheck(K key) { Class<?> keyClass = key.getClass();//獲取類型信息 if (keyClass != keyType && keyClass.getSuperclass() != keyType) throw new ClassCastException(keyClass + " != " + keyType); } //代表NULL值得空對象實例 private static final Object NULL = new Object() { public int hashCode() { return 0; } public String toString() { return "java.util.EnumMap.NULL"; } }; private Object maskNull(Object value) { //如果值爲空,返回NULL對象,否則返回value return (value == null ? NULL : value); } @SuppressWarnings("unchecked") private V unmaskNull(Object value) { //將NULL對象轉換爲null值 return (V)(value == NULL ? null : value); } public V get(Object key) { return (isValidKey(key) ? unmaskNull(vals[((Enum<?>)key).ordinal()]) : null); } //對Key值的有效性和類型信息進行判斷 private boolean isValidKey(Object key) { if (key == null) return false; // Cheaper than instanceof Enum followed by getDeclaringClass Class<?> keyClass = key.getClass(); return keyClass == keyType || keyClass.getSuperclass() == keyType; } public V remove(Object key) { //判斷key值是否有效 if (!isValidKey(key)) return null; //直接獲取索引 int index = ((Enum<?>)key).ordinal(); Object oldValue = vals[index]; //對應下標元素值設置爲null vals[index] = null; if (oldValue != null) size--;//減size return unmaskNull(oldValue); } //判斷是否包含某value public boolean containsValue(Object value) { value = maskNull(value); //遍歷數組實現 for (Object val : vals) if (value.equals(val)) return true; return false; } //判斷是否包含key public boolean containsKey(Object key) { return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null; } }
下面例子測試其方法:
public class MyTest { public static void main(String[] args) { EnumMap enumMap=new EnumMap(Date.class); //添加元素 enumMap.put(Date.ONE,1); enumMap.put(Date.TWO,2); enumMap.put(Date.THREE,3); //刪除元素 enumMap.remove(Date.TWO); //遍歷輸出 System.out.println(enumMap.toString()); } }
運行輸出:
{ONE=1, THREE=3}
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable { //元素的class final Class<E> elementType; final Enum<?>[] universe; private static Enum<?>[] ZERO_LENGTH_ENUM_ARRAY = new Enum<?>[0]; EnumSet(Class<E>elementType, Enum<?>[] universe) { this.elementType = elementType; this.universe = universe; } //創建一個具有指定元素類型的空EnumSet。 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum<?>[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); } //創建一個指定元素類型幷包含所有枚舉值的EnumSet public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) { EnumSet<E> result = noneOf(elementType); result.addAll(); return result; } abstract void addAll(); 創建一個包含參數容器中的所有元素的EnumSet public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) { return s.clone(); } public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c) { if (c instanceof EnumSet) { return ((EnumSet<E>)c).clone(); } else { if (c.isEmpty()) throw new IllegalArgumentException("Collection is empty"); Iterator<E> i = c.iterator(); E first = i.next(); EnumSet<E> result = EnumSet.of(first); while (i.hasNext()) result.add(i.next()); return result; } } // 初始集合包括指定集合的補集 public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s) { EnumSet<E> result = copyOf(s); result.complement(); return result; } // 創建一個包括參數中所有元素的EnumSet public static <E extends Enum<E>> EnumSet<E> of(E e) { EnumSet<E> result = noneOf(e.getDeclaringClass()); result.add(e); return result; } public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) { EnumSet<E> result = noneOf(e1.getDeclaringClass()); result.add(e1); result.add(e2); return result; } public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) { EnumSet<E> result = noneOf(e1.getDeclaringClass()); result.add(e1); result.add(e2); result.add(e3); return result; } public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) { EnumSet<E> result = noneOf(e1.getDeclaringClass()); result.add(e1); result.add(e2); result.add(e3); result.add(e4); return result; } public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5) { EnumSet<E> result = noneOf(e1.getDeclaringClass()); result.add(e1); result.add(e2); result.add(e3); result.add(e4); result.add(e5); return result; } @SafeVarargs public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) { EnumSet<E> result = noneOf(first.getDeclaringClass()); result.add(first); for (E e : rest) result.add(e); return result; } // 創建一個包括枚舉值中指定範圍元素的EnumSet public static <E extends Enum<E>> EnumSet<E> range(E from, E to) { if (from.compareTo(to) > 0) throw new IllegalArgumentException(from + " > " + to); EnumSet<E> result = noneOf(from.getDeclaringClass()); result.addRange(from, to); return result; } abstract void addRange(E from, E to); @SuppressWarnings("unchecked") public EnumSet<E> clone() { try { return (EnumSet<E>) super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(e); } } abstract void complement(); final void typeCheck(E e) { Class<?> eClass = e.getClass(); if (eClass != elementType && eClass.getSuperclass() != elementType) throw new ClassCastException(eClass + " != " + elementType); } private static <E extends Enum<E>> E[] getUniverse(Class<E> elementType) { return SharedSecrets.getJavaLangAccess() .getEnumConstantsShared(elementType); } }
EnumSet
EnumSet是與枚舉類型一起使用的專用 Set 集合,EnumSet 中所有元素都必須是枚舉類型。注意EnumSet不允許使用 null 元素。試圖插入 null 元素將拋出 NullPointerException,但試圖測試判斷是否存在null 元素或移除 null 元素則不會拋出異常,與大多數collection 實現一樣,EnumSet不是線程安全的,因此在多線程環境下應該注意數據同步問題。
下面例子測試其方法:
創建EnumSet並不能使用new關鍵字,因爲它是個抽象類,而應該使用其提供的靜態工廠方法,EnumSet的靜態工廠方法比較多,如下
public class MyTest { public static void main(String[] args) { EnumSet<Date> enumSet= EnumSet.noneOf(Date.class); enumSet.add(Date.ONE); enumSet.add(Date.TWO); enumSet.add(Date.THREE); System.out.println("添加後:"+enumSet.toString()); EnumSet<Date> enumSet1= EnumSet.allOf(Date.class); System.out.println("add後:"+enumSet1.toString()); //初始集合包括枚舉值中指定範圍的元素 EnumSet<Date> enumSet2= EnumSet.range(Date.ONE,Date.THREE); System.out.println("指定範圍:"+enumSet2.toString()); //指定補集,也就是從全部枚舉類型中去除參數集合中的元素,如下去掉上述enumSet2的元素 EnumSet<Date> enumSet3= EnumSet.complementOf(enumSet2); System.out.println("指定補集:"+enumSet3.toString()); //初始化時直接指定元素 EnumSet<Date> enumSet4= EnumSet.of(Date.ONE); System.out.println("指定Date.ONE元素:"+enumSet4.toString()); EnumSet<Date> enumSet5= EnumSet.of(Date.ONE,Date.FOUR); System.out.println("指定Color.BLACK和Color.GREEN元素:"+enumSet5.toString()); //複製enumSet5容器的數據作爲初始化數據 EnumSet<Date> enumSet6= EnumSet.copyOf(enumSet5); System.out.println("enumSet6:"+enumSet6.toString()); List<Date> list = new ArrayList<Date>(); list.add(Date.ONE); list.add(Date.ONE);//重複元素 list.add(Date.TWO); list.add(Date.THREE); System.out.println("list:"+list.toString()); //使用copyOf(Collection<E> c) EnumSet enumSet7=EnumSet.copyOf(list); System.out.println("enumSet7:"+enumSet7.toString()); } }
運行輸出:
添加後:[ONE, TWO, THREE]
add後:[ONE, TWO, THREE, FOUR, FIVE]
指定範圍:[ONE, TWO, THREE]
指定補集:[FOUR, FIVE]
指定Date.ONE元素:[ONE]
指定Color.BLACK和Color.GREEN元素:[ONE, FOUR]
enumSet6:[ONE, FOUR]
list:[ONE, ONE, TWO, THREE]
enumSet7:[ONE, TWO, THREE]
String字符串
一、String基礎
1、創建字符串方式
- String test = “abc”;
- String test = new String(“abc”);
2、String類是不可變的
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; ... }
String類被final關鍵字修飾,意味着String類不能被繼承,並且它的成員方法都默認爲final方法;字符串一旦創建就不能再修改。
String實例的值是通過字符數組實現字符串存儲的。
String類不可變的好處?
作爲HashMap的鍵:因爲String是不可變的,當創建String的時候哈希嗎已經計算好了,所以每次在使用字符串的哈希碼的時候就不用再計算一次,更高效。
線程安全:因爲字符串是不可變的,所以一定是線程安全的,不用考慮多線程訪問加鎖的情況。
字符串常量池的需要。
3、String類常用方法
二、String高級
1、字符串常量池
字符串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間的,而且字符串使用的非常多。JVM爲了提高性能和減少內存的開銷,在實例化字符串的時候進行了一些優化:使用字符串常量池。每當創建字符串常量時,JVM會首先檢查字符串常量池,如果該字符串已經存在常量池中,那麼就直接返回常量池中的實例引用。如果字符串不存在常量池中,就會實例化該字符串並且將其放到常量池中。由於String字符串的不可變性,常量池中一定不存在兩個相同的字符串。
字符串常量池在jdk1.7之前是存在方法去的,從1.7之後放到堆裏面了。
2、String test = "abc"和new String(“abc”)區別
String test = “abc”:首先檢查字符串常量池中是否存在此字符串,如果存在則直接返回字符串常量池中字符串的引用,如果不存在則添加此字符串進常量池然後返回此引用。
new String(“abc”):直接在堆中創建字符串返回新創建字符串的引用,每次創建都是一個新的對象。
如下例子:
String str = "bobo"; String str1 = "bobo"; String str2 = new String("bobo"); String str3 = new String("bobo"); System.out.println(str == str1);//true System.out.println(str == str2);//false System.out.println(str2 == str3);//false
3、intern()方法
直接使用雙引號創建出來的String對象會直接存儲在字符串常量池中,new創建的的String對象,可以使用String提供的intern方法。intern 方法是一個native方法,intern方法會從字符串常量池中查詢當前字符串是否存在,如果存在,就直接返回當前字符串;如果不存在就會將當前字符串放入常量池中,之後再返回。
如下例子:
String str = "bobo"; String str2 = new String("bobo").intern(); System.out.println(str == str2);//true
4、+字符串拼接
使用“+”拼接字符串時,JVM會隱式創建StringBuilder對象,每一個拼接就會隱式創建一個StringBuilder對象,當大量字符串拼接時,就會有大量StringBuilder在堆內存中創建,肯定會造成效率的損失,所以一般大量字符串拼接建議用StringBuilder(線程不安全)或StringBuffer(線程安全)。
如下例子看一下兩者效率:
public class MyTest { public static void main(String[] args) { int count = 100000; long startTime = System.currentTimeMillis(); String result = ""; for (int i = 0; i < count; i++) { result += "bobo"; } long endTime = System.currentTimeMillis(); System.out.println(endTime - startTime); long startTime1 = System.currentTimeMillis(); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < count; i++) { stringBuilder.append("bobo"); } long endTime1 = System.currentTimeMillis(); System.out.println(endTime1 - startTime1); } }
String字符串直接相加不會在堆中創建臨時對象,效率高,就不用使用stringbuffer,否則大部分情況就用後者,
5、StringBuilder和StringBuffer拼接字符串
StringBuilder爲了解決String使用”+“大量拼接字符串時產生很多中間對象問題而提供的一個類,提供append和add方法,可以將字符串添加到已有序列的末尾或指定位置,實際上底層都是利用可修改的char數組(JDK 9 以後是 byte數組)來存儲的,當前容量不足時觸發擴容機制。
StringBuffer和StringBuilder的機制一樣,唯一不同點是StringBuffer內部使用synchronized關鍵字來保證線程安全,保證線程安全不可避免的產生了一些額外的開銷,如果不要求線程安全的情況下還是建議優先使用StringBuilder。
StringBuilder和StringBuffer都是繼承AbstractStringBuilder抽象類,裏面實現了字符串拼接的具體邏輯,我們來看一下:
abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value; ... }
數據都是保存在char[]裏面,當容量不足時進行數組的擴容:
private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } private int newCapacity(int minCapacity) { // overflow-conscious code int newCapacity = (value.length << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; } private int hugeCapacity(int minCapacity) { if (Integer.MAX_VALUE - minCapacity < 0) { // overflow throw new OutOfMemoryError(); } return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE; }
從源碼可以看到,當需要容量不足時觸發擴容機制,擴容爲原來的2倍+2的容量,最大數組擴容容量爲:MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,Integer.MAX_VALUE爲2的31次方-1。擴容完後把老的數組裏面的數據拷貝到新的數組裏面。
6、String字符串長度限制
要回答這個問題要分兩個階段看,分別是編譯期和運行期。不同的時期限制不一樣。
編譯期
我們所能想到的是String源碼中定義,存儲String字符的char數組最大是Integer.MAX_VALUE爲2的31次方-1,那麼也就是說可以存儲2147483647個字符,分析到這時是不是以爲大功告成了,其實不然,我們忽略了一點。
我們編譯java文件爲class文件的時候是把字符串放在class的常量池中的,JVM對常量池的容量有嚴格的限制,JVN規定,字符串是由CONSTANTUtf8info類型的結構,CONSTANTUtf8_info的定義如下:
CONSTANT_Utf8_info{
u1 tag;
u2 length;
u1 bytes;
}
其中u1、u2是無符號的分別代表1個字節和2個字節,那麼我們只需要看length最多允許兩個字節的長度,因此理論上允許的的最大長度是2^16=65536。而 java class 文件是使用一種變體UTF-8格式來存放字符的,null 值使用兩個 字節來表示,因此只剩下 65536- 2 = 65534個字節。
所以,在Java中,所有需要保存在常量池中的數據,長度最大不能超過65535,這當然也包括字符串的定義。
運行期
上面提到的這種String長度的限制是編譯期的限制,也就是使用String str= “”;這種字面值方式定義的時候纔會有的限制。
String在運行期的限制,其實就是我們前文提到的那個Integer.MAX_VALUE ,這個值約等於4G,在運行期,如果String的長度超過這個範圍,就會拋出異常。
一、構造方法調用順序
1、何爲構造方法
構造方法是類的一種特殊方法,用來初始化類的一個新的對象,每個類至少有一個構造方法,如果類中沒有顯式定義構造方法,編譯器在編譯的時候會生成一個默認的構造方法,默認的構造方法不包含任何參數,並且方法體爲空。如果類中顯式地定義了一個或多個構造方法,則 Java 不再提供默認構造方法。
構造方法必須具有和類名相同的名稱,而且沒有返回類型。構造方法的默認返回類型就是對象類型本身,並且構造方法不能被 static、final、synchronized、abstract 和 native 修飾。
在一個類中,與類名相同的方法就是構造方法。每個類可以具有多個構造方法,但要求它們各自包含不同的方法參數。
構造方法可以用public、protected、默認、private修飾。
2、子類構造方法調用父類構造方法
子類構造方法要麼調用父類無參構造方法(包括當父類沒有構造方法時。系統默認給的無參構造方法),要麼調用父類有參構造方法。當子類構造方法調用父類無參構造方法,一般都是默認不寫的,要寫的話就是super(),且要放在構造方法的第一句。當子類構造方法要調用父類有參數的構造方法,那麼子類的構造方法中必須要用super(參數)調用父類構造方法,且要放在構造方法的第一句。
看完如下例子對子類父類構造函數調用順序應該就能明白了:
public class Father { private String name; public Father() { System.out.println("父類無參構造方法"); } public Father(String name) { System.out.println("父類有參構造方法 name:" + name); } } public class Son extends Father { public Son() { System.out.println("子類無參構造函數"); } public Son(String name) { super(name); System.out.println("子類有參構造函數"); } public Son(String name, int age) { System.out.println("子類有參構造函數 name and age"); } public static void main(String[] args) { new Son(); System.out.println("------------參數-----------"); new Son("bobo"); System.out.println("------------參數 name and age-----------"); new Son("bobo", 26); } }
父類無參構造方法 子類無參構造函數 ------------參數----------- 父類有參構造方法 name:bobo 子類有參構造函數 ------------參數 name and age----------- 父類無參構造方法 子類有參構造函數 name and age
二、代碼塊執行順序
Java中代碼塊分爲普通代碼塊和靜態代碼塊,普通代碼塊是屬於實例對象的,其實在編譯期編譯器會把普通代碼塊的內容放在構造函數裏面來執行的。靜態代碼塊是屬於類的,是隨着類的初始化而調用的,那麼類是什麼時候初始化呢?請參考之前寫的喫透Java基礎三:觸發類初始化的五種方式
1、普通代碼塊編譯後自動添加到構造函數最上面執行
看下面例子:
public class Son { private String name; { System.out.println("普通代碼塊"); } public Son() { System.out.println("子類無參構造函數"); } public Son(String name) { this.name = name; System.out.println("子類有參構造函數"); } }
編譯後的class文件反編譯:
public class Son { private String name; public Son() { System.out.println("普通代碼塊"); System.out.println("子類無參構造函數"); } public Son(String name) { System.out.println("普通代碼塊"); this.name = name; System.out.println("子類有參構造函數"); } }
2、有繼承關係的靜態代碼塊和普通代碼塊調用順序
只需要記住一點:靜態代碼塊是在類初始化的時候調用的,普通代碼塊是在創建對象實例的時候在構造函數裏面調用的,類初始化一定在創建對象實例之前,所以靜態代碼塊一定在普通代碼塊之前調用。
看如下例子:
public class Father { static { System.out.println("父類靜態代碼塊"); } { System.out.println("父類普通代碼塊"); } public Father() { System.out.println("父類無參構造方法"); } } public class Son extends Father{ { System.out.println("子類普通代碼塊"); } static { System.out.println("子類靜態代碼塊"); } public Son() { System.out.println("子類構造函數"); } public static void main(String[] args) { new Son(); } }
父類靜態代碼塊
子類靜態代碼塊
父類普通代碼塊
父類無參構造方法
子類普通代碼塊
子類構造函數
三、方法的重載與重寫
方法的重寫(Overriding)和重載(Overloading)是java多態性的不同表現,重寫是父類與子類之間多態性的一種表現,重載可以理解成多態的具體表現形式。
1、重載
重載(overloading) 是在一個類裏面,方法名字相同,而參數不同。返回類型可以相同也可以不同。每個重載的方法(或者構造函數)都必須有一個獨一無二的參數類型列表。最常用的地方就是構造器的重載。
規則:
- 被重載的方法必須改變參數列表(參數個數或類型不一樣)。
- 被重載的方法可以改變返回類型。
- 被重載的方法可以改變訪問修飾符。
- 被重載的方法可以聲明新的或更廣的檢查異常。
- 方法能夠在同一個類中或者在一個子類中被重載。
- 無法以返回值類型作爲重載函數的區分標準。
綜上:參數列表區分重載
public class MyTest { public int test(){ System.out.println("test1"); return 1; } public void test(int a){ System.out.println("test2"); } //以下兩個參數類型順序不同 public String test(int a,String s){ System.out.println("test3"); return "test3"; } public String test(String s,int a){ System.out.println("test4"); return "test4"; } }
2、重寫
重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 方法名和形參都不能改變。子類重寫父類方法的規則:
子類重寫的方法的方法名和形參列表與父類被重寫的方法的方法名和形參列表相同。
子類重寫的方法的權限修飾符不小於父類被重寫的方法的權限修飾符,特殊情況:子類不能重寫父類被聲明爲private權限的方法。
返回值類型:
1、父類是void,子類只能是void。
2、父類是A類型,子類是A類或A類的子類。
3、父類是基本數據類型(如:int),子類返回值類型必須相同。
子類重寫的方法拋出的異常的類型不大於父類被重寫的方法拋出的異常類型。
如下例子:
public class Father { public void overrideMethod1(){ } public int overrideMethod2() throws IOException { return 0; } protected List<String> overrideMethod3(){ return null; } } public class Son extends Father{ /** * 父類方法返回值是void,子類重寫此方法返回值只能是void */ @Override public void overrideMethod1() { } /** * 1、父類方法訪問權限是protected,子類重寫時訪問權限可以是public。 * 2、父類方法拋出的異常是IOException,子類重寫方法時拋出的異常可以是其子類FileNotFoundException。 * 3、父類方法返回值是int基本數據類型,子類重寫時返回值類型只能是int。 * @return * @throws FileNotFoundException */ @Override public int overrideMethod2() throws FileNotFoundException { return 0; } /** * 父類方法返回值類型是List,子類重寫方法返回值類型可以是其子類ArrayList。 * @return */ @Override public ArrayList<String> overrideMethod3() { return null; } }