JavaSE基础复习总结

面向对象的三大特征

封装

封装就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

继承

继承是面向对象的基本特征之一,继承机制允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为

多态

多态同一个行为具有多个不同表现形式或形态的能力。是指一个类实例(对象)的相同方法在不同情形有不同表现形式。
多态存在的三个必要条件:

继承
重写(子类继承父类后对父类方法进行重新定义)
父类引用指向子类对象

多态的实现原理

多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用(invokevitual)和接口引用调用(invokeinterface)的实现则有所不同。
下面来详解其过程:
假如有两个类A、B,在A类中我们调用了B类中的一个静态方法,在编译期,这个调用的动作会被编译成一条静态调用指令,该指令就对应常量池中的CONSTANT_Methodref表中所存储的该方法的符号引用,通过这个符号引用可以得到静态方法的全类名B,JVM加载B类,便会得到B类中方法的直接地址,该地址会被存储到A类常量池中对应的常量表中,这便是常量池解析过程,再次发起调用时,就会直接根据这个直接地址调用对应方法。 以上过程可以看出,在编译阶段我们就已经确定了应该执行哪一个字节码代码。

静态绑定

Java中的静态方法、私有方法以及final修饰的方法的调用,都属于静态绑定,对于重载的实例方法的调用,也是采用静态绑定。静态绑定的原理主要是一个常量池解析的过程,

动态绑定

在运行时根据具体对象的类型进行绑定。
在JVM加载类的同时,会在方法区中为这个类存放很多信息(详见《Java 虚拟机体系结构 》)。其中就有一个数据结构叫方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。

public class App {
    public static void main(String[] args) {
         Father father = new Son();
         father.f(); //father
    }
}
class Father{

    public void f(){
        System.out.println("father");
    }
}
class Son extends Father{
   public void f(){
        System.out.println("son");
    }
}

打印的结果大家也都比较清楚,但是JVM是如何知道father.f()调用的是子类Sun中方法而不是Father中的方法呢?这就是运用到了动态绑定
在JVM加载类的同时,会在方法区中为这个类存放很多信息
其中就有一个数据结构叫方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。
方法表的特点:

子类方法表中继承了父类的方法,比如Father extends Object。
相同的方法(相同的方法签名:方法名和参数列表)在所有类的方法表中的索引相同。比如Father方法表中的f1()和Son方法表中的f1()都位于各自方法表的第11项中。

对于上面的源代码,编译器首先会把main方法编译成下面的字节码指令:

0  new hr.test.Son [13] //在堆中开辟一个Son对象的内存空间,并将对象引用压入操作数栈
3  dup  
4  invokespecial #7 [15] // 调用初始化方法来初始化堆中的Son对象 
7  astore_1 //弹出操作数栈的Son对象引用压入局部变量1中
8  aload_1 //取出局部变量1中的对象引用压入操作数栈
9  invokevirtual #15 //调用f1()方法
12  return

很明显,根据对象(father)的声明类型(Father)还不能够确定调用方法f1的位置,必须根据father在堆中实际创建的对象类型Son来确定f1方法所在的位置。这种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做动态绑定机制 。

可参考文章
https://www.cnblogs.com/kaleidoscope/p/9790766.html

final关键字

修饰属性:

private final int MAXSIZE = 255;

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

修饰方法:

public final void add(){
        //方法逻辑
    }

使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。
**注意:**类的private方法会隐式地被指定为final方法。

修饰类:

public final class App {
}

当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

Static关键字

static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。

Static和非Static的区别

变量

静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。

方法

static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

代码块

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

单例模式

懒汉

线程不安全

class A{
private static A a;
private A(){
}
public static A getA(){
	if(a==null){
	a = new A();
}
	return a;
}
}

饿汉

//线程安全

class B{
    private static  B b = new B();
    private B(){
    }
    public static B getB(){
        return b;
    }
}

线程安全的懒汉

class C{
    private static C c;
private C(){}
  public synchronized static C getC(){
        if(c == null){
        c= new C();
        }
        return c;
        }

        }

静态内部类

class D{

 private D(){}
private static class Dd{
  private static final D DD = new D();	
}
public static D getD(){
return Dd.DD;
}
}

双重检查懒汉式

class E{
    private static E e;
    private E(){
    }
    public static E getE(){

        if(e==null){
            synchronized (E.class) {
                if(e==null) {
                    e = new E();
                }
            }
        }
        return e;
    }
}

访问限定符

在这里插入图片描述

重载和重写的区别

重载

  1. 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载是一个类中多态性的一种表现。

  2. Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。

  3. 重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。
    重载规则:
    被重载的方法必须改变参数列表(参数个数或类型不一样);
    被重载的方法可以改变返回类型;
    被重载的方法可以改变访问修饰符;
    被重载的方法可以声明新的或更广的检查异常;
    方法能够在同一个类中或者在一个子类中被重载。
    无法以返回值类型作为重载函数的区分标准。

重写

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
方法的重写规则:

参数列表与被重写方法的参数列表必须完全相同。

返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

父类的成员方法只能被它的子类重写。

声明为 final 的方法不能被重写。

声明为 static 的方法不能被重写,但是能够被再次声明。

子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

构造方法不能被重写。

如果不能继承一个类,则不能重写该类的方法。

Super 关键字的使用
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

接口和抽象类的区别

接口

Java中接口使用interface关键字修饰,特点为:

接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);
接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
一个类可以实现多个接口;
JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。	

抽象类

在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:

抽象类不能被实例化只能被继承;
包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;
抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。

