在线目录生成工具

目录

0.前言

个人学习、整理和记录Java基础相关知识点用。其中大部分内容来自以下地址,表示感谢。
CS-Notes

1.数据类型

基本类型

基本类型:byte/8 char/16 short/16 int/32 float/32 long/64 double/64 boolean/~
boolean只有两个值,true、false,可以使用1bit来存储,但是具体大小没有明确规定。JVM会在编译时期将boolean类型的数据装换为int,1表示true,0表示false。JVM支持boolean数组,但是是通过读写byte数组来实现的。

包装类型

基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值,是使用自动装箱与自动拆箱完成的。装箱是基本数据类型装换为包装类型,拆箱是包装类型装换为基本数据类型。

Integer x = 2;     // 装箱 调用了 Integer.valueOf(2)
int y = x;         // 拆箱 调用了 X.intValue()

缓存池

对于基本数据类型,在虚拟机中会有部分缓存,在使用这些基本类型对应的包装类型时,如果该数值范围在缓存范围内,就可以直接使用缓存池中的对象。范围如下:
boolean : true false;
byte : all byte values;
short : [-128,127];
int : [-128,127];
char : [\u0000 , \u007F]。

new Integer(123)与Integer.valueOf(123)的区别在于:
new Integer(123)每次都会新建对象;
Integer.valueOf(123)会使用缓存池中的对象,多次调用会取得同一个对象的引用。

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实例使用自动装箱来创建,那么就会引用相同的对象。

Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true

在jdk1.8所有的数值缓存池中,Integer的缓存池IntegerCache很特殊,这个缓存池的下界是-128,上界默认是127,但是这个上界是可调的,在启动jvm的时候,通过-XX:AutoBoxCacheMax=来指定缓存池的大小,该选项在JVM初始化的时候会设定一个名为java.lang.IntegerCache.high系统属性,然后IntegerCache初始化的时候就会读取该系统属性来决定上界。

2.String

String被申明为final,因此不可被继承。(其他包装类也不能被继承)
在Java8中,String内部使用char数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在Java9之后,String类的实现改成byte数组存储字符串,同时使用coder来标识使用了哪种编码。这一改动使得某些场景下的String字符串内存减少一半,进而减少GC次数。具体看这里 还有这里

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不可变的好处

1.可以缓存hash值
因为String的hash值经常被使用,例如String用做HashMap的key。不可变的特性可以使得hash值也不可变,因此只需要进行一次计算。
2.String Pool的需要
如果一个String对象已经被创建过了,那么就会从String Pool中取得引用。只有String是不可变的,才可能使用String Pool。
3.安全性
String经常作为参数,String不可变性可以保证参数不可变。例如在作为网络连接参数的情况下,如果String是可变的,那么在网络连接过程中,String被改变,改变String的那一方以为现在连接的是其他主机,而实际情况却不一定是。(没有看懂啥意思。。。)
4.线程安全
String不可变性天生具备线程安全,可以在多个线程中安全使用。

String,StringBuffer 和 StringBuilder

1.可变性
String不可变;
StringBuffer和StringBuilder可变
2.线程安全
String线程安全;
StringBuffer线程安全,内部使用synchronized进行同步;
StringBuilder不是线程安全的。

String Pool

String Pool字符串常量池保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用String的intern()方法在运行过程中将字符串添加到String Pool中。

当一个字符串调用intern()方法时,如果String Pool中已经存在一个字符串和该字符串值相等(使用equals()方法进行确定),那么就返回StringPool中字符串的引用;否则,就会在StringPool中添加一个新的字符串,并返回这个新字符串的引用。

下面示例中,s1和s2采用new String()的方式新建里两个不同字符串,而s3和s4是通过s1.intern()和s2.intern()方法取得同一个字符串引用。intern()首先把"aaa"放到StringPool中,然后返回这个字符串引用,因此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”这种字面量的形式创建字符串,会自动将字符串放入StringPool中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true

在Java7之前,StringPool被放在运行时常量池中,它属于永久代。而在Java7,StringPool被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致OOM错误。

new String("abc")

