赞
踩
基本数据类型 | 封装类 |
---|---|
byte/8 | Byte |
short/16 | Short |
int/32 | Integer |
long/64 | Long |
float/32 | Float |
double/64 | Double |
boolean~ | Boolean |
char/16 | Character |
boolean 只有两个值:true、false,可以使⽤ 1 bit 来存储,但是具体⼤⼩没有明确规定。JVM 会在编 译时期将 boolean 类型的数据转换为 int,使⽤ 1 来表示 true,0 表示 false。JVM ⽀持 boolean 数组, 但是是通过读写 byte 数组来实现的。
包装类型:基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使⽤⾃动装箱与拆箱完成。
缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
new Integer(123) 每次都会新建⼀个对象;
Integer.valueOf(123) 会使⽤缓存池中的对象,多次调⽤会取得同⼀个对象的引⽤。
Integer x = 2; // 装箱 调⽤了 Integer.valueOf(2)
int y = x; // 拆箱 调⽤了 X.intValue()Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
valueOf() ⽅法的实现⽐较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
编译器会在⾃动装箱过程调⽤ valueOf() ⽅法,因此多个值相同且值在缓存池范围内的 Integer 实例使⽤ 。⾃动装箱来创建,那么就会引⽤相同的对象。
基本类型对应的缓冲池如下:
boolean values true and false
all byte values
short values between -128 and 127
int values between -128 and 127
char in the range \u0000 to \u007F
在使⽤这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使⽤缓冲池中的对象。
在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax= 来指定这个缓冲池的⼤⼩,该选项在 JVM 初始化的时候会设定⼀个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。
概览
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
在 Java 8 中,String 内部使⽤ char 数组存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
在 Java 9 之后,String 类的实现改⽤ byte 数组存储字符串,同时使⽤ coder 来标识使⽤了哪种编码。
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; }
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引⽤其它数组。并且 String 内部没有改变 value 数组的⽅法,因此可以保证 String 不可变。
不可变的好处
因为 String 的 hash 值经常被使⽤,例如 String ⽤做 HashMap 的 key。不可变的特性可以使得 hash值也不可变,因此只需要进⾏⼀次计算。
如果⼀个 String 对象已经被创建过了,那么就会从 String Pool 中取得引⽤。只有 String 是不可变的,才可能使⽤ String Pool。
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为⽹络连接参数的情况下如果 String 是可变的,那么在⽹络连接过程中,String 被改变,改变 String 的那⼀⽅以为现在连接的是其它主机,⽽实际情况却不⼀定是。
String 不可变性天⽣具备线程安全,可以在多个线程中安全地使⽤。
String, StringBuffer and StringBuilder的异同
String 不可变
StringBuffer 和 StringBuilder 可变
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使⽤ synchronized 进⾏同步
String Pool
字符串常量池(String Pool)保存着所有字符串字⾯量(literal strings),这些字⾯量在编译时期就确定。不仅如此,还可以使⽤ String 的 intern() ⽅法在运⾏过程将字符串添加到 String Pool 中。当⼀个字符串调⽤ intern() ⽅法时,如果 String Pool 中已经存在⼀个字符串和该字符串值相等(使⽤ equals() ⽅法进⾏确定),那么就会返回 String Pool 中字符串的引⽤;否则,就会在 String Pool 中添 加⼀个新的字符串,并返回这个新字符串的引⽤。
下⾯示例中,s1 和 s2 采⽤ new String() 的⽅式新建了两个不同字符串,⽽ s3 和 s4 是通过 s1.intern() 和 s2.intern() ⽅法取得同⼀个字符串引⽤。intern() ⾸先把 “aaa” 放到 String Pool 中,然后返回这个字 符串引⽤,因此 s3 和 s4 引⽤的是同⼀个字符串。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4); // true
如果是采⽤ “bbb” 这种字⾯量的形式创建字符串,会⾃动地将字符串放⼊ String Pool 中。
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
在 Java 7 之前,String Pool 被放在运⾏时常量池中,它属于永久代。⽽在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在⼤量使⽤字符串的场景下会导致 OutOfMemoryError 错误。
new String(“abc”)
使⽤这种⽅式⼀共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。
“abc” 属于字符串字⾯量,因此编译时期会在 String Pool 中创建⼀个字符串对象,指向这个 “abc” 字符串字⾯量;
参数传递
Java 的参数是以值传递的形式传⼊⽅法中,⽽不是引⽤传递。
以下代码中 Dog dog 的 dog 是⼀个指针,存储的是对象的地址。在将⼀个参数传⼊⼀个⽅法时,本质上是将对象的地址以值的⽅式传递到形参中。
public class Dog {
String name;
Dog(String name) {
this.name = name;
}
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
String getObjectAddress() {
return super.toString();
}
}
在⽅法中改变对象的字段值会改变原对象该字段值,因为引⽤的是同⼀个对象。
class PassByValueExample { public static void main(String[] args) { Dog dog = new Dog("A"); func(dog); System.out.println(dog.getName()); // B } private static void func(Dog dog) { dog.setName("B"); }}
但是在⽅法中将指针引⽤了其它对象,那么此时⽅法⾥和⽅法外的两个指针指向了不同的对象,在⼀个指针改变其所指向对象的内容对另⼀个指针所指向的对象没有影响。
public class PassByValueExample {public static void main(String[] args) { Dog dog = new Dog("A"); System.out.println(dog.getObjectAddress()); // Dog@4554617c func(dog); System.out.println(dog.getObjectAddress()); // Dog@4554617c System.out.println(dog.getName()); // A } private static void func(Dog dog) { System.out.println(dog.getObjectAddress()); // Dog@4554617c dog = new Dog("B"); System.out.println(dog.getObjectAddress()); // Dog@74a14482 System.out.println(dog.getName()); // B }}
float与double
Java 不能隐式执⾏向下转型,因为这会使得精度降低。
1.1 字⾯量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
// float f = 1.1;
1.1f 字⾯量才是 float 类型。
float f = 1.1f;
隐式类型转换
因为字⾯量 1 是 int 类型,它⽐ short 类型精度要⾼,因此不能隐式地将 int 类型向下转型为 short 类型。
short s1 = 1;// s1 = s1 + 1;
但是使⽤ += 或者 ++ 运算符会执⾏隐式类型转换。
s1 += 1;s1++;
上⾯的语句相当于将 s1 + 1 的计算结果进⾏了向下转型:
s1 = (short) (s1 + 1);
switch
从 Java 7 开始,可以在 switch 条件判断语句中使⽤ String 对象。
String s = "a";
switch (s) {
case "a":
System.out.println("aaa");
break;
case "b":
System.out.println("bbb");
break;
default:
System.out.println("default");
}
switch 不⽀持 long、float、double,是因为 switch 的设计初衷是对那些只有少数⼏个值的类型进⾏等值判断,如果值过于复杂,那么还是⽤ if ⽐较合适。
default未匹配到才执行。
final
声明数据为常量,可以是编译时常量,也可以是在运⾏时被初始化后不能被改变的常量。
对于基本类型,final 使数值不变;
对于引⽤类型,final 使引⽤不变,也就不能引⽤其它对象,但是被引⽤的对象本身是可以修改的。
final int x = 1;
// x = 2; // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;
声明⽅法不能被⼦类重写。
private ⽅法隐式地被指定为 final,如果在⼦类中定义的⽅法和基类中的⼀个 private ⽅法签名相同,此 时⼦类的⽅法不是重写基类⽅法,⽽是在⼦类中定义了⼀个新的⽅法。
声明类不允许被继承。
static
静态变量:⼜称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接 通过类名来访问它。静态变量在内存中只存在⼀份。
实例变量:每创建⼀个实例就会产⽣⼀个实例变量,它与该实例同⽣共死。
public class A {
private int x; // 实例变量
private static int y; // 静态变量
}// 静态变量}
静态⽅法在类加载的时候就存在了,它不依赖于任何实例。所以静态⽅法必须有实现,也就是说它不能 是抽象⽅法。
public abstract class A {
public static void func1(){
}
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
}
只能访问所属类的静态字段和静态⽅法,⽅法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。
public class A {
private static int x;
private int y;
public static void func1(){
int a = x;
// int b = y; // Non-static field 'y' cannot be referenced from a
static context
// int b = this.y; // 'A.this' cannot be referenced from a
static context
}
}
静态语句块在类初始化时运⾏⼀次。
public class A {
static {
System.out.println("123");
}
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
}
}
123
⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能⽤这个实例去创建⾮静态内部类。⽽静态内部类不需要。
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();
}
}
静态内部类不能访问外部类的⾮静态的变量和⽅法。
import static com.xxx.ClassName.*
在静态导入之前:
public class TestStatic {
public static void main(String[] args) {
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.toHexString(42));
} }
在静态导入之后:
import static java.lang.System.out;
import static java.lang.Integer.*;
public class TestStaticImport {
public static void main(String[] args) {
out.println(MAX_VALUE);
out.println(toHexString(42));
} }
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们
在代码中的顺序。 最后才是构造函数的初始化。 存在继承的情况下,初始化顺序为:
public static String staticField = "静态变量";
static {
System.out.println("静态语句块");
}
public String field = "实例变量";
{
System.out.println("普通语句块");
}
概览
public native int hashCode()
public boolean equals(Object obj)
protected native Object clone() throws CloneNotSupportedException
public String toString()
public final native Class<?> getClass()
protected void finalize() throws Throwable {}
public final native void notify()
public final native void notifyAll()
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
equals()
两个对象具有等价关系,需要满⾜以下五个条件:
Ⅰ ⾃反性
x.equals(x); // true
Ⅱ 对称性
x.equals(y) == y.equals(x); // true
Ⅲ 传递性
if (x.equals(y) && y.equals(z))
x.equals(z); // true;
Ⅳ ⼀致性
多次调⽤ equals() ⽅法结果不变
x.equals(y) == x.equals(y); // true
Ⅴ 与 null 的⽐较
对任何不是 null 的对象 x 调⽤ x.equals(null) 结果都为 false
x.equals(null); // false;
对于基本类型,== 判断两个值是否相等,基本类型没有 equals() ⽅法。
对于引⽤类型,== 判断两个变量是否引⽤同⼀个对象,⽽ equals() 判断引⽤的对象是否等价。
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); //true
System.out.println(x == y); //flase
hashCode()
hashCode() 返回哈希值,⽽ equals() 是⽤来判断两个对象是否等价。等价的两个对象散列值⼀定相同,但是散列值相同的两个对象不⼀定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。
在覆盖 equals() ⽅法时应当总是覆盖 hashCode() ⽅法,保证等价的两个对象哈希值也相等。
HashSet 和 HashMap 等集合类使⽤了 hashCode() ⽅法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现 hashCode() ⽅法。
下⾯的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成⼀样 的,只在集合中添加⼀个对象。但是 EqualExample 没有实现 hashCode() ⽅法,因此这两个对象的哈希值是不同的,最终导致集合添加了两个等价的对象。
EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size()); // 2
理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某⼀位,然后组成⼀个 R 进制的整数。
R ⼀般取 31,因为它是⼀个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移⼀位,最左边的位丢失。并且⼀个数与 31 相乘可以转换成移位和减法: 31*x == (x<<5)-x ,编译器会⾃动进⾏这个优化。
toString()
默认返回 ToStringExample@4554617c 这种形式,其中 @ 后⾯的数值为散列码的⽆符号⼗六进制表示。
//定义一个类
public class ToStringExample {
}
//执行方法
ToStringExample example = new ToStringExample();
System.out.println(example.toString());
ToStringExample@4554617c//输出结果
clone()
clone() 是 Object 的 protected ⽅法,它不是 public,⼀个类不显式去重写 clone(),其它类就不能直接去调⽤该类实例的 clone() ⽅法。
public class CloneExample {
private int a;
private int b; }
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in
'java.lang.Object'
重写 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
拷⻉对象和原始对象的引⽤类型引⽤不同对象。
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
使⽤ 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
权限修饰符
private:(同一个类中使用)。
缺省(default):(同一个包中使用)。
protected:(不同包中的子类可以使用)。
public:(同一个工程中使用)。
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进⾏通信,⼀个模块不需要知道其他模块的内部⼯作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
如果⼦类的⽅法重写了⽗类的⽅法,那么⼦类中该⽅法的访问级别不允许低于⽗类的访问级别。这是为了确保可以使⽤⽗类实例的地⽅都可以使⽤⼦类实例去代替,也就是确保满⾜⾥⽒替换原则。
字段决不能是公有的,因为这么做的话就失去了对这个字段修改⾏为的控制,客户端可以对其随意修改。可以使⽤公有的 getter 和 setter ⽅法来替换公有字段,这样的话就可以控制对字段的修改⾏为。
抽象类
抽象类和抽象⽅法都使⽤ abstract 关键字进⾏声明。如果⼀个类中包含抽象⽅法,那么这个类必须声明为抽象类。
抽象类和普通类最⼤的区别是,抽象类不能被实例化,只能被继承。
接⼝
接⼝是抽象类的延伸,在 Java 8 之前,它可以看成是⼀个完全抽象的类,也就是说它不能有任何的⽅法实现。
从 Java 8 开始,接⼝也可以拥有默认的⽅法实现,这是因为不⽀持默认⽅法的接⼝的维护成本太⾼了。在 Java 8 之前,如果⼀个接⼝想要添加新的⽅法,那么要修改所有实现了该接⼝的类,让它们都实现新增的⽅法。
接⼝的成员(字段 + ⽅法)默认都是 public 的,并且不允许定义为 private 或者 protected。从 Java 9开始,允许将⽅法定义为 private,这样就能定义某些复⽤的代码⼜不会把⽅法暴露出去。
接⼝的字段默认都是 static 和 final 的。
public interface InterfaceExample {
void func1();
default void func2(){
System.out.println("func2");
}
int x = 123;
// int y; // Variable 'y' might not have been initialized
public int z = 0; // Modifier 'public' is redundant for interface fields
// private int k = 0; // Modifier 'private' not allowed here
// protected int l = 0; // Modifier 'protected' not allowed here
// private void fun3(); // Modifier 'private' not allowed here
}
比较
接口和抽象类都不能实例化
类可以实现多个接口,弥补了Java单继承的局限性。
接⼝的字段只能是 static 和 final 类型的,⽽抽象类的字段没有这种限制。
使⽤选择
使⽤接⼝:
需要让不相关的类都实现⼀个⽅法,例如不相关的类都可以实现 Comparable 接⼝中的 compareTo() ⽅法;
需要使⽤多重继承。
使⽤抽象类:
需要在⼏个相关的类中共享代码。
需要能控制继承来的成员的访问权限,⽽不是都为 public。
需要继承⾮静态和⾮常量字段。
在很多情况下,接⼝优先于抽象类。因为接⼝没有抽象类严格的类层次结构要求,可以灵活地为⼀个类添加⾏为。
super
访问⽗类的构造函数:可以使⽤ super() 函数访问⽗类的构造函数,从⽽委托⽗类完成⼀些初始化的⼯作。应该注意到,⼦类⼀定会调⽤⽗类的构造函数来完成初始化⼯作,⼀般是调⽤⽗类的默认构造函数,如果⼦类需要调⽤⽗类其它构造函数,那么就可以使⽤ super() 函数。
访问⽗类的成员:如果⼦类重写了⽗类的某个⽅法,可以通过使⽤ super 关键字来引⽤⽗类的⽅法实现。
public class SuperExample {
protected int x;
protected int y;
public SuperExample(int x, int y) {
this.x = x;
this.y = y;
}
public void func() {
System.out.println("SuperExample.func()");
}
}
public class SuperExtendExample extends SuperExample {
private int z;
public SuperExtendExample(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
public void func() {
super.func();
System.out.println("SuperExtendExample.func()");
}
}
SuperExample e = new SuperExtendExample(1, 2, 3);
e.func();
SuperExample.func()
SuperExtendExample.func()
重写
存在于继承体系中,指⼦类实现了⼀个与⽗类在⽅法声明上完全相同的⼀个⽅法。 为了满⾜⾥式替换原则,重写有以下三个限制:
⼦类⽅法的访问权限必须⼤于等于⽗类⽅法;
⼦类⽅法的返回类型必须是⽗类⽅法返回类型或为其⼦类型。
⼦类⽅法抛出的异常类型必须是⽗类抛出异常类型或为其⼦类型。
使⽤ @Override 注解,可以让编译器帮忙检查是否满⾜上⾯的三个限制条件。
重写方法调用优先级:本类–>父类 -->接口(如果多个接口实现了该方法,则接口冲突(报错))
重载
存在于同⼀个类中,指⼀个⽅法与已经存在的⽅法名称上相同,但是参数类型、个数、顺序⾄少有⼀个不同。
应该注意的是,返回值不同,其它都相同不算是重载。
Java反射的概念
程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Class类
每一个类都有一个Class对象,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。
将一个类加载到Java虚拟机中需要经历三个阶段:加载->链接(验证、准备,解析)->初始化。
1. **加载**:这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法区的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。
2. **链接:**
2.1.**验证**:验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求。
2.2.**准备**:为静态域分配存储空间并设置类变量的初始值(默认值),
2.3.**解析**:将常量池中的符号引用转化为直接引用。
3. **初始化**:类的初始化顺序 :父类(静态变量、静态代码块)–>子类(静态变量、静态代码块)–>父类(变量、代码块)–> 父类构造器–>子类(变量、初始化块)–>子类构造器。注意:静态代码和静态变量同级,变量和代码块同级。谁在前先执行谁。类只会初始化一次。
补充:静态初始化,是在加载类的时候初始化。而非静态初始化,是new类实例对象的时候加载。
1、获取类的Class对象
方式 | 说明 |
---|---|
Class.forName(“类的全限定名”) | 如果类没有加载,加载类,并做类的静态初始化。 |
对象.getClass() | 对象已经存在,说明类已经初始化了。多态问题:Parent son = new Son();返回的是:对象的实际类型的Class,即class Son |
类名.class | 不会初始化类。 |
包装类.TYPE | 返回的是:对应的基本数据类型的Class对象 |
注意:基本数据类型的Class对象和包装类的Class对象是不一样的
2、获取类的Fields
方式 | 说明 |
---|---|
Field getField(String name) | 返回该类和其所有父类指定的一个公有属性,不能获取私有的属性 |
Field[] getFields() | 返回该类和其所有父类的所有公有属性,不能获取私有的属性 |
Field getDeclaredField(String name) | 返回该类声明的指定一个属性(包括私有属性)。不能获取父类声明的属性 |
Field[] getDeclaredFields() | 返回该类声明的所有属性(包括私有属性)。不能获取父类声明的属性 |
测试代码:
public class Test { public static void main(String[] args) { Class son = new Son().getClass(); try { System.out.println("----返回该类和其所有父类指定的一个公有属性---"); System.out.println(son.getField("a")); System.out.println("----返回该类声明的指定一个属性---"); System.out.println(son.getDeclaredField("sb")); } catch (NoSuchFieldException e) { e.printStackTrace(); } System.out.println("----返回该类和其所有父类的所有公有属性---"); Field[] fields = son.getFields(); for (int i=0;i<fields.length;i++){ System.out.println(fields[i]); } System.out.println("----返回该类声明的所有属性(包括私有属性)---"); Field[] declaredFields = son.getDeclaredFields(); for (int i=0;i<declaredFields.length;i++){ System.out.println(declaredFields[i]); } } } class Parent { public String a; private String b; } class Son extends Parent { public String sa; private String sb; }
运行结果:----返回该类和其所有父类指定的一个公有属性—
public java.lang.String Parent.a
----返回该类声明的指定一个属性—
private java.lang.String Son.sb
----返回该类和其所有父类的所有公有属性—
public java.lang.String Son.sa
public java.lang.String Parent.a
----返回该类声明的所有属性(包括私有属性)—
public java.lang.String Son.sa
private java.lang.String Son.sb
补充:如果子类和父类定义了一样的属性,获得的属性是子类的属性。
3、获取类的Method
方式 | 说明 |
---|---|
Method getMethod(String name,Class<?>… parameterTypes) | 返回该类和其所有父类指定的一个公有方法,不能获取私有的方法 |
Method[] getMethods() | 返回该类和其所有父类的所有公有方法,不能获取私有的方法 |
Method getDeclaredMethod(Stringname,Class<?>… parameterTypes) | 返回该类声明的指定一个方法(包括私有方法)。不能获取父类声明的方法 |
Method[] getDeclaredMethods() | 返回该类声明的所有方法(包括私有方法)。不能获取父类声明的方法 |
补充:如果子类重写了父类的方法,获取的是子类的方法。
4、获取类的Constructor
方式 | 说明 |
---|---|
Constructor getConstructor(Class<?>… parameterTypes) | 返回该类指定的一个公有构造器,不能获取私有的构造器 |
Constructor<?>[] getConstructors() | 返回该类的所有公有构造器,不能获取私有的方法 |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回该类声明的指定一个构造器(包括私有构造器) |
Constructor<?>[] getDeclaredConstructors() | 返回该类声明的所有构造器(包括私有构造器) |
补充:上述的每个方法都不能获取到父类的构造器。
代码测试
public class Test { public static void main(String[] args) { Class son = new Son().getClass(); try { System.out.println("----返回该类指定的一个公有构造器---"); System.out.println(son.getConstructor(String.class)); System.out.println("----返回该类声明的指定一个构造器---"); System.out.println(son.getDeclaredConstructor(int.class)); } catch (NoSuchMethodException e) { e.printStackTrace(); } System.out.println("----返回该类的所有公有构造器---"); Constructor[] constructors = son.getConstructors(); for (int i=0;i<constructors.length;i++){ System.out.println(constructors[i]); } System.out.println("----返回该类声明的所有构造器---"); Constructor[] declaredConstructors = son.getDeclaredConstructors(); for (int i=0;i<declaredConstructors.length;i++){ System.out.println(declaredConstructors[i]); } } } class Parent { public double a; private boolean b; public Parent() { } public Parent(double a, boolean b) { this.a = a; this.b = b; } } class Son extends Parent { public String sa; private int sb; public Son() { } private Son(int sb) { this.sb = sb; } public Son(String sa) { this.sa = sa; } public Son(String sa, int sb) { this.sa = sa; this.sb = sb; } }
运行结果:----返回该类指定的一个公有构造器—
public Son(java.lang.String)
----返回该类声明的指定一个构造器—
private Son(int)
----返回该类的所有公有构造器—
public Son(java.lang.String,int)
public Son(java.lang.String)
public Son()
----返回该类声明的所有构造器—
public Son(java.lang.String,int)
public Son(java.lang.String)
private Son(int)
public Son()
java.lang.reflect中常用类
常用类 | 说明 |
---|---|
Field类 | 提供一个类的域的信息以及访问类的域的接口。 |
Method类 | 提供一个类的方法的信息以及访问类的方法的接口。 |
Constructor类 | 提供一个类的构造函数的信息以及访问类的构造函数的接口。 |
Proxy类 | 提供动态地生成代理类和类实例的静态方法。 |
Array类 | 该类提供动态地生成和访问JAVA数组的方法。 |
AccessibleObject类 | 该类是域(field)对象、方法(method)对象、构造函数(constructor)对象的基础类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。 |
Proxy类 | 提供动态地生成代理类和类实例的静态方法。 |
注意:上述获取类的Fields,Method,Constructor的返回值是Fields类对象(访问类的域的接口),Method类对象(访问类的方法的接口),Constructor类对象(访问类的构造函数的接口)
如何使用反射
1、通过反射创建类的实例
1. 调用类的Class对象的newInstance方法,该方法会调用对象的默认构造器,如果没有默认构造器,会调用失败.
2. 调用默认Constructor对象的newInstance方法
3. 调用带参数Constructor对象的newInstance方法
测试代码
public class Test { public static void main(String[] args) { try { System.out.print("调用类的Class对象的newInstance方法,创建类实例:"); System.out.println(Class.forName("Son").newInstance()); System.out.print("调用默认Constructor对象的newInstance方法,创建类实例:"); System.out.println(Class.forName("Son").getDeclaredConstructor().newInstance()); System.out.print("调用带参数Constructor对象的newInstance方法,创建类实例:"); System.out.println(Class.forName("Son").getDeclaredConstructor(String.class,int.class).newInstance("唐三",20)); System.out.print("调用私有Constructor对象的newInstance方法,创建类实例:"); Constructor<?> son = Class.forName("Son").getDeclaredConstructor(String.class); son.setAccessible(true); System.out.println(son.newInstance("小舞")); } catch (Exception e) { e.printStackTrace(); } } } class Son { public String sa; private int sb; public Son() { } private Son(String sa) { this.sa = sa; } public Son(String sa, int sb) { this.sa = sa; this.sb = sb; } }
运行结果:
调用类的Class对象的newInstance方法,创建类实例:Son@1540e19d
调用默认Constructor对象的newInstance方法,创建类实例:Son@677327b6
调用带参数Constructor对象的newInstance方法,创建类实例:Son@14ae5a5
调用私有Constructor对象的newInstance方法,创建类实例:Son@7f31245a
补充:对于私有的属性,方法,构造器的访问我们需要将setAccessible()方法设置为true。取消默认 Java 语言访问控制检查的能力。
2、调用类的函数
调用Invoke方法执行函数。invoke方法的参数,第一个是调用该方法的对象,后面是方法形参。
测试代码
public class Test { public static void main(String[] args) { try { Class<Son> sonClass = (Class<Son>) Class.forName("Son"); Son son = sonClass.newInstance(); //通过反射执行私有方法 Method prm = sonClass.getDeclaredMethod("privateMethod",String.class); prm.setAccessible(true);//取消默认 Java 语言访问控制检查的能力。 prm.invoke(son,"测试方法执行了"); //通过反射执行公有方法 Method pum = sonClass.getDeclaredMethod("publicMethod",String.class); pum.invoke(son,"测试方法执行了"); } catch (Exception e) { e.printStackTrace(); } } } class Son { public String sa; private int sb; private void privateMethod(String a){ System.out.println("私有"+a); } public void publicMethod(String s){ System.out.println("公有"+s); } }
运行结果:私有测试方法执行了
公有测试方法执行了
3、设置/获取类的属性值
1. 通过set()设置属性值
2. 通过get(son)获取属性值
同样需要指明是哪个对象设置/获取类的属性值。
测试代码
public class Test { public static void main(String[] args) { try { Class<Son> sonClass = (Class<Son>) Class.forName("Son"); Son son = sonClass.newInstance(); //通过反射设置获取公有属性 Field sa = sonClass.getDeclaredField("sa"); sa.set(son,"宁荣荣");//设置公有属性 System.out.println(sa.get(son));//获取公有属性 //通过反射设置获取私有属性 Field sb = sonClass.getDeclaredField("sb"); sb.setAccessible(true); sb.set(son,18);//设置私有属性 System.out.println(sb.get(son));//获取私有属性 } catch (Exception e) { e.printStackTrace(); } } } class Son { public String sa; private int sb; }
运行结果:宁荣荣 18
任何可以作为异常抛出的类,分为两种: Error和 Exception。其中 Error ⽤来表示 JVM ⽆法处理的错误,Exception 分为两种:
异常的处理过程:抓抛模型
throw new 异常类名(参数);//throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
示例:
public static int div(int a,int b)
{
if(b==0)
throw new ArithmeticException("异常信息:除数不能为0");//抛出具体问题,编译时不检测
return a/b;
}
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2 ... { }//用在方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常
示例:
public static void readFile() throws FileNotFoundException {
InputStream is = new FileInputStream("E:/iodemo/ch01.txt");
}
try {
... //监视代码执行过程,一旦返现异常则直接跳转至catch,
// 如果没有异常则直接跳转至finally
} catch (Exception e) {
... //可选执行的代码块,如果没有任何异常发生则不会执行;
//如果发现异常则进行处理或向上抛出。
//如有继承关系,子类在上
} finally {
... //必选执行的代码块,不管是否有异常发生。
}
自定义异常
除了JDK定义好的异常类外,在开发过程中根据业务的异常情况自定义异常类。
public class UserNotExistsException extends RuntimeException{
public UserNotExistsException() {
super();
}
public UserNotExistsException(String message) {
super(message);
}
}
一个被举了无数次的例子:
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);//此处会抛出ClassCastException
Log.d("泛型测试","item = " + item);
}
我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在编译阶段,编译器就会报错
在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,泛型信息不会进入到运行时阶段。
泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);
Generic generic = new Generic("111111");
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
class FruitGenerator<T> implements Generator<T>
class FruitGenerator implements Generator<String>
// 如果不声明泛型,如:
class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
泛型通配符
通配符?:相当于Object可以放任何类型。可以解决当具体类型不确定的时候,这个通配符就是 ? 。
public void showKeyValue1(Generic<?> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
限制条件的通配符的使用
泛型方法
权限修饰符与 返回值中间非常重要,可以理解为声明此方法为泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。方法体中使用到泛型的位置都指定为调用方法时的泛型类型。
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}
Object obj = genericMethod(Class.forName("com.test.test"));
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
类中的泛型方法
当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下
public class GenericFruit { class Fruit{ @Override public String toString() { return "fruit"; } } class Apple extends Fruit{ @Override public String toString() { return "apple"; } } class Person{ @Override public String toString() { return "Person"; } } class GenerateTest<T>{ public void show_1(T t){ System.out.println(t.toString()); } //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。 //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。 public <E> void show_3(E t){ System.out.println(t.toString()); } //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。 public <T> void show_2(T t){ System.out.println(t.toString()); } } public static void main(String[] args) { Apple apple = new Apple(); Person person = new Person(); GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>(); //apple是Fruit的子类,所以这里可以 generateTest.show_1(apple); //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person //generateTest.show_1(person); //使用这两个方法都可以成功 generateTest.show_2(apple); generateTest.show_2(person); //使用这两个方法也都可以成功 generateTest.show_3(apple); generateTest.show_3(person); } }
泛型方法与可变参数
再看一个泛型方法和可变参数的例子:
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型测试","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
静态方法与泛型
类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法。
public class StaticGenerator<T> {
public static <T> void show(T t){
}
}
什么是注解?
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
注解的用处
注解的原理
注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。
元注解
java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Retention – 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
@Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
● ElementType.CONSTRUCTOR: 用于描述构造器
● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE: 用于描述局部变量
● ElementType.METHOD: 用于描述方法
● ElementType.PACKAGE: 用于描述包
● ElementType.PARAMETER: 用于描述参数
● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。
@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。
自定义注解
自定义注解类编写的一些规则:
\1. Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
\2. 参数成员只能用public 或默认(default) 这两个访问权修饰
\3. 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
\4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
\5. 注解也可以没有定义成员,,不过这样注解就没啥用了
PS:自定义注解需要使用到元注解
自定义注解实例
FruitName.java
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
FruitColor.java
// 水果颜色注解 @Target(FIELD) @Retention(RUNTIME) @Documented public @interface FruitColor { /** * 颜色枚举 */ public enum Color{ BLUE,RED,GREEN}; /** * 颜色属性 */ Color fruitColor() default Color.GREEN; }
FruitProvider.java
/** * 水果供应者注解 */ @Target(FIELD) @Retention(RUNTIME) @Documented public @interface FruitProvider { /** * 供应商编号 */ public int id() default -1; /** * 供应商名称 */ public String name() default ""; /** * 供应商地址 */ public String address() default ""; }
FruitInfoUtil.java
/** * 注解处理器 */ public class FruitInfoUtil { public static void getFruitInfo(Class<?> clazz){ String strFruitName=" 水果名称:"; String strFruitColor=" 水果颜色:"; String strFruitProvicer="供应商信息:"; Field[] fields = clazz.getDeclaredFields(); for(Field field :fields){ if(field.isAnnotationPresent(FruitName.class)){ FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class); strFruitName=strFruitName+fruitName.value(); System.out.println(strFruitName); } else if(field.isAnnotationPresent(FruitColor.class)){ FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class); strFruitColor=strFruitColor+fruitColor.fruitColor().toString(); System.out.println(strFruitColor); } else if(field.isAnnotationPresent(FruitProvider.class)){ FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class); strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address(); System.out.println(strFruitProvicer); } } } }
Apple.java
import test.FruitColor.Color; /** * 注解使用 */ public class Apple { @FruitName("Apple") private String appleName; @FruitColor(fruitColor=Color.RED) private String appleColor; @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦") private String appleProvider; public void setAppleColor(String appleColor) { this.appleColor = appleColor; } public String getAppleColor() { return appleColor; } public void setAppleName(String appleName) { this.appleName = appleName; } public String getAppleName() { return appleName; } public void setAppleProvider(String appleProvider) { this.appleProvider = appleProvider; } public String getAppleProvider() { return appleProvider; } public void displayName(){ System.out.println("水果的名字是:苹果"); } }
FruitRun.java
/**
* 输出结果
*/
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
运行结果是:
水果名称:Apple
水果颜色:RED
供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。