Java后台开发易错点

一、Java基础部分

1、面向对象和面向切面的区别?

AOP(面向切面编程):通过预编译方式运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。
OOP(面向对象编程) 针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。但是也有它的缺点,最明显的就是关注点聚焦时,面向对象无法简单的解决这个问题,一个关注点是面向所有而不是单一的类,不受类的边界的约束,因此OOP无法将关注点聚焦来解决,只能分散到各个类中。 AOP(面向切面编程)则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。 AOP并不是与OOP对立的,而是为了弥补OOP的不足。OOP解决了竖向的问题,AOP则解决横向的问题。因为有了AOP我们的调试和监控就变得简单清晰。
参考:知乎-AOP与OOP有什么区别,谈谈AOP的原理是什么

2、线程池相关知识

线程池作用:
1、减少在创建和销毁线程上所花的时间以及系统资源的开销。
2、如果不使用线程池,可能造成系统创建大量线程。
线程池的种类:

  1. 可缓存线程池(newCachedThreadPool) 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. 指定工作线程数量的线程池(newFixedThreadPool) 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
  3. 单线程线程池(newSingleThreadExecutor) 创建一个单线程化的Executor,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  4. 定时的线程池(newScheduledThreadPool) 创建一个定长的线程池,支持定时及周期性任务执行。
    参考:JAVA线程池有几种类型?

线程池参数:

  1. corePoolSize:核心线程数,默认会一直保持在线程池中,即便是空闲也会保持。除非设置了allowCoreThreadTimeOut参数,设置此参数后,超时后会被销毁。
  2. maximumPoolSize:线程池中所允许的最大线程数。
  3. keepAliveTime:超过核心线程数的线程,如果在此参数指定的时间内还没有接收到新任务,则会被销毁。也就是说此参数是指定超过核心线程数的空闲线程的最大存活时间。
  4. unit:存活时间的单位。
  5. workQueue:用来保存通过execute(Runnable command)方法提交的未执行的任务。也就是在任务执行之前用来保存任务的队列。
  6. threadFactory:用来创建线程的工厂。
  7. handler:饱和拒绝策略。当不能创建新线程即已经达到了可创建线程的边界(maximumPoolSize)并且用来保存任务的队列(workQueue)已满的时候执行的策略。

工作队列:
1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
3、SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
4、PriorityBlockingQueue:一个具有优先级的无限阻塞队列
拒绝策略:
1、Abort策略:丢弃任务并抛一个RejectedExecutionException异常。(默认拒绝策略)
2、CallerRuns策略:由调用线程(提交任务的线程)处理该任务。
3、DiscardPolicy策略:直接丢弃任务,不抛异常。
4、DiscardOldestPolicy策略:丢弃队列头中的第一个任务,重新提交被拒绝的任务。
参考:JDK1.8线程池参数

3、JDK1.8 JVM运行时数据区域划分

这里介绍的是JDK1.8 JVM运行时内存数据区域划分。1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。

  1. 程序计数器: 线程私有,每个线程一块,指向当前线程正在执行的字节码代码的行号。如果当前线程执行的是native方法,则其值为null。
  2. Java虚拟机栈: 线程私有,生命周期与线程同进同退。每个Java方法在被调用的时候都会创建一个栈帧,并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就完成了使命。
    在这里插入图片描述
  3. 本地方法栈: 线程私有,功能与Java虚拟机栈十分相同。区别在于,本地方法栈为虚拟机使用到的native方法服务。
  4. Java堆: 线程共享,是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:所有的对象实例及数组都在堆上进行分配。1.7后,字符串常量池从永久代中剥离出来,存放在堆中
    在这里插入图片描述
  5. 元数据区: 线程共享,元数据区取代了1.7版本及以前的永久代。元数据区和永久代本质上都是方法区的实现方法区存放虚拟机加载的类信息,静态变量,常量等数据。运行时常量池存在于内存的元空间中。
    参考:JDK1.8 JVM运行时数据区域划分

4、==和equals()的区别?

  1. ==:如果比较的是基本数据类型,则直接比较其存储的 “值”是否相等;如果比较的是引用类型的变量,则比较的是所指向的对象的地址。
  2. equles():如果没有重写equals()方法比较的是对象的地址,因为对Object来说对象没有什么属性可以比较,只能比较最底层的地址。而如果重写equals()方法时,该方法的对象因为是Object的子类,所以调用时会调用子类对象里面的方法。所以只有重写equals()方法后,两者比较的才是内容、或者说重写可以使自己定义比较的规则,不想按照地址去比较。
  3. 如何重写自己写的类中的equals()方法?
    答:(1)判断要比较对象的是否为null,若是直接返回false,若不比较则可能会出现空指针异常(NullPointerException);
    (2)判断是否在与自身比较(通过==比较地址),若是直接返回true;
    (3)判断要比较的两个对象是否为同类,若是再进行接下来比较,若不是直接返回false。若不判断,则可能出现强转异常(classCastException);
    (4)通过向下转型,比较两对象内容是否相等。
  4. equals和hashcode常规协定
    1、两个对象用equals()比较返回true,那么两个对象的hashCode()方法必须返回相同的结果。
    2、两个对象用equals()比较返回false,不要求hashCode()方法也一定返回不同的值,但是最好返回不同值,以提高哈希表性能。
    3、重写equals()方法,必须重写hashCode()方法,以保证equals方法相等。
