字面量:
整数如:1、2、3……它们的字面量为整型(int)
小数如:0.1、1.1……它们的字面量为双精度浮点型(double)
注意字面量的问题,因为这会涉及到数据类型转换的问题。
byte、short类型稍有例外。虽然如1、2这样字面量是int型的常数,但是可以赋值给byte、short类型。
数值类型的表示范围:byte<short<int<long<float<double
时刻注意各种数据类型尤其是数值型数据类型,在进行运算的时候千万别出现数据溢出的情况!
还有一点也是要注意的:时刻防止出现数组越界的情况。
&和&&(逻辑与和短路与)的区别、|和||(逻辑或和短路或)的区别:
分支结构:如:if…else if… else if… …else和switch…case这两种分支结构,当有一条判断语句为真时,就会执行它之后的代码,同时这条判断语句之后的所以和它同级的判断语句及其之后的代码就一定不会再去执行了!
同时需要注意的是:注意这些分支语句,谁和谁是同级的?!
switch()括号内的表达式的类型只能是如下四种中的一种:char、byte、short、int
注意变量的作用域方面的问题!
在定义类的时候,没有必要一定要找全它在现实中的所有的属性、方法。我们只需要关注我们需要的!
用类去声明对象时,方法一:类名 对象名 = null;这只是声明了一个引用,引用是放在栈内存中的,但不在堆内存中给它分配了对应的存储空间,所以这并不算生成一个对象,所以虽然抽象类或接口不能生成一个对象,但是还是可以使用这种方法生成一个引用,主要用在向上转型中,所以也就要求有继承了抽象类或接口的实现类;方法二:类名 对象名 = new 类名();这既声明了在栈内存中的一个引用,而且还在堆内存中给它分配了对应的存储空间,这种方法就不能用于抽象类或接口了。
匿名对象:匿名对象一般是一次性的,用完之后就再也找不到它了,因为它没有名字。
函数重载:要保证1、两个或多个函数在同一个类中;2、函数的名字相同;3、函数的参数列表不同
构造函数:如果在类中没有自定义构造函数,那么编译器就会自动添加一个无参数无方法体的默认构造函数,但是如果自己新定义了一个构造函数,那么编译器就不会自动添加一个无参数的默认构造函数,所以这时候如果再去使用无参数的构造函数就有可能出错。解决方法可以是,当你自定义一个有参数的构造函数时,同时也定义一下无参数的构造函数!而且习惯上无参数的构造函数还是要定义的!
而且构造函数不属于成员函数!
调用本类中的构造函数时会使用到的关键字this:如果想用this在一个构造函数中调用本类中的其他构造函数的话,this的代码必须是放在第一行的语句。而且注意,在一个构造函数中要想去用this调用两个或两个以上的本类中的其他构造函数是不允许的,只能是一个!!
但是可以有这样的情况:在构造函数1中用this在第一行调用构造函数0,再在构造函数2中用this在第一行调用构造函数1,……以此类推。
this不光可以用来构造函数,还可以用来在本类调用本类的成员函数。
静态函数、静态变量、静态代码块(关键字:static):静态变量是这个类的所有对象所共有的;静态函数中不能使用this关键字,所以静态函数中不能直接使用非静态变量;静态代码块会在装载这个类的时候使用(静态代码块格式,例如:static:{System.out.println(“Hello,World”);}),静态代码块的主要作用是为静态变量赋初始值(不过静态代码块使用的比较少)。
继承中的构造函数问题中会使用到的关键字super:子类在继承父类时,在子类的构造函数当中,必须用super(参数)调用父类的构造函数(因为子类继承了父类中的成员变量和成员函数,但是没有继承父类的构造函数)。如果编程人员没有写调用父类构造函数的代码,那么编译器会默认以添加super()的方式来自动调用父类的无参数构造函数。如果编程人员写了调用父类函数的代码,编译器就不会自动调用父类的无参数构造函数了,而是直接调用编程人员写的构造函数!
还有一点和在子类中的构造函数调用同类中的构造函数时要用this的使用方法一样的是super的代码必须放在子类构造函数的第一行。
总之虽然无法继承父类的构造函数,但是可以用super(参数)方法调用父类的构造函数,从而解决成员变量赋值的重复代码问题
super不光可以用来调用父类的构造函数,还可以用来在子类中调用父类的成员函数。
this和super的相似性:this(参数)可以在本类的构造函数中调用本类的另一个构造函数或本类的成员函数;super(参数)可以在子类的构造函数中调用父类的构造函数或父类的成员函数!
方法的重写Overriding和重载Overloading是Java多态性的不同表现:重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。
重写Overriding是针对继承中父类与子类的同名函数而言(同时参数列表也是相同的);而重载Overloading是针对同一个类中的同名函数而言的(但是这些同名函数的参数列表不同)
在重写子类中的方法时,有必要的话(即子类的同名方法只是在父类同名方法的基础之上增加了一些代码,并没有否定父类同名方法中的代码),可以先使用“super.父类中同名的方法”的方式来在子类的方法中调用父类的同名方法,再把自己的代码写到子类的方法中,实现重写,又能避免重复代码。
不过还有一点上面的继承中的构造函数使用super不一样的是:这时候用到super的代码不一定非得写在第一行。根据自己希望程序执行的顺序自己决定。
对象的转型:首先要明确该语法在应用中的使用频率极其高。它是多态性的一个体现。
(1)对象的向上转型:将子类的对象复制给父类的引用。向上转型只要是父类与子类之间就一定会成功。
例如:Student s = new Student(); Person p = s; //其中Person是Student的父类
或者
Person p = new Student();//其中Person是Student的父类
在向上转型时,一个引用能够调用哪些成员(变量和函数),取决于这个引用的类型中有没有这个变量和函数,如果有就可以调用,如果没有就不能调用,不过还有一点需要注意:当父类与子类存在同名函数时,并且使用向上转型时,父类的引用将调用子类的方法。
使用向上转型还有一个好处:当父类有多个子类的时候,先声明一个父类的引用,形如:父类 变量名 = null;那么每个子类都可以对这个父类的引用向上转型,就可以只使用这一个父类的引用去在不同的情况下调用不同的子类的成员方法。会很灵活!
(2)对象的向下转型:将父类的对象赋值给子类的引用
例如:Student s1 = new Student();//先生成一个子类的对象
Person p = s1;//将子类的对象赋值给父类的引用
//注意:向下转型的前提是先进行向上转型,所以该代码的前两步是向上转型
Student s2 = (Student)p;//再把父类向下转型,一定要用如(Student)p方式,强制转换
注意:如果之前没有进行向上转型,而直接进行向下转型(即将父类的对象赋值给子类的引用),这样的方式是错的,编译时可能不会报错,但是在运行时会报异常。所以向下转型的前提是先进行向上转型,在用完向上转型后再用向下转型转回来,而不能直接用向下转型
抽象函数:只有函数定义,没有函数体的函数被称为抽象函数
形如:abstract void fun(参数列表) ; 注意:没有函数体,也不需要写{},因为只要有{}就相当于有函数体,只是函数体为空罢了。
抽象类:用abstract定义的类为抽象类(且必须要使用abstract),形如abstract class 类名{……}
(1)抽象类不能够生成对象,即抽象类无法实例化
(2)如果一个类当中包含有一个或一个以上的抽象函数,那么这个类必须声明为抽象类
(3)如果一个类当中没有抽象函数,那么这个类也可以被声明为抽象类
抽象类天生就是用来当爹的,他就是专门用来被继承的。继承它的子类可以生成对象。但是子类在继承抽象类的时候需要注意一点:如果没有在子类中复写所有的父类中的抽象函数,那么子类必须也要被声明为抽象类;要想不让子类再做抽象类,能够用子类来生成对象,就必须在子类中复写父类当中所有的抽象函数。
抽象类不能够生成对象,但是可以写这样的代码:抽象父类 变量
= new 非抽象子类();或抽象父类 变量
= null,使用向上转型或声明一个引用,这样不算对抽象类实例化。这样其实
是子类的实例化,因为变量名只是一个放在栈内存中的引用,而它的成员变量和成员函数都放到该引用所指向的堆内存中,即变量名并不是对象本身。
虽然抽象类不能实例化,但是抽象类中也需要有构造函数,用来在生成子类对象的时候被调用。因为抽象类是专门用来做父类给子类继承用的,而在生成子类对象的时候必须先调用父类的构造函数。
有的类无法写出一个它的子类可以通用的成员函数,必须让子类自己去实现它的时候,就可以把这个函数定义为抽象函数,把父类定义为抽象类,好处就是当写子类的代码时,可以强制子类必须去对这个抽象函数进行复写,就不可能忘记去实现该函数,否则就会出现语法错误,程序就无法编译通过,就相当于是对程序员的提醒。
软件包:将类放置到一个包当中,需要使用”package包名”的方式
软件包为java类提供了命名空间。
编译时需要使用-d参数,该参数的作用是依照包名生成相应的文件夹。-d后面需要写的是一个路径,也就是希望这个依照包名生成的文件夹放置的路径位置(.表示当前文件夹)。
一个类一旦打了包之后,类的名字也就发生了变化,应该是”包名.类名”,而且只能是这个名字,不能再用原来的类名了。
习惯上包名的命名规范:1.一般要求包名所有的字母都要小些;2.包名在一般情况下,是你的域名倒过来写(例如域名是perfect.org,则包名应该写成org.perfect),一般还会再加上一个自己给的有实际的单词(如:包名为org.perfect.user),包名中可以有点(.),如果有n个点就会相应多n级目录。
包中的权限问题:
public可以修饰类、成员变量和成员函数,在不同的包当中,一个类想要访问另外一个类的话(跨包访问),那么要求被访问的这个类必须有public权限,如果再想访问这个被访问的有public权限的类的成员变量和成员函数的话,也要求这些成员变量和成员函数拥有public权限!同一个包中的类当然也可以访问。
拥有private权限的成员变量和成员函数只能被本类访问,不能被其他的任何类访问,跟这些类在不在同一个包里无关!只能在本类内部使用,决不能被任何外部类使用。
如果不在类或成员变量或成员函数前面使用public或private或protected关键字的话,那么就默认它们的权限为default(不写权限修饰符就是default权限)。如果两个类在同一个包中,其中一个类可以访问另一个拥有default权限的类,也可以访问这个类中的拥有的default权限的成员变量和成员函数。但是如果想跨包访问的话,这样就不行了,就只能是上面所说的public权限才能跨包访问,即使跨包访问时如果被访问的类具有public权限,但是也只是能完成对类的访问,但是其中的没有被标记有public权限的成员变量和成员函数也是不能被访问的,即使这些成员变量和成员函数有default权限。
又有包又有继承时的访问权限问题:
一个类要想继承另一个包中的类,那么另一个类必须是public类型,但是其中的成员变量和成员函数不一定要是public类型,不过如果其中的成员变量和成员函数不是public权限的话,那么虽然可以继承它们,但是不能使用它们。
如果再同一个包里一个类去继承另外一个类,public或default权限的类可以被继承,而且被继承的类中的public或default权限的成员变量和成员函数可以被子类使用,而private权限的成员变量和成员函数虽然可以被继承,但是子类不能使用它们。
protected权限首先拥有和default一样的功能,但是default可以修饰类、成员变量和成员函数,而protected不能修饰类,只能修饰成员变量和成员函数。除此之外,当跨包继承时,protected权限的成员变量和成员函数不光可以被继承,还可以在子类当中使用它们。和public不同的是:如果成员变量或成员函数为public权限,不光在继承时外包或本包中的子类可以使用它们,其他的任何类都可以使用它们,而protected权限的成员变量或成员函数只能在无论外包或本包中在子类中被使用,但是不能被任何其他非子类使用。
如果子类和父类不在同一个包中,则子类虽然能继承父类当中的default权限的成员变量和成员函数,但是不能进行使用。而如果两个类在同一个包里的话,子类能继承父类当中的default权限的成员变量和成员函数,也能进行使用
由严到松:private < default < protected < public
类、成员变量、成员函数的权限应该尽可能的严,这样才能体现出面向对象的封装性。
不同包中的类的导入(关键字import):形如:import 包
.类
,就可以在不同的包里使用其他的包里的类时不要在将类名写成如:包名.类名 这种比较繁琐的形式,直接用 类名 形式就可以了,因为已经用import导入这个类了,说明这个类是在原来那个包里了,就不要在写那么长的 包名.类名 形式了。
注意:用import com.perfect.* 的形式就是表明向现在的包中导入这个com.perfect中的所有的类
接口:接口的使用方法真正的体现了面向对象的精髓所在。对接口的掌握情况基本上决定了对于面向对象的编程思想的掌握情况!重要性不言而喻!!!
定义了接口就是定义了调用对象的标准!
关键字:interface。可以把接口看成是一个纯粹的抽象类(所以抽象类的语法在接口都可以用),因为接口当中的方法都是抽象方法,但是接口中接口名和其中的方法不需要用关键字abstract,而接口名要用interface(形如:interface 接口名{}),因为接口默认就是抽象类,里面的方法默认就是抽象方法,而且接口当中的方法都默认是public权限,public虽然不需要显式声明,但是最好养成在这个时候显式写一下public的习惯。接口与类在这两点是不同的!
接口不能用来生成对象。但是可以这样用,形如:接口名 变量名 = null,注意这并不是生成对象,不是实例化,只是声明一个引用,引用并不是实例,因为变量名只是一个放在栈内存中的引用,而它的成员变量和成员函数都放到该引用所指向的堆内存中,即变量名并不是对象本身。这样的声明方式是为了用来向上转型的使用。
也可以用一个类来实现接口(可以把实现看成特殊的继承),但是不使用extends关键字,而是使用implements关键字。不过要在子类中复写接口中的所有方法,因为接口中的所有方法都是抽象的。当然也可以使用向上转型。
一个类只能够继承一个父类,不同于类与类之间继承的语法的是一个类可以实现多个接口(使用的形式如下所示:class 类1 implements 接口1,接口2{}),而且一个接口可以继承多个接口(接口继承接口时要用关键字extends,使用形式如下:class 接口 extend2 接口1,接口2{})。这一点要和类的继承区别清楚!
当一个类实现多个接口的时候,可以向上转型到接口1去用与接口1相关的方法,也可以向上转型到接口2去用与接口2相关的方法……以此类推。
工厂方法模式:(百度)工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
(Mars)把生成对象的代码,也就是使用new调用构造函数的代码封装在工厂类当中,这样类的使用者来说不需要去选择new某个具体的类,只需要调用工厂类的一个方法就可以了(因为工厂类里的方法会实现根据情况选择去new哪个类),方便用户,而且当类的种类……变化时,不需要对new类的代码进行修改,只需去修改工厂类的方法就可以了。这样既能减少重复代码,又能方便代码适应现实情况的变化。
java中的异常(Exception):异常是中断了正常指令流的事件,当出现异常时,异常前面的代码正常运行,当运行到异常时,异常就会中断正常指令流,后面的代码就不会被执行了。可以认为异常是一种对象,异常对象是在出现异常时由虚拟机帮我们产生的,不是我们自己生成的。异常不是在编译时出的错误,也就是说异常不是语法错误,而是在运行过程中出了一些事故…导致运行失败。
Exception分为uncheck Exception(包括RuntimeException和RuntimeException的子类)和check Exception(包括Exception以及Exception除了RuntimeException之外的所有子类)。
java的应用程序是运行在虚拟机上的。程序产生了Error则虚拟机就会停止工作,那么程序也就无法执行了(程序员对于Error无能为力),但是如果遇到Exception,程序员可以进行进行异常处理然后就可以继续工作了。
try…catch…finally结构的使用方法:把有可能出现异常(异常的出现是概率性的,不是必然的)的代码放到try{}中,如果出现了异常,那么就会自动跳到catch{}中来执行catch{}中以及其之后的代码;如果不出异常,那么catch{}中的代码就不会执行,但是catch{}之后的代码没有受影响,这样就保证了对异常的处理,并且不影响后面代码的执行。对于check Exception,无论是java提供的还是程序员自己定义的,都必须进行try…catch…处理或声明(编译器会强制程序员进行异常处理),而uncheck Exception就不是一定要去进行try…catch…处理的(编译器
强制程序员进行异常处理)。
无论try{}中的代码出不出现异常,finally{}中的代码都会被执行。所以finally中常做一些清理工作,比如:关闭文件、关闭数据库、关闭socket……
把可能出现异常的代码放到try{}里面,把处理异常的代码放到catch{}里面,而且至少要在catch{}里面把异常打印出来,把清理资源、释放资源的代码放到finally{}里面。
对异常的处理关系到系统的健壮性。
throw和throws:打不开文件、连不上网络……是java已经提供了的异常,但是还有一些在现实中可能出现的异常,比如年龄赋值为小于0的数,但是java没有提供这方面的异常,那么就需要程序员判断数据正确与否,去生成一个异常对象,再抛出这个异常。
Exception分为uncheck Exception,又叫运行时异常(包括RuntimeException和RuntimeException的子类)和check Exception,又叫编译期异常(包括Exception以及Exception除了RuntimeException之外的所有子类)。
throw用来抛出异常,throws用来声明异常,对于uncheck Exception可以只用throw而不用throws,但是对于check Exception而言throw和throws都要用到,因为对于check Exception编译器会强制程序员进行异常处理。
对于check Exception,无论是java提供的还是程序员自己定义的,都必须进行try…catch…处理或声明,声明的作用是让别人在调用这程序时,提醒他一定要进行异常处理。总之一定要进行异常处理,无论是自己处理,还是通过声明来告诉别人去处理。
java中的IO:文件的压缩和解压缩、图片的读取、视频的处理……都要用到IO操作的知识。
IO操作的目标:从数据源当中读取数据,以及将数据写入数据目的地当中。
流向:输入
数据进入程序中;输出
数据从程序中出来。
为什么叫“流“(输入流、输出流),以为输入或输出文件或数据的时候不是一次性的一下子就输入或输出了的,而是像流水一样,一点一点流入或流出。
IO流的3种分类方法:1.输入流和输出流;2.字节流和字符流;3.节点流和处理流。
java中有关IO的类都放在java.io这个包里面了。
字节流:字节流读写文件时,以字节为基础,每次读取/写入一个或多个字节。
字节流中的核心类:InputStream(它是所有字节输入流的父类,是抽象类。它的最常用的子类是FileInputStream,用int read(byte [] b , int off , int len)(返回值是读取的字节的个数,当读完之后返回值为-1)方法从文件向java程序中读入数据)和OutputStream(它是所有字节输出流的父类,也是抽象类。它的最常用的子类是FileOutputStream,用void write(byte [] b , int off , int len)方法从java程序向文件中写入数据)。FileInputStream和FileOutputStream分别用来从硬盘文件读数据和向硬盘文件当中写入数据。
字符流:字符流读写文件时,以字符为基础,每次读取/写入一个或多字符。
字符流中的核心类:Reader(它是所有字符输入流的父类,是抽象类。它的最常用的子类是FileReader,使用int read(char [] c , int off , int len)方法(返回值是读取的字符的个数,当读完之后返回值为-1)从文件向java程序中读入数据)和Writer(它是所有字符输出流的父类,是抽象类。它的最常用的子类是FileWriter,使用void write(char [] c , int off , int len)方法从java程序向文件中写入数据)
大文件的读写:当文件很大时,使用字节流或字符流的方法的话,不可能一次性地要读取文件中的数据放到一个数组中,再一次性地写入到要写入的文件中,这时候就可以使用循环的方法:一次读取一部分到数组中,接着写入文件,如此循环,直到读写完所有的数据。
IO操作的时候会产生编译期异常,所以要进行try…catch…处理。
最后要在finally里把输入流和输出流关闭。不过关闭文件还可能出现异常,所以还得再在finally中用try…catch…去处理异常,(不过这是并不需要再用finally了)。
处理流:比如BufferdedReader字符输入处理流,是Reader的子类,常使用它的public String readLine()方法,实现一行一行的读入数据(遇到\n或\r的时候就认为一行数据读取结束)(返回值就是读取的那一行数据,当读取完成后,也就是后面没有数据了之后,返回值就为null)。生成BufferedReader对象的方法:BufferedReader in = new BufferedReader(new FileReader(“文件的路径”));也可以不用FileReader,用其他的但同样要是继承了Reader的类。凡是要使用处理流的时候,都要先生成一个节点流,处理流在节点流的基础上新添加了一些功能,因为readLine()方法并不是直接从文件中读取数据,而是先使用节点流从文件中读取数据,再在readLine()当中处理这些使用节点流从文件中读取出来的数据。这里FileReader作为节点流,BufferedReader作为处理流,处理流是用来装饰节点流的(处理流是装饰者,节点流是被装饰者,装饰者是给被装饰者添加新的功能的)。这里用到了装饰者设计模式
装饰者设计模式(链接):java的23种设计模式之一,英文叫Decorator Pattern,又叫装饰模式。装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。特点如下:
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的引用(reference)
(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
内部类和匿名内部类:内部类就是一个类定义在另一个类里面,内部类编译后将会生成两个文件,一个是外部类.class,另一个是外部类$内部类.class,那么生成一个内部类的对象的方法是:外部类.内部类 内部类对象 = new 外部类() . new 内部类();或者外部类 外部类对象a = new 外部类();外部类.内部类 内部类对象 = 外部类对象a . new 内部类();(也就是要想生成一个内部类对象,首先要生成一个外部类对象)
如果B是A的内部类,那么在B当中就可以随意使用A中成员变量和成员函数,但是并不是说B继承了A,B可以使用A中的成员变量和成员函数,但是B并没有拥有A中的成员变量和成员函数。因为使用外部类.内部类 内部类对象 = new 外部类().new 内部类();(也就是要想生成一个内部类对象,首先要生成一个外部类对象)的内部类对象的生成方法,可以看出每个内部类的对象都与一个外部类的对象相关联,有内部类对象就必然有一个与它相关联的外部类对象,所以在B当中就可以随意使用A中成员变量和成员函数。
匿名内部类:匿名内部类一般都是写在函数参数里的。……
java中的线程:创建线程的方法
方式1:定义一个线程类,它继承了Thread并重写了其中的方法run(),方法run()称为线程体,由于Java只支持单继承,用这种方式定义的类就不能再继承其他类了。这时还有一点要注意,要想在其他线程中插入这个线程的代码,为了能产生两个线程,要用start()方法,而不要用run()方法,因为要使用run()方法的话,其实就相当于是一个线程。只有用start()方法才相当于是两个线程。要想启动新线程,要用start()方法,而不用run()方法。
方式2:提供一个实现接口Runnable的类作为线程的目标对象,并复写Runnable的run()方法。在初始化一个Thread类或Thread子类的线程对象时,把目标对象即Runnable对象传递给这个线程实例,由该目标对象提供线程体。形如:RunnableImpl ri = new RunnableImpl(); //RunnableImpl实现了Runnable接口 Thread t =new Thread(ri); t.start();注意这里要启动新线程,还是使用start()方法。还有一点需要注意的是:多个Thread类的对象可以共用一个实现Runnable的类的对象作为参数,那么它们也会共用这一个继承了Runnable的类中的变量等。
区分start()和run():使用start()主要的作用是为了启动线程,当线程运行的时候,线程执行的是run()里的代码。
在实际开发中,更倾向于第二种方法,其中一个原因就是实际开发中更倾向于实现接口,而不是去继承类,因为java的语法只允许继承一个类,所以就会有局限;还有一个原因:把线程体(即实现Runnable的类的对象)和执行线程(即Thread或继承了Thread的类的对象)拆分开了。
sleep(时间)方法是线程休眠,休眠时线程处于阻塞状态,休眠时间到了并不意味着它就能一定被执行,而是进入就绪状态,再去和其他线程抢占CPU资源,所以可能休眠时间到了,再过一段时间才能被执行。
yield()方法是静态方法,正在使用CPU的线程让出CPU,就是暂时让CPU清空正在运行的线程,再让所有的线程再去竞争CPU的使用权,但是它让出CPU时,不意味着另外的就一定能抢到CPU,因为当这个线程使用yield()方法后会让出CPU,但是让出后,它又去抢占CPU,所以可能还是它再去抢到CPU!
线程的优先级:Thread对象的getPriority()方法是获得线程的优先级,默认的优先级是5,setPriority(参数)方法是设置线程的优先级。优先级为Thread.MIN_PRIORITY也即是1到Thread.MAX_PRIORITY也即是10,用法,例如:Thread t = new Thread(); t.setpriority(Thread.MIN_PRIORITY)。Thread.MIN_PRIORITY和Thread.MAX_PRIORITY是Thread的静态常量
优先级越高的线程,它执行的概率就越大,而不是一定它就先执行。
综上两条可以看出,线程的运行不会是一定的,是概率性事件。线程的使用中,只有概率的高低,没有一定与一定不!
每一个线程都有名字,可以通过Thread对象的setName()方法设置线程名字,也可以使用getName()方法获取对象的名字。
线程的同步:Thread.currentThread()方法时Thread的静态方法,就是为了获得当前的代码正在那个线程中运行。在下面这样的情况下使用这个方法会有用:多个Thread类的对象可以共用一个实现Runnable的类的对象作为参数。就可以用来获得到底是在哪个Thread类对象中运行的。
多个Thread类的对象可以共用一个实现Runnable的类的对象作为参数,这时它们也会共用这一个继承了Runnable的类中的变量等,多线程共用同一份数据时就可能出现错误。这个时候就需要在实现Runnable的类中用到同步代码块,关键字是synchronized,形如:synchronized (this){……},这个方法的作用是:保证一个线程在执行该代码块里的代码时,不会有其他代码也来执行这段代码(不是其他线程没来抢占,而是就算它们来抢占CPU了,就算它们抢到CPU了,在这种情况下,它也没有资格执行代码块里的代码,因为有线程正在占用该代码块,占有了该代码块的锁,所以它就会再进入等待状态,另外的占有代码块的线程就又会获得CPU资源继续执行代码块里的代码,直到执行完代码块里的代码),只有当该线程执行完了代码块里的代码后,释放了该代码块的锁,其他线程才有资格再来占有该代码块里的锁,进而去执行代码块里的代码。这就类似于数据库中的锁机制,保证共享数据的一致性,保证共享数据的安全。(注意代码块的锁与CPU资源不是等价的!!只是再用了代码块锁机制后从效果上来看貌似是等价的,其实不是!!)
java中数据的定义和使用方法:容易搞混的一点就是数组的类型和数组中元素的类型是不一样的两个东西。
数组的静态和动态定义法:比如数据的静态定义方法(int arr[] = {1,2,3,4};或者int[] arr = {1,2,3,4};)其中arr是整型数组类型(数组类型是引用数据类型(引用数据类型:在栈内存中存的是引用,在堆内存中存储的才是这个对象的本体,类似于类的引用和类的实体的区别)),不是整型,而它其中的元素才是整型
比如有数组int arr[] = {1,2,3,4};,可以用arr.length(length是数组的参数)获得数组的长度
动态定义方法:形如:int arr[] = new int[10];就是创建一个arr数组,数组的长度是10,还是用arr.length(length是数组的参数)获得数组的长度。
定义了数组,如果没有给数组的元素赋值,则int数组类型数组的元素默认值为0、boolean数组型数组的数组元素默认值是false、char数组型数组的数组元素默认值是null。
二维数组:静态方法:int arr[][] = {{1,2},{2,3},{4,5}};或者int[][] arr= {{1,2},{2,3},{4,5}};动态定义方法:形如:int arr[][] = new int[10][10];
二维数组中length的使用:代码
int arr[][] = {{1,2,3},{4,5,6},{7,8,9}};
for(int i = 0;i < arr.length;i++){
for(int j= 0;j < arr[i].length;j++){
System.out.println(arr[i][j]);
}
}
注意二维数组的静态定义可以用以下形式int arr[][] = {{1,2,3},{4,5,6},{7,8}};最后的{}里的元素是2个,而不是像前面两个{}里的有3个,它并不要求一定要对齐。
数组的元素排序是从0开始的,虽然都说烂了,但是在这方面犯的错误还不少。容易犯数组越界的错误,java会抛出java.lang,ArrayIndexOutOfBoundsException的越界异常。而且数组一旦被声明了,那么它的长度在刚开始声明定义的时候给固定了,不能在以后去改变它的长度。
类集框架:集合虽然没有顺序,不像列表,但是因为它其中的元素不能重复,所以取元素的时候,有没有顺序无所谓,因为元素不重复,所以可以直接确定某一个元素