java概述
1. 简单性、面向对象、分布式、健壮性、安全性、体系结构中立、可移植性、解释性、高性能、多线程、动态性。
java基础程序设计结构
数据类型
int(4字节 32位)、short(2字节 16位)、long(8字节 64位)、byte(1字节 8位)。
long有一个后缀L或l,(如4000000000L);十六进制数值有前缀0x或0X(如0xCAFE);八进制有前缀0(如010代表8),易混淆,不建议使用八进制。
1. 浮点型
float的数值有一个后缀F或f(如3.12f);如果3.12没有后缀默认为double型,double型也可加后缀D或d。
如果在数值计算中不允许任何舍入误差,就用BigDecimal类。
1. char型
有些Unicode字符用char值描述,另些Unicode字符需要两个char表示。
代码单元:在基本的多语言级别中,每个字符用16位表示,称为代码单元;辅助字符采用一对连续的代码单元。
变量
常量
利用关键字final指示常量,习惯上常量名全大写。(如final double NUM = 2.23)。
运算符
数学函数与常量
在Math类中,提供了三角函数,指示函数,π等各式各样的数学函数和常量(如开方Math.sqrt(x)、Math.PI)。
数值之间的类型转换
如果两个数中有一个double,另一个就会转为double;否则其中一个为float,另一个转为float;否则其中一个为long,另一个转为long;否则另个都将转为int。
括号与运算级别
+=是右结合运算,所以表达式a += b += c等价于a += (b += c)
字符串
API:java.lang.String、java.lang.StringBuffer、java.lang.StringBuilder
拼接
用指定符号将多个字符串拼接成新字符串用静态的join()方法。
如:String all = String.join("/","a","cc","s");
不可变字符串
构建字符串
StringBuilder(效率高,线程不安全)前身是StringBuffer(线程安全)。
控制流程
块作用域
条件语句
}else{
循环
多重选择
1. 类型为char、byte、short、int的常量表达式。
大数值
API:java.math.BigInteger、java.math.BigDecimal
如果基本的整数和浮点数精度不能满足要求,可使用java.math包的BigInter和BigDecimal(如金额)。
使用静态的valueOf方法可以将普通数值转为大数值(如BigInteger a = BigInteger.valueOf(100);)。
大数值不能用算术运算符(+、-、*、/等),必须使用大数值类中的方法处理大数值(如一个加法运算: BigInteger c = a.add(b);等价于c = a + b;)。
数组
for each循环
for(variable : conllection) {statement...}
数组拷贝
Arrays.copyOf(oldArray,oldArray.length);
快速打印数组
2. 二维数组:Arrays.deepToString(a);
对象与类
OOP概述
类
封装:实现封装的关键在于决不让类中的方法直接访问其他类的实例域(对象中的数据)。程序仅通过对象的方法与对象数据之间进行交互。
对象
类与类之间的关系
1. 依赖:A类的方法操纵B类的对象,A类依赖于B类。
2. 聚合:A对象包含B对象。
3. 继承:A类扩展B类,并且A类添加自己的额外功能。
预定义类
对象与对象变量
任何对象变量的值都是对存储在另一个地方的一个对象的引用。new操作的返回值是一个引用。一个方法不能作用于一个值为null的对象上。
局部变量不会自动初始化为null,必须通过new或设置为null。
LocalDate类
LocalDate类:日历表示法,用于维护日期;使用静态工厂方法代表调用构造器,而不是new关键字(如:LocalDate.now();//表构造这个对象的日期)。
用户自定义类
源(类)文件名必须与public类名字相匹配;在一个源文件中,只能有一个公共类,但可以有任意多的非公共类。
构造器
隐式参数与显式参数
1. 隐式参数:出现在方法名前的类对象(如User.add(x)中的User对象)。在每个方法中this表示隐式参数。
2. 显式参数:方法名后括号中的值为显式参数(如User.add(x)中的x)。
封装的优点
1. 一个私有的数据域。
2. 一个共有的域访问器方法。
3. 一个共有的与更改器方法。
优点:数据域改变内部实现,不会影响其他类代码。更改器方法可以检验数据域的合法性。
注意:不要编写返回引用可变对象的访问构造器;解决,先对可变数据域(可变对象)克隆再返回。
私有方法
一个计算代码划分成若干独立的辅助方法,这些辅助方法可设为私有方法。这是由于他们往往与当前实现机制非常紧密,或需要一个特别的协议以及一个特别的调用次序。
静态域与静态方法
静态域
如果将域定义为static,每个类中只能有一个这样的域。它属于类,不属于任何独立对象。
静态常量
静态方法
静态方法不能向对象实施操作方法,即没有隐式对象或没有this参数的方法。
1. 一个方法不需要访问对象状态,所需参数都是显式参数提供。
工厂方法
main方法
main方法将执行并创建所需对象
方法参数
方法得到的是对象引用的拷贝,对象引用及其他拷贝同时引用同一个对象。java对对象采用的不是引用调用,对象引用是按值传递的。
对象构造
重载
返回类型不是方法签名的一部分。即不能有两个名字相同、参数类型也相同却返回值不同类型值的方法。
无参构造器
如果类中至少有一个有参构造器,却没有无参构造器,则不能使用无参构造器实例对象。
显式域初始化
调用另一个构造器
关键字this不仅是引用方法的隐式参数;还可以通过this(...)调用同一个类的另一个构造器。这样可以对公共的构造器代码只写一次。
初始化块
只要构造类对象,这些块就会被执行。即先运行初始化块,再运行构造器主体部分。
可以提供静态域的初始值;但如果对类的静态域初始化代码比较复杂,也可以使用一个静态初始化块(static{})初始化。
对象析构与finalize方法
java有自动的垃圾回收器,不需人工回收内存,所以java不支持析构器。
如果某个资源需要在使用完毕后立刻关闭,那么就需要人工来管理。对象用完时,可以应用一个close方法来完成相应的清理操作。
包
java允许用包将类组织起来。所有标准的java包都处于java和javax包层次中。
类的导入
静态导入
import不仅可以导入类,还增加了导入静态方法和静态域的功能。
包作用域
如果没有指定public或private,这个部分(类、方法、或变量)可以被同一个包中的所有方法访问。
类路径
2. 查看源文件是否比类文件新,如果新,源文件会自动重新编译。
类设计技巧
6. 有限使用不可变的类。
类、超类、子类
子类构造器
如果子类构造器没有显式的调用超类构造器,将自动调用超类默认构造器。如果超类没有不带参数的构造器,并且在子类构造器有没有显式的调用超类的其他构造器,将编译报错。
super关键字作用:
1. 调用超类方法。
一个对象变量可以指示多种实际类型的现象称为多态。在运行时能够自动的选择调用哪个方法的现象称为动态绑定。
继承层次
有一个公共超类派生出来的所有类集合称为继承层次。在继承层次中,某个特定的类到其祖先的路径称为该类的继承链。
多态
一个类变量既可以引用一个类对象,也可与引用其的任何一个子对象。但是,不能将一个超类的引用赋给子类变量。
方法调用
private方法,static方法、final方法或者构造器,这些“方法”编译器可以准确的知道该调用哪个方法,这种调用方式称为静态绑定。
虚拟机会预先为每个类创建一个方法表,其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候虚拟机仅查找这张表。
动态绑定的特性:无需对现存代码进行修改,就可以对程序进行扩展。
警告:在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。
阻止继承:final类和方法
如果将类声明为final,只有其中的方法自动成为final,而不包括域。
强制类型转换
只能在继承层次内进行转换。在将超类转换成子类前,应该用instanceof进行检查。
抽象类
类即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化。
可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。
受保护访问
Object类:所有类的超类
equals方法
Object类中的equals方法用于检验一个对象是否等于另一个对象。
相等测试与继承
1. 如果子类能够拥有自己相等的概念,则对称性需求经强制性采用getClass进行检测。
2. 如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同子类对象之间进行相等比较。
假设某个超类的id作为相等检测标准,并且这个相等概念适用于所有子类,就可以适用instanceof进行检测,并且应该将类名.equals方法声明为final。
编写完美equals方法建议:
2. 检测this和otherObject是否引用同一个对象。
3. 检测otherObject是否为null,如果为null,返回false。
4. 比较this与otherObject是否属于同一个类。
如果在子类重新定义equals方法,就要在其中调用super.equals(other)。
对于数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等。
hashCode方法
API:java.util.Object、java.util.Objects、java.lang.基本数据类型的包装类、java.util.Arrays;这些类的hashCode相关方法。
由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。最好使用null安全的方法Objects.hashCode。基本数据类型使用包装类名.hashCode来避免创建包装类对象。
equals和hashCode的定义必须一致,如果x.equals(y)放回true,那么x.hashCode()就必须与y.hashCode()相同。
如果存在数组类型的域,那么可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码由元素的散列码组成。
toString方法
API:java.lang.Object的toString方法和eaqals方法,在自定义的类中,应该覆盖这两个方法。
toString只要目的:只要一个对象与一个字符串通过操作符“+”连接起来,java就会自动的调用toString方法,以便获得这个对象的字符串描述。
在调用x.toString方法时可以用""+x代替。如果x是基本类型,这条语句照样能执行。
Object类定义了toString方法,用来打印输出对象所属的类名和散列码。
泛型数组列表
使用add方法可以将元素添加到数组列表中。数组列表管理着对象引用的一个内部数组。如果调用add且内部数组已经满了,数组列表就会自动创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中去。
访问数组列表元素
使用add方法数组添加新元素(可以插入中间元素),而不用set方法,set方法会覆盖已经存在的元素内容。
对数组实施插入和删除的效率比较低;如果数组储存元素比较多,就应该使用链表。
类型化与原始数组的兼容性
一旦能保证不会造成严重后果,可以用@SuppressWarnings("unchecked")标注来标记完成这个变量能够接受类型转换。如下:
@SuppressWarnings("unchecked")
ArrayList<A> res = (ArrayList<A>) a.find(b);//fields warning.
对象包装器与自动装箱
API:java.lang.Integer、javaj.lang.NumberFormat
所有基本类型都有一个对应的包装(器)类;整形和浮点型的包装器的公共超类Number。对象包装类是不可变的,即一旦构造了包装器,就不允许改变包装在其中的值。对象包装器还是final,因此不能定义它们的子类。
警告:由于每个值分别包装在对象中,所以ArrayList<Integer>的效率远低于int[]的效率,因此乐意用它构造小型集合。
自动装箱,如:list.add(3);自动转为list.add(Integer.valueOf(3));
如果在一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double。
拆箱和装箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
参数数量可变方法
如:public static String method(String a,Object... args){
“...”是java代码的一部分,它表明这个方法可以接受任意数量的对象(除a参数外)。
String m = method("cc",1,"bb","xx");编译器经new Object[]{1,"bb","xx"}传递给method方法。
枚举类
SMALL,MEDIUM,LARGE,EXTRAL_LARGE
在比较两个枚举类型是相等时,永远不要调用equals方法,而直接使用“==”就可以了。
如果需要的话,可以在枚举类型中添加一些构造器、方法和域;构造器只是在构造枚举时调用。
反射
API:java.lang.Class、java.lang.reflect.Constructor、java.lang.reflect.Field、java.lang.reflect.Method、java.lang.reflect.Modifier
反射库提供了一个非常丰富且精心设计的工具集,方便编写动态操纵java代码的程序。这项功能被大量用于javaBean中,他是java组件的体系结构。
Class类
3. Class.forName(类名路径或接口名的字符串)。无论何时,这个方法都要提供异常处理器。
Class类实际上是一个泛型类。如Employee.class的类型是Class<Employee>。
虚拟机为每个类型管理一个Class对象。因此可以用“==”运算符实现两个类对象比较。
Class类型.newInstance()调用默认构造器初始化新创建的对象。如果这个类没有默认构造器,就会抛异常。如果要使用有参构造器,必须使用Constructor类的newInstance方法。
捕获异常
利用Throwable类的printStackTrace方法打印出栈的轨迹。Throwable是Exception的超类。
在运行时使用反射分析对象
API:java.lang.reflect.ObjectAnalyzer
AccessibleObject是Field、Method、Constructor类的公共超类。
已知Class类对象,可以使用getDeclareFields获取所有数据域,然后使用Field类的setAccessible方法将所有域设置为可访问,这时可以用Field类的get和set方法获取每个域的值或者设置新值。
泛型toString方法查看任意对象内部信息问题:循环引用可能导致无限递归,ObjectAnalyzer将记录已经被访问的对象。例:
return new ObjectAnalyzer().toString(实例对象);
使用反射编写泛型编写数组代码
如过一个User[]对象零时转成Object[]数组,然后再把它装换回来是可以的,但从一开始就是Object[]的数组却永远不能转成User[]数组。
整形数组类型int[]可以转换成Object,但不能转换成对象数组。
使用反射编写泛型数组实现方法:
public static Object goodCopyOf(Object a,int newLength){
Class cl = a.getClass();//获取数组a的类对象
if(!cl.isArray()) return null;//确认a是一个数组
Class componentType = cl.getComponentType();//使用Class类的getComponentType方法确定数组的对应类型
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));//复制
使用反射编写泛型数组实现方法:
public static Object goodCopyOf(Object a,int newLength){
Class cl = a.getClass();//获取数组a的类对象
if(!cl.isArray()) return null;//确认a是一个数组
Class componentType = cl.getComponentType();//使用Class类的getComponentType方法确定数组的对应类型
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));//复制
继承的设计技巧
接口、lambda表达式、与内部类
接口
概念
API:java.lang.Comparable<T>、java.util.Arrays
Arrays类中的sort方法可以对对象数组进行排序,但要满足一下前提:对象所属的类必须实现Comparable接口。
接口中所有的方法都属于public,因此可以省略public关键字。
接口的特性
静态方法
默认方法
可以为接口方法提供一个默认实现(defaule修饰的方法)。
解决默认方法冲突
2. 接口冲突。必须覆盖这个方法,或可以选择两个方法中的一个。
注意:千万不要让一个默认方法重新定义Object类中的某个方法。
接口示例
Comparator 接口(Arrays.sort的第二个版本)
一个数组和一个比较器(comparator)作为参数,比较器是实现Comparator接口类的实例。
compare方法要在比较器对象上使用,而不是在字符串本身上调用。
对象克隆
cloneable是java提供的一组标记接口。标记接口不包含任何方法,他的唯一作用是允许在类查询中使用instanceof。
即使clone的默认拷贝(浅拷贝)实现能够满足要求,还是需要实现Cloneale接口,将clone方法重新定义为public,再调用super.clone().
深拷贝中,主要是对可变变量的拷贝(除开没有更改器的常量、不可变对象外)。前提是这些不可变子对象也实现了clone。
2. 实现类中定义clone方法,先调用父类clone,再拷贝可变子对象,返回拷贝对象。
lambda表达式
API:java.util.function 常用函数式接口、基本类型函数式接口
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
lambda表达式语法
即使lambda表达式没有参数仍要提供空括号,就像无参方法一样。
如果可以推导出一个lambda表达式的参数类型,则可忽略其参数类型。
如果方法只有一个参数,而且这个参数的参数类型可以推导的出,可省略小括号。
无需指定lambda表达式的返回类型,返回类型总是会由上下文推导得出。
如果一个lambda表达式只在某些分支返回一个值,而在另一些分支不返回值,这是不合法的。
函数式接口
对于只有一个抽象方法的接口,需要这个接口的对象时,就可以提供一个lambda表达式这种接口称为函数式接口。
最好把lambda表达式看作是一个函数,而不是对象。另外lambda表达式可以传递到函数式接口。
不能把lambda表达式赋给类型为Object的变量,Object不是一个函数式接口。
方法引用
有时可能已经有现成的方法可以完成你想要传递到其他代码的某个动作。
1. object::instanceMethod。如System.out::println等价于System.out.println(x)
2. Class::staticMethod。如Math::pow等价于(x,y)->Math.pow(x,y)
3. Class::instanceMethod。如String::compareToIngnoreCase等同于(x,y)->x.compareToIngnoreCase(y)
可以在方法中引用使用this参数。this::equals等同于x->this.equals(x)。使用super也是合法的,super::instanceMethod。
构造器引用
可以用数组类型建立构造器引用。如:int[]::new等价于x->new int[x]。
变量作用域
lambda表达式可以捕获外围作用域变量的值。要确保所捕获的值是明确定义的。
lambda表达式捕获的变量必须实际上是最终变量(初始化后不再赋值的变量)。
lambda表达式中声明一个与局部变量同名的参数或者局部变量是不合法的。
处理lambda表达式
如果是自己设计的接口,其中只有一个抽象方法,可以用@FunctionalInterface注解来标记这个接口。
内部类
内部类访问对象状态
内部类既可以访问自身的数据域,也可以访问创建他的外部对象的数据域。
外部类引用在构造器中设置。如内部类没有定义构造器,会默认生成一个参数含外部引用的构造器。如构造器:public Time(Talk t){
内部类的特殊语法规则
局部内部类
局部类不能用public和private访问说明进行声明。他的作用域被限定在声明这个局部类的块中。他可以对外部世界完全的隐藏起来。
由外部发方法访问变量
局部内部类不仅能够访问包含他们的外部类,还可以访问局部变量,局部变量必须有final修饰。
匿名内部类
new SuperType(construction paramters){
匿名类不能有构造器,而是将构造器参数传给超类构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。如下:
习惯用匿名内部类实现事件监听和其他回调。如今最好的做法是使用lambda表达式
new Object(){}.getClass().getEnclosingClass() //get class of static method
静态内部类
1. 在内部类不需要访问外围类对象的时候,应该使用静态内部类。
3. 声明在接口中的内部类自动成为static和public类。