**前言:**今年选修了Java,我的老师工程能力应该是还不错的,但不太会讲课,也不怎么讲课,只能自学了呜哇哇。
注,本章仅分享与C/C++区别的知识点,以及值得强调的知识点(不过多赘述)。
Java程序可以分为两种类型:Java应用程序(Java application)和Java小应用程序(Java applet )。本章仅对 application程序进行学习,applet 程序主要应用在网页编程上。
Java语言概述
发展历史
用C++来写内嵌式软件,常会出现使系统失效的程序错误。尤其是在内存管理方面,需要程序员记录并管理内存资源。这造成设计师们极大的负担,并可能产生许多潜在的程序错误问题。
Gosling决定发展一种新的语言,来解决C++的潜在安全问题,起初叫Oak。保留了大部分与C++相似的语法,但把较具危险性的部分加以改进,像内存资源管理,由语言本身来管理,以减少程序设计师的负担及错误的产生。
小趣事:因为Oak 这个商标已被注册,工程师们便想列以手中享用的咖啡(Java)来重新命名,后来公司被Oracle收购。我之前看英语文章提到Java,大为震惊,后来才发现是咖啡☕🤣。
和C/C++的比较
1.java程序不能定义全局变量,类中的公共、静态变量就相当于这个类的全局变量。全局变量封装在类中,保证了安全性。
2.指针是C/C++语言中最灵活,但也是最容易出错的数据类型。在 Java中,不支持指针操作,Java 中的数组是通过类和对象的机制来实现,提供了数组访问时的边界检测。
3.在C/C++中,申请的内存需程序员手动释放。Java中,提供运算符new用于内存资源的分配,而这些资源释放可完全交与虚拟机的垃圾回收器完成,无需程序员操心,避免了内存管理不周而引起的系统崩溃。
4.在C/C++语言中,一些数据类型如 int、float 等的字长是根据运行平台的机器字长而定的,导致了代码数据移植困难。在java中,对于不同的平台,对数据类型的字长分配总是固定的,保证了Java 数据的平台无关性和可移植性。
5.在C/C++中,可以通过指针进行任意的类型转换,不安全因素大大增加。而在Java语言中系统会对对象的处理进行严格的相容性检查,防止不安全的转换。
6.在C/C++中,使用头文件声明类的原型和全局变量及库函数等,在大的系统中,维护这些头文件是非常困难的。Java不支持头文件,类成员的类型和访问权限都封装在一个类中,运行时系统对访问进行控制,防止非法的访问。Java还提供了包机制,用于类的管理,解决类命名重名的问题。
7.C/C++语言中有宏定义,而Java不支持宏定义。
Java平台的工作原理
计算机高级语言类型主要有编译型和解释型两种,Java 是两种类型的结合。
在Java中,源文件名称的后缀为.java,通过编译使.java的源文件生成一个后缀为.class的字节码文件,然后再由Java的专用程序解释(JVM)执行.class文件。
JVM全称为Java Virtual Machine,即Java 虚拟机,是一种利用软件方法来实现硬件功能的虚拟计算机。所有的.class文件都在Jvm上运行,即.class 文件只需要认识Jvm,由Jvm再去适应各个操作系统。如果不同的操作系统安装上符合其类型的Jvm,那么程序无论到哪个操作系统上都是可以正确执行的。Jvm是Java跨平台的保证。
Java语言基础
前言
1.用"//"表示注释行。
2.类是Java的基本封装单元,Java中所有的程序活动都必须发生在一个类中。
3.类中一个公共的、静态的和无返回值的main方法是所有Java应用程序执行的入口(但不是Java applet的入口)。方法是一个程序段,实现某个特定的功能。main方法必须同时含有public、static 和 void属性。在成员main方法中,args是参数变量,string[]是参数变量的数据类型,这个数据类型不可以被修改。
java
public static void main(String[] args){
}
数据类型
Java中提供了4种整数类型,其中常用的(在编程题中要考虑是否超范围)
| 类型 | 存储要求 | 表示范围 |
|---|---|---|
| int | 4字节 | -2147483648~2147483647,即+-21亿 |
| long | 8字节 | -9223372036854775808~9223372036854775807 |
Java语言中字符类型采用unicode码,字长是两字节。C或C++语言中字符类型采用ascii码,字长是1字节。
布尔类型的关键词为boolean,Java中的布尔类型不能与其它类型相互转换。
常变量
常变量表示变量的值初始化后不再修改。在声明时,类型名前添加关键字final,常变量名一般采用大写字母。
java
final double PI = 3.14;
此外,Java中常量无需在声明时立即初始化,如下;c++语言中用关键字const表示常量,并要在声明时立即初始化
java
final double PI;
PI = 3.14;
字符串相加
当"+"操作出现在字符串中时,为字符串连接符,会将前后的数据进行拼接(若另一个操作数不是字符串,会将其转为字符串),并产生一个新的字符串

连续"+"操作,是从左到右逐个执行(先1+2,在拼接)

例:s+true="123true";1+2+s+2+1="312321"
字符相加
byte、short、char三类数据在进行运算时都会先提升为int类型(ASCII值)再进行计算

