欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAVA 知识点汇总

程序员文章站 2022-07-12 22:19:19
...

1,面向对象的特征:

  1. 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象(类的属性)和行为抽象(类的方法)两方面。
  2. 封装:封装就是把描述一个对象的属性和行为的代码封装在一个类中,隐藏对象的属性和实现细节,仅对外公开接口。封装的目标就是要实现软件部件的“高内聚、低耦合”。
  3. 继承:继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。子类是对父类的拓展,是一种特殊的父类。(JAVA中只允许单继承)
  4. 多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。方法重载实现的是编译时的多态性,而方法重写实现的是运行时的多态性。

2,访问修饰符

修饰符 所在类 同一包 子类 任何地方
private × × ×
default × ×
protected ×
public

字段一般用private修饰,为了安全性;
拥有实现细节的方法用private修饰,隐藏细节;
一般的方法,用public修饰,供外界调用;
在继承关系中,用protected修饰,供子类访问;

3,数据类型

1)基本数据类型:byte , char , short , int , long , float , double , boolean
2)引用数据类型:数组,类(如:String),接口 等

基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在上。
引用数据类型在被创建时,首先要在栈上给其引用(句柄)分配一块内存,而对象的具体信息都存储在内存上,然后由栈上面的引用指向堆中对象的地址。

4,基本数据类型对应的包装类型

基本类型 二进制位数 包装类型
boolean 1 Boolean
byte 8 Byte
char 16 Character
short 16 Short
int 32 Integer
long 64 Long
float 32 Float
double 64 Double

包装类型相当于将基本数据类型“包装起来”,使其具有对象的特征,并为其添加了属性和方法,丰富了基本数据类型的操作。

从JDK1.5就开始引入了自动拆装箱的语法功能,也就是系统将自动进行基本数据类型和与之相对应的包装类型之间的转换。在自动装箱过程时,编译器调用的是static Integer valueOf(int i)这个方法。

public class Test01{
	public static void main(String[] args) {
        Integer a = new Integer(3);
        Integer b = 3;                  // 将3自动装箱成Integer类型
        int c = 3;
        System.out.println(a == b);     // false 两个引用没有引用同一对象
        System.out.println(a == c);     // true a自动拆箱成int类型再和c比较
   	}
}
public class Test02 {
    public static void main(String[] args) {
        Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;

        System.out.println(f1 == f2);//true  值在-128到127之间,不会new Integer对象
        System.out.println(f3 == f4);//false  值在-128到127之外,会new Integer对象
    }
}

5,JAVA 程序执行过程

JAVA 知识点汇总
Java程序具体执行的过程

如上图所示,首先Java源代码文件(.java后缀)会被Java编译器编(JavaC)译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。因此,在Java中我们常常说到的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)。

6,JVM 内存模型

JAVA 知识点汇总
JVM 内存模型
  • 程序计数器(Program Counter Register):是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器,也就是说是用来指示执行哪条指令的。程序计数器是每个线程所私有的。
  • Java 虚拟机栈(Java Virtual Machine Stacks):是线程私有的,它的生命周期与线程相同。Java虚拟机栈描述的是Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法创建一个栈帧,栈帧存放了当前方法的数据信息(如:局部变量,对于基本数据类型的变量,则直接存储它的值;对于引用类型的变量,则存的是指向对象的引用。),当方法调用完毕,该方法的栈帧就被销毁了。
  • 本地方法栈(Native Method Stacks):与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
  • Java 堆(Java Heap):被所有线程共享的一块内存区域,在虚拟机启动时创建,在JVM中只存在一个堆。所有的对象实例以及数组都要在堆上分配(使用new关键字,就表示在堆中开辟一块新的存储空间)。这部分空间也是Java垃圾回收器管理的主要区域。
  • 方法区(Method Area):被所有线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量以及编译器编译后的代码数据等(这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载)。

例:String str = new String("hello");
     上面的语句中变量str放在上,用new创建出来的字符串对象放在上,而"hello"这个字面量放在方法区

GC(Garbage Collection):垃圾回收器。
Java的自动垃圾回收机制:简单理解为,程序员就不需要再手动的去控制内存的释放。当JVM发觉内存资源紧张的时候,就会自动地去清理无用对象(没有被引用到的对象)所占用的内存空间。

7,JVM加载class文件的原理机制

JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)。

   1)装载:查找和导入class文件;

   2)连接
     (1)检查:检查载入的class文件数据的正确性;
     (2)准备:为类的静态变量分配存储空间;
     (3)解析:将符号引用转换成直接引用(这一步是可选的)

   3)初始化:初始化静态变量,静态代码块。

这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。

8,java的类加载顺序和创建对象时的调用顺序

