Java学习笔记

JAVA:

public class HelloWorld{
    public static void main(String[] args){
        System.out.println("HelloWorld!");
    }
}  

基本:

类名和文件名一致。
一个文件中只能有一个public类。
JAVA严格区分大小写。
申请内存不用释放,java有垃圾存储区。
成员变量(static int a),自动初始化为0。

不要在 public class 类中声明成员变量,那样在 void main 里无法调用,因为 void main 是静态的,不能调用非静态的变量。

类型:

boolean布尔类型:true,false

浮点数默认为double类型,如果要声明一个float类型,浮点数后面必须加f或F,比如:float f = 12.3f;

数据类型转换:

boolean不能转换为其他类型。

byte,short,char -> int -> long -> float -> double
byte,short,char 之间不会互相转换,他们三者在计算时首先转换为int类型。
容量大的数据类型转换为容量小的数据类型时,要加强转。

浮点数默认double,整数默认int
float 后面要加 f或F
long 后面要加 l或L (当超过int所能表示的极限的时候)

char a=1,b=2; //可以!
char c=a+b; //不行! 必须加强转: char c=(char)(a+b); (输出可以,会输出对应数符的ASCII码)

(强转的话,原类型所占内存大小不会变)
float a=(float)1.2 占8个
float a=1.2f 占4个

输出:

System.out.print(“a=” + a); (print不自动换行)
System.out.println(“a=” + a); (println自动换行)

运算符:

“+”还可以做字符串连接符,如:
String s = “hello” + “world”;

“+”运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串然后再进行连接。

当进行打印时,无论任何类型,都自动转换为字符串进行打印

IF-ELSE:

public class Hello{

    public static void main(String[] args){
        int a=30;
        if(a>10)
        {
            System.out.print("Hello world!");
            System.out.println("a=" + a);
        } else if(a<0)
        {
            System.out.println("Hello!");
        } else
            System.out.println("World!");
    }
}

for循环格式:

for(int i=0; i<=10; i++)
{
;
}

for(String name:ArrayName) //每一次从 ArrayName 数组中取一个数赋值给name,直到取完

switch 语句:

JAVA中switch语句只能检测int类型

方法(函数):

public class TestMethod{
    public static void main(String[] args){
        m();
    }
    public static void m(){
        System.out.println("Hello!");
    }
}

关系:

关联:有关系。
继承:XX是一种XX。
聚合:整体和部分,如 球队:队长、队员。(聚集) 人:身体、头、腿、胳膊等(组合(关系密不可分))
实现:喂食宠物:工人、领导等(由谁去实现)

对象:

使用new关键字创建对象
对象 。 成员变量 对象 。 方法

构造方法:

构造方法和类必须名字要完全一样并且没有返回值。

public class Person{
    int id;
    int age = 20;

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

和new一起来使用。
Person tom = new Person(1,25);

当不写任何构造方法,系统自动默认添加空的构造方法。

约定俗成命名规则:

类名的首字母大写。
变量名和方法名的首字母小写。
运用驼峰标识。(fetColor)

向方法中传类:

public class Point{
    double x,y,z;

    Point (double _x, double _y, double _z){
        x = _x;
        y = _y;
        z = _z;
    }

    double Long(Point p){
        double longs;
        longs = (x - p.x) * (x - p.x) + (y - p.y) * (y - p.y) + (z - p.z) * (z - p.z);
        return longs;
    }

    public static void main(String[] args){
        double longs;

        Point p1 = new Point(1.0,2.0,3.0);
        Point p2 = new Point(1.0,1.0,1.0);

        longs = p1.Long(p2);

        System.out.println("longs=" + longs);
    }
}

也可以 : System.out.println ( p1.Long ( new Point(1.0, 2.0, 3.0) ) )

方法的重载:

可以两个方法名一样(前提是返回类型一样,但是传参不一样

this关键字:

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

package包装:

每个代码之前敲  package com.xxxx.aaaa; 标明你的文件的所属(前面是公司域名倒置,后面一般接项目名),避免类调用的混淆。

package相当于类的衣服,每个声明package的class类文件必须放在 执行java当前文件的目录/com/xxxx/aaaa目录下(一个点代表下一个目录)

在其他文件调用类的时候必须把名字写全:com.xxxx.aaaa.Cat c = new com.xxxx.aaaa.Cat();

可以用 import 在开头引入类声明
import com.xxxx.aaaa.Cat;
下面就可以直接用了
Cat c = new Cat();

也可以 import com.xxxx.aaaa.*;(表示这个包下面所有的类都引入进来)

如果执行的java文件与class文件不在当前目录,那么应在环境变量*classpath中加入 当前目录*(com位于当前目录中),即可访问。

执行一个类要写全包名

打包jar包:

在com的上一层目录输入 jar -cvf xxx.jar 的意思是将该目录下所有子目录打包)
别人拿来jar包,就能用里面的类

使用jar包:

在环境变量 classpath 中写入: .;E:\java\2\test.jar
就可直接调用里面的类

权限修饰符:

用来限定其他对象对该类对象成员的访问权限:
类内部 同一个包 子类 任何地方
private √
default √ √
protected √ √ √
public √ √ √ √

这些关键词,一方面可以修饰成员变量,另一方面可以修饰类class(class修饰只能是public或者default,default就是前面什么都不写)

继承:

通过继承,子类自动拥有了基类的所有成员(包括成员变量和方法)。
Java只支持单继承,不允许多继承:
一个子类只能有一个基类,一个基类可以派生出多个子类。

private也会被继承下来。但是不能够去访问它

package m;

public class TestProtecter extends T{   //TestProtecter从T中继承  TestProtecter是一种T,拥有T的所有东西,并可以有自己定义的东西
    public void mthod(){
        System.out.println(k);
    }
}

方法的重写:

子类觉得父类的方法不适合它,可以对方法进行重写。

class people{
    private String name;
    private int age;

    public void setName(String name){
        this.name = name;
    }

    public void setAge(int age){
        this.age = age;
    }

    public String getName(){
        return name;
    }

    public int getAge(){
        return age;
    }

    public String getInfo(){
        return "Name:" + name + "\n" + "age:" + age;
    }
}

class Student extends Person{
    private String school;
    public void setSchool(String school){
        this.school = school;
    }
    public String getSchool(){
        return school;
    }
    public String getInfo(){   //对方法进行重写,新加了本身的school
        return "name:" + name + "\n" + "age:" + age + "\n" + "school:" + school;
    }
}

要注意,重写的方法 传参,名称,返回类型 要全部一致。
重写的方法不能比被重写的方法的权限更严格。

super 关键字:

与this相似,this是对当前对象的引用,而super是对当前对象的父类对象的引用。

class Father{
    public int value;
    public void f(){
        value = 100;
        System.out.println("Father.value=" + value);
    }
}

class Child extends Father{
    public int value;
    public void f(){
        super.f();
        value = 200;
        System.out.println("Child.value=" + value);
        System.out.println(value);
        System.out.println(super.value);
    }
}

public class TestInherit{
    public static void main(String[] args){
        Child cc = new Child();
        cc.f();
    }
}

因为父类完成了一部分功能,子类继承后不想重写这部分功能,就可以用super.f()

继承中的构造方法:

子类的构造的过程中必须调用其父类的构造方法。
子类可以在自己的构造方法中使用super(argument_list) (argument_list表示参数列表)调用父类的构造方法。

使用this(argument_list)调用本类的另外的构造方法。
如果调用super,必须写在子类构造方法的第一行。

如果子类的构造方法中没有显示调用父类构造方法,则系统默认调用父类无参数的构造方法。
如果子类的构造方法中既没有显示调用父类构造方法,而父类中又没有无参数的构造方法,则编译出错。

Object 类:

Object类是所有java类的根基类。

如果在类声明中未使用extends关键字指明其基类,则默认基类为Object类。

Object内置——>toString方法:

Object类中定义有 public String toString() 方法,其返回值是 String 类型,描述当前对象的有关信息。

在进行 String 与其他类型数据的连接操作时( 如:System.out.println(“info” + person) ) ,将自动调用该对象类的 toString() 方法。

可以根据需要在用户自定义类型中重写 toString() 方法。

public class TestToString{
    public static void main(String[] args) {
        Dog d = new Dog();
        System.out.println("d:" + d);   //相当于  System.out.println("d:" + d.toString());
    }
}

class Dog {

}

哈希编码:独一无二的找到一个对象。 每个哈希编码代表了一个对象。

Object内置——>equals方法:

public class TestEquals{
    public static void main(String[] args){
        Cat c1 = new Cat(1, 2, 3);
        Cat c2 = new Cat(1, 2, 3);
        System.out.println(c1 == c2);
    }
}

class Cat{
    private int color;
    private int height, weight;

    public Cat(int color, int height, int weight){
        this.color = color;
        this.height = height;
        this.weight = weight;
    }
}

上述程序打印出来永远是 false 。 c1 == c2 比较的是内存中的地址(两者所指向的东西),永远不一样。

可以重写 equals(比较相等) 方法来实现你想要达到的效果。

public boolean equals(Object obj) {
    return true;  //相等语句永远等于true了
}
    public boolean equals(Object obj){
        if(obj == null) 
            return false;
        else {
            if(obj instanceof Cat){
                Cat c = (Cat)obj;
                if(c.color == this.color && c.height == this.height && c.weight == this.weight)
                    return true;
            }
        }
        return false;
    }
}

看其你定义的成员变量是否一一对应相等,如果相等,返回 true 。

System.out.println(c1.equals(c2));

重写后此条语句为 true 。(但是 c1==c2 仍然为false,因为这是指向的对象是否相等)

一些特殊的类比如 String , Date 等,对 equals方法 进行了重写,使其只比较内容
如:

String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1.equals(s2));

上述程序打印出来为 true 。 (但是 s1==s2 仍然为 false ,因为指向对象不同)

对象转型:

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

Animal a = new Animal("name");
Cat c = new Cat("catname", "blue");
Dog d = new Dog("dogname", "black");

System.out.println(a instanceof Animal); // true a是一个动物
System.out.println(c instanceof Animal); // true c是一个动物
System.out.println(a instanceof Cat); // false a是一只猫

a = new Dog("bigyellow", "yellow");
System.out.println(a.name); // bigyellow
System.out.println(a.furColor);  //错误!!furColor是Dog的独有属性,不能用基类变量a去访问,基类变量a指向子类只能访问其基类独有的内容
System.out.println(a instanceof Animal); // true a是一个动物
System.out.println(a instanceof Dog); // true a是一只狗,因为a指向了狗,相当于将动物a转型成了狗a(不能访问furColor的狗a)

//想访问,要加强转

Dog d1 = (Dog)a;  //声明一个Dog类型d1,将指向Dog类型的Animal类型a强转为Dog类型
System.out.println(d1.furColor); // yellow ,强转后可以访问

对象转型的实例:

public class TestAnimal{
    public static void main(String[] args){
        TestAnimal test = new TestAnimal();
        Animal a = new Animal("name");
        Dog d = new Dog("dogname", "black");
        Cat c = new Cat("catname", "blue");
        test.f(a);
        test.f(c);
        test.f(d);
    }

    public void f(Animal a){
        System.out.println("name:" + a.name);
        if(a instanceof Cat){
            Cat cat = (Cat)a;
            System.out.println("\n" + cat.eyesColor + " eyes");
        }
        else if(a instanceof Dog){
            Dog dog = (Dog)a;
            System.out.println("\n" + dog.furColor + " fur");
        }
    }
}

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