'a'+"abc"="aabc",有字符串则为拼接操作
逻辑运算符
四种逻辑运算符与C++相同:与&,或|,非!,异或^
短路运算符(短路与&&,短路或||),效率高,当左边的表达式能确定最终的结果,右边就不会运行了
如下面短路与逻辑,在左边判断为false后,右边代码则不再执行,b仍然为10

类型转换
如果两个操作数的类型不同,在运算前会对操作数进行隐式转换(精度小的可以转换为精度大的,如int->double)。如果转换过程中有精度损失,必须通过强制转换来完成。
java
double x = 3.14;
int val = (int)x; // 得到val为3
Java中将整数类型转为字符类型(大->小)需要强制类型转换,而C/C++不用。
例题:将A~G这7个大写字母转换为对应的小写字母
java
public class Tolower {
public static void main(String[] args) {
for(char c='A';c<='G';c++){
int num='a'-'A';
System.out.print(c);
System.out.print((char)(c + num)+"\t");
}
}
}
输出如下

嵌套循环
C++中break和continue只能作用于当前所在的嵌套循环,而Java还提供了带标签的break(跳出标签对应的外层循环)和continue(回到标签对应的外层循环)。
java
import java.util.Scanner;
public class PrimeNumber {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("枚举不大于N的素数,请输入N:");
int n = sc.nextInt();
Outer:
for(int i = 2; i <= n; i++){
for(int j = 2; j < i; j++){
if(i % j == 0) // i不是素数
continue Outer; // 结束当前循环,跳到Outer所标记的外层for循环(执行下一层外循环)
}
System.out.print(i + " ");2
}
}
}
Java内存机制
Java 内存机制中存在两种内存,一种是栈内存,另一种是堆内存。基本类型的变量和对象引用(即对象的名称)都是栈内存中分配,当变量所在的代码块运行结束,java会自动释放掉为该变量分配的内存空间。堆内存用来存放由 new 创建的对象,在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理(只是将此堆内存的使用权给了对应的栈内存空间,且一个堆内存空间可以同时被多个栈空间所指向)。

java
Person xiaoming2 = null;
xiaoming2 = xiaoming;
与C/C++的区别:对于简单数据类型,Java和C++一样采取直接存储的方式。但是对于对象则不同。如"Object o;",Java中是一个对象引用,它记录的是对象的内存地址,对象本身被存储在别处。在C++中, "Object o;"这句话将创建一个对象o,且开辟了存储这个对象所需的内存空间。
"Object o2 = o1;"在Java中,只是把o1对象的引用复制给了o2,它们指向同一个对象,了,即只存在一个对象。在C++中则是将一个名为o1的对象的数据复制给o2,即存在两个对象,它们的值相同。
局部变量
如果一个变量是在方法体或某个代码块中声明的,它被称为局部变量,局部变量先声明后使用。注意:Java中没有为局部变量提供默认值,否则将编译出错。
java
void method(){
int i;
System.out.println("i = ", + i); // 编译出错,变量i尚未初始化
}
类与对象
方法参数传递
Java只有一种参数传递方式,即按值传递。如果传入方法的是基本类型的数据,方法声明的形参得到的是基本类型数据的一份拷贝。如果当引用语句中的实参是对象或数组时,那么被引用的方法声明的形参得到的是对象引用的拷贝,即实参和形参指向同一对象。
此外:C++的方法参数表中可以对形参赋值,使形参具有默认值;而java不提供这个机制。
java
void method1 (int a, int i = 100){} // 报错,此处形参i不能被赋值
方法的重载
在java中,方法的名称和形式参数表等构成了方法的签名。系统根据方法的签名确定引用的是哪个方法,因此方法的签名必须唯一。(方法的返回值类型对方法的签名没有影响,即返回值类型不能用于区分方法,因为方法可以没有返回值。重载方法之间是以所带参数来区分的)。
调用本类的其它重载的构造方法
在构造和函数中通过this关键字调用本类中的其它构造函数
java
public class Demo{
private int i = 0;
Demo(int i){
this.i = i + 1;
System.out.println("constructor1 i = " + i + ", this.i = " + this.i); // 字符串拼接
Demo(String s){
System.out.println("constructor2 s = " + s);
}
Demo(int i, String s){
this(s); // this调用第二个构造方法
this.i = i++;
System.out.println("constructor3 i = " + i + "\n" + "constructor3 s = " +s);
}
public static void main(String[] args){
Demo t0 = new Demo(10); // 输出为:constructor1 i = 10, this.i = 11
Demo t1 = new Demo("ok"); // 输出为:constructor2 s = ok
Demo t3 = new Demo(20, "ok again!") // 输出为:constructor3 i = 21
// constructor3 s = ok again!
}
注意:通过this调用构造函数时,对this的调用必须是构造函数中的第一个语句,且只能调用一个且仅一次
java
Demo(int i, String s){
this(s);
this(i); // 报错
}
访问控制权限
类成员的访问控制符

类的访问控制符

Object类
类java.lang.Object是所有Java类的直接或间接父类。在Java中,若一个类没有显示的声明父类,则它就隐式继承Object类。
1)因此可以声明Object类型的对象引用指向任何类型的对象。
2)Object类中定义的一些方法,自动继承给所有类