类的加载顺序
有父子关系的类在加载时,先调用父类静态初始化块,静态属性,但不包括静态方法,然后再是,子类静态初始化块,静态属性,但同样不包括静态方法 。

创建对象的过程
先是父类非静态初始化块,非静态属性,再是父类构造函数,然后是子类非静态初始化块,非静态属性,最后是子类构造函数。

/**父类*/  
package test;  
public class Father {  
    static  
    { System.out.println("父类静态初始化块");}  
    { System.out.println("父类初始化块");      }  
    private static int b = 1;  
    public Father() {  
        System.out.println("调用了父类无参构造器");  
    }  
    public Father(int b) {  
        this.b = b;  
        System.out.println("调用父类的有参构造器");  
    }  
}  
/**子类*/  
package test;  
public class Son extends Father {  
    static  
    { System.out.println("子类静态初始化块"); }  
    { System.out.println("子类初始化块");       }  
    private static int a =1;  
    public Son() {  
        System.out.println("调用子类的构造器");  
    }  
    public Son(int a){  
        this.a=a;  
        System.out.println("调用子类的有参构造器");  
    }  
}  
package test;  
public class Test {  
      public static void main(String[] args) {  
        Father father = new Son();  
    }  
}  

打印语句:

	父类静态初始化块
	子类静态初始化块
	父类初始化块
	调用了父类无参构造器
	子类初始化块
	调用子类的构造器

静态对象总是在第一次创建对象前就已经初始化完毕,初始化的顺序从最顶端的父类到子类,只会初始化一次。然后就是从父类到子类中,依次初始化非静态对象和构造器。

9,Java 方法重载与方法重写

方法重载(Overload):

    1)在同一个类中

    2)方法名相同

    3)参数的个数或类型不同

    4)与方法的返回类型无关

    5)与方法的修饰符无关

方法重写(Override):

  方法重写必须是子类继承父类,才能进行对父类的方法进行重写。

    1)重写方法与被重写方法必须拥有相同的方法名

    2)重写方法与被重写方法必须拥有相同的参数列表

    3)重写方法的返回值类型必须与被重写方法的返回值类型相同

    4)重写方法不能缩小被重写方法的访问权限

  这里注意的是重写方法,必须是子类中能访问到的父类的方法。

方法重载与方法重写的区别:

1)重载是同一个类中的同名方法,要求方法名相同,参数列表不同,与返回值类型无关

2)重写涉及的是子类和父类之间的同名方法,要求方法名相同、参数列表相同、返回值类型相同

构造器不能被继承,因此不能被重写,但可以被重载。

10,String和StringBuilder、StringBuffer的区别

Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。

  • String:只读字符串,不可变字符串;
  • StringBuffer:可变字符串、效率低、线程安全;被synchronized修饰;
  • StringBuilder:可变字符序列、效率高、线程不安全;无synchronized修饰;

三者继承关系

JAVA 知识点汇总
class StringEqualTest {
    public static void main(String[] args) {
        String s1 = "Programming";
        String s2 = new String("Programming");
        String s3 = "Program" + "ming";
        System.out.println(s1 == s2);//false
        System.out.println(s1 == s3);//true
        System.out.println(s1 == s1.intern());//true
    }
}

11,抽象类(abstract class)和接口(interface)

抽象类(abstract class)和接口(Interface)是Java语言中对于抽象类定义进行支持的两种机制,赋予了Java强大的面向对象能力

  • 抽象类是可以有私有的方法或者私有的变量,如果一个类中有抽象方法,那么就是抽象类,而抽象类未必要有抽象方法。在java语言中,可以把类或类中方法声明为abstract来表示一个类是抽象类。
  • 接口是公开的,里面不能有私有的方法或变量,是用于让别人使用的。接口是指一个方法的集合,接口中所有的方法都没有方法体,在java语言中用关键字interface来实现。

    接口与抽象类的相同点
    1)都不能被实例化。
    2)接口的实现类或抽象类的子类都只有实现了接口或者抽象类中的方法后才能被实例化。

    接口与抽象类的区别
类型 抽象类 接口
定义 abstract class 关键字 interface 关键字
继承 子类只能继承一个抽象类 子类能实现多个接口
访问修饰符 抽象方法可以是 public、protected、default 这些修饰符 接口方法必须是 public 修饰符
方法实现 可以定义构造器、抽象方法(无方法体)和具体方法(有方法体) 所有方法都是抽象的,没有构造器
实现方式 子类使用 extends 关键字来继承抽象类 子类使用 implements 关键字来实现接口
作用 把相同的东西提取出来,即重用;强调所属关系 把程序模块进行固化,即降低耦合;强调特定功能的实现

12,阐述静态变量和实例变量的区别

  • 静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,只要程序加载了类的字节码,不用创建任何实例对象就会被分配空间,就可以被使用。一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;静态变量可以实现让多个对象共享内存。

  • 实例变量必须依存于某一实例,必须创建了实例对象(如 new)才会被分配空间,才可以使用实例变量。

     实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
    

