侧边栏壁纸
博主头像
lmg博主等级

  • 累计撰写 55 篇文章
  • 累计创建 6 个标签
  • 累计收到 2 条评论
标签搜索

Java核心技术卷一总结

lmg
lmg
2020-06-13 / 0 评论 / 0 点赞 / 346 阅读 / 10,203 字
温馨提示:
本文最后更新于 2022-05-25,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

实例域private修饰和public

public : 允许程序中的任何方法对其进行修改和读取

private: 只有该类自身的方法能够访问这些实例域

私有方法:除了类自身方法,还可以通过反射访问。反射访问私有方法

static修饰实例域:这个类的实例共享这个实例域,它属于类,不属于对象。

public class Math
{
public static final double PI = 3.1415926;
}
//程序可以使用Math.PI获得这个常量,如果忽略static,PI就变成了Math类的一个实例域。需要通过Math类的对象访问PI,而且每一个Math对象都有它自己的一份PI拷贝

static修饰方法:不能访问非静态域,只可以访问静态域。不带隐式参数this

static修饰类:静态内部类

//静态内部类实现单例模式
public class Singleton {
    
   // 声明为 private 避免调用默认构造方法创建对象
    private Singleton() {
    }
    
   // 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}
/*当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。
只有当调用 getUniqueInstance()方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,
此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。*/

构造器

构造器总是伴随着new操作符的执行被调用

java方法总是按值传递,也就是说,方法得到的是所有参数值的一份拷贝。

所以:

1.一个方法不能修改一个基本数据类型的参数

2.一个方法可以改变一个对象参数的状态(改变的是对象的拷贝(引用的拷贝)的状态,两个引用执行一个对象)

3.一个方法不能让对象参数引用一个新的对象(如不能交换两个存储在a和b对象的引用,因为交换的是这两个的拷贝)

动态绑定

当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最适合的那个类的方法。

编译器在每次调用方法时都要进行搜索,时间开销相当大。因此虚拟机会预先为每个类创建一个方发表(method table),其中列出了所有方法的签名和实际调用的方法。

Java用于控制可见性的4个访问修饰符:

1)仅对本类可见----private

2)对所有类可见----public

3)对本包和所有子类可见----protected

4)对本包可见----默认,不需要修饰符

Object:所有类的父类

在Java中,只有基本类型不是对象,所有的数组类型,和其它的都继承自Object类

反射

Class类: Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类。一个类中这些成员,成员方法、构造方法、在加入类中都有一个类来描述。

熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

Java虚拟机为每个类型管理一个Class对象,获取Class类对象的三种方法

1)

Empolyee e;

Class c1 = e.getClass();

2)

Class c1 = Class.forName(“java.util.Random”);

3)

Class c1 = Random.class;

        Random e = new Random();
        Class c1 = e.getClass();
        Class c2 = Class.forName("java.util.Random");
        Class c3 = Random.class;
        System.out.println(c1==c2);//true 
        System.out.println(c2==c3);//true

LoadClass和forName的区别

class.forName()除了将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中的static块。
而classLoader只干一件事情,就是将.class文件加载到JVM中,不会执行static中的内容,只有在newInstance才会去执行static块。

创建一个与e具有相同类类型的实例

1)调用c2.newInstance()方法

此方法调用默认的构造器(无参构造器)初始化新创建的对象,如果这个类没有无参构造器,会抛异常。

2)Constructor类中的newInstance方法可以向类的构造器提供参数。

在java.lang.reflect包有三个类Field,Method和Constructor分别用于描述类的域,方法和构造器。

Employee e = new Employee("harry",21,'M');
Class c1 = e.getClass();
Field f = c1.getDeclaredField("name");//获取feiled
Object v = f.get(harry);// the v: "harry"

Method m = c1.getDeclaredMethod("getName");//获取方法
String name = m.invoke(e);//harry
//invoke方法,如果调用的是静态方法,第一个隐式参数可以被忽略,即可以将它设置为null
//如 m = Math.class.getMethod("sqrt", double.class);
//double y = m.invoke(null,x);

接口

接口中的方法都自动的设置为public

接口中不能包含实例域,却可以包含常量,接口中的域将自动设为public static final

clone

浅拷贝:没有克隆对象中引用的其它对象

深拷贝:克隆对象中引用的其它对象

动态代理

见Spring总结

常见面试题

抽象类和接口的区别