注意,equals方法比较的是两对象的引用是否相等,作用等同于"=="。若需判断内容是否相等,需覆写这个方法,如String类通过覆写,equals()方法判断的是内容是否相等
包装类
Java中有8种基本的数据类型(byte、short、int、long、float、double、char、boolean),但它们都不是面向对象的。为了能够将基本的数据类型也视为对象来处理并支持相关方法,Java为每个基本数据类型提供了相关包装类:Byte、Short、Integer、Long、Float、Double、Character、Boolean、Void。这些类都定义在java.lang中,使用方法大都相似。
基本数据类型的数据与包装类的对象的转换
java
class IntegerDemo{
public static void main(String[] args){
int a = 29;
Integer obj = new Integer(a); // int转Integer,使用包装类的构造方法
System.out.println(obj.intValue()); // 取得包装类对象的数值,调用包装类typeValue()方法
}
}
包装类的对象或基本数据类的数据与字符串的转换
java
class StringToInteger{
public static void main(String[] args){
String str = Integer.toString(123); // int类型数据转为字符串(使用Integer的toString静态方法)
int val = Integer.parseInteger("234"); // 字符串转为int类型数据(使用包装类静态方法)
Integer obj = new Integer("345"); // 字符串转Integer对象(使用构造方法)
System.out.println("str = " + str);
System.out.println("val = " + val);
System.out.println("obj = " + obj.toString());
}
}
自动装箱、拆箱
JDK5.0版本以后,Java虚拟机支持自动装箱、拆箱机制。自动装箱指将基本数据类型的数据自动封装为相应的包装类对象。自动拆箱指从包装类对象中自动提取基本数据类型的数据。
java
class BoxingDemo{
public static void main(String[] args){
Integer val = new Integer(123);
val++; // 自动拆箱,做++运算,再自动装箱
System.out.println("val = " + val);
int a = val.intValue();
System.out.println("a = " + a);
}
}
包
为了便于管理大型软件系统中数目众多的类,解决类命名重名的问题,Java引入了包(package),对类文件进行分类管理。一个包对应一个文件夹,包中还可以有子包。经过包声明后,在同一文件内的接口或类都被纳入相同的包中。
包的声明
java
package 包名;
不同等级的包名用标识符加"."分割
java
package test.demoa; // 类文件在包文件夹test/demoa下
系统常用包

