Java语言学习日记

0xGeekCat · 2020-8-10 · 次阅读


Study-method(really important)

  • 查文档,做实验
  • Google
    • 关键词选择
      • 由精细到粗化
    • 略读页面介绍,分析可能性
      • 超过5页无结果,不要往后翻阅
    • 先中文,后英文
  • 论坛
  • 深入钻研自己方向的知识,非方向了解即可,切忌走火入魔

Java-grammar

新建Java项目

new project -> choice Project SDK -> create project from template -> choice Java Hello World -> choice Project Location -> finish

这样子创建就不会发生命令行执行Java文件出错情况

Java程序执行过程

程序(硬盘) --装载至内存区--> 代码(内存)

操作系统代码(内存) --找到main开始执行--> 代码(内存)

Java执行过程中的内存管理

  • heap 动态申请内存
  • stack 局部变量
  • data segment 静态变量 字符串常量
  • code segment 存放代码(类的方法)

Java字符型

双引号包裹字符串,单引号包裹字符

Java字符采用Unicode编码,每个字符占2个字节

Java整型

Java没有无符号整型,整型常量默认为int型,声明long型在常量后加’l’或’L’即可

int a = 6; long a = 6L

Java浮点类型常量三种表示形式

  • 十进制
  • 八进制:要求以0开头
  • 十六进制:要就以0x或者0X开头
类型 占用存储空间 表数范围
byte 1字节 -128~127
short 2字节 -2^15~2^15-1
int 4字节 -2^31~2^31-1
long 8字节 -2^63~2^63-1

Java浮点型

Java浮点类型常量两种表示形式

  • 十进制数形式:3.14
  • 科学计数法形式:3.14e2 3.14E2 1e10 == 1 * 10 ^ 10

Java浮点型常量默认double型,声明float型在数字后面加’f’或’F’

double a = 1.1; float a = 1.1f

类型 占用存储空间
float 4字节
double 8字节

Java布尔类型

boolean不可以与其他数据类型进行相互转换

byte,short,char不会互相转换

数据类型依据容量大小转换,容量小类型自动转换为容量大的数据类型

byte,short,char -> int -> long -> float -> double

容量大类型可强制转换容量小类型,但会发生精度降低或溢出

byte b1 = 1; // 可以直接将int常量赋值给byte,short,char
byte b2 = 2;
byte b3 = (byte) (b1 + b2); // byte,short,char运算时先转换为int b1 + b2 => int 之后强制转换 

float f2 = 123; // 正确

int i = 1;
i = i * 0.1; // double赋值给int 强制转换

Java运算符

&& 短路与 || 短路或 如果第一个操作数就可以决定结果 则不计算第二个操作数

& 逻辑与 || 逻辑或 无论第一个操作数结果如何 都会进行第二个操作数

int i2 = 20;
int i = (i2 ++);  // 先将i2赋值给i,之后i2自增
// 输出结果 i = 20 i2= 21

int i2 = 20;
int i = (++ i2); // i2先自增,之后i2赋值给i
// 输出结果 i = 21 i2= 21

字符串规则

  • 加号运算符两侧操作数只有一个字符串类型,系统自动将另一个转换为字符串再进行连接
  • 当进行打印时,无论什么类型,都自动转换为字符串

Java switch语句

  • 小心case穿透,推荐break语句
  • default可以省略,但不推荐省略
  • Java中switch语句只能探测int,但也可以接收byte,short,char,只要可以转换为int即可

Java方法

public static void main(String[] args) {

  m('1', 2); // 可以传字符 传入值为 '1' 的 ascii code
}

public static void m(int i, int j) {

  System.out.println(i + j);
}

Java-oop

Java面向对象

成员变量

在定义成员变量时可以对其初始化,如果不初始化,Java对其默认初始化,但局部变量没有此功能

成员变量范围是整个类体

成员变量类型 取值
byte 0
short 0
int 0
long 0L
char ‘\u0000’
float 0.0F
double 0.0D
boolean false
所有引用类型 null

Java内存解析

引用

  • Java除基本类型(之前提到八种数据类型)之外变量都成为引用类型
  • Java对象是通过引用对其操作的

基础类型

int i = 0
// 内存中分配一个名为的空间值是0
// 基础类型占一块内存

引用类型

String s; //a
// 声明String类型变量 a代码在内存中生成一个null的区域
// 引用类型占两块内存

// new语句创建String类型对象 让s指向它 
// 此时之前null区域的值变为可以找到之前new出来的对象(位于堆内存)的标识
// ** 特别注意 **
// 此标识并不是之前new出来的对象的物理地址
s = new String("Hello World");

如何在内存中区分类和对象

  • 类是静态的概念,代码区
  • 对象是new出来的,位于堆内存
  • 除静态变量外,类的每个成员变量在不同的对象中有不同的值,而方法执行时才占用内存

Java构造方法

构造方法缺省情况下,系统默认添加代码className() {}

  • new + 构造方法,创建一个新的对象

  • 构造函数是定义在Java类中一个用来初始化对象的函数

  • 构造函数与类同名且没有返回值

public class Person {

    private int id;
    private int age;

    Person(int _id, int _age) {
        id = _id;
        age = _age;
    }

    public static void main(String[] args) {
        Person mac = new Person(1, 18);
    }
}  

// ** 内存解析 **
// 栈内存中存放Person 引用类型 的 局部变量mac

// 调用方法时,方法(此处为构造方法)中的变量也是局部变量
// 栈内存中存放局部变量_id,_age
// 堆内存中存放new对象Person,存在id,age成员变量
// 构造方法中代码执行将栈中_id,_age的值赋值给堆中id,age
// 构造方法调用完,栈中_id,_age自动回收

// 如果方法有返回值,返回值存入栈内存

// ** 特别注意 **
// 类的方法代码只有一份 并不是每个对象存一份 成员变量则每个对象中都有
// 但调用方法时才会在内存中分配空间

// 当数据打印后,栈内存中相应数据自动消失

Java命名规则

  • 类名首字母大写
  • 变量名和方法名首字母小写
  • 运用驼峰标识

Java方法重载

  • 方法重载指一个类中可以定义有相同的名字,但参数不同的多个方法;调用时,根据不同参数表选择对用方法

Java方法(包括构造方法)允许重载,但不允许重名

// 重名现象 会造成混淆
public class Person {

    void a() { }
    int a() { }
}

Java this关键字

  • 在类的方法定义中使用的this关键字代表使用该方法的对象的引用
  • 当必须指出当前使用方法的对象是谁时要使用this
  • 使用this处理方法中成员变量和参数重名情况
  • this也看作一个变量,值为当前对象的引用

Java static关键字

  • 在类中,用static声明的成员变量为静态成员变量,它为类的共有变量,在第一次使用时被初始化
  • 对该类所有对象而言,static成员变量只有一份
  • static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以static方法不可访问非static成员
  • 可以通过对象引用或类名(无需实例化)访问静态成员

可以通过类名 . [静态变量名]的方式访问静态变量

package和import语句

class文件最上层包的父目录位于classpath下

Java中一个文件中可以有多个私有类,但只能有一个公共类且类名

为便于管理大型软件系统中众多的类,解决类中命名冲突问题,Java引入包(package)机制,提供类的多重类命名空间

包命名格式

package [倒过来的公司域名][.项目名]

  • package语句作为Java源文件第一条语句,指明该文件中定义的类所在的包(缺省表示无名包)

  • Java编译器把包对应于文件系统的目录管理,package语句中’.’指明目录层次

    package com.sxt 表明该文件所有的类位于.\com\sxt目录下

exp

文件结构
.
├── Demo
│   └── Cat.java
|__ Dog.java

Cat.java
package Demo;

public class Cat { }

Dog.java(普通写法)
public class Dog {
     Demo.Cat cat = new Demo.Cat();
}

Dog.java(import引入)
import Demo.Cat; // 如果要引入文件夹下所有类 import Demo.*

public class Dog {

     Cat cat = new Cat();
}

// 访问同一个包中的类不需要引入

