当前位置 : 首页 » 文章分类 :  开发  »  Java-基础

Java-基础

[toc]


一、概述

Java各版本的区别

简单的说
Java SE 是做桌面端软件的。
Java EE 是用来做网站的(例如我们常见的JSP技术)
Java ME 是做手机软件的。

  • Java SE (J2SE, Java Platform Standard Edition) 标准版
    J2SE是Java的标准版,主要用于开发客户端(桌面应用软件),例如常用的文本编辑器、下载软件、即时通讯工具等,都可以通过J2SE实现。
    J2SE包含了Java的核心类库,例如数据库连接、接口定义、输入/输出、网络编程等。
    学习Java编程就是从J2SE入手。

  • Java EE (J2EE, Java Platform Enterprise Edition) 企业版
    J2EE是功能最丰富的一个版本,主要用于开发高访问量、大数据量、高并发量的网站,例如美团、去哪儿网的后台都是J2EE。
    通常所说的JSP开发就是J2EE的一部分。
    J2EE包含J2SE中的类,还包含用于开发企业级应用的类,例如EJB、servlet、JSP、XML、事务控制等。
    J2EE也可以用来开发技术比较庞杂的管理软件,例如ERP系统(Enterprise Resource Planning,企业资源计划系统)。

  • Java ME (J2ME, Java Platform Micro Edition) 微型版
    J2ME 只包含J2SE中的一部分类,受平台影响比较大,主要用于嵌入式系统和移动平台的开发,例如呼机、智能卡、手机(功能机)、机顶盒等。
    在智能手机还没有进入公众视野的时候,你是否还记得你的摩托罗拉、诺基亚手机上有很多Java小游戏吗?这就是用J2ME开发的。
    Java的初衷就是做这一块的开发。
    注意:Android手机有自己的开发组件,不使用J2ME进行开发。

Java5.0版本后,J2SE、J2EE、J2ME分别更名为Java SE、Java EE、Java ME,由于习惯的原因,我们依然称之为J2SE、J2EE、J2ME。


JDK

JDK(Java Development Kit)是一系列工具的集合,这些工具是编译Java源码、运行Java程序所必需的,例如JVM、基础类库、编译器、打包工具等。

JDK下载
http://www.oracle.com/technetwork/java/javase/downloads/index.html
页面最下面有Java Archive,点开能下载各个历史版本的JDK,所谓java版本,实际上是指JDK的版本。

JDK所提供的部分工具:
java编译器:javac.exe
java解释器:java.exe
java文档生成器:javadoc.exe
java调试器:jdb.exe

文档

JDK版本

  • JDK(Java SE Development Kit),适用于Java开发者,包括全套开发、调试、监控工具,最全。
  • Server JRE(Server Java Runtime Environment),用于服务器部署。
  • JRE(Java Runtime Environment),终端用户运行java程序所需。

linux中JDK安装

.bin自解压版

下载官网的 jdk-6u43-linux-x64.bin 自解压文件
给所有用户添加可执行的权限 chmod +x jdk-6u43-linux-x64.bin
执行该文件 ./jdk-6u43-linux-x64.bin
默认装在/usr/java目录下

配置环境变量
配置所有用户共享的jdk环境变量
编辑/etc/profile

export JAVA_HOME=/usr/java/jdk1.6.0_43
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOME/bin

让系统重新执行下/etc/profile : source /etc/profile
修改文件后要想马上生效还要运行source /etc/profile不然只能在下次重进此用户时生效。

tar.gz压缩包

下载官网的jdk-8u121-linux-x64.tar.gz压缩包并上传到linux目录:/usr/local/
进入/usr/local/目录,tar -xzvf jdk-8u121-linux-x64.tar.gz 解压到当前目录,得到jdk1.8.0_121文件夹

配置环境变量
配置所有用户共享的jdk环境变量
编辑/etc/profile

export JAVA_HOME=/usr/local/jdk1.8.0_121
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOME/bin

重启或执行命令 source /etc/profile 使配置生效


Windows中Java环境变量配置

进入环境变量配置窗口,在“用户变量”中,设置3项属性:JAVA_HOME、PATH、CLASSPATH(大小写无所谓),若已存在则点击“编辑”,不存在则点击“新建”:

  • JAVA_HOME:设为JDK的安装路径(如C:\Program Files (x86)\Java\jdk1.6.0_45),此路径下包括lib,bin,jre等文件夹(此变量最好设置,因为以后运行tomcat,eclipse等都需要依靠此变量)。
  • Path:使得系统可以在任何路径下识别java命令,设为:%JAVA_HOME%\bin%JAVA_HOME%就是引用前面指定的JAVA_HOME变量。
  • CLASSPATH:Java运行环境加载类的路径,只有类在classpath中,才能被识别和加载,设为 .;%JAVA_HOME%\lib(注意前面的点号(.),点号表示当前路径)。

打开一个CMD窗口,输入java -version或者javac命令,看到很多说明信息,证明已经安装并配置成功了。
注意:我配置完用户环境变量后不起作用,重启后也不行,后来改为配置系统环境变量,不需要重启即可生效。

我的java环境变量,系统变量:

  • JAVA_HOME: C:\Program Files (x86)\Java\jdk1.6.0_45
  • Path: 增加%JAVA_HOME%\bin,和之前的内容以;隔开
  • CLASSPATH: .;%JAVA_HOME%\lib

