首页 > 编程语言 >【八股文】 Java基础

【八股文】 Java基础

时间:2022-12-03 11:23:31浏览次数:35  
标签:八股文 String 对象 基础 字符串 Java 异常 Class append

核心关键词:final、finally、finalize、static

1. static

  • 基础知识

    1. static声明的成员变量为静态成员变量,或类变量
    2. 类变量的生命周期和类相同,在整个应用程序执行期间均有效
  • 细节

    1. static修饰的成员变量和方法,从属于类
    2. 普通变量和方法,从属于对象
    3. 静态方法不能调用非静态成员,编译将报错
    4. 被static修饰的方法或者变量,不需要依靠对象进行访问,只要类被加载,即类的生命周期开始,就可以通过类名进行访问。
    5. 在访问权限足够的情况下,对象是可以访问静态方法和静态变量的。所以通过this代表对象是可以访问静态方法和静态变量的。
    6. Java语法规定,static不允许修饰局部变量。
  • 用途
    便于在没有创建对象的情况下进行调用(方法/变量)。
    1.修饰类的成员方法、类的成员变量
    2.编写静态代码块,优化程序性能

  • 实例

    1. 静态方法
      静态方法不依赖于任何对象就可以直接访问,因此不需要this,也无法使用this,所有根据此特性,静态方法无法访问非静态成员变量和非静态方法,因为非静态成员变量和非静态方法都必须依赖于具体对象进行调用。
    2. 静态变量
      静态变量被所有对象共享,在内存中只有一个副本,在类初次加载时即被初始化。
      非静态变量是对象拥有的,在创建对象时初始化,存在多个副本,各个对象拥有的副本,互不影响。
    3. 静态代码块
      构造方法用于对象的初始化静态初始化快,用于类的初始化操作
      在静态代码块中不能直接访问非static成员。
      作用:提升程序性能。
      原因:减少重复对象的生成,降低空间浪费。
  • 面试点
    情景:B继承A,A和B类都有自己的静态代码块和构造器,main方法,new一个B对象,说明两个类的静态代码块和构造器的执行顺序。
    答案:执行main之前需要加载B类。加载时发现B继承A,于是先加载A类。加载A类时发现有staic块,先①执行static块。A类加载完,加载B类,发现B有static块,②执行static块。A、B加载之后,执行main方法,new一个B对象,④调用B(子)类构造器前,先③调用A(父)类构造器。
    结果:A静态代码块 → B静态代码块 → A构造器 → B构造器
    补充情景:C类含静态代码块和构造器,A和B类中均有一个C类对象C c = new C();
    结果:A静态代码块 → B静态代码块 → C静态代码块 → C构造器 → A构造器 → C构造器 → B构造器
    细节:调用B类构造器生成对象前,需要先初始化父类A的成员变量,执行C c = new C();,发现C未加载,加载C,发现C有静态代码块,执行静态代码块,再执行C构造器。调用父类A构造器,完成父类A初始化,再初始化B类成员变量,执行C类构造器。

2.final

  • 基础知识
    被final修饰的类是最终类,不能有子类
    如:String类

  • 细节

  1. 被final修饰的方法为最终方法,子类可以继承使用,但是不能修改
  2. 被final修饰的变量(常量,一旦被复制,不可修改,必须再初始化对象时赋值):
    • 成员变量:必须声明时初始化或构造器中初始化,否则编译报错
    • 局部变量:必须声明时赋值,否则声明不报错,调用时编译报错
    • 方法参数:方法中,不可改变该参数,例:public void Test(final Integer i)
  3. 被final修饰的数据类型:
    • 大多修饰基本数据类型,实际变量值不变
    • 修饰引用类型,引用地址不变,即不能再指向别的对象,所指对象内容是可以改变的
  4. final不能修饰
    • 不能修饰抽象类:因为抽象类被继承才会有作用,儿final修饰的类不能被继承
    • 不能修饰构造器:因为构造器既不能被继承,也不能被重写,因此使用final无意义
  5. final关键词有点
    • 使用final关键词提高性能,JVM和Java应用都会缓存final变量
    • final变量可以安全地在多线程环境下共享,而不需要额外同步开销
    • 使用final关键词,JVM会对方法、变量、类进行优化

3.finally

  • 基础知识
    finally与try、catch组合使用,用于捕捉异常进行处理。

  • 细节

    • finally块中内容先于try中return的执行。
      如果finally中有return,则直接会在finally中返回,这也是不建议在finally中返回的原因。
    • try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:
      1. 如inally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
      2. 如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
      3. 如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
        1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
        2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。

