Java速通(应用程序)

**前言:**今年选修了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(){       // 设置同步方法
}
相关推荐
爱学习的小可爱卢3 小时前
JavaEE进阶——Spring Bean与Java Bean的核心区别
java·后端·java-ee
我是人机不吃鸭梨3 小时前
Flutter AI 集成革命(2025版):从 Gemini 模型到智能表单验证器的终极方案
开发语言·javascript·人工智能·flutter·microsoft·架构
沐知全栈开发3 小时前
服务定位器模式
开发语言
期待のcode3 小时前
Java Object 类
java·开发语言
悟能不能悟4 小时前
如何处理 丢失更新(不可重复读)
java
Wang's Blog4 小时前
Lua: 协程编程详解之从基础到多任务处理与应用实战
开发语言·lua
笙枫4 小时前
LangGraph Agent 架构基础:从概念到第一个可运行的Agent
开发语言·架构·php
李拾叁的摸鱼日常4 小时前
Java Optional 最佳实践+注意事项+避坑指南
java·后端·面试
雨中飘荡的记忆4 小时前
MyBatis配置解析模块详解
java·mybatis