13,JAVA 中的内部类

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类

  • 成员内部类
        成员内部类是定义在另一个类的内部的类,它可以访问外部类的所有成员属性和成员方法(包括 private 成员和静态成员)。当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
  • 局部内部类
        局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
  • 匿名内部类
         匿名内部类就是一个没有显式的名字的内部类,在实际开发中,此种内部类用的是非常多的。它会隐式的继承一个类或者实现一个接口,或者说,匿名内部类是一个继承了该类或者实现了该接口的子类匿名对象。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

    在使用匿名内部类的过程中,我们需要注意如下几点:
          1)使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得。
          2)匿名内部类中是不能定义构造函数的。
          3)匿名内部类中不能存在任何的静态成员变量和静态方法。
          4)匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
          5)匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

  • 静态内部类
         静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

14,JAVA 日期时间

在Java中,操作日期主要涉及到一下几个类:

  • 1)java.util.Date
    类 Date 表示特定的瞬间,精确到毫秒。从 JDK 1.1 开始,应该使用 Calendar 类实现日期和时间字段之间转换,使用 DateFormat 类来格式化和分析日期字符串。Date 中的把日期解释为年、月、日、小时、分钟和秒值的方法已废弃。

  • 2)java.text.DateFormat(抽象类)
    DateFormat 是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并分析日期或时间。日期/时间格式化子类(如 SimpleDateFormat)允许进行格式化(也就是日期 -> 文本)、分析(文本-> 日期)和标准化。将日期表示为 Date 对象,或者表示为从 GMT(格林尼治标准时间)1970 年,1 月 1 日 00:00:00 这一刻开始的毫秒数。

  • 3)java.text.SimpleDateFormat(DateFormat的直接子类)
    SimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。
    SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。但是,仍然建议通过 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance 来新的创建日期-时间格式化程序。

  • 4)java.util.Calendar(抽象类)
    Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。
    与其他语言环境敏感类一样,Calendar 提供了一个类方法 getInstance,以获得此类型的一个通用的对象。Calendar 的 getInstance 方法返回一个 Calendar 对象,其日历字段已由当前日期和时间初始化。

  • 5)java.util.GregorianCalendar(Calendar的直接子类)
    GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家使用的标准日历系统。
    GregorianCalendar 是一种混合日历,在单一间断性的支持下同时支持儒略历和格里高利历系统,在默认情况下,它对应格里高利日历创立时的格里高利历日期(某些国家是在 1582 年 10 月 15 日创立,在其他国家要晚一些)。可由调用方通过调用 setGregorianChange() 来更改起始日期。

    使用 SimpleDateFormat 格式化日期示例:

    import  java.util.*;
    import java.text.*;
     
    public class DateDemo {
       public static void main(String args[]) {
     
          Date dNow = new Date( );
          SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
     
          System.out.println("当前时间为: " + ft.format(dNow));//当前时间为: 2019-02-18 17:15:34
       }
    }
    

    Calendar类
    创建一个Canlendar对象:
    Calendar c = Calendar.getInstance();//默认是当前日期

    创建一个指定日期的Canlendar对象
    Calendar c1= new Calendar.getIntance();
    c1.set(2019,02,11);
         //20190211

    把c1对象的日期加上10,也就是c1也就表示为10天后的日期,其它所有的数值会被重新计算
    c1.add(Calendar.DATE, 10);

    Calendar类对象信息的获得:

    Calendar c1 = Calendar.getInstance();
    // 获得年份
    int year = c1.get(Calendar.YEAR);
    // 获得月份
    int month = c1.get(Calendar.MONTH) + 1;
    // 获得日期
    int date = c1.get(Calendar.DATE);
    // 获得小时
    int hour = c1.get(Calendar.HOUR_OF_DAY);
    // 获得分钟
    int minute = c1.get(Calendar.MINUTE);
    // 获得秒
    int second = c1.get(Calendar.SECOND);
    // 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)
    int day = c1.get(Calendar.DAY_OF_WEEK);
    

    Calendar中些陷阱,很容易掉下去:
    (1) Calendar的星期是从周日开始的,常量值为0。
    (2) Calendar的月份是从一月开始的,常量值为0。
    (3) Calendar的每个月的第一天值为1。

15, JAVA 中的断言(assert)

断言(assert)是用于对程序进行调试的,对于执行结构的判断,而不是对于业务流程的判断。(相当于一个if ()语句,如果满足断言的执行程序,如果不满足则抛错误)。

在Java中,assert关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都 将忽略!),如果要开启断言检查,则需要用开关-enableassertions或-ea来开启。