4.finalize

  • 基础知识

    1. finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
    2. finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性
    3. 不建议用finalize方法完成“非内存资源”的清理工作,但建议用于:
      ① 清理本地对象(通过JNI创建的对象);
      ② 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。
  • 原理

    1. Jvm会给每个实现了finalize方法的实例创建一个监听,这个称为Finalizer。每次调用对象的finalize方法时,JVM会创建一个java.lang.ref.Finalizer对象,这个Finalizer对象会持有这个对象的引用,由于这些对象被Finilizer对象引用了,当对象数量较多时,就会导致Eden区空间满了,经历多次youngGC后可能对象就进入到老年代了。
      java.lang.ref.Finalizer类继承自java.lang.ref.FinalReference,因此Finalizer类里有一个引用队列,是JVM和垃圾回收器打交道的唯一途径,当垃圾回收器需要回收该对象时,会把该对象放到引用队列中,这样java.lang.ref.Finalizer类就可以从队列中取出该对象,执行对象的finalize方法,并清除和该对象的引用关系。需要注意的是只有finalize方法实现不为空时JVM才会执行上述操作,JVM在类的加载过程中会标记该类是否为finalize类。
    2. 当老年代空间达到了OldGC条件时,JVM执行一次OldGC,当OldGC执行后JVM检测到这些对象只被Finalizer对象引用,这些对象会被标记成要被清除的对象,GC会把所有的Finalizer对象放入到一个引用队列:java.lang.ref.Finalizer.ReferenceQueue。
    3. JVM默认会创建一个finalizer线程来处理Finalizer对象。这个线程唯一的职责就是不断的从java.lang.ref.Finalizer.ReferenceQueue队列中取对象,当一个对象进入到队列中,finalizer线程就执行对象的finalize方法并且把对象从队列中删除,因此在下一次GC周期中可以看到这个对象和Finalizer对象都被清除了。
      大部分场景finalizer线程清理finalizer队列是比较快的,但是一旦你在finalize方法里执行一些耗时的操作,可能导致内存无法及时释放进而导致内存溢出的错误,在实际场景还是推荐尽量少用finalize方法。

补充:
目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;新生代又被划分成Eden 空间、 From Survivor 和 To Survivor 三块区域。

我们把Eden : From Survivor : To Survivor 空间大小设成 8 : 1 : 1 ,对象总是在 Eden 区出生, From Survivor 保存当前的幸存对象, To Survivor 为空。一次 gc 发生后:

  1. Eden 区活着的对象 + From Survivor 存储的对象被复制到 To Survivor ;
  2. 清空 Eden 和 From Survivor ;
  3. 颠倒 From Survivor 和 To Survivor 的逻辑关系: From 变 To , To 变 From 。可以看出,只有在 Eden 空间快满的时候才会触发 Minor GC 。而 Eden 空间占新生代的绝大部分,所以 Minor GC 的频率得以降低。当然,使用两个 Survivor 这种方式我们也付出了一定的代价,如 10% 的空间浪费、复制对象的开销等。

String相关

字符型常量和字符串常量的区别

形式上: 字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符
含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算,字符串常量代表一个地址值(该字符串在内存中存放位置)
占内存大小:字符常量只占一个字节,字符串常量占若干个字节(至少一个字符结束标志)

什么是字符串常量池

  • 基础知识
    字符串常量池又称为:字符串池,全局字符串池,英文也叫String Pool。 在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这就是我们今天要讨论的核心:字符串常量池。字符串常量池由String类私有的维护。
    在JDK7之前字符串常量池是在永久代里边的,但是在JDK7之后,把字符串常量池分进了堆里,如下图所示:
    image
    在堆中的字符串常量池:堆里边的字符串常量池存放的是字符串的引用或者字符串(两者都有)

  • 细节

    1. 创造字符串的两种方式:
      1. 采用字面值的方式创建一个字符串"aaa"
        JVM首先会去字符串池中查找是否存在"aaa"这个对象,
        如果不存在,则在字符串池中创建"aaa"这个对象,然后将池中"aaa"这个对象的引用地址返回给字符串常量str,这样str会指向池中"aaa"这个字符串对象;
        如果存在,则不创建任何对象,直接将池中"aaa"这个对象的地址返回,赋给字符串常量。
      2. 采用new关键字新建一个字符串对象
        JVM首先在字符串常量池中查找有没有"aaa"这个字符串对象,
        如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str1,这样,str1就指向了堆中创建的这个"aaa"字符串对象;
        如果没有,则首先在字符串常量池池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str1引用,这样,str1指向了堆中创建的这个"aaa"字符串对象。

字符串池的实现有一个前提条件:String对象是不可变的。因为这样可以保证多个引用可以同时指向字符串池中的同一个对象。如果字符串是可变的,那么一个引用操作改变了对象的值,对其他引用会有影响,这样显然是不合理的。

String、StringBuffer、StringBuilder

  • String 和 StringBuffer,StringBuilder
    相同点:String,StringBuffer,StringBuilder都是可以用来存储字符串的
    不同点

    1. String存储的字符串是不可变的,StringBuffer、StringBuilder存储的字符串是可变的。
      其中在String中增加字符串时,有两种方法:用 “+” 拼接和concat()添加【注意:这里拼接后的字符串是重新的一个字符串。这个字符串如果在常连池存在就直接指向,如果不在就是创建之后放入常连池,再指向,而并不是在原来的基础上拼接】
    2. 一般StringBuffer和StringBuilder添加字符串的速度比String添加的快【因为String是重新创建一个字符串,在重新指向;而StringBuffer和StringBuilder是直接在后面拼接】
  • StringBuffer 和 StringBuilder
    相同点:共用一套API,StringBuilder是在StringBuffer的基础上产生的;
    不同点

  1. StringBuffer线程安全,StringBuilder线程不安全;
  2. StringBuilder速率比StringBuffer速率快;(有点在某些方面有时候就是缺点,StringBuffer慢的原因就是有锁安全,因此就慢,想象一下自己在排队过安检的时候)

包装类型

image
前六个的父类是抽象类Number,Number在继承Object,后两个直接继承Object。

  1. Number提供了拆箱的方法。将基本数据类型变为包装类型成为装箱,反之为拆箱。
  2. Integer类的构造方法。Integer(int value)和integer( String s)两种构造方法
  3. Integer类提供两个常量:MAX_VALUE和MIN_VALUE
  4. 自JDK1.5之后,支持自动拆箱和装箱。

反射

什么是反射(内省)

反射是由Class对象开始的,从Class对象中,我们可以:

  1. 获取有关该类的全部成员的完整列表
  2. 找出该类的所有类型(它实现的接口和扩展的类)
  3. 发现关于类自身的信息(它所应用的修饰符public、abstract、final等等,或者它所在的包)

反射机制提供的功能主要有:

  1. 得到一个对象所属的类
  2. 获取一个类的所有成员变量和方法
  3. 在运行时创建对象
  4. 在运行时调用对象的方法

获取类的三种方法(返回类型都是Class类型)

  1. Class.forName("类的路径带包名")
  2. 类名.Class
  3. 对象.getClass
    注:必须先获得Class才能获取Method、Constructor、Field。

通过反射实例化对象

对象.newInstance()

注:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。 否则会抛出java.lang.InstantiationException异常。
举例:

class ReflectTest02{
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        // 下面这段代码是以反射机制的方式创建对象。

        // 通过反射机制,获取Class,通过Class来实例化对象
        Class c = Class.forName("javase.reflectBean.User");
        // newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。
        // 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

重点

  1. JDBC重点(Class.forName导致类加载)
    情景:只希望一个类的静态代码块执行,其他代码不执行,可以使用:Class.forName("完整类名");
    这个方法的执行会导致类加载,类加载时,静态代码块执行
  2. set()可以访问私有属性嘛?不可以,需要打破封装,才可以。
    image
        // 可以访问私有的属性吗?
        Field nameField = studentClass.getDeclaredField("name");
        // 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
        // 这样设置完之后,在外部也是可以访问private的。
        nameField.setAccessible(true);
        // 给name属性赋值
        nameField.set(obj, "xiaowu");
        // 获取name属性的值
        System.out.println(nameField.get(obj));

方法应用

  1. 反编译一个类的属性Field
//通过反射机制,反编译一个类的属性Field(了解一下)
class ReflectTest06{
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder s = new StringBuilder();