接口与抽象类相同点

(1)都不能被实例化
(2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

接口与抽象类不同点

(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。	

异常相关

常见的异常

空指针异常: NullPointerException
数据类型转换异常:java.lang.ClassCastException
数组下标越界异常:ArrayIndexOutOfBoundsException
下标越界异常:IndexOutOfBoundsExecption
加载类失败异常:ClassNotFoundExecption
文件读写异常:IOExecption
操作数据库异常:SQLException

异常处理机制

try{
//可能会发生异常的代码
}catch(异常类){
执行异常
}finally{
一般情况下无论代码是否异常都会被执行的部分,
作为资源的释放使用\事务的提交
如果在执行代码块中使用了System.exit(1)
}

int peek(){
if(size==0){
throw new Exception
}
return element(size-1);
}

void fun() throws 异常类{
Thread.sleep(1000);
}

Object常用方法

Object是所有类的父类,任何类都默认继承Object。Object类到底实现了哪些方法?

equals

默认比较两个对象地址

String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。

toString

默认返回对象名+@+对象的十六进制的哈希值。

String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息

Clone

保护方法,实现对象的浅拷贝,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

getClass

final方法,获得运行时类型。

HashCode

该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。

wait

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。

(1)其他线程调用了该对象的notify方法。

(2)其他线程调用了该对象的notifyAll方法。

(3)其他线程调用了interrupt中断该线程。

(4)时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

notify

该方法唤醒在该对象上等待的某个线程。

nottifyAll

该方法唤醒在该对象上等待的所有线程。

finalize

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

类加载

类加载时机

1、创建对象实例,new对象的时候,会对类进行初始化,前提是类没有被初始化
2、调用类的静态属性或者是类的静态方法
3、通过class文件反射创建对象
4、初始化一个类的子类,使用子类的时候先初始化父类
5、java虚拟机启动时被标记为启动类的类,如:main方法所在的类

注:java类的加载是动态的,并不会一次性加载所有的类到JVM中才执行,保证程序能够正常运行的基础类,其他的类则在需要时才会加载,节约内存开销
不会进行分类加载的情况:

1、在同一个类加载器下只能初始化类一次,已经初始化的就不会在进行初始化
2、在编译的时候就能确定下来的静态变量,不会对类进行初始化,比如final修饰的静态变量

类的初始化顺序

(1)父类的静态变量  静态块
(2)子类的静态变量 静态块
(3)父类的实例变量 实例块 构造器
(4)子类的实例变量 实例块 构造器

类加载过程

加载:

通过双亲委派机制加载类 返回一个class对象

链接:

验证:

验证字节码的正确性 和JDK版本匹配

准备:

为类的静态的变量分配内存,并初始化默认值

解析:

把类中的符号引用转化为直接引用

初始化:

为类的静态变量赋予正确的初始值并且执行静态代码块

双亲委派模型

在这里插入图片描述
各个加载器的工作责任:
Bootstrap ClassLoader:
负责加载JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
Extension ClassLoader:
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
App ClassLoader:
负责加载classpath中指定的jar包及目录中class

双亲委派模型的工作过程:

1、当前的类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回已经加载的类型
2、如果没有找到,就去委托父类加载器去加载,父类加载器采用相同的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托其父类去加载,直到委托到启动类加载器为止,
3、从启动类开始加载,如果启动加载器失败,就是使用扩展类加载器来尝试加载,继续失败则会使用Application ClassLoader加载器类加载,继续失败就会抛出一个异常:ClassNotFoundException

使用双亲委派的好处:

1、安全性,避免用户自己编写的类动态替换java的核心类
2、避免类的重复加载,因为JVM判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的class文件被不同的类加载器加载得到的结果就是两个不同的类。

破坏双亲委派模型

第一次破坏:

由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。在此之前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法唯一逻辑就是去调用自己的loadClass()。

第二次破坏:

双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的同一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美。

如果基础类又要调用回用户的代码,那该么办?

一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,
它的代码由启动类加载器去加载(在JDK1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者的代码,但启动类加载器不可能“认识”这些代码。

为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

第三次破坏

双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换、模块热部署等,简答的说就是机器不用重启,只要部署上就能用。
OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi幻境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当受到类加载请求时,OSGi将按照下面的顺序进行类搜索:
1)将java.*开头的类委派给父类加载器加载。
2)否则,将委派列表名单内的类委派给父类加载器加载。
3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
7)否则,类加载器失败。

双亲委派模型被破坏举例(JDBC)

原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的mysql-connector-.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-.jar中的Driver类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由应用程序启动类去进行类加载。于是乎,这个时候就引入线程上下文件类加载器(Thread Context ClassLoader)。有了这个东西之后,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。

反射

什么是反射及反射机制

反射就是把java类中的各个成分映射成一个个java对象
即在运行状态时
1.对与任意一个类,都能够知道这个类的所有属性和方法,
2.对与任意一个对象,都能够调用这个对象的所有属性和方法。

反射代码实现

new实现

//创建一个动物对象:汤姆猫
        Animal tomcat = new Animal();
        //输入跑步时间,返回跑步距离
        int length = tomcat.run(2);

反射实现

Class clz = Class.forName("com.liangcheng.app.Animal");
            Method method = clz.getMethod("run", int.class);
             Constructor constructor = clz.getConstructor();
            Object object = constructor.newInstance();
            Object length = method.invoke(object, 2);

反射机制的优缺点

优点:
可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
缺点:
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

反射的原理

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值