assert关键字语法很简单,有两种用法:

  1)assert <boolean表达式>
  如果<boolean表达式>为true,则程序继续执行。
  如果为false,则程序抛出AssertionError,并终止执行。

  2)assert <boolean表达式> : <错误信息表达式>
  如果<boolean表达式>为true,则程序继续执行。
  如果为false,则程序抛出java.lang.AssertionError,并输入<错误信息表达式>。

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		boolean isRight = 1 > 2;
		try {
			assert isRight:"程序错误";//在:后写自定义的异常
			System.out.println("程序正常");
		} catch (AssertionError e) {
			// TODO: handle exception
			System.out.println(e.getMessage());//返回值:程序错误。(没错,这就是我们自定义的异常)
		}
	}

assert既然是为了调试测试程序用,不在正式生产环境下用,那应该考虑更好的测试JUint来代替其做用,JUint相对assert关键的所提供的功能是有过之而无不及。当然完全可以通过IDE debug来进行调试测试。

16, JAVA 中的异常处理

异常就是有异于常态,和正常情况不一样,有错误出错。在java中,阻止当前方法或作用域的情况,称之为异常。

Java中的所有不正常类都继承于Throwable类。Throwable主要包括两个大类,一个是Error类,另一个是Exception类

  • Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者;
  • Exception类,也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(或运行异常,RuntimeException)和检查异常(Exception,其他的一些异常);
JAVA 知识点汇总

非检查异常(RuntimeException): RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如:除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。

检查异常(checked exception):除了Error 和 RuntimeException外的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException(SQL异常) , IOException(文件异常) , ClassNotFoundException 等。

常见的非检查异常(或运行异常)

JAVA 知识点汇总

异常处理的基本语法

在编写代码处理异常时,对于检查异常,有2种不同的处理方式:使用try…catch…finally语句块处理它。或者,在函数签名中使用throws 声明交给函数调用者caller去解决。

  • try…catch…finally语句块
	try{
	     //try块中放可能发生异常的代码。
	     //如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
	     //如果发生异常,则尝试去匹配catch块。
	 }catch(SQLException SQLexception){
	    //每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
	    //catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
	    //在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
	    //如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
	    //如果try中没有发生异常,则所有的catch块将被忽略。
	 }catch(Exception exception){
	    //...
	}finally{
	   //finally块通常是可选的。
	   //无论异常是否发生,异常是否匹配被处理,finally都会执行。
	   //一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
	   }
  • throws 函数声明
    throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
	public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
	{ 
	     //foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
	}

     程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。

	public void save(User user)
	{
	      if(user  == null) 
	          throw new IllegalArgumentException("User对象为空");
	      //......
	 }

17, JAVA 中的多线程

1)基本概念

  • 并发与并行

    • 并发:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
    • 并行:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时,而是同一时间段内执行。

    并发编程三要素
    (1)原子性:原子,即一个不可再被分割的颗粒。在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
    (2)有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)。
    (3)可见性:当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。

  • 进程与线程

    • 进程:指一个内存中运行中的应用程序。每个进程都有自己独立的一块内存空间,一个应用程序可以同时启动多个进程。
    • 线程:指进程中的一个执行任务(控制单元),一个进程可以同时并发运行多个线程。
  • 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程。

  • 线程安全:指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如:不加事务的转账案例。

  • 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。线程安全的优先级高于性能。

  • 悲观锁与乐观锁

    • 悲观锁:每次操作都会加锁,会造成线程阻塞。
    • 乐观锁:每次操作不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,不会造成线程阻塞。​

2)线程状态

JAVA 知识点汇总

A 新建状态(New):新创建了一个线程对象。

B 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

C 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

D 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
     a、等待阻塞:运行的线程执行 wait() 方法,JVM会把该线程放入等待池中。(wait会释放持有的锁),当线程执行 notify() / notifyAll() 方法时,该线程恢复锁。
     b、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
     c、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

E 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3)线程的创建与启动

JAVA的线程是通过java.lang.Thread类来实现的,每一个Thread对象代表一个新的线程。创建一个新线程出来有两种方法:第一个是从Thread类继承,另一个是实现接口Runnable。对于线程的启动而言,都是调用线程对象的 start() 方法。

  • 继承Thread类,重写该类的run()方法。
class MyThread extends Thread {
    
    private int i = 0;

    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

public class ThreadTest {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread myThread1 = new MyThread();     // 创建一个新的线程  myThread1  此线程进入新建状态
                Thread myThread2 = new MyThread();     // 创建一个新的线程 myThread2 此线程进入新建状态
                myThread1.start();                     // 调用start()方法使得线程进入就绪状态
                myThread2.start();                     // 调用start()方法使得线程进入就绪状态
            }
        }
    }
}
  • 实现Runnable接口,并重写该接口的run()方法
