《JAVA核心技术卷I(第11版)》第5章和第6章 复习

第五章 继承

1.final关键词

  • final修饰的类,为最终类,该类不能被继承。如String 类(线程安全)

  • final修饰的方法可以被继承和重载,但不能被重写

  • final修饰的变量不能被修改,是个常量

2.Object:所有类的超类

  • equals():

    • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。

    • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。

  • hashcode():

    • 等价的两个对象的hashcode()相等;但是两个对象的hashcode()相等并不一定代表两个对象等价。
  • toString():

    • 默认返回 类名@hash值

    • 如果想自定义返回,则需重写toString()方法

  • clone():

    • clone()需要实现Cloneable接口,否则会抛出不支持clone异常;
    • clone()分为浅拷贝和深拷贝两种
      • 浅拷贝:拷贝的两个对象引用相同的对象,即一方属性的修改会影响到另一方;
      • 深拷贝:拷贝的两个对象引用不同的对象,即一方属性的修改不会影响到另一方。
    • 为了避免拷贝过程出现异常和类型转换的情况,官方推荐使用使用构造方法进行拷贝,拷贝的两个对象引用不同的对象。

面试题:重写equals方法一定要重写hashcode方法?

  • 提高判断相等的效率;如果采用重写hashcode方法,那么可以先对两个比较对象的hashcode值是否相等,如果相等,则再次进行equals方法的比较(判断是否为null,数据类型是否相等,字段属性值是否相等);如果不相等,直接返回false。

  • 如果重写了equals就必须重写hashCode,如果不重写将引起与散列集合(HashMap、HashSet、HashTable、ConcurrentHashMap)的冲突。

3.泛型数组列表

面试题:数组和ArrayList的区别?

  • 数组和ArrayList的本质区别在于前者是类型安全的,而后者是类型不安全的.
  • ArrayList为了兼容所有类型对象,使用了Object数组,在使用元素的时候会有装箱和拆箱的操作,降低了程序的性能.
  • 数组的容量是固定的,而ArrayList会动态扩充容量,容量为原来的2倍.
  • ArrayList只有把元素添加进去之后才可以通过下标访问相应的元素;get(下标)方法获取元素,add(元素)添加元素.
  • 数组在创建的时候就已经确定了数据类型,并且它的长度是固定的,只能通过下标改变各个元素的值和访问.
  • 应用场景:
    • 如果已经知道数据的长度并且不需要频繁的做插入和删除操作,建议使用数组,反之亦然.

4.自动装箱和拆箱

基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。

Integer x = 2;     // 装箱 调用了 Integer.valueOf(2)
int y = x;         // 拆箱 调用了 X.intValue()

5.参数数量可变的方法

  • 使用…表示参数数量可变

6.枚举类

  • 什么情况下使用枚举类?

    • 一个类的对象是有限且固定的
  • 为什么不用静态常量来替代枚举类呢?

    public static final int SEASON_SPRING = 1;
    public static final int SEASON_SUMMER = 2;
    public static final int SEASON_FALL = 3;
    public static final int SEASON_WINTER = 4;

枚举类更加直观,类型安全。使用常量会有以下几个缺陷:

1. 类型不安全。若一个方法中要求传入季节这个参数,用常量的话,形参就是int类型,开发者传入任意类型的int类型值就行,但是如果是枚举类型的话,就只能传入枚举类中包含的对象。

2. 没有命名空间。开发者要在命名的时候以SEASON_开头,这样另外一个开发者再看这段代码的时候,才知道这四个常量分别代表季节。

7.反射

  • 概念:Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。

1.java反射的作用是什么?

答:反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

2.Java反射创建对象效率高还是通过new创建对象的效率高?

答:通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低。

3.除了使用new创建对象之外,还可以用什么方法创建对象?

答:使用Java反射可以创建对象。

4.反射的实现方式都有什么?