class Dog extends Animal{
    String furColor;
    public Dog(String n, String furColor){
        super(n); 
        this.furColor = furColor;
    }
}

class Cat extends Animal{
    String eyesColor;
    public Cat(String n, String eyesColor){
        super(n);
        this.eyesColor = eyesColor;
    }
}

主要看:

    public void f(Animal a){
        System.out.println("name:" + a.name);
        if(a instanceof Cat){
            Cat cat = (Cat)a;
            System.out.println("\n" + cat.eyesColor + " eyes");
        }
        else if(a instanceof Dog){
            Dog dog = (Dog)a;
            System.out.println("\n" + dog.furColor + " fur");
        }
    }
}

可以传进去一个基类a,可以使基类a指向其子类(传进a的子类),引用子类独有的变量只需要新增加一个子类的类型,然后将基类强转为子类,即可调用(注意,调用时因为是构造类型设置的成员变量,所以成员变量权限不可为 private,可为 default )

如上述程序,用 instanceof 判断传进去的是什么,从而做出不同的反应
(大大减少了方法数量,不然要3个方法,分别为基类和两个子类)

多态:

在执行期间判断所引用对象的实际类型,根据其实际类型调用其相应的方法(子类对方法的重写,不用再对象转换而可以直接调用子类重写方法)。

class Animal {
    private String name;

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

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

class Cat extends Animal {
    private String eyesColor;

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

    public void enjoy(){
        System.out.println("miao~~~");
    }
}

class Dog extends Animal {
    private String furColor;

    Dog(String n, String f){
        super(n);
        furColor = f;
    }

    public void enjoy(){
        System.out.println("woofuuuu!!!");
    }
}

class  Talk{
    private Animal pet;

    Talk(Animal pet){
        this.pet = pet;
    }

    public void PetTalk(){  
        pet.enjoy();  
    }
}

public class TestDuo{
    public static void main(String[] args){
        Dog d = new Dog("Dogname", "black");
        Cat c = new Cat("Catname", "blue");
        Talk dogtalk = new Talk(d);
        Talk cattalk = new Talk(c);

        dogtalk.PetTalk();
        cattalk.PetTalk();
    }
}

只能对于子类重写的方法,子类定义的新方法还是要 对象转换。

抽象类:

abstract关键字来修饰一个类时,这个类叫做抽象类
abstract来修饰一个方法时,这个方法叫做抽象方法

含有抽象方法的类必须被声明为抽象类,抽象类必须被继承,抽象方法必须被重写。

抽象类不能被实例化。

抽象方法只需声明,而不需实现。

abstract class Animal {
    private String name;

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

    public abstract  void enjoy();
}

因为抽象类是残缺的类,所以不能声明这个类的对象。
抽象类只是为其子类可以提供一个重写的方法,方便其多态调用。

Final关键字:(相当于C语言的 const)

final 的变量的值不能够被改变
final 的成员变量
final 的局部变量(实参)

final 的方法不能被重写。
final 的类不能被继承。
(已经是最好的了,到头了,不希望被继承重写了)

public class TestFinal{
    public static void main(String[] args){

    }
}

class T {
    final int i = 8;  //一旦初始化,其他任何人不许改变这个i的值了

    public void M(final int j) {  //j的值传进来不允许在方法之中改变
    }
}

ArrayList 可变数组:

(相当于C语言的链表)

ArrayList myList = new ArrayList();

ArrayList<类型名> 去声明一个可变数组。
add 增加一个元素。 myList.add(a);
remove 移除指定元素。 myList.remove(a);
size 返回数组大小。
contains 查询特定元素。(返回 boolean 类型 true 或 false)
indexOf 查询特定元素位置。 (返回的是游标,比如第二个元素,就返回 1 )
isEmpty 判断集合是否为空。 (返回boolean类型)

接口():(为实现多继承关系)

多个无关的类可以实现同一个接口。
一个类可以实现多个无关的接口。
与继承关系类似,接口与实现类之间存在多态性。

定义java的语法格式:
< modifier> class < name> [extends < superclass>]
[implements < interface> [ , < interface>]* ] {
< declarations>*
}

接口是抽象方法和常量值的定义的集合。
从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含变量和方法的定义,而没有变量和方法的实现。
接口定义举例:

public interface Runner {
    public static final int id = 1;   //声明为 public static final 是怕子类的多个父类里面有相同的成员变量产生冲突

    public void start();
    public void run();
    public void stop();
}

抽象类里方法全是抽象方法,且成员变量全是静态和final这种不可改变的变量。

所有的方法不用写 abstract , 自动定义为 abstract 。
(int id = 1) 前面的 static final 不用写,自动定义为这种状态。

特性:
接口可以多重实现。
接口中的声明默认为 public static final 的,也只能是 public static final 的。
接口中只能定义抽象方法,而且这些方法默认为 public 的,也只能是 public 的。
接口可以继承其他的接口,并添加新的属性和抽象方法。

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

class Student implements Singer {
    private String name;

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

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

    public String getName() {
        return name;
    }

    public void sing() {
        System.out.println("student is singing");
    }

    public void sleep() {
        System.out.println("student is sleeping");
    }
}

implements 相当于继承关系。

如果新加一个接口:

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

那么我可以让一个类同时“继承”两个实现多继承关系。

class Teacher implements Singer,Painter {
    private String name;

    public String getString() {
        return name;
    }

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

    public void teach() {
        System.out.println("teaching");
    }

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

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

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

    public void eat() {
        System.out.println("teacher is eatting");
    }
}

我也可以声明一个接口对象去指向其子类,因多态的关系,可以直接调用子类里面重写的方法。

public class Test {
    public static void main(String[] args) {
        Singer s1 = new Student("le");

        s1.sing();
        s1.sleep();

        Singer s2 = new Teacher("steyen");

        s2.sing();
        s2.sleep();

        Painter p1 = (Painter)s2;  //因为s2类型为Singer指向Teacher,所以只能访问Singer的方法,如果要访问 Painter 的方法,要将s2强转为Painter类型

        p1.paint();
        p1.eat();
    }
}

也可以写:

class A extends B implements C,D {}

继承的同时实现两个接口。

异常处理:

try {
    System.out.println(2/0);
} catch (ArithmeticException a) {   // a 是为这个对象取得名字,随便取
    System.out.println("系统正在维护,请与管理员联系!");
    a.printStackTrace();  //把这个错误的堆栈信息打印出来(与这个错误有关的所有信息)
}

如果出现这种错误的话,就执行catch语句。

public void someMethod()
                    throws SomeException {   //声明如果调用该方法可能出现什么异常
     if (someCondition()) {                  //如果出现这种异常
         throw new SomeException("错误原因");  //构造并抛出异常对象
    }
}

异常错误类型:

Error : 称为错误,由Java虚拟机生成并抛出,包括 动态链接失败、虚拟机错误等,程序对其不做处理。
Exception : 所有异常类的父类,其子类对应了各种各样可能出现的异常事件,一般需要用户显式的声明或捕获。
Runtime Exception : 一类特殊的异常,如被0除、数组下标超范围等,其产生比较频繁,处理麻烦,如果显式的声明或捕获将会对程序可读性和运行效率影响很大。因此由系统自动检测并将它们交给缺省的异常处理程序(用户可不必对其处理)。

void m(int i) throws ArithmeticException {
    if(i==0)
        throw new ArithmeticException("被除数为0");
}

一个try可以跟着多个catch。

如果API文档里一个方法throw了一个错误,则调用该方法必须对异常进行捕捉。

finally语句 :

try{
    //语句1;
    //语句2;
} catch (SomeException1 e){
    ...;
} catch (SomeException2 e){
    ...;
} finally {
    ...;
}

finally语句 无论如何都会执行。
如果语句1报错,则语句1下面的语句不会执行,会直接跳转到 catch 错误的地方,然后执行 finally ,再执行 finally 下面的语句。
如果不报错,则执行完 try 语句中的语句之后, 执行 finally 语句,再执行 finally 下面的语句。

(假如你 语句1 打开一个文件,想在下面语句把文件关上,但是因为 语句1 报错,所以文件永远是打开状态,所以要提供一个一致的出口)
在 finally 写文件关闭语句时,因为文件关闭语句本身还有可能报错,所以可以在 finally 语句里写 try , 然后抓住关闭文件时可能出现的错误。

抛(throws):

处理不了的异常,就往外抛出可能出现的异常:

void f() throws FileNotFoundException, IOException {
    ...;
}

这样就不会报错了。(让别人去处理)

当你调用一个方法的时候,一定要捕获这个方法抛出的异常,一定要去处理(哪怕把错误信息打印出来)。
(System.out.println( a.getMessage() );) ( e.printStackTrace(); )

有一个简单的方法: 直接 throws Exception ,把所有异常抛出。
(除了做测试,main方法不能抛Exception , 应该 try-catch,这是一个习惯)
(能处理的一定要处理,实在处理不了的,再抛出去)

也可以在抛出错误的同时把信息打印出来(用 throw 不是 throws):

void m(int i) throws ArithmeticException {
    if(i==0)
        throw new ArithmeticException("被除数为0");
}

异常有包括性:Exception 包括 IOException 包括 FileNotFoundException 等等(自行查API文档)
应该先捕获小的异常,再捕获大的,可以并列写(IOException,FileNotFoundException )(知道小的不要只写大的,都写出来是一种好习惯)

(不要catch了大的,又在下面catch小的,这样抓住的异常只会执行大的catch)

自定义的异常:

  1. 通过继承 java.lang.Exception 类声明自己的异常类。
  2. 在方法适当的位置 生成自定义异常的实例,并用 throws 抛出。
  3. 在方法的声明部分用 throws 语句声明该方法可能抛出的异常。
class MyException extends Exception {
    private int id;

    public MyException (String message, int id) {
        super(message);
        this.id = id;
    }

    public int getId() {
        return id;
    }
}

如果异常确定,用 throw 抛出异常之后,这个方法的语句到此结束,方法中后面的语句不会执行。
重写的方法抛出的异常必须与基类方法一模一样(不抛异常也可以重写)

数组:

声明:

int[] a;
a = new int[3];
a[0] = 1;
a[1] = 2;
a[2] = 3;  //先分配空间,再赋值

默认声明:

int a[] = new int[5]; //默认数组a里所有的变量全是 0 

a.length 可以指明数组的长度(元素个数)

public class TestArray {
    public static void main(String[] args) {
        for( int i=0; i<args.length; i++) {
            System.out.println( args[i] );
        }
    }
}

传进主函数的 args 数组属于命令行参数,会将你在命令行的输入全部打印出来,比如:
命令行输入: java TestArray 22 33 asa
则打印 22 33 asa(每一个输入之间有回车,也就是说每一个输入占一行)

System.exit(-1);  // -1 表示非正常退出
System.exit(0);   // 0 表示正常退出

简单的计算器程序:

public class TestArray {
    public static void main(String[] args) {
        double d1 = Double.parseDouble(args[0]);
        double d2 = Double.parseDouble(args[2]);
        double d = 0;

        if(args[1].equals("+"))
            d = d1 + d2;
        else if (args[1].equals("-"))
            d = d1 - d2;
        else if (args[1].equals("x"))
            d = d1 * d2;
        else if (args[1].equals("/")) {
            if( d2 == 0 ) {
                System.out.println("Error operator");
                System.exit(-1);
            }
            else
                d = d1 / d2;
        }
        else {
            System.out.println("Error operator");
            System.exit(-1);
        }

        System.out.println(d);
    }
}

Double.parseDouble(String a) 将字符串强转为 double 类型。
类似的还有: Integer.parseInt(String a) (想转为什么类型就去查API文档里的 java.lang)
arg[] 默认是命令行中输入的字符串。

可以: int[] a = new int[args.length]; 输入多长分配多大。
可以在主方法中写一个 private static 不对外公开的方法。

可以临时声明任何类型,比如中间要排序了,可以在 if 语句中写 int temp = a[j];(但是每次都要分配效率不高,还是一开始就分配好比较好)

对对象的排序:

public class TestAry2 {
    public static void main(String[] args) {
        Date[] days = { new Date(2004,5,8), new Date(2001,6,7), new Date(2004,6,9), new Date(2002,4,3), new Date(2001,6,7) };  //声明对象数组
        Date day = new Date(1000,1,1);

        for(int i=days.length-2; i>=0; i--) 
            for(int j=0; j<=i; j++)
                if( days[j].compare(days[j+1]) == 1) {
                    day = days[j];
                    days[j] = days[j+1];
                    days[j+1] = day;
                }

        printf(days);
    }