class MyRunnable implements Runnable {
    private int i = 0;

    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

public class ThreadTest {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
                Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
                Thread thread2 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
                thread1.start();  // 调用start()方法使得线程进入就绪状态
                thread2.start(); // 调用start()方法使得线程进入就绪状态
            }
        }
    }
}

总结:
一个类如果继承Thread类,则不适合资源共享;如果实现了Rubnable接口,则容易实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:

     A:适合多个相同的程序代码的线程去处理同一个资源
     B:可以避免java中的单继承的限制
     C:增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
     D:线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

main方法其实也是一个线程。在java中所有的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。

4)线程同步块(Synchronized)

Synchronized 是JVM实现的一种锁,Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上,所有同步在一个对象上的同步块同时只能被一个线程进入并执行操作。所有其他线程等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。

  • 同步方法
    即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
    代码如:
    public synchronized void save(){}

synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

  • 同步代码块
    即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
    代码如:
    synchronized(同步锁){ }
    同步锁:
    • 对于非static方法,同步锁就是this.
    • 对于static方法,我们使用当前方法所在类的字节码对象(如:App.class)。

同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

5)常用函数

join():等待该线程终止;
sleep(long millis):在指定时间内让当前线程休眠(暂停执行),单位为毫秒;
yield():线程礼让,暂停当前正在执行的线程,让其他线程执行。当前线程进入就绪状态,让具有相同或更高优先级的线程获得运行机会;
wait()::当前线程主动释放对象锁,进入休眠;
notify():唤醒一个在 wait 状态的线程,如果有多个线程,则随机唤醒其中一个;
notifyAll():唤醒所有在 wait 状态的线程,如果有多个线程,则优先级高的线程先执行;

wait()、notify()、notifyAll() 方法必须与 synchronized(object) 一起使用,必须放在 synchronized(obj){ } 语法块中。

sleep()与yield()方法的区别:

  • sleep() 使当前线程进入阻塞状态,限定时间内不会被执行;yield() 让当前线程进入就绪状态,该线程有可能马上再次执行。
  • sleep() 使当前线程睡眠一段时间,时间长短可有程序控制;yield() 使当前线程让出CPU占有权,让出的时间不可设定。
  • sleep() 允许较低优先级的线程获得运行机会;yield() 只允许相同或较优先级的线程获得运行机会。

sleep()与wait()方法的区别:

  • sleep() 属于Thread 类的方法;wait() 属于 Object 类的方法;
  • 二者都可以使当前线程进入休眠状态,但sleep() 没有释放对象锁;wait() 则释放了对象锁;
  • sleep() 可以在任何地方使用;wait() 必须在同步方法或同步块中使用;

6)Lock 类

Lock类实际上是一个接口,我们在实例化的时候实际上是实例化实现了该接口的类Lock lock = new ReentrantLock(); 。创建Lock对象 lock,用 lock.lock 来加锁,用 lock.unlock 来释放锁,在两者中间放置需要同步处理的代码。

class MyConditionService {

    private Lock lock = new ReentrantLock();
    public void testMethod(){
    	try{
	        lock.lock(); //获取锁
	        for (int i = 0 ;i < 5;i++){
	            System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1)));
        	}
        }catch(Exception e){
			e.printStackTrace();
		}finally{
        	lock.unlock(); //释放锁
    	}
}

public class LockTest {

    public static void main(String[] args) throws InterruptedException {
		MyConditionService service = new MyConditionService();
		new Thread(new Runnable(){
			public void run(){
				service.testMethod();
			}
		}).start();
	   new Thread(service::testMethod).start(); //等同于上面代码
	        	        
	   Thread.sleep(1000 * 5);
	}
}

Condition 类

Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能(await/signal 方法),Condition对象是由lock对象所创建的(lock.newCondition();)。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。