import语句
如果要使用的几个类分别属于不同的包,在某个类中要访问其它类的成员时,可通过import命令,将某个包的整个类导入
java
package test.demob; // 声明本类所属的包
import test.demoa.PackageDemo1; // 引入包文件夹test/demoa下的PackageDemo1类
若需引入一个包下中的全部类,可用"*"表示
java
import java.util.*;
继承与多态
继承
c++中支持多继承,而Java中只支持单继承。
在 Java中所有的类都是直接或间接的继承类java.lang.Object。当一个类没有显式地指定其父类时,就直接继承了这个类。
extends
使用关键词extends表明子类继承父类
java
class SubClass extends BaseClass{
//子类类体,定义子类的新成员
}
super
在隐式调用父类的构造方法时,调用的是默认构造方法。显式调用父类的构造方法,使用关键字super。
java
class Animal{
pirvate int age;
Animal (){
System.out.print1n ("Animal default constructor");
}
Animal (int a) {
age = a;
System.out.print1n("Animal constructor");
}
}
class Dog extends Animal{
String kind;
Dog (String k,int a){
super (a);
kind = k;
System.out.print1n("Dog constructor") ;
}
}
class DogDemo{
public static void main (String[] args) {
Dog d = new Dog ("Pekingese", 1);
}
}
系统执行到super(参数)语句时,会根据参数表判断调用父类中哪个构造方法。参数表可以为空,为空时调用父类的默认构造方法。使用super调用父类构造方法时必须放在子类构造方法中的第1句,否则会引起编译错误。super的参数不可以是关键词this或当前对象的对象成员,因为构造方法还没完成,当前对象还没有完成创建。
上述代码输出为:Animal constructor
Dog constructor
子类对象初始化过程
1.从父类到子类,直至当前类,依次初始化静态成员变量
2.从父类到子类,直至当前类,依次初始化对象的成员变量
3.执行父类的实例初始化块(类里直接用{ }包裹的代码块,不写static,就是实例初始化块)
4.调用父类的构造器
5.执行子类的实例初始化块
6.调用子类的构造器
java
class BaseClass{
BaseClass(){
System.out.print1n ( "BaseClass constructor") ;
}
{ //初始化块
System.out.print1n ("BaseClass initial scope") ;
}
class SubClass extends BaseClass{
SubClass (){
System. out.print1n("SubClass constructor") ;
}
{
System.out.print1n("SubClass initial scope") ;
}
public static void main (String[] args) {
SubClass s = newSubClass();
}
}
程序运行结果:

重载与覆写
若子类中的成员方法名与父类相同,参数表不同,则属于同名方法的重载,而不是覆写。覆写机制是指子类的方法的方法名、参数表、返回值与父类中被覆写的方法都相同,而方法体不同。
若子类中方法f()覆写了父类中的方法,则相对于父类中的f()方法。子类中方法 f()的访问控制不能缩小。如,父类中的f()方法是 protected 修饰的,则子类中的f()方法必须是protected或者public修饰。
final修饰的方法在继承过程中不能被覆写,final修饰的类不能被其他类继承,即它没有子类。
抽象方法与抽象类
抽象方法
只有方法名、参数表和返回值,没有方法体的方法。
在方法声明前使用关键词abstract修饰,不能被执行。
java
abstract void fly();
抽象类
如果某个类含有抽象方法,那么这个类必须定义为抽象类(在类定义前使用关键词abstract修饰),抽象类没有具体的对象。一个抽象类中可以没有抽象方法。
虽然抽象方法不能创建对象,但可以作为变量类型(多态)。
若一个子类未实现父类的抽象方法,则这个子类也是抽象类,必须用关键词abstract修饰。
静态方法、私有方法、final修饰的方法不能被覆写,所以这三类方法不能被定义为抽象方法。
多态
多态是面向对象语言的核心特性之一,指 "同一行为,不同实现"。
通过父类引用指向子类对象,调用方法时实际执行的是子类的重写实现。
java
abstract class Animal { // 父类(抽象类,含抽象方法)
public abstract void cry(); // 抽象方法(只定义,不实现)
}
class Cat extends Animal { // 子类(重写父类方法)
@Override // 声明覆写(可读性更好),可省略
public void cry() {
System.out.println("猫叫:喵喵~");
}
}
class Dog extends Animal {
@Override
public void cry() {
System.out.println("狗叫:汪汪~");
}
}
class Bird extends Animal {
@Override
public void cry() {
System.out.println("鸟叫:叽叽喳喳~");
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
Animal animal1 = new Cat(); // 父类引用指向子类对象
Animal animal2 = new Dog();
Animal animal3 = new Bird();
// 同一方法调用,表现出不同行为(多态的体现)
animal1.cry();
animal2.cry();
animal3.cry();
// 更简洁的方式:通过方法统一调用
makeAnimalCry(animal1);
makeAnimalCry(animal2);
makeAnimalCry(animal3);
}
// 定义一个通用方法:接收父类类型参数,实现代码复用
public static void makeAnimalCry(Animal animal) {
animal.cry(); // 调用时自动匹配子类的实现
}
}
运行结果

引用变量存储的是对象在堆内存中的地址,因此操作引用时,本质是通过地址找到对象并操作。
接口
接口又叫"纯抽象类",成员方法都是抽象方法,使用关键词interface
接口的成员方法默认为抽象的、公有的,成员属性默认为静态的、final修饰的。
java
interface Flyable{ // 定义接口Flyable
void fly();
}
接口只是提供一种形式,具体的实现细节交由实现它的子类完成,一个类实现接口使用关键词implements,与继承相似
java
class Bird implements Flyable{...}
和继承不同的是,一个类可以实现多个接口,接口之间使用","分隔
java
class MyClass implements A, B, C{...}
对于实现接口的某个类,必须实现接口中定义的所有方法。继承中的对象类型转换、多态等内容在这里仍然适用,示例代码如下
java
// FlyableDemo.java
interface Flyable{
void fly(); // 默认为公共的
}
abstract class Animal{ // 抽象类
int Age;
Animal(int a){
age=a;
}
int getAge(){
return age;
}
abstract void speak();
}
class Bird extends Animal implements Flyable{ // 继承Animal,实现接口Flyable
Bird(int a){
super(a);
}
public void fly(){ // 类中需声明public
System.out println("Bird is flying.");
}
void speak(){ // 覆写speak方法
System.out println("Bird is speaking.");
}
}
class Plane implements Flyable{
String type;
Plane(String t){
type=t;
}
public void fly(){
System.out println("Plane is flying.");
}
}
class FlyableDemo{
public static void main(String[] args){
Bird b=new Bird(2);
Plane p=new Plane("plane");
makeFly(b); // 参数向上转型(父类)
makeFly(p);
}
static void makeFly(Flyable f){
f.fly(); // 多态
}
}
程序运行结果为:Bird is flying.
Plane is flying.
Java中不支持多继承,但类可以实现多接口。若类需要包含一些方法的实现,定义为抽象类,其它两者皆可的情况,优先选择使用接口。
内部类
Java允许在一个类的内部定另一个类,称这个在类体内部的类为内部类,包含内部类的类叫外部类。内部类可以访问外部类的成员,外部类可以访问其内部类(包括其私有成员)。
示例如下
java
// InnerClassDemo.java
class OuterClass{
int field;
class InnerClassA{
private int val;
public InnerClassA(int v){
val=v;
System.out.println("InnnerClassA constructor.");
}
void fInnerClassA(){
fOuterClass(); // 内部类可以访问外部类的方法
field++; // 内部类也可以访问外部类的属性(等同于OuterClass.this.field++)
}
}
private class InnerClassB{
InnerClassB(){
System.out.println("InnnerClassB constructor.");
}
}
static class InnerClassC{}
void fOuterClass(){
System.out.println("In fOuterClass.");
}
void gOuterClass(){
System.out.println("In gOuterClass.");
InnerClassA inner = new InnerClassA(1); // 在外部类中创建内部类对象
inner.fInnerClassA(); // 通过内部类对象调用其方法
System.out.println("val = " + inner.val); // 外部类可访问内部类私有成员
}
}
class InnerClassDemo{
public static void main(String[] args){
OuterClass outer = new OuterClass();
outer.gOuterClass();
OuterClass.InnerClassA inner = outer.new InnerClassA(3); // 非静态内部类须依赖外部类的实例才能创建
OuterClass.InnerClassC c = new OuterClass.InnerClassC(); // 静态内部类可通过"外部类名.内部类名"的方式构建
}
}

匿名内部类
匿名内部类是没有类名的内部类,通常用于快速创建一个类的实例,用完即弃,无法重复实例化。
无需像普通类那样用class关键字定义类名,而是在创建时直接使用,没有构造方法(无类名)。不能独立存在,只能通过 "继承父类" 或 "实现接口" 的方式定义。
实现接口的匿名内部类
java
interface Greeting { // 定义接口
void sayHello();
}
public class AnonInnerDemo {
public static void main(String[] args) {
Greeting greeting = new Greeting() { // 匿名内部类:实现Greeting接口,同时创建实例
@Override
public void sayHello() { // 实现接口的抽象方法
System.out.println("Hello, 匿名内部类!");
}
};
greeting.sayHello();
}
}
继承父类的匿名内部类
java
class Animal { // 定义父类
void cry() {
System.out.println("动物叫...");
}
}
public class AnonInnerDemo2 {
public static void main(String[] args) {
Animal cat = new Animal() { // 匿名内部类:继承Animal类,同时创建实例
@Override
void cry() { // 重写父类方法
System.out.println("猫叫:喵喵~");
}
};
cat.cry(); // 调用方法:输出 "猫叫:喵喵~"
}
}
键盘录入
导包
在类定义的上方导入Scanner这个包(包含键盘操作)
java
import java.util.Scanner;
创建对象
创建Scanner对象
java
Scanner sc = new Scanner(System.in);
接收数据
接收键盘录入的整型数据赋值给变量i
java
int i = sc.nextInt();
我们可以输入数据,再将接收到的数据打印出来看一看

当然我们也可以用上一章的快捷键,直接创建Scanner对象再用"ctrl+shift+o"自动导包
关闭资源
最后还要用.close()方法关闭sc对象,节省资源,或使用with语句
java
sc.close(); // 关闭资源
练习
从键盘输入两个整数,求它们的和

输入与输出
java
char ch = sc.next(); // 接收单个单词
int num = sc.nextInt(); // 接收整数
float a = sc.nextFloat(); // 接收浮点数
String str = sc.nextLine(); // 接收整行字符串
System.out.print(str); // 输出后不换行
System.out.println(str); // 输出后自动换行
System.out.printf("姓名:%s,年龄:%d\n", name, age); // 格式化输出
常用数据结构
数组
一维数组
声明一个一维数组有以下两种形式:
java
int array[];
int[] arr;
创建数组空间
java
arr = new int[20];
声明和创建数组空间可以合并成一条语句(数组的动态初始化,创建时指定数组长度,由虚拟机给出默认的初始化值)
java
int[] a = new int[20];
数组的静态初始化
数组创建完成后长度不能再变化,如下方的数组长度为3
java
int[] array = new int[]{11,22,33}; //完整格式
int[] array = {11,22,33}; //简化格式
Java中将数组作为对象来实现,提供了一些方法与属性
每个数组都有一个属性length用于记录数组的长度,还提供数组下标越界检测(若数组下标越界,系统将抛出异常)
java
array.length
练习
输入一组整数,用冒泡排序算法使它们非递减有序并输出结果
java
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入待排序整数的个数:");
int n = scanner.nextInt();
int[] arr = new int[n];
System.out.println("请输入待排序的整数数组,用空格间隔:");
for(int i=0; i<n; i++){
arr[i] = scanner.nextInt();
}
for(int i=1; i<n; i++){
for(int j=0; j<n-i; j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for(int i=0; i<n; i++){
System.out.print(arr[i] + " ");
}
scanner.close();
}
}
数组中的元素也可以是复合数据类型的,此时,数组元素实际上是对象的引用
java
import java.util.Scanner;
class Complex{
Complex(double real, double imag){
this.real = real;
this.imag = imag;
}
void display(){
System.out.println(real + " + " + imag + "i");
}
private double real;
private double imag;
}
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入3个复数的实部和虚部:");
Complex[] arr = new Complex[3];
for(int i=0;i<3;i++){
arr[i] = new Complex(scanner.nextDouble(), scanner.nextDouble());
}
for(int i=0;i<3;i++){
arr[i].display();
}
scanner.close();
}
}
注意:
一个.java源文件中,最多只能有一个用public修饰的类,且这个公共类的类名必须与源文件的文件名完全一致。Complex类不用加public(默认是 "包访问权限",同一包内可访问),代码可正常编译。(苯人今天就犯了这个错误)
二维数组
二维数组的声明与创建
java
int[][] arr = new int[2][3];
int arr[][] = new int[2][3];
不规则数组
java
int[][] b = new int[2][];
b[0] = new int[3];
b[1] = new int[1];
练习
矩阵相乘
java
public class Test {
public static void main(String[] args) {
int[][] a = {{2,3,4},{5,6,7}}; // 二维数组的静态初始化
int[][] b = {{1,2},{3,4},{5,6}};
int i,j,k;
int[][] result = new int[a.length][b[0].length];
for(i=0;i<a.length;i++) {
for(j=0;j<b[0].length;j++) {
result[i][j] = 0;
for(k=0;k<a[0].length;k++) {
result[i][j] += a[i][k] * b[k][j];
}
}
}
for(i=0;i<result.length;i++) {
for(j=0;j<result[0].length;j++) {
System.out.print(result[i][j] + " ");
}
System.out.println();
}
}
}
foreach形式的for语句
Java中也支持for语句遍历数组或集合元素
java
public class Test {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
int[][] arr2 = {{1, 2, 3},{4, 5, 6}};
for(int a:arr1){
a += 10; // foreach形式的for语句无法修改元素值
}
for(int a:arr1){
System.out.print(a + " ");
}
System.err.println();
for(int[] subArr:arr2){
for(int b:subArr){
System.out.print(b + " ");
}
System.out.println();
}
}
}
Arrays类
Arrays类是数组的操作类,提供了用来操作数组的各种静态方法(各种方法都是static修饰的,可通过类名Arrays调用)

