写点什么

Java 中容易混淆的基础知识

作者:秋名山码民
  • 2022 年 5 月 07 日
  • 本文字数:8263 字

    阅读完需:约 27 分钟

Java中容易混淆的基础知识

面向对象

三大特性:继承,封装,多态

封装

3 中修饰符:public,private,protected,给位于同一个或不同包中的对象赋予了不同的访问权限封装的一些好处


  • 通过隐藏对象的属性来保护对象内部的状态

  • 提高代码的可用性,可维护性

  • 提高模块化

继承

给对象提供从基类获取字段和方法的能力,基础提高代码的重用性,可以在不修改类的情况下添加新的特性

多态

多态就是同一函数在不同类中有不同的实现;


  • 面向对象的多态性,即“一个接口,多个方法”。

  • 多态性体现在基类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。

  • 多态性允许一个接口被多个同类使用,弥补了单继承的不足。

final,finally,finalize 的区别

final

final 可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个 常量不能被重新赋值。

finally

finally 一般作用在 try-catch 代码块中,在处理异常的时候,通常我们将一定要执行的代码放入 finally 代码块中,表示不管是 否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。

finalize

finalize 是一个方法,属于 Object 类的一个方法,而 Object 类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调 用 System.gc() 方法的时候,由垃圾回收器调用 finalize(),回收垃圾,一个对象是否可回收的最后判断。

int 和 integer 的区别

int

int 是基本数据类型

integer

integer 是其包装类,是一个类


为了在各种类型中转换,通过各种方法调用


int a = 0;String result = Integer.toString(a);//将int转换为String
复制代码

重载与重写的区别

override 重写

overload 重载

抽象类和接口的区别

接口时公开的,不能有私有的方法和变量,抽象类可以有私有的方法或私有的变量抽象类和接口的区别

反射的用途以及实现

反射机制所提供的功能


在运行时创造一个类的对象;判断一个类所具有的成员变量和方法调用一个对象的方法生成动态代理


Java 反射的主要功能:


确定一个对象的类取出类的 modifiers,数据成员,方法,构造类,超类在运行时刻调用动态对象的方法.创建数组,数组大小和类型


反射机制的理解和应用

自定义注解的场景及实现

登陆、权限拦截、日志处理,以及各种 Java 框架,如 Spring,Hibernate,JUnit 提到注解就不能不说反射,Java 自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过 AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志 注解,就进行日志记录。

http 请求的 get 和 post 方式的区别

原理区别

一般我们在浏览器输入一个网址访问网站都是 GET 请求;再 FORM 表单中,可以通过设置 Method 指定提交方式为 GET 或者 POST 提交方式,默认为 GET 提交方式。


HTTP 定义了与服务器交互的不同方法,其中最基本的四种:GET,POST,PUT,DELETE,HEAD,其中 GET 和 HEAD 被称为安全方法,因为使用 GET 和 HEAD 的 HTTP 请求不会产生什么动作。不会产生动作意味着 GET 和 HEAD 的 HTTP 请求不会在服务器上产生任何结果。但是安全方法并不是什么动作都不产生,这里的安全方法仅仅指不会修改信息。


根据 HTTP 规范,POST 可能会修改服务器上的资源的请求。比如 CSDN 的博客,用户提交一篇文章或者一个读者提交评论是通过 POST 请求来实现的,因为再提交文章或者评论提交后资源(即某个页面)不同了,或者说资源被修改了,这些便是“不安全方法”。

请求的区别

GET 方法会把名值对追加在请求的 URL 后面。因为 URL 对字符数目有限制,进而限制了用在 客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。


POST 方法通过把请求参数值放在请求体中来克服 GET 方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过 POST 请求传递的敏感信息对外部客户端是不可见的。


参考:get和post请求方式的区别