J2SDK(JDK)主要包介绍(暂不研究jar包生成)

  • java lang:包含一些java语句核心类,如String,Math,Integer,System和Thread,提供常用功能
  • java awt:包含构造抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)
  • java applet:包含applet运行所需的一些类(不再流行)
  • java net:包含执行与网络相关的操作的类
  • java io:包含能提供多种输入输出功能的类
  • java util:包含一些实用工具类,如定义系统特性、使用与日期日历相关函数

Java lang包比较特别,可以直接使用,而其它包使用必须引入

查询jdk路径$ /usr/libexec/java_home -V

查询结果/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk

/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jarrt => runtime

Java运行文件都在rt.jar文件下,jar是一种压缩文件(目的将整体封装方便使用)

封装命令$ jar -cvf [jar name] *.*

*.*表示将当前目录及子目录的所有文件打印到jar包中

以下是解压后目录

$ tree -L 1
.
├── META-INF
├── apple
├── com
├── java
├── javax
├── jdk
├── org
└── sun

$ cd java

$ tree -L 1
.
├── applet
├── awt
├── beans
├── io
├── lang(直接使用 不需引入)
├── math
├── net
├── nio
├── rmi
├── security
├── sql
├── text
├── time
└── util

Java继承与权限控制

权限控制

Java权限修饰符public,protected,private置于类的成员定义前,用来限定其它对象对该类成员访问权限

如果权限修饰符缺省,默认为default(包权限)

修饰符 类内部 同一个包 子类 任何地方
private yes
default yes yes
protected yes yes yes
public Yes yes yes yes

对于class的权限修饰只可以用public和default

  • public类可以在任意地方被访问
  • default类只可以被同一个包内部的类访问

继承

  • Java中使用extends关键字实现类的继承机制
  • 通过继承子类自动拥有基类所有成员变量与方法,但不一定拥有使用权,基类私有成员子类无法使用
  • Java只支持单继承,一个子类只能拥有一个基类,但基类可以派生多个子类

Java方法重写

  • 在子类中可以根据需要对从基类中继承来的方法进行重写
  • 重写方法必须和被重写方法具有相同方法名称、参数列表、返回类型
  • 重写方法不能使用比被重写方法更严格的访问权限

书写规范:复制粘贴原方法之后再修改,避免发生书写错误

Java super关键字

Java类中使用super来引用基类方法,与this相对

Java继承中的构造方法

  • 子类构造过程中必须调用其基类构造方法
  • 子类可以在自己构造方法中使用super()调用基类构造方法
  • 调用super(),必须写在子类构造方法的第一行
  • 使用this()调用本类的另外的构造方法
  • 子类构造方法没有显式地调用基类构造方法,系统默认调用基类无参数构造方法
  • 如果子类构造方法既没有显式调用基类构造方法,而基类有没有无参构造方法,编译出错

Java Object类

  • Object类是所有Java类的根基类
  • 类声明时未使用extends指明基类,默认基类为Object类

Java toString方法

  • Object类中定义有public String toString()方法,其返回值为String类型,描述当前对象的有关信息
  • 在String与其它类型数据的连接操作时,自动调用该对象类的toString()方法
  • 用户可以自定义重写toString()方法
// Cat.java

package Demo;

class Cat { }

// Dog.java

package Demo;

public class Dog {

    public static void main(String[] args) {
        Cat c= new Cat();
        System.out.println(c.toString()); // Demo.Cat@61bbe9ba
    }
}

// Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成

// Cat.java(重写toString)
package Demo;

class Cat {
    @Override
    public String toString() {
        return "I am a cat"; // 最后执行Dog.java 结果 I am a cat
    }
}

HashCode解释

从Java虚拟机角度看内存布局,在Java运行时,需要找到内存地址,hashcodes table存着每个对象的hash编码

通过hash编码寻找内存地址,但可能存在多对象具有相同hash编码的情况,暂不讨论原因

Java equals方法

  • Object的equals方法定义:x.equals(y) 当x,y为同一个对象的引用是返回true或false
  • J2SKD提供一些类,如String,重写equals方法,x.equals(y),当x和y所引用的对象是同一类对象,且属性内容相等(并不一定为相同对象),返回true或false
  • 用户可以自定义重写equals()方法
package Demo;

class Cat {
    private int color;

    private Cat(int color) {
        this.color = color;
    }

    @Override
    public boolean equals(Object obj) { // 栈内存中开辟 Object引用类型变量obj
        // 对象不能为空值
        if (obj == null)
            return false;
        else {
            // obj 为 Cat 对象的引用
            if (obj instanceof Cat) {
                Cat c = (Cat) obj; // 强制将Object类型转换为Cat类型
                return c.color == this.color;
            }
        }

        return false;
    }

    public static void main(String[] args) {
        Cat c1 = new Cat(1);
        Cat c2 = new Cat(1);

        System.out.println(c1.equals(c2)); // true 
    }
}

Java对象转型

  • 一个基类的引用类型变量可以指向其子类的对象
  • 一个基类的引用不可以访问其子类对象新增的成员(属性和方法)
  • 可以使用引用变量instanceof类名,来判断该引用型变量所指向的对象是否属于该类或该类子类
  • 子类的对象可以当做基类的对象来使用称作向上转型,反之称为向下转型

instanceof

package Demo;

class Animal { }

class Cat extends Animal { }

public class Test {

    public static void main(String[] args) {
        Animal a = new Animal();
        Cat c = new Cat();
        System.out.println(a instanceof Animal); // true
        System.out.println(c instanceof Animal); // true
        System.out.println(a instanceof Cat); // false 
    }
}

强制转换

package Demo;

class Animal {
    public String name;

    Animal(String name) {
        this.name = name;
    }
}

class Cat extends Animal {
    public String eyesColor;

    Cat(String n, String c) {
        super(n);
        eyesColor = c;
    }
}

public class Test {

    public static void main(String[] args) {
        Animal a = new Cat("Cat", "blue");
        System.out.println(a.eyesColor); // error
        Cat c = (Cat) a;
        System.out.println(c.eyesColor); // blue
    }
}

Java动态绑定和多态

动态绑定是指在执行期间(非编译期间)判断所引用对象的实际类型,根据实际类型调用其相应方法

  • 要有继承
  • 要有重写
  • 父类引用指向子类对象

多态例子

class Animal {
    private String name;

    Animal(String name) {
        this.name = name;
    }

    public void enjoy() {
        System.out.println("叫声");
    }
}

class Cat extends Animal {

    private String eyeColor;

    Cat(String name, String eyeColor) {
        super(name);
        this.eyeColor = eyeColor;
    }

    public void enjoy() {
        System.out.println("猫叫");
    }
}

class Lady {

    private String name;
    private Animal pet;

    Lady(String name, Animal pet) { 
        this.name = name;
        this.pet = pet;
    }

    public void mePetEnjoy() {
        pet.enjoy(); // b
    }
}

public class Main {

    public static void main(String[] args) {
        Cat c = new Cat("Cat", "blue");
        Lady l = new Lady("Lady", c); // a
        l.mePetEnjoy(); // 猫叫
    }
}

内存解析

堆内存中new的对象虽然不含方法(code segment),但存在可以指向方法的标识,具体指向什么方法依据情况动态改变

a代码表示Animal pet = c,虽然c的引用类型是Cat,但由于pet引用类型是Animal,则调用的为Animal类的enjoy()方法

但测试结果却是Cat类的enjoy()方法,原因就是**动态绑定(迟绑定)**,根据引用对象的实际类型动态调用方法

Java抽象类

  • 用abstract关键字来修饰一个类时,这个类叫抽象类;修饰方法时叫做抽象方法
  • 含有抽象方法的类必须被声明为抽象类,抽象类必须被继承,抽象方法必须被重写
  • 抽象类不能被实例化
  • 抽象方法只需声明,不需实现

抽象类

package Demo;

abstract class Animal {
    private String name;
    Animal(String name) {
        this.name = name;
    }

    public abstract void enjoy();
}

Java final关键字

  • final的变量的值不能够被改变(局部变量,形参)
  • final的方法不能被重写
  • final的类不能被继承

Java接口

  • 多个无关的类可以实现同一个接口
  • 一个类可以实现多个无关的接口
  • 与继承关系类似,接口与实现类之间存在多态性
  • 用于处理现实生活中的多继承问题(Java只能单继承)

