黑马程序员——Java中的面向对象

----android培训java培训、期待与您交流!
----

通过一段时间的学习,对于面向对象有了更深刻的了解,面向对象是将复杂的事情简单化,面向对象的思想更能全面详细的想到问题的关键,下面是对面向对象的一些总结:

面向对象:将功能封装进对象,强调具备了功能的对象

特点: 1:将复杂的事情简单化。

               2:面向对象将以前的过程中的执行者,变成了指挥者。

               3:面向对象这种思想是符合现在人们思考习惯的一种思想。

面向过程是执行者   ,   而面向对象是指挥者

我对面向对象的理解:我说要喝饮料,但是我没有去买,而是告诉别人去商店买饮料,我没有动,但是我找了一个能给我去买饮料的人,使用它的跑腿的功能,而我就只要付钱就可以了。而跑腿的人对于我来说就是对象,所以我就从执行者变成了指挥者,这就是面向对象。

面向对象是一种思想,它能让复杂的问题简单化,能让我们得角度进行转变,从执行者变为指挥者

类就是:对现实生活中事物的描述,,,描述事物其实就是在描述事物得属性和行为

属性对应的是类中的变量,行为对应的类中的函数

对象:就是这类事物,实实在在存在的个体

成员变量和局部变量:

成员变量作用于整个类中,局部变量作用于函数中。。。

成员变量存在堆内存中,因为对象存在,才在内存中存在,,,

局部变量存在栈内存中

匿名对象使用方式:

当对对象的方法只调用一次,可以用匿名对象来完成,

如果对一个对象进行对个成员调用,必须给这个对象起个名字。。。

面向对象的三个特征:封装,继承和多态

封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式

好处:将变化隔离

便于使用

提高重用性

提高安全性

封装的原则:

将不需要对外提供的内容都隐藏起来

把属性都隐藏,提供公共方法对其访问

private: 私有 权限修饰符:用于修饰类中的成员(成员变量,成员函数)私有只在本类中有效

注意:私有仅仅是封装的一种表现形式

之所以对外提供访问方式,就因为可以在访问方式中加入逻辑判断等语句,对访问的数据进行操作,提高代码健壮性。

成员变量和局部变量的不同:成员变量都与默认值,不赋值,但是能参与运算,因为成员变量在堆内存中,而局部变量不初始化是不能参与运算的,因为局部变量在栈内存中,必须初始化值后才能参与运算。

构造函数特点:

1,函数名与类名相同

2,不用定义返回值类型

3,不可以写return语句

构造函数的作用:可以用于给对象进行初始化

构造函数的小细节:当一个类中没有定义构造函数时,那么系统就会默认给该类加入一个空参数的构造函数

当在类中自定义构造函数后,默认的构造函数就没有了

构造函数和一般函数在写法上有不同,在运行上也有不同

构造函数是在对象一建立就运行,给对象初始化

而一般方法是对象调用才执行,给对象添加对象具备的功能

一个对象建立,构造函数只运行一次

而一般方法可以被该对象调用多次

什么时候定义构造函数呢?

当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中

构造代码块作用:给对象进行初始化,对象一建立就运行,而且优先于构造函数执行

构造代码块和构造函数的区别:

构造代码块是给所有对象进行统一初始化

而构造函数是给对应的对象初始化

格式:

{

构造代码块中定义的是不同对象共性的初始化内容

}

this:是用于区分局部变量和成员变量同名情况

this 代表本类的对象,

this 代表它所在函数所属对象的引用

简单说,哪个对象在调用this所在的函数,this就代表哪个对象

this的应用:当定义类中功能时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象。但凡本类功能内部使用了本类对象,

this语句:用于构造函数之间互相调用

this语句只能定义在构造函数的第一行,因为初始化要先执行

创建一个对象都在内存中做了什么事情?

1因为new用到了Person.class,所以会先找到Preson.class文件并加载到内存中

2执行该类中的static代码块,如果有的话,给Person.class类进行初始化

3在堆内存中开辟一个实体空间,分配了一个内存首地址值。new

4在堆内存汇总建立对象的特有属性,并进行了默认初始化。

5对空间中的属性进行显示初始化。

6对对象进行构造代码块初始化。

7对对象进行对应的构造函数初始化。()

8将内存地址赋给栈内存中的p变量

Static静态关键字:用于修饰成员(成员变量和成员函数)

被修饰的成员具备以下特点:

1,随着类得加载而加载

也就是说:静态会随着类的消失而消失,说明它的生命周期最长

2,优先于对象存在

明确一点:静态是先存在的,对象是后存在的

3,被所有对象所共享

4,可以直接被类名调用

静态的使用注意:

1,静态方法只能访问静态成员,非静态方法既可以访问静态也可以访问非静态

2,静态方法中不可以写定义this  super关键字, 因为静态优先于对象存在,所以静态方法中不可以出现this

3,主函数是静态的

静态变量存在方法区中

成员变量和类变量的区别:

1,存放位置

类变量随着类的加载而存在于方法区中

成员变量随着对象的建立而存在于堆内存中

2,生命周期

类变量生命周期最长,随着类的消失而消失

成员变量生命周期随着对象的消失而消失

3,成员变量所属于对象。所以也称为实例变量。

静态变量所属于类。所以也称为类变量。

4,成员变量只能被对象所调用 。

静态变量可以被对象调用,也可以被类名调用。

所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。

静态的好处:

1,对对象的共享数据进行单独控件的存储,节省空间,没有必要每一个对象中都存储一份

2,可以直接被类名调用

静态的弊端:

1,生命周期过长

2,访问出现局限性(静态随好,只能访问静态)

主函数:是一个特殊的函数,作为程序的入口,可以被jvm调用

主函数的定义:

Public:代表着该函数访问权限是最大的

Static:代表主函数随着类的加载就已经存在了

Void:主函数没有具体的返回值

Main:不是关键字,但是是一个特殊的单词,可以被jvm识别

(String[] arr):函数的参数,参数类型是一个数组,该数组中的元素是字符串,字符串类型的数组,传递的实际参数是 new String[0]。

主函数是固定格式:jvm识别

什么时候使用静态呢?

要从两方面入手:

什么时候定义静态变量(类变量)呢?

当对象中出现共享数据时,该数据被静态锁修饰,对象中的特有数据要定义成非静态存在于堆内存中

 什么时候定义静态函数呢?

当功能内部没有访问到非静态数据(对象特有的数据),那么该功能可以定义成静态的

生成帮助文档:想要生成帮助文档的类,必须是公有的

默认构造函数的权限是随着类得变化而变化的

生成Java帮助文档:命令格式:javadoc –d 文件夹名 –auther –version *.java

/**     //格式

*类描述

*@author 作者名

*@version 版本号

*/

/**

*方法描述

*@param  参数描述

*@return  返回值描述

*/

静态代码块

格式:

Static

{

静态代码块中的执行语句

}

特点:随着类的加载而执行,只执行一次,并优先与主函数

用于给类进行初始化

继承

继承的好处:

1,提高了代码的复用性

2,让类与类之间产生了关系,有了这个关系,才有了多态的特性

注意:千万不要为了获取其他类的功能,简化代码而继承

必须是类与类之间有所属关系才可以继承,

Java语言中:java只支持单继承,不支持多继承

因为多继承容易带来安全隐患:当多个父类中定义了相同功能,当功能内容不同时,子类对象不确定要运行哪一个。

Java支持多层继承,也就是一个继承体系

为什么不支持多继承呢?

因为当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪一个呢?因为父类中的方法中存在方法体。

但是java支持多重继承。A继承B  B继承C  C继承D。

多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。

所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。

简单说:对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。

子父类出现后,类成员的特点:

类中成员

1,变量

2,函数

3,构造函数

  1,变量:

如果子类中出现非私有的同名成员变量时

子类要访问本类中的变量  用this

子类要访问父类中的同名变量  用super

Super的使用和this的使用几乎一样

This代表的是本类对象的引用

Super代表的是父类对象的引用

2,子父类中的函数

当子类出现和父类一模一样的函数时

当子类对象调用该函数,会运行子类函数的内容

如同父类的函数被覆盖一样

这种情况是函数的另一个特性:重写(覆盖)

当子类集成继承父类,沿袭了父类的功能,到子类中

但是子类虽具备该功能,但是功能的内容却和父类不一致

这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。

覆盖注意:子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败

静态只能覆盖静态

记住:

重载:只看同名函数的参数列表

重写:子父类方法要一模一样,包括返回类型

3,子父类中的构造函数

Super()调用父类的构造函数

在对子类对象进行初始化时,父类的构造函数也会运行

那是因为子类的构造函数默认第一行有一条隐式的语句super()

