java_oop

基础概念

JVM JRE JDK

JDK 用来开发的工具,面向开发人员
JRE java运行环境,面向java文件
JVM java虚拟机,解释class文件(字节码文件)成机器码,让不同的操作系统都能够执行。

  1. bin(JVM) + lib(类库)= JRE

  2. JRE + java工具(javac,java,jconsole)=JDK

  3. java是解释与编译共存的语言,原因在于.class -> 机器码这一步,JVM类加载器先加载字节码文件,然后通过解释器逐行解释,速度很慢(而且有些方法和代码块会被反复调用)。后面引入了JIT编译器,采用运行时编译的方式,会将第一次编译后字节码对应的机器码保留下来,下次可以直接使用。


包装类型

  1. 8种基本数据类型都有对应的包装类型,包装类型不赋值就是null

  2. Byte,Short,Integer,Long都实现了常量池技术(默认创建[-128,127]的相应类型的缓存数据),Character创建了[0, 127]的缓存数据,Boolean直接返回True或False。

Integer i1 = 40; //发生装箱,等价于Integer i1 = Integer.valueOf(40)。i1直接使用常量池中的对象
Integer i2 = new Integer(40);  //创建新的对象
System.out.println(i1==i2);
  1. 所有整型包装类对象之间值的比较,全部使用 equals 方法比较

拆箱、装箱

Integer i = 10;  //装箱, ->调用了valueOf()方法 
int n = i;   //拆箱,-> intValue()方法

等价于

Integer i = Integer.valueOf(10);
int n = i.intValue();

频繁拆装箱,严重影响性能,避免该类型操作


== 和 equals

  1. ==对比的是栈中的值,对于基本类型,对比变量值,引用类型对比堆中对象的地址

  2. equals存在于object中,默认采用==比较,通常需要重写
    (当创建String类型的对象时,虚拟机会在常量池中查找有没有存在的值和要创建的值相同的对象,如果有就把它赋给当前引用,没有就在常量池中重新创建一个String对象。)

String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb);// true
System.out.println(a == b);// false
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true

hashCode和equals