        Class studentClass = Class.forName("javase.reflectBean.Student");
        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");// Class类的getName方法
        //获取所有的属性
        Field[] fields = studentClass.getDeclaredFields();
        for (Field f : fields){
            s.append("\t");
            // 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
            // 用Modifier类的toString转换成字符串
            s.append(Modifier.toString(f.getModifiers()));
            if (f.getModifiers() != 0) s.append(" ");
            s.append(f.getType().getSimpleName());// 获取属性的类型
            s.append(" ");
            s.append(f.getName());// 获取属性的名字
            s.append(";\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
  1. 通过反射机制访问一个java对象的属性
/*
必须掌握:
    怎么通过反射机制访问一个java对象的属性?
        给属性赋值set
        获取属性的值get
 */
class ReflectTest07{
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //不使用反射机制给属性赋值
        Student student = new Student();
        /**给属性赋值三要素:给s对象的no属性赋值1111
         * 要素1:对象s
         * 要素2:no属性
         * 要素3:1111
         */
        student.no = 1111;
        /**读属性值两个要素:获取s对象的no属性的值。
         * 要素1:对象s
         * 要素2:no属性
         */
        System.out.println(student.no);

        //使用反射机制给属性赋值
        Class studentClass = Class.forName("javase.reflectBean.Student");
        Object obj = studentClass.newInstance();// obj就是Student对象。(底层调用无参数构造方法)

        // 获取no属性(根据属性的名称来获取Field)
        Field noField = studentClass.getDeclaredField("no");
        // 给obj对象(Student对象)的no属性赋值
        /*
            虽然使用了反射机制,但是三要素还是缺一不可:
                要素1:obj对象
                要素2:no属性
                要素3:22222值
            注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
         */
        noField.set(obj, 22222);

        // 读取属性的值
        // 两个要素:获取obj对象的no属性的值。
        System.out.println(noField.get(obj));
    }
  1. 通过反射机制调用一个对象的方法
/*
重点:必须掌握,通过反射机制怎么调用一个对象的方法?
    五颗星*****

    反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,
    将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,
    但是java代码不需要做任何改动。这就是反射机制的魅力。
 */
class ReflectTest10{
    public static void main(String[] args) throws Exception {
        // 不使用反射机制,怎么调用方法
        // 创建对象
        UserService userService = new UserService();
        // 调用方法
        /*
            要素分析:
                要素1:对象userService
                要素2:login方法名
                要素3:实参列表
                要素4:返回值
         */
        System.out.println(userService.login("admin", "123") ? "登入成功!" : "登入失败!");

        //使用反射机制调用方法
        Class userServiceClass = Class.forName("javase.reflectBean.UserService");
        // 创建对象
        Object obj = userServiceClass.newInstance();
        // 获取Method
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
//        Method loginMethod = userServiceClass.getDeclaredMethod("login");//注:没有形参就不传
        // 调用方法
        // 调用方法有几个要素? 也需要4要素。
        // 反射机制中最最最最最重要的一个方法,必须记住。
        /*
            四要素:
            loginMethod方法
            obj对象
            "admin","123" 实参
            retValue 返回值
         */
        Object resValues = loginMethod.invoke(obj, "admin", "123");//注:方法返回值是void 结果是null
        System.out.println(resValues);
    }
}

/*
反编译一个类的Constructor构造方法。
*/
class ReflectTest11{
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();

    Class vipClass = Class.forName("javase.reflectBean.Vip");

    //public class UserService {
    s.append(Modifier.toString(vipClass.getModifiers()));
    s.append(" class ");
    s.append(vipClass.getSimpleName());
    s.append("{\n");

    Constructor[] constructors = vipClass.getDeclaredConstructors();
    for (Constructor c : constructors){
        //public Vip(int no, String name, String birth, boolean sex) {
        s.append("\t");
        s.append(Modifier.toString(c.getModifiers()));
        s.append(" ");

// s.append(c.getName());//包名+类名
s.append(vipClass.getSimpleName());//类名
s.append("(");
Class[] parameterTypes = c.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++){
s.append(parameterTypes[i].getSimpleName());
if (i != parameterTypes.length - 1 ) s.append(", ");
}
s.append("){}\n");
}

    s.append("}");
    System.out.println(s);
}

}

  1. 反编译一个类的构造方法Constructor
/*
反编译一个类的Constructor构造方法。
 */
class ReflectTest11{
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder s = new StringBuilder();

        Class vipClass = Class.forName("javase.reflectBean.Vip");

        //public class UserService {
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");

        Constructor[] constructors = vipClass.getDeclaredConstructors();
        for (Constructor c : constructors){
            //public Vip(int no, String name, String birth, boolean sex) {
            s.append("\t");
            s.append(Modifier.toString(c.getModifiers()));
            s.append(" ");
//            s.append(c.getName());//包名+类名
            s.append(vipClass.getSimpleName());//类名
            s.append("(");
            Class[] parameterTypes = c.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++){
                s.append(parameterTypes[i].getSimpleName());
                if (i != parameterTypes.length - 1 ) s.append(", ");
            }
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }
}

反射机制的优缺点

  • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。

  • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。

反射机制的原理

流程

  • 准备阶段:编译期装载所有的类,将每个类的元信息保存至Class类对象中,每一个类对应一个Class对象
  • 获取Class对象:调用x.class/x.getClass()/Class.forName() 获取x的Class对象clz
  • 进行实际反射操作:通过clz对象获取Field/Method/Constructor对象进行进一步操作

反射机制的应用场景

反射是框架设计的灵魂。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
举例

  • 我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;
  • Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:
    1. 将程序内所有 XML 或 Properties 配置文件加载入内存中;
    2. Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;
    3. 使用反射机制,根据这个字符串获得某个类的Class实例;
    4. 动态配置实例的属性

泛型

  • 什么是泛型
    Java泛型是JDK5中引入的新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

  • 泛型的好处

  1. 保证了类型的安全性。
    在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果不小心插入了错误的类型对象,在运行时的转换处理就会出错。
    有了泛型后,定义好的集合names在编译的时候add错误类型的对象就会编译不通过。
    相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。
  2. 消除强制转换
    泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。
    image
  3. 避免了不必要的装箱、拆箱操作,提高程序的性能
    在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
    泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。
    image
  4. 提高了代码的重用性。
  • 泛型的原理?什么是类型擦除
    泛型本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。
    例如泛型类 ,只是用来现丁类型的,我们不知道其具体类型。反编译该类,会发现编译器擦除Caculate类后面的两个尖括号,并且将num的类型定义为Object类型。
    1. 大部分泛型类型都以Object进行擦除
public class Caculate<T>{
	private T num; //→ private Object num;
}
2. 使用extends和super语法的有界类型,T会被String擦除,如:
//这是一个类型限定的语法,它限定 T 是 String 或者 String 的子类
//也就是构建 Caculate 实例的时候只能限定 T 为 String 或者 String 的子类
//所以无论限定T为什么类型,String 都是父类,不会出现类型不匹配的问题,于是可以使用 String 进行类型擦除。
public class Caculate<T extends String>
{
	private T num;  //→  private String num;
}
  • 什么是泛型中的限定通配符和非限定通配符 ?