一次编译,到处运行

我们编写的Java源码,编译后会生成一种 .class 文件,称为字节码文件。Java虚拟机(Java Virtual Machine,简称JVM)就是负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说,只要在不同平台上安装对应的JVM,就可以运行字节码文件,运行我们编写的Java程序。

Java代码首先被编译成字节码文件,再由JVM将字节码文件翻译成机器语言,从而达到运行Java程序的目的。

注意:编译的结果不是生成机器码,而是生成字节码,字节码不能直接运行,必须通过JVM翻译成机器码才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。

注意:跨平台的是Java程序,不是JVM。JVM是用C/C++开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的JVM。


二、包和类库

源文件声明规则

  • 一个源文件中只能有一个public类,一个源文件可以有多个非public类。
  • 源文件的名称应该和public类的类名保持一致。例如:源文件中public类的类名是Employee,那么源文件应该命名为Employee.java。
  • 如果一个类定义在某个包中,那么package语句应该在源文件的首行。
  • 如果源文件包含import语句,那么应该放在package语句和类定义之间。如果没有package语句,那么import语句应该在源文件中最前面。
  • import语句和package语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

Java中,为了组织代码的方便,可以将功能相似的类放到一个文件夹内,这个文件夹,就叫做包。
包不但可以包含类,还可以包含接口和其他的包。
包以”.”来表示层级关系,例如引用时 p1.p2.Test 表示的类为 \p1\p2\Test.class。
当包中还有包时,可以使用“包1.包2.…….包n”进行指定,其中,包1为最外层的包,而包n则为最内层的包。

声明包

通过 package 关键字可以声明一个包,例如:

package p1.p2;
public class pclass {
    ... ...
}

必须将 package 语句放在所有语句的前面
在Java中提供的包,相当于系统中的文件夹。例如,上面代码中的pclass类如果保存到C盘根目录下,那么它的实际路径应该为C:\p1\p2\pclass.java。

调用包中的类

(1) 在每个类名前面加上完整的包名
(2) 通过 import 语句引入包中的类
(3) 同一个包中的类相互调用不需要import


常用包

包命名规则

  • java.* 开头的是Java的核心包,所有程序都会使用这些包中的类;
  • javax.* 开头的是扩展包,x是extension的意思,也就是扩展。虽然javax是对java的优化和扩展,但是由于javax使用的越来越多,很多程序都依赖于javax,所以javax也是核心的一部分了,也随JDK一起发布。
  • org.* 开头的是各个机构或组织发布的包,因为这些组织很有影响力,它们的代码质量很高,所以也将它们开发的部分常用的类随JDK一起发布。
  • 自定义包
    为了防止重名,有一个惯例:大家都以自己域名的倒写形式作为开头来为自己开发的包命名,例如百度发布的包会以 com.baidu.开头。
    可以认为 org.开头的包为非盈利组织机构发布的包,它们一般是开源的,可以免费使用在自己的产品中,不用考虑侵权问题,而以 com.开头的包往往由盈利性的公司发布,可能会有版权问题,使用时要注意。

常用包介绍

  • java.lang,该包提供了Java编程的基础类,例如 Object、Math、String、StringBuffer、System、Thread等,不使用该包就很难编写Java代码了。
  • java.util,该包提供了包含集合框架、遗留的集合类、事件模型、日期和时间实施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。
  • java.io,该包通过文件系统、数据流和序列化提供系统的输入与输出。
  • java.net,该包提供实现网络应用与开发的类。
  • java.sql,该包提供了使用Java语言访问并处理存储在数据源(通常是一个关系型数据库)中的数据API。
  • java.awt,java.awt包提供了创建界面和绘制图形图像的所有类
  • javax.swing,javax.swing包提供了一组“轻量级”的组件,尽量让这些组件在所有平台上的工作方式相同。
  • java.text,提供了与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。

默认导入java.lang包中的类
Java 编译器默认为所有的 Java 程序导入了 JDK 的 java.lang 包中所有的类(import java.lang.*;),其中定义了一些常用类,如 System、String、Object、Math 等,因此我们可以直接使用这些类而不必显式导入。但是使用其他类必须先导入。


import搜索路径

安装JDK时,我们已经设置了环境变量 CLASSPATH 来指明类库的路径,它的值为 .;%JAVA_HOME%\lib,而 JAVA_HOME 又为 C:\Program Files (x86)\Java\jdk1.6.0_45,所以 CLASSPATH 等价于 .;C:\Program Files (x86)\Java\jdk1.6.0_45\lib

执行import p1.Test;
Java 运行环境将依次到下面的路径寻找并载入字节码文件 Test.class:
.p1\Test.class(”.”表示当前路径)
C:\Program Files (x86)\Java\jdk1.6.0_45\lib\p1\Test.class
如果在第一个路径下找到了所需的类文件,则停止搜索,否则继续搜索后面的路径,如果在所有的路径下都未能找到所需的类文件,则编译或运行出错。

你可以在CLASSPATH变量中增加搜索路径,例如 .;%JAVA_HOME%\lib;C:\javalib,那么你就可以将类文件放在 C:\javalib 目录下,Java运行环境一样会找到。


三、数据类型

标识符与命名规则

Java标识符
Java标识符由字母、数字、下划线“_”、美元符号“$”或者人民币符号“¥”组成,并且首字母不能是数字。不能把关键字和保留字作为标识符。没有长度限制。对大小写敏感。

命名规则

  • 类名:首字母大写,其余小写。如有多个单词组成,则每个单词的首字母大写。例如:Class ImageSpride
  • 包名:一个唯一包名的前缀总是全部小写的ASCII字母并且是一个顶级域名,通常是com,edu,gov,mil,net,org,或1981年ISO
    3166标准所指定的标识国家的英文双字符代码。包名的后续部分根据不同机构各自内部的命名规范而不尽相同。这类命名规范可能以特定目录名的组成来区分部门(department),项目(project),机器(machine),或注册名(login names)。例如:com.sun.eng, com.apple.quicktime.v2, edu.cmu.cs.bovik.cheese 等;
  • 接口:命名规则:大小写规则与类名相似。例如:interface RasterDelegate;
  • 方法:方法名是一个动词,采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写。例如:run(); runFast();
  • 变量:除了变量名外,所有实例,包括类,类常量,均采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写。变量名不应以下划线或美元符号开头,尽管这在语法上是允许的。变量名应简短且富于描述。变量名的选用应该易于记忆,即,能够指出其用途。尽量避免单个字符的变量名,除非是一次性的临时变量。临时变量通常被取名为i,j,k,m和n,它们一般用于整型;c,d,e,它们一般用于字符型。例如:char c; int i;
  • 实例变量:大小写规则和变量名相似,除了前面需要一个下划线。int _employeeId; String _name;
  • 常量:类常量和ANSI常量的声明,应该全部大写,单词间用下划线隔开。(尽量避免ANSI常量,容易引起错误),例如:
    static final int MIN_WIDTH = 4;
    static final int MAX_WIDTH = 999;

关键字

  • 访问控制:private, protected, public
  • 类,方法和变量修饰符:abstract, class, extends, final, implements, interface, native, new, static, strictfp, synchronized, transient, volatile
  • 程序控制:break, continue, return, do, while, if, else, for, instanceof, switch, case, default
  • 错误处理:try, cathc, throw, throws
  • 包相关:import, package
  • 基本类型:boolean, byte, char, double, float, int, long, short, null, true, false
  • 变量引用:super, this, void
  • 保留字:goto, const

null关键字

注意:null是关键字,但NULL不是关键字

Object obj = NULL; // Not Ok
Object obj1 = null  //Ok

可以将null赋予任何引用类型,你也可以将null转化成任何类型,但不能将null赋给基本类型变量

instanceof操作符

instanceof是一个二元操作符(运算符),和==类似,判断其左边对象是否为其右边类的实例,返回值为boolean类型。
如果变量引用的是当前类或它的子类的实例,instanceof 返回 true,否则返回 false。
boolean result = object instanceof class


整型

在Java中,整型数据的长度与平台无关,相反,C/C++ 整型数据的长度是与平台相关的。
与c/c++不同,Java 不支持无符号类型(unsigned)。

八进制有一个前缀 0,例如 010 对应十进制中的 8;
十六进制有一个前缀 0x,例如 0xCAFE;
从 Java 7 开始,可以使用前缀 0b 来表示二进制数据,例如 0b1001 对应十进制中的 9。
同样从 Java 7 开始,可以使用下划线来分隔数字,类似英文数字写法,例如 1_000_000 表示 1,000,000,也就是一百万。下划线只是为了让代码更加易读,编译器会删除这些下划线。


浮点型

float 类型有效数字最长为 7 位,有效数字长度包括了整数部分和小数部分,
每个float类型后面都有一个标志“F”或“f”,大小写无关。

double 类型有效数字最长为 15 位,double 后面都有标志“D”或“d”,大小写无关。

注意:不带任何标志的浮点型数据,系统默认是 double 类型。


字符型

char类型占两个字节,可直接赋值为一个汉字,例如
char lastName = '张';


静态数组

定义
与C、C++不同,Java在定义数组时并不为数组元素分配内存,因此[ ]中无需指定数组元素的个数(数组长度):

int demoArray[];
int[] demoArray;

定义后必须用new为其分配内存空间:
demoArray=new int[3];
或者可以定义的同时分配空间:
int demoArray[] = new int[3];

初始化
定义的同时初始化:

int intArray[] = {3,2,4};
String stringArray[] = {"我", "是", "谁?"};

字符串数组初始化

String[] str = new String[5]; //创建一个长度为5的String(字符串)型的一维数组
String[] str = new String[]{"","","","",""};
String[] str = {"","","","",""};

String[] str = {"1","2","3"}String[] str = newString[]{"1","2","3"}的区别
String[] str = {"1","2","3"}; 这种形式叫数组初始化式(ArrayInitializer),只能用在声明同时赋值的情况下。
String[] str = new String[]{"1","2","3"}是一般形式的赋值,=号的右边叫数组字面量(ArrayLiteral),数组字面量可以用在任何需要一个数组的地方(类型兼容的情况下)。
如:

String[] str = {"1","2","3"}; // 正确的
String[] str = new String[]{"1","2","3"} // 也是正确的

String[] str;
str = {"1","2","3"}; // 编译错误

因为数组初始化式只能用于声明同时赋值的情况下。
改为:

String[] str;
str = new String[] {"1","2","3"}; // 正确了

又如:

void f(String[] str) {
}
f({"1","2","3"}); // 编译错误

正确的应该是:

f(new String[] {"1","2","3"});

使用
与C、C++不同,Java对数组元素要进行越界检查以保证安全性。
每个数组都有一个length属性来指明它的长度,长度指的是已分配的空间个数,而不是实际使用的空间个数。

foreach遍历

for( arrayType varName: arrayName ){
    // Some Code
}

每循环一次,就会获取数组中下一个元素的值,保存到 varName 变量,直到数组结束,例如:

for( int i : intArray ) {
    System.out.println(i);
}

二维数组
Java语言中,由于把二维数组看作是数组的数组,数组空间不是连续分配的,所以不要求二维数组每一维的大小相同:

int a[ ][ ] = new int[2][ ];
a[0] = new int[3];
a[1] = new int[5];

包装类(Wrapper Classes)

Java为每种基本数据类型分别设计了对应的类:

基本数据类型 对应的包装类
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean
  • 装箱:由基本类型向对应的包装类转换称为装箱,例如把 int 包装成 Integer 类的对象;
  • 拆箱:包装类向对应的基本类型转换称为拆箱,例如把 Integer 类的对象重新简化为 int;
    Java 1.5 之后可以自动拆箱装箱。

函数func(int n)和func(Integer n)构成重载
线上出现过的一个问题,本来调用func(Integer n)时应该传入Integer类型参数,由于编译器版本不同,新来的同事编译后参数类型被改为了Integer.intValue(),调用func(Integer n)时报错说找不到func()函数。后来看了下,我用的jdk1.7,他用的1.6。


四、面向对象

Object类

Object 类位于 java.lang 包中,是所有 Java 类的祖先,Java 中的每个类都由它扩展而来。所有类都自动继承Object类,不需要定义时指定。

方法:

  • boolean equals(Object obj)
    equals()方法只能比较引用类型,“==”可以比较引用类型及基本类型。
    当用 equals() 方法进行比较时,对类 File、String、Date 及包装类来说,是比较类型及内容而不考虑引用的是否是同一个实例。

  • hashCode()
    在同一个应用程序执行期间,对同一个对象调用 hashCode(),必须返回相同的整数结果——前提是 equals() 所比较的信息都不曾被改动过。
    如果两个对象被 equals() 方法视为相等,那么对这两个对象调用 hashCode() 必须获得相同的整数结果。

  • String toString()
    在进行 String 与其它类型数据的连接操作时,会自动调用 toString() 方法


继承

继承使用extends关键字。

单继承性
Java 允许一个类仅能继承一个其它类,即一个类只能有一个父类,这个限制被称做单继承性。
接口允许多继承

构造方法不能被继承
一个类能得到构造方法,只有两个办法:编写构造方法,或者根本没有构造方法,类有一个默认的构造方法。


static关键字

  • 1、静态变量
    静态变量不属于任何独立的对象,编译器只为整个类创建一个静态变量的副本,多实例共享该内存。
    注意:只要类被装载,静态变量就会被初始化,不管你是否使用了这个静态变量

  • 2、静态方法
    静态方法不能向对象实施任何操作,所以不能在静态方法中访问实例变量,只能访问类的静态变量
    以下情形可以使用静态方法:
    一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如 Math.pow())。
    一个方法只需要访问类的静态变量。
    注意:Java程序的main()方法必须设为public static,在程序启动时还没有任何对象,main()方法是程序的入口。

  • 3、静态初始器(静态块)
    块是由大括号包围的一段代码。
    静态初始器(Static Initializer)是一个存在于类中、方法外面的静态块。
    静态初始器仅仅在类装载的时候(第一次使用类的时候)执行一次,往往用来初始化静态变量。

  • 4、静态导入
    静态导入是 Java 5 的新增特性,用来导入类的静态变量和静态方法
    例如:

    import static packageName.className.methonName;  // 导入某个特定的静态方法
    import static packageName.className.*;  // 导入类中的所有静态成员
    

final关键字

final所修饰的数据具有“终态”的特征,表示“最终的”意思。

  • final 修饰的类不能被继承。
  • final 修饰的方法不能被子类重写。
  • final 修饰的变量(成员变量或局部变量)即成为常量,只能赋值一次。
  • final 修饰的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
  • final 修饰的局部变量可以只声明不赋值,然后再进行一次性的赋值。

static final

用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。
特别要注意一个问题:
对于被static和final修饰过的实例常量,实例本身不能再改变了,但对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象,这一点在编程中用到很多。
例如:

public class TestStaticFinal {
    private static final String strStaticFinalVar = "aaa";
    private static String strStaticVar = null;
    private final String strFinalVar = null;
    private static final int intStaticFinalVar = 0;
    private static final Integer integerStaticFinalVar = new Integer(8);
    private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>();

    private void test() {
        System.out.println("-------------值处理前----------\r\n");
        System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");
        System.out.println("strStaticVar=" + strStaticVar + "\r\n");
        System.out.println("strFinalVar=" + strFinalVar + "\r\n");
        System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");
        System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");
        System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");


        //strStaticFinalVar="哈哈哈哈";    //错误,final表示终态,不可以改变变量本身.
        strStaticVar = "哈哈哈哈";         //正确,static表示类变量,值可以改变.
        //strFinalVar="呵呵呵呵";          //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
        //intStaticFinalVar=2;            //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
        //integerStaticFinalVar=new Integer(8);  //错误, final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
        alStaticFinalVar.add("aaa");    //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。
        alStaticFinalVar.add("bbb");    //正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。

        System.out.println("-------------值处理后----------\r\n");
        System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");
        System.out.println("strStaticVar=" + strStaticVar + "\r\n");
        System.out.println("strFinalVar=" + strFinalVar + "\r\n");
        System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");
        System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");
        System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");
    }

    public static void main(String args[]) {
        new TestStaticFinal().test();
    }
}

内部类

在一个类(或方法、语句块)的内部定义的类,叫内部类(Inner Class),或嵌套类(Nested Class)

内部类会被编译成独立的字节码文件。
编译器将会把内部类翻译成用$符号分隔外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。
注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。

内部类只能访问外部的final变量

匿名内部类和局部内部类只能访问外部的final变量
原因是:局部变量的生命周期与局部内部类对象的生命周期的不一致性!
设方法f()被调用,从而在它的调用栈中生成了变量i,此时产生了一个局部内部类对象inner_object,它访问了该局部变量i。当方法f()运行结束后,局部变量i就已死亡了,不存在了。但局部内部类对象inner_object还可能一直存在(只能没有人再引用该对象时,它才会死亡),它不会随着方法f()运行结束而死亡。这时就出现了一个”荒唐”结果:局部内部类对象inner_object要访问一个已不存在的局部变量i
如何才能实现?
当变量是final时,通过将final局部变量”复制”一份,复制品直接作为局部内部中的数据成员。这样,当局部内部类访问局部变量时,其实真正访问的是这个局部变量的”复制品”(即这个复制品就代表了那个局部变量)。因此,当运行栈中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是”复制品”),给人的感觉:好像是局部变量的”生命期”延长了。
那么,核心的问题是:怎么才能使得访问”复制品”与访问真正的原始的局部变量其语义效果是一样的呢?
当变量是final时,若是基本数据类型,由于其值不变,因而其复制品与原始的量是一样,语义效果相同(若不是final,就无法保证复制品与原始变量保持一致了,因为在方法中改的是原始变量,而局部内部类中改的是复制品)。
当变量是final时,若是引用类型,由于其引用值不变(即永远指向同一个对象),因而其复制品与原始的引用变量一样,永远指向同一个对象(由于是final从而保证只能指向这个对象,再不能指向其它对象),达到局部内部类中访问的复制品与方法代码中访问的原始对象永远都是同一个,即语义效果是一样的。否则,当方法中改原始变量,而局部内部类中改复制品时,就无法保证复制品与原始变量保持一致了。


super关键字

super关键字与this类似,用来表示父类。
super 也可以用在子类的子类中,super能自动向上层类追溯,直到找到方法的最早定义。
super关键字的功能:

  • 调用父类中声明为 private 的变量。
  • 调用父类中已经被覆盖了的方法。
  • 作为方法名表示父类构造方法。

多态

方法重载

在一个类中,有相同的函数名称,但形参不同的函数。方法的重载是根据函数的参数列表来决定的,即:参数列表的个数,类型,顺序三个方面。
(1) 声明为final的方法不能被重载
(2) 仅返回类型不同不构成重载。

方法覆盖

如果在新类中定义一个方法,其名称、返回值类型和参数列表正好与父类中的相同,那么,新方法被称做覆盖旧方法
(1) 覆盖方法的返回类型、方法名称、参数列表必须与原方法的相同。子类覆盖父类的方法,在jdk1.5后,参数返回类可以是父类方法返回类的子类,即返回类型可以是父类返回类型的兼容类型。
(2) 被覆盖的方法不能是final类型,因为final修饰的方法是无法覆盖的。
(3) 被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
(4) 被覆盖的方法不能为static。如果父类中的方法为静态的,而子类中的方法不是静态的,但是两个方法除了这一点外其他都满足覆盖条件,那么会发生编译错误;反之亦然。即使父类和子类中的方法都是静态的,并且满足覆盖条件,但是仍然不会发生覆盖,因为静态方法是在编译的时候把静态方法和类的引用类型进行匹配。
(5) 子类覆盖父类方法,可以修改方法作用域修饰符,但只能把方法的作用域放大,而不能把public修改为private,即子类方法不能缩小父类方法的访问权限

子类覆盖父类方法时对于抛出异常的要求

子类覆盖父类方法时,如有异常,子类中抛出的异常与父类中一致或是其子类型,即子类抛出的异常类型不能比父类抛出的异常类型更宽泛。
如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。
如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不能抛出异常。
对于构造函数,又有不同:
如果父类的无参构造函数抛出了异常,则子类的无参构造函数不能省略不写,并且必须抛出父类的异常或父类异常的父类,即必须抛出更广泛的异常。
这看似违背了子类抛出的异常类型不能比父类抛出的异常类型更宽泛的原则。但其实并不是这样,因为构造函数没有覆写的概念,只是构造函数间的引用调用而已,即子类的无参构造函数默认调用的是父类的构造函数,所以子类的无参构造函数抛出的异常必须必父类更广泛。
如果是自定义构造函数则没有此限制,因为子类的自定义构造函数并没有调用父类的构造函数。

覆盖和重载的区别
(1) 方法覆盖要求参数列表必须一致,而方法重载要求参数列表必须不一致。
(2) 方法覆盖要求返回类型必须一致,方法重载对此没有要求。
(3) 方法覆盖只能用于子类覆盖父类的方法,方法重载用于同一个类中的所有方法(包括从父类中继承而来的方法)。方法覆盖只存在于子类和父类之间,同一个类中只能重载。
(4) 方法覆盖对方法的访问权限和抛出的异常有特殊的要求,而方法重载在这方面没有任何限制。
(5) 父类的一个方法只能被子类覆盖一次,而一个方法可以在所有的类中可以被重载多次。

运行时多态(动态绑定)

父类变量指向子类对象

多态存在的三个必要条件:要有继承、要有重写、父类变量引用子类对象。
JVM预先为每个类创建了一个方法表(method lable),其中列出了所有方法的名称、参数签名和所属的类


抽象类

只给出方法定义而不具体实现的方法被称为抽象方法,抽象方法是没有方法体的,在代码的表达上就是没有“{}”。
包含一个或多个抽象方法的类也必须被声明为抽象类
使用abstract修饰符来表示抽象方法和抽象类。

  • 抽象类除了包含抽象方法外,还可以包含具体的变量和具体的方法。
  • 类即使不包含抽象方法,也可以被声明为抽象类,防止被实例化。
  • 包含抽象方法的类一定是抽象类。
  • 抽象类不能直接使用,必须用子类去实现抽象类,然后使用其子类的实例。

使用场景:
创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例,也就是可以使用抽象类来充当形参,实际实现类作为实参,也就是多态的应用。


接口interface

接口可以看做是一种特殊的抽象类,接口中所有的方法必须都是抽象的,不能有方法体,它比抽象类更加“抽象”。
接口指定一个类必须做什么,而不是规定它如何去做。
接口使用interface关键字来声明。

  • 接口中声明的成员变量默认都是public static final的,必须显示的初始化。因而在常量声明时可以省略这些修饰符。
  • 接口中只能定义抽象方法,这些方法默认为public abstract的,因而在声明方法时可以省略这些修饰符。
  • 接口中没有构造方法,不能被实例化。
  • 一个接口不实现另一个接口,但可以继承多个其他接口。接口的多继承特点弥补了类的单继承。

类实现接口
类实现接口的关键字为implements

  • 如果一个类不能实现该接口的所有抽象方法,那么这个类必须被定义为抽象类。
  • 一个类只能继承一个父类,但却可以实现多个接口。

接口作为类型
不允许创建接口的实例,但允许定义接口类型的引用变量,该变量指向了实现接口的类的实例。
使用指向实现接口的类的实例的引用类型可以访问类中所实现的接口中的方法。

抽象类和接口的区别
(1) 抽象类可以为部分方法提供实现,避免了在子类中重复实现这些方法,提高了代码的可重用性,这是抽象类的优势;而接口中只能包含抽象方法,不能包含任何实现。
(2) 一个类只能继承一个直接的父类(可能是抽象类),但一个类可以实现多个接口,这个就是接口的优势。


泛型

所谓“泛型”,就是“宽泛的数据类型”,任意的数据类型。

泛型类
类型参数需要在类名后面给出,类型参数(泛型参数)由尖括号包围,多个参数由逗号分隔。
泛型类定义
class className<T1,T2>{ ... }
泛型类在实例化时必须指出具体的类型,也就是向类型参数传值:
className variable<T1, T2> = new className<T1, T2>();

泛型方法
泛型方法与泛型类没有必然的联系,泛型方法有自己的类型参数,在普通类中也可以定义泛型方法。

类型擦除
在使用泛型时没有指明数据类型,那么就会擦除泛型类型,编译器会将所有数据向上转型为 Object。


五、异常处理

异常类

当异常情况发生,一个代表该异常的对象被创建并且在导致该错误的方法中被抛出,该方法可以选择自己处理异常或传递该异常。
所有异常类型都是内置类Throwable的子类,Throwable下有Exception子类和Error子类。
Throwable重载了toString()方法(由Object定义),返回一个包含异常描述的字符串,所以在catch语句中可以将各种异常e的信息输出:
System.out.println("Exception: " + e);

Java异常类层次结构图:

Throwable有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

不受检查的异常

Java编译器不会检查此类异常,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
当然,如果要抛出运行时异常并捕捉后自己处理,也是可以的。
不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。
不受检查的异常 = 运行时异常 + 错误

受检查的异常

即必须进行处理的异常,如果不处理,程序就不能编译通过。
受检查的异常包括RuntimeException以外的异常,类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
受检查的异常 = RuntimeException以外的异常


异常处理机制

在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。

  • 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
  • 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。

对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同:

  • 运行时异常:由于运行时异常的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
  • 错误:对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
  • 受检查的异常:对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。

异常捕获try/catch

多重catch
当异常被引发时,每一个catch子句被依次检查,第一个匹配异常类型的子句执行。
当一个catch语句执行以后,其他的子句被旁路,执行从try/catch块以后的代码开始继续。

多重catch时,异常子类必须在它们任何父类之前catch,否则子类将永远不会达到。
因为父类的catch语句将捕获该类型及其所有子类类型的异常。
含有无法到达代码时无法编译通过。

嵌套try
如果一个内部的try语句不含特殊异常的catch处理程序,则外层try语句的catch处理程序将检查是否与之匹配。这个过程将继续直到一个catch语句匹配成功,或者是直到所有的嵌套try语句被检查耗尽。如果没有catch语句匹配,Java的运行时系统将处理这个异常。


throws抛出异常

程序可以用throw语句抛出明确的异常,程序执行在throw语句之后立即停止。

所有的Java内置的运行时异常有两个构造函数:一个没有参数,一个带有一个字符串参数,当用到第二种形式时,参数指定描述异常的字符串。
所以可以在抛出异常时携带字符串参数:
throw new NullPointerException("demo");
如果对象用作 print( )或println( )的参数时,该字符串被显示。这同样可以通过调用getMessage( )来实现,getMessage( )是由Throwable定义的。

throws子句列举了一个方法可能抛出的所有异常类型。
一个方法可以抛出的所有其他类型的异常必须在throws子句中声明。如果不这样做,将会导致编译错误。
多数从RuntimeException派生的异常类都自动可用,并且它们不需要被包含在任何方法的throws列表中。

throws子句格式

type method-name(parameter-list) throws exception-list{
    // body of method
}

exception-list是该方法可以抛出的以有逗号分割的异常类列表

接口抛出异常

  • 接口可以声明抛出异常,他的实现可以不抛出异常,调用的时候也会抛出异常
  • 接口没有声明抛出异常,它的实现不能抛出异常
  • 子类覆盖父类方法时,如有异常,子类中抛出的异常与父类中一致或是其子类型,即子类抛出的异常类型不能比父类抛出的异常类型更宽泛。如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不能抛出异常。

finally

finally创建一个代码块。该代码块在一个try/catch 块完成之后另一个try/catch出现之前执行。finally块无论有没有异常抛出都会执行。
inally子句是可选项,可以有也可以无。然而每一个try语句至少需要一个catch或finally子句。

使用场景
finally在关闭文件句柄和释放任何在方法开始时被分配的其他资源时是很有用的

返回前也会被调用
一个方法将从一个try/catch块返回到调用程序的任何时候,即使finally块之前有return语句,finally子句在方法返回之前也会被调用。

小结
try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
catch 块:用于处理try捕获到的异常。
finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中发生了异常。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)关闭CPU。

深入理解java异常处理机制
http://blog.csdn.net/hguisu/article/details/6155636


断言assert

assert格式
assert 布尔表达式
assert 布尔表达式 : 细节描述
如果布尔表达式的值为false,将抛出AssertionError异常,细节描述可填入想展示的信息。

编译
使用命令行编译时要加上参数’-source 1.4’:
javac -source 1.4 AssertExample.java

执行
命令行运行时屏蔽断言:
java –disableassertions 类名 或 java –da 类名
命令行运行时允许断言:
java –enableassertions 类名 或 java –ea 类名


六、输入输出

java.io包中的每一个类都代表了一种特定的输入或输出流。
Java提供了两种类型的输入输出流:一种是面向字节的流,另一种是面向字符的流。

  • 字节流
    面向字节的流,数据的处理以字节为基本单位;
    字节流(Byte Stream)每次读写8位二进制数,也称为二进制字节流或位流。
    字节流除了能够处理纯文本文件之外,还能用来处理二进制文件的数据。
    InputStream类和OutputStream类是所有字节流的父类。
  • 字符流
    面向字符的流,用于字符数据的处理;
    字符流一次读写16位二进制数,并将其做一个字符而不是二进制位来处理。
    Java语言的字符编码采用的是16位的Unicode码。Java 把一个汉字或英文字母作为一个字符对待,回车或换行作为两个字符对待。
    Reader和Writer是java.io包中所有字符流的父类。

java系统提供3个可以直接使用的流对象:
System.in(标准输入),通常代表键盘输入。
System.out(标准输出):通常写往显示器。
System.err(标准错误输出):通常写往显示器。

标准输入输出
println() 输出内容后换行,print() 不换行。
Java中没有像C语言中的scanf()语句,从控制台获取输入有点麻烦,推荐使用Scanner类:

Scanner sc = new Scanner(System.in);
System.out.print("输入年份:");
int year = sc.nextInt();

File.separator
String File.separator,与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符
系统默认名称分隔符由系统属性 File.separator 定义,可通过File类的公共静态字段 separator 和 separatorChar 获得。
对于 UNIX 平台,绝对路径名的前缀始终是 “/“。相对路径名没有前缀。表示根目录的绝对路径名的前缀为 “/“ 且名称序列为空。
对于 Microsoft Windows 平台,包含盘符的路径名前缀由驱动器号和一个 “:” 组成。如果路径名是绝对路径名,还可能后跟 “\\”。UNC 路径名的前缀是\\\\;主机名和共享名是名称序列中的前两个名称。没有指定驱动器的相对路径名没有前缀。


面向字符的输入流

面向字符的输入流类都是Reader的子类

Reader类层次结构

Reader类主要子类

类名 功能描述
CharArrayReader 从字符数组读取的输入流
BufferedReader 缓冲输入字符流
PipedReader 输入管道
InputStreamReader 将字节转换到字符的输入流
FilterReader 过滤输入流
StringReader 从字符串读取的输入流
LineNumberReader 为输入数据附加行号
PushbackReader 返回一个字符并把此字节放回输入流
FileReader 从文件读取的输入流

