关闭

疯狂java 读书笔记

838人阅读 评论(0) 收藏 举报

类和对象;

类: 构造器,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允许同一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含了两个活两个以上的方法名相同,但形参列表不同,则被称为方法重载。

方法三要素: 调用者,方法名,形参列表。

 

成员变量和局部变量

计算机生成了可选文字: 实例属性(不以static修饰)类属性(以static修饰)产J.七量变员成形参(方法签名中定义的变量)局部变t方法局部变食(在方法内定义)IK;量变有所代码块局部变量(在代码块内定义)图5.9变量分类图

 

 

java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this或类名作为调用者来限定访问成员变量。

 

static变量在堆内存中只有一块空间,无论哪个实例访问,都是同一块内存,所以当程序需要访问类field使,尽量使用类作为主调,而不要使用对象作为主调。

局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中,占内存中的变量无须系统垃圾回收,往往随方法或代码的运行结束而结束

 

当我们定义一个成员变量时,成员变量将被放置到堆内存中,成员变量的作用域将扩大到类存在范围或者对象存在范围,这种范围的扩大有两个害处:

1.增大了变量的生存时间,这将导致更大的内存开销

2.扩大了变量的作用域,这不利于提高程序的内聚性。

 

考虑成员变量的情形:1.某个类的固有信息。2.某个类或者实例运行的状态信息

3.某个需要在某个类的多个方法之间进行共享

即使在程序中使用局部变量,也应该尽可能地缩小局部变量的作用范围,局部变量的作用范围越小,它在内存里停留的时间就越短,程序运行性能就越好,因此,能用局部变量的地方,就尽量不用方法局部变量。

 

隐藏和封装:

 

计算机生成了可选文字: 声watedefaultpro忱烈edpublic同一个类中矿了矿7同一个包中』{一-}-一一二乞一一一…了了了了子类中J全局范围内表5.1访问控制级别表

如果一个java类的每个Field都被使用private修饰,并为每个Field都提供了public修饰settergetter方法,那么这个类就是一个复合JavaBean规范的类

 

类里的绝大部分Field都应该使用private修饰,只有一些static修饰的,类似全局变量的Field,才可能考虑使用public修饰

如果某个类主要用作其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用protected 修饰这些方法。

希望暴露出来的给其他类自由调用的方法应该使用public修饰。

 

Java.lang这个包包含了java语言的核心类,String,Math, System, Thread

Java.util这个包包含了Java的大量工具类/接口和集合框架类/接口,arrays,listset

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引用变量有两种类型: 一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。

计算机生成了可选文字: 、.........……阅卜李·性东:舞二4.....……口门..............................……月勺............................……口门.,..............……,................……有........……,..............……引用变量在编译阶段只能调用其编译时类型所具有的方法时类型所具有的方法.因此,编写Java代码时,但运行时则执行它运行里包含的方法。例如我们通过objectP二引用变童只能调用声明该变童时所用类neVV能调用Object类的方法,而不能调用PersonPersonO代码定义一个变量p,则这个p只类里定义的方法。

 

相同类型的变量,调用同一个方法时呈现出来多种不同的行为特征,这就是多态。

 

(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

计算机生成了可选文字: 表6.1基本数据类型和包装类的对应基本数据类型包装类ByteshonInte罗r切ngCha口C记rint一Iong一charno妞tF10曲tdoUbk肠扣le自目以〕uble

8个包装类除了Character之外,还可以通过传入一个字符串参数来构建包装类对象。

计算机生成了可选文字: 通过倪姗WrappefC妞s以prl欣如吧冶健通过Wra哪曰如Stance幻以Va晚妨祛图6.1基本类型变量和包装类对象之间的转换

计算机生成了可选文字: 遥生S拍阅拍如民减闪m而Ve琳换通过W甩哪犯汇湘“P.lseX口叻祛图6.2基本类型变量和String之间的转换

 

每次把一个不再-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.使用privatefinal修饰符来修饰该类的Field.

                       2.提供带参数构造器,用于根据传入参数来初始化类里的Field.

                       3.仅为该类Field提供getter方法,不要为该类的Field提供setter方法,

                       4.如果有必要,重写object类的hasCodeequals方法

 

抽象类:抽象方法是只有方法签名,没有方法实现的方法

abstract修饰符来定义的,不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含Field,方法,构造器,初始化块,内部类,枚举类6种成分。含有抽象方法的类(包括直接定义了一个抽象方法,继承了一个抽象父类,但没有完全实现父类包含的抽象方法,以及实现了一个接口,但没有完全实现接口包含的抽象方法3种情况)只能被定义为抽象类。

 

定义抽象方法只需在普通方法上增加abstract修饰符,并把普通方法的方法体全部去掉,并在方法后增加分号即可。

finalabstract不能同时使用

当使用static修饰一个方法时,表明这个方法属于该类本身,即通过类就可调用该方法,但如果该方法被定义为抽象方法,则将导致通过该类来调用该方法时出现错误,因此staticabstract不能同时修饰某个方法

 

抽象类体现的是一种模版模式的设计,抽象类作为多个子类的通用模版,子类在抽象类的基础上进行扩展,改造,但子类总体上会大致保留抽象类的行为方式。父类的普通方法依赖于一个抽象方法,而抽象方法则推迟到子类中提供实现。

 

接口 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()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行。

 

强,软,弱,虚引用….

计算机生成了可选文字: 表6.2顶层类/接口J成员属性J方修饰符适用范围总表法}琢丁万j「习少勺丁乙块成员内部类JJ了JJ护了J局部成员OO矿一J一了一矿矿一J一J一矿一J一了一了一了一J一」矿}J一」廿一」J一了publicprolected包访问控制符pnvateabstract6仙】S扭1犯strict币synchroni理d朋tive仃ansleotvolatile召一了

 

 

Java集合:

计算机生成了可选文字: 无序集合,元素不可重复U.队列序集合,素可重复Unk.dH自ths创口口图当图7.1Collection集合体系的继承树.日

 

计算机生成了可选文字: \座州钾嘿零爵UnkedHashM.P一_______七prOP.丙.,Tr二M.p;·碑划图7.2MaP体系的继承树

 

collection接口用法: 添加元素,删除元素,返回collection集合的元素个数以及清空整个集合等

Iterator接口定义了如下的三个方法:

Boolean hasNext(); 如果被迭代的集合元素还没有遍历,则返回true

Object next(): 返回集合里的下一个元素

Void remove():删除集合里上一次next方法返回的元素

Iterator必须依附于collection对象,若有一个Iterator对象,则必然有一个与之关联的collection对象。

当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代器来遍历,所以修改迭代变量的值对集合元素本身没有任何影响。

当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iteratorremove方法删除上一次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在处理这些对象时将会非常复杂,而且容易出错,为了让程序更加健壮,推荐HashSetTreeSet集合中只放入不可变对象。

 

HashSet的性能总是比TreeSet好,因为TreeSet需要额外的红黑树算法来维护集合元素的次序,只有当需要一个保持排序的Set时,才应该使用TreeSet.

HashSet有个子类: LinkedHashSet 有了链表,遍历LinkedHashSet会更快

 

HashSet, TreeSetEnumSet都是线程不安全的。通常通过collections工具类的synchronizedSortedSet方法来包装Set集合

 

List

List集合代表一个元素有序,可重复的集合,集合中每个元素都有其对应的顺序索引,List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。

listIterator()方法:

 boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素

 Object previous() 返回该迭代器的上一个元素

 void add() 在指定位置插入一个元素

 

ArrayList  

Vector:

都封装了一个动态的,允许再分配的object[]数组,使用initialCapacity参数来设置该数组的长度,当向ArrayListVector中添加元素超出了该数组的长度,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里的valuekeyvalue都可以是任何引用类型的数据,Mapkey不允许重复,即同一个Map对象的任何两个key通过equals方法,总要返回false.

 

Java是先实现了Map,然后通过包装一个所有value都为nullMap来实现了set集合。

 

HashMap:

Hashtable:

Hashtable是个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMapHashtable的性能高一点,但如果有多个线程访问同一个Map对象时,使用Hashtable实现类会更好。

Hashtable不允许使用null作为keyvalue,如果试图把null值放进Hashtable中,将会引发异常,但Hashmap可以使用null作为keyvalue.

由于mapkey不允许重复,所以HashMap里最多只有一个key-value对的keynull,但可以有无数多个key-value对的valuenull.

 

Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便。可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中,可以把属性文件中的“属性名=属性值”加载到Map对象中

 

HashMap Hashtable及其子类而言,它们采用hash算法来决定Mapkey的存储,并通过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实现类的实例,并以此实例作为Threadtarget来创建Thread对象,该Thread对象才是真正的线程对象。

3.调用线程对象的start()方法来启动该线程

 

Callable()接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大:

1.call()方法可以有返回值  2.call()方法可以声明抛出异常。

 

创建线程的方式对比:

计算机生成了可选文字: 采用实现Runnable接口方式的多线程:》线程类只是实现了Runnable接口,还可以继承其他类。》在这种方式下,可以多个线程共享同一个terget对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CpU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。》劣势是:编程稍稍复杂,如果需要访问当前线程,必须使用Thread.current丁hread()方法。采用继承Thread类方式的多线程:》劣势是:因为线程类己经继承了Thread类,所以不能再继承其他父类。》优势是:编写简单,如果需要访问当前线程,无须使用Thread.currentThread()方法,直接使用this即可获得当前线程。实际上几乎所有的多线程应用都可采用第一种方式,也就是实现Runnable接口的方式。

线程的生命周期: New, Runnable, Running, Blocked, Dead

启动线程的正确方式是调用Thread对象的start()方法,而不是直接调用run()方法,否则就变成单线程程序了

 

线程以下面方式结束:

Run()call()方法执行完成,线程正常结束

线程抛出一个未捕获的ExceptionError

直接调用该线程的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集合的写入操作都是对数组的副本执行操作,因此线程是安全的

 

对象初始化:

计算机生成了可选文字: 总结一下对象的创建过程,假设有个名为Dog的类:1.即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位加乡dass文件。2.然后载入Dog.dass(后面会学到,这将创建一个class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。3.当用newDogO创建对象的时候,首先将在堆上为加g对象分配足够的存储空间。4.这块存储空间会被清零,这就自动地将Dog对象中的所有墓本类型数据都设置成了肚认值(对数字来说就是O,对布尔型和字符型也相同),而引用则被设置成了null。5.执行所有出现于字段定义处的初始化动作。6.执行构造器。正如将在第7章所看到的,这可能会牵涉到很多动作,尤其是涉及继承的

 

计算机生成了可选文字: 解决的办法就是后期绑定,它的含义就是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行时绑定。如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。后期绑定机制随编程语言的不同而有所不同,但是只要想一下就会得知,不管怎样都必须在对象中安置某种“类型信.良”。Java中除Tstatic方法和nnal方法(private方法属于nnal方法)之外,其他所有的方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该进行后期绑定―它会自动发生。为什么要将某个方法声明为final呢?正如前一章提到的那样,它可以防止其他人覆盖该方法。但更重要的一点或许是:这样做可以有效地“关闭”动态绑定,或者说,告诉编译器不需要对其进行动态绑定。这样,编译器就可以为nnal方法调用生成更有效的代码。然而,大多数情况下,这样做对程序的整体性能不会有什么改观。所以,最好根据设计来决定是否使用nnal,而不是出于试图提高性能的目的来使用nnal。

 

计算机生成了可选文字: 整类型字符类型,1个字节:2个字节:4个字节:.8个字节:2个字节:b难s加rtintfongchar荃本数据类型浮点类型长个字节:noat个字节:d侧ble布尔类型1位:肠川ean图3.8基本数据类型分类

 

计算机生成了可选文字: 逻辑运算符用于操作两个布尔型的变量或常量。逻辑运算符主要有如下6个:》&&:与,必须前后两个操作数都是true才返回true,否则返回felse。》&:不短路与,作用与&&相同,但不会短路。》}}:或,只要两个操作数中有一个true,就可以返回true,否则返回felse。》卜不短路或,作用与!!相同,但不会短路。》!:非,只需要一个操作数,如果操作数为true,返回falso,如果操作数为false,返回true。》^:异或,当两个操作数不同时才返回true,如果两个操作数相同则返回falseo

 

使用switch语句时,有两个值得注意的地方:第一个地方是switch语句后的expression表达式的数据类型只能是byte,short,char, int四个整数类型和枚举类型,第二个地方是如果省略了case后代码快的break,将引入一个陷阱。

 

Java数组

Java语言支持两种语法格式来定义数组 

Type[]  arrayName; (推荐)   Type[] 是一种新类型,变量名是arrayName;

TypearrayName[];

 

数组初始化:静态初始化,动态初始化

计算机生成了可选文字: 》数组元素的类型是基本类型中的整数类型》数组元素的类型是基本类型中的浮点类型》数组元素的类型是基本类型中的字符类型》数组元素的类型是基本类型中的布尔类型’心,一产二沪、沪,甲,11二门甘2气切,J.刁目口,》只U王石(byte、short、int和Iong),则数组元素的值是0。(月oat、double),则数组元素的值是0.0。(char),则数组元素的值是、u0000,。(boolean),则数组元素的值是fe!se。》数组元素的类型是引用类型(类、峨.接口和数组),则数组元素的值是null。

For each循环:

使用foreach循环迭代数组元素时,并不能改变数组元素的值,因此不要对foreach的循环变量进行赋值。

计算机生成了可选文字: 从上面运行结果来看,山于我们在foreach循环中对数组元素进行赋值,组元素,不能准确取出每个数组元素的值。而且当我们再次访问第-结果导致不能正确遍历数值依然没有改变。不难看出,当使用·个数组元素时,发现数组元素的foreach来迭代访问数组元素时,个临时变量,系统会把数组元素依次赋给这个临时变量,foreach中的循环变量相当于一存了数组元素的值。因此,如果希望改变数组元素的值,而这个临时变量并不是数组元素,它只是保则不能使用这种foreach循环。

实际的数组对象被存储在堆(heap)内存中,如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中

计算机生成了可选文字: ,吵亩卜实际数组元枝内存堆内存图4.2数组在内存中的存储示意图

 

计算机生成了可选文字: 学生提问:为什么有找内存和堆内存之分?_了砂答:咨一个方挂抚行时,每个方挂都套建立.己的内存找,在这个方技内定义的变童将套逐个教入这块钱内存里,随寿方挂的执行祛束,这个方挂的内存钱也将自盆箱鼓了。因此,所有在方挂中定义的变寺都走教在找内存中的;右我们在柱序中创建一个对衣时,这个对衣将位保存到退行时么据区中,*‘谈反氛利用(因苟对衣的创建成碑通常毅大),这个退行时么据区北走堆内存。堆内存中的对衣不合涟方挂的祛束而箱鼓,仰使方技倍束后,这个对衣迷可倪披另一个引用变考所引用(方挂的奉么传递时很常觅),则这个对衣依盆不合狱箱放。只有宙一个对衣没有任何引用变贵引用它时,系沈的位极田收机钊才会在合适的时候.收它。

定义并初始化一个数组,在内存中分分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身

计算机生成了可选文字: 程序员进行程序开发时,不要仅仅停留在代码表面,而要深入底层的运行机制,才可以对程序的运行机制有更准确的把握。当我们看一个数组时,一样要把数组看成两个部分:一个是数组引用,也就是在代码中定义的数组引用变量;还有一个是实际数组本身,这个部分是运行在系统内存里的,通常无法直接访问它,只能通过数组引用变量来访问它。

基本类型的数组初始化:

计算机生成了可选文字: 曰口口存J口校拉l、鱿.内存图4.5定义iArr数组变t后的存储示意当执仃iArr=newint[5];动态初始化后,系统将负责为该数组分配内存空问,并分配默认的初始值:所有数组元素都被赋为0,此时内存中的存储示意如图4.6所示:往内存图4.6动态初始化iArr数组后的存储示愈此时认rr数组的每个数组元素的值都是0,当循环为该数组的每个数组元素依次赋值后,此时每个数组元素的值都变成程序指定的值.显式指定数组元素值后存储示意如图4,7所示:一竺生氏‘翻月舫}.1卜l』油山.彼姐,.桂内存图4.,显式指定每个数组元素的值从图4.7中可以看到基本类型数组的存储示意图,每个数组元素的值直接存储在对应的内存里。操作基本类型数组的数组元素时,实际上就是操作幕本类型的变最考

 

Java语言采用type[][] arrName;来定义二维数组,但它的实质还是一维数组,只是其数组元素也是引用,数组元素里保存的引用指向一维数组。

计算机生成了可选文字: 致组变困一梭内存堆内存图4.13初始化aI01后存储示意

 

计算机生成了可选文字: 卜学生提问:我及否可以让图4.13中灰色搜盖的数组元素再次指向另一个数组?这样不可以扩展成三维救组呜?甚至扩展到更多维的救组?/答:不倪!至少在这个程序中不倪。自石Jav三走桩奥璧的语言,亩我们定义a么租时,已妞确定了a么租幼么租元索走int[]奥傀,则a[0』么租的么租元索只倪int奥型,所冰灰色彼孟的么租元素里只倪存借int奥型的变贵。对于其他肠奥型的语言,例‘Javascript和Ruby等,确实可冰把一谁么租无限扩展,扩展成共雍么祖、三难么祖……么某落在Java语言中实跪这种可无限扩展的么祖,则可忍定义一个Object[]奥型的么租,这个么租的元素走object奥型,因此可碑再次柑向一个Object[]奥型的么祖,这样北可碑从一谁么祖扩展到共稚么租、三稚么组·····一直下去。,

 

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1940次
    • 积分:49
    • 等级:
    • 排名:千里之外
    • 原创:3篇
    • 转载:1篇
    • 译文:0篇
    • 评论:0条
    文章存档