  • List<? extends T>和List <? super T>之间有什么区别?

    • 限定通配符:
      1. 表示类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类
      2. 表示类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类
    • 非限定通配符:类型为,可以用任意类型来替代。
  • 可以把List传递给一个接受List参数的方法吗?
    不可以
    原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
    Collection c = new Vector();//原来的方法接受一个集合参数,新的类型也要能传进去
    参数化类型不考虑类型参数的继承关系:
    Vector v = new Vector(); //错误!///不写没错,写了就是明知故犯
    Vector v = new Vector(); //也错误!
    编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
    Vector vectorList[] = new Vector[10];
    泛型中的类型参数严格说明集合中装载的数据类型是什么和可以加入什么类型的数据,注意:Collection和Collection是两个没有转换关系的参数化的类型。
    假设Vector v = new Vector();可以的话,那么以后从v中取出的对象当作String用,而v实际指向的对象中可以加入任意的类型对象;
    假设Vector v = new Vector();可以的话,那么以后可以向v中加入任意的类型对象,而v实际指向的集合中只能装String类型的对象。肯定会报错了

序列化

什么是序列化与反序列化

  • Java序列化就是指把Java对象转换为字节序列的过程
  • Java反序列化就是指把字节序列恢复为Java对象的过程。

序列化与反序列化的作用

  • 序列化最重要的作用:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
  • 反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
  • 总结:核心作用就是对象状态的保存和重建。(整个过程核心点就是字节流中所保存的对象状态及描述信息)

序列化的优点

将对象转为字节流存储到硬盘上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。
序列化成字节流形式的对象可以进行网络传输(二进制形式),方便了网络传输。
通过序列化可以在进程间传递对象。

什么时候需要序列化和反序列化

所有可在网络上传输的对象都必须是可序列化的,所有需要保存到磁盘的Java对象都必须是可序列化的。通常,程序创建的每个JavaBean类都实现Serializeable接口。

序列化的实现方式有哪些

1. 对实现serialiable接口的java对象,可以通过ObjectOutputStream与ObjectInputStream俩个重要类进行序列化与反序列化,当然还有其他java类的支持,比如ByteArrayOutputStream序列化为二进制字节,或者FileOutputStream序列化为二进制文件。

2. 通过一些第三方开源框架如XStream(当然还有很多这类框架,有空可以研究下性能),可以把java对象序列化为json文本文件或者为xml文本文件,这对与开放的网络传输非常方便,很多跨语言通信都通过json格式传输或xml传输;就传输效率来讲json格式比xml格式效率高,占用空间小。

3. 可以自己实现一种序列化框架,序列化最主要的就是暂存在某个地方,将来某个时间重新实例化,这个留个自己以后研究。

什么是serialVersionUID

serialVersionUID用作Serializable类中的版本控件。如果您没有显式声明serialVersionUID,JVM将根据您的Serializable类的各个方面自动为您执行此操作。

public class Dml implements Serializable {
     private static final long erialVersionUID = 2611556444074013268L;

序列号UID

简而言之,我们使用serialVersionUID属性记住Serializable类的版本,以验证加载的类和序列化的对象是否兼容。
不同类的serialVersionUID属性是独立的。因此,不同的类不必具有唯一的值。

  • 创建一个可序列化的类,并声明一个serialVersionUID标识符:
//代码来自baeldung
public class AppleProduct implements Serializable {
    private static final long serialVersionUID = 1234567L;
    public String headphonePort;
    public String thunderboltPort;
}

异常

Error 和 Exception 区别是什么?

  • Error类
    一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
  • Exception类
    表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

非受检查异常(运行时异常)和受检查异常(一般异常)区别是什么?

Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception )

  • 运行时异常ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。
  • 受检查的异常,要么用try...catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。

throw 和 throws 的区别是什么?

throws - 声明异常

用throws关键字在方法中声明可能出现的异常。如果出现异常,这些异常只是被抛出,方法本身不对其做出处理,而是传给上级处理。
格式:在方法名和大括号之间加入throws 异常类
例如:

public int  method ( int a )  throws   Exception {
}

可以声明多个异常,在异常类之间用逗号隔开:

public  int  method (int a)  throws  Exception 1 , Exception 2 ,Exception3 {
}

throw - 抛出异常

当异常发生时,在方法内部使用throw 关键字抛出异常,把它抛给上一级调用者。抛出的是异常对象或者异常引用。
例如:
public int method (int a) method throw Exception{
...
throw new ArithmeticException();
...
}

两者区别