接口和抽象类都是继承树的上层,他们的共同点如下:

  1. 都是上层的抽象层
  2. 不能被实例化
  3. 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不比提供具体的实现。
    他们的区别如下:
  4. 抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法(default关键字修饰除外)。
  5. 一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口

ps:如果要继承多个类的多个方法,可以采用内部类。或直接就用接口。

class A extends B{
    class InnerA extends C{
    //在这里扩充类C
    }
}

JVM JDK 和 JRE 最详细通俗的解答

  • JVM

Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。

什么是字节码?采用字节码的好处是什么?

在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。

Java 程序从源代码到运行一般有下面 3 步:

  • JDK 和 JRE

JDK 是 Java Development Kit,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。

JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序

如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。

Java 多态

用最简单的一句话就是:父类型的引用指向子类型的对象。用一句比较通俗的话:同一操作作用于不同的对象,可以产生不同的效果。这就是多态。

多态是同一个行为具有多个不同表现形式或形态的能力。

多态的优点

  • 消除类型之间的耦合关系
  • 可替换性
  • 可扩充性
  • 接口性
  • 灵活性
  • 简化性

多态存在的三个必要条件

  • 继承
  • 子类对父类方法的重写
  • 父类引用指向子类对象

重载(Overload)和(Override)的区别

Overload: 同一个类或子类中,具有相同的方法名,但是方法的参数不同(类型,个数,顺序)。返回值可以相同也可以不同。

Override: 子类重写父类的方法,方法名相同,参数相同,返回类型相同。具体实现不同。

请说明一下final, finally, finalize的区别。

final 用于声明属性,方法和类,分别表示属性不可变,方法不可重写,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

String为什么是不可变对象

String底层实现是final修饰的char数组,不可变。同时String类也被final修饰,不可被继承。

StringBuilder 底层是一个可变的char数组。初始长度为16,存在扩容。

StringBuffer 同StringBuilder,经过Synchronized修饰

Default关键字

1.在switch语句的时候使用default

2.在定义接口的时候使用default来修饰具体的方法

Java八种基本数据类型,各占多少字节

byte 1字节;short 2字节;int 4字节;long 8字节;char 2字节,可以存储一个汉字。float 4字节;double 8字节;boolean false/true(理论上占用1bit,1/8字节,bai实际处理按1byte处理)

基本类型和引用类型的区别

基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上。

引用数据类型在被创建时,首先要在栈上给其引用(句柄)分配一块内存,而对象的具体信息都存储在堆内存上,然后由栈上面的引用指向堆中对象的地址。

int和Integer装箱和拆箱

装箱时调用valueOf方法将原始类型值转换成对象

拆箱时调用intValue()方法

 Integer a=300;
 int b=300;
 System.out.println(a==b);//true,自动装,拆箱

 Integer a=300;
 Integer b=300;
 Integer c=100;
 Integer d=100;
 System.out.println(a==b);//false,比较的是引用
 System.out.println(c==d);//true,-128和127之间,有一个cache整型数组,用来放在缓存中。
//这样也就是说任意一个相同数值的Integer的数,如果在-128和127之间,那么它们之间的内存地址是相同的

序列化的优缺点(implements Serializbale)

序列化机制允许将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。

缺点:

1.Java序列化技术是Java语言内部的私有协议,其他语言无法进行反序列化。

2.序列化的性能较低(耗时)

3.码流太大(耗空间)

Integer和int的初始化值

引用类型的初始化值为null

int类型的为0

public class Lee2 {
    private static  Integer age1;
    private static int age2;
    public static void main(String[] args){
    
        System.out.println(age1);//null
        System.out.println(age2);//0
    }
}

Collections.sort和Arrays.sort排序原理

Collections.sort方法底层就是调用的Arrays.sort方法,而Arrays.sort使用了三种排序方法,插入排序,快速排序和优化的归并排序。

快速排序主要是对那些基本类型数据(int,short,long等)排序, 而归并排序用于对Object类型进行排序。

总结:
首先先判断需要排序的数据量是否大于60。
小于60:使用插入排序,插入排序是稳定的
大于60的数据量会根据数据类型选择排序方式:
基本类型:使用快速排序。因为基本类型。1、2都是指向同一个常量池不需要考虑稳定性。
Object类型:使用归并排序。因为归并排序具有稳定性。
注意:不管是快速排序还是归并排序。在二分的时候小于60的数据量依旧会使用插入排序

