Java语言比较特殊,它既是编译型语言又是解释型语言。Java的源程序代码必须通过编译器将其编译为Java字节码(byte code)后才能运行,所以被称为编译型语言。但Java字节码不能直接运行,只能在Java虚拟机环境中被解释执行,因此称之为解释型语言
Java程序是由虚拟机负责执行,并非是操作系统,这样做的优点是可以实现程序的跨平台运行
简单性
与C++语言相比,Java语言有着突出的特点,摒弃了C++语言中不宜理解的部分,减少了编程的复杂性,例如去掉了指针变量、结构体、运算符重载、多重继承等复杂特性
面型对象
除了基本数据类型外,都是对象,程序代码以类的形式组织,由类来定义对象的各种属性和行为
平台无关性
安全性
实时内存分配及布局来防止程序员直接修改物理内存布局;通过字节码验证器对字节码进行检验,防止网络病毒及其他非法代码侵入;异常处理机制
分布式
分布式包括数据分布和操作分布。数据分布是指数据可以分散在不同主机之上,而操作分布是指把一个计算任务分散到不同主机上进行操作
多线程机制
并行处理多项任务
内存管理机制
自动垃圾回收机制对内存进行管理
Java开发工具包(Java Development Kit, JDK)
Java运行环境工具JRE(Java Runtime Environment)
Java程序使用的各种对象,如变量、方法、类,数组等都要有名字,这些名字称为标识符(Identifier),标识符需遵循如下语法规则
标识符被Java语言赋予特定含义的称为保留字(Reserved Word),而在保留字中又有一些标识符对Java的编译器有特殊的意义,它们用来表示一种数据类型,或者表示程序的结构等,称之为关键字(Keyword)
Java语言中共有53个保留字,其中50个为关键字(goto和const目前尚未使用),另外3个保留字是值,分别为true,false和null
Java的数据类型分为基本数据类型和引用数据类型两种。基本数据类型在声明变量后会立刻分配数据的内存空间,数据占用的空间大小是固定的,与软硬件环境无关;引用数据类型在声明变量时不会分配数据的内存空间,只会分配一个空间来存储数据的内存地址。
Java语言共有8种基本数据类型和3种引用数据类型,如下图
基本数据类型对应的存储需求如下
类型 | 存储需求 | 类型 | 存储需求 |
---|---|---|---|
byte | 1字节 | float | 4字节 |
short | 2字节 | double | 8字节 |
int | 4字节 | ||
long | 8字节 |
通常情况下,程序中出现的浮点型数值默认为double类型,如果要将一个浮点型数值指定为float类型,需要在数值后加字母F或f
整型常量,程序中出现的整型常量常被看作int型,十进制的整型常量以非0数字开头,八进制以数字0开头,十六进制以0x或者0X开头,二进制以0b或者0B开头
自动类型转换都是将低精度数据转换为高精度数据,但在实际情况中有时需要将高精度数据转换为低精度数据,如将double型转化为int型,此时需要强制类型转换完成。强制类型转换的格式为 (目标类型)变量名
当x,y均为浮点型,进行相等比较时,要避免使用x==y,可以写为Math.abs(x-y)<1e-6
Java语言中,位运算符只能用于整型和字符型数据
条件运算符是Java语言中唯一的三目运算符,其格式为 表达式1?表达式2:表达式3
else不能单独使用,与离它最近的if配对
switch语句中每个case后面的语句块都应该有break语句,若缺少break,则会在执行某一个语句块之后自动将其后面的所有语句块都执行一遍
do-while后面的分号”;“不能少
while语句多用于循环次数不定的情况,在不满足条件时不执行;
do-while语句多用于至少执行一次循坏体的情况;
for语句多用于循环次数固定的情况
foreach循环中只允许访问数组元素,而不允许修改数组元素
Java语言中,数组是引用数据类型
栈内存和堆内存
栈内存中存储基本数据类型、对象引用变量(对象名)和数组引用变量(数组名),当超出变量的作用域之后,Java会自动释放这些变量占用的内存空间,该内存空间可以立刻被另作它用
堆内存用于存储数组和对象的数据。在堆内存中分配的内存,由Java虚拟机负责回收管理。在堆内存中创建一个数组或者对象时,一般会将其在堆内存中的首地址赋值给栈内存中声明的变量,这个变量就是数组或者对象的引用变量。这样,就可以使用栈内存中的引用变量来访问堆内存中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号
Java语言在数组声明时并不为数组元素分配空间,但只是在栈内存中为数组名(引用变量)分配了空间,但值未定
数组拷贝
Arrays.copyOf();
Arrays.copyOfRange();
System.arraycopy();
String s1 = "Java";
String s2 = new String("Java");
变量s1和s2都表示字符串“Java",但是两者有区别。s1指向的字符串”Java"存储在字符串常量池,s2指向的字符串“Java"存储在堆内存中
Java虚拟机为了减少字符串常量池的重复创建,开辟了一个特殊的内存区域,称为字符串常量池。当以字符串常量池形式来创建字符串对象时,Java虚拟机首先会对这个常量进行检查,如果字符串常量池中存在相同内容的字符串对象,则将这个对象引用返回;否则,创建一个新的字符串对象,然后将这个对象放入字符串常量池,并返回该对象引用。第2种形式创建的字符串对象,会在堆内存中为其分配空间,并且即使字符串对象与之前创建的字符串对象内容相同仍然会再次分配空间。总之,使用new关键字创建字符串对象一定会分配新的内存空间
String类
StringBuffer类
StringBulider类
同StringBuffer类似
【三者比较】
String与StringBuffer二者区别
相同点:
不同点:
StringBuffer可变类 ,内容可以修改,诸多方法是在修改内容
StringBuffer 没有重写
String和StringBuffer都重写了Object的toString()方法,String返回实例本身,StringBuffer返回一个内容相同的String对象的引用
String对象可以用操作符“+”连接。
运行速度:StringBuilder > StringBuffer > String
安全性:StringBuilder是线程不安全的,而StringBuffer是线程安全的
Scanner类的成员方法nextLine()
和next()
都可以读取字符串,但它们之间有着本质区别。next()方法一定要读取到有效字符(非空格、非Tab键、非Enter键)后才能完成输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等分隔符,next()方法会自动将其忽略。只有在输入有效字符之后,next()方法才能将其后输入的空格键、Tab键或Enter键等视为分隔符。所以next()方法不能得到带空格的字符串。而nextLine()方法的结束符只能是Enter键,即nextLine()方法返回的是Enter键之前的所有字符,可以得到带空格的字符串
//import java.io.IOException;
//import java.util.Scanner;
class ShowIO{
public void showIO() {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
float b = sc.nextFloat();
double c = sc.nextDouble();
String str = sc.next();
char cc = 0;
try {
cc = (char)System.in.read(); //字节形式
} catch (IOException e) {
e.printStackTrace();
}
}
public void seriesRead() {
Scanner sc = new Scanner(System.in);
//连续读入直到输入“0”结束
while(!sc.hasNext("0")) {
int x = sc.nextInt();
System.out.println(Integer.toBinaryString(x));
}
}
}
面向对象程序设计有三个基本特性:封装、继承、多态
类由数据成员与方法成员封装而成,其中数据成员表示类的属性,方法成员表示类的行为。Java语言把数据成员称为域变量、属性、成员变量等,而把方法成员也称为成员方法
类的实例化结果是对象
类修饰符 | 说明 |
---|---|
public | 将一个类定义为公共类,该类可以被任何类访问 |
abstract | 将一个类定义为抽象类,不可以创建它的对象 |
final | 将一个类定义为最终类,他不能被其他类继承 |
缺省 | 只有在同一个包中的类才能访问这个类 |
成员变量修饰符 | 说明 |
---|---|
public | 公共访问修饰符,该变量为公共的,可以被任何类的方法访问 |
private | 私有访问修饰符,该变量只允许本类的方法访问,其他任何类(包括子类)中的方法均不能访问 |
protected | 保护访问修饰符,该变量可以被本类、子类及同一个包中的类访问,在子类中可以覆盖此变量 |
缺省 | 缺省访问修饰符,该变量在同一个包中的类可以访问,其他包中的类不能访问 |
final | 最终修饰符,该变量的值不能被修改 |
static | 静态修饰符,该变量属于类,被类的所有对象共享,即所有对象都可以使用该变量 |
transient | 临时修饰符,该变量在对象序列化时不被保存 |
volatile | 易失修饰符,该变量可以同时被几个线程控制和修改 |
成员方法修饰符 | 说明 |
---|---|
public | 公共访问修饰符,该方法为公共的,可以被任何类的方法访问 |
private | 私有访问修饰符,该方法只允许本类的方法访问,其他任何类(包括子类)中的方法均不能访问 |
protected | 保护访问修饰符,该方法可以被本类、子类及同一个包中的类访问 |
缺省 | 缺省访问修饰符,该方法在同一个包中的类可以访问,其他包中的类不能访问 |
final | 最终修饰符,该方法不能被重载 |
static | 静态修饰符,该方法属于这个类,不需要实例化就可以使用 |
abstract | 抽象修饰符,该方法只有方法原型,没有方法实现,需要在子类中实现 |
synchronzied | 同步修饰符,在多线程程序中,改修饰符用于在运行前,对它所属的方法加锁,以防止其它线程的访问,运行结束后解锁 |
native | 本地修饰符,该方法的方法体是用其它语言在程序外部编写的 |
数据类型 | 初始值 | 数据类型 | 初始值 |
---|---|---|---|
byte | 0 | float | 0.0f |
short | 0 | double | 0.0 |
int | 0 | char | ‘\0’ |
long | 0L | boolean | false |
引用类型 | null |
关键字this用来指代当前对象本身。this是系统资源,只允许用户读而不允许写,它存储着当前对象的地址
在构造方法中调用其他的构造方法时,只能使用this引用且必须位于构造方法的第一行
构造方法的形式参数与类的成员变量名字相同时,需要使用this引用来区分两者
在链式调用中返回对象本身,链式调用是指在一个语句中连续调用多个方法,如obj.fa().fb(),这时要求fa()方法的返回值必须是一个对象,fb()方法是该返回值对象的成员方法
return this;
构造方法(constructor)是在对象被创建时用于初始化成员变量的方法。构造方法的方法名必须与类名相同,且没有返回值,也不能使用void修饰符。构造方法允许有参数,参数一般与成员变量相对应
通常情况下需要自定义不带参数的构造方法,以防止错误发生
在构造方法中要调用其他构造方法时,就要采用显示调用方法。此时必须通过this引用来完成,并且必须是构造方法中的第一条语句
class Person{
private String name;
private int age;
private char sex = '男';
Person(){
}
Person(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person(String name) {
this(name, 18, '男');
}
}
【静态成员变量】
使用static来修饰成员变量,称为静态成员变量(简称静态变量)或类变量。静态变量在内存空间中只有一份,是一个公共的存储空间,类的所有对象都可以访问它
。静态变量可以通过类名访问,也可以通过对象名访问,形式为
(1)类名.静态变量
(2)对象名.静态变量
【静态成员方法】
用static修饰的成员方法是类的静态成员方法(静态方法),也称为类方法。在静态方法中可以访问方法内部定义的局部变量、类的静态变量和静态方法,不能访问类的非静态变量。静态方法的调用方式也有两种
(1)类.静态方法()
(2)对象.静态方法()
静态方法不依赖于类的对象而存在,即使类的对象不存在,它仍然可以被调用。假设静态方法能够访问非静态变量,那么当没有创建任何对象时,通过类调用静态方法,程序无法获得非静态变量,必然出错
。所以静态方法中不能访问非静态变量,而在静态方法中不能使用this
【匿名对象】
【包】package,通常将功能相关的类放在同一个组内,这个组就称为包
【方法重载】方法重载是指定义多个同名的方法,但要求这些方法的参数列表不能相同,即参数个数不同,参数类型不同或者参数的顺序不同。Java语言中不允许参数个数或者参数类型完全相同,而只有返回值数据类型不同的方法重载
【对象间的赋值】
Java语言中对象间的赋值不会重新创建一个对象,只是让两个对象名指向同一块内存区域。如果要完成对象间整体的拷贝,需要用到clone()方法
【对象数组初始化】
Person[] pers = {
new Person("Tom", 18, '男'), new Person("Lily", 18, '女')};
继承是面向对象另一个显著的特性。继承是从已有的类中派生出新类,被继承的类称为父类或超类(superclass),由继承而得到的类称为子类(subclass)。子类能继承父类的数据属性和行为,并能扩展新的能力。
与C++语言不同,Java语言不支持多重继承机制,一个类只允许有一个直接父类。父类是所有子类的公共成员的抽象,而每个子类则是父类的特殊化,是对公共成员变量和方法在功能、内涵方面的扩展和延伸。子类不能选择性地继承父类,但是子类继承父类的成员变量和成员方法之后,可以对其进行修改或重写,也可以添加父类所没有的成员变量和方法
在创建子类对象时除了执行子类的构造方法外,还需要调用父类的构造方法,遵循原则如下:
/*
子类名(参数列表){
super(参数列表);
语句块
}
*/
【子类访问父类成员】
在子类中,使用super不但可以访问父类的构造方法,还可以访问父类的成员变量和成员方法。子类访问父类成员的格式为
super.成员变量;
super.成员方法();
需要注意的是,使用super()不能访问父类的private成员,但是允许访问protected成员。如果某个类需要作为父类,那么它的成员变量一般也最好声明为protected
Java语言的多态性分为静态多态性和动态多态性。静态多态性是指在程序编译时系统就能根据方法的调用语句确定执行哪个方法体,Java语言采用重载实现静态多态。动态多态是指系统无法在程序编译时确定,必须等到程序运行时才能确定执行哪个方法体
Java语言中,动态多态性的实现需要3个条件:
【方法覆盖】在子类中调用父类被覆盖的成员方法时,只能使用super关键字
【final关键字】
map和list对应是栈中存储的地址,final表示地址不能修改,但是地址对应的内存区域的值是可以修改的
用final修饰的方法可以重写但不能重载
【上溯造型】
类之间的继承关系使子类具有父类所有成员变量和成员方法,这就意味着父类的成员可以在子类中使用,所有子类对象也是父类对象,即子类对象既可以作为本类的对象也可以看做父类的对象。这样,所有从一个父类派生的各子类都可以作为父类的类型。将一种子类对象的引用转换为父类对象的引用,转换方向是从下至上,因此称为上溯造型
抽象类不能创建对象
abstract class 类名{
声明成员变量;
返回类型 方法名(参数表){
······
}
abstract 返回类型 方法名(参数表);
}
接口与类都是引用类型,接口的定义格式为
[public] interface 接口名称 [extends 父接口名列表]{
[public][static][final] 数据类型 成员变量名 = 常量;
······
[public][abstrsct] 返回类型 方法名(参数列表)
······
}
其中方括号是默认选项,可以省略。也就是说,接口中的成员变量都是静态常量,成员方法都是公用抽象方法
实现接口的格式为
class 类名称 implements 接口名表{
······
}
一个类实现接口时应该注意以下问题:
【接口的继承】
接口与类相似,也可以有继承关系以完成更复杂的功能。接口也通过关键字extends完成继承,但与类继承不同的是,子接口允许继承多个父接口。子接口继承父接口之后,自动拥有父接口中的常量和方法。如果子接口中定义了与父接口相同的常量或者方法,则父接口中的常量被隐藏,方法被覆盖
【接口的应用】
在面向对象的程序设计中,一个类可以有多个父类,该子类可以继承所有父类的成员,称为多重继承。Java语言不支持类的多重继承,但可以利用接口间接地解决多重继承的问题
一个类只能继承一个父类,但是它可以同时实现多个接口。一个类实现多个接口时,在implements子句中用逗号分隔开各个接口名,定义格式为
[public] class 类名 extends 父类名 implements 接口名1,接口名2,···,接口名n{
······
}
接口与抽象类的作用相同,但是两者有着本质区别:
抽象类可以拥有普通成员方法的实现,而接口中只能包含抽象方法
public static final
,且只能是public static final,同时接口中的成员变量必须显式初始化在一个类的内部再定义一个类,这个类称为内部类,包含内部类的类称为外部类。定义内部类的目的往往是供外部类使用,并不对外公开。Java语言中内部类分为内部成员类、静态嵌套类、方法内部类和匿名内部类。
【成员内部类】
内部类不能与外部类同名。但内部类的成员变量和成员方法可以与外部类相同。
内部类的成员方法可以直接访问外部类的成员变量和成员方法,包括访问权限为private的成员,使用起来非常方便,这是内部类的主要优点
。但外部类要访问内部类的成员变量和成员方法时,则需要创建内部类的对象,然后通过该对象来访问内部类的成员
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
Outer.Inner in = new Outer().new Inner();
【匿名内部类】
匿名内部类是一种没有名字的内部类,只能使用一次。使用匿名类必须继承一个父类或实现一个接口,其格式为
new 父类构造方法{
匿名类类体
}
或
new 接口名(){
匿名类类体,实现接口中的全部方法
}
【包装类】
Java语言是一个面向对象的语言,但是Java中的基本数据类型却不是对象,这在实际用时存在很多的不便。为了解决这个问题,Java语言为每个基本数据类型设计了一个对应的类,称为包装类(Wrapper Class),也可以翻译为外覆类或数据类型类
基本数据类型与包装类对应表
基本数据类型 | 包装类 | 基本数据类型 | 包装类 |
---|---|---|---|
byte | Byte | boolean | Boolean |
short | Short | char | Character |
int | Integer | float | Float |
long | Long | double | Double |
包装类的主要用途体现在以下两方面:
包装类中包含每种基本数据类型的相关属性,如最大值、最小值等,以及相应的操作方法
基本数据类型与包装类的不同点体现在以下几个方面
【错误与异常】
【运行时异常与检查型异常】
【异常类】
【Exception类常用的构造方法和成员方法】
方法原型 | 说明 |
---|---|
public Exception() | 构造方法,创建一个异常对象 |
public Exception(String message) | 构造方法,创建一个带有指定信息的异常对象 |
public String toString() | 返回当前异常对象的信息 |
public String getMessage() | 返回当前异常对象的信息 |
public void printStackTrace() | 打印当前异常对象使用栈的轨迹 |
Exception类的子类分为两种:
【异常处理方式】
异常处理可以看做是一种控制结构。当异常发生时,将停止程序的正常执行顺序,转向异常处理代码。当异常发生时,称为抛出异常(throws)。当执行与异常匹配的异常处理代码时,称为捕获异常(try-catch-finally)
try{
//语句序列
}catch (ExceptionType1 e) {
//语句序列
}catch (ExceptionType2 | ExceptionType3 e) {
//---
}finally {
//语句序列
}
使用try-catch-finally语句时,应该注意以下几点:
try块只能有一个,catch块可以有0到多个,finally块可以有、也可以没有。以下这几种组合都是合法的:
try-catch-finally,try-catch,try-finally
catch块尽量使用最低级别的异常子类来捕获异常,这样能够更详细地了解问题所在
将捕获子类异常的catch块放在前面,将捕获父类异常的catch块放在后面
可以使用一个catch块来捕获多种类型的异常,此时它的异常参数类型应该是更一般的异常类型,但这种方式使程序不能判断异常的具体类型,无法做有针对性的处理
如果try块中抛出的异常对象没有被某个catch块捕获,就会传给Java虚拟机,由系统来进行异常处理,终止程序的运行
使用throws声明异常,异常列表由异常类的类名组成,有多个异常类时,以逗号分隔。
当一个方法可能抛出异常时,可以采用多种方式来处理:
该方法自行捕获异常
class ExceptionHandle{
public int calculate(int a, int b) {
int result = 0;
try {
result = a / b; //0做除数,抛出异常
} catch (ArithmeticException e) {
System.out.println("发生算数异常:" + e.toString());
}
return result;
}
}
public class Main {
public static void main(String[] args){
ExceptionHandle eh = new ExceptionHandle();
int result = eh.calculate(6, 0);
System.out.println(result);
}
}
该方法只是声明异常,由其调用方法来捕获异常
class ExceptionHandle{
public int calculate(int a, int b) throws ArithmeticException {
int result = 0;
result = a / b; //0做除数,抛出异常
return result;
}
}
public class Main {
public static void main(String[] args){
ExceptionHandle eh = new ExceptionHandle();
try {
int result = eh.calculate(6, 0);
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("发生算数异常:" + e.toString());
}
}
}
该方法及其调用方法都声明异常,最后由Java虚拟机捕获异常
class ExceptionHandle{
public int calculate(int a, int b) throws ArithmeticException {
int result = 0;
result = a / b; //0做除数,抛出异常
return result;
}
}
public class Main {
public static void main (String[] args) throws ArithmeticException {
ExceptionHandle eh = new ExceptionHandle();
int result = eh.calculate(6, 0);
System.out.println(result);
}
}
异常处理过程包括异常的抛出和异常的捕获。程序抛出异常时,会生成一个代表该异常的对象,并把它提交给Java虚拟机。抛出异常后,Java虚拟机从生成异常对象代码开始,沿方法的调用栈逐层回溯来查找与该异常匹配的异常处理代码。如果找到了就把异常对象传给该方法,执行相应的异常处理代码,这就是异常的捕获。如果没有找到相应的异常处理代码,最后Java虚拟机将捕获它,输出相应的错误信息,终止程序的运行。
若方法的调用顺序是方法A->方法B->方法C->方法D,查找异常处理代码(catch块)的顺序是方法D->方法C->方法B->方法A。
需要注意的是,一个方法被覆盖时,覆盖它的方法只能抛出相同的异常或该异常的子类,即不能抛出新的异常
人为抛出异常的格式为 throw 异常对象
public class Main {
public static void main (String[] args) {
int age;
Scanner sc = new Scanner(System.in);
try {
age = sc.nextInt();
if(age < 0 || age > 150) {
throw new Exception(); //人为抛出异常对象
}
System.out.println("年龄是" + age + "岁");
} catch (InputMismatchException e) {
System.out.println("输出的数据类型不匹配");
} catch (Exception e) {
//可捕获人为抛出的异常对象
System.out.println("输入数据应该在0-150之间");
}
System.out.println("程序结束");
}
}
自定义异常类的一般形式为
class MyException extends Exception{
//······
}
class OutOfRangeException extends Exception {
OutOfRangeException() {
super("数值不在正常范围内"); //调用父类Exception的构造方法
}
}
//... catch (OutOfRangeException e) ....
Java输入/输出流
『Java多线程』基础之基础
由于内容过多,且整理发表时间不一样,另外两章见发表连接(2020/1/8编辑)
参考资料:《Java语言程序设计实用教程》主编 王素琴
创作整理不易,如果对您有帮助的话,还望多+点赞、收藏☚
欢迎在评论区留下您宝贵的建议