  1. throws是声明异常用在方法上,throw是抛出异常用在方法中。
  2. throws后面加 异常类 , throw后面加 异常对象
  3. throws 是可能发生异常 ,throw是已经发生异常。

NoClassDefFoundError 和 ClassNotFoundException 区别?

NoClassDefFoundError异常

从类继承层次上看,NoClassDefFoundError是从Error继承的。和ClassNotFoundException相比,明显的一个区别是:NoClassDefFoundError并不需要应用程序去关心catch的问题。

当JVM在加载一个类的时候,如果这个类在编译时是可用的,但是在运行时找不到这个类的定义的时候,JVM就会抛出一个NoClassDefFoundError错误。比如当我们在new一个类的实例的时候,如果在运行是类找不到,则会抛出一个NoClassDefFoundError的错误。

ClassNotFoundException

一个检查异常。从类继承层次上来看,ClassNotFoundException是从Exception继承的,所以ClassNotFoundException是一个检查异常。
当应用程序运行的过程中尝试使用类加载器去加载Class文件的时候,如果没有在classpath中查找到指定的类,就会抛出ClassNotFoundException。一般情况下,当我们使用Class.forName()或者ClassLoader.loadClass以及使用ClassLoader.findSystemClass()在运行时加载类的时候,如果类没有被找到,那么就会导致JVM抛出ClassNotFoundException。
最简单的,当我们使用JDBC去连接数据库的时候,我们一般会使用Class.forName()的方式去加载JDBC的驱动,如果我们没有将驱动放到应用的classpath下,那么会导致运行时找不到类,所以运行Class.forName()会抛出ClassNotFoundException。

Java常见异常有哪些?

  1. java.lang.NullPointerException(空指针异常)
    调用了未经初始化的对象或者是不存在的对象。经常出现在创建图片,调用数组这些操作中,比如图片未经初始化,或者图片创建时的路径错误等等。对数组操作中出现空指针, 即把数组的初始化和数组元素的初始化混淆起来了。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化, 依然是空的,所以还需要对每个元素都进行初始化(如果要调用的话)。
  2. java.lang.ClassNotFoundException指定的类不存在
    这里主要考虑一下类的名称和路径是否正确即可,通常都是程序试图通过字符串来加载某个类时可能引发异常。比如:调用Class.forName();或者调用ClassLoad的finaSystemClass();或者LoadClass();
  3. java.lang.NumberFormatException字符串转换为数字异常
    当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常.如现在讲字符型的数据“123456”转换为数值型数据时,是允许的。但是如果字符型数据中包含了非数字型的字符,如123#56,此时转换为数值型时就会出现异常。系统就会捕捉到这个异常,并进行处理。
  4. java.lang.IndexOutOfBoundsException数组下标越界异常
    查看调用的数组或者字符串的下标值是不是超出了数组的范围,一般来说,显示(即直接用常数当下标)调用不太容易出这样的错,但隐式(即用变量表示下标)调用就经常出错了,还有一种情况,是程序中定义的数组的长度是通过某些特定方法决定的,不是事先声明的,这个时候先查看一下数组的length,以免出现这个异常。
  5. java.lang.IllegalArgumentException方法的参数错误
    比如g.setColor(int red,int green,int blue)这个方法中的三个值,如果有超过255的也会出现这个异常,因此一旦发现这个异常,我们要做的,就是赶紧去检查一下方法调用中的参数传递是不是出现了错误。
  6. java.lang.IllegalAccessException没有访问权限
    当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了Package的情况下要注意这个异常。
  7. java.lang.ArithmeticException数学运算异常
    当算术运算中出现了除以零这样的运算就会出这样的异常。
  8. java.lang.ClassCastException数据类型转换异常
    当试图将对某个对象强制执行向下转型,但该对象又不可转换又不可转换为其子类的实例时将引发该异常,如下列代码。
    Object obj = new Integer(0);
    String str = obj;
  9. java.lang.FileNotFoundException文件未找到异常
    当程序试图打开一个不存在的文件进行读写时将会引发该异常。该异常由FileInputStream,FileOutputStream,RandomAccessFile的构造器声明抛出,即使被操作的文件存在,但是由于某些原因不可访问,比如打开一个只读文件进行写入,这些构造方法仍然会引发异常。
  10. java.lang.ArrayStoreException数组存储异常
    当试图将类型不兼容类型的对象存入一个Object[]数组时将引发异常,如
    Object[] obj = new String[3]
    obj[0] = new Integer(0);
  11. java.lang.NoSuchMethodException方法不存在异常
    当程序试图通过反射来创建对象,访问(修改或读取)某个方法,但是该方法不存在就会引发异常。
  12. java.lang.NoSuchFiledException方法不存在异常
    当程序试图通过反射来创建对象,访问(修改或读取)某个filed,但是该filed不存在就会引发异常。
  13. java.lang.EOFException文件已结束异常
    当程序在输入的过程中遇到文件或流的结尾时,引发异常。因此该异常用于检查是否达到文件或流的结尾
  14. java.lang.InstantiationException实例化异常
    当试图通过Class的newInstance()方法创建某个类的实例,但程序无法通过该构造器来创建该对象时引发。Class对象表示一个抽象类,接口,数组类,基本类型 。该Class表示的类没有对应的构造器。
  15. java.lang.InterruptedException被中止异常
    当某个线程处于长时间的等待、休眠或其他暂停状态,而此时其他的线程通过Thread的interrupt方法终止该线程时抛出该异常。
  16. java.lang.CloneNotSupportedException 不支持克隆异常
    当没有实现Cloneable接口或者不支持克隆方法时,调用其clone()方法则抛出该异常。
  17. java.lang.OutOfMemoryException 内存不足错误
    当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
  18. java.lang.NoClassDefFoundException 未找到类定义错误
    当Java虚拟机或者类装载器试图实例化某个类,而找不到该类的定义时抛出该错误。
    违背安全原则异常:SecturityException
    操作数据库异常:SQLException
    输入输出异常:IOException
    通信异常:SocketException

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

在 一 → 四个关键词 → finally中已说明。

JVM 是如何处理异常的?