    private static void printf(Date[] d) {
        for(int i=0; i<5; i++) 
            System.out.println(d[i].year + "," + d[i].month + "," + d[i].day);
    }
}

class Date {
    int year, month, day;

    Date(int y, int m, int d) {
        year = y;
        month = m;
        day = d;
    }

    public int compare(Date date) {
        return year>date.year ? 1
                :year<date.year ? -1
                :month>date.month ? 1
                :month<date.month ? -1
                :day>date.day ? 1
                :day<date.day ? -1:0;   //不推荐这么写,可读性差
    }
}

返回类型可以是数组,且可以不用数组去取用这个返回值,因为数组是引用,所以只要方法中改变数组的值,值就被改变
(字符串不会改,但字符串数组会改)

public class Tess {
    public static void main(String[] args) {
        int[] a = new int[1];

        a[0] = 1;

        System.out.println(a[0]);   // 1

        H(a);   //直接调用方法,不用新 new 一个数组去取

        System.out.println(a[0]);   // 4
    }

    public static int[] H(int[] a) {    //就算返回值为 void ,值也会被改变,因为数组是引用类型
        a[0] = 4;

        return a;
    }
}

围墙问题:

数组模拟算法:

1 2 3 0
0 1 2 3 每一个值存着下一个位置。

面向对象的重要思想! :

public class Count3Quit2 {
    public static void main(String[] args) {
        KidCircle kc = new KidCircle(500);
        int countNum = 0;
        Kid k = kc.first;
        while(kc.count > 1) {
            countNum++;
            if(countNum == 3) {
                countNum = 0;
                kc.delete(k);
            }
            k = k.right;
        }

        System.out.println(kc.first.id);
    }
}

class Kid {
    int id;
    Kid left;
    Kid right;
}

class KidCircle {
    int count = 0;
    Kid first, last;

    KidCircle(int n) {      
        for(int i=0; i<n; i++) {
            add();
        }
    }

    void add() {
        Kid k = new Kid();
        k.id = count;
        if(count <= 0) {
            first = k;
            last = k;
            k.left = k;
            k.right = k;
        } else {
            last.right = k;
            k.left = last;
            k.right = first;
            first.left = k;
            last = k;
        }
        count++;
    }

    void delete (Kid k) {
        if(count <= 0) {
            return;
        } else if(count == 1) {
            first = last = null;
        } else {
            k.left.right = k.right;
            k.right.left = k.left;

            if(k == first) {
                first = k.right;
            }   else if (k == last) {
                last = k.left;
            }
        }
        count--;
    }
}

二维数组:

二维数组的第一个长度是new了几个数组(也就是说二维数组里面保存着 n 个一维数组),第二个长度是那几个一维数组的大小。

遍历方法:

for(int i=0; i<a.length; i++)
    for(int j=0; j<a[i].length; j++);

字符串二维数组:(注意字符串是引用,要在数组里 new 出一个指向字符串的引用)

String[][] s;
s = new String[3][];
s[0] = new String[2];
s[1] = new String[3];
s[2] = new String[2];
for(int i=0; i<s.length; i++)
    for(int j=0; j<s[i].length; j++) {
        s[i][j] = new String("我的位置是:"+ i + "," + j);
    }

arraycopy 拷贝数组:

String[] s = ("Hello","world!");
String[] sBak = new String[2];
System.arraycopy(s,0,sBak,0,s.length);  //从 s 数组的下标为 0 的位置开始,打印s.length个长度,拷贝到从 sBak 数组下标为 0 依次往后的地方
int[][] intArray = {{1,2},{1,2,3},{3,4}};
int[][] intArrayBak = new int[3][];
System.arraycopy(intArray,0,intArrayBak,0,intArray.length); //一维已经标志出有三个数组了,所以直接将二维的每个数组长度拷贝过去
(就相当于从一维数组的第0个位置开始,将每个数组拷贝过去)

intArrayBak[2][1] = 100;   //注意! 因为数组是引用,所以拷贝过去的也是引用(相当于指针),所以Bak改变会将指向地址中的值改变,                  intArray[2][1]打印出来也是100

常用类:

String:

String a = "Hello";
String b = "Hello";
a==b;  // true

因为没有新new出来一个对象,所以 Hello 存放在数据区,当再声明一个字符串类型 b = “Hello” 的时候,因为数据区已经有了一个完全相同的字符串,所以直接将 b 指向了 a 所指向的字符串,所以 a==b 是对的。

当用equals的时候,查API看看该调用有没有重写equals,String重写了equals使得调用时是看两字符串是否一致。

char c[] = {'s','u','n',' ','j','a','v','a'};
String s4 = new String(c);  //将c转换成字符串保存在s4中
String s5 = new String(c,4,4);  //从c的第4个位置开始向后拷贝4个字符,c中就算之后字符改变,new出来的String也不会变(完完全全的拷贝)

System.out.println(s4); //sun java
System.out.println(s5); //java
String类常用方法: (字符串如果在方法中改,本身是不会变的)(但字符串数组如果传进去是会变的)

(1):
public char charAt (int index)
返回字符串中第index位置(游标)上的字符
public int length()
返回字符串的长度
public int indexOf (String str)
返回字符串中出现str的第一个位置(游标) (如果没有这个字符串,则返回-1)
public int indexOf (String str, int fromIndex)
返回字符串中从fromIndex(游标位置)开始出现str的第一个位置(游标)
public boolean equalsIgnoreCase (String another)
比较字符串与another是否一样(忽略大小写)
public String replace (char oldChar, char newChar)
在字符串中用newChar字符替换oldChar字符

(2):
public boolean startWith (String prefix)
判断字符串是否以 prefix 字符串开头
public boolean endsWith (String prefix)
判断字符串是否以 prefix 字符串结尾
public String toUpperCase()
返回一个字符串为该字符串的大写形式 // String s = “We are Family”; String sU = s.toUpperCase(); 打印后:WE ARE FAMILY
public String toLowerCase() // String s = “We are Family”; String sL = s.toLowerCase(); 打印后:we are family
返回一个字符串为该字符串的小写形式
public String substring(int beginIndex) // String s = “We are Family”; String subs = s.substring(1) 打印后:e are Family
返回该字符串从beginIndex(游标位置)开始到结尾的字符串
public String substring(int beginIndex, int endIndex)
返回该字符串从beginIndex(游标位置)开始到endIndex(游标位置)结尾的子字符串
public String trim()
返回该字符串去掉开头和结尾空格后的字符串

(3):
静态重载方法:

public static String valueOf(index) 可以将基本数据类型转换成字符串

int j = 1234567;
String sNumber = String.valueOf(i);
System.out.println(sNumber.length());   //可以直接打印出 j 是几位数

public String[] split (String regex) 可以将一个字符串按照指定的分隔符分隔,返回分隔后的字符串数组

String s = "Mary,F,1976";
String[] sPlit = s.split(",");   //将 "s" 从 "," 断开成一个String数组
for (int i=0; i<sPlit.length; i++) {
    System.out.println(sPlit[i]);     //打印出来第一个是 Mary  第二个是 F  第三个是 1976
}

StringBuffer类:

可变的字符序列。
StringBuffer 和String类似,但StringBuffer可以对其字符串进行改变。

StringBuffer常见构造方法:
StringBuffer()
创建一个不包含字符序列的“空”的StringBuffer对象。
StringBuffer(String str)
创建一个StringBuffer对象,包含与String对象str相同的序列。

String s1 = "Hello";
String s2 = "world!";
s1 += s2;

这个的意思是重新开辟一块s1+s2大小的空间,将s1+s2的值放进去,然后让s1指向这块空间。
而StringBuffer会自己开辟新的空间。(可变)

StringBuffer常用方法:

(1):
重载方法 public StringBuffer append(…) 可以为该 StringBuffer 对象添加字符序列,返回添加后的该 StringBuffer 对象引用,例如:
public StringBuffer append(String str)
public StringBuffer append(StringBuffer sbuf)
public StringBuffer append(char[] str)
public StringBuffer append(char[] str, int offset, int len)
public StringBuffer append(double d)
public StringBuffer append(Object obj)

(2):
重载方法 public StringBuffer insert(…) 可以为该 StringBuffer 对象在指定位置插入字符序列,返回修改后的该 StringBuffer 对象引用,例如:
public StringBuffer insert(int offset, String str)
public StringBuffer insert(int offset, double d)

方法 public StringBuffer delete(int start, int end) 可以删除从 start (游标)开始到 end-1 (第几个数)为止的一段字符序列,返回修改后的该 StringBuffer 对象引用。

(3):
和 String 类含义类似的方法:
public int indexOf (String str)
public int indexOf (String str, int fromIndex)
public String substring (int start)
public String substring (int start, int end)
public int length()

方法 public StringBuffer reverse() 用于将字符串逆序,返回修改后的该 StringBuffer 对象引用。

例:

public class Tesa {
    public static void main(String[] args) {
        String s = "Mircosoft";
        char[] a = {'a', 'b', 'c'};
        StringBuffer sb1 = new StringBuffer(s);

        sb1.append('/').append("IBM").append('/').append("Sun");

        System.out.println(sb1);

        StringBuffer sb2 = new StringBuffer("NUM");

        for(int i=0; i<=9; i++) {
            sb2.append(i);
        }

        System.out.println(sb2);

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

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

可以 append().delete().insert().reverse() 连起来用。

基本数据类型包装类:

包装类(如 Integer , Double 等)这些类封装了一个相应的基本数据类型数值,并为其提供了一系列操作。
以java.lang.Integer类为例,构造方法:
Integer(int value)
Integer(String s)

常见方法:
public static final int MAX_VALUE 最大的int型数 (2的第31次方-1)
public static final int MIN_VALUE 最小的int型数 (-231)
public long longValue() 返回封装数据的long型值
public double doubleValue() 返回封装数据的double型值
public int intValue() 返回封装数据的int型值
public static int parseInt(String s) throw NumberFormatException
将字符串解析成int型数据,返回该数据
public static Integer valueOf(String s) throw NumberFormatException
返回Integer对象,其中封装的整型数据为字符串s所表示。

例:一串字符串序列分割数字保存在double二维数组中:

public class Practise2 {
    public static void main(String[] args) {
        String s = "1,2;3,4,5;6,7,8";
        double[][] d = new double[3][];
        d[0] = new double[2];
        d[1] = new double[3];
        d[2] = new double[3];

        Lop(s,d);

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

    public static void Lop(String s, double[][] d) {
        String[] a = s.split("[;,,]");  //多重分割符  [x,y],分割x和y
        int k=0;

        for(int i=0; i<d.length; i++) {
            for(int j=0; j<d[i].length; j++) {
                d[i][j] = Double.valueOf(a[k]);
                k++;
            }
        }
    }
}

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存储了当前系统的路径分隔符。

常用方法:

通过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() throw IOException
public boolean delete()
public boolean mkdir()
public boolean mkdirs() //创建在路径中的一系列目录

打印所有子文件夹和子文件(递归):

import java.io.*;

public class FileList {
    public static void main(String[] args) {
        File f = new File("e:/A");
        Tree(f);
    }

    private static void Tree (File f) {
        File[] childs = f.listFiles();   // listFiles 是 f 的所有子目录
        for(int i=0; i<childs.length; i++) {    
            System.out.println(childs[i].getName());   //打印出所有子目录和文件的名字
            if(childs[i].isDirectory()) {   //如果当前打印的是目录的话
                Tree(childs[i]);            //递归打印子目录下的所有文件和目录
            }
        }
    }
}

容器:

(一个图,一个类,3个知识点,6个接口)

容器API位于 java.util 包里面。

图:
图

Collection 接口—–定义了存取一组对象的方法,其子接口Set和List分别定义了存储方式。

set:数据对象没有顺序并且不可以重复。
list:数据对象有顺序且可以重复。

Map 接口—–定义了存储“键(key) — 值(value)映射对”的方法。

Collection 举例

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Collection c = new ArrayList();    //可以放入不同类型的对象

        c.add("hello");
        c.add( new Name("f1","l1") );
        c.add( new Integer(100) );

        System.out.println( c.size() );
        System.out.println(c);  
    }
}

//输出结果:3
           [hello, f1 l1, 100]

c.add(); 只能添加对象,不能添加基本的数据类型。

Iterator 接口:

所有实现了 Collection 接口的容器类都有一个 iterator 方法用以返回一个实现了 Iterator 接口的对象。
Iterator 对象称作迭代器,用以方便的实现对容器内元素的遍历操作。
(提供的方法可以删除左元素,查询右边是否有元素,并将右元素拿出并游标后移)

方法:
boolean hasNext(); // 判断游标右边是否有元素
Object next(); // 返回游标右边的元素并将游标移动到下一个位置
void remove(); // 删除游标左边的元素,在执行完next之后该操作只能
执行一次

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Collection c = new HashSet();

