Java基础
JAVA基础
java概述
特点:
-
编译期检查异常
-
体系中立,生成平台无关字节码。并且及时编译将热点字节序列编译成机器码
-
可移植性强,字节码面向虚拟机,强类型定义明确,规范中没有“依赖具体实现”
-
程序=算法+数据结构,面向过程强调如何操作数据,面向对象强点如何组织数据
java基本程序结构
java源代码编译后空白会被忽略。
基本类型:
-
整型:long(8字节)、int(4字节)、short(2字节)、byte(1字节)
-
浮点型:double(8字节)、float(1字节)
-
Unicode字符类型:char(2字节)
-
布尔类型:boolean(1字节)
boolean编译后是int类型占用4字节,对于32位机器高效存取。boolean数组在oracle中每个元素编译为byte类型 通过 与 、或 位操作可以保证幂等性;同一个数字 异或 两次返回原值,可以用于简单加密;A^B=C则C^A=B并且C^B=A
关键字
strictfp:严格浮点计算,可能会产生溢出 大数运算:BigInteger(整数)、BigDecimal(浮点数)
Java类中五大成员:
属性、方法、构造器、代码块、内部类
Java只有一维数组,多维数组是不规则的数组,每一行都可以有不同的长度
final用来修饰基本类型和不可变类型
值传递:方法接受参数的值拷贝;引用传递:方法接受参数的地址拷贝
方法签名:函数名称+参数类型,如indexOf(int)
C++中一个构造器不能调用另外的构造器
类与对象
类成员:
属性、方法、构造器、代码块、内部类
java中默认访问权限下是包访问权限,包访问权限只能同一包中的其他类访问,子包以及上层包均不能访问
有返回值的方法称为 函数,没返回值的方法称为 过程,返回值是布尔类型的函数叫做 谓词
对象之间的关系:
-
依赖(use-a):一个对象的方法操作另一个对象,应该尽可能的降低对象之间的依赖(低耦合)
-
聚合(has-a):一个对象的属性是包含另外的对象(聚合也被称为"关联")
-
继承(is-a):对象间的继承和实现关系
一个对象的引用变量,并没有包含一个对象,而只是一个指针。因此不要在方法中返回成员对象,而应该返回一个克隆对象
java中每一个实例方法第一个参数都是隐式参数,也就是该对象,在方法中 this 关键字表示该对象。而类方法(static修饰)没有隐式参数
java是引用传递,向方法中传递参数时如果是基础类型,则会将原值复制给实参变量。如果传递参数是引用类型,则会将原对象的地址复制一份传递给实参引用。因此无论怎样实参的中的值都是在原值的基础的上的一份拷贝,在方法中对其改变并不会对方法外的实际值产生影响。
验证方法中传递对象是地址拷贝,可以通过在方法中交换交换引用地址与方法外的对象进行比较。最终可以验证是同一地址的两份拷贝。
finalize 在垃圾回收器清除对象之前调用,但是不知道具体调用时机
import 只能用 * 导入一个包,import 编译器可以查看其它文件内部,去其他类寻找对象,所以和C++中的#include不同,C++不能查看其它文件内部,#include只是将外部声明特性加载进来
继承
子类能继承父类的 属性 和 方法 但是不能访问父类private
修饰的成员,同名属性子类会隐藏父类(不会重写),访问属性时通过 编译期 的变量类型来决定访问 父类 还是 子类的属性。而访问方法时,是通过 运行期 实际类型来决定访问方法
java中所有继承都是公有继承,C++中::限定符在Java中通过super实现
函数返回值不是 方法签名 的一部分,因此方法重载不可以用函数返回值作为区分
如果一些父类方法没有在子类中被覆盖,则编译器会进行优化,可能会进行内联化(inline),通过
final
修饰的方法/类 一定不会在子类中实现,因此更有可能利用到 即时编译器的内联化优化向上向下转型是对应 继承链 来说,向上:子类->父类;向下:父类->子类;在C++中向下转型(强转)失败会返回空指针,转换后判断指针是否为NULL再使用,而java需要在转换前通过
instanceOf
判断能否转换
子类实例 instanceOf 父类
判断实例是否是该类的实例,即使实例是null,不会报错;会返回false;java中通过
abstract
声明抽象类/抽象方法,而C++中没有专门声明抽象类的关键字,约定在C++中只要包含一个或者多个纯虚函数则该类就为抽象类;抽象类则不能实例化实例,声明纯虚函数virtual 返回值类型 方法名(形参列表)=0
C++中没有所有类的根类,java中Object是所有类的父类
泛型不能是基本类型,java中的泛型式假泛型,JVM并不支持泛型,只是编译阶段通过checkcase检查类型
Java类库中原设计将接口类的静态放发放在伴生类中(伴生类在原类名后加s,类中全是静态方法静态常量),jdk8以后接口可以提供默认实现,增加默认实现的目的是扩展父类的同时不会对子类造成影响
重写规则
-
子类重写方法访问权限一定不能小于父类访问权限
-
子类参数列表必须与父类相同
-
子类方法的返回值类型只能和父类相同或者是父类返回值的子类
-
子类方法的抛出异常只能是父类抛出异常的子类或者相同类
-
final 方法不能重写
-
private方法会被继承但是对子类不可见
-
构造器隐式被
static
修饰 -
静态方法不会被重写,如果重写了则父类的静态方法会被子类静态方法隐藏,静态方法不具备多态特性,属于静态绑定,由调用的对象类型决定
类加载过程:加载->验证->准备->解析->初始化
static方法和属性在初始化之前在方法区被加载,而非static成员在初始化之后内动态创建
常用接口
需要实现克隆方法的对象都要继承Cloneable接口,并且重写clone方法,调用Object的clone(super.clone())并强制类型转换成子类会进行浅拷贝,需要手动再将引用对象深拷贝
lambda表达式
带参数变量的表达式叫做lambda表达式,源自《数学原理》中用lambda表示自由变量
只有一个抽象方法的接口叫 函数接口,函数通过@FunctionalInterface
修饰,大量的函数接口被定义在java.util.function
,lambda表达式就是 函数接口 的匿名内部类实现。传入lambda表达式的参数必须是不变的,lambda表达式中的this是只创建这个lambda表达式的方法的this;传递参数的时候是通过匿名内部类的方式传递的,作用是 延迟执行代码,lambda引用的外部参数必须是 final 防止多线程下的变量不安全 lambda表达式中的this指向的创建lambda表达式的类对象 方法引用 是 函数接口 的实现体有且只有一行方法调用代码,则可进行的简写形式
// 函数接口定义
多态
java中默认是动态绑定,父类引用指向子类对象方法调用的是子类实现,而C++中默认是静态绑定调用的是父类方法,如果使用动态绑定需要声明函数是虚函数或者纯虚函数
内部类
内部类模仿包访问权限;编译器编译后 外部类$内部类,内部类是一种编译器行为,虚拟机无感知;内部类编译后会加上final 外部类名 this$0
引用外部类,可以访问外部类私有数据;包含内部类的外部类编译后会对每个属性(非final修饰)生成static 外部类属性类型 access$数字(外部类)
的静态方法,在内部类访问外部类属性时会被调用;所以使用内部类不安全,可以构造虚拟机指令访问到私有方法;普通内部类不能包含静态属性和静态方法
局部内部类
java中使用可以使用局部内部类来实现 闭包,局部内部类被定义在某一方法内,局部内部类不仅可以访问到外部类成员(属性和方法),还可以访问到定义访问内的局部变量,但是访问的局部变量必须是final
的;在编译过后访问的局部变量会将值复制作为局部内部类的属性值(final 类型名 val$局部变量名
)
将外部变量声明为final是保证复制阶段局部变量和局部内部类拷贝一致,外部变量也可以叫做 自由变量 ,如果需要对局部内部类的外部变量做修改,则以将外部变量放在一个用
final
修饰的可变对象中,传入局部内部类
匿名内部类
局部内部类的一种,定义后即生成对象;没有构造函数,定义过程会调用父类构造器(匿名内部类必须作为一个父类的实现类);
可以直接扩展一个类定义重定义代码块,做一些自定义操作。例:
new ArrayList<int>(){{add(1);add(2)}}
,外面的大括号代表是作为匿名内部类实现,内部的大括号代表的是实现代码块(对象成员) 在静态方法中获取外部类名,可以通过new Object(){}.getClass().getEnclosingClass()
获取生成当前Object对象的外部类
静态内部类
静态内部类又叫 内嵌类,不保存外部类的对象实例,可以定义静态和非静态成员
最佳实践
-
实现compare方法如果是int类型最好不要要两个参数相减,如果是负数可能会产生溢出
-
通过steam可以创建指定类型数组,Person[] people = stream.toArray(Person[]::new):
反射
java运行期间为每个 对象 保存了所属 类 的信息
对象的.getClass(); 类名.class Class.forName("类名");
java中数组对象不支持向下转型,编写通用的数组方法,new创建Object[]数组,不能转换成实子类型,运行报错;通过 Array.newInstance(class对象,长度) 创建指定类型的数组对象(返回Object[]数据)可以向下转型成class类型对象
自省(Intro Spector)
java中自省机制是对Bean对象的属性一种通用调用方法,可以通过属性名获取对象的setter/getter方法;再通过反射调用;自省机制主要涉及三个类:InteroSpector、BeanInfo、PropertyDescriptor
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
// 获取JavaBean描述
BeanInfo user = Introspector.getBeanInfo(User.class);
// 获取属性描述器
PropertyDescriptor pd = new PropertyDescriptor(name, bean.getClass());
// 获取Bean中所有属性描述符
PropertyDescriptor[] pds = user.getPropertyDescriptors();
// 通过属性描述符调用setter方法
Method method = pd.getReadMethod();
// 通过属性描述符调用setter方法
Method method = pd.getWriterMethod();
// 通过反射调用制定方法,返货/设置值
Object result = method.invoke(user);
异常
其中Error和RuntimeException异常也称为 非受查异常,这种异常不受控(Error)或者应该避免发生(RuntimeException),因此开发者不要字定义此类的子类;其他异常被称为 受查异常
java中只能抛出Throwable的子类,但是C++中可以抛出任何类型的对象,Java中捕获的异常默认以
final
修饰,代码中抛出异常会中止剩余代码执行,并且退出这个方法执行 java7自动关闭资源机制,关闭资源出现的异常被自动捕获并被抑制,并调用addSuppressed方法添加到被抑制的异常列表 异常“早抛出,晚捕获”
// 耦合try-catch和try-finallys是最佳实践,因为这种设计方式不仅简单,而且能处理finally中可能出现的异常
try{
try{
// 可能出现的异常
}finally{
// 关闭资源
}
}catch(受检查的异常类型 e){
// 异常处理
}
断言机制
断言机制使开发测试阶段插入的一些检查代码(如果检查不通过会抛出 AssertionError 异常)在发布时会自动移走,默认运行程序时是关闭断言的,通过运行程序加上-ea
命令启用断言,启用和禁用断言是类加载器功能,不会降低程序运行速度;当禁用断言时,程序会跳过断言代码
断言可以针对某个特定类或者特定包,通过
-da
参数来禁用断言使断言代码失效,有些 系统类 不是由类加载器创建而是直接由虚拟机直接加载,这些类通过-esa
来开启断言 断言只用于开发和测试阶段,用于报告致命的、不可恢复的错误,是一种战术性工具,而日志是一种在程序整个生命周期都可使用的策略性工具