需注意,若equals()的参数是多维数组,则是将多维数组看作是一个元素为数组对象引用的一维数组,toString()同理,所以要操作其中内容时使用deepEquals()/deepToString()
java
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] arr1 = {5,5,4,2,7,6};
int[][] arr2 = {{1,2,3},{4,5},{6,7}};
int[][] arr3 = {{1,2,3},{4,5},{6,7}};
System.out.println("Arrays.toString(arr1): " + Arrays.toString(arr1));
System.out.println("Arrays.toString(arr2): " + Arrays.toString(arr2));
System.out.println("Arrays.deepToString(arr2)" + Arrays.deepToString(arr2));
System.out.println("Arrays.equals(arr2, arr3) " + Arrays.equals(arr2, arr3));
System.out.println("Arrays.deepEquals(arr2, arr3): " + Arrays.deepEquals(arr2, arr3));
Arrays.sort(arr1);
System.out.println("AArrays.sort(arr1): " + Arrays.toString(arr1));
}
}
运行结构如下

字符串
字符串是组织字符的基本数据结构
String类
String类一般用来表示字符串常量,用一对双引号括的字符串序列表示字符串常量
创建String对象
java
String str1 = new String("Hello");
String str2 = "World";
char arr = {'a','b','c'};
String str3 = new String(arr);
常用方法
+运算符,用于两个字符串的拼接操作
java
str = "Hello,"+"world";
str = str+'!'; // 系统自动将其它类型转为字符串类型,str指向的是一个新的字符串对象
charAt(),返回指定位置的字符
java
"Hello".charAt(1); // 返回'e'
equals(),用于判断两字符串的内容是否相同(与==区分)
java
String str1 = "abv";
String str2 = "abv";
System.out.println("str1=str2: " + str1.equals(str2));
其它
java
class Test{
public static void main(String[] args) {
String str1 = "Hello, World!";
String str2;
System.out.println("str1的长度为:" + str1.length());
System.out.println("字符串llo第一次出现的位置:" + str1.indexOf("llo"));
System.out.println("字符串o最后一次出现的位置:" + str1.lastIndexOf("o"));
System.out.println("将str1转换为大写:" + str1.toUpperCase());
String[] arr = str1.split(", ");
System.out.println("使用逗号和空格分割字符串后的结果:");
for (String s : arr) {
System.out.println(s);
}
str2 = str1.replace("World", "Java");
System.out.println(str2);
}
}
StringBuffer与StringBuilder类
StringBuffer和StringBuilder类用于字符串遍历,功能大致相同,但StringBuffer类支持多线程,StringBuilder类不支持。在单线程编程中建议优先采用StringBuilder类。
创建StringBuffer对象
java
StringBuffer s1 = new StringBuffer(); // 空串
StringBuffer s2 = new StringBuffer(10); // 初始容量为10的StringBuffer对象,空串
StringBuffer s3 = new StringBuffer("Hello"); // 内容为Hello的StringBuffer对象,初始容量为21(字符串长度+16)
常用方法
追加新内容
java
s3.append(',');
s3.append("world");
插入新内容
java
s3.insert(6,"Java "); // s3="Hello,Java world"
StringBuilder为例
java
class Test{
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder("abcdefg hijklmnop");
s1.replace(1,7, "ddd"); // 将索引1到7的内容替换为abc
System.out.println(s1);
s1.delete(1,7);
System.out.println(s1);
s1.reverse();
System.out.println(s1);
}
}
修改String对象的值,Java编译器会自动创建StringBuffer对象实现。所以在处理内容可变的字符串时用StringBuffer或StringBuilder效率更高
java
String s = "Hello"+",world"+"!";
//等同于
String s = new StringBuffer("Hello").append(",world").append("!").toString(); // StringBuffer类对象转换成String类,使用toString()方法
Vector类
vector类用于描述向量,可视为可变长数组。其长度指向量存储的元素个数,容量指向量的大小,增量指当容量不足时每次扩容的量(自动进行)。
创建Vector对象
java
Vector v1 = new Vector(); // 空的向量对象,容量为10,增量为0
Vector v2 = new Vector(10); // 初始容量为10,增量为0
Vector v3 = new Vector(20,5); // 初始容量为20,增量为5
常用方法
add(),添加元素
java
v1.add("hello"); // 在最后一个元素之后添加元素
v1.add(0,"java"); // 在指定下标位置插入元素,等同于v1.insertElementAt("java",0),插入位置之后的元素都向后移动1个单位
elementAt(),访问指定下标处的元素(不能使用数组的[])
java
String s = v1.elementAt(1);
练习,用Vector类实现栈
java
import java.util.Vector;
class MyStack{
private Vector v;
public MyStack(int s){
v=new Vector(s);
}
public void push(Object obj){
v.add(obj);
}
public Object pop(){
Object obj=v.lastElement();
v.remove(v.size()-1);
return obj;
}
public Object getTop(){
return v.lastElement();
}
public int getLength(){
return v.size();
}
}
class Test{
public static void main(String[] args) {
MyStack stack=new MyStack(5);
for(int i=1; i<=5; i++)
stack.push(i);
for(int i=0; stack.getLength()>0; i++)
System.out.print(stack.pop()+"\t");
}
}
异常处理
一个异常由一个对象来表示,所有的异常都直接或间接的继承自Throwable接口的实例。Throwable接口是类库java.lang包中的一个类,它派生了Error和Exception两个子类。
Error类
Error类及其子类主要用来描述一些java运行时刻系统内部的错误或资源枯竭导致的错误,此类错误不能抛出,一般由系统处理。
Exception类
编程中,对异常的处理主要是对Exception及其子类,常用方法如下:
java
public Exception() // 构造
public Exception(String message) // 构造
public String toString() // 返回描述当前Exception类信息的字符串
Exception类又分为两类:
RuntimeException异常------程序缺陷异常,不处理也不会有语法上的错误,可以不用try...catch处理,是非受检异常。
非RuntimeException异常------一般是程序外部引起的异常,在语法上必须要求处理,是受检异常(必须捕获,如FileNotFoundException、IOException)
自定义异常类
java
class myException extends Exception {
public myException() {
super();
}
public String toString(){
return "This is my exception";
}
}
自定义异常类需要自己抛出和捕捉,系统定义异常仅需要捕捉
抛出和声明异常
用户自定义异常需要通过throw语句抛出
java
import java.util.Scanner;
class myException extends Exception {
public myException() {
super();
}
public String toString(){
return "This is my exception";
}
}
public class MyTest {
public static void main(String[] args) throws myException {
Scanner scanner = new Scanner(System.in);
int n=scanner.nextInt();
System.out.println(n);
scanner.close();
if(n<10){
throw new myException();
}
}
}
捕捉异常
使用try...catch...finally机制来捕捉异常,并进行处理
java
import java.util.Scanner;
public class MyTest {
public static void main(String[] args) {
String[] colors={"Red", "Green", "Blue"};
Scanner scanner=new Scanner(System.in);
try{
String str=scanner.nextLine();
int i=Integer.parseInt(str); // 需要捕捉numberformatexception异常
System.out.println(i+",");
System.out.println(colors[i]); // 需要捕捉arrayindexoutofboundsexception异常
}catch(NumberFormatException e){
System.out.println("输入的不是整数");
e.printStackTrace();
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界");
e.printStackTrace();
}
finally{
System.out.println("程序结束");
}
scanner.close();
}
}
多线程编程
进程和线程
进程是程序的一次动态的执行过程,对应了从代码加载、执行到执行完毕的一个完整过程。多进程是计算机操作系统同时运行几个程序或任务的能力(严格来说,一个单CPU计算机在任何时刻智能执行一个任务,是操作系统很快地在各个程序之间切换,看起来在同时执行多个程序)。
一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程。多线程是在同一应用程序中,有多个顺序流同时执行,完成不同的功能。
线程的实现
Java的线程通过java.lang.Thread类来实现,一个Thread类的对象代表一个线程。Runnable接口只有一个方法run(),它可以被运行系统自动识别和执行。所以一个实现Runnable接口的类实际上是定义一个新线程的操作。Thread类实现了Runnable接口。
Java中实现多线程有两种途径:继承Thread类构造线程;实现Runnable接口构造线程。
继承Thread类
一个类只要继承了Thread类就可作为多线程操作类,在Thread子类中必须覆写Thread类中的run()方法。
java
class CountThreads extends Thread { // 1.继承Thread类
private String name;
public CountThreads(String name) {
this.name = name;
}
public void run(){ // 2.覆写run()方法
System.out.println("Thread " + name + " is running.");
for(int i=0;i<9;i++){
System.out.println("Thread " + name + " : " + i);
}
System.out.println("Thread " + name + " is exiting.");
}
}
public class MyTest {
public static void main(String[] args) {
CountThreads t1 = new CountThreads("A"); // 3.创建线程对象
CountThreads t2 = new CountThreads("B");
t1.start(); // 4.启动多线程
t2.start();
}
}
不能确定执行结构,无法准确知道线程在什么时间开始执行,由系统来确定。
注意:使用start()方法时,系统启动线程,并分配虚拟CPU开始执行这个线程的run()方法后立即返回,不是等run()方法执行结束后返回。
实现Runnable接口
把实现了整个接口的类的实例传给Thread类的构造方法即可实现多线程操作。
java
class CountThread implements Runnable { // 1.实现Runnable接口
private String name;
public CountThread(String name) {
this.name = name;
}
public void run() { // 2.覆写run()方法
for (int i = 1; i < 10; i++) {
System.out.println(name + " : " + i);
}
System.out.println("线程结束:" + this.name);
}
}
public class MyTest {
public static void main(String[] args) {
CountThread t1 = new CountThread("A"); // 3.创建对象
CountThread t2 = new CountThread("B");
Thread thread1 = new Thread(t1); // 4.创建Thread对象
Thread thread2 = new Thread(t2);
thread1.start(); // 5.启动线程
thread2.start();
}
}
使用Runnable接口可以避免Java的单继承局限。
线程的优先级
Thread类有三个有关线程优先级的静态常量
java
public static final int MIN_PRIORITY // 最低优先级,表示常量1
public static final int NORM_PRIORITY // 普通优先级(默认),表示常量5
public static final int MAX_PRIORITY // 最高优先级,表示常量5
在创建线程对象后,可以使用setPriority(int p)方法改变线程的优先级,也可使用getPriority()获得线程的优先级
线程的基本控制
线程睡眠
程序运行过程中让一个线程进入指定时间的暂时睡眠状态,使用Thread对象的sleep()方法
线程状态测试
线程由start()方法启动后到其被终止,之间任何时刻都处于活动状态,可通过Thread对象的isAlive()方法来获取线程是否处于活动状态
线程加入
如果有一个A线程正在运行,希望插入一个B线程,并要求B先执行完毕,再继续A的执行,可以使用Thread对象的join()方法完成
java
class CountThread implements Runnable {
private String name;
public CountThread(String name) {
this.name = name;
}
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println(name + " : " + i);
}
System.out.println("线程结束:" + this.name);
}
}
public class MyTest {
public static void main(String[] args) {
Thread t1 = new Thread(new CountThread("线程A"));
t1.start();
for(int k=0;k<5;k++){
if(k>=2){
try{
t1.join(); // 线程A强制执行,执行完毕后,主线程才继续执行
}catch(InterruptedException e){}
}
System.out.println("主线程 : " + k);
}
}
}
多线程的同步与死锁
由于线程是共享资源的,所以可能会产生共享资源的争夺问题。我们要保证线程的同步,才能保证多线程的程序不会出问题。
共享资源同步
在Java中引入了"对象互斥锁",阻止多个线程同时访问同一个条件变量。用synchronized声明一个操作共享数据的代码块或方法,可以实现"对象互斥锁"。
同步代码块
在代码块上加一个synchronized修饰,那在任何时刻只能有一个线程获得访问权。当一个线程要执行这段代码时,只有获取到特定对象的锁才能执行。在特定对象未释放的情况,线程必须等待
使用同步代码块必须指定一个需要同步的对象,一般将当前对象this设置为同步对象
java
class Account{
private double balance;
Account(double balance){
this.balance=balance;
}
double withdraw(double i){
if(i>balance){
System.out.println("Insufficient Balance");
return 0;
}
balance-=i;
return i;
}
double getBalance(){
return balance;
}
}
class WithdrawThread implements Runnable{
private Account account;
private double amount;
public WithdrawThread(Account account,double amount){
this.account=account;
this.amount=amount;
}
public void run(){
synchronized(this){ // 设置同步块
double balance=account.getBalance();
System.out.println("Thread "+Thread.currentThread().getName()+" checking balance: "+balance);
account.withdraw(amount);
try{
Thread.sleep(1); // 模拟花费时间延迟
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("余额为:"+ account.getBalance());
}
}
}
public class MyTest {
public static void main(String[] args) {
Account account=new Account(1000);
WithdrawThread withdrawTask=new WithdrawThread(account,800);
Thread t1=new Thread(withdrawTask,"T1"); // 两个线程共用1个WithdrawThread 实例
Thread t2=new Thread(withdrawTask,"T2"); // 其中一个获得WithdrawThread对象锁后另一个会阻塞等待
t1.start();
t2.start();
}
}
同步方法
用synchronized修饰的方法称为同步方法。当一个线程调用一个同步方法时,首先判断该方法上的锁是否已锁定,若未锁,则执行该方法,同时给这个方法上锁,运行完后释放锁;若已被锁定,则线程必须等待,直至锁被释放。
java
public synchronized void process(){ // 设置同步方法
}
