这套Java面试题应对金九银十面试,应该够了

Android&Java面试题大全—金九银十面试必备【上】 ①通过 hash 方法获取 hash 值,根据 hash 值寻址②如果未发生碰撞,直接放到桶中。③如果发生碰撞,则以链表形式放在桶后④当链表长度大于阈值后会触发树化,将链表转换为红黑树。⑤如果数组长度达到阈值,会调用 resize 方法扩展容量。 阅读详情

Java 基础篇

Java 有哪些特点

  • 并发性的:你可以在其中执行许多语句,而不必一次执行它
  • 面向对象的:基于类和面向对象的编程语言。
  • 独立性的:支持一次编写,到处运行的独立编程语言,即编译后的代码可以在支持 Java 的所有平台上运行。

     

Java 的特性

Java 的特性有如下这几点

  • 简单,Java 会让你的工作变得更加轻松,使你把关注点放在主要业务逻辑上,而不必关心指针、运算符重载、内存回收等与主要业务无关的功能。
  • 便携性,Java 是平台无关性的,这意味着在一个平台上编写的任何应用程序都可以轻松移植到另一个平台上。
  • 安全性, 编译后会将所有的代码转换为字节码,人类无法读取。它使开发无病毒,无篡改的系统/应用成为可能。
  • 动态性,它具有适应不断变化的环境的能力,它能够支持动态内存分配,从而减少了内存浪费,提高了应用程序的性能。
  • 分布式,Java 提供的功能有助于创建分布式应用。使用远程方法调用(RMI),程序可以通过网络调用另一个程序的方法并获取输出。您可以通过从互联网上的任何计算机上调用方法来访问文件。这是革命性的一个特点,对于当今的互联网来说太重要了。
  • 健壮性,Java 有强大的内存管理功能,在编译和运行时检查代码,它有助于消除错误。
  • 高性能,Java 最黑的科技就是字节码编程,Java 代码编译成的字节码可以轻松转换为本地机器代码。通过 JIT 即时编译器来实现高性能。
  • 解释性,Java 被编译成字节码,由 Java 运行时环境解释。
  • 多线程性,Java支持多个执行线程(也称为轻量级进程),包括一组同步原语。这使得使用线程编程更加容易,Java 通过管程模型来实现线程安全性。

描述一下值传递和引用传递的区别

简单理解的话就是

值传递是指在调用函数时将实际参数复制一份到函数中,这样的话如果函数对其传递过来的形式参数进行修改,将不会影响到实际参数

引用传递 是指在调用函数时将对象的地址直接传递到函数中,如果在对形式参数进行修改,将影响到实际参数的值。

== 和 equals 区别是什么

== 是 Java 中一种操作符,它有两种比较方式

  • 对于基本数据类型来说, == 判断的是两边的值是否相等
public class DoubleCompareAndEquals {

    Person person1 = new Person(24,"boy");
    Person person2 = new Person(24,"girl");
    int c = 10;

    private void doubleCompare(){

        int a = 10;
        int b = 10;

        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(person1.getId() == person2.getId());

    }
}

  • 对于引用类型来说, == 判断的是两边的引用是否相等,也就是判断两个对象是否指向了同一块内存区域。
private void equals(){

  System.out.println(person1.getName().equals(person2.getName()));
}

equals 是 Java 中所有对象的父类,即 Object 类定义的一个方法。它只能比较对象,它表示的是引用双方的值是否相等。所以记住,并不是说 == 比较的就是引用是否相等,equals 比较的就是值,这需要区分来说的。

equals 用作对象之间的比较具有如下特性

  • 自反性:对于任何非空引用 x 来说,x.equals(x) 应该返回 true。
  • 对称性:对于任何非空引用 x 和 y 来说,若x.equals(y)为 true,则y.equals(x)也为 true。
  • 传递性:对于任何非空引用的值来说,有三个值,x、y 和 z,如果x.equals(y) 返回true,y.equals(z) 返回true,那么x.equals(z) 也应该返回true。
  • 一致性:对于任何非空引用 x 和 y 来说,如果 x.equals(y) 相等的话,那么它们必须始终相等。
  • 非空性:对于任何非空引用的值 x 来说,x.equals(null) 必须返回 false。

String 中的 equals 是如何重写的

String 代表的是 Java 中的字符串,String 类比较特殊,它整个类都是被 final 修饰的,也就是说,String 不能被任何类继承,任何 修改 String 字符串的方法都是创建了一个新的字符串。

equals 方法是 Object 类定义的方法,Object 是所有类的父类,当然也包括 String,String 重写了 equals 方法,下面我们来看看是怎么重写的

  • 首先会判断要比较的两个字符串它们的引用是否相等。如果引用相等的话,直接返回 true ,不相等的话继续下面的判断
  • 然后再判断被比较的对象是否是 String 的实例,如果不是的话直接返回 false,如果是的话,再比较两个字符串的长度是否相等,如果长度不想等的话也就没有比较的必要了;长度如果相同,会比较字符串中的每个 字符 是否相等,一旦有一个字符不相等,就会直接返回 false。