  1. JVM捕获异常
    1. 编译而成的字节码中,每个方法都附带一个异常表。
    2. 异常表中每一个条目代表一个异常处理器
    3. 触发异常时,JVM会遍历异常表,比较触发异常的字节码的索引值是否在异常处理器的from指针到to指针的范围内。
    4. 范围匹配后,会去比较异常类型和异常处理器中的type是否相同。
    5. 类型匹配后,会跳转到target指针所指向的字节码(catch代码块的开始位置)
    6. 如果没有匹配到异常处理器,会弹出当前方法对应的Java栈帧,并对调用者重复上述操作。

解释:异常表

  1. 每个方法都附带一个异常表
  2. 异常表中每一个条目, 就是一个异常处理器

解释:异常处理器

  1. 异常处理器由from指针、to指针、target指针,以及所捕获的异常类型所构成(type)。
  2. 这些指针的数值就是字节码的索引(bytecode index, bci),可以直接去定位字节码。
  3. from指针和to指针,标识了该异常处理器所监控的返回
  4. target指针,指向异常处理器的起始位置。如catch代码块的起始位置
  5. type:捕获的异常类型,如Exception

问题:如果在方法的异常表中没有匹配到异常处理器,会怎么样?

  1. 会弹出当前方法对应的Java栈帧
  2. 在调用者上重复异常匹配的流程。
  3. 最坏情况下,JVM需要编译当前线程Java栈上所有方法的异常表

IO

Java的IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流
  • 按照操作单元划分,可以划分为字节流和字符流
  • 按照流的角色划分为节点流和处理流

image
image

字节流如何转为字符流?

使用情景:
当编程时需大量读写,并不知文件格式时,采用字节流进行读写,再进行转换。
将字节流转换成字符流的过程被叫作转换流。
设计模式:装饰者模式
转换流:
InputStreamReader

public class InputStreamReader extends Reader {

    private final StreamDecoder sd;

    /**
     * Creates an InputStreamReader that uses the default charset.
     *
     * @param  in   An InputStream 要转换的字节流
     */
    public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }

    /**
     * Creates an InputStreamReader that uses the given charset.
     *
     * @param  in       An InputStream 待转换字符流
     * @param  cs       A charset 指定的字符编码
     *
     *将其转换后可直接使用字符流的方式进行读写
     *
     * @since 1.4
     * @spec JSR-51
     */
    public InputStreamReader(InputStream in, Charset cs) {
        super(in);
        if (cs == null)
            throw new NullPointerException("charset");
        sd = StreamDecoder.forInputStreamReader(in, this, cs);
    }

	......

}

OutputStreamWriter 基本相同
案例:

//文件中原有内容:abcde我爱中国123
FileInputStream fis1=new FileInputStream("D:\\aaa\\helloworld.txt");
InputStreamReader isr=new InputStreamReader(fis1,"utf-8");
int ch;
while((ch=isr.read())!=-1) {
	System.out.print((char)ch);
}
isr.close();
//输出:abcde我爱中国123
FileOutputStream fos2=new FileOutputStream("D:\\aaa\\helloworld.txt");
OutputStreamWriter osw=new OutputStreamWriter(fos2,"utf-8");
osw.write("我是中国人");
osw.write(65);
osw.write(66);
char[] c=new char[] {'1','2','3'};
osw.write(c);
osw.close();
//文件中写入:我是中国人AB123

