Java基础知识易混淆知识点总结

一:continue、break 和 return 的区别是什么

在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:

  1. continue :指跳出当前的这一次循环,继续下一次循环。
  2. break :指跳出整个循环体,继续执行循环下面的语句。

return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:

  1. return; :直接使用 return 结束方法执行,用于没有返回值函数的方法
  2. return value; :return 一个特定值,用于有返回值函数的方法

 下面运行的结果是啥呢?

    public static void main(String[] args) {
        boolean flag = false;
        for (int i = 0; i <= 3; i++) {
            if (i == 0) {
                System.out.println("0");
            } else if (i == 1) {
                System.out.println("1");
                continue;
            } else if (i == 2) {
                System.out.println("2");
                flag = true;
            } else if (i == 3) {
                System.out.println("3");
                break;
            } else if (i == 4) {
                System.out.println("4");
            }
            System.out.println("xixi");
        }
        if (flag) {
            System.out.println("haha");
            return;
        }
        System.out.println("heihei");
    }

运行结果

0
xixi
1
2
xixi
3
haha

二:new一个对象过程中发生了什么?

  1. 确认类元信息是否存在。当 JVM 接收到 new 指令时,首先在 metaspace 内检查需要创建的类元信息是否存在。 若不存在,那么在双亲委派模式下,使用当前类加载器以 ClassLoader + 包名+类名为 Key 进行查找对应的 class 文件。 如果没有找到文件,则抛出 ClassNotFoundException 异常 , 如果找到,则进行类加载(加载 - 验证 - 准备 - 解析 - 初始化),并生成对应的 Class 类对象。
  2. 分配对象内存。 首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可,即 4 个字节大小,接着在堆中划分—块内存给新对象。 在分配内存空间时,需要进行同步操作,比如采用 CAS (Compare And Swap) 失败重试、 区域加锁等方式保证分配操作的原子性。
  3. 设定默认值。 成员变量值都需要设定为默认值, 即各种不同形式的零值。
  4. 设置对象头。设置新对象的哈希码、 GC 信息、锁信息、对象所属的类元信息等。这个过程的具体设置方式取决于 JVM 实现。
  5. 执行 init 方法。 初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

三:静态方法为什么不能调用非静态成员?

这个需要结合 JVM 的相关知识,主要原因如下:

  1. 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
  2. 在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。
  3. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制

四:基本类型和包装类型的区别?

  • 包装类型不赋值就是 null ,而基本类型有默认值且不是 null
  • 包装类型可用于泛型,而基本类型不可以。
  • 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
  • 相比于对象类型, 基本数据类型占用的空间非常小。

从字节码中,我们发现装箱其实就是调用了 包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。

因此,

  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue();

注意:如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。

包装类型间的相等判断应该用equals,而不应该用==

五:面向对象三大特征

1:封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息

2:继承是不同类型的对象,相互之间经常有一定数量的共同点。

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。

3:多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

六:深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

 七:== 和 equals() 的区别

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

八:try-catch-finally 如何使用?

  • try块 : 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • *catch块 : 用于处理 try 捕获到的异常。
  • finally 块 : 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

九:值传递相关

(1):形参&实参

方法的定义可能会用到 参数(有参的方法),参数在程序语言中分为:

  • 实参(实际参数) :用于传递给函数/方法的参数,必须有确定的值。
  • 形参(形式参数) :用于定义函数/方法,接收实参,不需要有确定的值。

(2):值传递&引用传递

程序设计语言将实参传递给方法(或函数)的方式分为两种:

  • 值传递 :方法接收的是实参值的拷贝,会创建副本。
  • 引用传递 :方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。

Java 中将实参传递给方法(或函数)的方式是 值传递 :

  • 如果参数是基本类型的话,很简单,传递的就是基本类型的字面量值的拷贝,会创建副本。
  • 如果参数是引用类型,传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本。

十:@Autowired 和 @Resource 的区别是什么?

简单总结一下:

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired 和@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显示指定名称,@Resource可以通过 name 属性来显示指定名称。

十一:Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多

十二:AspectJ 定义的通知类型有哪些?

  • Before(前置通知):目标对象的方法调用之前触发
  • After (后置通知):目标对象的方法调用之后触发
  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing(异常通知) :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around: (环绕通知)编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

十三:@PathVariable ,@RequestParam和 @RequestBody

@PathVariable用于获取路径参数,@RequestParam用于获取查询参数。

举个简单的例子:

@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
         @PathVariable("klassId") Long klassId,
         @RequestParam(value = "type", required = false) String type ) {
...
}

如果我们请求的 url 是:/klasses/123456/teachers?type=web

那么我们服务获取到的数据就是:klassId=123456,type=web

@RequestBody 用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。

需要注意的是:一个请求方法只可以有一个@RequestBody,但是可以有多个@RequestParam@PathVariable。 如果你的方法必须要用两个 @RequestBody来接受数据的话,大概率是你的数据库设计或者系统设计出问题了

十四:Mybatis中#{}和${}的区别是什么?

  • ${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc. Driver
  • #{}是 sql 的参数占位符,MyBatis 会将 sql 中的#{}替换为? 号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的? 号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()

十五:sleep() 方法和 wait() 方法对比

共同点 :两者都可以暂停线程的执行。

区别 :

  • sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
  • wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。
  • wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
  • sleep() 是 Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。

十六:为什么 wait() 方法不定义在 Thread 中?

wait() 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object)而非当前的线程(Thread)。

类似的问题:为什么 sleep() 方法定义在 Thread 中?

因为 sleep() 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。

十七:synchronized 和 volatile 的区别?

synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!

  • volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

十八:synchronized 和 ReentrantLock 的区别

两者都是可重入锁

“可重入锁” 指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果是不可重入锁的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。

#synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API

synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

#ReentrantLock 比 synchronized 增加了一些高级功能

相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点:

  • 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
  • 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
  • 可实现选择性通知(锁可以绑定多个条件)synchronized关键字与wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

Condition是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是 Condition 接口默认提供的。而synchronized关键字就相当于整个 Lock 对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。

如果你想使用上述功能,那么选择 ReentrantLock 是一个不错的选择。性能已不是选择标准

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值