该文章是博主采集于各大博文,用于复习和总结相关知识点,将会持续的收集和更新。
目前只维护两个JDK版本,一个是8,一个是11(2018年)
Java虚拟机(JVM)
有针对不同系统的特定实现
(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。JDK
它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。
JRE
是 Java运行时环境。 它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。
八种基本数据类型:byte、short、int、long、float、double、boolean、char。
一个字节等于8位
IEE754标准(32位):1位是符号位,8位是阶码用移码表示,23位尾数
java支持中文,因为其采用的是 Unicode 编码,使之更趋于国际化
类型可以存放一个汉字, java 中的 char 使用 utf-16 编码
编码名称 | 解释 |
---|---|
ASCII 字符编码 | 只支持英文字母、标点符号、数字字符等, ASCII 码占用 1 个字节,所以 ASCII 码最多可以表示 256 个字符. 小 a 97 大 A 65,’0’是 48 |
ISO-8859-1 | 有称 latin-1,是国际化标准或组织 ISO 制定的,主要为了西欧语言中的字符 编码,和 ASCII 兼容,仍不支持中文 |
GB2312/GBK/GB18030 | 主要是汉字编码,三种编码从容量上看是包含关系 简体中文: GBK < GB2312 < GB18030 繁体中文: Big5【大五码】 |
unicode | Unicode 统 一 了 全 世 界 上 的 所 有 文 字 编 码 , unicode 有 几 种 实 现 : UTF-8,UTF-16,UTF-32 java 语言采用的是 Unicode 编码,所以在 java 中标识符也可以使用中文 |
在 java 中基本类型可以相互转换, boolean 类型比较特殊不可以转换成其他类型
转换分为默认转换和强制转换:
默认转换:容量小的类型会默认转换为容量大的类型
byte–>short–> int–>long–>float–>double
byte、 short、 char 之间计算不会互相转换,首先先转换成 int
强制转换:
public class DataTypeTest08
{
public static void main(String[] args){
long x = 100L;
int y = x;//编译不通过
long a = 2147483648L;
int b = (int)a;
System.out.println(b);//出现精度丢失问题,大类型-->>小类型会出现问题,输出-2147483648
byte a = 1000;//出现错误, 1000 超出了 byte 的范围
long g = 10;
int h = g/3;//出现错误,多个数值在运算过程中,会转换成容量最大的类型
byte h3 = (byte)(int)g/3;//考察优先级,将g先转换成int,再强转成byte,再除以3得到int,赋值错误
byte h4 = (byte)(int)(g/3);//正确的
byte h5 = (byte)g/3;//考察优先级,先转换成byte,再运算
byte h6 = (byte)(g/3);//正确
short h7 = (short)(g/3);//正确
short i = 10;
byte j = 5;
short k = i + j;//错误的,short和byte运算,首先会转换成int再运算
}
}
短路与和逻辑与的区别?
短路与比逻辑与智能,短路与效率高。
短路或和逻辑或的区别?
短路或:左边的算子结果是 true,右边的表达式不执行,发生短路
a += 3和 a = a + 3; 是一样的吗?
扩展赋值运算符不改变运算结果的类型。初始类型和最终运算结果类型完全相同。
public class OperatorTest09
{
public static void main(String[] args){
byte b = 10;
//编译错误
//b = b + 3;
//修改
b = (byte)(b + 3);
System.out.println(b); //13
b += 3;
System.out.println(b); //16
b += 10000; //等同于 b = (byte)(b + 10000);
System.out.println(b); //32
}
}
switch 语句
switch 也称为多重分支,具体格式如下
switch (表达式) {
case 值 1:
语句
break;
case 值 2:
语句
break;
default:
语句
Break;
}
说明:
需求:
假定系统给定学生的考试成绩,考试成绩可以带有小数。
假定成绩是合法的[0-100],请根据学生考试成绩判断该
学生成绩等级:
[90-100] A
[80-90) B
[70-80) C
[60-70) D
[0-60) E
以上业务只能使用 switch 语句完成,不允许使用 if 语句。
public class SwitchTest04 {
public static void main(String[] args) {
//考试成绩合法
double score = 100;
//开始判断
int grade = (int) (score / 10);//case条件不能为浮点数
switch (grade) {
case 10:
System.out.println("A");
break;
case 9:
System.out.println("A");
break;
case 8:
System.out.println("B");
break;
case 7:
System.out.println("C");
break;
case 6:
System.out.println("D");
break;
default:
System.out.println("E");
}
//重点: case 是可以合并的
switch (grade) {
case 10:
case 9:
System.out.println("A");
break;
case 8:
System.out.println("B");
break;
case 7:
System.out.println("C");
break;
case 6:
System.out.println("D");
break;
default:
System.out.println("E");
}
}
}
for语句
for(;false;){//会出现编译错误,因为无法访问
System.out.println("呵呵");
}
for(;true;){//死循环
System.out.println("哈哈");
}
方法的返回值问题:
public class MethodTest07
{
//缺少返回语句,程序编译时无法判断是否能走到else,无法编译通过
public static int m1(){
boolean flag = true;
if(flag){
return 1;
}
}
//正确
public static int m2(){
boolean flag = true;
if(flag){
return 1;
}else{
return 0;
}
}
//编译错误
public static int m3(){
boolean flag = false;
if(flag){//
return 1;//return后不能接任何语句
System.out.println("??????????");
}
System.out.println("??????????");
return 0;
System.out.println("??????????");
}
}
面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低
这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。
而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
关于继承如下 3 点请记住:
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。
抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。比如,我们要设计一个学生成绩管理系统,考察学生这个对象时,我们只关心他的班级、学号、成绩等,而不用去关心他的身高、体重这些信息。
所有基本数据类型的都是值传递,其他类型的为址传递
this 关键字指的是当前调用的对象, 只能用在构造函数和实例方法内部,还可以应用在成员变量的声明上, static 标识的方法里是不能使用 this 的。
作用:代码复用。
调用父类的构造方法
没有显示地调用 super(); 父类的无参构造方法也执行
必须将 super 放到子类的构造函数的第一语句来调用父类的构造方法
调用父类的成员方法
为什么会有 super 关键字?
因为子类必须要调用父类的构造方法,先把父类构造完成,因为子类依赖于父类,没有父,也就 没有子
有时需要在子类中显示的调用父类的成员方法
那么我们以前为什么没有看到 super,而且我们也有继承,如: Student 继承了 Person?
注意构造方法不存在覆盖的概念,构造方法可以重载
可以用来修饰它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。
final 表示不可改变的含义
修饰引用变量:
主要修饰的是变量的地址
,那么这个引用只能指向一个对象,也就是说这个引用不能再次赋值,但被指向的对象是可以修改的public class FinalTest05 {
public static void main(String[] args) {
Person p1 = new Person();
//可以赋值
p1.name = "张三";
System.out.println(p1.name);
final Person p2 = new Person();
p2.name = "李四";
System.out.println(p2.name);
//不能编译通过
//p2 采用 final 修饰,主要限制了 p2 指向堆区中的地址不能修改(也就是 p2 只能指向一个对象)
//p2 指向的对象的属性是可以修改的
p2 = new Person();
}
}
class Person {
String name;
}
final 和 static 联合修饰实例变量==常量 (尽量使用一个静态工具类抽取出常量)
静态属性和静态代码块按照代码顺序执行,实例代码块和成员属性同理
使用 static 关键字可以定义静态语句块,静态语句块具有以下特点:
在类加载的时候就做一些事情,可以在静态语句块中来实现
实例语句块和静态代码块没有关系,实例语句块有以下特点:
在对象初始化时刻就做一些事情,可以在实例语句块中实现
//静态语句块
static{
System.out.println(1);
}
//实例语句块
{
System.out.println(1);
}
如何实现Java多继承?
使用内部类就可以多继承,严格来说,还不是实现多继承,但是这种方法可以实现多继承所需的功能,所以把它称为实现了多继承。
定义多个内部类,每个内部类都可以继承一个父类
然后定义创建每个内部类的成员变量
在方法中调用成员的方法
class Call {
public void callSomebody(String phoneNum){
System.out.println("我在打电话喔,呼叫的号码是:" + phoneNum);
}
}
class SendMessage {
public void sendToSomebody(String phoneNum){
System.out.println("我在发短信喔,发送给 :" + phoneNum);
}
}
public class Phone {
private class MyCall extends Call{
}
private class MySendMessage extends SendMessage{
}
private MyCall call = new MyCall();
private MySendMessage send = new MySendMessage();
public void phoneCall(String phoneNum){
call.callSomebody(phoneNum);
}
public void phoneSend(String phoneNum){
send.sendToSomebody(phoneNum);
}
public static void main(String[] args) {
Phone phone = new Phone();
phone.phoneCall("110");
phone.phoneSend("119");
}
}
继承特征:
继承是面向对象的重要概念,软件中的继承和现实中的继承概念是一样的
继承是实现软件可重用性的重要手段,如: A 继承 B, A 就拥有了 B 的所有特性,如现实世界中的儿子继承父亲的财产,儿子不用努力就有了财产,这就是重用性
Java 中只支持类的单继承,也就是说 A 只能继承 B, A 不能同时继承 C
Java 中的继承使用 extends 关键字,语法格式:
[修饰符] class 子类 extends 父类 {
}
方法的重载的条件
方法的覆盖(Override)的条件:
必须要有继承关系
为什么需要覆盖?
要点:
class Parent {
static {
System.out.println("父类的静态块");
}
private static String staticStr = getStaticStr();
private String str = getStr();
{
System.out.println("父类的实例块");
}
public Parent() {
System.out.println("父类的构造方法");
}
private static String getStaticStr() {
System.out.println("父类的静态属性初始化");
return null;
}
private String getStr() {
System.out.println("父类的实例属性初始化");
return null;
}
}
class Child extends Parent {
private static String staticStr = getStaticStr();
static {
System.out.println("子类的静态块");
}
{
System.out.println("子类的实例块");
}
public Child() {
System.out.println("子类的构造方法");
}
private String str = getStr();
private static String getStaticStr() {
System.out.println("子类的静态属性初始化");
return null;
}
private String getStr() {
System.out.println("子类的实例属性初始化");
return null;
}
}
public class Test {
public static void main(String[] args) {
new Child();
}
}
分析:
首先先加载类到JVM的方法区中,则先加载静态的内容,比如静态代码块和静态属性,并且先加载父类,且按照代码顺序加载
接着加载对象到堆内存中,先加载父类的实例语句块和实例属性,按照父类优先,根据代码顺序加载,最后加载构造方法
执行结果:
父类的静态块
父类的静态属性初始化
子类的静态属性初始化
子类的静态块
父类的实例属性初始化
父类的实例块
父类的构造方法
子类的实例块
子类的实例属性初始化
子类的构造方法
看我们以前示例中的 Person、 Student 和 Employee,从我们使用的角度来看主要对 Student 和 Employee 进行实例化, Person 中主要包含了一些公共的属性和方法,而 Person 我们通常不会实例化,所以我们可以把它
定义成抽象的:
在 java 中采用 abstract 关键字定义的类就是抽象类,采用 abstract 关键字定义的方法就是抽象方法
抽象的方法只需在抽象类中,提供声明,不需要实现
如果一个类中含有抽象方法,那么这个类必须定义成抽象类,一个抽象类不一定含有抽象方法
如果这个类是抽象的,那么这个类被子类继承,抽象方法必须被重写。如果在子类中不复写该抽象方法,那么必须将此类再次声明为抽象类
抽象的类是不能实例化的,就像现实世界中人其实是抽象的,张三、李四才是具体的
抽象类不能被 final 修饰
抽象方法不能被 final 修饰,因为抽象方法就是被子类实现的
抽象类中可以包含方法实现,可以将一些公共的代码放到抽象类中,另外在抽象类中可以定义一些抽象的方法,这样就会存在一个约束,而子类必须实现我们定义的方法,如: teacher 必须实现 printInfo 方法, Student也必须实现 printInfo 方法,方法名称不能修改,必须为 printInfo,这样就能实现多态的机制,有了多态的机制,我们在运行期就可以动态的调用子类的方法。所以在运行期可以灵活的互换实现。
抽象类和普通类的区别?
抽象类 | 普通类 |
---|---|
不能被实例化,也就是使用new关键字 | 可以被实例化 |
权限限定于Public和Protected,因为需要子类去继承抽象类 JDK 1.8以前,抽象类的方法默认访问权限为protected JDK 1.8时,抽象类的方法默认访问权限变为default |
没有权限限制 |
如果一个类继承抽象类,则必须实现抽象类的抽象方法 如果没有实现抽象方法,则该类必须定义成抽象类 |
不强制实现父类的方法 |
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
接口我们可以看作是抽象类的一种特殊情况,在接口中只能定义抽象的方法和常量(完全抽象)
注意:接口里的所有数据都是 public 修饰的!
在 java 中接口其实描述了类需要做的事情,类要遵循接口的定义来做事,使用接口到底有什么本质的好
处?可以归纳为两点:
接口和抽象类的区别?
接口 | 抽象类 |
---|---|
不能被实例化 | 不能被实例化 |
需要被子类实现,并实现接口的方法 | 需要被子类继承,并实现抽象方法 |
只能做方法的声明(JDK1.8之后允许方法体) | 可以做方法的声明,也可以做方法的实现 |
如果子类不能实现接口中的所有方法,则该类只能是抽象类 | 如果子类不能实现抽象类的所有抽象,则该类只能是抽象类 |
属性只能是静态的常量 | 没有限制 |
接口与接口之间可以多继承 | 只能单继承 |
泛化关系
实现关系
关联关系
一个类中属性是另个类
public class 学生 {
private 班级 班级;
// getter/setter
}
public class 班级 {
}
聚合关系
是关联关系的一种,有着较强的关联关系
在java中一个类是整体,使用对象数组包含另个类;另个类属于某个整体
public class 汽车 {
private 轮胎集合 轮胎;
//getter/setter
}
public class 轮胎 {
private 汽车 汽车;
//getter/setter
}
依赖关系
依赖关系是比关联关系弱的关系,在 java 语言中体现为返回值,参数,局部变量和静态方法调用
public class Test {
public static void main(String[] args) {
Person person = new Person();
}
}
class Person {
}
equals的源码是这样写的:
public boolean equals(Object obj) {
//this - s1
//obj - s2
return (this == obj);
}
所以,默认情况下比较的是地址值,但是可以让我们覆写该方法,实现对象的比较。
如何覆写equals方法?
public class ObjectDemo {
public static void main(String args[]){
Student student1 = new Student("生命壹号",22,"成都");
Student student2 = new Student("生命壹号",22,"成都");
System.out.println(student1==student2);
System.out.println(student1.equals(student2));
}
}
class Student {
private String name;
private int age;
private String address;
public Student(String name,int age,String address){
this.name = name;
this.age = age;
this.address = address;
}
//重写Object类中的equals方法(比较两个对象的值是否相等)
public boolean equals(Object obj){
//为了提高效率:如果两个内存地址相等,那么一定是指向同一个对内存中的对象,就无需比较两个对象的属性值(自己跟自己比,没啥意义嘛)
if(this==obj){
return true;
}
//为了提供程序的健壮性
//我先判断一下,obj是不是学生的一个对象,如果是,再做向下转型,如果不是,直接返回false。
//这个时候,我们要判断的是对象是否是某个类的对象?
//记住一个格式:对象名 instanceof 类名。表示:判断该对象是否是该类的一个对象
if(!(obj instanceof Student)){
return false;
}
//如果是就继续
Student s = (Student)obj;//强制转换,即向下转型(毕竟Object类型没有具体的对象属性)
return this.name.equals(s.name) && this.age == s.age && this.address.equals(s.address);//判断两个对象的属性值是否相等
}
}
==与 equals()区别?
== | equals() |
---|---|
等号比较的是值, 特别是比较引用类型,比较的是引用的内存地址的那个值 | 默认源码使用的是==,但是可以通过覆写该方法,实现对象的比较 |
对于基本数据的包装类型(Byte, Short, Character,Integer, Float, Double, Long, Boolean)除了 Float和 Double 之外,其他的六种都是实现了常量池的,因此对于这些数据类型而言,一般我们也可以直接通过==来判断是否相等
public class Test {
public static void main(String[] args){
Integer a = 127;
Integer b = 127;
System.out.println(a==b);//true
Integer c = 128;
Integer d = 128;
System.out.println(c==d);//false
}
}
因为 Integer 在常量池中的存储范围为[-128,127], 127 在这范围内,因此是直接存储于常量池的,而
128 不在这范围内,所以会在堆内存中创建一个新的对象来保存这个值,所以 m, n 分别指向了两个不同的
对象地址,故而导致了不相等。
当垃圾收集器将要收集某个垃圾对象时将会调用 finalize,建议不要使用此方法,因为此方法的运行时间不确定,如果执行此方法出现错误,程序不会报告,仍然继续运行
JVM当看到对象类含有finalize函数,会将该对象交给FinalizerThread处理,但是处理的时间不确定。
范围由大到小的排序:public > protected > 缺省 > private
对类的修饰只有public和缺省,内部类除外
修饰符 | 类的内部 | 同一个包中 | 子类 | 任何地方 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y |
N |
缺省 | Y | Y |
N | N |
private | Y |
N | N | N |
总结为一句话:private修饰的只能类的内部调用;缺省的可以在一个包中调用;protected扩展到了子类中,比如继承某个类,则可以使用那个类的属性和方法;public可以在任何地方访问。
分为四种内部类:
特点:
public class OuterClass
{
//静态变量
private static String s1 = "静态变量";
//实例变量
private String s2 = "实例变量";
//实例内部类
public class InnerClass
{
//编译错误,实例内部类中不允许有静态的声明
public static void m1(){}
//实例方法
public void m2(){
System.out.println(s1);
System.out.println(s2);
}
}
//入口
public static void main(String[] args){
OuterClass oc = new OuterClass();
InnerClass innerClass = oc.new InnerClass();//??
innerClass.m2();
}
}
特点:
public class OuterClass
{
//静态变量
private static String s1 = "静态变量";
//实例变量
private String s2 = "实例变量";
//静态内部类
//静态内部类可以使用任何一个访问控制权限修饰符修饰。
protected static class InnerClass{
//静态方法
public static void m1(){
System.out.println(s1);
//System.out.println(s2);
}
//实例方法
public void m2(){
System.out.println(s1);
//System.out.println(s2);
}
} //入口
public static void main(String[] args){
OuterClass.InnerClass.m1();//外部类.可以省略
InnerClass innerClass = new OuterClass.InnerClass();
innerClass.m2();
}
}
局部内部类访问本地变量的时候,方法中的参数需要使用 final 修饰
public class OuterClass {
private int a = 100;
//局部变量在内部类中使用必须采用 final 修饰
public void method1(final int temp) {
class Inner3 {
int i1 = 10;
//可以访问外部类的成员变量
int i2 = a;
int i3 = temp;
}
//使用内部类
Inner3 inner3 = new Inner3();
System.out.println(inner3.i1);
System.out.println(inner3.i3);
}
public static void main(String[] args) {
OuterClass out = new OuterClass ();
out.method1(300);
}
}
public class Test {
public static void main(String[] args){
//在方法中实现接口
new Thread(new Runnable() {
@Override
public void run() {
}
}).run();
}
}
任意的异常都是在运行时发生的!!!
所有的异常都是Throwable的子类
Thorwable有两个直接子类Error和Exception
Error:
Exception的直接子类:
Exception 的直接子类叫做编译时异常、受控异常、检查异常。它虽然叫做编译时异常,但是它不是发
生在编译阶段的异常, 之所以叫做编译时异常是因为编译时异常要求必须在程序编译的阶段就手动的处理,如果不处理这些异常的话,程序无法编译通过。
对于编译时异常有两种手段处理,一是 try catch 捕获,一是 throws 抛出
RuntimeException 的直接子类:
自定义异常:
Error
受控异常
不受控异常
NullPointerException(空指针异常)
ArithmeticException(算术异常)
ArrayIndexOutOfBoundsException(数组下表越界异常)
异常的捕获应该从小到大
一般有两种方式
finally 在任何情况下都会执行,除非JVM挂掉,通常在 finally 里关闭资源
public class ExceptionTest12 {
public static void main(String[] args) {
int r = method1();
//输出为: 100? 50?
System.out.println(r);//输出是50
}
private static int method1() {
int a = 10;
try {
a = 50;
return a;//直接返回值
}finally {
a = 100;//该语句也会执行,只是a已经返回
}
}
}
throws 和 throw 的区别 ?
throws | throw |
---|---|
thorws 是声明异常 | thorws 是声明异常 |
用在函数上 | 用在函数内部 |