        c.add( new Name("f1", "l1") );
        c.add( new Name("f2", "l2") );
        c.add( new Name("f3", "l3") );

        Iterator i = c.iterator();

        while( i.hasNext() ) {     
        // next()的返回值为 Object 类型,需要转换为相应类型
            Name n = (Name)i.next();
            System.out.println(n.getFirstName + " ");
        }
    }
}
Collection c = new HashSet();

c.add( new Name("fff1", "lll1") );
c.add( new Name("f2", "l2") );
c.add( new Name("fff3", "lll3") );

for(Iterator i = c.iterator(); i.hasNext(); ) {
    Name name = (Name)i.next();
    if(name.getFirstName().length()<3) {
        i.remove();
        // 如果换成 c.remove(name); 会产生例外
    }
}

System.out.println( c );

增强的for循环:

import java.util.*;

public class EnhancedFor {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};

        for(int i : arr)
            System.out.println( i );

        Collection c = new ArrayList();

        c.add( new String("aaa") );
        c.add( new String("bbb") );
        c.add( new String("ccc") );

        for(Object o : c)   
            System.out.println( o );
    }
}

将冒号右边的数组里的每一个值拿出来赋给冒号左边的变量。

增强的for循环对于遍历 array 或 Collection 的时候相当简便。

缺陷:
数组:不能方便的访问下标值。
集合:与使用 Iterator 相比,不能方便的删除集合中的内容(在内部也是调用Iterator)

总结:
除了简单遍历并读出其中内容外,不建议使用增强for循环。

Set接口:

Set接口是Collection接口的子接口,Set接口没有提供额外的方法,但实现Set接口的容器类中的元素是没有顺序的,而且不可以重复。

Set容器可以与数学中“集合”的概念相对应。

Set容器类有 HashSet , TreeSet 等。

public static void main(String[] args) {
    Set s = new HashSet();

    s.add( "hello" );
    s.add( "world" );
    s.add( new Name("f1", "f2") );
    s.add( new Integer(100) );
    s.add( new Name("f1", "f2") );     // 相同元素不会被加入
    s.add( "hello" );                  // 相同元素不会被加入

    System.out.println( s );
}

// 输出结果: [100, hello, world, f1 f2]
public static void main(String[] args) {
    Set s1 = new HashSet();
    Set s2 = new HashSet();

    s1.add("a");
    s1.add("b");
    s1.add("c");

    s2.add("d");
    s2.add("a");
    s2.add("b");

    // Set和List容器类都具有Constructor(Collection c)构造方法
       用以初始化容器类

    Set sn = new HashSet(s1);

    sn.retainAll(s2);      // 取得两个对象的交集  

    Set su = new HashSet(s1);

    su.addAll(s2);         // 所有元素加进去,共同的元素就不加了

    System.out.println(sn);  // [a, b]
    System.out.println(su);  // [d, a, c, b]
}

List接口:

List接口是Collection的子接口,实现List接口的容器类中的元素是有顺序的,而且可以重复的。
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
List容器类有ArrayList , LinkedList等。

方法:
Object get (int index)
Object set (int index, Object element)
void add (int index, Object element)
Object remove (int index)
int indexOf (Object o)
int lastIndexOf (Object o)

List l1 = new LinkedList();

for(int i=0; i<=5; i++) {
    l1.add("a" + i);
}

System.out.println(l1);      // [a0, a1, a2, a3, a4, a5]

l1.add(3, "a100");
System.out.println(l1);      // [a0, a1, a2, a100, a3, a4, a5]

l1.set(6, "a200");
System.out.println(l1);      // [a0, a1, a2, a100, a3, a4, a200]

System.out.println( (String)l1.get(2) + " " );       // a2
System.out.println( l1.indexOf("a3") );              // 4

l1.remove(1);
System.out.println(l1);      // [a0, a2, a100, a3, a4, a200]

类 java.util.Collections 提供了一些静态方法实现了基于List容器的一些常用算法。

void soft (List) 对容器内的元素排序
void shuffle(List) 对List容器内的对象进行随机排列
void reverse (List) 对List容器内的对象进行逆序排列
void fill (List, Object) 用一个特定的对象重写整个List容器
void copy (List dest, List src) 将 src List 容器内容拷贝到 dest List 容器
int binarySearch (List, Object) 对于顺序的List容器,采用折半查找的方法查找
特定对象

List l1 = new LinkedList();
List l2 = new LinkedList();

for(int i=0; i<=9; i++) {
    l1.add("a" + i);
}
System.out.println(l1);  // [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]

Collections.shuffle(l1); // 随机排列
System.out.println(l1);  // [a1, a3, a8, a9, a4, a6, a5, a2, a0, a7]

Collections.reverse(l1); // 逆序
System.out.println(l1);  // [a7, a0, a2, a5, a6, a4, a9, a8, a3, a1]

Collections.sort(l1);    // 排序
System.out.println(l1);  // [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]

System.out.println( Collections.binarySearch(l1, "a5") ); // 折半查找
// 5

Comparable接口:

问题:上面的算法根据什么确定容器中对象的“大小”顺序?
所有可以“排序”的类都实现了 java.util.Comparable 接口,Comparable 接口中只有一个方法: public int compareTo (Object o);
该方法:
返回 0 表示 this == obj
返回 正数 表示 this > obj
返回 负数 表示 this < obj

实现了 Comparable 接口的类通过实现 compareTo 方法从而确定该类对象的排序方式。

class Name implements Comparable {
    … … …
    public int compareTo (Object o) {
        Name n = (Name)o;
        int lastCmp = lastName.compareTo(n.lastName);
        return ( lastCmp != 0 ? 
                    lastCmp : firstName.compareTo(n.firstName) )
    }
}

如何选择数据结构:

衡量标准:读的效率和改的效率。
Array 读快改慢。
Linked 改快读慢。
Hash 两者之间。(有一些table类效率极低,不推荐用)
(一般用 Set, List, Map)

Map接口:

实现 Map 接口的类用来存储 键—值 对。
Map 接口的实现类有 HashMap 和 TreeMap 等。
Map 类中存储的 键—值 对 通过键来标识,所以键值不能重复。
( Key 和 Value 必须都是对象)(1.5之后可以不是对象,详见Auto-boxing)

方法:
Object put (Object key, Object value);
传进去的value替换掉本身的value,本身的value作为返回值返回
Object get (Object key);
Object remove (Object key);
boolean containsKey (Object key);
boolean containsValue (Object value);
int size();
boolean isEmpty();
void putAll (Map t);
void clear();

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Map m1 = new HashMap();
        Map m2 = new TreeMap();

        m1.put("one", new Integer(1));
        // 添加键—值对 one—new Integer(1)
        m1.put("two", new Integer(2));
        m1.put("three", new Integer(3));

        m2.put("A", new Integer(1));
        m2.put("B", new Integer(2));

        System.out.println( m1.size() );   // 3
        System.out.println( m1.containsKey("one") );  // true
        System.out.println( m2.containsValue( new Integer(1) ) );
        // true

        if( m1.containsKey("two") ) {
            int i = ( (Integer)m1.get("two") ).intValue();
            // m1.get()获得"two"所对应的value为Object类型
               强转为Integer对象,再调用intValue()方法转为int基本类型
            System.out.println(i);
        }

        Map m3 = new HashMap(m1);  // 将m1拷贝到m3中

        m3.putAll(m2);             // 将m2添加到m3中

        System.out.println(m3);  
        // {A=1, B=2, two=2, three=3, one=1}
    }
}

Auto-boxing/unboxing:

在合适的时机自动打包、解包。
——自动将基础类型转换为对象
——自动将对象转换为基础类型

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Map m1 = new HashMap();
        Map m2 = new TreeMap();

        m1.put("one", 1);
        m1.put("two", 2);
        m1.put("three", 3);

        m2.put("A", 1);
        m2.put("B", 2);

        System.out.println( m1.size() );   // 3
        System.out.println( m1.containsKey("one") );  // true
        System.out.println( m2.containsValue( 1 ) );
        // true

        if( m1.containsKey("two") ) {
            int i = (Integer)m1.get("two");
            // 将Integer对象自动解包为int基本类型
            System.out.println(i);
        }

        Map m3 = new HashMap(m1); 

        m3.putAll(m2);             

        System.out.println(m3);
    }
}

泛型:

在定义集合的时候同时定义集合中对象的类型。 <类型(对象)>
——可以在定义Collection的时候指定
——也可以在循环时用Iterator指定
(增强程序的可读性和稳定性)

import java.util.*;