什么是接口

  • 接口(interface)是抽象方法的常数值得定义的集合
  • 本质上,接口是一种特殊的抽象类,其只包含常量和方法的定义,没有变量和方法的实现

接口

public interface Runner {
    public static final int id = 1; 
    // public static final解决c++多继承中容易出现的问题 因为该变量不属于任何对象 位于data segment3
    public void start();
    public void run();
    public void stop();
}

接口特性

  • 接口可以多重实现
  • 接口声明的属性默认为public static final,也只能是这个
  • 接口只能定义抽象方法,而且这些方法默认为public,也只能是这个
  • 接口可以继承(extends)其它接口,并添加新的属性和抽象方法
package Demo;

public interface Singer {
    public void sing();
    public void sleep();
}

package Demo;

public interface Painter {
    public void paint();
    public void eat();
}

package Demo;

public class Teacher implements Singer, Painter {
    private String name;
    Teacher(String name) {
        this.name = name;
    }

    public void study() {
        System.out.println("studying");
    }

    @Override
    public void sing() {
        System.out.println("teacher is singing");
    }

    @Override
    public void sleep() {
        System.out.println("teacher is sleeping");
    }

    @Override
    public void paint() {
        System.out.println("teacher is painting");
    }

    @Override
    public void eat() {
        System.out.println("teacher is eating");
    }
} 

Java-exceptional handling

c++不检测数组是否越界,但Java检测

Java异常

  • Java异常是Java提供用于处理程序中错误的机制
  • 所谓错误是指在程序运行过程中发生的异常事件
  • 设计良好程序应该在异常发生时提供处理错误的方法,使用程序不会因为异常的发生而阻断或产生不可预期结果
  • Java程序执行过程出现异常事件,可生成一个异常类对象,该异常对象封装异常事件的信息并将被提交给Java运行时系统,该过程称为抛出(throw)异常
  • Java运行时系统接收到异常对象,会寻找到可以处理这一异常的代码并把当前异常对象交给其处理,该过程称为捕获(catch)异常
package Demo;

public class Demo {
    public static void main(String[] args) {
        try {
            System.out.println(2/0);
        } catch (ArithmeticException ae) {
            System.out.println("error");
            ae.printStackTrace(); // 打印异常栈轨迹
            // getMessage() 得到有关异常事件的信息
        }
    }
}

// 运行结果
// error
// java.lang.ArithmeticException: / by zero
    at Demo.Demo.main(Demo.java:8)

J2SDK中定义很多异常类,这些类对应各种各样可能出现的异常事件,所有类都来源于Throwable类

Java文档中标明有throw的方法必须要接

  • error Java虚拟机系统内部错误 无法处理
  • exception 可处理错误 一定要catch
  • runtimeException 运行时错误 可以忽略的异常

异常的捕获和处理

  • try代码包含可能产生异常的代码
  • try代码段后跟一个或多个catch代码段
  • 每个catch代码段声明其能处理的一种特定类型的异常并提供处理的方法
  • 当异常发生时,程序会终止当前流程(之后的语句将不再执行),根据获取异常的类型去执行相应的catch代码段
  • finally段的代码无论是否发生异常都有执行(异常处理的统一出口,常处理资源清理功能)
package Demo;
import java.io.*;