下面是它的流程图

这里再提示一下,你可能有疑惑什么时候是

if (this == anObject) {
  return true;
}

这个判断语句如何才能返回 true?因为都是字符串啊,字符串比较的不都是堆空间吗,猛然一看发现好像永远也不会走,但是你忘记了 String.intern() 方法,它表示的概念在不同的 JDK 版本有不同的区分

在 JDK1.7 及以后调用 intern 方法是判断运行时常量池中是否有指定的字符串,如果没有的话,就把字符串添加到常量池中,并返回常量池中的对象。

验证过程如下

private void StringOverrideEquals(){

  String s1 = "aaa";
  String s2 = "aa" + new String("a");
  String s3 = new String("aaa");

  System.out.println(s1.intern().equals(s1));
  System.out.println(s1.intern().equals(s2));
  System.out.println(s3.intern().equals(s1));

}

  • 首先 s1.intern.equals(s1) 这个无论如何都返回 true,因为 s1 字符串创建出来就已经在常量池中存在了。
  • 然后第二条语句返回 false,因为 s1 返回的是常量池中的对象,而 s2 返回的是堆中的对象
  • 第三条语句 s3.intern.equals(s1),返回 true ,因为 s3 对象虽然在堆中创建了一个对象,但是 s3 中的 "aaa" 返回的是常量池中的对象。

为什么重写 equals 方法必须重写 hashcode 方法

equals 方法和 hashCode 都是 Object 中定义的方法,它们经常被一起重写。

equals 方法是用来比较对象大小是否相等的方法,hashcode 方法是用来判断每个对象 hash 值的一种方法。如果只重写 equals 方法而不重写 hashcode 方法,很可能会造成两个不同的对象,它们的 hashcode 也相等,造成冲突。比如

String str1 = "通话";
String str2 = "重地";

它们两个的 hashcode 相等,但是 equals 可不相等。

我们来看一下 hashCode 官方的定义

总结起来就是

  • 如果在 Java 运行期间对同一个对象调用 hashCode 方法后,无论调用多少次,都应该返回相同的 hashCode,但是在不同的 Java 程序中,执行 hashCode 方法返回的值可能不一致。
  • 如果两个对象的 equals 相等,那么 hashCode 必须相同
  • 如果两个对象 equals 不相等,那么 hashCode 也有可能相同,所以需要重写 hashCode 方法,因为你不知道 hashCode 的底层构造(反正我是不知道,有大牛可以传授传授),所以你需要重写 hashCode 方法,来为不同的对象生成不同的 hashCode 值,这样能够提高不同对象的访问速度。
  • hashCode 通常是将地址转换为整数来实现的。

String s1 = new String("abc") 在内存中创建了几个对象

一个或者两个,String s1 是声明了一个 String 类型的 s1 变量,它不是对象。使用 new 关键字会在堆中创建一个对象,另外一个对象是 abc ,它会在常量池中创建,所以一共创建了两个对象;如果 abc 在常量池中已经存在的话,那么就会创建一个对象。

String 为什么是不可变的、jdk 源码中的 String 如何定义的、为什么这么设计。

首先了解一下什么是不可变对象,不可变对象就是一经创建后,其对象的内部状态不能被修改,啥意思呢?也就是说不可变对象需要遵守下面几条原则

  • 不可变对象的内部属性都是 final 的
  • 不可变对象的内部属性都是 private 的
  • 不可变对象不能提供任何可以修改内部状态的方法、setter 方法也不行
  • 不可变对象不能被继承和扩展

与其说问 String 为什么是不可变的,不如说如何把 String 设计成不可变的。

String 类是一种对象,它是独立于 Java 基本数据类型而存在的,String 你可以把它理解为字符串的集合,String 被设计为 final 的,表示 String 对象一经创建后,它的值就不能再被修改,任何对 String 值进行修改的方法就是重新创建一个字符串。String 对象创建后会存在于运行时常量池中,运行时常量池是属于方法区的一部分,JDK1.7 后把它移到了堆中。

不可变对象不是真的不可变,可以通过反射来对其内部的属性和值进行修改,不过一般我们不这样做。

static 关键字是干什么用的?谈谈你的理解

static 是 Java 中非常重要的关键字,static 表示的概念是 静态的,在 Java 中,static 主要用来

  • 修饰变量,static 修饰的变量称为静态变量、也称为类变量,类变量属于类所有,对于不同的类来说,static 变量只有一份,static 修饰的变量位于方法区中;static 修饰的变量能够直接通过 类名.变量名 来进行访问,不用通过实例化类再进行使用。
  • 修饰方法,static 修饰的方法被称为静态方法,静态方法能够直接通过 类名.方法名来使用,在静态方法内部不能使用非静态属性和方法
  • static 可以修饰代码块,主要分为两种,一种直接定义在类中,使用 static{},这种被称为静态代码块,一种是在类中定义静态内部类,使用 static class xxx 来进行定义。
  • static 可以用于静态导包,通过使用 import static xxx 来实现,这种方式一般不推荐使用
  • static 可以和单例模式一起使用,通过双重检查锁来实现线程安全的单例模式。