字符流与字节流的区别?

  • 两者的组成不同:
    1. 字节流的组成:字节流是由字节组成的。
    2. 字符流的组成:字符流是由字符组成的。
    3. Java里字符由两个字节组成. 1字符=2字节
      JAVA中的字节流是采用ASCII编码的,
      字符流是采用好似UTF编码,支持中文的
  • 两者的处理不同:
    字节流是最基本的,采用ASCII编码,所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的但实际中很多的数据是文本。
    又提出了字符流的概念,采用Unicode编码,它是按虚拟机的encode来处理,也就是要进行字符集的转化这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联

BIO、NIO、AIO的区别?

BIO

BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

NIO

NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

AIO

AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

Java IO都有哪些设计模式?

设计策略

首先 Java 的 IO 库提供了一种链接(Chaining)机制,可以将一个流处理器跟另一个流处理器首尾相接,以其中之一的输出作为另一个的输入而形成一个流管道链接,譬如常见的 new DataInputStream(new FileInputStream(file)) 就是把 FileInputStream 流当作 DataInputStream 流的管道链接。

其次,对于 Java IO 流还涉及一种对称性的设计策略,其表现为输入输出对称性(如 InputStream 和 OutputStream 的字节输入输出操作,Reader 和 Writer 的字符输入输出操作)和字节字符的对称性(InputStream 和 Reader 的字节字符输入操作,OutputStream 和 Writer 的字节字符输出操作)。

设计模式

Java IO中涉及到适配器模式以及装饰者模式。

  • 装饰者模式:动态地将责任附加到对象上,若要扩展功能,装饰者模提供了比继承更有弹性的替代方案。
//把InputStreamReader装饰成BufferedReader来成为具备缓冲能力的Reader。
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  • 适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作。
//把FileInputStream文件字节流适配成InputStreamReader字符流来操作文件字符串。
FileInputStream fileInput = new FileInputStream(file);
InputStreamReader inputStreamReader = new InputStreamReader(fileInput);

标签:八股文,String,对象,基础,字符串,Java,异常,Class,append
From: https://www.cnblogs.com/mlstudyjava/p/16919158.html

相关文章

  • 2022-2023-1 20221318 《计算机基础和程序设计》第十四周学习总结
    这个作业属于那个班级https://edu.cnblogs.com/campus/besti/2022-2023-1-CFAP作业要求https://www.cnblogs.com/rocedu/p/9577842.html#WEEK14作业目标学习《C语言程......
  • 老司机带带你,教你学会Java中又骚又暴力的“反射”技术
    在Java中有这么一个很骚的技术,几乎贯穿了所有主流的框架,在所有主流框架的底层中你都可以看见它的身影,这个技术就是反射。关于反射,有很多小白会觉得很难,搞不清楚到底是怎么回......
  • Mysql基础
    1.数据库相关概念以前我们做系统,数据持久化的存储采用的是文件存储。存储到文件中可以达到系统关闭数据不会丢失的效果,当然文件存储也有它的弊端。假设在文件中存储以下......
  • Java中的简单题目
    输入输出importjava.util.Scanner;publicclassTestDemo1{publicstaticvoidmain(String[]args){Scannerscan=newScanner(System.in);inta=scan.nextInt(......
  • MinIo windows版基础知识(启动与使用)
    一:启动(1)第一步:打开CMD命令行窗口(2)第二步:输入以下命令  minio.exeserverD:\myMinIO解释:minio.exeserver后面跟的D:\myMinIO是minio.exe所在文件夹的路径,回车启动......
  • Java String 类和常量池
    String对象的两种创建方式:Stringstr1="abcd";Stringstr2=newString("abcd");//falseSystem.out.println(str1==str2);这两种不同的创建方法是有差别的,第一种方......
  • Java通过Lambda表达式根据指定字段去除重复数据(集合去重)
    这里博主给大家封装好了一个工具类,里面有两个方法。方法一:可以根据指定字段去除重复数据。方法二:可以获取到重复的数据。大家在使用过程中直接拷贝下方代码在要去重的类中调......
  • 强制解决Java参数乱码问题
    在我们日常开发中,常常复制的url路径都是这样的原路径:https://xxxx.oss-cn-hangzhou.xxxxxx.com/2022/xx/文件名.pdf复制出来的路径:https://xxxx.oss-cn-hangzhou.xxxxxx.com......
  • 【JavaEE进阶系列 | 从小白到工程师】基本类型包装类的使用,装箱以及拆箱与parseInt方
    一、包装类概述Java中的数据类型分为基本类型和引用类型两大类,使用基本类型可以提升效率但是java是面向对象的语言,java的设计思想是一切皆对象,而基本数据类型不是对象,于是J......
  • 看完这篇,还不懂JAVA内存模型(JMM)算我输
    欢迎关注专栏【JAVA并发】更多技术干活尽在个人公众号——JAVA旭阳前言开篇一个例子,我看看都有谁会?如果不会的,或者不知道原理的,还是老老实实看完这篇文章吧。@Slf4j(......