使用这种方式一共会创建两个字符串对象(前提是StringPool中还没有“abc”字符串对象)。
1.“abc”属于字符串字面量,因此编译器会在StringPool中创建一个字符串对象,指向这个“abc”字符串字面量;
2.而使用new的方式会在堆中创建一个字符串对象。

3.运算

参数传递

Java的参数是以值传递的形式传入方法中,而不是引用传递。
当参数类型是一个非基本类型时,传递的是一个指针,存储的是对象的地址,在将一个参数传入一个方法时,本质上是将对象的地址以值的形式传递到形参中。这时,在方法中改变对象的字段值会改变原对象该字段值,因为引用的是同一个对象。
但是,在方法中将指针引用了其他对象,那么此时方法里和方法外的两个指针指向了不同的对象,在一个指针改变其所指对象的内容对另外一个指针指向的对象没有影响。

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变量,因为这是向下转型。
1.1f字面量才是float类型。

// float f = 1.1;
float f = 1.1f;

隐式类型转换

1的字面量是int类型,它比short类型精度要高,因此不能隐式地将int类型向下转型为short类型。

short s1 = 1;
// s1 = s1 + 1;

但是使用+=或者++运算符,会执行隐式类型转换。相当于将s1+1的计算结果进行了向下转型。

s1 += 1;
s1++;

s1 = (short) (s1 + 1);

switch

从Java7开始,可以在switch条件判断语句中使用String对象。switch不支持long、float、double,是因为switch的设计初衷是对那些只有少数几个值得类型进行等值判断,如果值过于复杂,那么还是用if比较合适。

4.关键字

final

1.数据
申明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能改变的常量。
对于基本类型,final使数值不变;
对于引用类型,final是引用不变,也就不能引用其他对象,但是被引用的对象本身是可以修改的。

final int x = 1;
// x = 2;  // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;

2.方法
申明方法不能被子类重写。
private方法隐式地被指定为final,如果在子类中定义的方法和基类中的一个private方法签名相同,此时子类不是重写基类方法,而是在子类中定义了一个新的方法。

3.类
申明类不允许被继承。

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.静态导包
在使用静态变量和方法时,不用再指明ClassName,从而简化代码,但可读性降低。

import static com.xxx.ClassName.*

6.初始化顺序
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于他们在代码中的顺序。最后才是构造函数的初始化。

public static String staticField = "静态变量";

static {
    System.out.println("静态语句块");
}

public String field = "实例变量";

{
    System.out.println("普通语句块");
}

public InitialOrderTest() {
    System.out.println("构造函数");
}

存在继承的情况下,初始化顺序为:父类静态变量、静态语句块 --》 子类静态变量、静态语句块 --》 父类实例变量、普通语句块 --》父类构造函数 --》 子类实例变量、普通语句块 --》子类构造函数。

5.Object通用方法

概览

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()

等价关系equals
两个对象具有等价关系,需要满足一下五个条件:
1.自反性

x.equals(x); // true

2.对称性

x.equals(y) == y.equals(x); // true

3.传递性

if (x.equals(y) && y.equals(z))
    x.equals(z); // true;

4.一致性
多次调用equals()方法结果不变

x.equals(y) == x.equals(y); // true

5.与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);      // false

实现
检查是否为同一个对象的引用,如果是直接返回true;
检查是否是为空,如果为空,直接返回false;
检查是否是同一类型,如果不是,直接返回false;
将Object对象进行转型,然后判断每个关键域是否相等。

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()

hashCode()返回哈希值,而equals()是用来判断两个对象是否等价。等价的两个对象哈希值一定相同,但是哈希值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。

在覆盖equals()方法时应当总是覆盖hashCode()方法,保证等价的两个对象哈希值也相等。

HashSet和HashMap等集合类使用了hashCode()方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现hashCode()方法。

下面的代码中,新建里两个等价的对象,并将他们添加到HashSet中。我们希望将这两个对象当成一样的,只在集合中添加一个对象。但是因为没有实现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@455ce4这种形式,其中@后面的数值为散列码的无符号十六进制表示。

clone()

1.cloneable
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.浅拷贝和深拷贝
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性值是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址。因为两个对象指向同一地址,所以其中一个对象改变,另外一个也会改变。
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,修改新对象不会影响原对象。