class ConditionWaitNotifyService {

    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();
    public void myAwait(){
        try{
            lock.lock();
            System.out.println("await的时间为 " + System.currentTimeMillis());
            condition.await();
            System.out.println("await结束的时间" + System.currentTimeMillis());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void mySignal(){
        try{
            lock.lock();
            System.out.println("signal的时间为" + System.currentTimeMillis());
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}

public class ConditionTest {
    public static void main(String[] args) throws InterruptedException {
		ConditionWaitNotifyService service = new ConditionWaitNotifyService();
        new Thread(service::myAwait).start();
        Thread.sleep(1000 * 3);
        new Thread(service::mySignal).start();
        Thread.sleep(1000);
    }
}

结果为:

	await的时间为 1550730385440
	signal的时间为1550730388440
	await结束的时间1550730388440

18,集合框架

集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法(底层都对应着某一种数据结构的算法)。

JAVA 知识点汇总

所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:CollectionMap 。Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。6个接口(短虚线表示)、5个抽象类(长虚线表示)、8个实现类(实线表示)。
Iterator是一个接口,用于遍历集合,即我们通常通过Iterator迭代器来遍历集合。
Collections和Arrays是操作数组、集合的两个工具类。

Collection 接口

Collection 接口是高度抽象出来的集合,它包含了集合的基本操作和属性。Collection包含了List和Set两大分支。

1)List 接口

List集合代表定义一个允许重复的有序集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。第一个元素的索引值是0。
实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

  • ArrayList 是一个数组队列,相当于动态数组,由数组实现。查找快,增删慢。在增删操作时会牵扯到数组扩容和数组拷贝,速度慢;而查找操作时直接按索引查找,速度快。
  • LinkedList 是一个双向链表,由链表实现。增删快,查找慢。在增删操作时,只需记住前后元素就可以了,速度快;而查找操作时,需要一个个地遍历,速度慢。它也可以被当作堆栈、队列或双端队列进行操作。
  • Vector 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
  • Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。
2)Set 接口

Set是一个不允许有重复元素的集合。Set集合可以存储多个对象,但并不会记住元素的存储顺序,也不允许集合中有重复元素。
Set接口有三个具体实现类,分别是散列集HashSet、链式散列集LinkedHashSet和树形集TreeSet。

  • HashSet类直接实现了Set接口, 其底层其实是包装了一个HashMap去实现的。HashSet采用HashCode算法来存取集合中的元素,因此具有比较好的读取和查找性能。HashSet是线程非安全的,元素值允许为 NULL,存储顺序和添加的顺序不同。
  • LikedHashSet是HashSet的子类,其底层是基于LinkedHashMap来实现的,有序,线程非安全。它也是根据元素的HashCode值进来决定元素的存储位置,但它能够同时使用链表来维护元素的添加次序,使得元素能以插入顺序保存。
  • TreeSet是SortedSet接口的实现类,其底层是基于TreeMap实现的(红黑树的数据结构),有序,线程非安全。TreeSet可以保证集合元素处于排序状态(所谓排序状态,就是元素按照一定的规则排序,比如升序排列,降序排列)。

集合遍历操作的3种方式
     ①使用for循环(Set为无序集合,不可以使用此方式)
     ②使用forEach遍历
     ③使用迭代器(iterator)遍历列表,使用列表调用.iteration()返回迭代器对象,
     使用迭代器对象.hasNext()方法判断是否又下一条数据,使用迭代器对象.next()方法取出下一条数据。
     当需要边跌代元素边删除指定的元素时,只有使用迭代器,iterator对象.remove()方法。

Map 接口

Map是一个映射接口,即key-value键值对,不能存在相同的key值,当然value值可以相同。Map中的键值对以Entry类型的对象实例形式存在。与Collection接口没有什么关系,Map不是Collection的子接口。

  • HashMap 基于哈希表实现,其中Entry对象是无序,线程非安全排列的,Key值和value值都可以为null(只能有一个)。

  • TreeMap 是一个有序,线程非安全的key-value集合,基于红黑树实现。TreeMap存储时会进行排序的,会根据key来对key-value键值对进行排序,其中排序方式也是分为两种,一种是自然排序,一种是定制排序,具体取决于使用的构造方法。

Set、List和Map可以看做集合的三大类:

  • List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
  • Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
  • Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。

19,JAVA I/O 操作

Java中使用IO(输入输出)来读取和写入,读写设备上的数据、硬盘文件、内存、键盘…根据数据的走向可分为输入流和输出流,这个走向是以内存为基准的,即往内存中读数据是输入流,从内存中往外写是输出流。

根据处理的数据类型可分为字节流和字符流

  • 字节流可以处理所有数据类型的数据,在java中以Stream结尾

  • 字符流处理文本数据,在java中以Reader和Writer结尾。

JAVA 知识点汇总

从上图可以看到,整个Java IO体系都是基于字节流(InputStream/OutputStream)字符流(Reader/Writer) 作为基类,根据不同的数据载体或功能派生出来的。

IO常用类

1)文件流:FileInputStream/FileOutputStream, FileReader/FileWriter

这四个类是专门操作文件流的,用法高度相似,区别在于前面两个是操作字节流,后面两个是操作字符流。它们都会直接操作文件流,直接与OS底层交互。因此他们也被称为节点流

注意使用这几个流的对象之后,需要关闭流对象,因为java垃圾回收器不会主动回收。不过在Java7之后,可以在 try() 括号中打开流,最后程序会自动关闭流对象,不再需要显示地close。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class TestIO {
	//需求:用文件字节流读取C盘根目录下的 tmp.txt 文件数据
    public static void FileInputStreamTest() throws IOException {
        FileInputStream fis = new FileInputStream("c:\\tmp.txt");
        byte[] buf = new byte[1024];
        int hasRead = 0;
        
        //read()返回的是单个字节数据(字节数据可以直接专程int类型),但是read(buf)返回的是读取到的字节数,真正的数据保存在buf中
        while ((hasRead = fis.read(buf)) > 0) {
            //每次最多将1024个字节转换成字符串,这里tmp.txt中的字符小于1024,所以一次就读完了
            //循环次数 = 文件字符数 除以 buf长度
            System.out.println(new String(buf, 0 ,hasRead));
            /*
             * 将字节强制转换成字符后逐个输出,能实现和上面一样的效果。但是如果源文件是中文的话可能会乱码
             
            for (byte b : buf)    {
                char ch = (char)b;
                if (ch != '\r')
                System.out.print(ch);
            }
            */
        }
        //在finally块里close更安全
        fis.close();
    }
    //需求:用文件字符流读取C盘根目录下的 tmp.txt 文件数据
    public static void FileReaderTest() {
        try (   // 在try() 中打开的文件, JVM会自动关闭
                FileReader fr = new FileReader("c:\\tmp.txt")) {
            char[] buf = new char[32];
            int hasRead = 0;
            // 每个char都占两个字节,每个字符或者汉字都是占2个字节,因此无论buf长度为多少,总是能读取中文字符长度的整数倍,不会乱码
            while ((hasRead = fr.read(buf)) > 0) {
                // 如果buf的长度大于文件每行的长度,就可以完整输出每行,否则会断行。
                // 循环次数 = 文件字符数 除以 buf长度
                System.out.println(new String(buf, 0, hasRead));
                // 跟上面效果一样
                // System.out.println(buf);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    //需求:将C盘根目录的 tmp.txt 文件拷贝至D盘根目录下
    public static void FileOutputStreamTest() throws FileNotFoundException {
        try (   //在try()中打开文件,JVM会自动关闭
                FileInputStream fis = new FileInputStream("c:\\tmp.txt");
                FileOutputStream fos = new FileOutputStream("d:\\tmp.txt");
                ) {
            byte[] buf = new byte[1024];
            int hasRead = 0;
            while ((hasRead = fis.read(buf)) > 0) {
                //每读取一次就写一次,读多少就写多少
                fos.write(buf, 0, hasRead);
            }
            System.out.println("write success");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //需求:在C盘根目录创建 tmp1.txt 文件,并向其中写数据
    public static void FileWriterTest() {
        try (FileWriter fw = new FileWriter("c:\\tmp1.txt")) {
            fw.write("天王盖地虎\r\n");
            fw.write("宝塔镇河妖\r\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws IOException {
        //FileInputStreamTest();
        //FileReaderTest();
        //FileOutputStreamTest();
        FileWriterTest();
    }
}

2)包装流:PrintStream / PrintWriter / Scanner

  • PrintStream可以封装(包装)直接与文件交互的节点流对象OutputStream, 使得编程人员可以忽略设备底层的差异,进行一致的IO操作。因此这种流也称为处理流或者包装流。
  • PrintWriter除了可以包装字节流OutputStream之外,还能包装字符流Writer。
  • Scanner可以包装键盘输入,方便地将键盘输入的内容转换成我们想要的数据类型。

3)字符串流:StringReader/StringWriter

这两个操作的是专门操作String字符串的流,其中StringReader能从String中方便地读取数据并保存到char数组,而StringWriter则将字符串类型的数据写入到StringBuffer中(因为String不可写)。

4)转换流:InputStreamReader/OutputStreamReader

这两个类可以将字节流转换成字符流,被称为字节流与字符流之间的桥梁。我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类

5)缓冲流:BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream

经过Buffered处理过的输入流将会从一个buffer内存区域读取数据,本地API只会在buffer空了之后才会被调用(可能一次调用会填充很多数据进buffer)。
经过Buffered处理过的输出流将会把数据写入到buffer中,本地API只会在buffer满了之后才会被调用。

BufferedReader提供一个readLine()可以方便地读取一行,而FileInputStream和FileReader只能读取一个字节或者一个字符,因此BufferedReader也被称为行读取器。

20,网络编程

网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。Java的网络编程主要涉及到的内容是
Socket编程。

Socket:套接字,就是两台主机之间逻辑连接的端点。在Java语言中,对于TCP方式的网络编程提供了良好的支持,在实际实现时,以java.net.Socket类代表客户端连接,以java.net.ServerSocket类代表服务器端连接。java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

  • 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
  • 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
  • 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
  • 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
JAVA 知识点汇总

示例:下面是一个客户端和服务器端进行数据交互的简单例子,客户端输入正方形的边长,服务器端接收到后计算面积并返回给客户端。

// 服务器端
public class SocketServer {

    public static void main(String[] args) throws IOException {

        // 监听端口号
        int port = 7000;
        // 在端口上创建一个服务器套接字
        ServerSocket serverSocket = new ServerSocket(port);
        // 监听来自客户端的连接
        Socket socket = serverSocket.accept();
		// 初始化流
        DataInputStream dis = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
        DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
        do {
            double length = dis.readDouble();
            // 输出
            System.out.println("服务器端收到的边长数据为:" + length);
            double result = length * length;
            // 向客户端反馈结果
            dos.writeDouble(result);
            dos.flush();
        } while (dis.readInt() != 0);

        socket.close();
        serverSocket.close();
    }
}
// 客户端
public class SocketClient {

    public static void main(String[] args) throws UnknownHostException, IOException {
	
        int port = 7000; //服务器端口号
        String host = "localhost"; // 服务器IP地址

        // 创建一个套接字并将其连接到指定端口号
        Socket socket = new Socket(host, port);
		// 初始化流
        DataInputStream dis = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
        DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
		
        Scanner sc = new Scanner(System.in);
		boolean flag = false;
        while (!flag) {
            System.out.println("请输入正方形的边长:");
            double length = sc.nextDouble(); //获取输入的长度
            // 发送数据
            dos.writeDouble(length);
            dos.flush();
            // 接收反馈数据
            double area = dis.readDouble();
            System.out.println("服务器返回的计算面积为:" + area);
            while (true) {
                System.out.println("继续计算?(Y/N)");
                String str = sc.next();
                if (str.equalsIgnoreCase("N")) {
                    dos.writeInt(0);
                    dos.flush();
                    flag = true;
                    break;
                } else if (str.equalsIgnoreCase("Y")) {
                    dos.writeInt(1);
                    dos.flush();
                    break;
                }
            }
        }
        socket.close();
    }
}

21,反射

1)Class 类

Class是一个类,封装了当前对象所对应的类的信息。一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类,Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。Class 对象只能由系统建立对象,一个类在 JVM 中只会有一个Class实例。

Class 是一个类; 一个描述类的类。封装了

  • 描述方法的 Method;
  • 描述字段的 Filed;
  • 描述构造器的 Constructor 等属性.;

Class 类的常用方法

JAVA 知识点汇总

2)ClassLoader

类装载器是用来把类(class)装载进 JVM 的。JVM 规范定义了两种类型的类装载器:启动类装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:

JAVA 知识点汇总

3)反射

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,并能直接操作任意对象的内部属性及方法。反射就是把java类中的各种成分映射成一个个的Java对象。

Java反射机制主要提供了以下功能