Super():会访问父类总的空参数的构造函数,而且子类中所有的构造函数默认第一行都是super()

为什么子类一定要访问父类中的构造函数呢?

因为父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的

所以子类在对象初始化时,要先访问一下父类中的构造函数

如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定

注意:super语句一定定义在子类构造函数的第一行

子类的实例化过程的结论:

子类的所有构造函数,默认都会访问父类中空参数的构造函数

因为子类每一个构造函数内的第一行都有一句隐式super()

当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问的构造函数。

 

当然:子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数,子类中至少会有一个构造函数去访问父类中的构造函数

final作为一个关键字的特点:

1,可以修饰类,函数,变量

2,被final修饰的类不可以被继承,为了避免被继承,被子类复写功能

3,被final修饰的方法是不可以被复写的

4,被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,也可以修饰局部变量

(当在描述事物时,一些数据的出现值时固定的,那么这时为了增强阅读性,都给这些值起个名字。方便于阅读,而这个值不需要改变,所以加上final修饰,

作为常量:常量的书写规范所有字母都大写,如果由多个单词组成,单词间通过_连接)

5,内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量

抽象类的特点:

1,抽象方法一定定义在抽象类中

2,抽象方法和抽象类都必须被abstract关键字修饰

3,抽象类不可以用new创建对象,因为调用抽象方法没有意义

4,抽象类中的抽象方法要被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用(如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类)

抽象类和一般类没有太大的不同

该如何描述事物,就如何描述事物,只不过,该事物出现了一些看不懂的东西

这些不确定的部分,也是该事物的功能,需要明确出现,但是无法定义主体,通过抽象方法来表示

抽象类比一般类多了抽象函数,抽象类不可以实例化,就是在类中可以定义抽象方法

特殊:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象

接口:是不可以创建对象的,因为有抽象方法,需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化,否则子类是一个抽象类

接口可以被类多实现,也是对多继承不支持的转换形式,java支持多实现

Java支持多继承,但是仅限于在接口与接口之间

接口的特点:

1,接口是对外暴露的规则

2,接口是程序的功能扩展

3,接口可以用来多实现

4,类与接口之间是实现关系,而且类可以竭诚一个类的同时实现多个接口

5,接口与接口之间可以有继承关系

抽象类与接口:

抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。

接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。

抽象类和接口的共性:都是不断向上抽取的结果。

抽象类和接口的区别:

1:抽象类只能被继承,而且只能单继承。

      接口需要被实现,而且可以多实现。

2:抽象类中可以定义非抽象方法,子类可以直接继承使用。

     接口中都有抽象方法,需要子类去实现。

3:抽象类使用的是  is a关系。

    接口使用的 like a关系。

4:抽象类的成员修饰符可以自定义。

     接口中的成员修饰符是固定的。全都是public的。

模板方法设计模式:

解决的问题:当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

abstract class GetTime{

public final void getTime(){//此功能如果不需要复写,可加final限定

long start = System.currentTimeMillis();

long end = System.currentTimeMillis();

System.out.println("毫秒是:"+(end-start));

}

publicabstract void code(); //抽象不确定的功能,让子类复写实现

}

class SubDemo extends GetTime{

public void code(){//子类复写功能方法

for(int y=0; y<1000; y++){

System.out.println("y");

}

}

}

什么是模版方法呢?

在定义功能时,功能的一部分是确定的,但是有一部分是不确定的,而确定的部分在使用不确定的部分,那么这时就将不确定的部分暴露出去,由该类的子类去完成。

多态:可以理解为事物存在的多种体现形态

多态的体现:

父类的引用指向了自己的子类对象

父类的引用也可以接受自己的子类对象

多态的前提:

必须是类与类之间有关系,要么继承,要么实现

通常还有一个前提,就是存在覆盖

多态的好处:

多态的出现大大的提高程序的扩展性

多态的弊端:

提高了扩展性,但是只能使用父类的引用访问父类中的成员

多态的应用:

多态的出现代码中的特点(多态使用的注意事项)

在多态中(Fu f=new Zi())成员函数的特点:

(1)  在编译时期:参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过,如果没有编译失败

(2)  在运行时期,参阅对象所属的类中是否有调用的方法

简单的总结就是:成员函数在多态调用时,编译时看左边,运行时看右边

注意:

千万不要出现这样的操作,就是将父类对象转成子类类型