class Person{
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String toString(){
        return this.name+"今年"+this.age+"岁";
    }
    @Override
	public int hashCode() {
		int result = 17; 
		result = result * 31 + name.hashCode(); 
 		result = result * 31 + age.hashCode(); 
		return result;
	}
	@Override
    public boolean equals(Object obj){//Object类可接受任何类
        if(obj==null){//判断是否为空,若不判断则会出先空指针异常(NullPointerException)
            return false;
        }
        if(this==obj){//判断是否在与自身比较(通过比较地址),若是则直接返回true
            return true;
        }
        if(!(obj instanceof Person)){//instanceof作用为判断其左边对象是否为右边对象的实例,此处为判断主方法中equals()方法括号中的对象是否为Person类
            return false;
        }
        //到达此处时必定是同类但不同地址的对象在比较
        Person per=(Person)obj;//向下转型,比较属性值
        return this.name.equals(per.name)&&this.age==per.age;//判定属性内容是否相等(易错点)
    }
}

参考:自定义类覆写equals方法

5. GC算法

判断对象是否死亡:
引用计数法。无法解决对象之间相互循环引用的问题。
可达性分析法。可以作为GC Root的有:
1、虚拟机栈(栈帧中的本地变量表)中引用的对象。
2、方法区中类静态属性引用的对象。
3、方法区中常量引用的对象。
4、本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

6、常量池相关

  1. String s1 = new String("123"); 此行代码创建了两个对象,在执行前会在常量池中创建一个"123"的对象,然后执行该行代码时new一个"123"的对象存放在堆区中;然后s1指向堆区中的对象;
  2. s1 = s1.intern(); 把堆中s1对象的引用放在常量池中
  3. String s3 = new String("2") + new String("2"); 仅在堆中创建了对象"22","22"字面量没有放入字符串常量池。
  4. String a3 = a1+a2; 同3.
  5. String s6 = "11" + "22"; 在编译阶段就会将这两个字符串常量进行拼接,也就是"1122",并放入字符串常量池中。
public class Main {
    public static void main(String[] args) {
        String s1 = new String("123");
        String s2 = "123";
        System.out.println(s1==s2); //false
        s1 = s1.intern();   //把堆中s1对象的引用放在常量池中
        System.out.println(s1==s2); //true
        String s3 = new String("2") + new String("2");  //仅在堆中创建了对象"22","22"字面量没有放入字符串常量池
        String s4 = "22";   //"22"字面量放入字符串常量池
        System.out.println(s3==s4); //false
        s3 = s3.intern();   //把堆中s3对象的引用放在常量池中
        System.out.println(s3==s4); //true
        String a1 = "11";
        String a2 = "22";
        String s5 = "1122";
        String a3 = a1+a2;
        System.out.println(a3==s5); //false
        a3 = a3.intern();
        System.out.println(a3==s5); //true
        String s6 = "11" + "22"; 	//在编译阶段就会将这两个字符串常量进行拼接,也就是"1122",并放入字符串常量池中。
        System.out.println(s5==s6); //true     
    }
}
  1. 内存中有一个java基本类型封装类的常量池。这些类包括Byte, Short, Integer, Long, Character, Boolean。需要注意的是,Float和Double这两个类并没有对应的常量池。
  2. Integer的常量池为: -128~127
  3. "=="两边有算术运算时才会自动拆箱,因此此时比较的是数值,而并非对象
public class Main {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;
        System.out.println(i1==i2); //true
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3==i4); //false
        Integer i5 = new Integer(127);
        Integer i6 = new Integer(0);
        System.out.println(i1==i5); //false
        System.out.println(i3==i4+0); //true
        System.out.println(i1==i5+0); //true
    }
}

7、继承相关

  1. 子类不可以继承父类的构造方法,只可以调用父类的构造方法。在子类的构造方法中调用父类的构造方法是用super(),如果没有写super(),则默认调用父类的无参构造方法。一个类都会有默认的空参数的构造函数,若指定了带参构造函数,那么默认的空参数的构造函数,就不存在了。这时如果子类的构造函数有默认的super()语句,那么就会出现错误,因为父类中没有空参数的构造函数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值