  • 在运行时构造任意一个类的对象
  • 在运行时获取任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法(属性)
  • 生成动态代理

获取Class对象的三种方式

  • 通过类名获取 类名.class
  • 通过对象获取 对象名.getClass()
  • 通过全类名获取 Class.forName(全类名)

一般地、一个类若声明一个带参的构造器,同时要声明一个无参数的构造器

获取及调用构造方法

  • 获取批量构造器:
     (1)public Constructor[ ] getConstructors():所有”公有的”构造方法
     (2)public Constructor[ ] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

  • 获取单个构造器,并调用:
     (1)public Constructor getConstructor(Class… parameterTypes):获取单个的”公有的”构造方法:
     (2)public Constructor getDeclaredConstructor(Class… parameterTypes):获取”某个构造方法”可以是私有的,或受保护、默认、公有;

  • 调用构造方法:Constructor–>newInstance(Object… initargs)

newInstance是 Constructor类(管理构造函数的类)的方法。
newInstance(Object… initargs) 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象,并为之调用。

获取和调用 Method

  • 获取批量 Method
     (1) public Method[] getMethods():获取所有”公有方法”;(包含了父类的方法也包含Object类)
     (2) public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
  • 获取单个 Method
     (1) public Method getMethod(String name,Class<?>… parameterTypes): :获取单个”公有方法”;(包含了父类的方法也包含Object类)
     (2) public Method getDeclaredMethod(String name,Class<?>… parameterTypes):获取单个的成员方法,包括私有的(不包括继承的)
       参数: name,方法名;paramterTypes,形参的Class类型对象;
  • 调用 Method
     (1) 如果方法是 private 修饰的, 需要先调用 Method 的 setAccessible(true), 使其变为可访问权限
     (2) 再调用method.invoke(obj, Object … args)
       参数:obj,要调用方法的对象;args,实参;

获取 Field 及 Field的值

  • 获取批量 Field
     (1) Field[] getFields():获取所有的”公有字段”
     (2) Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有
  • 获取批量 Field
     (1) public Field getField(String fieldName):获取某个”公有的”字段
     (2) public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
  • 设置 Field 的值:field.set(Obejct obj, Object val)
     参数: obj, 字段所在的对象;val,字段要设置的值;