hashCode()作用是获取哈希码(散列码),返回一个int整数,作用是确定该对象在哈希表中的索引位置。定义在JDK的Object.java中,是一个native方法,java中任何类都含有hashCode()函数。散列表中存储的是key-value,特点是能够根据key快速检索出对应的值。

  • 以HashSet()如何检查重复为例子来说明为什么要有hashCode:
    对象加入HashSet时,计算hashcode值判断对象加入的位置,如果该位置有值,会调用equals方法检查两个对象是否相同。相同不会让该对象加入进去,不同则重新散列(特殊处理)到其他位置。这样就减少了equals的次数,提高了执行速度
  1. 两个对象相等,hashcode一定相等

  2. 两个对象相等,分别调用equals都返回true

  3. 两个对象相同hashcode,它们不一定相等(哈希碰撞,越糟糕的哈希算法越容易发送碰撞)

  4. equals方法被重写过,hashCode()也必须重写(因为1、2,若equals被重写了,但hashCode()没被重写,就可能导致equals判断相等的两个值,hashCode值却不同

  5. hashCode()默认行为是对堆上的对象产生独特值。若未被重写,则该class的两个对象无论如何都不会相等(即使指向相同的数据)


成员变量和局部变量

  1. **语法形式:**成员变量属于类,局部变量在代码块或方法中定义的变量或是方法的参数;成员变量可以被private、public、static修饰,局部变量不能。但是成员和局部都可以被final修饰

  2. **存储方式:**如果成员变量被static修饰,则属于类,如果没有,则属于实例。对象存在于堆内,局部变量则存在于栈内。

  3. **生存时间:**成员变量是对象的一部分,随着对象的创建而存在,而局部变量随着方法的调用而自动消失。

  4. **默认值:**成员变量没有赋初值,则自动以类型的默认值赋值(例外:被final修饰的成员变量必须显式地赋值),局部变量不会自动赋值;


创建对象

  • 创建对象用new,创建的对象实例在堆内存中,对象引用指向对象实例(对象引用放在栈内存中)

  • 为什么创建对象的时候加一个括号?因为要调用对象的无参的构造方法。

  • 构造方法不能被重写,可以被重载


封装、继承、多态

  1. 封装
    把一个对象的状态信息隐藏在内部,不允许外部对象直接访问对象的内部信息。属性私有化

  2. 继承
    子类拥有父类所有属性和方法,但是私有方法和属性无法访问,只是拥有
    子类可以拥有自己的属性和方法,即对父类扩展
    子类可以用自己的方式实现父类的方法

  3. 多态
    具体表现为父类的引用指向子类的实例
    引用类型的变量调用哪个类中的方法只有在程序运行时才知道
    多态不能调用只在子类存在不在父类中存在的方法
    如果子类重写了父类的方法,执行的是子类覆盖的方法,否则执行父类


接口和抽象类

  • 共同点
    都不能被实例化
    都可以包含抽象方法
    都可以有默认实现的方法(java 8可以用default关键字在接口中定义默认方法

  • 区别
    接口主要对类的行为约束,抽象类用于代码复用(强调所属关系)
    接口可以多继承,抽象类不能
    接口成员变量只能是public static final类型,不能被修改且必须有初始值,抽象类成员变量默认default,可以在子类中重新被定义与赋值。


深拷贝和浅拷贝

  • 浅拷贝:在堆上创建一个新的对象(区别于引用拷贝),如果原对象内部属性是引用类型,浅拷贝会直接复制内部对象的引用地址,即拷贝对象和原对象共用一个内部对象。

  • 深拷贝:完全复制整个对象,包括对象包含的内部对象。

  • 浅拷贝只复制所考虑的对象,不复制它引用的对象


List、Set、Queue、Map

  • List:有序,按对象进入顺序保存对象,可重复,允许多个null元素对象,可以使用Iterator取出所有元素逐一遍历,还可以使用get(int index)获取指定下表的对象

  • Set:无序不可重复,最多允许一个null元素对象,只能用Iterator接口取得所有元素,再逐一遍历

  • Queue:先后顺序有序可重复

  • Map :键值对存储,key无序不可重复,value无序可重复。每个键最多一个值


###ArrayList和LinkedList的区别


方法

  • 静态方法为什么不能调用非静态方法?

final

修饰类:不可被继承
修饰方法:不可被重写,但是可以被重载
修饰变量:变量一经赋值,不可更改

  • 修饰成员变量
    类变量:只能在静态初始化块中指定初始值或者声明该变量时指定初始值
    成员变量:非静态初始化块、声明或在构造器中执行初始值

  • 修饰局部变量
    必须由程序员初始化,可以定义时不指定,后面再赋(仅一次)

  • 修饰基本类型和引用类型
    基本类型:不能更改
    引用类型:不能指向另一个对象,但是引用的值可以修改

  • 为什么局部内部类和匿名内部类只能访问局部final变量
    内部类和外部类是同一级别,不会因为定义在方法中随着方法执行完毕就被销毁
    矛盾1:因为垃圾回收机制,导致外部类执行完毕,局部变量就被销毁,而内部类对象访问了一个不存在的变量。解决方法是copy一份给内部类。
    矛盾2:但必须保证两个变量一致,如果内部类中修改了成员变量,方法中的局部变量也要跟着变,怎么办?所以需要妥协,即在局部变量上加一个final。


String、StringBuilder、StringBuffer

  1. String
  • final修饰的,不可变,每次操作都会产生新的对象(s+“d”)
    浪费内存,不断创建对象

  • ++=是专门为String重载过的运算符(也是仅有的两个),其实际过程是用StringBuilder调用append()实现的,拼接完成后调用toString()得到一个String对象。
    因此String不适合在循环内使用+或+=的拼接方式,编译器不会创建单个StringBuilder进行复用,每一次循环都会单独创建一个StringBuilder对象。

  • String#equals()Object#equals()的区别:String中的equals方法是被重写过的,比较的是字符串的值是否相等,Object比较的是对象的内存地址。

  • 字符串常量池是JVM为了提高性能和减少内存消耗针对String专门开辟的一块区域,目的是为了避免字符串的重复创建。

String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa==bb);// true

(JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区。JDK1.7 的时候,字符串常量池被从方法区拿到了堆中。)

  1. StringBuilder
    原对象上操作
    线程不安全(多线程环境、共享变量,结果是正常的,不会被更改,是预计结果,不会出现并发问题,即线程安全

  2. StringBuffer
    原对象上操作
    线程安全(因为里面的方法都是synchronized修饰的)
    对方法加了同步锁,或对调用的方法加了同步锁

性能:StringBuilder > StringBuffer > String


重载和重写

  • 重载:同一个类中,方法名相同,参数类型、个数、顺序,方法返回值、访问修饰符都可以不同(编译报错),发生在编译时

  • 重写:父子类中,方法名、参数列表必须相同,返回值范围小于父类抛出异常范围小于父类访问修饰符范围大于父类;子类不能重写private方法


接口和抽象类

  1. 抽象类可存在普通成员函数,接口只能存在public abstract方法中
  2. 抽象类中成员变量可以是各种类型的,接口中成员变量只能是public static final类型的
  3. 抽象类只能继承一个,接口可以继承多个

泛型

Java 泛型(generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。

  1. 泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}
  1. 泛型接口
public interface Generator<T> {
    public T method();
}

实现泛型接口,可以指定也可以不指定类型

  1. 泛型方法
public static <E> void printArray(E[] inputArray) {
    for (E element : inputArray) {
        System.out.printf("%s ", element);
    }
    System.out.println();
}
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray(intArray);
printArray(stringArray);
  1. 常用通配符: T、E、K、V?
  • ? 不确定
  • T(type) 具体的一个java类型
  • K V(key value)表示键值中的key、value
  • E element

反射

赋予了我们在运行时,分析类和执行类中方法的能力,通过反射可以获取一个了类中的所有属性和方法,你还可以调用这些方法和属性。

  1. 优缺点
    优点: 代码更灵活,为各种框架提供开箱即用的功能提供了便利
    缺点: 增加安全问题,比如会无视泛型参数的安全检查(编译时)。另外,性能稍差。
  2. Spring框架大量使用了动态代理,动态代理的实现也依赖于反射

基本操作:

  1. 创建一个要使用反射操作的类TargetObject
package cn.javaguide;

public class TargetObject {
    private String value;

    public TargetObject() {
        value = "JavaGuide";
    }

    public void publicMethod(String s) {
        System.out.println("I love " + s);
    }

    private void privateMethod() {
        System.out.println("value is " + value);
    }
}

  1. 使用反射操作这个类的方法以及参数
package cn.javaguide;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
        /**
         * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
         */
        Class<?> tagetClass = Class.forName("cn.javaguide.TargetObject");
        TargetObject targetObject = (TargetObject) tagetClass.newInstance();
        /**
         * 获取 TargetObject 类中定义的所有方法
         */
        Method[] methods = targetClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }

        /**
         * 获取指定方法并调用
         */
        Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
                String.class);

        publicMethod.invoke(targetObject, "JavaGuide");

        /**
         * 获取指定参数并对参数进行修改
         */
        Field field = targetClass.getDeclaredField("value");
        //为了对类中的参数进行修改我们取消安全检查
        field.setAccessible(true);
        field.set(targetObject, "JavaGuide");

        /**
         * 调用 private 方法
         */
        Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
        //为了调用private方法我们取消安全检查
        privateMethod.setAccessible(true);
        privateMethod.invoke(targetObject);
    }
}
  1. 输出内容