6.继承

访问权限

Java中有三个访问权限修饰符:private、protected 和 public,如果不加访问修饰符,表示包级可见。
可以对类或类中的成员(字段和方法)加上访问修饰符。类可见表示其他类可以用这个类创建实例对象。成员可见表示其他类可以用这个类的实例对象访问到该成员。

protected用于修饰成员,表示在继承体系中成员对于子类可见,但是这个修饰符对于类没有意义。

设计良好的模块会隐藏所有的实现细节,把他的API与它的实现清晰地隔离开来。模块之间只通过他们的API进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例去替代,也就是确保里氏替换原则。

抽象类与接口

1.抽象类
抽象类和抽象方法都使用abstract关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

2.接口
接口是抽象类的延伸,在Java8之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。从Java8开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口维护成本太高。在Java8之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让他们都实现新增的方法。
接口的成员(字段+方法)默认是public,并且不允许定义为private或者protected。从Java9开始,允许将方法定义为private,这样就能定义某些复用的代码又不会把方法暴露出去。
接口的字段默认都是static和final的。

3.比较
从设计层面上看,抽象类提供了一种IS-A关系,需要满足里氏替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种LIKE-A关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有IS-A关系。
从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
接口的字段只能是static和final类型的,而抽象类的字段没有这种限制。
接口的成员只能是public,而抽象类的成员可以有多种访问权限。
多数情况下,接口优于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。

super

访问父类的构造函数:可以使用super()函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类的其他构造函数,那么就可以使用spure()函数。
访问父类的成员:如果子类重写了父类的某个方法,可以通过使用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();

//out
SuperExample.func()
SuperExtendExample.func()

重写与重载

1.重写(Override)
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的方法。为了满足里氏替换原则,重写有以下三个限制:
子类方法的访问权限必须大于等于父类方法;
子类方法的返回类型必须是父类方法返回类型或为其子类型;
子类方法抛出的异常必须是父类抛出异常类型或为其子类型。

使用@Override注解,可以让编译器帮忙检查是否满足上面的三个限制条件。下面的示例中,SubClass为SuperClass的子类,SubClass重写了SuperClass的func()方法。其中:
子类方法访问权限为public,大于父类的protected;
子类的返回类型为ArrayList,是父类返回类型List的子类;
子类抛出的异常类型为Exception,是父类抛出异常Throwable的子类。
子类重写方法使用@Override注解,从而让编译器自动检查是否满足限制条件。

class SuperClass {
    protected List<Integer> func() throws Throwable {
        return new ArrayList<>();
    }
}

class SubClass extends SuperClass {
    @Override
    public ArrayList<Integer> func() throws Exception {
        return new ArrayList<>();
    }
}

在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
this.func(this) -> super.func(this) -> this.func(super) -> super.func(super)。

class A {

    public void show(A obj) {
        System.out.println("A.show(A)");
    }

    public void show(C obj) {
        System.out.println("A.show(C)");
    }
}

class B extends A {

    @Override
    public void show(A obj) {
        System.out.println("B.show(A)");
    }
}

class C extends B {
}

class D extends C {
}

public static void main(String[] args) {

    A a = new A();
    B b = new B();
    C c = new C();
    D d = new D();

    // 在 A 中存在 show(A obj),直接调用
    a.show(a); // A.show(A)
    // 在 A 中不存在 show(B obj),将 B 转型成其父类 A
    a.show(b); // A.show(A)
    // 在 B 中存在从 A 继承来的 show(C obj),直接调用
    b.show(c); // A.show(C)
    // 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
    b.show(d); // A.show(C)

    // 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
    A ba = new B();
    ba.show(c); // A.show(C)
    ba.show(d); // A.show(C)
}

2.重载(Overload)
存在于同一类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不用。需要注意的是,返回值不同,其他都相同不算是重载。

7.反射