我们能转换的是父类应用指向了自己的子类对象时,该应用可以被提升,也可以被强制转换

多态自始至终都是子类对象在做着变化

面试题:

在多态中成员变量的特点:无论编译和运行,都参考左边(引用型变量所属的类)

在多态中,静态成员函数的特点:无论编译和运行,都参考左边

Object:所有类的直接或者间接父类,Java认为所有的对象都具备一些基本的共性内容,这些内容可以不断的向上抽取,最终就抽取到了一个最顶层的类中的,该类中定义的就是所有对象都具备的功能。

具体方法:

1,boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。

而根据对象的属性不同,判断对象是否相同的具体内容也不一样。所以在定义类时,一般都会复写equals方法,建立本类特有的判断对象是否相同的依据。

public boolean equals(Object obj){

if(!(obj instanceof Person))

return false;

Person p = (Person)obj;

return this.age == p.age;

}

Object类:该类中定义的肯定是所有对象都具备的功能

如果用到了对象中的特有方法,在重写父类的方法的时候,就要向下转型,并且可以使用instanceof来判断

  2,String.toString():将对象变成字符串;默认返回的格式:类名@哈希值 = getClass().getName() + ‘@‘ + Integer.toHexString(hashCode())

为了对象对应的字符串内容有意义,可以通过复写,建立该类对象自己特有的字符串表现形式。

public String toString(){

return "person : "+age;

}

3,Class getClass():获取任意对象运行时的所属字节码文件对象。

4,int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。

通常equals,toString,hashCode,在应用中都会被复写,建立具体对象的特有的内容。

 

 

内部类:将一个类定义在另一个类的里面,对里面那个类就称为内部类(内置类,嵌套类)

访问特点:

1,内部类可以直接访问外部类中的成员,包括私有成员。

之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,   格式:   外部类名.this

2,外部类想要访问内部类,必须要建立内部类的对象。

类能不能被私有化修饰,能,但是仅限于是内部类

class Outer{

int num = 4;

class  Inner {

void show(){

System.out.println("inner show run "+num);

}

}

public void method(){

Inner in = new Inner();//创建内部类的对象。

in.show();//调用内部类的方法。

}

}

访问格式:

1,当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中,可以直接建立内部类对象。

格式:

 外部类名.内部类名  变量名=外部类对象.内部类对象

   Outer.Inner in=new Outer().new Inner();

2,当内部类在成员位置上,就可以被成员修饰符所修饰,

比如,

private 将内部类在外部类中进行封装

Static内部类就具备static的特性

当内部类被static修饰后,只能直接访问外部类中的static成员,出现了访问局限

在外部其他类中,如何直接访问static内部类的非静态成员函数呢?

 格式:new Outer.Inner().function(); 

因为function是非静态的方法,所以要用对象去调用

在外部其他类中,如何直接访问static内部类的静态成员函数呢?

格式:Outer.Inner.function()

注意:当内部类中定义了静态成员,该内部类必须是静态的

      当外部类中的静态方法访问内部类时,内部类也必须是静态的

内部类的定义原则:

当描述事物时,事物的内部还有事物,该事物用内部类来描述,  因为内部事物在使用外部事物的内容,内部类就是能访问外部类的成员的一个具体事物

内部类定义在局部时:

1,不可以被成员修饰符修饰

2,可以直接访问外部类中的成员,因为还持有外部类中的引用

但是不可以访问它所在的局部中的变量,只能访问被finall修饰的局部变量

匿名内部类:没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象

匿名内部类:

1, 匿名内部类其实就是内部类的简写格式

2,定义匿名内部类的前提: 内部类必须是继承一个类或者实现接口

3, 匿名内部类的格式:new 父类名或者接口名(){ 定义子类成员或者覆盖父类方法 }.方法。

4,其实匿名内部类就是一个匿名子类对象,而且这个对象有点胖(可以理解为带内容的对象)

5,匿名内部类中定义的方法最好不要超过3个

匿名内部类的使用场景:

当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。

其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。

例题:

Test.function().method()  给这个分解——其实就是

Inter in=Test.function();

In.method();

Test.function():Test类中有一个静态的方法function

.method() :function这个方法运算后的结果是一个对象,而且是一个Inter类型的对象,因为只有是Inter类型的对象,才可以调用method方法

interface Inter

{

void method();

}

class Test

