1.Java程序设计概述
- 1)简单性
Java没有头文件、指针运算、结构、联合、操作符重载、虚基类等。 - 2)面向对象
Java没有多重继承,取而代之的是更简单的接口概念。Java提供了更丰富的运行时自省功能。 - 3)分布式
Java有一个丰富的例程库,用于处理HTTP和FTP之类的TCP/IP协议。 - 4)健壮性
Java采用的指针模型可以消除重写内存和损坏数据的可能性。 - 5)安全性
Java适用于网络/分布式环境。Java防范各种攻击:
运行时堆栈溢出;
破坏自己的进程空间外的内存;
未经授权读写文件。 - 6)体系结构中立
Java编译器生成与特定的计算机体系结构无关的字节码指令,可以在任何机器上解释执行,也可以动态地翻译成本地机器代码。 - 7)可移植性
Java规范中没有“依赖具体实现”的地方。基本数据类型的大小以及有关运算都做了明确的说明。
除了与用户界面有关的部件外,所有其他Java库都能很好地支持平台独立性。包括文件、正则表达式、XML、日期和时间、数据库、网络连接、线程等,而不用关心底层操作系统。 - 8)解释型
- 9)高性能
字节码可以在运行时刻动态地翻译成对应运行这个应用的特定CPU的机器码。
即时编译器的优势:可以监控经常执行哪些代码并优化这些代码以提高速度。更为复杂的优化是函数调用(内联)。必要时,还可以撤销优化。 - 10)多线程
- 11)动态性
当需要将某些代码添加到正在运行的程序中时,动态性将是一个非常重要的特性。
- 用户从Internet下载Java字节码,并在自己的机器上运行。在网页运行的Java程序称为applet。
2.Java程序设计环境
-
java术语
JRE——包含虚拟机,但不包含编译器。
- Windows和Linux上安装JDK时,需要将jdk/bin目录加入到执行路径中——执行路径是操作系统查找可执行文件时所遍历的目录列表
- C:\Program Files\Java\jdk-10\lib\src.zip文件中包含了所有公共类库的源代码。新建一个目录javasrc,将其解压到这个新目录。
更多的源代码,例如编译器、虚拟机、本地方法以及私有辅助类,访问http://jdk.java.net/10/。 - javadoc的安装
javadoc的下载路径 - 使用Windows命名行工具
javac是一个java编译器,将文件Welcome.java编译成Welcome.class。java启动Java虚拟机,虚拟机执行编译器放在class文件中的字节码。
有关问题可以查看java在线文档。
E:
cd E:\IdeaProjects\corejava\v1ch02\Welcome
dir //查看当前目录有哪些文件
javac Welcome.java //编译器编译成字节码
java Welcome //虚拟机执行字节码
cd .. //返回到上一级目录
cd ImageViewer // 可以使用Tab键补全
javac ImageViewer.java
java ImageViewer
- Java区分大小写
3.Java的基本程序设计结构
- 源代码的文件名必须与公共类的名字相同,并用.java作为扩展名。
- java ClassName
运行已编译的程序时,Java虚拟机将从指定类中的main方法开始执行。
public class ClassName
{
public static void main(String[] args)
{
program statements
}
}
- Java中所有函数都属于某个类的方法,因此,main方法必须有一个外壳类,并且main方法必须是静态的。main方法没有为操作系统返回“退出代码”。
- Java使用的通用语法:
object.method(parameters)
- 注释
// /* /
/* */ 这种注释可以用来自动生成文档。
-
数据类型
1)四种整型
Java没有任何无符号形式的int long short byte类型
2)两种浮点型
Double.POSITIVE_INFINITY正无穷大
Double.NEGATIVE_INFINITY负无穷大
Double.NaN(不是一个数字)
3)表示Unicode编码的字符单元的字符类型char
转义序列\u0000到\uffff
特殊字符的转义序列:
在Java中,char类型描述了UTF-16编码中的一个代码单元,强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。
4)表示真值的boolean
false和true,整型值和布尔值之间不能进行转换。这个跟C++不一样,因此会避免因为转换而带来的麻烦。
if (Double.isNaN(x)) //检查x是否不是一个数字
- final指示常量
static final设置一个类常量,可以在一个类中的多个方法中使用。
- Math类中的sqrt方法处理的不是对象,这样的方法被称为静态方法。
>>>运算符会用0填充高位,>>用符号位填充高位。不存在<<<运算符。
移位运算符的右操作数要完成模32的运算。1 << 35等价于 1 << 3.
- Java字符串是Unicode字符序列,Java没有内置的字符串类型,由标准类库中的String提供。
Java API查询
任何一个Java对象都可以转换成字符串。
String类对象称为不可变字符串。不可变字符串虽然修改(需要重新创建)慢,但是可以让字符串共享。
equals方法检测两个字符串是否相等;==只能确定两个字符串是否放置在同一位置上。
Java字符串由char值序列组成。大多数常用的Unicode字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。length方法返回采用UTF-16编码表示的给定字符串所需的代码单元数量。要想得到实际的长度,即码点数量,可以使用codePointCount.
遍历字符串,查看每一个码点(不要使用char,这个是查看代码单元)如下,另一种方法是使用codePoints
//方法1
int cp = sentence.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) i += 2;
else i++;
//方法2
int[] codePoints = str.codePoints().toArray();
//将码点数组转换为一个字符串
String str = new String(codePoints, 0, codePoints.length);
- 构建字符串
采用字符串连接的方式效率比较低,每次连接字符串,都会构建一个新的String对象,既耗时,又浪费空间。
StringBuilder类可以避免这个问题。
StringBuilder builder = new StringBuilder();
builder.append(ch);
builder.append(str);
String completedString = builder.toString();
-
标准输入输出
Scanner类定义在java.util包中。
当使用的类不是定义在基本java.lang包中,一定要使用import指示将相应的包加载进来。
关于时间的格式化选项,在新代码中,应该使用java.time包方法。
//输入
import java.util.*;
Scanner in = new Scanner(System.in);
String name = in.nextLine();
//格式化输出
System.out.printf("Hello, %s. Next year, you'll be %d", name, age);
- 文件输入输出
//读取
Scanner int = new Scanner(Paths.get("C:\\mydirectory\\myfile.txt"), "UTF-8");
//写入
PrintWriter out = new PrinterWriter("myfile.txt", "UTF-8"); //文件位于java虚拟机启动路径的相对位置
- 不能在嵌套的两个块中声明同名的变量。与C++不同。
在循环中,检测两个浮点数是否相等需要格外小心,由于舍入误差,最终可能得不到精确值,因此永远不会结束。
Java提供一种带标签的break语句,类似于C++ goto。
- 大数值
如果基本的整数和浮点数精度不能够满足需求,可以使用java.math包中的两个很有用的类:BigInteger和BigDecimal。可以处理包含任意长度数字序列的数值。BigInteger实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算。
Java没有提供运算符重载的功能。
- 数组
数组长度不要求是常量;
越界访问数组后面的元素会引发异常而终止执行;
一旦创建了数组,就不能再改变大小。
int[] a = new int[100];
// for each循环
for (variable: collection) statement
//更贱大的打印数组中所有值的方法
System.out.println(Arrays.toString(a));
- Java数组与C++分配在堆上的数组指针一样,[]运算符被预定义为检查数组边界,没有指针运算。
- 在Java应用程序的main方法中,程序名并没有存储在args数组中
java Message -h world
args[0] 是"-h"。
- 对数组的一些操作可以使用java.util.Arrays
- Java的多维数组被解释为“数组的数组”,Java只有一维数组。可以创建一个不规则数组,每一个子数组元素不一样多。
4.对象与类
- 在Java中,所有类都源自Object
- 对象的三个主要特性
1)行为
2)状态
3)标识 - OOP程序设计:首先从设计类开始,然后再往每个类中添加方法。
识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。 -
类之间的关系
1)依赖("uses-a")
如果一个类的方法操纵另一个类的对象,就说一个类依赖于另一个类。
应该尽可能将相互依赖的类减至最少。
2)聚合("has-a")
3)继承("is-a")
- Math类只封装了功能,它不需要也不必隐藏数据。
- 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
可以将Java对象变量看作C++对象指针。
所有Java对象都存储在堆中。
Date deadline;//没有指向任何对象,不能使用
deadline = new Date(); //可以使用
- 要想创建一个完整的程序,应该将若干类组合在一起,其中只有一个类有main方法。
- 可以认为Java编译器内置了make功能
- 构造器总是伴随着new操作符的执行被调用。
- 在Java中,所有的方法都必须在类的内部定义。是否将某个方法设置为内联方法是Java虚拟机的任务。
- 封装的优点
1)可以改变内部实现,除了该类的方法之外,不会影响其他代码
2)更改器可以执行错误检查,然而直接对域进行赋值将不会进行这些处理 - 注意不要编写返回引用可变对象的访问器方法。如果需要返回一个可变对象的引用,应该首先对它进行克隆。
- final修饰可变的类,有点类似于顶层const,表示final变量中的对象引用不能再指向其他的对象,但是这个对象中的内容可以更改。
final修饰大都用于基本类型域或不可变类的域。(String类就是不可变的类。)表示在后面的操作中,不能够再对它进行修改。
- 可以认为静态方法是没有this参数的方法。静态方法不能操作对象,不能访问实例域。但是可以访问自身类中的静态域。
在下面两种情况下使用静态方法:
1)一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(如Math.pow)
2)一个方法只需要访问类的静态域 - main是静态的。在启动程序时还没有任何一个对象,静态的main方法将执行并创建程序所需要的对象。
每一个类可以有一个main方法。这是一个常用于对类进行单元测试的技巧。
独立测试类:java classname
测试整个程序(类属于这个程序):java Application
- Java总是采用按值调用。
- 方法参数有两种类型
1)基本数据类型(数字、布尔值)
2)对象引用
如下swap函数并不能实现对象的交换,在C++中要实现该交换,不能只交换指针,而是要交换指针所指向的内容。C++也有引用交换,实际上交换的也是内容。
//该函数并不能实现对象的交换
public static void swap(Employee x, Employee y) { //x y都是对象引用
Employee temp = x;
x = y;
y = temp;
}
- 方法的签名signature——方法名+参数类型。返回类型不是方法签名的一部分。Java基于签名进行重载。Java重载和C++重载一样,都是基于签名进行重载。
- 仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器。
- C++ 11规定,可以为数据成员提供一个类内初始值。没有初始值的成员将被默认初始化。这个跟Java的显式域初始化是一致的。
- Java通常使用a+实例域作为参数;或者直接this.name = name使用this的形式。
C++使用m或x。
- 三种初始化数据域的方法
1)在构造器中设置值
2)在声明中赋值
3)初始化块
首先运行初始化块,然后运行构造器的主体部分。
- 由于有自动垃圾回收器,所以Java不支持析构器。
但是,当对象使用了内存之外的其他资源,比如文件,或使用了系统资源的另一个对象句柄。这时将其回收和再利用将显得十分重要。
可以添加一个finalize方法,该方法将在垃圾回收器清除对象之前调用。
- 所有标准的Java包都处于java和javax包层次中。
使用包的主要原因是确保类名的唯一性。 - 有两种方式访问另一个包中的公有类
1)在每个类名之前添加完整的包名
2)使用import语句(类似于C++的namespace和using)
import语句是一种引用包含在包中的类的简明描述。一旦使用了import语句,就不必写出包的全名。
如果两个包中有相同名字的类,再增加一个特定的import语句来表明使用哪个类。如果两个Date类都需要,则使用完整的包名即可。
java.time.LocalDate today = java.time.LocalDate.now();
import java.util.*;
import java.sql.*;
import java.util.Date; //sql中也有Date类
- import还可以导入静态方法和静态域的功能
import static java.lang.System.*; //导入类System的静态方法和静态域
- 将类放入包中的方法
在源文件的开头,定义类代码之前,加入包的名字。
如果没有package语句,会放在一个默认包中。
将包中的文件放到与完整的包名匹配的子目录中。com.horstmann.corejava包中所有源文件放在com/horstmann/corejava。
注意:编译器在编译源文件的时候不检查目录结构。但是如果包与目录不匹配,虚拟机就找不到类。
package com.horstmann.corejava;
public class Employee;
{
...
}
- 没有指定public和private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。(这个默认的范围有点大)
- 类加载器明确禁止加载用户自定义的、包名以“java.”开始的类。
并且可以通过包密封机制来解决将各种包混在在一起的问题。如果将一个包密封起来,就不能再向这个包添加类了。
-
类路径
为了使类能够被多个程序共享,需要做到以下几点:
如下,类路径包括:
1)基目录/home/user/classdir或c:\classes;
2)当前目录(.);
3)JAR文件/home/user/archives/archive.jar后c:\archives\archive.jar
运行时库文件(rt.jar和在jre/lib与jre/lib/ext目录下的一些其他的JAR文件)会被自动搜索,不必将它们显式地列在类路径中。
javac编译器总是在当前的目录中查找文件,但Java虚拟机仅在类路径中有"."目录的时候才查看当前目录。
//UNIX
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar
//Windows
java -classpath c:\classdir;.;c:\archives\archive.jar
虚拟机查询的过程:
假定要查找com.horstmann.corejava.Employee类
1)jre/lib和jre/lib/ext目录下
2)/home/user/classdir/com/horstmann/corejava/Employee.class
3)com/horstmann/corejava/Employee.class从当前目录开始
4)com/horstmann/corejava/Employee.class inside /home/user/archives/archive.jar编译器查询的过程
假定源文件包含
import java.util.;
import com.hortstmann.corejava.;
并且源代码引用了Employee类。
1)查找java.lang.Employee(默认导入的lang包)
2)java.util.Employee
3)com.horstmann.corejava.Employee
4)当前包中的Employee
5)编译器还要查看源文件是否比类文件新。如果是,则源文件就会被自动得重新编译。
- javadoc可以由源文件生成一个HTML文档。
- 类设计技巧
1)一定要保证数据私有
2)一定要对数据初始化
3)不要在类中使用过多的基本类型
4)不是所有的域都需要独立的域访问器和域更改器
5)将职责过多的类进行分解
6)类名和方法名要能够体现它们的职责
7)优先使用不可变的类
5.继承
- Java中所有的继承都是公有继承
- 子类覆盖超类方法的注意点
1)不能直接访问超类的私有域
2)调用超类的访问器时,加上super.
public class Manager extends Employee
{
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
}
- 子类构造器
使用super调用超类构造器必须是子类构造器的第一条语句。如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。 - 在Java中,不需要将方法声明为虚拟方法,动态绑定是默认的处理方式。如果不希望一个方法具有虚拟特征,可以将其标记为final。
- 由一个公共超类派生出来的所有类的集合称为继承层次。
- "is-a"规则,表明子类的每个对象也是超类的对象。
另一种表述法是置换法则。表明程序中出现超类对象的任何地方都可以用子类置换。
Java中,对象变量是多态的。一个Employee变量既可以引用一个Employee类对象,也可以引用一个Employee类的任何一个子类的对象。 - 同C++一样,反过来赋值不可以,且超类引用子类对象时,不能调用子类特有的方法,这是由声明类型决定的。
- 一个超类数组和一个子类数组同时引用同一个子类对象,此时对超类存储超类引用,应该引发错误
Manager[] managers = new Manager[10];
Employeep[ staff = managers;
staff[0] = new Employee(...); //引发ArrayStoreException异常
- 理解方法调用
1)获取所有候选方法
包括重载的超类中同名的属性为public的方法
2)进行参数类型匹配
3)如果是private、static、final方法或构造器,编译器静态绑定。如果依赖于隐式参数的实际类型,则在运行时实现动态绑定 - 每次都搜索的开销太大。虚拟机为每个类创建了一个方法表,列出了所有方法的签名和实际调用的方法。
此时的解析过程变为:
1)虚拟机提取对象的实际类型的方法表
2)虚拟机搜索定义指定方法签名的类
3)虚拟机调用方法 - 在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。
- 阻止继承:final类和方法
类中特定的方法也可以被声明为final,子类不能覆盖这个方法。
一个类声明为final,其中的方法自动成为final,不包括域。
虚拟机中的即时编译器如果发现:方法很简短、别频繁调用且没有真正被覆盖,就对其进行内联处理。 - 强制类型转换,类似于C++ dynamic_cast
1)只能在继承层次内进行类型转换
2)在将超类转换为子类之前,应该使用instanceof进行检查
一般情况下,应该尽量少用类型转换和instanceof运算符。 - 抽象类——C++的抽象类(包含纯虚函数virtual xxx() = 0;)
关键字abstract
包含一个或多个抽象方法的类必须被声明为抽象的。
抽象方法不需要实现。
类即使不含抽象类,也可以声明为抽象类。抽象类不能被实例化。
可以定义一个抽象类的对象变量(引用),它只能引用非抽象子类的对象。 - 受保护访问
总结:
1)仅对本类可见——private
2)对所有类可见——public
3)对本包和所有子类可见——protected
4)对本包可见——默认,不需要修饰符
- Object:所有类的超类
在Java中,只有基本类型不是对象(比如数值、字符和布尔类型)
所有数组类型都扩展了Object类。 -
equals方法
判断两个对象是否具有相同的引用。
编写一个equals的建议:
- hashCode方法
散列码是由对象导出的一个整型值。hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。
Equals与hasnCode的定义必须一直:如果x.equals(y)返回true,那么x.HashCode()必须与y.hashCode()具有相同的值。 - toString方法
返回表示对象值的字符串。
如果x是任意一个对象,并调用System.out.println(x);会直接调用x.toString()。
数组类型需要调用静态方法:Arrays.toString,多维数组调用Arrays.deepToString
建议:为自定义的每一个类增加toString方法,可以让所有使用这个类的程序员从这个日志记录支持中受益。
- 泛型数组列表(Java没有运算符重载)
ArrayList是一个采用类型参数的泛型类,实现动态数组。(跟C++ vector是一样的)
使用add方法为数组添加新元素,而不要使用set方法。set方法只能替换数组中已经存在的元素的内容。
- 对象包装器与自动装箱
所有的类型都有一个与之对应的类。这些类成为包装器(wrapper)。
Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean。
对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类。
自动装箱:可以将int类型自动装箱,自动变成Integer。
ArrayList<Integer> list = new ArrayList<>();
//自动装箱
list.add(3);
//自动拆箱
int n = list.get(i);
//将基本方法放置在包装器中
int x = Integer.parseInt(s); //静态方法,字符串转整型
- Integer对象是不可变的:包含在包装器中的内容不会改变。
修改数值参数值的方法,需要使用在org.omg.CORBA中定义的holder类型。
public static void triple(IntHolder x)
{
x.value = 3 * x.value;
}
- Java的可变参数与数组是一样的
- 枚举声明定义的类型是一个类,并且刚好有定义的数量个实例。
比较两个枚举类的值时,不需要equals,直接使用==即可。
所有枚举类都是Enum类的子类。重要的方法有toString和valueOf。
- 反射库被大量用于JavaBeans,是Java组件的体系结构。
能够分析类能力的程序称为反射(reflective)。反射机制用于:
1)在运行时分析类的能力
2)在运行时查看对象
3)实现通用的数组操作diamante
4)利用Method对象,这个对象很像C++的函数指针 - Class类
运行时,Java运行时系统为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
保存这些信息的类被称为Class。Object的getClass()方法返回一个Class类型的实例。