final 关键字是干什么用的?谈谈你的理解

final 是 Java 中的关键字,它表示的意思是 不可变的,在 Java 中,final 主要用来

  • 修饰类,final 修饰的类不能被继承,不能被继承的意思就是不能使用 extends 来继承被 final 修饰的类。
  • 修饰变量,final 修饰的变量不能被改写,不能被改写的意思有两种,对于基本数据类型来说,final 修饰的变量,其值不能被改变,final 修饰的对象,对象的引用不能被改变,但是对象内部的属性可以被修改。final 修饰的变量在某种程度上起到了不可变的效果,所以,可以用来保护只读数据,尤其是在并发编程中,因为明确的不能再为 final 变量进行赋值,有利于减少额外的同步开销。
  • 修饰方法,final 修饰的方法不能被重写。
  • final 修饰符和 Java 程序性能优化没有必然联系

抽象类和接口的区别是什么

抽象类和接口都是 Java 中的关键字,抽象类和接口中都允许进行方法的定义,而不用具体的方法实现。抽象类和接口都允许被继承,它们广泛的应用于 JDK 和框架的源码中,来实现多态和不同的设计模式。

不同点在于

  • 抽象级别不同:类、抽象类、接口其实是三种不同的抽象级别,抽象程度依次是 接口 > 抽象类 > 类。在接口中,只允许进行方法的定义,不允许有方法的实现,抽象类中可以进行方法的定义和实现;而类中只允许进行方法的实现,我说的方法的定义是不允许在方法后面出现 {}
  • 使用的关键字不同:类使用 class 来表示;抽象类使用 abstract class 来表示;接口使用 interface 来表示
  • 变量:接口中定义的变量只能是公共的静态常量,抽象类中的变量是普通变量。

重写和重载的区别

在 Java 中,重写和重载都是对同一方法的不同表现形式,下面我们针对重写和重载做一下简单的区分

  • 子父级关系不同,重写是针对子级和父级的不同表现形式,而重载是在同一类中的不同表现形式;
  • 概念不同,子类重写父类的方法一般使用 @override 来表示;重写后的方法其方法的声明和参数类型、顺序必须要与父类完全一致;重载是针对同一类中概念,它要求重载的方法必须满足下面任何一个要求:方法参数的顺序,参数的个数,参数的类型任意一个保持不同即可。

byte的取值范围是多少,怎么计算出来的

byte 的取值范围是 -128 -> 127 之间,一共是 256 。一个 byte 类型在计算机中占据一个字节,那么就是 8 bit,所以最大就是 2^7 = 1111 1111。

Java 中用补码来表示二进制数,补码的最高位是符号位,最高位用 0 表示正数,最高位 1 表示负数,正数的补码就是其本身,由于最高位是符号位,所以正数表示的就是 0111 1111 ,也就是 127。最大负数就是 1111 1111,这其中会涉及到两个 0 ,一个 +0 ,一个 -0 ,+0 归为正数,也就是 0 ,-0 归为负数,也就是 -128,所以 byte 的范围就是 -128 - 127。

HashMap 和 HashTable 的区别

相同点

HashMap 和 HashTable 都是基于哈希表实现的,其内部每个元素都是 key-value 键值对,HashMap 和 HashTable 都实现了 Map、Cloneable、Serializable 接口。

不同点

  • 父类不同:HashMap 继承了 AbstractMap 类,而 HashTable 继承了 Dictionary 类

     

  • 空值不同:HashMap 允许空的 key 和 value 值,HashTable 不允许空的 key 和 value 值。HashMap 会把 Null key 当做普通的 key 对待。不允许 null key 重复。

     

  • 线程安全性:HashMap 不是线程安全的,如果多个外部操作同时修改 HashMap 的数据结构比如 add 或者是 delete,必须进行同步操作,仅仅对 key 或者 value 的修改不是改变数据结构的操作。可以选择构造线程安全的 Map 比如 Collections.synchronizedMap 或者是 ConcurrentHashMap。而 HashTable 本身就是线程安全的容器。

  • 性能方面:虽然 HashMap 和 HashTable 都是基于单链表的,但是 HashMap 进行 put 或者 get 操作,可以达到常数时间的性能;而 HashTable 的 put 和 get 操作都是加了 synchronized 锁的,所以效率很差。

     

  • 初始容量不同:HashTable 的初始长度是11,之后每次扩充容量变为之前的 2n+1(n为上一次的长度)而 HashMap 的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。

HashMap 和 HashSet 的区别

HashSet 继承于 AbstractSet 接口,实现了 Set、Cloneable,、java.io.Serializable 接口。HashSet 不允许集合中出现重复的值。HashSet 底层其实就是 HashMap,所有对 HashSet 的操作其实就是对 HashMap 的操作。所以 HashSet 也不保证集合的顺序,也不是线程安全的容器。

HashMap 的底层结构