每个类都有一个Class对象,包含了与类相关的信息。当编译一个新类时,会产生一个同名的.class文件,该文件内容保存着Class对象。
类加载相当于Class对象的加载,类在第一次使用时才动态加载到JVM中。也可以使用Class.forName("com.mysql.jdbc.Driver")这种方式来控制类的加载,该方法会返回一个Class对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的.class不存在也可以加载进来。
Class和java.lang.reflect一起对反射提供了支持,java.lang.reflect类库主要包含了以下三个类:
Field:可以使用get()和set()方法读取和修改Field对象关联的字段;
Method:可以使用invoke()方法调用与Method对象关联的方法;
Constructor:可以用Constructor的newInstance()创建新的对象。

反射的优点:
1.可扩展性:应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类;
2.类浏览器和可视化开发环境:一个类浏览器需要可以枚举类的成员。可视化开发环境(如IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码;
3.调试器和测试工具:调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的API定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点:
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射时,要牢记几点:
1.性能开销:反射涉及了动态类型的解析,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
2.安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了。
3.内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有属性和方法),所以使用反射可能导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

反射进阶

反射的主要功能:
1.在运行时判断任意一个对象所属的类;
2.在运行时构造任意一个类的对象;
3.在运行时判断任意一个类所具有的成员变量和方法(通过反射可以调用private方法);
4.在运行时调用任意一个对象的方法。
重点是运行时,不是编译时。

反射的使用
1.获取Class对象
有三种方式获取:
(1)使用Class类的forName静态方法;

public static Class<?> forName(String className)

Class.forName("com.xxxx.xx");

(2)直接获取某一个类的class;

            Class c = int.class;
            Class c2 = String.class;

(3)调用某个对象的getClass()方法;

            String s = "";
            Class c3 = s.getClass();

2.判断是否为某个类的实例
一般地,我们使用 instanceof 关键字来判断是否为某个类的实例。也可以借助反射中Class对象的 isInstance() 方法来判断是否为某个类的实例,这是一个 native 方法。

            String s = "";
            Class c3 = s.getClass();
            c3.isInstance(String.class);

	
public native boolean isInstance(Object obj);

3.创建实例
通过反射来生成对象主要有两种方式。
(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。

Class<?> c = String.class;
Object str = c.newInstance();

(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。

//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);

4.获取方法
获取某个Class对象的方法集合,主要有以下几个方法:
getDeclaredMethods()方法,返回类或接口声明的所有方法,包括公共、保护、默认访问和私有方法,但不包括继承的方法。
getMethods()方法,返回某个类所有公用(public)方法,包括其继承类的公用方法。
getMethod()方法,返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。

	
public Method[] getDeclaredMethods() throws SecurityException
public Method[] getMethods() throws SecurityException
public Method getMethod(String name, Class<?>... parameterTypes)

public class test1 {
	public static void test() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
	        Class<?> c = methodClass.class;
	        Object object = c.newInstance();
	        Method[] methods = c.getMethods();
	        Method[] declaredMethods = c.getDeclaredMethods();
	        //获取methodClass类的add方法
	        Method method = c.getMethod("add", int.class, int.class);
	        //getMethods()方法获取的所有方法
	        System.out.println("getMethods获取的方法:");
	        for(Method m:methods)
	            System.out.println(m);
	        //getDeclaredMethods()方法获取的所有方法
	        System.out.println("getDeclaredMethods获取的方法:");
	        for(Method m:declaredMethods)
	            System.out.println(m);
	    }
    }
class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
        return a+b;
    }
    public int sub(int a,int b) {
        return a+b;
    }
}

5.获取构造器信息
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor()方法得到Constructor类的实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

public T newInstance(Object ... initargs)

次方法可以根据传入的参数来调用对应的Constructor创建对象实例。

6.获取类的成员变量
主要有几个方法:
getFiled():访问公有的成员变量
getDeclaredFiled():所有已声明的成员变量,但不能得到其父类的成员变量
getFiled()和getDeclaredFiled()用法参照Method。

7.调用方法
通过获取到的method对象,可以用invoke()方法来调用这个方法。如果方法是私有的,要先加下面的代码“

method.setAccessible(true);//调用私有方法前

//invoke方法
public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

