静态代码块
静态代码块的格式:
static { }
public class CodeDemo { public static String schoolName ; public static ArrayList<String> lists = new ArrayList<>(); // 静态代码块,属于类,与类一起加载一次! static { System.out.println("静态代码块被触发执行~~~~~~~"); // 在静态代码块中进行静态资源的初始化操作 schoolName = "张三"; lists.add("3"); lists.add("4"); lists.add("5"); } public static void main(String[] args) { System.out.println("main方法被执行"); System.out.println(schoolName); System.out.println(lists); } } /*静态代码块被触发执行~~~~~~~ main方法被执行 张三 [3, 4, 5] */
实例代码块
实例代码块的格式:
{ }
实例代码块的特点:
实例代码块的作用:实例代码块可以在创建对象之前进行实例资源的初始化操作
public class CodeDemo { private String name; private ArrayList<String> lists = new ArrayList<>(); { name = "代码块"; lists.add("java"); System.out.println("实例代码块被触发执行一次~~~~~~~~"); } public CodeDemo02(){ }//构造方法 public CodeDemo02(String name){} public static void main(String[] args) { CodeDemo c = new CodeDemo();//实例代码块被触发执行一次 System.out.println(c.name); System.out.println(c.lists); new CodeDemo02();//实例代码块被触发执行一次 } }
基本介绍
Object 类是 Java 中的祖宗类,一个类或者默认继承 Object 类,或者间接继承 Object 类,Object 类的方法是一切子类都可以直接使用
Object 类常用方法:
public String toString()
:默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址,例:Student@735b478;
public boolean equals(Object o)
:默认是比较两个对象的引用是否相同protected Object clone()
:创建并返回此对象的副本只要两个对象的内容一样,就认为是相等的:
public boolean equals(Object o) { // 1.判断是否自己和自己比较,如果是同一个对象比较直接返回true if (this == o) return true; // 2.判断被比较者是否为null ,以及是否是学生类型。 if (o == null || this.getClass() != o.getClass()) return false; // 3.o一定是学生类型,强制转换成学生,开始比较内容! Student student = (Student) o; return age == student.age && sex == student.sex && Objects.equals(name, student.name); }
面试题:== 和 equals 的区别
hashCode 的作用:
深浅克隆
深浅拷贝(克隆)的概念:
浅拷贝 (shallowCopy):对基本数据类型进行值传递,对引用数据类型只是复制了引用,被复制对象属性的所有的引用仍然指向原来的对象,简而言之就是增加了一个指针指向原来对象的内存地址
Java 中的复制方法基本都是浅克隆:Object.clone()、System.arraycopy()、Arrays.copyOf()
深拷贝 (deepCopy):对基本数据类型进行值传递,对引用数据类型是一个整个独立的对象拷贝,会拷贝所有的属性并指向的动态分配的内存,简而言之就是把所有属性复制到一个新的内存,增加一个指针指向新内存。所以使用深拷贝的情况下,释放内存的时候不会出现使用浅拷贝时释放同一块内存的错误
Object 的 clone() 是 protected 方法,一个类不显式去重写 clone(),就不能直接去调用该类实例的 clone() 方法
Cloneable 接口是一个标识性接口,即该接口不包含任何方法(包括clone()),但是如果一个类想合法的进行克隆,那么就必须实现这个接口,在使用 clone() 方法时,若该类未实现 Cloneable 接口,则抛出异常
Clone & Copy:Student s = new Student
Student s1 = s
:只是 copy 了一下 reference,s 和 s1 指向内存中同一个 object,对对象的修改会影响对方
Student s2 = s.clone()
:会生成一个新的Student对象,并且和s具有相同的属性值和方法
Shallow Clone & Deep Clone:
浅克隆:Object 中的 clone() 方法在对某个对象克隆时对其仅仅是简单地执行域对域的 copy
深克隆:在对整个对象浅克隆后,对其引用变量进行克隆,并将其更新到浅克隆对象中去
public class Student implements Cloneable{ private String name; private Integer age; private Date date; @Override protected Object clone() throws CloneNotSupportedException { Student s = (Student) super.clone(); s.date = (Date) date.clone(); return s; } //..... }
SDP → 创建型 → 原型模式
Objects 类与 Object 是继承关系。
Objects的方法:
public static boolean equals(Object a, Object b)
: 比较两个对象是否相同。 底层进行非空判断,从而可以避免空指针异常,更安全!!推荐使用!!
public static boolean equals(Object a, Object b) { return a == b || a != null && a.equals(b); }
public static boolean isNull(Object obj)
: 判断变量是否为null ,为null返回true ,反之!
public static String toString(对象)
: 返回参数中对象的字符串表示形式
public static String toString(对象, 默认字符串)
: 返回对象的字符串表示形式。
public class ObjectsDemo { public static void main(String[] args) { Student s1 = null; Student s2 = new Student(); System.out.println(Objects.equals(s1 , s2));//推荐使用 // System.out.println(s1.equals(s2)); // 空指针异常 System.out.println(Objects.isNull(s1)); System.out.println(s1 == null);//直接判断比较好 } } public class Student { }
基本介绍
String 被声明为 final,因此不可被继承 (Integer 等包装类也不能被继承)
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final byte[] value; /** The identifier of the encoding used to encode the bytes in {@code value}. */ private final byte coder; }
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder
来标识使用了哪种编码
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组,并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变,也保证线程安全
注意:不能改变的意思是每次更改字符串都会产生新的对象,并不是对原始对象进行改变
String s = "abc"; s = s + "cd"; //s = abccd 新对象
常用方法
常用 API:
public boolean equals(String s)
: 比较两个字符串内容是否相同、区分大小写
public boolean equalsIgnoreCase(String anotherString)
: 比较字符串的内容,忽略大小写
public int length()
: 返回此字符串的长度
public String trim()
: 返回一个字符串,其值为此字符串,并删除任何前导和尾随空格
public String[] split(String regex)
: 将字符串按给定的正则表达式分割成字符串数组
public char charAt(int index)
: 取索引处的值
public char[] toCharArray()
: 将字符串拆分为字符数组后返回
public boolean startsWith(String prefix)
: 测试此字符串是否以指定的前缀开头
public int indexOf(String str)
: 返回指定子字符串第一次出现的字符串内的索引,没有返回 -1
public int lastIndexOf(String str)
: 返回字符串最后一次出现str的索引,没有返回 -1
public String substring(int beginIndex)
: 返回子字符串,以原字符串指定索引处到结尾
public String substring(int i, int j)
: 指定索引处扩展到 j - 1 的位置,字符串长度为 j - i
public String toLowerCase()
: 将此 String 所有字符转换为小写,使用默认语言环境的规则
public String toUpperCase()
: 使用默认语言环境的规则将此 String 所有字符转换为大写
public String replace(CharSequence target, CharSequence replacement)
: 使用新值,将字符串中的旧值替换,得到新的字符串
String s = 123-78; s.replace("-","");//12378
构造方式
构造方法:
public String()
: 创建一个空白字符串对象,不含有任何内容public String(char[] chs)
: 根据字符数组的内容,来创建字符串对象public String(String original)
: 根据传入的字符串内容,来创建字符串对象直接赋值:String s = "abc"
直接赋值的方式创建字符串对象,内容就是 abc
String str = new String("abc")
创建字符串对象:
new String("a") + new String("b")
创建字符串对象:
对象 1:new StringBuilder()
对象 2:new String("a")、对象 3:常量池中的 a
对象 4:new String("b")、对象 5:常量池中的 b
StringBuilder 的 toString():
@Override public String toString() { return new String(value, 0, count); }
String Pool
基本介绍
字符串常量池(String Pool / StringTable / 串池)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定,常量池类似于 Java 系统级别提供的缓存,存放对象和引用
-XX:StringTableSize
设置大小,JDK 1.8 中默认 60013intern()
JDK 1.8:当一个字符串调用 intern() 方法时,如果 String Pool 中:
JDK 1.6:将这个字符串对象尝试放入串池,如果有就不放入,返回已有的串池中的对象的地址;如果没有会把此对象复制一份,放入串池,把串池中的对象返回
public class Demo { // 常量池中的信息都加载到运行时常量池,这时a b ab是常量池中的符号,还不是java字符串对象,是懒惰的 // ldc #2 会把 a 符号变为 "a" 字符串对象 ldc:反编译后的指令 // ldc #3 会把 b 符号变为 "b" 字符串对象 // ldc #4 会把 ab 符号变为 "ab" 字符串对象 public static void main(String[] args) { String s1 = "a"; // 懒惰的 String s2 = "b"; String s3 = "ab"; // 串池 String s4 = s1 + s2; // 返回的是堆内地址 // 原理:new StringBuilder().append("a").append("b").toString() new String("ab") String s5 = "a" + "b"; // javac 在编译期间的优化,结果已经在编译期确定为ab System.out.println(s3 == s4); // false System.out.println(s3 == s5); // true String x2 = new String("c") + new String("d"); // new String("cd") // 虽然 new,但是在字符串常量池没有 cd 对象,toString() 方法 x2.intern(); String x1 = "cd"; System.out.println(x1 == x2); //true } }
结论:
String s1 = "ab"; // 仅放入串池 String s2 = new String("a") + new String("b"); // 仅放入堆 // 上面两条指令的结果和下面的 效果 相同 String s = new String("ab");
面试问题
问题一:
public static void main(String[] args) { String s = new String("a") + new String("b");//new String("ab") //在上一行代码执行完以后,字符串常量池中并没有"ab" String s2 = s.intern(); //jdk6:串池中创建一个字符串"ab" //jdk8:串池中没有创建字符串"ab",而是创建一个引用指向new String("ab"),将此引用返回 System.out.println(s2 == "ab");//jdk6:true jdk8:true System.out.println(s == "ab");//jdk6:false jdk8:true }
问题二:
public static void main(String[] args) { String str1 = new StringBuilder("58").append("tongcheng").toString(); System.out.println(str1 == str1.intern());//true,字符串池中不存在,把堆中的引用复制一份放入串池 String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2 == str2.intern());//false }
原因:
System 类当调用 Version 的静态方法,导致 Version 初始化:
private static void initializeSystemClass() { sun.misc.Version.init(); }
Version类初始化时需要对静态常量字段初始化,被 launcher_name 静态常量字段所引用的"java"字符串字面量就被放入的字符串常量池:
package sun.misc; public class Version { private static final String launcher_name = "java"; private static final String java_version = "1.8.0_221"; private static final String java_runtime_name = "Java(TM) SE Runtime Environment"; private static final String java_profile_name = ""; private static final String java_runtime_version = "1.8.0_221-b11"; //... }
内存位置
Java 7之前,String Pool 被放在运行时常量池中,它属于永久代;Java 7以后,String Pool 被移到堆中,这是因为永久代的空间有限,在大量使用字符串的场景下会导致OutOfMemoryError 错误
演示 StringTable 位置:
-Xmx10m
设置堆内存10m
在jdk8下设置: -Xmx10m -XX:-UseGCOverheadLimit
(运行参数在Run Configurations VM options)
在jdk6下设置: -XX:MaxPermSize=10m
public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); int i = 0; try { for (int j = 0; j < 260000; j++) { list.add(String.valueOf(j).intern()); i++; } } catch (Throwable e) { e.printStackTrace(); } finally { System.out.println(i); } }
优化常量池
两种方式:
调整 -XX:StringTableSize=桶个数,数量越少,性能越差
intern 将字符串对象放入常量池,通过复用字符串的引用,减少内存占用
/** * 演示 intern 减少内存占用 * -XX:StringTableSize=200000 -XX:+PrintStringTableStatistics * -Xsx500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=200000 */ public class Demo1_25 { public static void main(String[] args) throws IOException { List<String> address = new ArrayList<>(); System.in.read(); for (int i = 0; i < 10; i++) { //很多数据 try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) { String line = null; long start = System.nanoTime(); while (true) { line = reader.readLine(); if(line == null) { break; } address.add(line.intern()); } System.out.println("cost:" +(System.nanoTime()-start)/1000000); } } System.in.read(); } }
不可变好处
String StringBuffer 和 StringBuilder 区别:
相同点:底层使用 char[] 存储
构造方法:
public StringBuilder()
:创建一个空白可变字符串对象,不含有任何内容public StringBuilder(String str)
:根据字符串的内容,来创建可变字符串对象常用API :
public StringBuilder append(任意类型)
:添加数据,并返回对象本身public StringBuilder reverse()
:返回相反的字符序列public String toString()
:通过 toString() 就可以实现把 StringBuilder 转换为 String存储原理:
String str = "abc"; char data[] = {'a', 'b', 'c'}; StringBuffer sb1 = new StringBuffer();//new byte[16] sb1.append('a'); //value[0] = 'a';
append 源码:扩容为二倍
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } private void ensureCapacityInternal(int minimumCapacity) { // 创建超过数组长度就新的char数组,把数据拷贝过去 if (minimumCapacity - value.length > 0) { //int newCapacity = (value.length << 1) + 2;每次扩容2倍+2 value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { // 将字符串中的字符复制到目标字符数组中 // 字符串调用该方法,此时value是字符串的值,dst是目标字符数组 System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }