java学习笔记(一)
----------
1.直接量:直接量是指在程序中通过源代码直接制定的值,例如在int a=5中,为变量a所份额皮的初始值5就是一个直接量. java支持8中类型的直接量.
2.常量池(constant pool):指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据,它包括类、方法、接口中的常量,也包括字符串直接量.
3.位运算符:java支持的位运算符有7个
&,按位与; |,按位或; ~,按位非; ^,按位或;
<<,左移运算符; >>,右移运算符;>>>,无符号右移运算符
4.对于低于int类型(如char,byte,short)的操作符总是要先自动类型转换成int类型后再移位。
5.字符串“true”和“false”不会直接转换成boolean类型,但如果使用一个boolean类型的值和字符串进行连接运算,则boolean类型的值将自动转换成字符串。
6.当一个算数表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。
-所有的byte类型,short类型和char类型都被提升为int类型
-整个算数表达式的数据类型自动提升为与表达式中最高等级操作数相同的类型
7.数组是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的:实际的数组对象被存储在堆(heap)中,如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)中。栈内存中的数据会随着方法的结束而销毁;而堆内存中的数据不会随方法的结束而销毁。即方法结束后,这个对象还可能被另一个引用变量所引用,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收器才会在合适的时候回收它。当我们看一个数组时,一定要把数组分成两部分:一部分是数组引用,也就是在代码中定义的数组应用变量;还有一部分是实际的数组对象,这部分是在堆内存里运行的,通常无法直接访问它,只能通过数组引用变量来访问。
4.6.5操作数组的工具类
Java提供的Arrays类里包含的一些static修饰的方法可以直接操作数组,这个Arrays类里包含了如下几个static修饰的方法(static修饰的方法可以直接通过类名调用)。
-int binarySearch(type[]a,type key):使用二分法查询key元素值在a数组中出现的索引;如果a数组不包含key元素值,则返回负数。调用该方法时要求数组中元素已经按照升序排序,这样才能得到正确结果。
-int binarySearch(type[]a,int fromIndex, int toIndex):这个方法与前一个方法类似,但它只搜索a数组中fromIndex到toIndex索引的元素。调用该方法时要求数组中元素已经按照升序排列,这样才能得到正确的结果。
-type[] copyOf(type[]original, int newLength):这个方法会把original数组复制成一个新数组,其中length是新数组的长度。如果length小于original数组的长度,则新数组就是原数组的前面length个元素:如果length大于original数组的长度,则新数组的前面元素就是原数组的所有元素,后面补充0(数值类型)、false(布尔类型)或者null(引用类型)。
-type[] copyOf(type[]original, int from, int to):这个方法与前面方法类似,但这个方法只复制original数组的from索引到to索引的元素。
-boolean equals(type[]a,type[] a2):如果a数组和a2数组的长度相等,而且a数组和a2数组的数组元素也一一相同,该方法会返回true。
-void fill(type[] a, type val):该方法会把数组a的所有元素都赋值为val。
-void fill(type[] a,int fromIndex, int toIndex, typeval):该方法与前一个方法的作用相同,区别只是该方法仅仅将a数组的fromIndex到toIndex索引的数组元素赋值为val。
-void sort(type[] a):该方法a数组的数组元素进行排序。
-void sort(type[]a,int fromIndex,int toIndex):该方法与前一个方法相似,区别是该方法仅仅对fromIndex到toIndex的索引元素进行排序。
-String toString(type[] a):该方法将一个数组转换成一个字符串。该方法按顺序把多个数组元素连缀在一起,多个数组元素使用英文逗号(,)和空格隔开。
第五章 面向对象(上)
5.1.1构造器不是没有返回值吗?为什么不能用void修饰呢?
简单的说,这是java的与法规定。实际上,类的构造器是有返回值的,当我们用new关键字来调用构造器时,构造器返回该类的实例,可以把这个类的实例当成构造器的返回值,因此构造器的返回值类型总是当前类,无须定义返回值类型。但必须注意:不能在构造器里显式使用return来返回当前类的对象,因为构造器的返回值是隐式的。
5.2.5方法重载
Java允许同一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含了两个或者两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。
方法重载有三个要素:
-
调用者,也就是方法的所属者,既可以是类,也可以是对象;
-
方法名,方法的标识;
-
形参列表,当调用方法时,系统将会根据传入的实参列表匹配。
方法重载的要求是两同一不同:同一个类中方法名相同,参数列表不同。
5.3.1
5.3.2成员变量的初始化和内存中的运行机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。
5.3.3局部变量的初始化和内存中的运行机制
局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。这意味着定义局部变量后,系统并未为这个变量分配内存控件,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中,如果局部变量是基本类型的变量,则直接把这个变量的值保存在该变量对应的内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。
5.4隐藏和封装
5.4.1封装
封装是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
-
隐藏类的实现细节。
-
让使用者只能通过事先预定的方法访问数据,从而可以在该方法里加入控制逻辑,限制对Field的不合理访问。
-
可对进行数据检查,从而有利于保证对象信息的完整性。
-
便于修改,提高代码的可维护性。
为了实现良好的封装,需要从两个方面考虑。
-
将对象的Field和实现细节隐藏起来,不允许外部直接访问。
-
把方法暴露出来,让方法控制对这些Field进行安全的访问和操作。(把该隐藏的隐藏起来,该暴露的暴露出来)
表5.4.2 访问控制符级别
| Private | Default | Protected | Public |
同一个类中 | √ | √ | √ | √ |
同一个包中 |
| √ | √ | √ |
子类中 |
|
| √ | √ |
全局范围内 |
|
|
| √ |
Tips:一个类常常就是一个小的模块,我们应该只让这个模块公开必须让外界知道的内容,而隐藏其他一切内容,进行程序设计时,应尽量避免一个模块直接操作和访问另一个模块的数据,模块设计追求高内聚(尽可能把模块的内部数据、功能实现细节隐藏在模块内部独立完成,不允许外部直接干预)、低耦合(仅暴露少量的方法给外部使用)。
5.4.4 java的常用包
- java.lang:这个包下包含了Java语言的核心类,如String、Math、System和Thread类等,使用这个包下的类无须使用import语句导入,系统会自动导入这个包下的所有类。
- java.util:这个包包含了一些Java的大量工具类/接口和集合和框架/接口,例如Arrays和List、Set等。
- java.net:这个包下包含了一些Java网罗编程相关的类/接口。
- java.io:这个包下包含了一些Java输入/输出编程相关的类/接口。
- java.text:这个包下包含了一些Java格式化相关的类。
- java.sql:这个包下包含了Java进行JDBC数据编程的相关类/接口。
- java.awt:这个包下包含了抽象窗口工具集(Abstract Window Toolkits)的相关类/接口,这些类主要用于构建图形用户界面(GUI)程序。
- java.swing:这个包下包含了Swing图形用户界面编程的类/接口,这些类可用于构建平台无关的GUI程序。
5.6.2重写父类的方法
有时候子类需要重写父类的方法,也称为方法覆盖(Override)。
应遵循“两同两小一大”规则:“两同”即方法名相同、形参列表相同;“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
覆盖方法要么都是类方法,要么都是实例方法。
5.6.3重写和重载
重载主要发生在一个类的多个同名方法之间,而重写发生在子类和父类的同名方法之间。
5.7多态
Java引用变量有两个类型:一个编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
5.7.2引用类型的强制类型转换
进行强制类型转换时需要注意:
-
基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。
-
引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将会引发ClassCastException异常。
在进行强制类型转换之前,先用instanceof运算符判断是否可以转换成功,从而避免出现ClassCastException异常,这样可以保证程序更健壮。
5.7.3 instanceof运算符
Instanceof运算符的前一个操作符通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。返回值为true或false。
5.8继承与组合
继承是实现类重用的重要手段,但继承带来了一个最大的坏处:破坏封装。组合也是类重用的重要手段,而采用组合方式来实现类重用则能提供更好的封装性。
5.8.2利用组合实现复用
代码:
class Animal
{
private void beat()
{
System.out.println("心脏跳动...");
};
public void breath()
{
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
};
class Bird
{
//将原来的父类嵌入到原来的子类中,作为子类的一个组合成分
private Animal a;
public Bird(Animal a)
{
this.a=a;
}
//重新定义一个自己的breath方法
public void breath()
{
//直接复用Animal提供的breath方法来实现Bird的breath方法
a.breath();
}
public void fly()
{
System.out.println("我在天空自在的飞翔...");
}
};
class Wolf
{
//将原来的父类嵌入到原来的子类,作为子类的一个组合成分
public Wolf(Animal a)
{
this.a=a;
};
//重新定义一个自己的breath方法
public void breath()
{
//直接复用Animal提供的breath方法实现Wolf的breath方法
a.breath();
}
public void run()
{
System.out.println("我在陆地上奔跑...");
}
}
public class CompositeTest
{
public static void main(String args)
{
//此时需要显式创建被嵌入的对象
Animal a1=new Animal();
Bird b=new Bird(a1);
b.breath();
b.fly();
//此时需要显式创建被嵌入的对象
Animal a2=new Animal();
Wolf w=new Wolf(a2);
w.breath();
w.run();
}
}
总之,继承要表达的是一种is-a的关系,而组合表达的是has-a的关系。
6.1装箱和拆箱
自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)
自动装箱:就是可以把一个基本类型变量直接赋值给对应的包装类变量,或者赋给Object变量(Object是所有类的父类,子类对象可以直接赋值给父类对象)。
自动拆箱:允许直接把包装类对象直接赋值给一个对应的基本类型变量。
Pay attention to:进行自动装箱和自动拆箱时必须注意类型匹配。
包装类还可以实现基本类型变量和字符串之间的转换。
把字符串类型转换成基本类型的值有两种方式:
-
利用包装类提供的parseXxx(Strings)静态方法(除了Character之外的所有包装类都提供了该方法)。
-
利用包装类提供的Xxx(String)构造器
String类提供了多个重载valueOf()方法,用于奖基本类型变量转换成字符串。
6.2.1打印对象和toString方法
ToString()方法是Object类里的一个实例方法,所有Java类都是Object类的子类,因此所有的Java对象都具有toString()方法。返回该对象实现类的“类名+@+hasCode”值,这个值并不能真正实现“自我描述”的功能,就必须重写Object类的toString方法。
6.2.2 ==和equals方法
1.当使用==来盘但两个变量是否相等时:如果两个变量时基本变量类型,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就返回true。但对于两个引用类型变量,它们必须指向同一个对象时,==判断才会返回true。==不可用于比较类型上没有父子关系的两个对象。
Pay attention to:常量池(constant pool)专门用于管理在编译期被确定并被保存在已编译的.class文件中的一些数据,它包括了关于类、方法、接口中的常量,还包括字符串常量。JVM常量池保证相同的字符串直接量只有一个,不会产生多个副本。
2.equals方法时Object类提供的一个实例方法,因此所有引用变量都可调用该方法来判断是否与其他引用变量相等。(也就是值相等就行)
6.3.2单例类(Singleton类)
一个类始终只能创建一个实例,则这个类被称为单例类。
根据良好封装的原则:一旦把该类的构造器隐藏起来,就需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过该对象,也就无法保证只创建一个对象。
代码:
class Singleton
{
//使用一个变量来缓曾经创建的实例
private static Singleton instance;
//对构造器使用private修饰,隐藏该构造器
private Singleton(){}
//提供一个静态方法,用于返回Singleton实例,该方法可以自定义控制,保证只产生一个Singleton实例
public static Singleton getInstance()
{
//如果instance为null,则表明还不曾创建Singleton对象
//入伏哦instance不为null,则表明已经创建了Singleton对象
if(instance==null)
{
//创建一个Singleton对象,并将其缓存起来
instance =new Singleton();
}
return instance;
}
};
public class SingletonTest
{
public static void main(String[] args)
{
//创建Singleton对象不能通过构造器
//只能 通过个体Instance方法得到实例
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
//将输出true
System.out.println(s1==s2);
}
}
6.5.1抽象方法和抽象类
抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义为抽象类,抽象类里可以没有抽象方法:
-
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体
-
抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。及时抽象类里不包含抽象方法,这个抽象类也不能创建实例。
-
抽象类可以包含Field、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类、枚举6种成分。抽象类的构造器不能用于创建实例,主要被其子类调用。
-
含有抽象方法的类(包括直接定义了一个抽象方法:继承了一个抽象父类,但没有完全是新父类包含的抽象方法,以及实现了一个接口,但没有完全实现接口包含的抽象方法3种情况)只能被定义为抽象类。
-
定义抽象方法只需在普通方法上增加abstract修饰符,并把普通方法的方法体(也就是方法后花括号括起来的部分)全部去掉,并在方法后增加分号即可。
6.6更彻底的抽象:接口
6.6.1 接口的概念
接口是从多个相似类种抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的哲学。
让规范和实现分离正式接口的好处,让软件系统的各组建之间面向接口耦合,是一种松耦合的设计。
6.6.2接口的定义
- 修饰符可以是public或省略,如果省略了public访问控制符,则默认采用包权限访问控制符。
- 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。(接口支持多继承)
- 一个类实现一个或多个接口后,必须实现这些接口里所定义的全部抽象方法
6.6.5 接口和抽象类
接口和抽象类很像,它们都有如下特征:
-
接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
-
接口和抽象类都可以包含抽象方法,实现接口或继承类的普通子类都必须实现这些抽象方法。
差别:
-
接口里只能包含抽象方法,不包含已经提供实现的方法,抽象类则完全可以包含普通方法。
-
接口里不能定义静态方法;抽象类可以定义静态方法。
-
接口里不包含构造器;抽象类里则完全可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
-
接口里不能包含初始化块;但抽象类则完全可以包含初始化块。
-
一个类最对只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。
6.9对象与垃圾回收
垃圾回收机制的特征:
-
垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)。
-
程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久性地失去引用后,系统会在合适地时候回收它所占用的内存。
-
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
6.9.1 对象在内存中的状态
当一个对象在对内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种:
-
可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的Field和方法。
-
可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可以恢复状态对象的finalize()方法进行资源清理。如果系统在调用finalize()方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达状态。
-
不可达状态:当对象与所有引用变量的关联被切断,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可达状态,那么这个对象将永远性地失去引用,最后变成不可达状态。只用当一个对象处于不可达状态时,系统才会真正收回该对象所占有地资源。
图 6.8对象的状态转换示意图
6.9.2 强制垃圾回收
程序只能控制一个对象何时不再被任何引用变量引用,绝不能控制它何时被回收。
强制系统垃圾回收有如下两个方法:
-
调用System类的gc()静态方法:System.gc()
-
调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()
6.9.3 finalize方法
方法原型为:
Protectedvoid finalize() throws Throwable
当finalize()方法 返回后,对象消失,垃圾回收机制开始执行。方法原型中的throws Throwable表示它可以抛出任何异常。(任何Java类都可以重写Object类的finalize()方法)
Finalize()方法具有如下4个特点:
-
永远不要主动调用某个对象的finalize()方法,该方法应该交给垃圾回收机制调用
-
Finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法
-
当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
-
当JVM执行finalize()方法出现异常时,垃圾回收机制不会报告异常,程序继续执行。
6.9.4 对象的软、弱和虚引用
Java.lang.ref包下提供了3个类:SoftReference、PhantomReference和WeakReference。分别代表:软引用、虚引用和弱引用。
-
强引用(StrongReference)
这是java程序做常见的引用方式。程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象,前面介绍的对象和数组都采用了这种强引用的方式。当一个对象被一个或一个以上的引用变量引用时,它处于可达状态,不能被系统垃圾回收机制回收。
-
软引用(SoftReference)
软引用需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统可能会回收它。软引用通常用于内存敏感的程序中。
-
弱引用(WeakReference)
弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就回立即被回收——正如那些失去引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收。
-
虚引用(PhantomReference)
虚引用通过PhantomReference类实现,虚引用完全类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那么它和没有引用的效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能被单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用。
6.10修饰符的适用范围
Abstract和final不能同时使用,abstract和static不能同时使用,abstract和private不能同时使用。
6.11使用jar文件
Jar文件的全称是Java ArchiveFile,意思是Java档案文件。通常jar文件是一种压缩文件,与我们常见的zip压缩兼容,通常也称作jar包。Jar文件中默认包含了一个名为META-INF/MANIFEST.MF的清单文件。
使用jar文件的好处:
-
安全。能够对jar文件进行数字签名,只让能够识别数字签名的用户使用里面的东西。
-
加快下载速度。在网上使用Applet时,如果存在多个文件而不打包,为了能够把每个文件都下载到客户端,需要为每个文件单独建立一个HTTP连接,这是非常耗时的工作。将这些文件压缩成一个jar包,只要建立一次HTTP连接就能够下载所有的文件。
-
压缩。使文件变小。
-
包装行。能够让jar包里的文件依赖于同一版本的类文件。
-
可移植性。Jar包作为内嵌在java平台内部处理的标准,能够在各种平台上直接使用。
6.11.1 jar命令详解
1.创建JAR文件:jar cf test.jartest
该命令没有显示压缩过程,执行结果是将当前路径下的test路径下的全部内容生成一个test.jar文件。如果当前目录中已经存在test.jar文件,那么该文件将被覆盖。
2.创建jar文件,并显示压缩过程:jar cvftest.jar test
与上一条命令相同,但是由于v参数的作用,显示出了打包过程。
3.不使用清单文件:jar cvfM test.jar test
与上一个命令类似,其中M选项表示不生成清单文件。因此生成的jar包中没有包含META-INF/MANIFEST.MF文件,打包过程的信息也略有差别。
4.自定义清单文件内容: jar cvfm test.jar manifest.mf test
其中m选项指定读取用户清单文件信息。因此生成的jar包中清单文件META-INF/MANIFEST.MF的内容有所不同。
5.查看jar包内容:jar tftest.jar
在test.jar文件已经存在的前提下,使用此命令可以查看test.jar中的内容。
-
查看jar包详细内容: jar tvf test.jar
除了显示基本内容以外,还包括包内文件的详细信息。
-
解压缩: jar xftest.jar
将test.jar文件解压缩到当前目录下,不显示任何信息。
带提示信息解压缩: jar xvf test.jar
与第七个命令相同,但系统会显示解压缩过程的详细信息。
-
更新jar文件:jar uf test.jar Hello.class
更新test.jar中的Hello.class文件。如果test.jar中已经有Hello.class文件,则使用新的Hello.class文件替换原来的Hello.class文件;如果test.jar中没有Hello.class文件,则把新的Hello.class文件添加到test.jar文件中。
-
更新时显示详细信息:jaruvf test.jar Hello.class
会显示详细的压缩信息。