//调用方法
 Class<?> klass = methodClass.class;
        //创建methodClass的实例
        Object obj = klass.newInstance();
        //获取methodClass类的add方法
        Method method = klass.getMethod("add",int.class,int.class);
        //调用method对应的方法 => add(1,4)
        Object result = method.invoke(obj,1,4);
        System.out.println(result);

8.异常

Throwable可以用来表示任何可以作为异常抛出的类,分为两种:Error和Exception。
其中Error表示JVM无法处理的错误。
Exception分为 受检异常 和 非受检异常。受检异常需要用try...catch语句捕获并进行处理,并且可以从异常中恢复;非受检异常是程序运行时错误,例如除0会引发ArithmeticException,此时程序崩溃并且无法恢复。

9.泛型

泛型,即”参数化类型“。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质就是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

一个经常被举的例子:

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
    String item = (String)arrayList.get(i);
    Log.d("泛型测试","item = " + item);
}

毫无疑问,程序运行会崩溃:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList可以存放任意类型对象,例子中添加了一个String类型,再使用时都会以String的方式使用,又添加了一个Integer类型,这时就会出现上述的异常。
为了能在编译期间解决以上问题,泛型应运而生。将第一行的申明改成:

List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在编译阶段,编译器就会报错

编译器就能在编译阶段帮助我们发现类似的问题。

泛型只在编译阶段有效。下列代码可以证明:

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    Log.d("泛型测试","类型相同");
}
//输入结果: 泛型测试: 类型相同

这个例子说明,在编译之后,程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效,编译过程中,正确校验泛型结果后,会将泛型的相关信息擦去,并且在对象进入和离开方法的边界处,添加类型检查和类型转换的方法,也就是说,泛型信息不会进入到运行阶段。
对此总结成一句话:泛型类型在逻辑上看成是多个不同的类型,实际上都是相同的基本类型。

泛型的使用
泛型有三种使用方式,分别是泛型类、泛型接口、泛型方法。

1.泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,比如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);

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型测试","key is " + genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());

定义的泛型类,是不是一定要传入泛型类型实参?其实不是的,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何类型,这时泛型起不到任何的限制作用。

2.泛型接口
泛型接口与泛型类的定义以及使用基本相同。泛型接口常被用在各种类的生产器中。

//定义一个泛型接口
public interface Generator<T> {
    public T next();
}

/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
 * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}

3.泛型方法
泛型类,是在实例化类的时候,指明泛型的具体类型;泛型方法,是在调用方法的时候,指明泛型的具体类型。

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

Object obj = genericMethod(Class.forName("com.test.test"));

10.注解

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素加上更直观明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或宽架使用。

注解用处

1.生成文档。这是最常见的,也是java最早提供的注解,比如@param @return等。
2.跟踪代码依赖性,实现替代配置文件功能。比如依赖注入宽架Dagger2提供大量注解使用。
3.在编译时进行格式检查。如@Override放在方法前,如果这个方法并不是覆盖了超类方法,则编译时就能检查出。

注解原理

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法,该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

元注解

元注解是用来注解其他注解的注解,在自定义注解的时候,需要用到元注解。java.lang.annotation提供了四种元注解:@Retention , @Target , @Documented , @Inherited。
1.@Retention,定义该注解的生命周期;
RetentionPolicy.SOURCE:在编译阶段丢弃。这些注解在编译结束后就不再有任何意义,所以他们不会写入字节码。@Override,@SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS:在类加载的时候丢弃。在字节码文件的处理中有用。默认使用这种方式。
RetentionPolicy.RUNTIME:始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。自定义注解通常使用这种方式。
2.@Target,表示该注解用于什么地方。默认为任何元素,表示可以用在任何地方;
ElementType.CONSTRUCTOR:用于描述构造器
ElementType.FIELD:成员变量、对象、属性(包括enum实例)
ElementType.LOCAL_VARIABLE:用于描述局部变量
ElementType.METHOD:用于描述方法
ElementType.PACKAGE:用于描述包
ElementType.PARAMETER:用于描述参数
ElementType.TYPE:用于描述类、接口或enum声明
3.@Decumented,一个简单的Annotation标记注解,表示是否将注解信息添加到Java文档中。
4.@Inherited,定义该注释和子类的关系
@Inherited元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。