public class Demo {
    public static void main(String[] args) {
        FileInputStream in = null;
        try {
            in = new FileInputStream("file");
            int b;
            b = in.read();
            while (b != -1) {
                System.out.println((char) b);
                b = in.read();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

throw向上级抛出错误

package Demo;
import java.io.*;

class D {

    public void f() throws FileNotFoundException, IOException {
        FileInputStream in = new FileInputStream("file");
        int b;
        b = in.read();
        while (b != -1) {
            System.out.println((char) b);
            b = in.read();
        }
    }

    public void f2() {
        try {
            f();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        D d = new D();
        d.f2();
    }
}

手动抛出错误

package Demo;

public class Demo {

    private static void m(int i) throws ArithmeticException {
        if (i == 2)
            throw new ArithmeticException("出现错误");
    }

    public static void main(String[] args) {
        m(2);
    }
}

// 输出结果
Exception in thread "main" java.lang.ArithmeticException: 出现错误
    at Demo.Demo.m(Demo.java:8)
  at Demo.Demo.main(Demo.java:13)

在try语句块中,基类异常的捕获语句不可以写在子类异常捕获语句之上

重写方法需要抛出与原方法所抛出异常类型一致异常或不抛出异常

Java-array

Java数组对象的创建

Java声明数组时与c++不同,其不能指定长度(数组中元素的个数),int a[5]; //非法

c++中数组可以直接在栈内存中生成,Java的数组是引用类型,其数据生成在堆内存中,元素默认值等于成员变量默认

Java使用关键词new创建数组对象数组名 = new 数组元素类型[数组元素个数];

创建数组

package Demo;

public class Demo {

    public static void main(String[] args) {

        int[] arr;
        arr = new int[5];

        for (int i = 0; i < 5; i ++) {
            System.out.print(arr[i] + "\t");
        }
    }
}

// 0 0 0 0 0

Java数组初始化

动态初始化:数组定义与数组元素分配空间和赋值的操作分开进行

Java数组元素的引用

  • 数组元素的下标可以是整数常量或整数表达式
  • 每个数组都有一个属性length指明其长度

Java命令行与args数组

public class Main {

    public static void main(String[] args) {

        System.out.println("Hello World!");

        for (String arg : args) {
            System.out.print(arg);
        }
    }
}

~/IdeaProjects/Java-study/src 
$ javac Main.java // 编译

~/IdeaProjects/Java-study/src 
$ java Main 1 2 3 // 执行
Hello World!
123%           

简单算数运算

public class Main {

    public static void main(String[] args) {

        if (args.length < 3)
            System.exit(-1);

        double d1 = Double.parseDouble(args[0]);
        double d2 = Double.parseDouble(args[2]);
        double d = 0;

        switch (args[1]) {
            case "+":
                d = d1 + d2;
                break;
            case "-":
                d = d1 - d2;
                break;
            case "x":
                d = d1 * d2;
                break;
            case "/":
                d = d1 / d2;
                break;
            default:
                System.exit(-1);
        }

        System.out.println(d);
    }
}

简单排序

public class Main {

    public static void main(String[] args) {

        int[] a = new int[args.length];
        for (int i = 0; i < args.length; i ++) {
            a[i] = Integer.parseInt(args[i]);
        }

        selectionSort(a);
        print(a);
    }

    private static void selectionSort(int[] a) {

        int k, temp;
        for (int i = 0; i < a.length - 1; i ++) {
            k = i;
            for (int j = i + 1; j < a.length; j ++) {
                if (a[j] < a[k])
                    k = j;
            }

            if (k != i) {
                temp = a[i];
                a[i] = a[k];
                a[k] = temp;
            }
        }
    }

    private static void print(int[] a) {
        for (int i1 : a) {
            System.out.print(i1 + " ");
        }
    }
}

二维数组

  • 二维数组可以看成数组为元素的数组

  • Java中多维数组的声明和初始化应按从高维到低维的顺序进行

    // 静态初始化
    int[][] a = {{1}, {2, 3}, {4, 5, 6}};
    int[3][2] b = {{1, 2}, {2, 3}, {4, 5}}; // 非法
    
    // 动态初始化
    int[][] a = new int[3][5];
    
    int[][] a = new int[3][];
    a[0] = new int[2];
    a[1] = new int[4];
    a[2] = new int[3];

数组的拷贝

拷贝数组最有效率的方法就是整块内存拷贝

  • 使用java.lang.System类的静态方法

    public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

  • 可以用于数组src从第srcPos项元素拷贝到目标数组从destPos项开始的length个位置

  • 如果源数据数目超过目标数组边界会抛出IndexOutOfBoundsException异常

public class Main {

    public static void main(String[] args) {

        int[] a = {1, 2, 3};
        int[] b = new int[3];

        System.arraycopy(a, 0, b, 0, a.length);

        for (int i1 : b) {
            System.out.println(i1);
        }
    }
}

Java-common class

String类

  • Java.lang.String类代表不可变的字符序列

  • “xxx”为该类的对象

  • String类常见构造方法

    String(String original) 创建一个String对象为original的拷贝

    String(char[] value) 用一个字符数组创建一个String对象

    String(char[] value, int offset, int count) 字符数组从offset开始 count个字符序列创建String对象

思考代码

public static void main(String[] args) {

  String s1 = "hello"; // a
  String s2 = "hello"; // b

  System.out.println(s1 == s2); // true
}

内存分析

“hello”为字符串常量,a行代码会在代码区中生成”hello”,并让s1指向它

当执行b行代码,编译器存在优化手段,代码区已经存在”hello”,则直接将s2指向它

思考代码

String是不可变字符序列,但这里为什么改变了?

    public static void main(String[] args) {

        String s1 = "hello";
        String s2 = "world";

        s1 += s2;

        System.out.println(s1); // helloworld
    }

内存分析

实质上是在data segment中开辟一块新内存,将s1、s2的内容复制进去,再令s1指向新内存(效率不高)

代码测试

public static void main(String[] args) {

  String s1 = new String("hello"); // new 分配到堆中
  String s2 = new String("hello");

  System.out.println(s1 == s2); // false == 比较地址 
  System.out.println(s1.equals(s2)); // true
}

字符数组

    public static void main(String[] args) {

        char c[] = {'a', 'b', 'c', 'd'};
        String s1 = new String(c);
        String s2 = new String(c, 1, 2);

        System.out.println(s1); // abcd
        System.out.println(s2); // bc
    }

String类常用方法

返回字符串第index个字符

public char charAt(int index)

返回字符串长度

public int length()

返回字符串中出现str的第一个位置

public int indexOf(String str)

返回字符串中从fromIndex开始出现str的第一个位置

public int indexOf(String str, int fromIndex)

比较字符串与another是否一样(忽略大小写)

public boolean equalsIgnoreCase(String another)

字符串中全部newChar字符替换oldChar字符

public String replace(char oldChar, char newchar)

判断字符串是否以prefix字符串开头

public boolean startsWith(String prefix)
判断字符串是否以prefix字符串结尾

public boolean ens=dsWith(String suffix)

返回字符串小写形式

public String toLowerCase()

返回字符串大写形式

public String toUpperCase()

返回字符串从beginIndex开始到结尾的字符串

public String substring(int beginIndex)

返回字符串从beginIndex开始到endIndex结尾的字符串

public String substring(int beginIndex, int endIndex)

返回该字符串去掉开头结尾空格后的字符串

public String trim()

将基本类型数据转换为字符串(如果参数是Object 调用toString() 多态)

public static String valueOf(...)

将字符串按照指定分隔符分隔,返回分隔后字符串数组

public String[] split(String regex)

public static void main(String[] args) {

  int j = 1234567;
  String sNumber = String.valueOf(j);
  String str = "j 是" + sNumber.length() + "位数";
  System.out.println(str);

  String s = "Mary,F,1976";
  String[] sPlit = s.split(",");
  for (String s1 : sPlit) {
    System.out.println(s1);
  }
}

StringBuffer类

  • Java.lang.StringBuffer代表可变字符序列
  • StringBuffer和String类似,但StringBuffer可以对其字符串进行改变

String类常用方法

StringBuffer对象添加字符序列,返回添加后的字符引用

public StringBuffer append(...)

StringBuffer对象在指定位置插入字符序列,返回修改后的字符引用

public StringBuffer append(int offset, String str)

可以删除从start开始到end-1位置的字符序列,返回修改后的字符引用

public StringBuffer delete(int start, int end)

public static void main(String[] args) {

  String s = "Microsoft";
  char[] a = {'a', 'b', 'c'};
  StringBuffer sb1 = new StringBuffer(s);
  sb1.append('/').append("IBM");

  System.out.println(sb1);

  StringBuffer sb2 = new StringBuffer("数字");
  for (int i = 0; i <= 9; i ++) {
    sb2.append(i);
  }

  System.out.println(sb2);

  sb2.delete(8, sb2.length()).insert(0, a);

  System.out.println(sb2);

  System.out.println(sb2.reverse());
}

基本数据类型包装类

java.lang.Integer

最大int型整数(2^31 - 1)

public static final int MAX_VALUE

最小int型整数(-2^31)

public static final int MIN_VALUE

返回封装数据long型值

public long longValue()

返回封装数据double型值

public double doubleValue()

将字符串参数作为有符号的十进制整数进行解析,返回封装数据int型值

public static int parseInt(String s) throws NumberFormatException

将字符串解析为int型数据,返回Integer对象

public static Integer valueOf(String s) throws NumberFormatException

public class Main {

    public static void main(String[] args) {

        double[][] d;
        String s = "1,2;3,4,5;6,7,8";

        String[] sFirst = s.split(";");
        d = new double[sFirst.length][];
        for (int i = 0; i < sFirst.length; i ++) {
            String[] sSecond = sFirst[i].split(",");
            d[i] = new double[sSecond.length];

            for (int j = 0; j < sSecond.length; j ++) {
                d[i][j] = Double.parseDouble(sSecond[j]);
            }
        }

        for (int i = 0; i < d.length; i ++) {
            for (int j = 0; j < d[i].length; j ++) {
                System.out.print(d[i][j] + " ");
            }
            System.out.println();
        }
    }
}

Math and File

Math类

java.lang.Math提供一系列静态方法用于科学计算,其方法的参数和返回值类型一般为double型

Math类常用方法

abs 绝对值

sqrt 平方根

log 自然对数

exp e为底指数

random() 返回0.0到1.0的随机数

File类

  • java.io.File类表示系统文件名(路径和文件名)

  • File类常见构造方法

    • public File(String pathname)

      以pathname为路径创建File对象,如果pathname是相对路径,则默认当前路径在系统属性user.dir中存储

    • public File(String parent, String child)

      以parent为父路径,child为子路径创建File对象

  • File静态属性String separator存储了当前系统的路径分隔符(window 反斜杠 window linux 正斜杠)

File类常用方法

  • 通过file对象可以访问文件的属性

    public boolean canRead()

    public boolean canWrite()

    public boolean exists()

    public boolean isDirectory()

    public boolean isFile()

    public boolean isHidden()

    public long lastModified()

    public long length()

    public String getName()

    public String getPath()

  • 通过File对象创建空文件或目录

    public boolean createNewFile() throws IOException

    public boolean delete()

    public bopolean mkdir()

    public boolean mkdirs() // 建立一系列路径

创建文件

import java.io.File;
import java.io.IOException;

public class Main {

    public static void main(String[] args) {

        String separator = File.separator;
        String filename = "file.txt";
        String director = "dir1" + separator + "dir2";

        File f = new File(director, filename);

        if (f.exists()) {
            System.out.println("文件名:" + f.getAbsolutePath());
            System.out.println("文件大小:" + f.length());
        } else {
            f.getParentFile().mkdirs(); // getParentFile 包的上级目录

            try {
                f.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

递归列出目录结构

package Week2;

import java.io.File;

public class Main {

    public static void main(String[] args) {

        File f = new File("/Users/max/IdeaProjects/Java-study/dir1");
        tree(f, 0);
    }

    private static void tree(File f, int level) {

        StringBuilder preStr = new StringBuilder();

        for (int i = 0; i < level; i ++) {
            preStr.append("    "); // 采用动态字符序列
        }

        File[] childs = f.listFiles();

        assert childs != null;
        for (File child : childs) {
            System.out.println(preStr + child.getName());

            if (child.isDirectory()) {
                tree(child, level + 1);
            }
        }
    }
}

Enum类

public class Main {

    private enum Color {red, green, blue}; // 定义枚举类型

    public static void main(String[] args) {
        Color c = Color.blue;

        switch (c) {
            case red:
                System.out.println("red");
                break;
            case blue:
                System.out.println("blue");
                break;
            case green:
                System.out.println("green");
                break;
            default:
                System.out.println("default");
                break;
        }
    }
}

日期处理

System.out.println(new Date()); // Wed Sep 11 18:53:42 CST 2019

返回以毫秒为单位的当前时间

System.out.println(System.currentTimeMillis()); // 1568133500627

Calendar

import java.util.Calendar;

public static void main(String[] args) {

  // 此时时间 2019-9-11
  Calendar c = Calendar.getInstance();

  System.out.println(c.get(Calendar.YEAR)); // 2019
  System.out.println(c.get(Calendar.MONTH)); // 8 
  System.out.println(c.get(Calendar.DAY_OF_WEEK)); // 4
  System.out.println(c.get(Calendar.DAY_OF_MONTH)); // 11
}

DateFormat

import java.text.DateFormat;
import java.util.Calendar;

public static void main(String[] args) {

  Calendar c = Calendar.getInstance();

  System.out.println(DateFormat.getDateInstance().format(c.getTime())); // 2019-9-11
  System.out.println(DateFormat.getInstance().format(c.getTime())); // 19-9-11 上午12:46
  System.out.println(DateFormat.getDateTimeInstance().format(c.getTime())); // 2019-9-11 0:46:45
}

SimpleDateFormat

import java.text.SimpleDateFormat;
import java.util.Calendar;

public static void main(String[] args) {

Calendar c = Calendar.getInstance();

SimpleDateFormat sm = new SimpleDateFormat("yyyy年MM月dd日 E HH时mm分ss秒");

System.out.println(sm.format(c.getTime())); // 2019年09月11日 星期三 00时53分44秒
}

Java-io

Java流式输入/输出原理

Java程序中,对于数据的输入/输出操作以”流”方式进行,J2SDK提供各种各样的”流”类,用以获取不同种类的数据;程序中通过标准的方法输入或输出数据

“流”可以理解为连接与程序和文件(硬盘)间用于数据传输的管道

Java输入/输出流的分类

  • java.io包中定义了多个流类型(类和抽象类)来实现输入/输出功能;可以从不同角度对其分类

    • 按数据流方向可以分为输入流和输出流
    • 安处理数据单位不同可以分为字节流和字符流(前者一个字节一个字节读,后者两个字节)
    • 按功能不同可以分为节点流和处理流
  • J2SDK所提供的所有流类型位于包java.io内都分别继承自一下四个抽象流类型(“管道”)

    字节流 字符流
    输入流 InputStream Reader
    输出流 OutputStream Writer

节点流和处理流

  • 节点流可以从一个特定的数据源(节点)读取数据(如 文件,内存)
  • 处理流是”连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更强大的读写功能

InputStream

继承自InputStream的流都是用于向程序中输入数据,且数据单位为字节(8 bits)

InputStream基本方法

读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾

int read() throws IOException

读取一系列字节并存储到一个数组buffer,返回实际读取字节数,如果读取前已到输入流末尾返回-1

int read(byte[] buffer) throws IOException buffer缓冲区有效减少访问硬盘次数,降低硬盘损耗

读取length个字节并存储到一个字节数组buffer,从offset位置开始返回实际读取字节,如果读取前已到输入流末尾返回-1

int read(byte[] buffer, int offset, int length) throws IOException

关闭流释放内存资源

void close() throws IOException

跳过n个字节不读,返回实际跳过的字节数

long skip(long n) throws IOException

OutputStream

继承自OutputStream的流式用于程序输出数据,且数据单位为字节(8 bits)

OutputStream基本方法

向输出流写入一个字节数据,字节数据为参数b的低八位

void write(int b) throws IOException

将一个字节类型数组中的数据写入输出流

void write(byte[] b) throws IOException

将字节类型数组从offfset开始的len个字节写入输出流

void write(byte[] b, int offset, int len) throws IOException

关闭流释放内存资源

void close() throws IOException

将输出流中缓冲数据全部写出到目的地(先flush后close)

void flush() throws IOException

Reader

与InputStream区别,数据单位为字符(16 bit)

Reader基本方法

int read() throws IOException

int read(char[] buffer) throws IOException

int read(char[] buffer, int offset, int length) throws IOException

void close() throws IOException

long skip(long n) throws IOException

Writer

与Outputstream区别,数据单位为字符(16 bit)

Write基本方法

void write(int b) throws IOException

void write(char[] b) throws IOException

void write(char[] b, int offset, int len) throws IOException

void write(String string) throws IOException

void write(String string, int offset, int len) throws IOException

void close() throws IOException

void flush() throws IOException

FileInputStream & FileOutputStream

文件读取

import java.io.*;;

public class Main {

    public static void main(String[] args) {

        int b = 0;
        FileInputStream in = null;

        try {
            in = new FileInputStream("/Users/max/IdeaProjects/Java-study/src/Main.java");
        } catch (FileNotFoundException e) {
            System.out.println("找不到指定文件");
            System.exit(-1);
        }

        try {
            int num = 0;
            while ((b = in.read()) != -1) {
                System.out.print((char)b);
                num ++;
            }
            in.close();
            System.out.println();
            System.out.println("共读取了" + num + "个字节");
        } catch (IOException e) {
            System.out.println("文件读取错误");
            System.exit(-1);
        }
    }
}

// 中文会乱码,因为中文占两个字节

文件复制

import java.io.*;

public class Main {

    public static void main(String[] args) {

        int b = 0;
        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("/Users/max/IdeaProjects/Java-study/src/Main.java");
            out = new FileOutputStream("/Users/max/IdeaProjects/Java-study/src/Copy.java");

            while ((b = in.read()) != -1) {
                out.write(b);
            }

            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            System.out.println("找不到指定文件");
            System.exit(-1);
        } catch (IOException e) {
            System.out.println("文件复制失败");
            System.exit(-1);
        }

        System.out.println("文件已复制");
    }
}

FileReader & FileWriter

import java.io.*;

public class Main {

    public static void main(String[] args) {

        int  b = 0;
        FileReader fr = null;

        try {
            fr = new FileReader("/Users/max/IdeaProjects/Java-study/src/Week2/Main.java");

            while ((b = fr.read()) != -1) {
                System.out.print((char)b);
            }

            fr.close();
        } catch (FileNotFoundException e) {
            System.out.println("找不到指定文件");
            System.exit(-1);
        } catch (IOException e) {
            System.out.println("文件复制失败");
            System.exit(-1);
        }
    }
}

// FileReader 解决中文乱码问题
import java.io.*;

public class Main {

    public static void main(String[] args) {

        FileWriter fw = null;

        try {
            fw = new FileWriter("/Users/max/IdeaProjects/Java-study/src/Unicode.txt");

            for (int i = 0; i <= 65545; i ++) {
                fw.write(i);
            }

            fw.close();
        } catch (IOException e) {
            System.out.println("文件写入错误");
            System.exit(-1);
        }

        System.out.println("文件写入成功");
    }
}

缓冲流

  • 缓冲流要”套接”在相应的节点流之上,对读写的数据提供缓冲功能,提高读写的效率,同时增加了新方法
  • 缓冲输入流支持其父类的mark和reset方法
  • BufferedReader提供readLine方法用于读取一行字符串(\r或\n分隔)
  • BufferedWriter提供newLine用于写入一个行分隔符
  • 对于输出的缓冲流,写出的数据会现在内存缓存,flush方法会将内存的数据立刻写入

BufferedInputStream

import java.io.*;;

public class Main {

    public static void main(String[] args) {

        try {
            FileInputStream fis = new FileInputStream("/Users/max/IdeaProjects/Java-study/src/Week2/Main.java");
            BufferedInputStream bis = new BufferedInputStream(fis); // 套接之前的字节流

            bis.mark(1); // 标记位 参数告知此输入流在标记位置无效之前允许读取的字节数

            System.out.println((char) bis.read()); // p
            System.out.println((char) bis.read()); // a

            for (int i = 0; i < 10; i ++)
                System.out.print((char) bis.read() + " "); // c k a g e   W e e k 

            System.out.print("\n");
            bis.reset(); // 回到标记位

            System.out.println((char) bis.read()); // p
            System.out.println((char) bis.read()); // a

            bis.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BufferedWriter & BufferedReader

import java.io.*;;

public class Main {

    public static void main(String[] args) {

        try {
            BufferedWriter bw = new BufferedWriter(new FileWriter("/Users/max/IdeaProjects/Java-study/src/Week2/random.txt"));
            BufferedReader br = new BufferedReader(new FileReader("/Users/max/IdeaProjects/Java-study/src/Week2/random.txt"));

            String s = null;

            for (int i = 0; i < 100; i ++) {
                s = String.valueOf(Math.random());
                bw.write(s);
                bw.newLine();
            }

            bw.flush();

            while ((s = br.readLine()) != null) {
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

转换流

  • InputStreamReader和OutputStreamWriter用于字节数据到字符数据之间的转换
  • InputStreamReader需要和InputStream”套接”
  • OutputStreamWriter需要和OutputStream”套接”
  • 转换流在构造时可以指定其编码集合
import java.io.*;;

public class Main {

    public static void main(String[] args) {

        try {
            OutputStreamWriter osw = new OutputStreamWriter(
                    new FileOutputStream("/Users/max/IdeaProjects/Java-study/src/Week2/file"));

            osw.write("microsoft");

            System.out.println(osw.getEncoding()); // UTF8
            osw.close();

            osw = new OutputStreamWriter(
                    new FileOutputStream("/Users/max/IdeaProjects/Java-study/src/Week2/file", true), "ISO8859_1"
            ); // true 表示追加 ISO8859_1 == latin_1 西欧语言

            osw.write("ibm");

            System.out.println(osw.getEncoding());
            osw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

System.in

import java.io.*;

public class Main {

    public static void main(String[] args) {

        InputStreamReader isr = new InputStreamReader(System.in); // 阻塞式(同步)方法 会一直等待输入

        BufferedReader br = new BufferedReader(isr);

        String s;

        try {
            s = br.readLine();

            while (s != null) {
                if (s.equalsIgnoreCase("exit"))
                    break;
                System.out.println(s.toUpperCase());

                s = br.readLine();
            }

            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

DateIO & ByteArrayIO(暂不研究)

Print流

  • PrintWriter和PrintStream都属于输出流,分别针对字符和字节
  • PrintWriter和PrintStream提供了重载的print
  • Println方法用于多种数据类型的输出
  • PrintWriter和PrintStream输出操作不会抛出异常,用户通过检测错误状态获取错误信息
  • PrintWriter和PrintStream有自动flush功能

PrintStream

import java.io.*;

public class Main {

    public static void main(String[] args) {

        try {
            PrintStream ps = new PrintStream(
                    new FileOutputStream("/Users/max/IdeaProjects/Java-study/src/Week2/file")
            );

            System.setOut(ps); // 重新分配“标准”输出流

            for (int i = 0; i < 100; i ++)
                System.out.print(i + "  "); //数据并不会打印到终端 而是到file中 
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;

public class Main {

    public static void main(String[] args) {

        String filename = args[0];

        if (filename != null)
            list(filename, System.out);
    }

    private static void list(String filename, PrintStream out) {

        try {
            BufferedReader bf = new BufferedReader(
                    new FileReader(filename)
            );

            String s = null;
            while ((s = bf.readLine()) != null)
                out.println(s);

            bf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PrintWriter

import java.io.*;
import java.util.Date;

public class Main {

    public static void main(String[] args) {

        String s = null;
        BufferedReader bf = new BufferedReader(
                new InputStreamReader(System.in)
        );

        try {
            FileWriter fw = new FileWriter("/Users/max/IdeaProjects/Java-study/src/file", true);

            PrintWriter log = new PrintWriter(fw);

            while ((s = bf.readLine()) != null) {
                if (s.equalsIgnoreCase("exit"))
                    break;
                System.out.println(s.toUpperCase());
                log.println("----");
                log.println(s.toUpperCase());
                log.flush();
            }

            log.println("===" + new Date() + "===");
            log.flush();
            log.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Object流(暂不研究)

Java-thread

线程的基本概念

  • 线程是一个程序内部的顺序控制流
  • 线程和进程的区别
    • 每个进程都有独立的代码和数据空间(进程上下文),进程间切换会有较大开销
    • 线程可看作轻量级进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),开销小
    • 多进程:在操作系统中能运行多个任务(程序)
    • 多线程:在同一应用程序中有多个顺序流同时执行

思考代码

public class Main {

    public static void main(String[] args) { 
        m1(); 
    }

    private static void m1() {
        m2();
        m3();
    }

    private static void m3() { }

    private static void m2() { }
}

// 依然是单线程 只有一个主线程
  • Java的线程是通过java.lang.Thread类来实现

  • Java虚拟机启动时会有一个主方法(public static void main() {})所定义的线程

  • 可以通过创建Thread实例来创建新线程

  • 每个线程都是通过某特定Thread对象所对应方法run()完成操作,run()称为线程体

  • 调用Thread类start()方法启动一个线程

    创建进程

创建线程的一种方法是声明实现 Runnable 接口的类,然后实现 run 方法。

然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动

public class Main {

    public static void main(String[] args) {
        Runner r = new Runner();

        Thread t = new Thread(r);
        t.start(); // 通知CPU产生新线程

        for (int i = 0; i < 100; i ++)
            System.out.println("%%%%%%" + i);
    }

}

class Runner implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i ++)
            System.out.println("******" + i);
    }
}

// 结果得出两线程并行运行

创建新执行线程的另一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。

接下来可以分配并启动该子类的实例

public class Main {

    public static void main(String[] args) {
        Runner r = new Runner();
        r.start();

        for (int i = 0; i < 100; i ++)
            System.out.println("%%%%%%" + i);
    }

}

class Runner extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 100; i ++)
            System.out.println("******" + i);
    }
}

推荐使用第一种接口方法,接口并不会影响继承其它父类

线程状态转换

大流程

创建内存 -start()-> 就绪状态 <-调度-> 运行状态 --> 终止

小循环

就绪状态 <-调度-> 运行状态 -阻塞事件-> 阻塞状态 -阻塞解除-> 就绪状态

位于就绪、运行、阻塞状态的线程为”活线程”

线程控制基本方法

方法 功能
isAlive() 判断线程是否还”活着”
getPriority() 获得线程优先级数值
setPriority() 设置线程优先级数值
Thread.sleep() 将当前线程睡眠指定毫秒数
join() 调用某线程该方法,将当前线程与该线程”合并”,即等待该线程结束,再恢复当前线程运行
yield() 让出CPU,当前线程进入就绪队列等待调度
wait() 当前进程进入对象的wait pool
notify()/notifyAll() 唤醒对象的wait pool中一个/所有等待进程

sleep方法

import java.util.Date;

public class Main {

    public static void main(String[] args) {
        Runner r = new Runner();
        Thread t = new Thread(r);

        t.start(); // 调用子进程run方法

        try {
            Thread.sleep(10000);
        } catch (InterruptedException ignored) { }

        t.interrupt();
    }
}

class Runner implements Runnable {

    boolean flag = true;

    @Override
    public void run() { // run方法一结束子进程就结束
        while (flag) {
            System.out.println("===" + new Date() + "===");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                flag = false;
            }
        }
    }
}

join方法

public class Main {

    public static void main(String[] args) {

        Thread t = new Thread("son thread");
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 10; i ++) {
            System.out.println("I am main Thread");
        }
    }
}

class Thread extends java.lang.Thread {

    Thread(String s) {
        super(s);
    }

    @Override
    public void run() {

        for (int i = 0; i < 10; i ++) {
            System.out.println("I am " + getName());
        }
    }
}

yield方法

public class Main {

    public static void main(String[] args) {

        Thread t1 = new Thread("t1");
        Thread t2 = new Thread("t2");

        t1.start();
        t2.start();
    }
}

class Thread extends java.lang.Thread {

    Thread(String s) {
        super(s);
    }

    @Override
    public void run() {

        for (int i = 1; i <= 100; i ++) {
            System.out.println(getName() + ": " + i);

            if (i % 10 == 0) // 满足10的倍数交换进程 
                yield();
        }
    }
}

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程

    线程调度器按照线程优先级决定应调度那个线程来执行

  • 线程优先级用数字表示,范围从1到10,一个线程的缺省优先级是5

    • Thread.MIN_PRIORITY = 1
    • Thread.MAX_PRIORITY = 10
    • Thread.NORM_PRIORITY = 5
  • 获得或设置线程对象的优先级

    • int getPriority();
    • void setPriority(int newPriority);
    public class Main {
    
      public static void main(String[] args) {
    
          Thread t1 = new Thread(new R1());
          Thread t2 = new Thread(new R2());
    
          t1.setPriority(Thread.NORM_PRIORITY + 3);
          t1.start();
          t2.start();
      }
    }
    

class R1 implements Runnable {

@Override
public void run() {
    for (int i = 0; i < 1000; i ++)
        System.out.println("%%%%%%" + i);
}

}

class R2 implements Runnable {

@Override
public void run() {
    for (int i = 0; i < 1000; i ++)
        System.out.println("++++++" + i);
}

}


## 停止线程

```java
public class Main {

   public static void main(String[] args) {

       Runner r = new Runner();
       Thread t = new Thread(r);

       t.start();

       for (int i = 0; i < 100; i ++)
           System.out.println("I am main thread " + i);

       System.out.println("main thread is over");

       r.shutDown();
   }
}

class Runner implements Runnable {

   private boolean flag = true;

   @Override
   public void run() {
       int i = 0;

       while (flag)
           System.out.println("I am son thread");
   }

   public void shutDown() {
       flag = false;
   }
}

isAlive方法

public class Main {

    public static void main(String[] args) {

        Runner r = new Runner();
        Thread t = new Thread(r);

        t.start();
    }
}

class Runner implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().isAlive());
    }
}

线程同步

思考代码

public class Main implements Runnable {

    private Timer timer = new Timer();

    public static void main(String[] args) {

        Main main = new Main();

        Thread t1 = new Thread(main, "t1");
        Thread t2 = new Thread(main, "t2");

        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        timer.add(Thread.currentThread().getName());
    }
}

class Timer {

    private int num = 0;

    void add(String name) {

        num ++;
        System.out.println(name + " 第 " + num + " 个timer线程");
    }
}

// 执行结果
t2 第 2 个timer线程
t1 第 2 个timer线程

结果分析

先执行t1子线程,num此时为1,之后发生线程打断,执行t2子线程,num为2,输出,之后t1子线程中num已经为2,输出

代码修改

synchronized void add(String name) { // 执行方法时锁定当前对象 (可理解是一个排队过程)

    num ++;
    System.out.println(name + "第 " + num + " 个timer线程");
}

// 执行结果
t1第 1 个timer线程
t2第 2 个timer线程

线程死锁

思考代码

public class Main implements Runnable {

    private int flag;

    private static final Object o1 = new Object();
    private static final Object o2 = new Object();

    @Override
    public void run() {

        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (o2) {
                    System.out.println("1");
                }
            }
        }

        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (o1) {
                    System.out.println("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        Main main1 = new Main();
        Main main2 = new Main();

        main1.flag = 1;
        main2.flag = 0;

        Thread t1 = new Thread(main1);
        Thread t2 = new Thread(main2);

        t1.start(); // a
        t2.start(); // b
    }
}

// 无响应

代码解析

当a代码执行时,调用run(),先锁定o1,需要o2即可完成;此时b代码也开始执行,立刻锁定o2,且需要o1,故形成死锁

解决方法

锁定一个大对象即可,避免锁定多个对象

Java-gui

AWT是Java比较久的图形开发包,并不能完全跨平台,会存在误差,javax.swing是Java的新开发包,但依然离不开AWT

Component & Container

  • Java图形用户界面最基本组成部分,Component类及其子类对象用来描述以图形化的方式显示在屏幕上并能与用户进行交互的GUI元素
  • 一般Component对象不能独立显示出来,必须放在Container对象中才行
  • Container是Component子类,Container子类对象可以容纳其它Component对象
  • Container对象可以使用add()向其中添加其它Component对象
  • Container是Component的子类,因此Container对象也可以被视为Component对象添加到其它Container中
  • 两种常见Container
    • Window:其对象表示自由停泊的顶级窗口
    • Panel:其对象可作为容纳其它Component对象,但不能独立存在,必须添加在其它Container中

Frame

  • Frame是window子类,由Frame或其子类创建的对象为一个窗体
  • 常见构造方法
    • Frame()
    • Frame(String s) 创建标题栏为s的窗口
import java.awt.*;

public class Main {

    public static void main(String[] args) {

        Frame f = new Frame("First Demo");

        f.setSize(170, 100); // 设置初始窗口大小
        f.setBackground(Color.BLUE); // 设置背景颜色
        f.setLocation(300, 300); // 设置初始窗口左上角坐标 默认是(0, 0)
        f.setResizable(false); // 设置不可以改变窗口大小
        f.setVisible(true); // 设置窗口可视
    }
}

// 目前窗口是关不掉的 因为没有做事件处理
import java.awt.*;

public class Main {

    public static void main(String[] args) {

        Frame f1 = new Frame(100, 100, 200, 200, Color.BLUE, "Frame1");
        Frame f2 = new Frame(300, 100, 200, 200, Color.YELLOW, "Frame2");
        Frame f3 = new Frame(100, 300, 200, 200, Color.GREEN, "Frame3");
        Frame f4 = new Frame(300, 300, 200, 200, Color.MAGENTA, "Frame4");
    }
}

class Frame extends java.awt.Frame {

    Frame(int x, int y, int w, int h, Color color, String s) {

        super(s);
        setBackground(color);
        setLayout(null); // 内部布局管理器设为空
        setBounds(x, y, w, h);
        setVisible(true);
    }
}

Panel

  • Panel对象可以看成可容纳Component的空间
  • Panel对象可以拥有自己的布局管理器
  • Panel类拥有从父类继承的方法
  • Panel的构造方法
    • Panel() 使用默认的FlowLayout类布局管理器初始化
    • Panel(LayoutManager layout) 使用指定布局管理器初始化·
import java.awt.*;

public class Main {

    public static void main(String[] args) {

        Frame f = new Frame("Java Frame with Panel");
        Panel p = new Panel(null);

        f.setLayout(null);
        f.setBounds(300, 300, 500, 500);
        f.setBackground(new Color(0, 0, 102));

        p.setBounds(50, 50, 400, 400); // 相对于 Frame 而言
        p.setBackground(new Color(203, 204, 255));

        f.add(p);
        f.setVisible(true);
    }
}
import java.awt.*;

public class Main {

    public static void main(String[] args) {

        new Frame(300, 300, 400, 300, Color.BLUE);
    }
}

class Frame extends java.awt.Frame {

    private Panel p;

    Frame(int x, int y, int w, int h, Color c) {

        super("Frame");
        setLayout(null);
        setBounds(x, y, w, h);
        setBackground(c);

        p = new Panel(null);

        p.setBounds(w/4, h/4, w/2, h/2);
        p.setBackground(Color.YELLOW);

        add(p);

        setVisible(true);
    }
}

布局管理器

  • Java中提供布局管理器类对象可以管理
    • 管理Component在Container中的布局,不必直接设置Component位置和大小
    • 每个Container都有一个布局管理器对象,当容器需要对某个组件进行定位或判断大小尺寸时,就会调用其对应布局管理器,调用Container的setLayout方法改变布局管理器对象

FlowLayout

  • FlowLayout是Panel类默认布局管理器

    • FlowLayout布局管理器对组件逐行定位,行内从左到右,一行排满后换行
    • 不改变组件大小,安组件原有尺寸显示组件,可设置不同组件间距,行距以及对其方法
  • FlowLayout布局管理器默认对齐方法是居中

  • 构造方法

    • new FlowLayout (FlowLayout.RIGHT,20,40);

      右对齐,组件之间水平间距20个像素,垂直间距40个像素

    • new FlowLayout(FlowLayout.LEFT);

      所对齐,水平和垂直间距为缺省值5

    • new FlowLayout();

      居中对齐,水平和垂直间距为5

BorderLayout

  • BorderLayout是Frame类的默认布局管理器
  • BorderLayout将整个容器分为东西南北中五个区域
  • 如不指定组件加入部分,默认加入中区
  • 每个区域只能加一个组件,如果加入多个,先前加入的会被覆盖
  • 尺寸缩放原则
    • 北南两个区域在水平方向缩放
    • 东西两个区域在垂直方向缩放
    • 中部在两个方向上缩放
import java.awt.*;

public class Main {

    public static void main(String[] args) {

        Frame f = new Frame("BorderLayout");

        Button bn = new Button("BN");
        Button bs = new Button("BS");
        Button bw = new Button("BW");
        Button be = new Button("BE");
        Button bc = new Button("BC");

        f.add(bn, BorderLayout.NORTH);
        f.add(bs, BorderLayout.SOUTH);
        f.add(bw, BorderLayout.WEST);
        f.add(be, BorderLayout.EAST);
        f.add(bc, BorderLayout.CENTER);

        f.setSize(200, 200);
        f.setVisible(true);
    }
}

GridLayout

  • GridLayout布局管理器将空间划分为规则矩形网格,每个单元格区域大小相等,组件添加时,从左到右,从上到下

  • GridLayout构造方法中指定分割行数列数

import java.awt.*;

public class Main {

    public static void main(String[] args) {

        Frame f = new Frame("GridLayout");

        Button b1 = new Button("B1");
        Button b2 = new Button("B2");
        Button b3 = new Button("B3");
        Button b4 = new Button("B4");
        Button b5 = new Button("B5");
        Button b6 = new Button("B6");

        f.add(b1);
        f.add(b2);
        f.add(b3);
        f.add(b4);
        f.add(b5);
        f.add(b6);

        f.setLayout(new GridLayout(3, 2));
        f.pack(); // 根据组件调成窗口大小
        f.setVisible(true);
    }
}

事件监听

事件源对象(当事件发生向监听器传送事件对象) -> 实现监听器接口的类对象(接到事件对象后进行处理) -(注册)-> 事件源对象

import java.awt.*;
import java.awt.event.*;

public class Main {

    public static void main(String[] args) {

        Frame f = new Frame("ActionListener");
        Button b1 = new Button("Start");
        Button b2 = new Button("Stop");
        Monitor m = new Monitor();

        b1.addActionListener(m); // 注册
        b2.addActionListener(m); // 注册
        b2.setActionCommand("Over");

        f.add(b1, BorderLayout.NORTH);
        f.add(b2, BorderLayout.CENTER);
        f.pack();
        f.setVisible(true);
    }
}

class Monitor implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("A button has been pressed, the relative info: " + e.getActionCommand());
    }
}

TextField事件监听

  • java.awt.TextField类用于创建文本框对象
import java.awt.*;
import java.awt.event.*;

public class Main {

    public static void main(String[] args) {

        new Frame();
    }
}

class Frame extends java.awt.Frame {

    Frame() {

        TextField tf = new TextField();
        add(tf);
        tf.addActionListener(new ActionListener());
        tf.setEchoChar('*'); // 设置回显字符 一般用于处理密码逻辑
        pack();
        setVisible(true);
    }
}

class ActionListener implements java.awt.event.ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        TextField tf = (TextField)e.getSource();
        System.out.println(tf.getText());
        tf.setText(""); // 清空文本框
    }
}

持有对方引用

简单加法计算器

import java.awt.*;
import java.awt.event.*;

public class Main {

    public static void main(String[] args) {

        new Frame();
    }
}

class Frame extends java.awt.Frame {

    public TextField num1, num2, num3;

    Frame() {
        num1 = new TextField(10);
        num2 = new TextField(10);
        num3 = new TextField(15);

        Label plus = new Label("+");

        Button equal = new Button("=");

        equal.addActionListener(new ActionLister(this));

        setLayout(new FlowLayout());

        add(num1);
        add(plus);
        add(num2);
        add(equal);
        add(num3);

        pack();
        setVisible(true);
    }
}

class ActionLister implements ActionListener {

    private Frame frame;

    ActionLister(Frame frame) {
        this.frame = frame;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        int n1 = Integer.parseInt(frame.num1.getText());
        int n2 = Integer.parseInt(frame.num2.getText());

        frame.num3.setText("" + (n1 + n2));
    }
}

可以用内部类的方法优化上端代码

内部类

当一个类不需要其他类进行访问时采用内部类

内部类可以非常方便的访问包装类的成员变量以及方法,可以更清楚的组织逻辑

内部类编译后,文件名有$符号

import java.awt.*;
import java.awt.event.*;

public class Main {

    public static void main(String[] args) {

        new Frame();
    }
}

class Frame extends java.awt.Frame {

    private TextField num1, num2, num3;

    Frame() {
        num1 = new TextField(10);
        num2 = new TextField(10);
        num3 = new TextField(15);

        Label plus = new Label("+");

        Button equal = new Button("=");

        equal.addActionListener(new ActionLister());

        setLayout(new FlowLayout());

        add(num1);
        add(plus);
        add(num2);
        add(equal);
        add(num3);

        pack();
        setVisible(true);
    }

    private class ActionLister implements ActionListener { // 私有
        @Override
        public void actionPerformed(ActionEvent e) {
            int n1 = Integer.parseInt(num1.getText());
            int n2 = Integer.parseInt(num2.getText());

            num3.setText("" + (n1 + n2));
        }
    }
}

Graphics类

每个Component都有一个paint(Graphics g)用于实现绘画目的,每次重画该Component时都自动调用paint方法

import java.awt.*;
import java.awt.event.*;

public class Main {

    public static void main(String[] args) {

        new Frame();
    }
}

class Frame extends java.awt.Frame {

    Frame() {
        setBounds(200, 200, 640, 480);
        setVisible(true);
    }

    @Override
    public void paint(Graphics g) {
        Color c = g.getColor(); // 得到当前画笔颜色

        g.setColor(Color.red);
        g.fillOval(50, 50, 30, 30);
        g.setColor(Color.GREEN);
        g.fillRect(80, 80, 40, 40);

        g.setColor(c); // 还原画笔颜色
    }
}

鼠标适配器

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {

        new Frame("Draw");
    }
}

class Frame extends java.awt.Frame {

    private ArrayList<Point> points; // 使用泛型

    Frame(String draw) {
        super(draw);

        points = new ArrayList<>();
        setLayout(null);
        setBounds(300, 300, 400, 300);
        setBackground(new Color(204, 204, 255));
        setVisible(true);
        addMouseListener(new Monitor());
    }

    @Override
    public void paint(Graphics g) {

        for (Point p : points) {
            g.setColor(Color.BLUE);
            g.fillOval(p.x, p.y, 10, 10);
        }
    }

    private void addPoint(Point p) {
        points.add(p);
    }

    private class Monitor extends MouseAdapter { // 若采用implements MouseListener 几种方法全要复写

        @Override
        public void mousePressed(MouseEvent e) {
            Frame f = (Frame)e.getSource();
            f.addPoint(new Point(e.getX(), e.getY()));
            f.repaint(); // 对当前画面进行重画
        }
    }
}

Window事件

匿名类

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {

        new Frame("Draw");
    }
}

class Frame extends java.awt.Frame {


    Frame(String draw) {
        super(draw);

        setLayout(null);
        setBounds(300, 300, 400, 300);
        setBackground(new Color(204, 204, 255));
        setVisible(true);

        // 匿名类
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                setVisible(false);
                System.exit(0);
            }
        });
    }
}
import java.awt.*;
import java.awt.event.*;

public class Main {

    public static void main(String[] args) {

        new Frame("Draw");
    }
}

class Frame extends java.awt.Frame {


    Frame(String draw) {
        super(draw);

        TextField tf = new TextField(10);
        Button b = new Button("Start");

        add(b, BorderLayout.NORTH);
        add(tf, BorderLayout.SOUTH);

        b.addActionListener(new ActionListener() {

            private int i;
            @Override
            public void actionPerformed(ActionEvent e) {

                tf.setText(e.getActionCommand() + (++ i));
            }
        });

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        pack();
        setVisible(true);
    }
}

键盘事件

import java.awt.event.*;

public class Main {

    public static void main(String[] args) {

        new Frame("Draw");
    }
}

class Frame extends java.awt.Frame {

    Frame(String draw) {
        super(draw);

        setBounds(200, 200, 300, 300);
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int KeyCode = e.getKeyCode();

                if (KeyCode == KeyEvent.VK_0) {
                    System.out.println("0");
                }
            }
        });

        setVisible(true);
    }
}