Java创建对象的几种方式

  1. new关键字
  2. 反射,newInstance
  3. clone()方法
  4. 反序列化

常见的编译时异常和运行时异常

  • 运行时异常
    1. NullPointerException
    2. StringIndexOutOfBoundsException String a = “abc” System.out.println(a.substring(1)); //正常,显示“bc” System.out.println(a.substring(4)); //错误,java.lang.StringIndexOutOfBoundsException: String index out of range: -1 因为一共只有3个字母。)
    3. ArithmeticException :当出现异常的运算条件时,抛出此异常。如除0异常
    4. ArrayIndexOutOfBoundsException int[] nums = new int[3]; System.out.println(nums[4]);
  • 编译时异常
    1. ClassNotFoundException :找不到具有指定名称的类的定义。
    2. FileNotFoundException :当试图打开指定路径名表示的文件失败时,抛出此异常。
    3. SQLException 比如SQL语句写错,访问的表不存在,连接数据库失败等等

java JDBC编程流程步骤

第1步:注冊驱动 (仅仅做一次)

Class.forName(“com.mysql.jdbc.Driver”);

第2步:建立连接(Connection)

Connection conn =DriverManager.getConnection(url, user, password);

第3步:创建运行SQL的语句(Statement)

Statement st = connection.createStatement();

Statement接口类还派生出两个接口类PreparedStatement和CallableStatement。PreparedStatement能够对SQL语句进行预编译,这样防止了 SQL注入,提高了安全性。PreparedStatement ps=connection.prepareStatement( "update user set id=? where username=?”)

第4步:运行语句

ResultSet rs =st.executeQuery(sql);

第5步:处理运行结果(ResultSet)

while(rs.next()){System.out.println(rs.getString("name"))}

第6步:释放资源

rs.close(); conn.close()

Java 关于强引用,软引用,弱引用的区别与用法

  • 强引用

    我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题

  • 软引用

    如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。

    应用:浏览器的后退按钮

    (1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建

    (2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

    这时候就可以使用软引用

  • 弱引用

    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

Java类的初始化过程

  1. 初始化父类中的静态成员变量和静态代码块 ;

  2. 初始化子类中的静态成员变量和静态代码块 ;

  3. 初始化父类的普通成员变量和代码块,再执行父类的构造方法;

  4. 初始化子类的普通成员变量和代码块,再执行子类的构造方法;

泛型

泛型本质是参数化类型,解决不确定对象具体类型的问题。

泛型的好处:① 类型安全,不存在 ClassCastException。② 提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的数据类型。

泛型用于编译阶段,编译后的字节码文件不包含泛型类型信息,因为虚拟机没有泛型类型对象,所有对象都属于普通类。例如定义 List<Object>List<String>,在编译后都会变成 List

IO流

Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。

同步与异步

  • 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
  • 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。

同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。

阻塞和非阻塞

  • 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
  • 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

那么同步阻塞、同步非阻塞和异步非阻塞又代表什么意思呢?

举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(同步阻塞)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(同步非阻塞)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。

  • BIO(Blocking I/O)

    同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。

    采用 BIO 通信模型 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在 while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。

    如果要让 BIO 通信模型 能够同时处理多个客户端请求,就必须使用多线程(主要原因是 socket.accept()socket.read()socket.write() 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 一请求一应答通信模型 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 线程池机制 改善,线程池还可以让线程的创建和回收成本相对较低。使用FixedThreadPool 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M)。如下图。

    采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

    伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。

  • NIO

    1. NIO的特性/NIO与IO的区别

    1.IO流是阻塞的,NIO流是不阻塞的。

    Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

    Java IO的各种流是阻塞的。这意味着,当一个线程调用 read()write() 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了

    2.IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。

    Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。

    在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

    最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。

    3.NIO 通过Channel(通道) 进行读写

    通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。

    4.Selectors(选择器)

    NIO有选择器,而IO没有。

    多路复用器,轮询检查多个 Channel 的状态,判断 Channel 是否处于可读或可写状态。使用前需要将 Channel 注册到 Selector,注册后会得到一个 SelectionKey,通过 SelectionKey 获取 Channel 和 Selector 相关信息。

    选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。

    NIO 读数据和写数据方式

    通常来说NIO中的所有IO都是从 Channel(通道) 开始的。

    • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
    • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

    数据读取和写入操作图示:

  • AIO

AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

0

评论区