java总复习(一)

一.面向对象的三/四大特征

1.面向对象的三大特征

1.1封装性

把描述对象属性的变量及实现对象功能的方法结合起来,定义一个程序单位,并保证外界不能任意更改内部属性,能任意调动内部的方法接口。
优点:
1.实现了低耦合高内聚;
2.类内部的结构可以自由修改;
3.隐藏实现细节,提出公共的访问方式。

1.2继承性

大大增强了代码的可复用性,提高了软件的开发效率为程序的修改扩充了材料。继承通过extends关键字进行实现,并且java.long.Object类是所有类的父类。
局限性:
1.Java中继承是单继承,即一个子类只能继承一个父类;
2.支持多重继承,即一个父类可以有多个子类;
3.在继承中只能继承非私有化的属性和方法;
4.构造方法不能都被继承。
5.在继承时,子类的权限>=父类权限
(public>protected>默认>private)

1.3 多态

多态是同一个行为具有多种不同的表现形式或形态的能力。允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)。
发生动态绑定(动多态)的三个条件:
1.继承
2.方法的重写
3.向上造型基类引引用引用派生类对象
优点:
1.可替换性。多态对已存在代码具有可替换性。例如,多态对Animal类设计,对其他任何动物,如狐狸,也同样具有吃饭的行为,以及工作的能力。
2.可扩充性。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如:在实现了dog吃饭行为之后,还可以添加小狗特有的行为抓老鼠等。
3.接口性。多态是父类通过方法声明,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。

多态的实现原理(动态绑定)

首先介绍一下JVM的结构
在这里插入图片描述
当程序运行某个类时,这个类的class文件会通过类加载器进入到JVM中,并且会在方法区中建立该类的类型信息。

//父类
class Father{
  public void A(){
    System.out.println("Father");
  }
  public void A(int i){
    System.out.println("Father"+i);
  }
}
//子类
public class Child extends Father(){
  public void A(){
    System.out.println("Child");
  }
    public void A(char c){
    System.out.println("Father"+c);
  }
  public static void main(String[]args){
    Father father=new Child():
     father.A()://Child
  }
}