答:获取Class对象,有4种方法:(1)Class.forName(“类的路径”);(2)类名.class;(3)对象名.getClass();(4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象。

5.实现java反射的类有什么?

答:(1)Class:表示正在运行的Java应用程序中的类和接口,注意所有获取对象的信息都需要Class类来实现;

(2)Field:提供有关类和接口的属性信息,以及对它的动态访问权限;

(3)Constructor:提供关于类的单个构造方法的信息以及它的访问权限;

(4)Method:提供类或接口中某个方法的信息。

6.反射机制的优缺点:

答:优点(1)能够运行时动态获取类的实例,提高灵活性;(2)与动态编译结合Class.forName(‘com.mysql.jdbc.Driver.class’);//加载MySQL的驱动类

缺点:使用反射性能较低,需要解析字节码,将内存中的对象进行解析。

其解决方案是:通过setAccessible(true)关闭JDK的安全检查来提升反射速度;多次创建一个类的实例时,有缓存会快很多;ReflflectASM工具类,通过字节码生成的方式加快反射速度。

7.Java反射API有几类?

答:反射 API 用来生成 JVM 中的类、接口或则对象的信息。

(1)Class 类:反射的核心类,可以获取类的属性,方法等信息。

(2)Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。

(3)Method 类:Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。

(4)Constructor 类:Java.lang.reflec 包中的类,表示类的构造方法。

8.反射使用步骤(获取Class对象、调用对象方法)

答:(1)获取想要操作的类的Class对象,他是反射的核心,通过Class对象我们可以任意调用类的方法。

(2)调用 Class 类中的方法,既就是反射的使用阶段。

(3)使用反射 API 来操作这些信息。

9.Java反射机制的作用有什么?

答:作用有(1)在运行时判断任意一个对象所属的类;(2)在运行时构造任意一个类的对象;(3)在运行时判断任意一个类所具有的成员变量和方法;(4)在运行时调用任意一个对象的方法。

10.下面的代码哪些地方会产生编译错误?

class Outer {

class Inner {

}

public static void foo() {

new Inner();

}

public void bar() {

new Inner();

}

public static void main(String[] args) {

new Inner();

}

}

答:Java中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中foo和main方法都是静态方法,静态方法中没有this,也就是说没有所谓的外部类对象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样做:

new Outer().new Inner();

在java编程中,用到反射的地方还是很多的,比如java框架就大量使用反射,还有在加载驱动和读取配置文件时,反射也是比不可少的。

借鉴自 https://blog.csdn.net/m0_66557301/article/details/123838148

第六章 接口、lamdba表达式与内部类

接口

面试题:为什么使用接口而不直接使用抽象类?

  • JAVA中类只支持单继承,使用接口可以实现多个接口,从而弥补类的单继承的缺陷
  • 接口更多的是在系统框架设计方法发挥作用,主要定义模块之间的通信(特别是springboot中调用数据库的接口方法),而抽象类在代码实现方面发挥作用,可以实现代码的重用。

面试题:抽象类和接口的区别?

含有abstract修饰符的class 即为抽象类。abstract类不能创建实例对象;含有abstract的方法的类必须定义为abstract class ;abstract class 里的方法不必是抽象的;抽象类中定义抽象方法必须放在具体子类中实现;所以呀,不能有抽象的构造方法或抽象的静态方法,如果子类没有实现抽象父类中的所有 方法,那么,子类也必须定义为抽象类。

接口(interface)可以说成是抽象类的特例。接口中的所有方法都必须是抽象的,接口中的方法定义默认为public abstract 。接口中的变量是全局常量,即public static final修饰的。

看一下他们在语法上的区别吧!

1、抽象类中可以包含非抽象的普通方法,而接口中所有的方法必须是抽象的,不能有非抽象的普通方法(新版本可以使用default关键词实现接口的方法)

2、一个类可以实现多个接口,但只能继承一个抽象类。

3、抽象类里可以有构造方法,而接口内不能有构造方法。

4、抽象类中的抽象方法的访问类型可以是public ,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

5、抽象类中可以包含静态方法,接口内不能包含静态方法。

6、抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认为public static final类型。

7、抽象类中可以有普通成员变量,而接口中不能有普通成员变量。

再补充点两者在应用上的区别:

接口更多的是在系统框架设计方法发挥作用,主要定义模块之间的通信(特别是springboot中调用数据库的接口方法),而抽象类在代码实现方面发挥作用,可以实现代码的重用。
借鉴自 https://blog.csdn.net/qq_21870555/article/details/82986651

lambda表达式与内部类

需求:三种方式实现启动一个线程,在控制台输出一句话:多线程程序启动了

  1. 方式1:定义一个类MyRunnable实现Runnable接口,重写run()方法

//创建MyRunnable类的对象

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("多线程程序启动了...");
    }
}

public class LambdaDemo {
    public static void main(String[] args) {
        //实现类的方式实现需求
        MyRunnable my = new MyRunnable();
        //创建Thread类的对象,把MyRunnable的对象作为构造参数传递
        Thread t = new Thread(my);
        //启动线程
        t.start();
    }
}
  1. 方式2:匿名内部类的方式改进
public class LambdaDemo {
    public static void main(String[] args) {
        //匿名内部类的方式改进
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多线程程序启动了...");
            }
        }).start();
    }
}
  • 匿名内部类中重写run()方法的代码分析
    ①方法形式参数为空,说明调用方法时不需要传递参数
    ②方法返回值类型为void,说明方法执行没有结果返回
    ③方法体中的内容,是我们具体要做的事情
  1. 方式3:Lambda表达式的方式改进
public class LambdaDemo {
    public static void main(String[] args) {
        //Lambda表达式改进
        new Thread(() -> {
            System.out.println("多线程程序启动了...");
        }).start();
    }
}
  • Lambda表达式的代码分析
    ①():里面没有内容,可以看成是方法形式参数为空
    ②->:用箭头指向后面要做的事情
    ③{ }:包含一段代码,我们称之为代码块,可以看成是方法体中的内容

  • 三种方式的优缺点

    • 当我们使用实现类的方式实现需求的时候,我们可以看到我们需要先创建一个实体类,然后将实现类的对象传进去才可以使用。
    • 当我们使用你们匿名内部类的方法实现需求时,我们可以发现需要重写run方法(当我们调用其他方法,忘记去重写什么方法时会比较懵逼),相比较也是比较麻烦的。
    • 当我们使用Lambda表达式来实现需求时,我们可以看到我们不用关心创建了什么实体类、重写了什么方法。我们只需要关心它最终要做的事情是System.out.println(“多线程程序启动了…”);
  • Lambda表达式的标准格式

    • 组成Lambda表达式的三要素:形式参数,箭头,代码块
    • Lambda表达式的格式
      ①格式:(形式参数) -> {代码块}
      ②形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
      ③->:由英文中画线和大于符号组成,固定写法。代表指向动作
      ④代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

借鉴自 https://blog.csdn.net/qq_44096670/article/details/116495047

代理

三种代理方式:静态代理、动态代理和Cglib代理(针对于springBoot的应用)

参考博客:https://blog.csdn.net/u013521220/article/details/107308956


版权声明:本文为immortalize原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。