Reader提供的常用方法

方法 功能描述
void close() 关闭输入流
void mark() 标记输入流的当前位置
boolean markSupported() 测试输入流是否支持 mark
int read() 从输入流中读取一个字符
int read(char[] ch) 从输入流中读取字符数组
int read(char[] ch, int off, int len) 从输入流中读 len 长的字符到 ch 内
boolean ready() 测试流是否可以读取
void reset() 重定位输入流
long skip(long n) 跳过流内的 n 个字符

使用FileReader类读取文件

FileReader类是InputStreamReader类的子类

import java.io.*;
class ep10_1{
    public static void main(String args[]) throws IOException{
        char a[]=new char[1000]; //创建可容纳 1000 个字符的数组
        FileReader b=new FileReader("ep10_1.txt");
        int num=b.read(a); //将数据读入到数组 a 中,并返回字符数
        String str=new String(a,0,num); //将字符串数组转换成字符串
        System.out.println("读取的字符个数为:"+num+",内容为:\n");
        System.out.println(str);
    }
}

面向字符的输出流

面向字符的输出流都是类 Writer 的子类

Writer类层次结构:

Writer类的主要子类:

类名 功能说明
CharArrayWriter 写到字符数组的输出流
BufferedWriter 缓冲输出字符流
PipedWriter 输出管道
OutputStreamWriter 转换字符到字节的输出流
FilterWriter 过滤输出流
StringWriter 输出到字符串的输出流
PrintWriter 包含 print()和 println()的输出流
FileWriter 输出到文件的输出流

Writer类提供的常用方法:

方法 功能描述
void close() 关闭输出流
void flush() 将缓冲区中的数据写到文件中
void writer(int c) 将单一字符 c 输出到流中
void writer(String str) 将字符串 str 输出到流中
void writer(char[] ch) 将字符数组 ch 输出到流
void writer(char[] ch, int offset, int length) 将一个数组内自offset起到length长的字符输出到流

使用FileWriter类写入文件:

FileWriter 类是 Writer 子类 OutputStreamWriter 类的子类

import java.io.*;
class ep10_3{
    public static void main(String args[]){
        try{
            FileWriter a=new FileWriter("ep10_3.txt");
            for(int i=32;i<126;i++){
                a.write(i);
            }
            a.close();
        }
        catch(IOException e){}
    }
}

面向字节的输入流

面向字节的输入流都是InputStream类的子类

InputStream类层次结构

InputStream的主要子类

类名 功能描述
FileInputStream 从文件中读取的输入流
PipedInputStream 输入管道
FilterInputStream 过滤输入流
ByteArrayInputStream 从字节数组读取的输入流
SequenceInputStream 两个或多个输入流的联合输入流,按顺序读取
ObjectInputStream 对象的输入流
LineNumberInputStream 为文本文件输入流附加行号
DataInputStream 包含读取 Java 标准数据类型方法的输入流
BufferedInputStream 缓冲输入流
PushbackInputStream 返回一个字节并把此字节放回输入流

InputStream提供的常用方法

方法 功能描述
void close() 关闭输入流
void mark() 标记输入流的当前位置
void reset() 将读取位置返回到标记处
int read() 从输入流中当前位置读入一个字节的二进制数据,以此数据为低位字节,补足16位的整型量(0~255)后返回,若输入流中当前位置没有数据,则返回-1
int read(byte b[]) 从输入流中的当前位置连续读入多个字节保存在数组中,并返回所读取的字节数
int read(byte b[], int off, int len) 从输入流中当前位置连续读len长的字节,从数组第off+1个元素位置处开始存放,并返回所读取的字节数
int available() 返回输入流中可以读取的字节数
long skip(long n) 略过n个字节
long skip(long n) 跳过流内的n个字符
boolean markSupported() 测试输入数据流是否支持标记

面向字节的输出流

面向字节的输出流都是OutputStream类的子类

OutputStream类的层次结构

OutputStream的主要子类

类名 功能描述
FileOutputStream 写入文件的输出流
PipedOutputStream 输出管道
FilterOutputStream 过滤输出流
ByteArrayOutputStream 写入字节数组的输出流
ObjectOutputStream 对象的输出流
DataOutputStream 包含写Java标准数据类型方法的输出流
BufferedOutputStream 缓冲输出流
PrintStream 包含print()和println()的输出流

OutputStream提供的常用方法

方法 功能描述
void close() 关闭输出流
void flush() 强制清空缓冲区并执行向外设输出数据
void write(int b) 将参数b的低位字节写入到输出流
void write(byte b[]) 按顺序将数组b[]中的全部字节写入到输出流
void write(byte b[], int off, int len) 按顺序将数组b[]中第off+1个元素开始的len个数据写入到输出流

上一篇 Java-字符串

下一篇 紫金陈《谋杀官员》系列读后感

域名迁移公告
2017年12月20日起,本博客迁移到新域名madaimeng.com,旧域名masikkk.com不再更新内容,但将永久保持可访问!
阅读
13,097
阅读预计48分钟
创建日期 2015-06-30
修改日期 2017-09-14
类别
百度推荐