public class BasicG {
    public static void main(String[] args) {
        List<String> c = new ArrayList<String>();

        c.add("aaa");
        c.add("bbb");
        c.add("ccc");

        for(int i = 0; i < c.size(); i++) {
            String s = c.get(i);
            System.out.println(s);
        }

        Collection<String> c2 = new HashSet<String>();

        c2.add("aaa");
        c2.add("bbb");
        c2.add("ccc");

        for(Iterator<String> it = c2.iterator(); it.hasNext()) {
            String s = it.next();
            System.out.println(s);
        }
    }
}

class MyName implements Comparable<MyName> {
    int age;

    public int compareTo(MyName mn) {
        if(this.age > mn.age)
            return 1;
        else if(this.age < mn.age)
            return -1;
        else
            return 0;
    }
}

IO:

流:

流

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

输入/输出流的分类: (站在程序的角度上来说)

java.io 包中定义了多个流类型(类或抽象类)来实现输入/输出功能;可以从不同角度对其进行分类:
——按数据流的方向不同可以分为输入流和输出流。
——按处理数据单位不同可以分为字节流和字符流。
——按照功能不同可以分为节点流和处理流。

JDK所提供的所有流类型位于包 java.io 内都分别继承自以下四种抽象流类型。
字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

节点流和处理流:

节点流可以从一个特定的数据源(节点)读写数据(如:文件,内存)。
节点流

处理流是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
处理流

InputStream:

继承自 InputStream 的流都是用于向程序中输入数据,且数据的单位为字节(8bit);
下图深色为节点流,浅色为处理流。
输入流

基本方法:
// 读取一个字节并以整数的形式返回(0~255)
// 如果返回 -1 已到输入流的末尾
int read() throws IOException

// 读取一系列字节并存储到一个数组 buffer
// 返回实际读取的字节数,如果读取前已到输入流的末尾则返回 -1
int read(byte[] buffer) throws IOException

// 读取 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 bit);
下图深色为节点流,浅色为处理流。
输出流

基本方法:
// 向输出流中写入一个字节数据,该字节数据为参数b的低8位
void write(int b) throws IOException

// 将一个字节类型的数组中的数据写入输出流
void write(byte[] b) throws IOException

// 将一个字节类型的数组中从指定位置(off)开始
// len 个字节写入到输出流
void write(byte[] b, int off, int len) throws IOException

// 关闭流释放内存资源
void close() throws IOException

// 将输出流中缓冲的数据全部写出到目的地
void flush() throws IOException

(良好的编程习惯:先 flush 再 close)
(可能缓冲区里还有东西,但是直接关闭了)

Reader:

继承自 Reader 的流都是用于向程序中输入数据,且数据的单位为字符(16bit)(两个字节);
下图深色为节点流,浅色为处理流。
字符输入流

基本方法:
// 读取一个字符并以整数的形式返回(0~255)
// 如果返回 -1 已到输入流的末尾
int read() throws IOException

// 读取一系列字符并存储到一个数组 buffer
// 返回实际读取的字符数,如果读取前已到输入流的末尾则返回 -1
int read(char[] cbuf) throws IOException

// 读取 length 个字符
// 并存储到一个数组 buffer ,从 offset 位置开始
// 返回实际读取的字符数,如果读取前已到输入流的末尾则返回 -1
int read(char[] cbuf, int offset, int length) throws IOException

// 关闭流释放内存资源
void close() throws IOException

// 跳过 n 个字符不读,返回实际跳过的字符数 (所用极少)
long skip(long n) throws IOException

Writer:

继承自 Writer 的流是用于程序中输出数据,且数据的单位为字符(16 bit)(两个字节);
下图深色为节点流,浅色为处理流。
字符输出流

基本方法:
// 向输出流中写入一个字符数据,该字节数据为参数b的低16位
void write(int c) throws IOException

// 将一个字符类型的数组中的数据写入输出流
void write(char[] cbuf) throws IOException

// 将一个字符类型的数组中从指定位置(off)开始
// len 个字符写入到输出流
void write(char[] cbuf, int off, int len) throws IOException

// 将一个字符串中的字符写入到输出流
void write(String string) throws IOException

// 将一个字符串从 offset 开始的 length 个字符写入到输出流
void write(String string, int offset, int length) throws IOException

// 关闭流释放内存资源
void close() throws IOException

// 将输出流中缓冲的数据全部写出到目的地
void flush() throws IOException

节点流类型:

节点流类型

从文件中读取:
FileInputStream:

import java.io.*;

public class FIOInputStream {
    public static void main(String[] args) {
        int b = 0;
        FileInputStream in = null;

        try {
            in = new FileInputStream("e:/java/8/HW.java");
        } catch(FileNotFoundException e) {
            System.out.println("Not found this file."); 
            System.exit(-1);
        }

        try {
            long num = 0;

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

            in.close();

            System.out.println();
            System.out.println("read: " + num );
        } catch (IOException e1) {
            System.out.println("File error!");
            System.exit(-1);
        }
    }
}

FileReader(可以读中文):

import java.io.*;

public class FR {
    public static void main(String[] args) {
        FileReader fr = null;
        int b = 0;

        try {
            fr = new FileReader("e:/java/8/Hello.java");
        } catch (FileNotFoundException e) {
            System.out.println("Not found file.");
            System.exit(-1);
        }

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

            fr.close();
        } catch (IOException e1) {
            System.out.println();
            System.out.println("error");
        }

    }
}

写入一个文件(若没有会自动创建):
FileOutputStream:

import java.io.*;

public class FIOOutputStream {
    public static void main(String[] args) {
        int b = 0;
        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("e:/java/8/Hello.java");
            out = new FileOutputStream("e:/java/8/HW.java");

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

            in.close();
            out.close();
        } catch (FileNotFoundException e2) {
            System.out.println("Not found file.");
            System.exit(-1);
        } catch (IOException e1) {
            System.out.println("File copy error!");
            System.exit(-1);
        }

        System.out.println("Copy success!");
    }
}

FileWriter(可以写中文):

import java.io.*;

public class FW {
    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;
        int b = 0;

        try {
            fr = new FileReader("e:/java/8/Hello.java");
        } catch (FileNotFoundException e) {
            System.out.println("Not found file.");
            System.exit(-1);
        } 

        try {
            fw = new FileWriter("e:/java/8/HWD.java");

            while ( (b = fr.read()) != -1 ) {
                fw.write( (char)b );
            }

            fr.close();
            fw.close();
        } catch(IOException e1) {
            System.out.println("error!");
            System.exit(-1);
        }

        System.out.println("success!");
    }
}

从文件中 读取 在 定义新对象 的时候应该抓 FileNotFoundException
从文件中 写入 在 定义新对象 的时候应该抓 IOException
(其他操作比如 .read .write 都要抓 IOException)

处理流类型:

处理流类型

缓冲流:

缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。
JDK提供了四种缓冲流,其常用的构造方法为:
BufferedReader (Reader in)
BufferedReader (Reader in, int sz) // sz 为自定义缓存区的大小
BufferedWriter (Writer out)
BufferedWriter (Writer out, int sz)
BufferedInputStream (InputStream in)
BufferedInputStream (InputStream in, int size)
BufferedOutputStream (OutputStream out)
BufferedOutputStream (OutputStream out, int size)

——缓冲输入流支持其父类的 mark 和 reset 方法。
——BufferedReader 提供了 readLine 方法用于读取一行字符串 (以 \r 或 \n 分隔)
——BufferedWriter 提供了 newLine 用于写入一个行分隔符。
——对于输出的缓冲流,写出的数据会先在内存中缓存,使用 flush 方法将会使内从中的数据立刻写出。

读取文件数据:

import java.io.*;

public class BFR {
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("e:/java/8/Hello.java");
            BufferedInputStream bis = new BufferedInputStream(fis);
            int c = 0;

            System.out.println( bis.read() );
            System.out.println( bis.read() );

            bis.mark(100);   // Start from 100 byte.

            for(int i = 0; i<=10 && ( c = bis.read() ) != -1; i++ ) {
                System.out.print((char)c + " ");
            }

            System.out.println();

            bis.reset();  // return from 100 byte.

