1,JRE:Java Runtime Environment,java程序的运行环境,java运行的所需的类库+JVM(java虚拟机)。
2,JDK:Java Development Kit,java的开发和运行环境,java的开发工具和jre。
3,配置环境变量:让java jdk\bin目录下的工具,可以在任意目录下运行。原因是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。
4,javac命令和java命令
javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm可以识别的字节码文件。也就是class文件,也就是java的运行程序。
java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行.
变量的作用域: 作用域从变量定义的位置开始,到该变量所在的那对大括号结束;
生命周期: 变量从定义的位置开始就在内存中活了;变量到达它所在的作用域的时候就在内存中消失了;
byte、short、int、long、float、double、char、boolean
3、& 和 &&区别:
& : 两边都运算(意为“全部”,两边都为真,结果才为真)。
&&: 如果左边为false,那么右边不参数与运算。
|: 两边都运算(意为“或者”,两边都为假,结果才为假)。
||: 如果左边为true,那么右边不参与运算。
重载:在一个类中,如果出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不同,即可称之为该函数重载了。
注:如何区分重载的方法是,当函数同名时,只看参数列表。和返回值类型没关系。
重写:父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。
区别1:
成员变量直接定义在类中。
局部变量定义在方法中,参数上,语句中。
区别2:
成员变量在这个类中有效。
局部变量只在自己所属的大括号内有效,大括号结束,局部变量失去作用域。
区别3:
成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。
局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。
给与之对应的对象进行初始化,它具有针对性,函数中的一种。
特点1:该函数的名称和所在类的名称相同。
特点2:该函数没有具体的返回值,不需要定义返回值类型。
记住:所有对象创建时,都需要初始化才可以使用。
注意事项1:一个类在定义时,如果没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,以便该类创建对象。如果在类中自定义了构造函数,那么默认的构造函数就没有了。
注意事项2:一个类中,可以有多个构造函数,因为它们的函数名称都相同,所以只能通过参数列表来区分。一个类中如果出现多个构造函数。它们的存在是以重载体现的。
构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块。
构造函数:是给与之对应的对象进行初始化。它具有针对性。
注1:执行顺序:(优先级从高到低)静态代码块>mian方法>构造代码块>构造方法。其中静态代码块只执行一次。构造代码块在每次创建对象是都会执行。
注2:静态代码块的作用:比如我们在调用C语言的动态库时会可把.so文件放在此处。
注3:构造代码块的功能:(可以把不同构造方法中相同的共性的东西写在它里面)。例如:比如不论任何机型的电脑都有开机这个功能,此时我们就可以把这个功能定义在构造代码块内。
this:代表对象。就是所在函数的所属对象的引用。哪个对象调用了this所在的函数,this就代表哪个对象,就是哪个对象的引用。
使用地点:
地点1:在定义功能时,如果该功能内部使用到了调用该功能的对象,就用this来表示这个对象。
地点2:this 还可以用于构造函数间的调用。
使用方法1:this对象后面跟上 “.”, 调用的是成员属性和成员方法(一般方法);
使用方法2:this对象后跟上 “()” ,调用格式:this(实际参数)。调用的是本类中的对应参数的构造函数。
注意:用this调用构造函数,必须定义在函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。
static:是一个修饰符,用于修饰成员(成员变量和成员函数)。static方法必须被实现,而不能是抽象的abstract。
static和final一块用表示什么
表示1: static final用来修饰成员变量和成员方法,可简单理解为"全局常量"(大家共享)!
表示2: 对于变量,表示一旦给值就不可修改,并且通过类名可以调用。
表示3: 对于方法,表示不可覆盖,并且可以通过类名直接调用。
备注1:在定义静态时,必须要明确,这个数据是否是被对象所共享的。因为特有数据会变成对象的共享数据会对事物的描述就出了问题。
备注2:静态方法只能访问静态成员,不可以访问非静态成员。
备注3:静态方法中不能使用this,super关键字。因为this,super代表对象,而静态在时,有可能没有对象,所以this无法使用。
备注4:成员变量和静态变量的区别:
成员变量所属于对象。所以也称为实例变量。 静态变量所属于类。所以也称为类变量。
成员变量随着对象创建而存在。随着对象被回收而消失。 静态变量随着类的加载而存在。随着类的消失而消失。
成员变量只能被对象所调用 。
静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。
备注5:静态代码块
就是一个有静态关键字标示的一个代码块区域。定义在类中。
作用:可以完成类的初始化。静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。
根据程序上下文环境,Java关键字final有"这是无法改变的"或者"终态的"含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变、设计或效率。
final类:不能被继承,没有子类,final类中的方法默认是final的。
final方法:不能被子类的方法覆盖,但可以被继承。
final成员变量:表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
abstract专门用于修饰抽象类。
特点1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。
特点2:抽象方法只定义方法声明,并不定义方法实现。
特点3:抽象类不可以被创建对象(实例化)。
特点4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法(implements实现)后,该子类才可以实例化。否则,该子类还是一个抽象类。
细节1:抽象类中是否有构造函数?
有,用于给子类对象进行初始化。
细节2:抽象类中是否可以定义非抽象方法?
可以。其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。
细节3:抽象关键字abstract和哪些不可以共存?
final , private , static
细节4:抽象类中可不可以不定义抽象方法?
可以。抽象方法目的仅仅为了不让该类创建对象。
封装就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系(一般是setter()、getter()是该对象对外开发的接口 )。也就是说用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。
private访问修饰符,对于封装而言,是最好的选择。若允许子类的成员来访问它们,这个时候就需要使用到protected。
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。对于封装而言,一个对象它所封装的是自己的属性和方法,所以它是不需要依赖其他对象就可以完成自己的操作。
封装可以使我们容易地修改类的内部实现,而无需修改使用了该类的客户代码。就可以对成员变量进行更精确的控制。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
继承所描述的是“is-a”的关系,如果有两个对象A和B,若可以描述为“A是B”,则可以表示A继承B,其中B是被继承者称之为父类或者超类,A是继承者称之为子类或者派生类。在继承关系中,继承者完全可以替换被继承者,反之则不可以,例如我们可以说猫是动物,但不能说动物是猫就是这个道理。
使用继承时需要记住三句话:
1、子类拥有父类非private的属性和方法,调用父类的构造方法我们使用super()即可。
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。
1. public class Person {
2. public void display(){
3. System.out.println("Play Person...");
4. }
5.
6. static void display(Person person){
7. person.display();
8. }
9. }
10.
11. public class Husband extends Person{
12. public static void main(String[] args) {
13. Husband husband = new Husband();
14. Person.display(husband); //向上转型
15. }
16. }
继承存在如下缺陷:
1、父类变,子类就必须变。
2、继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。
3、继承是一种强耦合关系。
将子类转换成父类,在继承关系上面是向上移动的,所以一般称之为向上转型。由于向上转型是从一个叫专用类型向较通用类型转换,所以它总是安全的,唯一发生变化的可能就是属性和方法的丢失。
到底要不要使用继承呢?问一问自己是否需要从子类向父类进行向上转型。如果必须向上转型,则继承是必要的,但是如果不需要,则应当好好考虑自己是否需要继承。
注意1:成员变量。
当子父类中出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
如果想要调用父类中的属性值,需要使用一个关键字:super
This:代表是本类类型的对象引用。
Super:代表是子类所属的父类引用。
注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了,再用Super调用即可。
注意2:成员函数。
当子父类中出现了一模一样的方法时,建立子类对象会运行子类中的方法。好像父类中的方法被覆盖掉一样。所以这种情况,是函数的另一个特性:重写
注意3:构造函数。
发现子类构造函数运行时,先运行了父类的构造函数。为什么呢?
原因:子类的所有构造函数中的第一行,其实都有一条隐身的语句super()。super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。
问1:super()和this()是否可以同时出现的构造函数中?
两个语句只能有一个定义在第一行,所以只能出现其中一个。
问2:super()或者this():为什么一定要定义在第一行?
因为super()或者this()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。
在方法覆盖(重写)时:
注意1:子类覆盖父类时,必须要保证,子类方法的权限必须大于等于父类方法权限。否则,编译失败。(举个例子,在父类中是public的方法,如果子类中将其降低访问权限为private,那么子类中重写以后的方法对于外部对象就不可访问了,这个就破坏了继承的含义)
注意2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)
注意3:继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。这时如何解决问题呢?介绍一个关键字,final。
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
编译时多态:主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数。
子类Child继承父类Father,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类Father对象,也可以处理子类Child对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。
实现多态有三个必要条件:继承、重写、向上转型。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,该引用才能够具备技能调用父类的方法和子类的方法。
在Java中有两种形式可以实现多态:继承和接口。
基于继承实现的多态:
是通过重写父类的同一方法的几个不同子类来体现的。对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。
基于接口实现的多态:
就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。
运行结果:
分析如下:
①②③比较好理解,一般不会出错。④⑤就有点糊涂了,为什么输出的不是"B and B”呢?
当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。(但是如果强制把超类转换成子类的话,就可以调用子类中新添加而超类没有的方法了。)
在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
上面程序中的A,B,C,D存在如下关系:
分析4:
a2.show(b),a2是一个引用变量,类型为A,则this为a2,b是B的一个实例,于是它到类A里面找show(B obj)方法,没有找到,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为B,(super)O即(super)B即A,因此它到类A里面找show(A obj)的方法,类A有这个方法,但是由于a2引用的是类B的一个对象,B覆盖了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为"B and A”。
分析5:
a2.show(c),a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。
分析8:
b.show(c),b是一个引用变量,类型为B,则this为b,c是C的一个实例,于是它到类B找show(C obj)方法,没有找到,转而到B的超类A里面找,A里面也没有,因此也转到第三优先级this.show((super)O),this为b,O为C,(super)O即(super)C即B,因此它到B里面找show(B obj)方法,找到了,由于b引用的是类B的一个对象,因此直接锁定到类B的show(B obj),输出为"B and B”。
按照同样的方法我也可以确认其他的答案。
当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这我们用一个例子来说明这句话所代表的含义:a2.show(b);
这里a2是引用变量,为A类型,它引用的是B对象,因此按照上面那句话的意思是说有B来决定调用谁的方法,所以a2.show(b)应该要调用B中的show(B obj),产生的结果应该是“B and B”,但是为什么会与前面的运行结果产生差异呢?这里我们忽略了后面那句话“被调用的方法必须是在超类中定义过的”,那么show(B obj)在A类中存在吗?根本就不存在!所以这句话在这里不适用?那么难道是这句话错误了?非也!其实这句话还隐含这这句话:它仍然要按照继承链中调用方法的优先级来确认。所以它才会在A类中找到show(A obj),同时由于B重写了该方法所以才会调用B类中的方法,否则就会调用A类中的方法。
注:如何判断对象是哪个具体的类型呢?可以可以通过一个关键字 instanceof ;
格式:<对象 instanceof 类型>
例: Student instanceof Person ==> true; //student继承了person类
接口是用关键字interface定义的。接口中包含的成员,最常见的有全局常量、抽象方法。
成员变量:public static final
成员方法:public abstract
interface Inter{ public static final int x = 3; public abstract void show(); }
接口必须由其子类实现接口中所有的抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。
类与类之间存在着继承关系(extends修饰),类与接口中间存在的是实现关系(implements修饰)。
注:接口与接口之间存在着继承关系,接口可以多继承接口。
java类是单继承的。classB Extends classA
java接口可以多继承。Interface3 Extends Interface0, Interface1, interface……
原因分析:不允许类多重继承的主要原因是,如果A同时继承B和C,而b和c同时有一个D方法,A如何决定该继承那一个呢?但接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口。
抽象类与接口的区别:
抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。
接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。
抽象类和接口的区别:
区别1:抽象类只能被继承,而且只能单继承。接口需要被实现,而且可以多实现。
区别2:抽象类中可以定义非抽象方法,子类可以直接继承使用。接口中都是抽象方法,需要子类去实现。
区别3:抽象类的成员修饰符可以自定义。接口中的成员修饰符是固定的。全都是public的。
内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。
--java.lang.Throwable:
Throwable :可抛出的。 |--Error :错误,一般情况下,不编写针对性的代码进行处理。 |--Exception:异常,可以有针对性的处理方式
可抛性的体现:就是这个体系中的类和对象都可以被throws和throw两个关键字所操作。
throw与throws区别:
- throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。
throw:就是自己进行异常处理,处理的时候有两种方式
方式一:自己捕获异常(也就是try catch进行捕捉);
方式二:声明抛出一个异常(就是throws 异常~~)。
- throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在函数上。
throws:将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。
throws格式:方法名(参数)throws 异常类1,异常类2,.....
处理方式有两种:1、捕捉(try);2、抛出(throws)。
对于捕捉:java有针对性的语句块进行处理。
try { 需要被检测的代码; } catch(异常类 变量名){ 异常处理代码; } fianlly{ 一定会执行的代码; }
记住:finally很有用,主要用户关闭资源。无论是否发生异常,资源都必须进行关闭。
定义异常处理时,什么时候定义try,什么时候定义throws呢?
(1)功能内部如果出现异常,如果内部可以处理,就用try;
(2)如果功能内部处理不了,就必须声明出来,就用throws,让调用者处理。使用throws抛出,交给调用者处理。谁调用了这个功能谁就是调用者;
注意:
如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的;如果子类的覆盖的方法中出现了异常,只能try不能throws。
(1)返回当前线程的名称:Thread.currentThread().getName()
(2)线程的名称是由:Thread-编号定义的。编号从0开始。
(3)线程要运行的代码都统一存放在了run方法中。
(4)线程要运行必须要通过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)
Thread类中run()和start()方法的区别:
start():用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
run():如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:start()方法最本质的功能是从CPU中申请另一个线程空间来执行 run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行。
创建线程
方式一:继承Thread ,由子类复写run方法。
步骤:
(1)定义类继承Thread类;
(2)目的是复写run方法,将要让线程运行的代码都存储到run方法中;
(3)通过创建Thread类的子类对象,创建线程对象;
(4)调用线程的start方法,开启线程,并执行run方法。
线程状态:
被创建:start() 冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格; 消亡:stop()
方式二:实现一个接口Runnable。
步骤:
(1)定义类实现Runnable接口。
(2)覆盖接口中的run方法(用于封装线程要运行的代码)。
(3)通过Thread类创建线程对象;
(4)将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
(5)调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
Ticket t = new Ticket(); /* 直接创建Ticket对象,并不是创建线程对象。 因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。 所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。 */ Thread t1 = new Thread(t); //创建线程。 /* 只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联 为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。 */ t1.start();
好处:解决了线程安全问题。Synchronized,将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行(被“锁”起来)
格式:
synchronized(对象) { //任意对象都可以。这个对象就是共享数据。 需要被同步的代码; }
弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。
同步代码块和同步函数的区别?
(1)同步代码块使用的锁可以是任意对象。
(2)同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
在一个类中只有一个同步的话,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
解决线程安全问题使用同步的形式,LOCK的出现替代了同步:
lock.lock();………lock.unlock();
获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法await()、signal()、signalAll()体现新版本对象的好处。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
用于存储数据的容器。
在使用一个体系时,原则:参阅顶层内容。建立底层对象。
我们用得最多的Java容器可分为两大类:
(1)Collection
(2)Map
List---ArrayList
是有序的集合(存储有序)
底层是数组,可以按位置索引号取出某个元素
允许元素重复和为null
ArrayList是非同步的
Set---HashMap
实现Map接口
允许为null
大都使用的是put方法来添加元素
HashMap是非同步的
区别一:ArrayList和LinkedList
ArrayList的底层是数组,LinkedList的底层是双向链表。
一般来说ArrayList的访问速度是要比LinkedList要快的
一般来说LinkedList的增删速度是要比ArrayList要快的
总体来说,如果数据量有百万级的时,还是ArrayList要快。(我测试过)
区别二:List和Map
(1)存储结构不同:
List是存储单列的集合(数组)
Map存储的是key-value键值对的集合(字典)
(2)元素是否可重复:
List允许元素重复
Map不允许key重复
(3)是否有序:
List集合是有序的(存储有序)
Map集合是无序的(存储无序)
与Java集合框架相关的有哪些最好的实践
第一步,根据需要确定集合的类型。
如果是单列的集合,我们考虑用Collection下的子接口ArrayList和Set。
如果是映射,我们就考虑使用Map~
第二步,确定使用该集合类型下的哪个子类~我认为可以简单分成几个步骤:
(1)是否需要同步:去找线程安全的集合类使用
(2)迭代时是否需要有序(插入顺序有序):找Linked双向列表结构的
(3)是否需要排序(自然顺序或者手动排序):去找Tree红黑树类型的(JDK1.8)
小结:
估算存放集合的数据量有多大,无论是List还是Map,它们实现动态增长,都是有性能消耗的。在初始集合的时候给出一个合理的容量会减少动态增长时的消耗~
使用泛型,避免在运行时出现ClassCastException
尽可能使用Collections工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提供代码重用性,它有着更好的稳定性和可维护性
(一)java中ArrayList的概念和用法
构造函数
ArrayList中定义了三种构造方法
1.ArrayList()----------------构造了空的链表。
2.ArrayList(Collection
3.ArrayList(int initialCapacity)---------构造了一个大小确定但内容为空的链表。initialCapacity参数表示初始容量大小。
常用的一些方法
1.添加元素
(1)在List尾部添加元素:void add(E element);
(2)在指定位置添加元素:void add(int index, E element);
(3)插入其他集合全部的元素:Boolean ArrayList(int index, Collection extends E> c);
2.删除元素
(1)删除所有元素:void clear();
(2)删除指定元素:E remove(int index);
(3)删除从某一个位置开始到某一个位置结束的元素:
protected void removeRange(int start, int end);
3.修改元素
E set(int index, E element);
4.获取(查询)元素
(1)查询指定范围内的元素:List subList(int fromIndex, int toIndex);
(2)查询指定位置的元素:E get(int index)
(3)查询元素位置:
int indexOf(Object a);--输出list集合中第一次出现a的位置,若list集合中没有a对象,则返回-1
lastIndexOf(Object a); --输出list集合中最后一次出现a的位置,若list集合中没有a对象,则返回-1
(4)是否包含某个特定的元素:Boolean contains(Object a);
5.检查该数组是否为空
boolean isEmpty();--返回true表示没有任何元素。
6.获取该数组的长度(包含元素的个数)
int size();
代码实例
public static void main(String[] args) { Listnames=new ArrayList (); /** * List 是一个接口 * ArrayList是实现List接口的实现类,内部是一个大小可变的数组 * 代表泛型 ,泛型的意思是ArrayList中存的值类型 **/ //ArrayList变量.add("泛型的数据"):添加数据 names.add("张安顺"); names.add("李四"); //ArrayList变量.size():返回大小 for(int i=0;iArraylist变量.iterator()把数组中的值放到了迭代器中,然后在迭代器中遍历 //iterator.hasNext():下标后移,并且判断是否有数据了 iterator.next()就是值 Iterator iterator = names.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("******************************************"); //ArrayList变量.remove(index):表示把下标为index的值删掉,后面的值下标向前移动 names.remove(1); Iterator it=names.iterator(); while(it.hasNext()) { System.out.println(it.next()); } System.out.println(names.get(1)); } }
(二)Java中HashSet的常用方法
注:在Map的实现类上穿了一层衣服就成了Set
方法概览
add(Object o)和addAll(Collection c)增加元素
contains(Object o)和containsAll(Collection c)判断元素是否存在
isEmpty()判断集合是否为空
remove(Object o)和removeAll(Collection c)删除元素
size()返回集合的大小
clear()清空集合
iterator()迭代器
toArray()将内容转到数组中
遍历
HashSet作为集合,有多种遍历方法,如普通for循环,增强for循环,迭代器
package com.xt.set; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class FunctionTest { public static void main(String[] args) { Setnames = new HashSet<>();//jdk7.0后实例化时<>中可以不加 //HashSet变量.add("嗷嗷"):把嗷嗷加入到集合中 names.add("张三"); names.add("李四"); //遍历方法不能用传统遍历方法,因为HashSet类中没有get方法 //第一种遍历方法:加强for循环 for (String name : names) { System.out.println(name); } //第二种遍历方法:迭代器遍历 Iterator iterator = names.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); } //HashSet变量.clear():清除所有的数据 names.clear(); //HashSet变量.idEmpty(): 判断是否为空 System.out.println(names.isEmpty()); } }
(三)Java中HashMap的常用方法
方法概览:
put(Object key,Object value)和putAll(Collection c)添加映射
get(Object key)根据键来获取对应的值
containsKey(Object key)和containsValue(Object value)
remove(Object key)
values()
isEmpty()
entrySet()
keySet()
遍历HashMap的5种方式
方式1-通过ForEach循环进行遍历
Mapmap = new HashMap (); map.put(1, 10); map.put(2, 20); // Iterating entries using a For Each loop for (Map.Entry entry : map.entrySet()) { System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); } }
方式2-ForEach迭代键值对方式
如果你只想使用键或者值,推荐使用如下方式
// 迭代键 for (Integer key : map.keySet()) { System.out.println("Key = " + key); } // 迭代值 for (Integer value : map.values()) { System.out.println("Value = " + value); } }
方式3-使用带泛型的迭代器进行遍历
Iterator> entries = map.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = entries.next(); System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); }
方式4-使用不带泛型的迭代器进行遍历
Iteratorentries = map.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); Integer key = (Integer) entry.getKey(); Integer value = (Integer) entry.getValue(); System.out.println("Key = " + key + ", Value = " + value); }
5、通过Java8 Lambda表达式遍历
map.forEach((k, v) -> System.out.println("key: " + k + " value:" + v)); }
(四)Java集合之LinkedList常用方法
LinkedList使用的是链表结构
(1)add(E e) 添加一个元素
(2)add(int index, E element) 添加一个元素到指定位置
(3)get(int index) 获取指定位置的元素
(4)remove(Object o) 删除指定的元素
(5)set(int index, E element) 在指定位置节点设置值
(6)indexOf(Object o) 查询指定元素值所在位置
lastIndexOf也一样,只是从尾部节点开始查询
(7)peek() 获取头节点的元素值,但不删除头节点
poll() 获取头节点的元素值,并删除头节点
pop() 获取头节点的元素值,并删除头节点,头节点为空则抛出异常
(8)offer(E e) 添加新元素到末尾;push(E e) 添加新元素到头节点
注意1、LinkedList作为双向链表,维护了头尾节点,头尾节点的插入比较方便,中间数据的插入需要遍历查询再做插入
注意2、查询指定位置时,使用了一次二分,最多需要遍历一半的节点