seesion 与 cookie 的区别

  1. cookie 数据存放在客户的浏览器上,session 数据放在服务器上.

  2. cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗考虑到安全应当使用 session。

  3. 设置 cookie 时间可以使 cookie 过期。但是使用 session-destory(),我们将会销毁会话。

  4. session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用 cookie。

  5. 单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。(Session 对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型)

JDBC 流程

  1. 加载 JDBC 驱动程序:在连接数据库之前,首先要加载想要连接的数据库的驱动到 JVM(Java 虚拟机), 这通过 java.lang.Class 类的静态方法 forName(String className)实现。例如:



try{//加载MySql的驱动类Class.forName("com.mysql.jdbc.Driver") ;}catch(ClassNotFoundException e){System.out.println("找不到驱动程序类 ,加载驱动失败!");e.printStackTrace() ;
复制代码


成功加载后,会将 Driver 类的实例注册到 DriverManager 类中。


  1. 提供 JDBC 连接的 URL 连接 URL 定义了连接数据库时的协议、子协议、数据源标识。书写形式:协议:子协议:数据源标识协议:在 JDBC 中总是以 jdbc 开始 子协议:是桥连接的驱动程序或是数据库管理系统名称。数据源标识:标记找到数据库来源的地址与连接端口。例如:jdbc:mysql://localhost:3306/test? useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql 的连接 URL)表示使用 Unicode 字符集。如果 characterEncoding 设置为 gb2312 或 GBK,本参数必须设置为 true 。characterEncoding=gbk:字符编码方式。

  2. 创建数据库的连接 java.sql.DriverManager Connection 代表一个数据库的连接。使用 DriverManager 的 getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和 密码来获得。例如: //连接 MySql 数据库,用户名和密码都是 root



String url = "jdbc:mysql://localhost:3306/test" ;String username = "root" ;String password = "root" ;try{Connection con = DriverManager.getConnection(url , username , password) ;}catch(SQLException se){System.out.println("数据库连接失败!");se.printStackTrace() ;
复制代码


  1. • 要执行 SQL 语句,必须获得 java.sql.Statement 实例,Statement 实例分为以下 3 种类型:1、执行静态 SQL 语句。通常通过 Statement 实例实现。2、执行动态 SQL 语句。通常通过 PreparedStatement 实例实现。3、执行数据库存储过程。通常通过 CallableStatement 实例实现。具体的实现方式:Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;

  2. 执行 SQL 语句 Statement 接口提供了三种执行 SQL 语句的方法:executeQuery 、executeUpdate 和 execute1、ResultSet executeQuery(String sqlString):执行查询数据库的 SQL 语句 ,返回一个结果集(ResultSet)对象。2、int executeUpdate(String sqlString):用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL 语句,如:CREATE TABLE 和 DROP TABLE 等 3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的 语句。 具体实现的代码:ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows =stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;

  3. 处理结果两种情况:1、执行更新返回的是本次操作影响到的记录数。2、执行查询返回的结果是一个 ResultSet 对象。• ResultSet 包含符合 SQL 语句中条件的所有行,并且它通过一套 get 方法提供了对这些 行中数据的访问。• 使用结果集(ResultSet)对象的访问方法获取数据:while(rs.next()){String name = rs.getString(“name”) ;String pass = rs.getString(1) ; // 此方法比较高效}(列是从左到右编号的,并且从列 1 开始)

  4. 关闭 JDBC 对象操作完成以后要把所有使用的 JDBC 对象全都关闭,以释放 JDBC 资源,关闭顺序和声 明顺序相反:1、关闭记录集 2、关闭声明 3、关闭连接对象


    if(rs!=null){ // 关闭记录集        try{        rs.close();        }catch(SQLException e){e.printStackTrace();        }        }try{        stmt.close();                }
}catch(SQLException e){e.printStackTrace(); }
if(conn!=null){ // 关闭连接对象 try{ conn.close(); }catch(SQLException e){ e.printStackTrace();20} }
复制代码

MVC 思想

M:Model 模型 V:View 视图 C:Controller 控制器


模型就是封装业务逻辑和数据的一个一个的模块,控制器就是调用这些模块的(java 中通常是 用 Servlet 来实现,框架的话很多是用 Struts2 来实现这一层),视图就主要是你看到的,比如 JSP 等.


当用户发出请求的时候,控制器根据请求来选择要处理的业务逻辑和要选择的数据,再返回去 把结果输出到视图层,这里可能是进行重定向或转发等.

equals 与 == 的区别

值类型(int,char,long,boolean 等)都是用==判断相等性。对象引用的话,判断引用所指的对象 是否是同一个。equals 是 Object 的成员函数,有些类会覆盖(override)这个方法,用于判 断对象的等价性。例如 String 类,两个引用所指向的 String 都是"abc",但可能出现他们实际对应的对象并不是同一个(和 jvm 实现方式有关),因此用 ==判断他们可能不相等,但用 equals 判断一定是相等的。

线程

创建线程的方法及实现

Java 中创建线程主要有三种方式:一、继承 Thread 类创建线程类(1) 定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任务。因此把 run()方法称为执行体。(2) 创建 Thread 子类的实例,即创建了线程对象。(3) 调用线程对象的 start()方法来启动该线程。


package com.thread;
public class FirstThreadTest extends Thread{int i = 0;//重写run方法,run方法的方法体就是现场执行体public void run(){for(;i<100;i++){ System.out.println(getName()+" "+i);}}public static void main(String[] args){for(int i = 0;i< 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i): if(i==20) { new FirstThreadTest().start(); new FirstThreadTest().start(); } }}
复制代码


二、通过 Runnable 接口创建线程类(1) 定义 runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样是该线程的线程执行体。(2) 创建 Runnable 实现类的实例的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。(3)调用线程对象的 start()方法来启动该线程



package com.thread;
public class RunnableThreadTest implements Runnable{
private int i; public void run(){for(i = 0;i <100;i++){System.out.println(Thread.currentThread().getName()+""+i);}}public static void main(String[] args){for(int i = 0;i < 100;i++){System.out.println(Thread.currentThread().getName()+""+i); if(i==20) { RunnableThreadTest rtt = new RunnableThreadTest(); new Thread(rtt,"新线程1").start(); new Thread(rtt,"新线程2").start(); } } } }
复制代码


三、通过 Callable 和 Future 创建线程(1) 创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体,并且有返回值。(2) 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值。(3) 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。(4) 调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值


package com.thread;
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); 13 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 的循 环变量i的值" + i); if (i == 20) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace();
} }

@Override public Integer call() throws Exception 34
{ int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; }}
复制代码


采用实现 Runnable、Callable 接口的方式创见多线程时,优势是:


线程类只是实现了 接口或 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同 一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面 向对象的思想。


劣势是:


编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread()方法。


使用继承 Thread 类的方式创建多线程时优势是:


编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法,直接使用 this 即可获得当前线程。


劣势是:


线程类已经继承了 Thread 类,所以不能再继承其他父类。

sleep() 、join()、yield()的区别

一、sleep()


在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度 程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如 果有 Synchronized 同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常比如有两个线程同时执行(没有 Synchronized),一个线程优先级为 MAX_PRIORITY,另一 个为 MIN_PRIORITY,如果没有 Sleep()方法,只有高优先级的线程执行完成后,低优先级 的线程才能执行;但当高优先级的线程 sleep(5000)后,低优先级就有机会执行了。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的 线程有执行的机会。


二、join()


Thread 的非静态方法 join()让一个线程 B“加入”到另外一个线程 A 的尾部。在 A 执行完毕之前, B 不能工作。保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有 存活,则当前线程不需要停止。


三、yield()


yield()方法和 sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield()方 法只是使当前线程重新回到可执行状态,所以执行 yield()的线程有可能在进入到可执行状态 后马上又被执行,另外 yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也 和 sleep()方法不同。


再给大家推荐一本书:Java 并发编程想要电子版也可以私信我

线程池的几种方式

newFixedThreadPool(int nThreads)创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数 量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程


newCachedThreadPool()创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当 需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制


newSingleThreadExecutor()这是一个单线程的 Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会 创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行


newScheduledThreadPool(int corePoolSize)创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer。


private static final Executor exec=Executors.newFixedThreadPool(50);
Runnable runnable=new Runnable(){public void run(){ ... }}exec.execute(runnable);
Callable<Object> callable=new Callable<Object>() { public Object call() throws Exception { return null; }}Future future=executorService.submit(callable);future.get(); // 等待计算完成后,获取结果future.isDone(); // 如果任务已完成,则返回 truefuture.isCancelled(); // 如果在任务正常完成前将其取消,则返回 truefuture.cancel(true); // 试图取消对此任务的执行,true中断运行的任务,false 允许正在运行的任务运行完成
复制代码

线程的生命周期

新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态(1)生命周期的五种状态新建(new Thread)当创建 Thread 类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1=new Thread();就绪(runnable)线程已经被启动,正在等待被分配给 CPU 时间片,也就是说此时线程正在就绪队列中排队 等候得到 CPU 资源。例如:t1.start();运行(running)线程获得 CPU 资源正在执行任务(run()方法),此时除非此线程自动放弃 CPU 资源或者有 优先级更高的线程进入,线程将一直运行到结束。死亡(dead)当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态 等待执行。自然终止:正常运行 run()方法后终止异常终止:调用**stop()**方法让一个线程终止运行堵塞(blocked)由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行,即进入堵塞状态。正在睡眠:用 sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。正在等待:调用 wait()方法。(调用 motify()方法回到就绪状态)被另一个线程所阻塞:调用 suspend()方法。(调用 resume()方法恢复)

锁机制

线程安全

线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲 突。


在 Java 里,线程安全一般体现在两个方面:1、多个 thread 对同一个 java 实例的访问(read 和 modify)不会相互干扰,它主要体现在关 键字 synchronized。如 ArrayList 和 Vector,HashMap 和 Hashtable(后者每个方法前都有 synchronized 关键字)。如果你在 interator 一个 List 对象时,其它线程 remove 一个 element, 问题就出现了。2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在 java.lang.ThreadLocal 类,而没有 Java 关键字支持,如像 static、transient 那样。

volatle 实现原理

深入分析 Volatile 的实现原理

悲观锁,乐观锁

是一种思想。可以用在很多方面。比如数据库方面。悲观锁就是 for update(锁定查询的行)乐观锁就是 version 字段(比较跟上一次的版本号,如果一样则更新,如果失败则要重复读­比较­写的操作。)JDK 方面:悲观锁就是 sync 乐观锁就是原子类(内部使用 CAS 实现)本质来说,就是悲观锁认为总会有人抢我的。乐观锁就认为,基本没人抢。


乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出 当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重 复读­比较­写的操作。CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。CAS 顶多算是乐观锁写那一步操作的一种实现方式罢了,不用 CAS 自己加锁也是可以的。


乐观锁的业务场景及实现方式


每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁, 但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不 进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁, 期间该数据可以被其他线程进行读写操作。乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可 能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量 的查询操作,降低了系统的吞吐量。

最后

都看到这了,给孩子一个三连支持一下吧,


Java 对初学者很友好;Java 资源丰富,因为它可以解决不同的问题;Java 有一个庞大而友好的社区;Java 无处不在,因此更容易找到第一份工作;Java 开发人员缺口大,薪水很高。


<font size=5 color=blue>只要你具备能力,薪资待遇你敢要公司就敢给。

用户头像

卷不死,就往…… 2021.10.19 加入

2019NOIP退役成员,华为云享专家,阿里云专家博主,csdn博主,努力进行算法分享,有问题欢迎私聊

评论

发布
暂无评论
Java中容易混淆的基础知识_java_秋名山码民_InfoQ写作社区