JDK1.7 中,HashMap 采用位桶 + 链表的实现,即使用链表来处理冲突,同一 hash 值的链表都存储在一个数组中。但是当位于一个桶中的元素较多,即 hash 值相等的元素较多时,通过 key 值依次查找的效率较低。

所以,与 JDK 1.7 相比,JDK 1.8 在底层结构方面做了一些改变,当每个桶中元素大于 8 的时候,会转变为红黑树,目的就是优化查询效率。

HashMap 的长度为什么是 2 的幂次方

这道题我想了几天,之前和群里小伙伴们探讨每日一题的时候,问他们为什么 length%hash == (n - 1) & hash,它们说相等的前提是 length 的长度 2 的幂次方,然后我回了一句难道 length 还能不是 2 的幂次方吗?其实是我没有搞懂因果关系,因为 HashMap 的长度是 2 的幂次方,所以使用余数来判断在桶中的下标。如果 length 的长度不是 2 的幂次方,小伙伴们可以举个例子来试试

例如长度为 9 时候,3 & (9-1) = 0,2 & (9-1) = 0 ,都在 0 上,碰撞了;

这样会增大 HashMap 碰撞的几率。

HashMap 多线程操作导致死循环问题

HashMap 不是一个线程安全的容器,在高并发场景下,应该使用 ConcurrentHashMap,在多线程场景下使用 HashMap 会造成死循环问题(基于 JDK1.7),出现问题的位置在 rehash 处,也就是

do {
    Entry<K,V> next = e.next; // <--假设线程一执行到这里就被调度挂起了
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);

这是 JDK1.7 的 rehash 代码片段,在并发的场景下会形成环。

JDK1.8 也会造成死循环问题。

HashMap 线程安全的实现有哪些

因为 HashMap 不是一个线程安全的容器,所以并发场景下推荐使用 ConcurrentHashMap,或者使用线程安全的 HashMap,使用 Collections 包下的线程安全的容器,比如说

Collections.synchronizedMap(new HashMap());

还可以使用 HashTable ,它也是线程安全的容器,基于 key-value 存储,经常用 HashMap 和 HashTable 做比较就是因为 HashTable 的数据结构和 HashMap 相同。

上面效率最高的就是 ConcurrentHashMap。

讲一下 HashMap put 的过程

首先会使用 hash 函数来计算 key,然后执行真正的插入方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
  Node<K,V>[] tab; Node<K,V> p; int n, i;
  // 如果table 为null 或者没有为table分配内存,就resize一次
  if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
  // 指定hash值节点为空则直接插入,这个(n - 1) & hash才是表中真正的哈希
  if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
  // 如果不为空
  else {
    Node<K,V> e; K k;
    // 计算表中的这个真正的哈希值与要插入的key.hash相比
    if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
      e = p;
    // 若不同的话,并且当前节点已经在 TreeNode 上了
    else if (p instanceof TreeNode)
      // 采用红黑树存储方式
      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    // key.hash 不同并且也不再 TreeNode 上,在链表上找到 p.next==null
    else {
      for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
          // 在表尾插入
          p.next = newNode(hash, key, value, null);
          // 新增节点后如果节点个数到达阈值,则进入 treeifyBin() 进行再次判断
          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
            treeifyBin(tab, hash);
          break;
        }
        // 如果找到了同hash、key的节点,那么直接退出循环
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
          break;
        // 更新 p 指向下一节点
        p = e;
      }
    }
    // map中含有旧值,返回旧值
    if (e != null) { // existing mapping for key
      V oldValue = e.value;
      if (!onlyIfAbsent || oldValue == null)
        e.value = value;
      afterNodeAccess(e);
      return oldValue;
    }
  }
  // map调整次数 + 1
  ++modCount;
  // 键值对的数量达到阈值,需要扩容
  if (++size > threshold)
    resize();
  afterNodeInsertion(evict);
  return null;
}

HashMap put 方法的核心就是在 putval 方法,它的插入过程如下

  • 首先会判断 HashMap 中是否是新构建的,如果是的话会首先进行 resize
  • 然后判断需要插入的元素在 HashMap 中是否已经存在(说明出现了碰撞情况),如果不存在,直接生成新的k-v 节点存放,再判断是否需要扩容。
  • 如果要插入的元素已经存在的话,说明发生了冲突,这就会转换成链表或者红黑树来解决冲突,首先判断链表中的 hash,key 是否相等,如果相等的话,就用新值替换旧值,如果节点是属于 TreeNode 类型,会直接在红黑树中进行处理,如果 hash ,key 不相等也不属于 TreeNode 类型,会直接转换为链表处理,进行链表遍历,如果链表的 next 节点是 null,判断是否转换为红黑树,如果不转换的话,在遍历过程中找到 key 完全相等的节点,则用新节点替换老节点

ConcurrentHashMap 底层实现

ConcurrentHashMap 是线程安全的 Map,它也是高并发场景下的首选数据结构,ConcurrentHashMap 底层是使用分段锁来实现的。

Integer 缓存池