publicMethod
privateMethod
I love JavaGuide
value is JavaGuide

注解

可以看作一种特殊的注释,主要用于修饰类、方法或者变量
本质是一个继承了Annotation的特殊接口

注解只有被解析之后才会生效,常见解析方法:

  • 编译器直接扫描:编译代码时扫描对应注解并处理。比如@Override

  • 运行期通过反射处理:框架自带的注解(Spring的@Value、@Component)都是反射来处理的


Exception和Error

  1. 异常

    共同父类——Throwable类

    • Exception:程序本身可以处理的异常,可以通过catch捕获,分为Checked Exception(受检查异常,必须处理)和Unchecked Exception(不受检查异常,可以不处理)
    • Error:程序无法处理的错误,无法通过catch捕获,如Java虚拟机运行错误(Virtual MachineError)、虚拟机内存不够(OutOfMemoryError)、类定义错误(NoClassDefFoundError)。这些错误发生时,JVM一般会选择线程终止。
  2. 受检查异常和不受检查异常区别
    没有被catch/throw处理则无法通过编译,除了RuntimeException及其子类,其他Exception类及其子类都是受检查异常。

  3. Throwable类常用方法

  • String getMessage() :异常发生的简要描述
  • String toString():异常发生的详细信息
  • String getLocalizedMessage():异常本地化信息。使用子类可以重写这个方法,若未重写则结果与getMessage一样
  1. try-catch-finally
  • 无论是否捕获到异常,finally都会执行。若try或catch中有return语句,finally会在方法返回前执行。
  • 不要在finally中使用return:当try和finally都有return时,try中return返回值会暂存在一个本地变量中,执行到finally中的return后,这个本地变量的值就变成了finally中的return返回值。
  • finally之前虚拟机被终止运行时,finally中的代码不会执行。
  1. try-with-resources
  • Java 7新特性
  • 所有实现了java.lang.AutoCloseable接口的类都可以在其中使用
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
             BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
            int b;
            while ((b = bin.read()) != -1) {
                bout.write(b);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }

I/O

从计算机结构的视角来看的话, I/O 描述了计算机系统与外部设备之间通信的过程。

冯诺依曼体系结构

为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为用户空间内核空间,用户进程想要执行IO操作必须通过系统调用间接访问内核空间。

  • UNIX 系统下, IO 模型一共有 5 种: 同步阻塞 I/O同步非阻塞 I/OI/O 多路复用信号驱动 I/O异步 I/O

  • Java三种常见IO模型:

    • BIO

      同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

      图源:《深入拆解Tomcat & Jetty》
    • NIO

      java1.4引入,NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。可以看作是 I/O 多路复用模型。而不是同步非阻塞

      图源:《深入拆解Tomcat & Jetty》

      同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。

      相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。

      但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。

      这个时候,I/O 多路复用模型 就上场了。

      img

      IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,目前几乎在所有的操作系统上都有支持。

      • select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。

      • epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率

    IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。

    Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

    img
    • AIO

      AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。

      异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

      img

      总结

      img
  1. 序列化和反序列化

    • 序列化:将数据结构或对象转换为二进制字节流的过程。(主要目的是通过网络传输对象或将对象存储到文件系统、数据库、内存中
    • 反序列化:将在序列化过程中生成的二进制字节流转换为数据结构或对象的过程
  2. transient

    阻止实例中用此关键字修饰的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。

    • 只能修饰变量,不能修饰类和方法
    • 如果修饰的int类型,反序列化后结果就是0(默认值)
  3. 键盘输入两种方法

    • Scanner

      Scanner input = new Scanner(System.in);
      String s  = input.nextLine();
      input.close();
      
    • BufferedReader

      BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
      String s = input.readLine();
      
  4. IO流划分

    • 流的流向:输入流、输出流
    • 操作单元:字节流、字符流
    • 流的角色:节点流、处理流

    IO流的40多个类都是从四个抽象类基类中派生出的:

    • InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
    • OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流
  5. 字符流作用

    不管是文件读写还是网络发送接收,信息最小存储单元都是字节,为什么还要有字符流呢?

    • 字符流有JVM将字节流转换得到的,这个过程非常耗时,若不注意编码类型还会出现乱码问题。因此IO流提供了直接操作字符的接口,方便直接对字符操作

代理

使用代理对象代替对真实对象的访问

  1. 静态代理

    手动完成、非常不灵活(接口新增方法,目标对象和代理对象都要修改)、麻烦(每个目标类单独写一个代理类)

    public SmsProxy(SmsService smsService){this.smsService = smsService;}

  2. 动态代理

  • JDK动态代理机制

    核心:InvocationHandler接口和Proxy

    Proxy类中频率最高的newProxyInstance()用来生成一个代理对象

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{
            ......
    }
    
    1. loader:类加载器,用于加载代理对象
    2. interfaces:被代理类实现的一些接口
    3. h:实现了InvocationHandler接口的对象

通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。

  • CGLIB动态代理机制

    JDK动态代理问题:只能代理实现了接口的类

    CGLIB可以避免

    核心:MethodInterceptor接口和Enhancer

    需要自定义MethodInterceptor并重写intercept方法,用于拦截增强被代理类的方法

    public interface MethodInterceptor
    extends Callback{
        // 拦截被代理类中的方法
        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                                   MethodProxy proxy) throws Throwable;
    }
    
    1. obj :被代理的对象(需要增强的对象)
    2. method :被拦截的方法(需要增强的方法)
    3. args :方法入参
    4. proxy :用于调用原始方法

    可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

  • 对比
    JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。

  • 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

  • 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

allback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}


1. **obj** :被代理的对象(需要增强的对象)
2. **method** :被拦截的方法(需要增强的方法)
3. **args** :方法入参
4. **proxy** :用于调用原始方法

可以通过 `Enhancer`类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 `MethodInterceptor` 中的 `intercept` 方法。

+ 对比
JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。

+ 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

+ 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。









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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值