以上代码的结果输出是:Child
JVM在调用father.A()方法时怎么知道指向的是Father的方法还是Child的方法?
因为在JVM加载类的同时,会在方法区中为这个类存放很多信息。其中就有一个数据结构叫方法表。在方法表中,子类继承了父类的方法,故它们的方法都存放在各自表的相同位置。
根据father的声明类型(Father)其实不能够确定调用方法A,必须根据father在堆中实际创建的对象类型Child来确定A方法所在的位置。这种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做动态绑定机制 。

  public static void main(String[]args){
    Father father=new Child():
    char c='a';
    father.A()://Father96

在代码中可以清楚看到Father类中并没有A(char c)方法,但结果显示调用了Child中的A(char c)方法,JVM首先是根据对象father声明的类型Father来解析常量池的(也就是用Father方法表中的索引项来代替常量池中的符号引用)。如果Father中没有匹配到"合适" 的方法,就无法进行常量池解析,这在编译阶段就通过不了。
如果方法中的参数类型在声明的类型中并不能找到呢?比如上面的代码中调用father.A(char),Father类型并没有A(char)的方法签名。实际上,JVM会找到一种“凑合”的办法,就是通过 参数的自动转型 来找 到“合适”的 方法。比如char可以通过自动转型成int,那么Father类中就可以匹配到这个方法了。

class Father{  
    public void A(Object o){  
        System.out.println("Object");  
    }  
    public void A(double[] d){  
        System.out.println("double[]");  
    }  
      
}  
public class Demo{  
    public static void main(String[] args) {  
        new Father().f1(null); //打印结果: double[]  
    }  
}  

null可以引用于任何的引用类型,这时就有一个标准:如果一个方法可以接受传递给另一个方法的任何参数,那么第一个方法就相对不合适。比如上面的代码: 任何传递给A(double[])方法的参数都可以传递给A(Object)方法,而反之却不行,那么A(double[])方法就更合适。因此JVM就会调用这个更合适的方法。

2.面向对象的四大特征

在三大特征的基础上添了一个抽象性。此处只介绍一下抽象性。

  • 抽象的实现是通过abstract关键字来定义抽象类和抽象方法。
  • 一个类中有抽象的方法,则该类一定是抽象类,反过来不一定。
  • 继承抽象类的子类必须实现其抽象方法。
  • 既然子类能够实现抽象父类的方法,那么父类的抽象方法的访问修饰符必然是public,否则不能实现。
  • 抽象类不能被实例化,虽然抽象类中可以有不抽象的方法,但仍然不能实例化。

二.final关键字

final关键字从三个方面来说:变量、方法、类

1.变量:final int INITSIZE=10;常量不能被修改
2.方法:final void fun(){}  方法不能被重写
3.类: final class String{}  方法不能被继承

三.Static关键字

static关键字也从三个方面来说明:变量、方法、类
变量

静态变量实例变量
存储位置静态变量在方法去存储实例变量在堆中存储
使用静态变量鱼类有关,一个类只能有一个静态变量实例变量与对象有关,几个对象几个实例

方法

静态方法实例方法
调用方式依赖于类,通过类的静态方法调用依赖于类的对象,需要创建对象后通过实例方法使用
使用静态方法内部可以定义和使用实例变量,但无法直接调用实例方法,静态方法不能被重写实例方法内部不能定义静态变量,但可以直接调用是静态方法

:类只能用来修饰内部类,静态内部类和实例内部类的区别

静态内部类非静态内部类
声明不依赖于外部类对象依赖于外部类对象
定义类变量、类方法、常量、成员变量和方法常量、成员变量和方法
访问能访问外部类的静态成员变量和静态方法,不能引用外部类的对象(this)访问外部类的所有成员和方法

类的初始化顺序
无继承:
静态变量/静态代码块 -> main方法 -> 非静态变量/代码块 -> 构造方法
继承:
1.子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了;
2.静态变量、静态初始化块顺序取决于它们在类中出现的先后顺序
3.变量、初始化块初始化顺序取决于它们在类中出现的先后顺序

四.单例模式

单例模式的条件:
1.构造函数私有
2.公有静态函数返回实例对象

4.1饿汉单例

饿汉单例的对象是被修饰成final类型。
饿汉单例首先就会创建一个对象。
线程是否安全:安全(由类加载机制保证)
在这里插入图片描述

4.2懒汉单例

懒汉单例创建了延迟对象,尽可能的节省内存空间。
线程是否安全:不安全
在多线程编程中,使用懒汉式可能造成类的对象在内存中不唯一,虽然用过修改代码可以改正这些问题,但是降低了效率线程不安全。
在这里插入图片描述

4.3全局锁式

线程是否安全:安全
线程同步时的效率不高

    第一种:同步代码块
		灵活
		synchronized(线程共享对象){
			同步代码块;
		}
	第二种:在实例方法上使用synchronized
		表示共享对象一定是this
		并且同步代码块是整个方法体。
	
	第三种:在静态方法上使用synchronized
		表示找类锁。
		类锁永远只有1把。
		就算创建了100个对象,那类锁也只有一把锁

在这里插入图片描述

4.4静态代码块

线程是否安全:安全
类主动加载时才初始化实例,实现了懒加载策略
在这里插入图片描述

4.5双重校验锁式

线程是否安全:安全
其实现了懒加载的策略,同时也保证了线程同步时的效率。使用volatile关键字时,会强制当前每次读操作进行时,保证其他所有线程写操作已完成,使得了JVM内部的编译器舍弃了编译时优化,对于性能有一定的影响。

public class Single5{
private static volatile Single5 singleton5;
private Single5(){}
public static Single5 getInstance(){
   if(singleton5==null){
     synchronized(Single5.class){
       if(singleton5==null){
   //内层if判断使用的时间(即起作用的时间)
   //第一次两线程同时调用getInstance,都会进入外层if判断
   //内层if判断是针对第二个synchronized代码块线程,此时第一个线程已经创建出对象,第二个线程无需创建
   singleton5=new Single5();
   }
  }
return singleton5;

4.6静态内部类

线程是否安全:安全
不存在线程同步问题,且单例对象在程序第一次getInstance()时主动加载SingletonHolder 和 静态成员INSTANCE
在这里插入图片描述

4.7枚举式

线程是否安全:安全
不存在线程同步问题,且单例对象在枚举类型INSTANCE,第一次引用时通过枚举的 构造函数 初始化。
这种方式是Effective Java 书籍提倡的方式。它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
在这里插入图片描述

五.类加载过程

类加载过程主要是加载–连接–初始化,三个方面,其中连接又可以分为验证–准备–解析。

5.1类加载的时机:

  1. new
  2. 调用静态变量或方法 指令invokesstatic专门调用静态方法
    invokespecial 用来调用构造
    invokevirtual 用来调用实例
  3. 反射
  4. main函数所在类优先被加载
  5. 先初始化父类在初始化子类。
父类静态变量  父类静态块
子类静态变量  子类静态块
父类的实例变量、实例块、构造
子类的实例变量、实例块、构造

5.2加载过程

加载
1.通过全类名获取定义此类的二进制字节流,class文件
2.将字节流所代表的静态存储结构转换为方法区运行时的数据结构,即存储到方法区
3.在内存中生成一个代表该类的java.lang.Class 对象,作为方法区这些数据的访问入口
在这里插入图片描述
为什么采用双亲委派模型,优点是什么?
双亲委派模型优点:
(1)安全性,避免用户自己编写的类动态替换Java的一些核心类。如果不采用双亲委派模型的加载方式进行类的加载工作,那我们就可以随时使用自定义的类来动态替代Java核心API中定义的类。例如:如果黑客将“病毒代码”植入到自定义的String类当中,随后类加载器将自定义的String类加载到JVM上,那么此时就会对JVM产生意想不到“病毒攻击”。而双亲委派的这种加载方式就可以避免这种情况,因为String类已经在启动时就被引导类加载器进行了加载。
(2)避免类的重复加载,因为JVM判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的class文件被不同的类加载器加载得到的结果就是两个不同的类。
验证
验证的阶段是十分耗时的,它是重要但非必须。验证是为了使得防止字节流中有安全问题产生。Java的安全性是由编译器所保证的,而类加载是在虚拟机中进行的,当无法确保来源的时候则需要验证。

文件格式验证----->元数据验证----->字节码验证---->符号引用验证

文件格式验证
这个阶段主要验证输入的二进制字节流是否符合class文件结构的规范。二进制字节流只有通过了本阶段的验证,才会被允许存入到方法区中。
其是基于二进制字节流的
加载和验证是交叉进行的,在加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作。
元数据验证
本阶段对方法区中的字节码描述信息进行语义分析,确保其符合Java语法规范。基于类特定的数据结构的。
字节码验证
本阶段是验证过程的最复杂的一个阶段。通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。基于类特定的数据结构的。
符号引用验证
本阶段验证发生在解析阶段,确保解析能正常执行。基于类特定的数据结构的。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配,类的静态成员变量也存储在方法区中。
为静态成员变量设置初始值初始值为0、false、null等。
解析
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。

初始化
初始化阶段就是执行类构造器()的过程,是类加载的最后一步。
对于方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。
必须对类进行初始化的情况
1.当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
2.使用 java.lang.reflect 包的方法对类进行反射调用时 。
3.初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
4.当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值