Integer 缓存池也就是 IntegerCache ,它是 Integer 的静态内部类。

它的默认值用于缓存 -128 - 127 之间的数字,如果有 -128 - 127 之间的数字的话,使用 new Integer 不用创建对象,会直接从缓存池中取,此操作会减少堆中对象的分配,有利于提高程序的运行效率。

Object 类中一般都有哪些方法

Object 类是所有对象的父类,它里面包含一些所有对象都能够使用的方法

  • hashCode():用于计算对象的哈希码
  • equals():用于对象之间比较值是否相等
  • toString(): 用于把对象转换成为字符串
  • clone(): 用于对象之间的拷贝
  • wait(): 用于实现对象之间的等待
  • notify(): 用于通知对象释放资源
  • notifyAll(): 用于通知所有对象释放资源
  • finalize(): 用于告知垃圾回收器进行垃圾回收
  • getClass(): 用于获得对象类

反射的基本原理,反射创建类实例的三种方式是什么

反射机制就是使 Java 程序在运行时具有自省(introspect) 的能力,通过反射我们可以直接操作类和对象,比如获取某个类的定义,获取类的属性和方法,构造方法等。

创建类实例的三种方式是

  • 对象实例.getClass();
  • 通过 Class.forName() 创建
  • 对象实例.newInstance() 方法创建

final、finally 和 finalize() 的区别

这三者可以说是没有任何关联之处,我们上面谈到了,final 可以用来修饰类、变量和方法,可以参考上面 final 的那道面试题。

finally 是一个关键字,它经常和 try 块一起使用,用于异常处理。使用 try...finally 的代码块种,finally 部分的代码一定会被执行,所以我们经常在 finally 方法中用于资源的关闭操作。

JDK1.7 中,推荐使用 try-with-resources 优雅的关闭资源,它直接使用 try(){} 进行资源的关闭即可,就不用写 finally 关键字了。

finalize 是 Object 对象中的一个方法,用于对象的回收方法,这个方法我们一般不推荐使用,finalize 是和垃圾回收关联在一起的,在 Java 9 中,将 finalize 标记为了 deprecated, 如果没有特别原因,不要实现 finalize 方法,也不要指望他来进行垃圾回收。

内部类有哪些分类,分别解释一下

在 Java 中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

内部类的分类一般主要有四种

  • 成员内部类
  • 局部内部类
  • 匿名内部类
  • 静态内部类

静态内部类就是定义在类内部的静态类,静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;

成员内部类 就是定义在类内部,成员位置上的非静态类,就是成员内部类。成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。

定义在方法中的内部类,就是局部内部类。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。

匿名内部类 就是没有名字的内部类,除了没有名字,匿名内部类还有以下特点:

  • 匿名内部类必须继承一个抽象类或者实现一个接口
  • 匿名内部类不能定义任何静态成员和静态方法。
  • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
  • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

说出几种常用的异常

  • NullPointerException: 空指针异常
  • NoSuchMethodException:找不到方法
  • IllegalArgumentException:不合法的参数异常
  • IndexOutOfBoundException: 数组下标越界异常
  • IOException:由于文件未找到、未打开或者I/O操作不能进行而引起异常
  • ClassNotFoundException :找不到文件所抛出的异常
  • NumberFormatException:字符的UTF代码数据格式有错引起异常;
  • InterruptedException:线程中断抛出的异常

静态绑定和动态绑定的区别

一个Java 程序要经过编写、编译、运行三个步骤,其中编写代码不在我们讨论的范围之内,那么我们的重点自然就放在了编译 和 运行这两个阶段,由于编译和运行阶段过程相当繁琐,下面就我的理解来进行解释:

Java 程序从源文件创建到程序运行要经过两大步骤:

1、编译时期是由编译器将源文件编译成字节码的过程

2、字节码文件由Java虚拟机解释执行

绑定

绑定就是一个方法的调用与调用这个方法的类连接在一起的过程被称为绑定。

绑定主要分为两种:

静态绑定 和 动态绑定

绑定的其他叫法

静态绑定 == 前期绑定 == 编译时绑定

动态绑定 == 后期绑定 == 运行时绑定

为了方便区分:下面统一称呼为静态绑定和动态绑定

静态绑定

在程序运行前,也就是编译时期 JVM 就能够确定方法由谁调用,这种机制称为静态绑定

识别静态绑定的三个关键字以及各自的理解

如果一个方法由 private、static、final 任意一个关键字所修饰,那么这个方法是前期绑定的

构造方法也是前期绑定

private:private 关键字是私有的意思,如果被 private 修饰的方法是无法由本类之外的其他类所调用的,也就是本类所特有的方法,所以也就由编译器识别此方法是属于哪个类的

public class Person {

    private String talk;

    private String canTalk(){
        return talk;
    }
}

class Animal{

    public static void main(String[] args) {
        Person p = new Person();
        // private 修饰的方法是Person类独有的,所以Animal类无法访问(动物本来就不能说话)
//        p.canTalk();
    }
}

