一、数据类型
8个基本类型,这8个基本类型都有对应的包装类型,基本类型和对象的包装类型之间的赋值可以自动装箱和拆箱。
装箱就是 自动将基本数据类型转换为包装器类型;拆箱就是 自动将包装器类型转换为基本数据类型。
比如 : Integer x=2;//装箱
int y=x; //拆箱
装箱和拆箱如何实现:
装箱调用包装器的ValueOf,拆箱调用xxxValue。xxx代表基本数据类型。
缓存池:
new Integer(123)和Integer.valueOf(123)的区别:
new Integer(123)每次会创建新对象。
Integer.valueOf(123)会使用缓存池中的对象,多次调用会取得同一个对象。
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
问题:缓存池和常量池的区别?
valueOf()的实现就是如果有一样的就返回,没有再新建。
编译器在自动装箱的时候会调用valueOf,因此多个Integer实例使用自动装箱来创建并且值相同,就会引用相同的对象。
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
使用基本类型的包装类型时候,就可以直接使用缓存池中的对象。
对于int,缓存池有-128到127的范围,所以一旦超出范围
Integer I=128;
Integer i2=128;==是false,因为都是new出来的。
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
都是false,因为double实现valueOf的方式与int不同。
:缓存池
https://www.cnblogs.com/Pjson/p/8777940.html
二、String
String是final,不可继承。不可变类是无法修改其实例的类。
Java 8 中String内存使用char数组来存储数据。
public final class String
implements [java.io](http://java.io/).Serializable, Comparable, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
但是java9之后,String的实现使用byte数组存储字符串,同时使用coder来标识使用了哪种编码。
public final class String
implements [java.io](http://java.io/).Serializable, Comparable, 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;
}
value数组被声明为final,这意味着value数组初始化之后就不能再引用其他数组,并且String内部没有改变value数组的方法,因此可以保证String不变。String为什么不能修改而只能改引用的原因。
为什么String不可变?
不可变的好处:
1.缓存HashCode,字符串的hash码在Java中经常使用,在HashMap或者HashSet中,不可变保证哈希码也不变的,意味着每次使用都不需要计算哈希码,效率高。
在String中有以下代码
private int hash;//缓存哈希码
String的hash值,HashMap咋用的?
2.String Pool的需要。如果一个String对象已经被创建过了,那么就会从String Pool直接引用,只有String不可变,String Pool才有意义。
3.安全性,String作为网络连接的参数时,被改变就不安全。
4.线程安全,String不可变性天生具备线程安全,可以在多个线程中安全使用。
5.促进其他类的使用
HashSet
set.add(new String("a"));
set.add(new String("b"));
set.add(new String("c"));
for(String a: set)
a.value = "a";
如果String可变可能会违反set的设计,set包含非重复元素。
https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/网页内容:为什么String不可变?
此外,使用String进行逻辑操作相当慢,并且根本不建议,因为JVM将String转换为字节码中的StringBuffer。浪费了很多开销,从String转换为StringBuffer,然后再转换回String
什么事String的逻辑运算?
字符串常量池保存着所有字符串字面量(liter strings),这些字面量在编译时期就确定,不仅如此,还可以使用String的intern()方法在运行的过程中把字符串添加到String Pool。
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
什么是字面量:
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
“bbb”这种就是字面量,用上述形式会自动的把字符串放到String Pool里面。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
上面这种,s1和s2是新建的两个对象,s1.intern()就是把s1引用的字符串放到String Pool里面,然后返回一个引用。当然,如果池里已经有了就不放了,直接返回。
在Java7之前,String Pool被放在运行时常量池,属于永久代,而在Java7,String Pool被移动到堆中,因为永久代空间有限,在大量字符串的场景下会导致OutOfMemoryError。
new String(“abc”)
使用这个会创建两个字符串对象(前提是String Pool里面没有“abc”字符串对象)
“abc”属于字符串字面量,因此在编译时期会在String Pool中创建一个字符串对象,指向这个“abc”。
而使用new会在堆中新建一个字符串对象。使用String Pool中的字符串对象作为String构造函数的参数。
String 构造函数的源码,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
三、运算
java都是值传递。
float与double:
Java 不能隐式执行向下转型,因为这会使得精度降低。
1.1字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
// float f = 1.1;
1.1f 字面量才是 float 类型。
float f = 1.1f;
隐式类型转换:
字面量1是int,所以不能隐式的将int向下转型为short。
short s1 = 1;
但是,使用+=或者++可以执行隐式类型转换
s1+=1;//相当于s1=(short)(s1+1);
switch不支持long类型,支持'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum’
四、继承:
访问权限应该尽可能让类的内部不被访问:
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,
抽象类和接口:
抽象类一般会包含抽象方法,抽象方法一定位于抽象类中
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
接口的字段默认都是 static 和 final 的。
比较:
从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
搜索:什么是is-a,like-a
接口使用:
需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
需要使用多重继承。
抽象类使用:
需要在几个相关的类中共享代码。
需要能控制继承来的成员的访问权限,而不是都为 public。
需要继承非静态和非常量字段。
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/
https://dzone.com/articles/when-to-use-abstract-class-and-intreface
super:
访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
https://docs.oracle.com/javase/tutorial/java/IandI/super.html
重写和重载:
1.重写:
存在于继承体系中,子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里氏替换原则,重写有以下两个限制:
子类方法的访问权限必须大于父类方法;
子类方法的返回类型必须上父类方法返回类型或子类型;
使用Override注解,可以让编译器检查是否满足上面两个条件。
2.重载:
在同一个类中,指一个方法与已经存在的方法名称相同,但是参数类型、个数、顺序至少有一个不同。
注意:返回值不同,其他都相同不是重载。
五、Object通用方法:
举几个例子:hashcode(),equals(),clone(),notify()等
equals()
1.等价关系
需要满足自反性,x.equals(x); // true
对称性 x.equals(y) == y.equals(x); // true
传递性 if (x.equals(y) && y.equals(z))
x.equals(z); // true;
一致性 多次调用结果不变
3.实现:
1.判断是否为同一个对象的引用,如果是直接返回true
2.判断是否是同一个类型,如果不是,直接返回false
3.将Object对象进行转型,转换成实际类型
4.判断每个关键域是否相等
public class EqualExample {
private int x;
private int y;
private int z;
public EqualExample(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EqualExample that = (EqualExample) o;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
}
}
hashcode()
equals()用来判断两个对象是否等价,等价的两个对象散列值一定相等,但是散列值相等的两个对象不一定等价。
覆盖equals()的时候应该一起覆盖hashcode(),保证等价的两个对象散列值也相等。
如果你只覆盖了equals(),而不覆盖hashCode(),可能让两个相同的对象散列值不相同,
toString(),对象的toString方法
默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
clone()
1.cloneable
clone()是Object的protected方法,它不是public,一个类不显式去重写clone(),就不能直接去调用该类实例的clone方法。
但是呢,如果你这样实现
public class CloneExample {
private int a;
private int b;
@Override
public CloneExample clone() throws CloneNotSupportedException {
return (CloneExample)super.clone();
}
}
CloneExample e1 = new CloneExample();
try {
CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
java.lang.CloneNotSupportedException: CloneExample
以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。
clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
这样就对了
public class CloneExample implements Cloneable {
private int a;
private int b;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2.浅拷贝
拷贝对象和原始对象的引用类型引用同一个对象。
public class ShallowCloneExample implements Cloneable {
private int[] arr;
public ShallowCloneExample() {
arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
@Override
protected ShallowCloneExample clone() throws CloneNotSupportedException {
return (ShallowCloneExample) super.clone();
}
}
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 222
3.深拷贝
拷贝对象和原始对象的恶引用类型引用不同的对象。
public class DeepCloneExample implements Cloneable {
private int[] arr;
public DeepCloneExample() {
arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
@Override
protected DeepCloneExample clone() throws CloneNotSupportedException {
DeepCloneExample result = (DeepCloneExample) super.clone();
result.arr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
result.arr[i] = arr[i];
}
return result;
}
}
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
4.clone()的替代方案
使用clone()拷贝一个对象有风险,Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。看着是用构造函数来实现。
public class CloneConstructorExample {
private int[] arr;
public CloneConstructorExample() {
arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public CloneConstructorExample(CloneConstructorExample original) {
arr = new int[original.arr.length];
for (int i = 0; i < original.arr.length; i++) {
arr[i] = original.arr[i];
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
六、关键字
static
1.静态变量,称为类变量,在内存中只有一个,所有实例用的都是同一个。
2.静态方法,静态方法在类加载的时候存在,不依赖于实例,所以静态方法必须有实现,所以说他不能是抽象方法。
静态方法只能访问所属类的静态字段和静态方法,方法中不能有this和super关键字。
3.静态语句块:静态语句块在类初始化时运行一次。
4.静态内部类
非静态内部类依赖于外部类的实例,而静态内部类不需要
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}
5.初始化顺序
静态变量和静态语句块又闲鱼实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
public static String staticField = "静态变量”;
static {
System.out.println("静态语句块");
}
public String field = "实例变量”;
{
System.out.println("普通语句块");
}
最后才是构造函数的初始化
public InitialOrderTest() {
System.out.println("构造函数");
}
存在继承的情况下,初始化的顺序为:
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)
七、反射
每个类都有一个class对象,包含了与类有关的信息,当编译个新类时,会产生一个同名的.class文件。
类加载相当于加载Class对象,类在第一次使用时才动态加载到JVM中。也可以使用Class.forName("com.mysql.jdbc.Driver”) 这种方式来控制加载,该方法会返回一个Class对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
http://www.importnew.com/7383.ht
类继承了之后,类成员也会被继承嘛?
补充:https://stackoverflow.com/questions/10578984/what-is-java-string-interning
https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123
https://stackoverflow.com/questions/8710619/why-dont-javas-compound-assignment-operators-require-casting
https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java