{

static Inter function()

{

return new Inter(){

public void method()

{

System.out.println("method run");

}

};

}

}

class  InnerDemo

{

public static void main(String[] args)

{

Test.function().method();

}

}

面试题:

(1)

new Object(){

void show(){

System.out.println("show run");

}

}.show();

(2)

Object obj = new Object(){

void show(){

System.out.println("show run");

}

};

obj.show();

1和2的写法正确吗?有区别吗?说出原因。

写法是正确,1和2都是在通过匿名内部类建立一个Object类的子类对象。

区别:

第一个可是编译通过,并运行。

第二个编译失败,因为匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提升为了Object类型,而编译时检查Object类中是否有show方法,所以编译失败。

-----android培训java培训、期待与您交流!
-----

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-08 11:05:09

黑马程序员——Java中的面向对象的相关文章

黑马程序员-java中的反射总结

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- Java 反射总结 类装载器工作机制 类装载器就是寻找类的节码文件并构造出类在JVM 内部表示对象的组件.在Java 中, 类装载器把一个类装入JVM 中,要经过以下步骤: 1.装载:查找和导入Class 文件: 通过一个类的全限定名来获

黑马程序员------Java中多线程学习总结(一)

Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! 一.多线程的概念 进程:是一种“自包容”的运行程序,有自己的地址空间. 基于进程的特点是允许计算机同时运行两个或更多的程序 线程:是进程内部单一的一个顺序控制流 . 基于线程的多任务处理环境中,线程是最小的处理单位. 在Java中,一个应用程序可以包含多个线程.每个线程执行特定的任务,并可与其他线程并发执行.多线程使系统的空转时间减少,提高了CPU的利用率.多线程编程隐藏了CPU在任务之间切换的事实. 二.创建

黑马程序员------Java中jdk1.5新特性

Java培训.Android培训.iOS培训..Net培训.期待与您交流! JDK1.5新特性: 为什么会出现新特性: 新的技术出现是为了解决老的问题,Java语言为了提高开发者的开发效率,对之前的某些不利于提高效率的技术进行改进. 静态导入: 静态导入:可以导入某个类下的静态方法,静态导入后,可以不写类名而直接使用此类下的静态方法. 语法:import static 包名.类名.静态方法 代码示例: package com.itheima.day1; /** * 静态导入 * @author

黑马程序员————java中的网络编程

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- java中的网络编程 一.网络编程概述:基于互联网的编程 就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换. 二.网络模型:OSI和TCP/IP 1.OSI(Open System Interconnection开放系统互连

黑马程序员------Java中GUI(图形用户界面)学习总结

Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! GUI: Graphical User Interface(图形用户接口). 即用图形的方式,来显示计算机操作的界面,以方便用户更容易更直观地操作. Java中为GUI提供的对象都在Java.Awt和Javax.Swing两个包中. java.Awt: Abstract Window ToolKit (抽象窗口工具包). 需要调用本地系统方法实现功能,属于重量级控件. javax.Swing: 在AWT的基础上

黑马程序员————java中的抽象类

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- 抽象类集中的体现了java面向对象的特性,对于每一种事物通过五个方面:属性,方法,构造器,代码块,内部类,来重新解构再进行组装,然后将类似的事物归为一类,这是面向对象的思想.java中常说万物皆对象,那么很显然我们可以进一步的将其中的方法

黑马程序员-Java中的基本数据类型

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- Java中的数据类型分为两个大类:基本数据类型和引用数据类型. 其中基本数据类型又可以分为四类:整型数据类型,小数数据类型,字符类型,布尔型. 整型数据类型还分为:byte,short,int(整型),long(长整型).默认为int型.

黑马程序员————java中面向对象的三大特性

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- 面向对象的思想是将一切事物看出由属性,方法,构造器,代码块,内部类等五个方面构成,只要事物在这五个方面有相似之处就可以归为一类,类与类之间则是通过封装,继承,多态来体现. 首先说封装,很简单的思想,上帝创造我们出来没有给我们翅膀,那我们即

黑马程序员------Java中多线程学习总结(二)

Java培训.Android培训.iOS培训..Net培训,期待您的交流 在Java多线程中,如果有多个线程同时操作共享数据时,就可能会发生数据异常 如下面这段代码: /* * 模拟卖票 */ class Ticket implements Runnable { private int tick = 10; Object obj = new Object(); public void run() { while(true) { if(tick>0) { try{Thread.sleep(10);