final:final 修饰的方法不能被重写,但是可以由子类进行调用,如果将方法声明为 final 可以有效的关闭动态绑定

public class Fruit {

    private String fruitName;

    final String eatingFruit(String name){
        System.out.println("eating " + name);
        return fruitName;
    }
}

class Apple extends Fruit{

      // 不能重写final方法,eatingFruit方法只属于Fruit类,Apple类无法调用
//    String eatingFruit(String name){
//        super.eatingFruit(name);
//    }

    String eatingApple(String name){
        return super.eatingFruit(name);
    }
}

static:static 修饰的方法比较特殊,不用通过 new 出某个类来调用,由类名.变量名直接调用该方法,这个就很关键了,new 很关键,也可以认为是开启多态的导火索,而由类名.变量名直接调用的话,此时的类名是确定的,并不会产生多态,如下代码:

public class SuperClass {

    public static void sayHello(){

        System.out.println("由 superClass 说你好");
    }
}

public class SubClass extends SuperClass{

    public static void sayHello(){
        System.out.println("由 SubClass 说你好");
    }

    public static void main(String[] args) {
        SuperClass.sayHello();
        SubClass.sayHello();
    }
}

SubClass 继承 SuperClass 后,在

是无法重写 sayHello 方法的,也就是说 sayHello() 方法是对子类隐藏的,但是你可以编写自己的 sayHello() 方法,也就是子类 SubClass 的sayHello() 方法,由此可见,方法由 static 关键词所修饰,也是编译时绑定

动态绑定

在运行时根据具体对象的类型进行绑定

除了由 private、final、static 所修饰的方法和构造方法外,JVM 在运行期间决定方法由哪个对象调用的过程称为动态绑定

如果把编译、运行看成一条时间线的话,在运行前必须要进行程序的编译过程,那么在编译期进行的绑定是前期绑定,在程序运行了,发生的绑定就是后期绑定

public class Father {

    void drinkMilk(){
        System.out.println("父亲喜欢喝牛奶");
    }
}

public class Son extends Father{

    @Override
    void drinkMilk() {
        System.out.println("儿子喜欢喝牛奶");
    }

    public static void main(String[] args) {
        Father son = new Son();
        son.drinkMilk();
    }
}

Son 类继承 Father 类,并重写了父类的 dringMilk() 方法,在输出结果得出的是儿子喜欢喝牛奶。那么上面的绑定方式是什么呢?

上面的绑定方式称之为动态绑定,因为在你编写 Father son = new Son() 的时候,编译器并不知道 son 对象真正引用的是谁,在程序运行时期才知道,这个 son 是一个 Father 类的对象,但是却指向了 Son 的引用,这种概念称之为多态,那么我们就能够整理出来多态的三个原则:

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

也就是说,在 Father son = new Son() ,触发了动态绑定机制。

动态绑定的过程

  1. 虚拟机提取对象的实际类型的方法表;
  2. 虚拟机搜索方法签名;
  3. 调用方法。

动态绑定和静态绑定的特点

静态绑定

静态绑定在编译时期触发,那么它的主要特点是

1、编译期触发,能够提早知道代码错误

2、提高程序运行效率

动态绑定

1、使用动态绑定的前提条件能够提高代码的可用性,使代码更加灵活。

2、多态是设计模式的基础,能够降低耦合性。
Java八股文面试题

避坑指南:群晖DSM 7.X挂载NTFS硬盘,为什么你的SATA口不行?从原理到实战一次讲清 本文深入解析群晖DSM 7.X系统挂载NTFS硬盘的底层原理与实战排查方法,揭示SATA接口权限配置的关键参数(如esataportcfg和internalportcfg),并提供安全修改端口配置的详细步骤。针对不同型号群晖NAS的配置差异,给出替代方案与常见故障排查技巧,帮助用户解决NTFS硬盘挂载问题。 阅读详情

相关推荐

STM32F4用FSMC驱动AX58100 EtherCAT从站芯片,保姆级配置流程与避坑指南

本文详细介绍了STM32F4通过FSMC接口驱动AX58100 EtherCAT从站芯片的完整配置流程,包括硬件连接、时序优化、协议栈移植及TwinCAT主站集成。针对工业自动化场景,提供高性价比的实时控制解决方案,特别适合机械臂、CNC机床等精确同步应用,并分享关键调试技巧与性能优化方法。

weixin_27869497的博客 306

面试稿·Java

潜伏·潜规则一级目录二级目录三级目录 一级目录 二级目录 三级目录

正趣果上果 1114

Buck-Boost电路

前面我们学习了降压的Buck电路 (左图) 和升压的Boost电路 (右图),那么如果我们把Buck电路和Boost电路结合一下会发生什么情况? 我们尝试着画一下: 我们用Buck电路作为输入,然后用Boost电路进行调整,最后输出。因此Buck电路中的负载、Boost电路的电源都可以删掉。得到下图: 由于Buck电路中的C2的作用是在输出的时候滤波,这个功能已经由Boost电路中的C1实现了,因此可以删除Buck电路中的C2电容。得到下图: 这里两个电感处于串联状态,可以合二为一,得到下面的电路:

wangeil007的博客 4万+

2w字长文!手撸一套 Java 基础面试题

Java 基础篇 Java 有哪些特点 并发性的: 你可以在其中执行许多语句,而不必一次执行它 面向对象的:基于类和面向对象的编程语言。 独立性的: 支持一次编写,到处运行的独立编程语言,即编译后的代码可以在支持 Java 的所有平台上运行。 Java 的特性 Java 的特性有如下这几点 简单,Java 会让你的工作变得更加轻松,使你把关注点放在主要业务逻辑上,而不必关心指针、运算符重载、内存回收等与主要业务无关的功能。 便携性,Java 是平台无关性的,这意味着在一个平台上编写的任何应用程

程序员cxuan的个人主页 3589

Unity2020 发布Android包,下载安装配置JDK、SDK、NDK详细教程

配置成功页面 JDK、SDK、NDK

qq_41263535的博客 1万+

路径规划:D*Lite寻路算法详解

D*Lite详解目录

mkr67n的博客 2万+

java开发面试 自我介绍!!!!!

面试官,你好!我叫(ZYLDLP),今天来公司面试JAVA开发工程师,之前在(反正钱没你这里多公司)任职,从事这一行已经有(几)个年头了。这几年开发,主要涉及的行业项目包括(你干过的项目!)等。在开发过程中,也用过好些框架,比如dubbo+zookeeper、springboot、springmvc、spring、Mybatis等框架,熟练掌握框架之间的整合技术。有时候因为项目需求或是为了开发的高效性,自己也会研究一些技术,使用一些常用的主流java技术,例如(吹!用没用过不重要,主要是就是英文的。...

iijik55的博客 1624

创意视觉项目实战:用ESP32-S3+OV5640构建智能图像流系统

本文详细介绍了如何使用ESP32-S3开发板与OV5640摄像头模块构建智能图像流系统。从硬件连接到软件配置,涵盖Arduino环境搭建、图像采集优化、WiFi视频流传输及外设集成,为开发者提供完整的实战指南,助力智能家居和机器人视觉项目开发。

tech5的博客 1292

Java面试突击指南+面试题库+模拟面试!

Spring AOP 通过动态代理实现,有两种方式:JDK 动态代理(针对实现接口的类,通过反射生成代理类)和 CGLIB 动态代理(针对没有实现接口的类,通过继承生成子类作为代理)。:您好,我有 3 年 Java 开发经验,一直从事电商系统开发,参与过订单中心、秒杀系统的设计与实现,熟悉 Spring 全家桶、JVM 调优、Redis 缓存和分布式架构,希望能在贵司继续深耕 Java 技术。:根据我的经验和市场行情,期望薪资在 35K-40K 之间,当然也可以结合贵司的薪资体系来谈。

IT_1024的博客 450

【网络图】:附Origin详细画图教程

网络图,是一种由节点(或顶点)和连接这些节点的边(或链路)组成的图形结构。在网络图中,节点通常代表系统中的一个实体(如项目中的一项任务、通信网络中的一个设备、或运输网络中的一个站点),而边则代表这些实体之间的直接联系或流动(如任务的先后顺序、数据的传输路径、或乘客的旅行路线)。 有向图:如果边具有方向性,即从一个节点指向另一个节点,则称为有向图。这种图常用于表示具有明确先后顺序或方向性的系统,如项目管理中的任务依赖关系。 无向图:如果图中的边没有方向,则称为无向图。它适用于表示实体间相互关联但无明确方向性的

weixin_50236590的博客 3955

【RT-DETR涨点改进】独家小目标涨点改进篇 | TGRS 2025 | RT-DETR引入ABRNet小目标检测顶刊中的:ARF自适应性感受野和MCA双向交叉轴注意力,适合小目标检测有效涨点

本文提出了一种改进的RT-DETR小目标检测方法,通过集成自适应感受野模块(ARF)和多尺度交叉轴注意力模块(BC)来提升检测性能。ARF模块采用多种卷积核动态调整感受野,有效捕获多尺度目标特征并减少背景干扰;BC模块通过多尺度交叉轴注意力机制增强全局信息提取和特征融合能力。实验表明,该方法在复杂背景下显著提升了红外小目标检测的精度和效率,同时降低了计算开销。文章详细介绍了模块结构、原理及实现代码,并提供了多种改进配置方案,为小目标检测任务提供了有效的解决方案。

Ai缝合怪、助力小伙伴高效发论文 570

java面试(总结篇)

java面试

admin0811的博客 2304

为JETSON TX2开发板部署YoloV5

参考资料一:nvidia为JETSON Tx2编译的pytorch 参考资料二:Jetson软件包下载 参考资料三:Tx2安装工具的使用 参考资料四: Jetson烧录工具 参考资料五: B站下载的行人视频 一、升级TX2系统版本到最新版(JetPack4.6) 我拿到的板子的版本太低了,在工作的过程中会有很多问题,因此要先升级到最新版 下载参考资料四中的烧录工具,在Ubuntu18.04中安装,只能,必须,一定是Ubuntu18.04 安装完成后使用非root用户启动SDK Manger,登录Nvi.

贱贱的剑 5887

Java经典面试题详解,全是干货突围金九银十面试季(附详细答案)

前言: 想在面试、工作中脱颖而出?想在最短的时间内快速掌握 Java 的核心基础知识点?想要成为一位优秀的 Java 工程师?本篇文章能助你一臂之力! 金九银十,目前正值招聘求职旺季,很多同学对一些新技术名词都能侃侃而谈,但对一些核心原理理解的不够透彻,特别是对 Java的一些核心基础知识点掌握的不够,例如JVM、常用的算法和数据结构等。正所谓万丈高楼平地起,只有把基础掌握的牢固,才能走的更远,面对不断更新的技术才能快速掌握,同时在面试、工作中也更能脱颖而出! 下方面试题我只挑选了几个比较好的,剩余的

banzhuanhu的博客 2万+

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

11 场面试无一被拒 Alibaba 金九银十面试参考指南(Java 版)真香

为了不影响大家的阅读体验就不把篇幅继续拉长了,需要的这份金九银十面试参考指南的朋友可以后台小信封【999】撩我领取!最近面试的小伙伴很多,对此我整理了一份Java面试题手册:基础知识、JavaOOP、Java集合/泛型面试题、Java异常面试题、Java中的IO与NIO面试题、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、...

m0_67322837的博客 229

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

11场面试无一被拒!Alibaba金九银十面试参考指南(Java版)真香

Java面试 今年时间属实过得挺快的,眼瞅着就要到8月了!想必有很多小伙伴这会已经在为金九银十面试跳槽做准备了。临近面试肯定是要想办法提升自己的面试能力,这个时候如果还去一昧地提升自己的代码能力对面试是毫无帮助的。大多数人在面试的时候都会遇到以下几种情况(大家可以看看自己中了几个): 1、自我感觉自己的Java的技术不错,但是面试往往不如人意; 2、对面试官提出的问题很懵,不知道他问的是什么? 3、面试才开始,有些面试官就疯狂地从知识基础的开始询问; 4、一些面试官一直在问有关业务场景的问题,完

ruanHbac的博客 191

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

金九银十面试题之《语言特性》

java语言特性面试题

这里是辛苦牛的博客 983

金九银十面试季:Java异常+Java IO与NIO面试题(含答案)

已经是金九银十了,虽说今年行情很不好,但是该来的还是要来,该面对还是要面对,小编准备搞个金九银十面试篇,把手头上有的面试题及答案分享给大家。这篇文章只整理了Java异常和IO和NIO的面试题,同时也整理了其他面试题在手里,与其吃灰还不如分享给大家。 需要的朋友可以点击:点这个!点这个!暗号:csdn Java异常面试篇 1、Java中异常分为哪两种? 编译时异常 运行时异常 2、异常的处理机制有几种? 异常捕捉:try…catch…finally 异常抛出:throws。 3、如何自定义一个异常 继承一个

程序员Monkey的专栏 4658

金九银十面试题之《JVM》

JVM面试题

这里是辛苦牛的博客 1329

2020金九银十面试总结,大厂Java面试必会知识点

1.Java集合框架是什么?说出一些集合框架的优点? 每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。集合框架的部分优点如下: (1)使用核心集合类降低开发成本,而非实现我们自己的集合类。 (2)随着使用经过严格测试的集合框架类,代码质量会得到

weixin_47955802的博客 1729

金九银十面试题之《Spring Data JPA、Spring MVC、AOP》

面试合集,Spring Data JPA、Spring MVC、AOP

这里是辛苦牛的博客 1193

【金九银十面试冲刺】Android岗面试题今日分享——Kotlin篇

扩展,(使用非集成的方式 扩张一个类的方法和变量):比方说 px和dp之间的转换 之前可能需要写个Util现在,通过扩展Float的变量 最后调用的时候仅仅是 123.dp这样px转成dp了lamdba表达式,函数式编程.

xiaokangss的博客 318

【金九银十面试冲刺】Android岗面试题每日分享

已经进入八月份了,我看到了许多朋友在焦急的准备“金九银十”跳槽面试,甚至很多即将毕业的大学生都在备战秋招,对于学历还算优秀的大学生来说,这是一次离大厂最近的机会,毕竟是应届毕业生,不会对技术有非常严格的要求。 而对于工作了一两年的Android开发朋友来说,这段时间加强技术能力,多掌握一些Android核心技术点是重中之重。 为了大家更好的冲刺金九银十,在此每天分享2~3道面试题,希望能对大家的面试起到一点帮助!

xiaokangss的博客 178

金九银十面试题之《IO、集合》

IO、集合面试题

这里是辛苦牛的博客 494
上一篇: 面试90%会问到的200+Java面试题汇总(含答案解析)
下一篇: 太硬核了 GitHub上堪称完美的神仙并发编程笔记,请收下我的下巴
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值