类和对象;
类: 构造器,Filed和方法
Static: static修饰的成员表明它属于这个类本身,而不属于该类的单个实例。通常把static修饰的Field和方法也称为类Field,类方法。不使用static修饰的普通方法,Field则属于该类的单个实例。
Static 的真正作用是用于区分Field,方法,内部类,初始化块,这四种成员到底属于类本身还是属于实例。
构造器语法: 修饰符,构造器名,形参列表
对象,引用和指针:
引用变量里存放的仅仅是一个引用,它指向实际的对象。实际对象存放在堆内存中。
实际上java里的引用就是C里的指针,只是java语言把这个指针封装起来,避免开发者进行繁琐的指针操作。
因此,当一个对象被创建成功以后,这个对象将保存在堆内存中,java程序不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象。
如果希望通知垃圾回收机制回收某个对象,只需切断该对象的所有引用变量和它之间的关系即可,也就是把这些引用变量赋值为null.
this作为对象的默认引用:
1.构造器中引用该构造器正在初始化的对象
2.在方法中引用调用该方法的对象
this所代表的对象只能是当前类,只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this就代表谁
java编程时,不要使用对象去调用static修饰的Field,方法,而是应该使用类去调用static修饰的Field,方法。
this引用也可以用于构造器中作为默认引用,由于构造器是直接使用new关键字来调用,而不是使用对象来调用的,所以this在构造器中引用的是该构造器进行初始化的对象。
当this作为对象的默认引用使用时,程序可以像访问普通引用变量一样来访问这个this引用,甚至可以把this当成普通方法的返回值。
同一个类的一个方法调用另外一个方法时,如果被调方法是普通方法,则默认使用this作为调用者,如果被调方法是静态方法,则默认实用类作为调用者。
方法的参数传递机制
java里方法的参数传递方式只有一种:值传递,所谓值传递,就是将实际参数值的副本传入方法内,而参数本身不会受到任何影响。
方法重载:
java允许同一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含了两个活两个以上的方法名相同,但形参列表不同,则被称为方法重载。
方法三要素: 调用者,方法名,形参列表。
成员变量和局部变量
java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this或类名作为调用者来限定访问成员变量。
static变量在堆内存中只有一块空间,无论哪个实例访问,都是同一块内存,所以当程序需要访问类field使,尽量使用类作为主调,而不要使用对象作为主调。
局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中,占内存中的变量无须系统垃圾回收,往往随方法或代码的运行结束而结束
当我们定义一个成员变量时,成员变量将被放置到堆内存中,成员变量的作用域将扩大到类存在范围或者对象存在范围,这种范围的扩大有两个害处:
1.增大了变量的生存时间,这将导致更大的内存开销
2.扩大了变量的作用域,这不利于提高程序的内聚性。
考虑成员变量的情形:1.某个类的固有信息。2.某个类或者实例运行的状态信息
3.某个需要在某个类的多个方法之间进行共享
即使在程序中使用局部变量,也应该尽可能地缩小局部变量的作用范围,局部变量的作用范围越小,它在内存里停留的时间就越短,程序运行性能就越好,因此,能用局部变量的地方,就尽量不用方法局部变量。
隐藏和封装:
如果一个java类的每个Field都被使用private修饰,并为每个Field都提供了public修饰setter和getter方法,那么这个类就是一个复合JavaBean规范的类
类里的绝大部分Field都应该使用private修饰,只有一些static修饰的,类似全局变量的Field,才可能考虑使用public修饰
如果某个类主要用作其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用protected 修饰这些方法。
希望暴露出来的给其他类自由调用的方法应该使用public修饰。
Java.lang这个包包含了java语言的核心类,String,Math, System, Thread类
Java.util这个包包含了Java的大量工具类/接口和集合框架类/接口,arrays,list和set等
Java.net包含了一些java网络编程相关的类/接口
Java.io这个包包含了一些java输入输出编程相关的类/接口
Java.text 包含了一些java格式化相关的类
Java.sql 包含了java进行JDBC数据库编程相关类/接口
深入构造器:
当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象在构造器执行之前就已经产生了,只是不能被外部程序访问,只能在构造器中通过this来引用,当构造器执行体结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用类型的变量,这样外部程序可以访问该对象。
因为构造器主要用于被其他方法调用,用以返回该类的实例,因而通常把构造器设置成public访问权限,从而允许系统中任何类来创建该类的对象,如果设置为protected,主要用于被其子类调用,把其设置为private,阻止其他类创建该类的实例。
PS:第三个构造器通过this来调用另一个重载构造器的初始化代码,程序中this(),调用表明调用该类的另一个有两个字符串参数的构造器
类的继承:
子类包含与父类同名方法的现象被称为方法重写,也称为方法覆盖,可以说子类重写了父类的方法,也可以会所子类覆盖了父类的方法
方法的重写要遵循两同两小一大的规则,“两同”即方法名相同,形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。
如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。
如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。
super用于限定该对象调用它从父类继承得到的Field或方法,super也不能出现在static修饰的方法中。
创建任何对象总是从该类所在继承树最顶层类的构造器开始执行,然后依次向下执行,最后才执行基类的构造器,如果某个父类通过this调用了同类重载的构造器,就会依次执行此父类的多个构造器。
多态
Java引用变量有两种类型: 一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。
相同类型的变量,调用同一个方法时呈现出来多种不同的行为特征,这就是多态。
(type)variable这种用法可以将variable变量转换成一个type类型的变量
在强制类型转换之前,用instanceof运算符判断是否可以成功转换,从而避免ClassCastException异常。
继承与组合:
继承最大的坏处:破坏封装,采用组合方式来实现类重用则能提供更好的封装性。
设计父类的原则: 尽量隐藏父类的内部数据
不要让子类可以随意访问,修改父类的方法
尽量不要在父类构造器中调用将要被子类重写的方法
如果想把某些类设置成最终类,即不能被当成父类,则可以使用final修饰这个类,使用private修饰这个类的所有构造器,从而保证子类无法调用该类的构造器,也就无法继承该类。
何时需要从父类派生新的子类:
1.子类需要额外增加属性,而不仅仅是属性值的改变。
2.子类需要增加自己独有的行为模式
组合是把旧类对象作为新类的Field嵌入,用以实现新类的功能,用户看到的是新类的方法,而不能看到被嵌入对象的方法。。通常需要在新类里使用private修饰被嵌入的旧类对象。
继承 is-a 组合 has-a
初始化块: 当创建java对象时,系统总是先调用该类里定义的初始化块,如果一个类里定义了2个普通初始化块,则前面定义的初始化块先执行,后面定义的初始化块后执行
初始化顺序:先执行初始化块或声明Field时指定的初始值,再执行构造器里指定的初始值
静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行,因此静态初始化块总是比普通初始化块先执行。
当JVM第一次主动使用某个类时,系统会在类准备阶段为该类的所有静态Field分配内存,在初始化阶段则负责初始化这些静态Field,初始化静态Field就是执行累初始化代码或者声明类Field时指定的初始值,它们的执行顺序与源代码中的排列顺序相同。
Wrapperclass
8个包装类除了Character之外,还可以通过传入一个字符串参数来构建包装类对象。
每次把一个不再-128---127范围内的整数自动装箱成Integer实例时,系统总是重新创建一个Integer实例
所有的java类都是Object类的子类,因此所有的Java对象都具有toString()方法
toString方法是一个非常特殊的方法,它是一个自我描述的方法,该方法通常用于实现这样一个功能,当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息。用以告诉外界该对象具有的状态信息。Object类提供toString方法总是返回该对象实现类的"类名+@+hashCode"值。
equals方法(是String类从它的超类Object中继承的)被用来检测两个对象是否相等,即两个对象的内容是否相等。
==用于比较引用和比较基本数据类型时具有不同的功能:
比较基本数据类型,如果两个值相同,则结果为true
而在比较引用时,如果引用指向内存中的同一对象,结果为true
==指向同一个对象时,才会返回true.
只要两个字符串所包含的字符序列相同,通过equals()比较将返回true,否则将返回false.
Object默认提供的equals()只是比较对象的地址,即Object类的equals方法比较的结果与==运算符比较的结果完全相同。因此,在实际应用中,equals方法的实现也是由系统要求决定的。
static关键字: 类成员(包括方法,初始化块,内部类和枚举类)不能访问实例成员,因为类成员是属于类的,类成员的作用域比实例成员的作用域更大,完全可能出现类成员已经初始化完成,但实例成员还不曾初始化的情况,如果允许类成员访问实例成员将会引起错误。
final修饰符
final关键字可用于修饰类,变量和方法。final修饰的成员变量必须由程序员显式地指定初始值。对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一对象,但这个对象完全可以发生改变。
直接量:使用final修饰符修饰,在定义该final变量时指定了初始值,该初始值可以在编译时就被确定下来
final修饰的方法仅仅是不能被重写,并不是不能被重载
不可变类: 1.使用private和final修饰符来修饰该类的Field.
2.提供带参数构造器,用于根据传入参数来初始化类里的Field.
3.仅为该类Field提供getter方法,不要为该类的Field提供setter方法,
4.如果有必要,重写object类的hasCode和equals方法
抽象类:抽象方法是只有方法签名,没有方法实现的方法
abstract修饰符来定义的,不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含Field,方法,构造器,初始化块,内部类,枚举类6种成分。含有抽象方法的类(包括直接定义了一个抽象方法,继承了一个抽象父类,但没有完全实现父类包含的抽象方法,以及实现了一个接口,但没有完全实现接口包含的抽象方法3种情况)只能被定义为抽象类。
定义抽象方法只需在普通方法上增加abstract修饰符,并把普通方法的方法体全部去掉,并在方法后增加分号即可。
final和abstract不能同时使用
当使用static修饰一个方法时,表明这个方法属于该类本身,即通过类就可调用该方法,但如果该方法被定义为抽象方法,则将导致通过该类来调用该方法时出现错误,因此static和abstract不能同时修饰某个方法
抽象类体现的是一种模版模式的设计,抽象类作为多个子类的通用模版,子类在抽象类的基础上进行扩展,改造,但子类总体上会大致保留抽象类的行为方式。父类的普通方法依赖于一个抽象方法,而抽象方法则推迟到子类中提供实现。
接口 interface
修饰符可以是public或者省略
接口名应当与类名采用相同的命名规则
一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类
对于接口里定义的方法而言,它们只能是抽象方法,因此系统会自动为其增加abstract修饰符,由于接口里的方法全部是抽象方法,因此接口里不允许定义静态方法,接口里的方法,总使用public abstract来修饰
一个类可以实现一个或多个接口,继承使用extends关键字,实现则使用implements关键字,因为一个类可以实现多个接口,这也是java为单继承灵活性不足所做的补充。
Implements 部分必须放在extends部分之后
接口与抽象类:
1.接口里只能包含抽象方法,不包含已经提供实现的方法,抽象类则完全可以包含普通方法
2.接口里不能定义静态方法,抽象类可以定义静态方法
3.接口里只能定义静态常量,不能定义普通常量,抽象类都可以定义
4.接口里不包含构造器,抽象类里可以包含构造器。抽象类里的构造器并不是用于创建对象的,而是让子类调用这些构造器来完成属于抽象类的初始化操作
5.接口里不能包含初始化块,抽象类则完全可以包含初始化块
6.一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口,通过实现多个接口可以弥补java单继承的不足
内部类:1.内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
2.内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问
3.匿名内部类使用于创建那些仅需要一次使用的类,匿名内部类不能是抽象类,匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器
对象与垃圾回收:
垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源。
程序无法精确地控制垃圾回收的运行,垃圾回收会在适当的时候进行
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活,从而导致垃圾回收机制取消回收
对象在内存中的状态:可达状态,可恢复状态,不可达状态
当某个对象被其他类的类变量引用时,只有该类被销毁后,该对象才会进入可恢复状态。
强制垃圾回收机制:1.调用system类的gc()静态方法,system.gc()
2.调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()
Finalize()方法
在垃圾回收机制回收某个对象锁占用的内存之前,通常要求程序调用适当的方法来清理资源,在没有明确指定清理资源的情况下,java提供了默认机制来清理该对象的资源。这个机制就是finalize()方法
Finalize()方法具有如下特点:
永远不会主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用
Finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法
当JVM执行可恢复对象的finalize()方法时,可能使该对象活系统中其他对象重新编程可达状态。
当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行。
强,软,弱,虚引用….
Java集合:
collection接口用法: 添加元素,删除元素,返回collection集合的元素个数以及清空整个集合等
Iterator接口定义了如下的三个方法:
Boolean hasNext(); 如果被迭代的集合元素还没有遍历,则返回true
Object next(): 返回集合里的下一个元素
Void remove():删除集合里上一次next方法返回的元素
Iterator必须依附于collection对象,若有一个Iterator对象,则必然有一个与之关联的collection对象。
当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代器来遍历,所以修改迭代变量的值对集合元素本身没有任何影响。
当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove方法删除上一次next方法返回的集合才可以,也就是说,在Iterator迭代过程中,不可修改集合元素,否则会引发异常。
Iterator迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改,就会有异常抛出
For each循环 for(Object obj : books) 与Iterator接口迭代访问集合元素类似,foreach循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量。
Set集合
set集合不允许包含相同的元素,如果试图把两个相同元素加入同一个set集合中,则添加会失败
set判断两个对象相同不是使用==运算符,而是根据equals方法
字符串池的概念
HashSet :
不能保证元素的排列顺序,顺序有可能发生变化
HashSet不是同步的,多个线程修改,则必须通过代码保证同步
集合元素可以是null
HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
当从HashSet中访问元素时,HashSet先计算该元素的hasCode值(也就是调用该对象的hashCode()方法的返回值),然后直接到该hashCode对象的位置去去除该元素----HashSet速度很快的原因
HashSet中每个能存储元素的slot通常被称为bucket,如果有多个元素的hashCode值相同,但它们通过equals()方法比较返回false,就需要在一个“桶”里放入多个元素,这样会导致性能下降
当向HashSet中添加可变对象时,必须十分小心,如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致HashSet无法准确访问该对象
TreeSet: 可以确保集合元素处于排序状态。
TreeSet采用红黑树的数据结构来存储集合元素, 自然排序和定制排序
向TreeSet集合中添加元素时,只有第一个元素无需实现Comparable接口,后面添加的所有元素都必须实现Comparable接口,这不是一种好做法,当试图从TreeSet中取出元素时,依然会引发ClassCastException异常。
如果TreeSet中包含了可变对象,当可变对象Field被修改时,TreeSet在处理这些对象时将会非常复杂,而且容易出错,为了让程序更加健壮,推荐HashSet和TreeSet集合中只放入不可变对象。
HashSet的性能总是比TreeSet好,因为TreeSet需要额外的红黑树算法来维护集合元素的次序,只有当需要一个保持排序的Set时,才应该使用TreeSet.
HashSet有个子类: LinkedHashSet , 有了链表,遍历LinkedHashSet会更快
HashSet, TreeSet和EnumSet都是线程不安全的。通常通过collections工具类的synchronizedSortedSet方法来包装Set集合
List:
List集合代表一个元素有序,可重复的集合,集合中每个元素都有其对应的顺序索引,List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。
listIterator()方法:
boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素
Object previous(): 返回该迭代器的上一个元素
void add(): 在指定位置插入一个元素
ArrayList :
Vector:
都封装了一个动态的,允许再分配的object[]数组,使用initialCapacity参数来设置该数组的长度,当向ArrayList或Vector中添加元素超出了该数组的长度,initialCapacity会自动增加
ArrayList是线程不安全的,当多个线程访问一个arraylist集合时,如果有一个修改了,则程序必须手动保证该集合的同步性。而Vector集合是线程安全的。Vector的性能比ArrayList的性能要低。
LinkedList既实现了List接口,也实现了Deque接口。
Array.ArrayList是个固定长度的List集合
Vector还提供了一个Stack子类,模拟栈的数据结构。
ArrayList是基于数组的线性表
LinkedList是基于链的线性表
Queue代表了队列
Deque代表了双端队列
-Xms 是设置JVM的堆内存初始大小
-Xmx是设置JVM的堆内存最大大小
如果需要遍历List集合元素,对于ArrayList,Vector集合,应该使用随机访问的(get)来遍历集合元素,对于LinkedList集合,应该采用迭代器Iterator来遍历集合元素
如果需要经常执行插入,删除操作来改变List集合的大小,应该使用LinkedList集合,而不是ArrayList,使用ArrayList, Vector集合需要经常重新分配内部数组的大小,其时间开销常常是使用LinkedList的时间开销的几十倍,效果很差
如果有多个线程需要同时访问List集合中的元素,可以考虑使用Collections将集合包装成线程安全的集合。
Map:
Map用于保存具有映射关系的数据:一组值用于保存Map里的key,另外一组值用于保存Map里的value,key和value都可以是任何引用类型的数据,Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法,总要返回false.
Java是先实现了Map,然后通过包装一个所有value都为null的Map来实现了set集合。
HashMap:
Hashtable:
Hashtable是个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable的性能高一点,但如果有多个线程访问同一个Map对象时,使用Hashtable实现类会更好。
Hashtable不允许使用null作为key和value,如果试图把null值放进Hashtable中,将会引发异常,但Hashmap可以使用null作为key或value.
由于map的key不允许重复,所以HashMap里最多只有一个key-value对的key为null,但可以有无数多个key-value对的value为null.
Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便。可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中,可以把属性文件中的“属性名=属性值”加载到Map对象中
HashMap, Hashtable及其子类而言,它们采用hash算法来决定Map中key的存储,并通过hash算法来增加key集合的大小
hash表属性:容量,初始容量,尺寸,负载因子。
负载极限:是一个0-1的数值,决定了hash表的最大填满程度,当hash表中的负载因子达到指定的负载极限时,hash表会自动成倍地增加容量,并将原有的对象重新分配,放入新的桶里,这就称为rehashing. 一般为0.75
collections类中提供多个synchronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合。从而可以解决多线程并发访问集合时的线程安全问题。
多线程:
进程的三个特征:1.独立性,2.动态性,3.并发性
一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程
多线程编程优点:进程之间不能共享内存,但线程之间共享内存非常容易
系统创建进程时需要为该进程重新 分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多线程的效率高
java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了java的多线程编程
继承Thread类创建线程类:
1.定义Thread类的子类,并重写该类的run()方法,该run方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体
2.创建Thread子类的实例,即创建了线程对象
3.调用线程对象的start()方法来启动该线程
Thread.currentThread():currentThread()是Thread类的静态方法,该方法总是返回当前正在执行的线程对象
getName():该方法是Thread类的实例方法,该方法返回调用该方法的线程名字
实现Runnable接口创建线程类:
1.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3.调用线程对象的start()方法来启动该线程
Callable()接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大:
1.call()方法可以有返回值 2.call()方法可以声明抛出异常。
创建线程的方式对比:
线程的生命周期: New, Runnable, Running, Blocked, Dead
启动线程的正确方式是调用Thread对象的start()方法,而不是直接调用run()方法,否则就变成单线程程序了
线程以下面方式结束:
Run()或call()方法执行完成,线程正常结束
线程抛出一个未捕获的Exception或Error
直接调用该线程的stop()方法来结束—————容易造成死锁
Thread提供了让一个线程等待另一个线程完成的方法---join()方法
后台线程:所有的前台线程都死亡,后台线程会自动死亡
Thread类还提供了一个isDaemon()方法,用于判断指定线程是否为后台线程
线程
线程睡眠sleep
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现
线程让步:
Yield()方法是一个和sleep()方法类似的方法,它也是Thread类提供的一个静态方法,它可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。
setPriority()改变线程优先级
线程通信: object类提供的wait(), notify()和notifyAll()方法
Wait()导致当前线程等待,知道其他线程调用该同步监视器的notify()方法活notifyAll()方法来唤醒该线程
Notify():唤醒在此同步监视器上等待的单个线程
notifyAll(): 唤醒再次同步监视器上等待的所有线程
线程安全的集合类
concurrent类, cocurrentHashMap
CopyOnWrite类, CopyOnWriteArrayList, CopyOnWriteArraySet
后者底层封装前者
CopyOnWriteArrayList适合用在读取操作远远大于写入操作的场景中
当线程对CopyOnWriteArrayList集合执行读取操作时,线程将会直接读取集合本身,无需加锁与阻塞,当线程对CopyOnWriteArrayList集合执行写入操作时,该集合会在底层复制一份新的数组,接下来对新的数组执行写入操作,由于对CopyOnWriteArrayList集合的写入操作都是对数组的副本执行操作,因此线程是安全的
对象初始化:
使用switch语句时,有两个值得注意的地方:第一个地方是switch语句后的expression表达式的数据类型只能是byte,short,char, int四个整数类型和枚举类型,第二个地方是如果省略了case后代码快的break,将引入一个陷阱。
Java数组
Java语言支持两种语法格式来定义数组
Type[] arrayName; (推荐) :Type[] 是一种新类型,变量名是arrayName;
TypearrayName[];
数组初始化:静态初始化,动态初始化
For each循环:
使用foreach循环迭代数组元素时,并不能改变数组元素的值,因此不要对foreach的循环变量进行赋值。
实际的数组对象被存储在堆(heap)内存中,如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中
定义并初始化一个数组,在内存中分分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身
基本类型的数组初始化:
Java语言采用type[][] arrName;来定义二维数组,但它的实质还是一维数组,只是其数组元素也是引用,数组元素里保存的引用指向一维数组。