            for(int i = 0; i<=10 && ( c = bis.read() ) != -1; i++ ) {
                System.out.print((char)c + " ");
            }

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

读取随机数并写入一个文件内,写入之后从文件读取:

import java.io.*;

public class BFW {
    public static void main(String[] args) {
        try {
            BufferedWriter bw = new BufferedWriter( new FileWriter("e:/java/8/HW.java") );
            BufferedReader br = new BufferedReader( new FileReader("e:/java/8/HW.java") );
            String s = null;

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

            bw.flush();

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

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

转换流:

InputStreamReader 和 OutputStreamWriter 用于字节数据到字符数据之间的转换。
InputStreamReader 需要和 InputStream “套接”
OutputStreamWriter 需要和 OutputStream “套接”
转换流在构造时可以指定其编码集合,例如:
InputStream isr = new InputStreamReader( System.in, “ISO8859_1” );

import java.io.*;

public class TestTrans {
    public static void main(String[] args) {
        try {
            OutputStreamWriter osw = new OutputStreamWriter (
                new FileOutputStream("e:/java/HW.txt") );

            osw.write("Hello");

            System.out.println( osw.getEncoding() );   // 打印编码方式

            osw.close();

            osw = new OutputStreamWriter(
                new FileOutputStream("e:/java/HW.txt", true) , "ISO8859_1");
            // true 的意思是追加,如果没有 true 则默认重写,原来的数据丢失
            // ISO8859_1 改变编码方式

            osw.write(" World!");

            System.out.println( osw.getEncoding() );

            osw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;

public class TestTrans2 {
    public static void main(String[] args) {
        InputStreamReader isr = new InputStreamReader(System.in); 
        // System.in 的意思是读取键盘输入
        BufferedReader br = new BufferedReader(isr); // 再套一层缓冲流
        String s = null;

        try {
            s = br.readLine(); // 读一行

            while( s != null ) {
                if( s.equalsIgnoreCase("exit") ) break; 
                // 忽略大小写如果输入是exit,则退出循环结束
                System.out.println( s.toUpperCase() ); // 转换为大写输出
                s = br.readLine(); // 读下一行
            }

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

数据流:

DataInputStream 和 DateOutputStream 分别继承自 InputStream 和 OutputStream ,它属于处理流,需要分别“套接”在 InputStream 和 OutputStream 类型的节点流上。

DataInputStream 和 DataOutputStream 提供了可以存取与机器无关的 Java 原始类型数据(如:int,double等)的方法。

DataInputStream 和 DataOutputStream 的构造方法为:
DataInputStream ( InputStream in )
DataOutputStream ( OutputStream out )

import java.io.*;

public class TestDS {
    public static void main(String[] args) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);

        try {
            dos.writeDouble(Math.random());
            dos.writeBoolean(true);

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

            System.out.println(bais.available());

            DataInputStream dis = new DataInputStream(bais);

            System.out.println(dis.readDouble());
            System.out.println(dis.readBoolean());

            dos.close();
            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

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

import java.io.*;

public class TestP {
    public static void main(String[] args) {
        PrintStream ps = null;

        try {
            FileOutputStream fos = new FileOutputStream("e:/java/10/log.dat");
            ps = new PrintStream(fos);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (ps != null) {
            System.setOut(ps);
        }

        int ln = 0;

        for(char c = 0; c <= 60000; c++) {
            System.out.println(c + " ");
            if (ln++ >= 100) {
                System.out.println();
                ln = 0;
            }
        }
    }
}                           
import java.io.*;

public class TestP2 {
    public static void main(String[] args) {
        String filename = args[0];

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

    public static void list(String f, PrintStream fs) {
        try {
            BufferedReader br = new BufferedReader(new FileReader(f));
            String s = null;

            while ( (s = br.readLine()) != null ) {
                fs.println(s);
            }

            br.close();
        } catch (IOException e) {
            fs.println("Not read this file.");
        }
    }
}
import java.io.*;
import java.util.*;

public class TestP3 {
    public static void main(String[] args) {
        String s = null;
        BufferedReader br = new BufferedReader( new InputStreamReader(System.in) );

        try {
            FileWriter fw = new FileWriter("e:/java/logfile.log", true);
            PrintWriter log = new PrintWriter(fw);

            while ( (s = br.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 流:

直接将 Object 写入或读出

transient 关键字 :透明的,修饰成员变量,所修饰的成员变量在序列化时不被考虑

serializable 接口 :标记性接口(没有可被重写的方法),表明该类可以被序列化。 想把某个类的对象序列化,必须实现这个接口。

externalizable 接口 (基本不用) :自己控制序列化过程。

import java.io.*;


public class TestOJ {
    public static void main(String[] args) throws Exception {
        T t = new T();
        t.k = 8;

        FileOutputStream fos = new FileOutputStream("e:/java/10/testobjectio.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeObject(t);
        oos.flush();
        oos.close();


        FileInputStream fis = new FileInputStream("e:/java/10/testobjectio.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        T tReaded = (T) ois.readObject();

        System.out.println(tReaded.i + " " + tReaded.j + " " + tReaded.d + " " + tReaded.k);
    }
}

class T implements Serializable {
    int i = 10;
    int j = 9;
    double d = 2.3;
    transient int k = 15;
}

多线程机制:

线程是一个程序里面不同的执行路径

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

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

    public static void m2() {}
    public static void m3() {}
}

每一个分支都叫一个线程。
m1在得到m2的返回值后继续往下执行m3,一条线。

(进程 是一个静态的概念,机器上的一个 class文件,一个exe文件,叫一个进程)
(当代码放入代码区后,还没有开始执行,说明进程已经产生,准备执行)

进程的执行:进程里面主线程开始执行。
机器里面实际上运行的都是线程
在同一个时间点上,一个CPU,只能支持一个线程的执行。
(多线程是因为CPU分成了多个时间点,先执行A,再执行B,来回切换,因为执行速度太快,所以感觉是在同时执行)

Java的线程是通过 java.lang.Thread 类实现的
VM(Java虚拟机)启动时会有一个由主方法(public static void main() {})所定义的线程
可以通过创建 Thread 的实例来创建新的线程
每个线程都是通过某个特定 Thread 对象所对应的方法 run() 来完成其操作的,方法 run() 称为线程体
通过调用 Thread类 的 start() 方法来启动一个线程

可以有两种方法创建新的线程
一:
定义线程实现 Runnable接口
Thread t = new Thread (r) // r 为 Runnable 类型
Runnable 只有一个方法:public void run() 用来定义线程运行体
使用 Runnable接口可以为多个线程提供共享的数据
在实现 Runnable接口的类的 run方法定义中可以使用 Thread的静态方法:
public static Thread currentThread() 获取当前线程的引用

二:
可以定义一个 Thread 的子类并重写其 run() 方法如:

class T extends Thread {
    public void run() {...}
}

然后生成该类的对象:
T t = new T();

public class TestT1 {
    public static void main(String[] args) {
        Runner1 r = new Runner1();
        r.run();
        Thread t = new Thread(r);
        //t.start();

        for(int i=0; i<100; i++) {
            System.out.println("Main Thread:------" + i);
        }
    }
}

class Runner1 implements Runnable {
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println("Runner1 :" + i);
        }
    }
}

直接调用 run 方法是挨个执行。
而用 start 方法则是并行执行(A,B来回切换执行,时间过短,所以看起来是同时进行) 。

推荐用接口来实现,比较灵活,还可以继承和实现其他接口(继承就不能实现其他了)。

线程状态转换:

线程状态转换
如果遇到阻塞情况,则要等阻塞解除,才能继续运行或直接终止。

线程控制基本方法:

线程控制基本方法
每个线程有自己的优先级,优先级越高的线程,CPU所分配的时间越多。

import java.util.*;

public class TestIn {
    public static void main(String[] args) {
        MyThread thread = new MyThread();

        thread.start();

        try {
            Thread.sleep(10000);
            thread.interrupt();
        } catch (InterruptedException e) {
            thread.interrupt();
        }
    }
}

class MyThread extends Thread {
    boolean flag = true;

    public void run() {
        while(flag) {
            System.out.println("===" + new Date() + "===");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                flag = false;
            }
        }
    }
}

程序:每隔一秒打印一次日期,10秒之后停止。

重写的方法不能抛出比被重写的方法所没有的异常。
在哪里调用的 sleep,就是让哪个线程睡眠。
(在类中写 Thread.sleep() 或者 直接写 sleep() )
调用 sleep 必须抓住 InterruptedException 异常,当程序被迫结束睡眠状态时候执行 catch 中语句。

interrupt() 方法:用来结束所调用线程睡眠状态。

join方法:

两个线程合并为一个线程。
(本来应该同时执行,但是因为子线程合并,所以必须等子线程执行完,才能执行主线程)

public class TestJ {
    public static void main(String[] args) {
        MyThread2 t1 = new MyThread2("t1");

        t1.start();

        try {
            t1.join();
        } catch (InterruptedException e) {}

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

class MyThread2 extends Thread {
    MyThread2 (String s) {
        super(s);
    }

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

        try {
            sleep(1000);
        } catch (InterruptedException e) {
            return;
        }
    }
}

加入 t1 后,main 必须等 t1 执行完,才会继续执行(不会同时执行了)

yield方法:

让出CPU,给其他线程执行机会。

public class TestJ {
    public static void main(String[] args) {
        MyThread3 t1 = new MyThread3("t1");
        MyThread3 t2 = new MyThread3("t2");

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

class MyThread3 extends Thread {
    MyThread3 (String s) {
        super(s);
    }

    public void run() {
        for(int i=1; i<=10; i++) {
            System.out.println(getName() + ":" + i);
            if(i%10==0) {
                yield();
            }
        }
    }
}

当一个线程中执行到 yield 方法时,立即暂停并切换到另一个线程,等待下一个被分配的时间点(让出位置,先让其他线程运行它的时间点,本质还是同时运行的)

线程的优先级别:

Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。
线程调度器按照线程的优先级决定应调度哪个线程来执行。
线程的优先级用数字表示,范围从1到10,一个线程的缺省(默认)优先级是5。

Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5

使用下述线方法获得或设置线程对象的优先级
int getPriority ();
void setPriority (int newPriority);

(优先级越高的得到CPU分配的时间片越多)

public class TestP {
    public static void main(String[] args) {
        Thread t1 = new Thread(new T1());
        Thread t2 = new Thread(new T2());

        t1.setPriority(Thread.NORM_PRIORITY + 3);
        t1.start();
        t2.start();
    }
}

class T1 implements Runnable {
    public void run() {
        for(int i=0; i<1000; i++) {
            System.out.println("T1:" + i);
        }
    }
}

class T2 implements Runnable {
    public void run() {
        for(int i=0; i<1000; i++) {
            System.out.println("T2:" + i);
        }
    }
}

同一个Runnable对象可以同时启用两个线程

Runner2 r = new Runner2();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);

run 方法一结束,线程就结束。
正确停止线程方法: 声明一个 shutDown 方法使得 flag == false
再循环体里 true 不断运行,只要调用 shutDown,线程停止。

线程同步:

public class TestT implements Runnable {
    Timer timer = new Timer();

    public static void main(String[] args) {
        TestT test = new TestT();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);

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

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

class Timer {
    private static int num = 0;
    public void add (String name) {
        synchronized(this) {
            num++;

            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {}

            System.out.println(name + " you are the " + num + " make this thread.");
        }
    }
}

如果没有 synchronized(this)
执行过程中由 t1 切换到 t2,num累加,最后结果都是 2。
synchronized :锁定当前线程执行,使得这一段语句不会被其他线程打断。

简便写法:

public synchronized void add(String name) {}

执行方法过程中当前对象被锁定。

死锁问题:
public class TestDL implements Runnable {
    public int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    public void run() {
        System.out.println("flag = " + flag);
        if(flag == 1) {
            synchronized(o1) {
                try {
                    Thread.sleep(500);
                } catch(Exception e) {
                    e.printStackTrace();
                }

                synchronized(o2) {
                    System.out.println("1");
                }
            }
        }
        if(flag == 0) {
            synchronized(o2) {
                try {
                    Thread.sleep(500);
                } catch(Exception e) {
                    e.printStackTrace();
                }
                synchronized(o1) {
                    System.out.println("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        TestDL td1 = new TestDL();
        TestDL td2 = new TestDL();

        td1.flag = 1;
        td2.flag = 0;

        Thread t1 = new Thread(td1);
        Thread t2 = new Thread(td2);

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

上述程序构成了死锁:t1 锁住了 o1 睡眠等待 o2,t2 锁住了 o2 睡眠等待o1
二者冲突,谁都无法打开。

一个线程锁住了一个对象,要另一个锁才能完成,而另一个线程刚好把另一个对象的锁锁住,等待第一个对象的锁才能完成,二者谁也打不开,构成死锁。

public class TT implements Runnable {
    int b = 100;

    public synchronized void m1 () throws Exception {
        b = 1000;
        Thread.sleep(5000);
        System.out.println("b = " + b);
    }

    public void m2 () {
        System.out.println(b);
    }

    public void run() {
        try {
            m1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        TT tt= new TT();
        Thread t = new Thread(tt);

        t.start();

        Thread.sleep(1000);
        tt.m2();
    }
}

上述程序可知,synchronized 锁定当前对象只锁定它访问的方法,在该线程访问所锁定的方法时,另一个线程完全可以调用该对象所构造的另一个未被锁定的方法(或锁定另一个对象的方法,如果另一个方法同时被锁定该对象,则无法访问)
(当第一个进入锁的线程执行时,所有该对象的锁就都被锁住,无法被其他线程访问)

wait 和 sleep 的区别:

wait 时别的线程可以访问锁定对象。
(调用 wait 方法的时候必须锁定该对象)

sleep 时别的线程也不可以访问锁定对象

public class ProducerConsumer {
    public static void main(String[] args) {
        SyncStack ss = new SyncStack();
        Producer p = new Producer(ss);
        Consumer c = new Consumer(ss);

        new Thread(p).start();
        new Thread(c).start();
    }
}


class WoTou {
    // 窝头

    int id;

    WoTou (int id) {
        this.id = id;
    }

    public String toString() {
        return "WoTou:" + id;
    }
}

class SyncStack {
    // 篮子

    int index = 0;
    WoTou[] arrWT = new WoTou[6];

    public synchronized void push (WoTou wt) {
        while(index == arrWT.length) {
            try {
                this.wait();  // 当前的正在访问的这个线程等待 锁定才能 wait
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();     // 叫醒其他的一个线程     
        arrWT[index] = wt;
        index++;
    }

    public synchronized WoTou pop () {
        while(index == 0) {
            try {
                this.wait(); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();        
        index--;
        return arrWT[index];
    }
}

class Producer implements Runnable {
    SyncStack ss = null;

    Producer (SyncStack ss) {
        this.ss = ss;
    }

    public void run() {
        for(int i=0; i<20; i++) {
            WoTou wt = new WoTou(i);
            ss.push(wt);
            System.out.println("make :" + wt);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    SyncStack ss = null;

    Consumer (SyncStack ss) {
        this.ss = ss;
    }

    public void run() {
        for(int i=0; i<20; i++) {
            WoTou wt = ss.pop();
            System.out.println("eat:" + wt);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

notify() 叫醒其他的一个线程。
notifyall() 叫醒其他的所有线程。

网络:

什么是网络通信协议?
计算机网络中实现通信必须有一些约定即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。

网络通信接口:
为了使两个节点之间能进行对话,必须在它们之间建立通信工具(即接口),使彼此之间能进行信息交换。接口包括两部分:
硬件装置:实现结点之间的信息传送
软件装置:规定双方进行通信的约定协议

通信协议分层的思想:

通信协议分层

分层参考模型:
分层参考模型

传输过程:

一.数据封装:
数据封装过程

二.数据拆封:
数据拆封过程

IP协议:

IP协议是网际层的主要协议,支持网间互连的数据报通信。它提供主要功能有:
无连接数据报传送。
数据报路由选择和差错控制。
(IP最大贡献:提供了独一无二的IP地址)

计算机内部由4个字节来表示IP地址,每个字节最大的数为255,所以每个“点”之间的数字不会超过255去。


A类网、B类网、C类网,可能由公司分配,C类网最多也只能有256台机器(前面3个地址固定)

IP字节中有些位数是网络的ID号,剩下的位数是主机的ID号,网络分为A类网、B类网等等,你具有一个独一无二的IP地址,能被其他人连接。

网络号代表了一个子网,而主机号代表了子网下的一个主机

如果想在子网里面再分子网,子网掩码在起作用,子网掩码前面都是1,代表网络的ID,如果其他人ID跟我的不一样,互相之间就不能直接访问。(比如192.168.0.6和192.168.0.7能访问,而193.168.0.6就不能访问)

子网掩码就是分辨IP地址是否在一个网上,是否能互相访问

网关:一边连着内网一边连着出口,如果有人想出去必须经过网关,从网关走,一般其具有两个以上的网卡(也不一定)

TCP协议和UDP协议:

TCP:
是专门设计用于在不可靠的因特网上提供可靠的、端到端的字节流通信的协议。它是一种面向连接的协议。TCP连接是字节流而非报文流。
(可靠的:一个包发过去,必须等对面有了回应,连上了,知道对面收到了我这个消息,才会继续发送,不然就会重复发一个包,如银行的密码确认,效率较低,时间慢)

UDP:
UDP向应用程序提供了一种发送封装的原始IP数据报的方法、并且发送时无需建立连接。是一种不可靠的连接。
(如语音通话,效率高,速度快,一个包发过去不管对面有没有回应,反正我已经发出去了)

Socket:

两个Java应用程序可通过一个双向的网络通信连接实现数据交换,这个双向链路的一端称为一个Socket(插座,由线插在两边插座上,实现连接)
Socket通常用来实现client - server连接
java.net包中定义的两个类Socket和ServerSocket,分别用来实现双向连接的client和server端
建立连接时所需的寻址信息为远程计算机的IP地址和端口号
端口号又分TCP和UDP两种,这两种不一样,每一个是65536个端口
(端口号:两个字节,用于区分同一台计算机上不同的应用程序的,一个应用程序可以占多个端口号,比如QQ发送消息不会发到微信上去,有一个应用程序占了,其他应用程序就无法去占了)
(如果自己写程序,用1024以上的端口号,因为1024以下的系统可能随时征用)

Socket通信模型:

通信模型

ServerSocket 传进去一个端口号(服务器端)
Socket 传进去一个IP地址和一个端口号(Client端,端口号是系统随机选的)
两个端口相连接,服务器端还要将用户端接收进来(Socket)
127.0.0.1 本机IP测试用。
accept 只能一次接受一个连接。
首先启动服务器端。

服务器端:

import java.io.*;
import java.net.*;

public class TServer {
    public static void main(String[] args) throws Exception {
        ServerSocket ss = new ServerSocket(6666);
        while(true) {
            Socket s = ss.accept();
            System.out.println("A client success!");
            DataInputStream dis = new DataInputStream(s.getInputStream());
            System.out.println(dis.readUTF());
            dis.close();
            s.close();
        }
    }
}

readUTF是阻塞式方法,必须要用户端发东西才能继续运行,如果连上的用户端不发东西,就只能等待无法执行accept方法,其他客户端就无法连接。

用户端:

import java.io.*;
import java.net.*;

public class TClient {
    public static void main(String[] args) throws Exception {
        Socket s = new Socket("127.0.0.1", 6666);
        OutputStream os = s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);

        dos.writeUTF("hello!");
        dos.flush();
        dos.close();
        s.close();

    }
}

Socket里面提供了getOutputStream和getInputStream方法,可以直接建立输入输出流。
Socket必须在最后关闭(s.close())

UDP:

不可靠的
效率高的
数据报/非连接
音频、视频等

(UDP没有意义上的服务器端与客户端,因为不需要连接上,发出去就不用管了,这里只是说明哪个在接收和发送而标注的名称)

服务器端:

import java.net.*;

public class US {
    public static void main(String[] args) throws Exception {
        byte buf[] = new byte[1024];

        DatagramPacket dp = new DatagramPacket(buf, buf.length);  // 包裹接收
        DatagramSocket ds = new DatagramSocket(5678);

        while(true) {
            ds.receive(dp);  // 阻塞式方法
            System.out.println(new String(buf, 0, dp.getLength()));  // 从0开始到字节数组长度,将这一部分打印出来
        }
    }
}

客户端:

import java.net.*;
import java.io.*;

public class UC {
    public static void main(String[] args) throws Exception {
        byte[] buf = (new String("Hello")).getBytes();  // 字符串解析为一个字节数组

        DatagramPacket dp = new DatagramPacket(buf, buf.length, new InetSocketAddress("127.0.0.1", 5678)); 
        // 字节数组扔到一个包裹里,把全部都扔出去(buf.length),扔到 127.0.0.1  5678端口

        DatagramSocket ds = new DatagramSocket(9999);  // Client端本身占据的端口

        ds.send(dp);
        ds.close();
    }
}

写入一个long类型的数:
(先写进ByteArray数组中,再从Socket端读出来)
(用ByteArrayOutputStream写)

服务器:

import java.net.*;


public class US2{
    public static void main(String[] args) throws Exception {
        byte buf[] = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf,buf.length);
        DatagramSocket ds = new DatagramSocket(5678);
        while(true) {
            ds.receive(dp);
            ByteArrayInputStream bais = new ByteArrayInputStream(buf);
            DataInputStream dis = new DataInputStream(bais);
            System.out.println(dis.readLong());
        }
    }
}

客户端:

import java.io.*;
import java.net.*;

public class US {
    public static void main(String[] args) throws Exception {
        long n = 10000L;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        dos.writeLong(n);

        byte[] buf = baos.toByteArray();
        System.out.println(buf.length);

        DatagramPacket dp = new DatagramPacket(buf,buf.length,new InetSocketAddress("127.0.0.1", 5678));
        DatagramSocket ds = new DatagramSocket(9999);
        ds.send(dp);
        ds.close();
    }
}

GUI编程:

AWT:

AWT包括了很多类和接口,用于Java Application的GUI编程。
GUI的各种元素由Java类来实现。
使用AWT所涉及的类一般在java.awt包及其子包中。
Container和Component是AWT中的两个核心类。

Component & Container:

所有可以显示出来的图形元素都叫 component .
container : 容纳其他 component 的元素。

Frame:

Frame 是 Window 的子类,由 Frame 或其子类创建的对象为一个窗体。
Frame 的常用构造方法:
Frame()
Frame(String s) 创建标题栏为字符串 s 的窗口。

import java.awt.*;

public class TFrame {
    public static void main(String[] args) {
        Frame f = new Frame("My first test.");
        f.setLocation(300, 300);  
        // 设置窗口出现时,左上角点的坐标的位置(窗口在屏幕的位置)
        // 屏幕从左上角开始,往右X递增,往下Y递增
        f.setSize(170, 100);
        f.setBackground(Color.blue);
        f.setResizable(false); // 设置为 false 不能改变大小
        f.setVisible(true);  // 设置是否可见
    }
}

也可以自己构造一种设置窗口的类(继承)

import java.awt.*;

public class TFrame {
    public static void main(String[] args) {
        MyFrame f1 = new MyFrame(100, 100, 200, 200, Color.BLUE);
        MyFrame f2 = new MyFrame(300, 100, 200, 200, Color.YELLOW);
        MyFrame f3 = new MyFrame(100, 300, 200, 200, Color.GREEN);
        MyFrame f4 = new MyFrame(300, 300, 200, 200, Color.MAGENTA);
    }
}

class MyFrame extends Frame {
    static int id = 0;
    MyFrame(int x, int y, int w, int h, Color color) {
        super("MyFrame:" + (++id));
        setBackground(color);
        setLayout(null);  // 把自己内部的布局管理器设置为空
        setBounds(x, y, w, h);
        setVisible(true);
    }
}
Panel:

Panel 对象可以看成可以容纳 Component 的空间
Panel 对象可以拥有自己的布局管理器
Panel 类拥有从其父类继承来的:
setBounds(int x, int y, int width, int height)
setSize(int width, int height)
setLocation(int x, int y)
setBackground(Color c)
setLayout(LayoutManager mgr) 等方法

Panel 的构造方法为:
Panel() 使用默认的 FlowLayout 类布局管理器初始化
Panel(LayoutManager layout) 使用指定的布局管理器初始化

import java.awt.*;

public class TFrame {
    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); // 装到谁里面就是相对于谁的位置
        p.setBackground(new Color(204, 204, 255));

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

public class TFrame {
    public static void main(String[] args) {
        new MyFrame2("MyFrameWithPanel", 300, 300, 400, 300);
    }
}

class MyFrame2 extends Frame {
    private Panel p1, p2, p3, p4;

    MyFrame2(String s, int x, int y, int w, int h) {
        super(s);
        setLayout(null);
        p1 = new Panel(null); // 不带自己的布局管理器
        p2 = new Panel(null);
        p3 = new Panel(null);
        p4 = new Panel(null);

        p1.setBounds(0, 0, w/2, h/2);
        p2.setBounds(0, h/2, w/2, h/2);
        p3.setBounds(w/2, 0, w/2, h/2);
        p4.setBounds(w/2, h/2, w/2, h/2);
        p1.setBackground(Color.BLUE);
        p2.setBackground(Color.GREEN);
        p3.setBackground(Color.YELLOW);
        p4.setBackground(Color.MAGENTA);
        add(p1);
        add(p2);
        add(p3);
        add(p4);
        setBounds(x, y, w, h);
        setVisible(true);
    }
}

布局管理器:

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

Awt提供了5种布局管理器类:
FlowLayout
BorderLayout
GridLayout
CardLayout
GridBagLayout

FlowLayout布局管理器:

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

构造方法:
new FlowLayout(FlowLayout.RIGHT, 20, 40);
右对齐,组件之间水平间距20个像素,垂直间距40个像素。
new FlowLayout(FlowLayout.LEFT);
左对齐,水平和垂直间距为缺省值(5)——-> (默认值为5)
new FlowLayout();
使用缺省的居中对齐方式,水平和垂直间距为缺省值(5)

eg1:

import java.awt.*;

public class TFL {
    public static void main(String[] args) {
        Frame f = new Frame("Flow Layout");
        Button button1 = new Button("Ok");
        Button button2 = new Button("Open");
        Button button3 = new Button("Close");

        f.setLayout(new FlowLayout());
        f.add(button1);
        f.add(button2);
        f.add(button3);
        f.setSize(100, 100);
        f.setVisible(true);
    }
}

eg2:

import java.awt.*;

public class TFL {
    public static void main(String[] args) {
        Frame f = new Frame("Java Frame");
        FlowLayout l = new FlowLayout(FlowLayout.CENTER, 20, 40);
        f.setLayout(l);
        f.setLocation(300, 400);
        f.setSize(300, 200);
        f.setBackground(new Color(204, 204, 255));

        for(int i=0; i<=7; i++) {
            f.add(new Button("BUTTON"));
        }

        f.setVisible(true);
    }
}
BorderLayout 布局管理器:

BorderLayout 是 Frame 类的默认布局管理器。
BorderLayout 将整个容器的布局划分成:

西



五个区域,组件只能被添加到指定区域。
如不指定组件的加入部位,则默认加入到 CENTER 区。
每个区域只能加入一个组件,如加入多个,则先前加入的会被覆盖。

BorderLayout 型布局管理器尺寸缩放原则:
北、南两个区域在水平方向缩放。(可左右收缩,不会垂直收缩)
东、西两个区域在垂直方向缩放。
中部可在两个方向上缩放。

import java.awt.*;

public class TFL {
    public static void main(String[] args) {
        Frame f = new Frame("Border Layout");
        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);
    }
}

其中:
f.add(bn, BorderLayout.NORTH);
也可用
f.add(bn, “North”);
来代替,但推荐用上一种,清晰,并且如果写错会无法编译。

GridLayout 布局管理器:

GridLayout 型布局管理器将空间划分成规则的矩形网络,每个单元格区域大小相等。组件被添加到每个单元格中,先从左到右添满一行后换行,再从上到下。
在 GridLayout 构造方法中指定分割的行数和列数:
如:GridLayout(3, 4)

import java.awt.*;

public class TFL {
    public static void main(String[] args) {
        Frame f = new Frame("GridLayout");

        for(int i=1; i<=6; i++) {
            f.add(new Button("b" + i));
        }

        f.setLayout(new GridLayout(3, 2));
        f.pack();  // 使窗口大小自动适应按钮充满后的大小(自动包住这6个按钮)
        f.setVisible(true);
    }
}

对整体组合的应用:

import java.awt.*;

public class TFL {
    public static void main(String[] args) {
        Frame f = new Frame("GridLayout");

        f.setLayout(new GridLayout(2, 1));
        f.setBounds(300, 400, 300, 200);
        f.setBackground(new Color(204, 204, 255));

        Panel p1 = new Panel(new BorderLayout());
        Panel p2 = new Panel(new BorderLayout());
        Panel p3 = new Panel(new GridLayout(2, 1));
        Panel p4 = new Panel(new GridLayout(2, 2));

        p1.add(new Button("BUTTON"), BorderLayout.WEST);
        p1.add(new Button("BUTTON"), BorderLayout.EAST);
        p2.add(new Button("BUTTON"), BorderLayout.WEST);
        p2.add(new Button("BUTTON"), BorderLayout.EAST);
        p3.add(new Button("BUTTON"));
        p3.add(new Button("BUTTON"));
        p4.add(new Button("BUTTON"));
        p4.add(new Button("BUTTON"));
        p4.add(new Button("BUTTON"));
        p4.add(new Button("BUTTON"));
        p1.add(p3, BorderLayout.CENTER);
        p2.add(p4, BorderLayout.CENTER);

        f.add(p1);
        f.add(p2);
        f.setVisible(true);
    }
}

先从大的方面划分 Frame ,然后套进两个 Panel ,Panel 再分别套两个 Panel

事件监听:

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

public class TAE {
    public static void main(String[] args) {
        Frame f = new Frame("Test");
        Button b = new Button("Press Me!");
        Monitor bh = new Monitor();

        b.addActionListener(bh);
        f.add(b, BorderLayout.CENTER);
        f.pack();
        f.setVisible(true);
    }
}

class Monitor implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        System.out.println("a button has been pressed");
    }
}
import java.awt.*;
import java.awt.event.*;

public class TAE {
    public static void main(String[] args) {
        Frame f = new Frame("Test");
        Button b1 = new Button("Start");
        Button b2 = new Button("Stop");
        Monitor bh = new Monitor();

        b1.addActionListener(bh);
        b2.addActionListener(bh);
        b2.setActionCommand("game over");
        f.add(b1, BorderLayout.NORTH);
        f.add(b2, BorderLayout.CENTER);
        f.pack();
        f.setVisible(true);
    }
}

class Monitor implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        System.out.println("a button has been pressed, " + e.getActionCommand());
    }
}

区分两个Button : 用 getActionCommand ,可以单独设定内容,如果不设定,默认按钮上面的文字。

TextField 类:

java.awt.TextField 类用来创建文本框对象

TextField 有如下常用方法:

TextField 事件监听:

TextField 对象可能发生 Action (光标在文本框内敲回车)事件。与该事件对应的事件类是 java.awt.event.ActionEvent
用来处理 ActionEvent 事件是实现了 java.awt.event.ActionListener 接口的类的对象。
ActionListener 接口定义有方法:
public void actionPerformed(ActionEvent e)
实现该接口的类要在该方法中添加处理该事件的语句。
使用 addActionListener(ActionListener l) 方法为 TextField 对象注册一个 ActionListener 对象,当 TextField 对象发生 Action 事件时,会生成一个 ActionEvent 对象,该对象作为参数传递给 ActionListener 对象的 actionPerformer 方法,在方法中可以获取该对象的信息,并做相应处理。

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

public class TF {
    public static void main(String[] args) {
        new TFFrame();
    }
}

class TFFrame extends Frame {
    TFFrame() {
        TextField tf = new TextField();
        add(tf);
        tf.addActionListener(new TFActionListener());
        tf.setEchoChar('*'); // 显示为‘*’
        pack();
        setVisible(true);
    }
}

class TFActionListener implements ActionListener {
    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 TFM {
    public static void main(String[] args) {
        new TFFrame().launchFrame();
    }
}

class TFFrame extends Frame {
    TextField num1, num2, num3;

    public void launchFrame() {                 // 运行该 Frame
        num1 = new TextField(10);     // 设置字符宽度为 10
        num2 = new TextField(10);
        num3 = new TextField(15);
        Label lblPlus = new Label("+");         // 静态文本
        Button btnEqual = new Button("=");

        btnEqual.addActionListener(new MyMonitor(this));

        setLayout(new FlowLayout());
        add(num1);
        add(lblPlus);
        add(num2);
        add(btnEqual);
        add(num3);
        pack();
        setVisible(true);
    }
}

class MyMonitor implements ActionListener {
/*    TextField num1, num2, num3;
    public MyMonitor(TextField num1, TextField num2, TextField num3) { 
         // Button 的监听器,如果拿 num, 只能构造方法一个一个传进去,效率低
        this.num1 = num1;
        this.num2 = num2;
        this.num3 = num3;
    } */

    TFFrame tf = null;
    public MyMonitor(TFFrame tf) {
        this.tf = tf;
    }

    public void actionPerformed(ActionEvent e) {   
        int n1 = Integer.parseInt(tf.num1.getText());
        int n2 = Integer.parseInt(tf.num2.getText());
        tf.num3.setText("" + (n1+n2));
    }
}

内部类:

内部类可以直接调用包装类的成员变量。

如上述例子:

class TFFrame extends Frame {
    TextField num1, num2, num3;
    public void lauFrame() {
        num1 = new TextField(10);
        num2 = new TextField(10);
        num3 = new TextField(15);
        Label l = new Label("+");
        Button b = new Button("=");

        b.addActionListener(new MyAction());
        setLayout(new FlowLayout());
        add(num1);
        add(l);
        add(num2);
        add(b);
        add(num3);
        pack();
        setVisible(true);
    }

    class MyAction implements ActionListener {
        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 方法

Graphics 类中提供了许多绘图的方法,如:
drawRect(int x, int y, int width, int height)
fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

import java.awt.*;

public class TP {
    public static void main(String[] args) {
        new PaintFrame().lauchFrame();
    }
}

class PaintFrame extends Frame {         
    public void lauchFrame() {
        setBounds(200, 200, 640, 480);
        setVisible(true);
    }

    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);                         // 恢复原来画笔的颜色
    }
}

paint 方法在每次窗口打开(或从被覆盖到前面)时都自动调用。

鼠标事件适配器:

抽象类 java.awt.event.MouseAdapter 实现了 MouseListener 接口,可以使用其子类作为 MouseEvent 的监听器,只要重写其相应的方法即可。
对于其他的监听器,也有对应的适配器。
使用适配器可以避免监听器类定义没有必要的空方法。
repaint - update() - paint();

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

public class MM {
    public static void main(String[] args) {
        new MyFrame("drawing...");
    }
}

class MyFrame extends Frame {
    ArrayList points = null;

    MyFrame(String s) {
        super(s);
        points = new ArrayList();
        setLayout(null);
        setBounds(300, 300, 400, 300);
        setBackground(new Color(204, 204, 255));
        setVisible(true);
        addMouseListener(new Monitor());
    }

    public void paint(Graphics g) {
        Iterator i = points.iterator();
        while(i.hasNext()) {
            Point p = (Point)i.next();
            g.setColor(Color.blue);
            g.fillOval((int)p.x, (int)p.y, 10, 10);
        }
    }

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

class Monitor extends MouseAdapter {        
    public void mousePressed(MouseEvent e) {
        MyFrame f = (MyFrame)e.getSource();
        f.addPoint(new Point(e.getX(), e.getY(), 0));
        f.repaint();
    }
}

MouseAdapter 空实现了 MouseListener 接口,所以只要继承就可以直接重写其方法。
如果直接实现 MouseListener 接口,则要重写其 5 个方法,没有必要。
repaint() // 对当前画面重画,如果不重画的话,你点下去看不到

Window 事件:

Window 事件所对应的事件类为 WindowEvent , 所对应的事件监听接口为 WindowListener

WindowListener 定义的方法有:

与 WindowListener 对应的适配器为 WindowAdapter

匿名类:

(知道就行,最好不用)
(在方法里面的类,不被其他方法调用)

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

键盘适配器:

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

public class TK {
    public static void main(String[] args) {
        new MyFrame().launchFrame();
    }
}

class MyFrame extends Frame {
    public void launchFrame() {
        setBounds(300, 300, 200, 200);
        addKeyListener(new MyK());
        setVisible(true);
    }

    class MyK extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            int keyCode = e.getKeyCode();

            if(keyCode == KeyEvent.VK_UP) {
                System.out.println("Ok");
            }
        }
    }
}

与其他类似。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值