Java

来自智得网
跳转至: 导航、​ 搜索
阿里巴巴Java开发手册

简介

java是一款面向对象的编程语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的高级程序设计语言。

java是目前市场中最广泛使用的编程语言之一,其主要版本引入的新特征如下:

  • JDK1.5新特性

自动装箱与拆箱、枚举、泛型、For-Each循环

  • JDK1.7新特性

switch支持字符串、try语句自动关闭资源

  • JDK8新特性

Lambda表达式

特点

跨平台

Java跨平台的特性源自其虚拟机的架构,编程语言分为编译型语言和解释性语言。

编译型语言是提前将源代码通过编译过程转换为CPU可以识别的二进制机器码指令。现代的PC或者智能手机硬件有数百个身子上千个指令,而单片机则只有几十个指令,而且不同的硬件平台的指令是不同的,所以编译器需要基于不同的硬件体系进行编译。所以编译型语言是不能跨平台的,除了其编译出来的可执行程序不能在异构平台运行之外,源代码也不能跨平台,例如linux平台下C语言的long型8个字节,睡眠函数是sleep(),但是windows下long只有4个字节,睡眠函数是大写字母开头的Sleep()。

C、C++、Golang是常见的编译型语言。

解释性语言都是跨平台的,因为解释性语言和程序真正在硬件执行是通过解释器翻译的,解释器屏蔽了底层硬件的差异,不同平台的解释器将遵循标准语法的源代码转成匹配硬件的指令进行执行,从而实现了“一次编写,到处运行”。

Python,Javascript,PHP都是常见的解释型语言。

相较于单纯的编译型语言和解释性语言,Java是两者的结合体,源代码先编译为硬件无关的字节码,然后通过虚拟机执行字节码,而且在执行过程中还可以会被进一步编译为机器码,通过这种方式,既保证了语言的执行效率也实现了跨平台。

为了实现跨平台的机制,Java编译器主要是基于栈的指令集架构,基于寄存器架构的指令会依赖硬件,可移植较差,效率较高,而基于栈的指令集架构有以下特点:

  • 设计实现简单,适用于资源受限的系统,比如机顶盒等。
  • 避开寄存器分配难题:使用零地址指令方式分配。
  • 寄存器架构多以一、二、三地址指令为主,而栈指令流中大部分都是零地址指令,执行过程依赖操作栈,指令集更小(零地址),编译器容易实现。
  • 不需要硬件支持,可移植性强,容易实现跨平台。

面向对象

面向过程,面向对象以及函数式编程都是不同的编程思想。

面向过程的思想是一个纵向表达问题的思想,通过拆解问题的步骤来实现对问题的解答过程。

但是随着计算机程序面对的问题愈发复杂,面向过程的程序会变得越来越复杂,而且问题种类也越来越多,面向过程开发出来的函数很难在复杂的场景实现复用。

一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象与数字建模。通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。同时,面向对象能有效提高编程的效率,通过封装技术,消息机制可以像搭积木的一样快速开发出一个全新的系统。面向对象是指一种程序设计范型,同时也是一种程序开发的方法。对象是类的具体化实现。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

面向对象具有三大特征:封装性、继承性和多态性,而面向过程没有继承性和多态性,并且面向过程的封装只是封装功能,而面向对象可以封装数据和功能。

java是保留的基本类型的双类型语言,java有8种基本类型:

数据类型 存储大小 取值范围 初始化默认值 包装类型
byte 1字节(8位) -128(-27)~127(27-1) 0 Byte
short 2字节(16位) -32768(-215)~32767(215-1) 0 Short
int 4字节(32位) -22147483648(-231)~22147483647(231-1) 0 Integer
long 8字节(64位) -263~263-1 0 Long
float 4字节(32位) 符合IEEE754标准的浮点数 0.0f Float
double 8字节(64位) 符合IEEE754标准的浮点数 0.0d Double
char 2字节(16bits) Unicode 0 ~ Unicode 216 -1(本质也是数值) null Character
boolean 1字节(8位)/ 4字节(32位) true/false false Boolean

java存在对象类型之外基本类型的原因主要是性能因素,8种基本类型都有对应的包装类,包装类型可以实现和对应基本类型相同的功能,但是相较于基本类型,因为包装类型要参与垃圾回收,所以对象需要存储额外的信息。例如double的包装类型是Double,double占用8个字节,但是Double却需要占用24字节。除了占用较大的存储空间之外,计算过程中包装类也有更多的性能损耗。

Object

基本类型之外的对象类型都继承自Object类,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

Object类的方法分为以下几类:

  • equals和hashCode

equals方法用来表示两个对象是否相等,其默认实现是两个对象的内存地址是否相等,可以通过重载该方法实现其他的判定相等的方式,例如字符串String类的equals用来比较两个字符串的内容是否相等。

java语言规定如果两个对象equals,那么它们的hashCode也必须相等,所以重写equals之后也要重写hashCode方法,hashCode方法默认的实现是返回对象的内存地址。

  • clone

clone方法得到对象的副本,因为clone是protected的,所以使用clone方法的前提是重写clone方法为public方法,而且需要实现标记接口 Cloneable,否则在调用clone的时候会得到 CloneNotSupportedException,clone方法默认的是浅拷贝,对象的成员都采用地址复制的方式来clone。

  • toString

toString方法控制对象打印的输出。

  • getClass

getClass可以获取对象的类型信息,是反射的一种实现方式。

  • finalize

finalize是对象被垃圾回收时候的勾子方法,在GC之前被调用。

  • wait和notify

每个对象都有一个内置的monitor,wait和notify是Object内置的同步方式。

动态性

静态语言中的静态一般是指程序中的变量在编译阶段就确定类型,编译器可以检查变量类型,运行时候程序的行为是静态的确定性的,反之动态语言可以在运行时判断变量值的类型,允许改变程序结构或变量类型。

反射

和C、C++等语言类似,java也是静态语言,在变量声明的时候需要指定变量的类型。但同时java提供了反射机制,可以在运行时动态生成对象、获取对象属性以及调用对象方法,甚至可以在运行时加载当前程序中从未出现过的新类型。

反射让java具备了一定动态语言的特性,使得语言功能更加强大。

java的反射语法一般通过Class,Constructor,Field,Method 四个类进行实现,其中Class通过类名作为入参,返回ClassLoader加载之后的类信息。例如使用JDBC进行数据库操作,需要根据底层数据库的差异动态的加载数据库驱动,其中加载mysql驱动使用Class.forName (“com.mysql.jdbc.Driver”),通过在运行时指定驱动的方式实现了程序的动态性。

Constructor(构造器)、Field(属性)、Method(方法)可以通过下列API获取:

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数 
 
Constructor[] getConstructors() -- 获得类的所有公共构造函数 
 
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关) 
 
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

Field getField(String name) -- 获得命名的公共字段 
 
Field[] getFields() -- 获得类的所有公共字段 
 
Field getDeclaredField(String name) -- 获得类声明的命名的字段 
 
Field[] getDeclaredFields() -- 获得类声明的所有字段

Method getMethod(String name, Class[] params) -- 使用特定的参数类型获得命名的公共方法 
 
Method[] getMethods() -- 获得类的所有公共方法 
 
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型获得类声明的命名的方法 
 
Method[] getDeclaredMethods() -- 获得类声明的所有方法
字节码

除了反射之外,java语言还可以使用字节码操作实现反射类似的功能,而且性能更高,功能更加强大。常用的字节码框架有 BCEL、ASM、CGLIB、Javassist等。

invokedynamic

ivookedynamic指令在jdk7引入,jdk8实际使用,用以支持动态语言的方法调用。它将调用点(CallSite)抽象成一个 Java 类,并且将原本由 Java 虚拟机控制的方法调用以及方法链接暴露给了应用程序。在运行过程中,每一条 invokedynamic 指令将捆绑一个调用点,并且会调用该调用点所链接的方法句柄。

jdk8引入的 Lambda 表达式都是通过invokedynamic实现的。

多线程

API

java实现多线程一般有两种方法:

  • 继承java.lang.Thread,重写其中的run方法。
  • 实现java.lang.Runnable接口,实现run方法。

java通过start方法启动线程。

interrupt()方法可以中断线程,该方法是一个线程去通知另一个线程去中断,被中断的线程自身决定是否停止,非强行停止。

如果interrupt强制中止其他线程,停止的线程可能会产生脏数据以及其他不一致的场景,所以接收到interrupt调用之后,是否中止以及如何中止的主动权应该由被中断的线程决定。

除了interrupt之外,还有stop,suspend两个已经标记为@Deprecated的方法:

stop()会直接把线程停止,没给线程足够的时间处理在停止前保存数据的逻辑,容易导致数据完整性等问题;

suspend()和resume()分别是暂停以及重启线程,但是调用suspend()之后,它并不会释放锁(因为锁在线程被resume()前,不会释放锁),就直接开始进入休眠,此时可能还持有锁,这就容易导致死锁;

停止线程建议使用一个标志位进行终止变量,例如:

class MyThread extends Thread {

    private volitale boolean flag = true;

    public void setter(boolean fl) {
        this.flag = fl;
    }

    public void run() {
        System.out.println("进入MyThread");
        while (flag == true){
            //dosomething
        }
        System.out.println("Mythread结束:"+this.flag);
    
    }

}

内存管理

Java内存模型

java运行时的内存分为若干区域,分别是栈,堆以及方法区、程序计数器等。

程序计数器类似于x86架构下PC寄存器的作用,是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作就是通过改变程序计数器的值来选择下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖程序计数器来完成。

虚拟机栈表达了Java方法执行的内存模型,每个方法在执行都会创建一个栈帧用于存储局部变量表、操作数栈、动态链表、方法出口信息等。方法调用和执行完成对应着虚拟机栈的入栈和出栈动作,Java虚拟机栈也是线程私有的 ,它的生命周期与线程相同。

Java堆是在虚拟机启动时创建所有线程共享的一块内存区域,堆内存会根据垃圾回收算法的需求分成不同的区域,比如采用分代回收算法的jvm中,Java堆分为新生代和老年代。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。

Java堆可以处于物理上不连续的内存空间,只要逻辑上连续的即可。在实现上,既可以实现固定大小的,也可以是扩展的。

如果堆中没有内存完成实例分配,并且堆也无法完成扩展时,将会抛出OutOfMemoryError异常。

垃圾回收

垃圾回收是内存管理的一种手段,对于C、C++等语言,内存的分配以及回收都由程序本身控制,这种方式实现成本低,代码性能高,但是复杂程序的内存管理是很繁琐的,尤其对象跨多线程共享之后,对象的生命周期很难追踪,什么时候应该销毁对象,回收内存,以及应该由哪个线程在哪段代码执行销毁动作都变得复杂。

垃圾回收将内存的释放托管给程序的运行时进行自动清理,例如JVM内置了多种垃圾回收算法。

垃圾回收从大的阶段可以分为标记阶段和清理阶段,这两个阶段分别对应多种实现。

标记算法

标记算法用于把已经不再有任何引用的对象寻找出来。