• [技术干货] java equals 方法域hashcode方法
    解析1.Object类 的equals 方法复制代码代码如下:   /**     * Indicates whether some other object is "equal to" this one.     * <p>     * The {@code equals} method implements an equivalence relation     * on non-null object references:     * <ul>     * <li>It is <i>reflexive</i>: for any non-null reference value     *     {@code x}, {@code x.equals(x)} should return     *     {@code true}.     * <li>It is <i>symmetric</i>: for any non-null reference values     *     {@code x} and {@code y}, {@code x.equals(y)}     *     should return {@code true} if and only if     *     {@code y.equals(x)} returns {@code true}.     * <li>It is <i>transitive</i>: for any non-null reference values     *     {@code x}, {@code y}, and {@code z}, if     *     {@code x.equals(y)} returns {@code true} and     *     {@code y.equals(z)} returns {@code true}, then     *     {@code x.equals(z)} should return {@code true}.     * <li>It is <i>consistent</i>: for any non-null reference values     *     {@code x} and {@code y}, multiple invocations of     *     {@code x.equals(y)} consistently return {@code true}     *     or consistently return {@code false}, provided no     *     information used in {@code equals} comparisons on the     *     objects is modified.     * <li>For any non-null reference value {@code x},     *     {@code x.equals(null)} should return {@code false}.     * </ul>     * <p>     * The {@code equals} method for class {@code Object} implements     * the most discriminating possible equivalence relation on objects;     * that is, for any non-null reference values {@code x} and     * {@code y}, this method returns {@code true} if and only     * if {@code x} and {@code y} refer to the same object     * ({@code x == y} has the value {@code true}).     * <p>     * Note that it is generally necessary to override the {@code hashCode}     * method whenever this method is overridden, so as to maintain the     * general contract for the {@code hashCode} method, which states     * that equal objects must have equal hash codes.     *     * @param   obj   the reference object with which to compare.     * @return  {@code true} if this object is the same as the obj     *          argument; {@code false} otherwise.     * @see     #hashCode()     * @see     java.util.HashMap     */    public boolean equals(Object obj) {        return (this == obj);    }看代码,Object的equals方法,采用== 进行比较,只是比较对象的引用,如果引用的对象相同,那么就返回true.看注释,Object的equals方法,具有如下特性1.reflexive-自反性  x.equals(x)  return true2.symmetric-对称性x.equals(y)  return truey.equals(x)  return true3.transitive-传递性x.equals(y)  return truey.equals(z)  return truex.equals(z)  return true4.consistent-一致性x.equals(y)  return true //那么不管调用多少次,肯定都是返回true5.与null的比较x.equals(null) return false //对于none-null的x对象,每次必然返回false
  • [技术干货] java打包2
    在进入正题之前,你要先打开我们刚刚做好的jar包看看,多了什么呢,META-INF目录?再看看里面是什么,还有一个MANIFEST.MF文件是不是?用文本编辑器(我这里是UltraEdit)打开它看看:  Manifest-Version: 1.0   Created-By: 1.4.2 (Sun Microsystems Inc.)    就是这样。这里我们对它进行修改,加一句:Main-Class: HelloWorld (在第三行)。这个就是我们之前写的那个类,也就是我们的入口类。也即,  Manifest-Version: 1.0   Created-By: 1.4.2 (Sun Microsystems Inc.)   Main-Class: HelloWorld    接下来,我们在命令提示符里执行:  jar  umf  MANIFEST.MF  app.jar  (应该是hello.jar吧)   这样我们使用了我们自己的MANIFEST.MF文件对原来默认的进行了更新。你不妨可以再进去看看是不是添上了Main-Class: HelloWorld这一句。    (是吗,我怎么没试出来,提示java.io.FileNotFoundException:MANIFEST.MF(系统找不到指定的文件)怎么回事?) Ok,这个最后的一步了,来验证我们做的一切,在命令提示符中输入:  java -jar hello.jar(执行)    出现了什么, Hi, Hello World!    我们再来看看jar文件在tomcat中发布,注意:在tomcat中我们就不能再用jar这种格式,而改war格式,它是专门用于web应用的,其实整个过程下来基本上和jar是类似的:   先准备我们要打包的资源。     找到存放tomcat的webapps目录,进到其中,新建一个文件夹,这里命名为hello,再进去新建WEB-INF文件夹,再进去新建classes文件夹,此时我们也将我们唯一的servlet,HelloWorld.java放到这里,在与classes目录同级下建立一文件web.xml。Ok,目前我们初步建立了一个简单的web应用。    这是HelloWorld.java:     import java.io.*;   import javax.servlet.*;   import javax.servlet.http.*;   public class HelloWorld extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)   throws ServletException, IOException {   res.setContentType("text/html");   PrintWriter out = res.getWriter();   out.println("");   out.println("");   out.println("");   out.println("Hello, World!");   out.println("");   }   }//end here!    对它编译。下面是web.xml:   <?xml version="1.0" encoding="UTF-8"?>   <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">   <web-app>   <servlet>   <servlet-name>hello</servlet-name>   <servlet-class>HelloWorld</servlet-class>   </servlet>   <servlet-mapping>   <servlet-name>hello</servlet-name>   <url-pattern>/HelloWorld</url-pattern>   </servlet-mapping>   </web-app>  在命令提示符下进到先前创制的hello目录下,执行 jar cvf hello.war * ,我们便得到hello.war。将它拷贝至webapps目录下,ok,来看最后一步,打开tomcat的目录conf中的server.xml,加入:  <Context path="/hello" docBase="hello.war" debug="0" reloadable="true"/>   大功告成!运行它,启动tomcat,后在浏览器中输入http://localhost:8080/hello/HelloWorld,有了吗?   最后,如果你想用ant来完成以上的打包活动,下面就告诉你:   对于jar来说。在build.xml中,  <target name="jar">   <jar destfile="${app_home}/hello.jar">   <fileset dir="${dest}" includes="**"/>   <!--fileset dir="${dest}" includes="**/action.properties"/-->   </jar>   </target>     对于war,  <war warfile="hello.war" webxml="./WEB-INF/web.xml">   <fileset dir="html"/>   <lib dir="lib/">   <exclude name="oracle*.jar"/>   </lib>   <classes dir="build/servlets">   <include name="**/*.class"/>   </classes>   </war>     好了,就这么多,希望对你有点帮助。:)  补充:   jar基本操作:     1. 创建jar文件    jar cf jar-file input-file(s)   c---want to Create a JAR file.   f---want the output to go to a file rather than to stdout.   eg: 1)jar cf myjar.jar query_maintain_insert.htm   2)jar cvf myjar.jar query_maintain_insert.htm   v---Produces verbose(详细的) output.  3)jar cvf myjar.jar query_maintain_insert.htm mydirectory   4)jar cv0f myjar.jar query_maintain_insert.htm mydirectory   0---don't want the JAR file to be compressed.   5)jar cmf MANIFEST.MF myjar.jar yahh.txt   m---Used to include manifest information from an existing manifest file.   6)jar cMf MANIFEST.MF myjar.jar yahh.txt   M---the default manifest file should not be produced.   7)jar cvf myjar.jar *   *---create all contents in current directory.   2. 察看jar文件     jar tf jar-file   t---want to view the Table of contents of the JAR file.   eg: 1)jar vft yahh.jar   v---Produces verbose(详细的) output.    3. 提取jar文件    jar xf jar-file [archived-file(s)]   x---want to extract files from the JAR archive.   eg: 1)jar xf yahh.jar yahh.txt(仅提取文件yahh.txt)  2)jar xf yahh.jar alex/yahhalex.txt(仅提取目录alex下的文件yahhalex.txt)    3)jar xf yahh.jar(提取该jar包中的所有文件或目录)    4. 修改Manifest文件    jar cmf manifest-addition jar-file input-file(s)   m---Used to include manifest information from an existing manifest file.    5. 更新jar文件    jar uf jar-file input-file(s)   u---want to update an existing JAR file.
  • [技术干货] java打包
    jar文件听说过吗,没有?或者陌生!好,没关系,这就是我们的第一站:打包发布。     为什么会有这个玩意呢,首先,这是jar的全称:JavaTM Archive (JAR) file,是的,就是java存档文件。这有点类似zip文件,想一想它是干什么的用的呢,压缩!?没错就是要压缩,将我们原先零散的东西放到一下,重新组织,所有这些目的只有一个:方便!好了,不用管他是怎么压缩的,我们的重点是哪些是我们要压缩的(输入),还有压缩成了什么(输出),进而将它发布(部署)。     那我们的输入(要压缩的东西)主要是class文件,还有辅助的资源(这其中可能有图片,jsp文件,html文件等等)。Jar技术在jdk1.1版本中就已存在,在1.2中又有了增强。接下来说说jar的好处吧,这是官方的描述:安全,快速下载,压缩,猎取包,版本化包,可携。    说了这么多,我们现在开始实施。     先打开命令提示符(win2000或在运行框里执行cmd命令,win98为DOS提示符),输入jar Chelp,然后回车(如果你盘上已经有了jdk1.1或以上版本),看到什么:     用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件] [-C 目录] 文件名 ...     选项:     -c 创建新的存档  -t 列出存档内容的列表  -x 展开存档中的命名的(或所有的〕文件  -u 更新已存在的存档  -v 生成详细输出到标准输出上  -f 指定存档文件名  -m 包含来自标明文件的标明信息  -0 只存储方式;未用zip压缩格式  -M 不产生所有项的清单(manifest〕文件  -i 为指定的jar文件产生索引信息  -C 改变到指定的目录,并且包含下列文件:    如果一个文件名是一个目录,它将被递归处理。     清单(manifest〕文件名和存档文件名都需要被指定,按'm' 和 'f'标志指定的相同顺序。     示例1:将两个class文件存档到一个名为 'classes.jar' 的存档文件中:  jar cvf classes.jar Foo.class Bar.class    示例2:用一个存在的清单(manifest)文件 'mymanifest' 将 foo/ 目录下的所有文件存档到一个名为 'classes.jar' 的存档文件中:  jar cvfm classes.jar mymanifest -C foo/ .    来个小例子试试看:  我们只有一个HelloWorld,如下:     public class HelloWorld{   public static void main(String[ ] args){   System.out.println(“Hi, Hello World!”);   }   }     将这个java文件存到C盘跟目录下,ok,接下来,  在先前打开的命令提示符下(跳转到C盘提示符下),我们输入javac HelloWorld.java,然后继续输入:jar  cvf  hello.jar  HelloWorld.class,回车后去你的C盘看看,多了什么,没错 hello.jar 。  基本的步骤我们现在都知道了,你可以自己去尝试一下随着jar后面的参数的不同,结果有什么变化。      紧接着我们看看如何运行我们的jar包。   
  • [技术干货] java多线程应用实现方法 4
    代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public synchronized void set(String name, int age){this.name=name;try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.age=age;}public synchronized void get(){try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.getName()+"<===>"+this.getAge());}private String name = "Rollen";private int age = 20;}/*** 生产者* */class Producer implements Runnable {private Info info = null;Producer(Info info) {this.info = info;}public void run() {boolean flag = false;for (int i = 0; i < 25; ++i) {if (flag) {this.info.set("Rollen", 20);flag = false;} else {this.info.set("ChunGe", 100);flag = true;}}}}/*** 消费者类* */class Consumer implements Runnable {private Info info = null;public Consumer(Info info) {this.info = info;}public void run() {for (int i = 0; i < 25; ++i) {try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}this.info.get();}}}/*** 测试类* */class hello {public static void main(String[] args) {Info info = new Info();Producer pro = new Producer(info);Consumer con = new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【运行结果】:Rollen<===>20ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100Rollen<===>20ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100从运行结果来看,错乱的问题解决了,现在是Rollen 对应20,ChunGe对于100,但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。如果想解决这个问题,就需要使用Object类帮忙了、,我们可以使用其中的等待和唤醒操作。要完成上面的功能,我们只需要修改Info类饥渴,在其中加上标志位,并且通过判断标志位完成等待和唤醒的操作,代码如下:复制代码代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public synchronized void set(String name, int age){if(!flag){try{super.wait();}catch (Exception e) {e.printStackTrace();}}this.name=name;try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.age=age;flag=false;super.notify();}public synchronized void get(){if(flag){try{super.wait();}catch (Exception e) {e.printStackTrace();}}try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.getName()+"<===>"+this.getAge());flag=true;super.notify();}private String name = "Rollen";private int age = 20;private boolean flag=false;}/*** 生产者* */class Producer implements Runnable {private Info info = null;Producer(Info info) {this.info = info;}public void run() {boolean flag = false;for (int i = 0; i < 25; ++i) {if (flag) {this.info.set("Rollen", 20);flag = false;} else {this.info.set("ChunGe", 100);flag = true;}}}}/*** 消费者类* */class Consumer implements Runnable {private Info info = null;public Consumer(Info info) {this.info = info;}public void run() {for (int i = 0; i < 25; ++i) {try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}this.info.get();}}}/*** 测试类* */class hello {public static void main(String[] args) {Info info = new Info();Producer pro = new Producer(info);Consumer con = new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【程序运行结果】:Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20
  • [技术干货] java多线程应用实现方法 3
    同步和死锁:【问题引出】:比如说对于买票系统,有下面的代码:复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for(int i=0;i<10;++i){if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}public static void main(String[] args) {hello he=new hello();Thread h1=new Thread(he);Thread h2=new Thread(he);Thread h3=new Thread(he);h1.start();h2.start();h3.start();}private int count=5;}【运行结果】:543210-1这里出现了-1,显然这个是错的。,应该票数不能为负值。如果想解决这种问题,就需要使用同步。所谓同步就是在统一时间段中只有有一个线程运行,其他的线程必须等到这个线程结束之后才能继续执行。【使用线程同步解决问题】采用同步的话,可以使用同步代码块和同步方法两种来完成。【同步代码块】:语法格式:synchronized(同步对象){//需要同步的代码}但是一般都把当前对象this作为同步对象。比如对于上面的买票的问题,如下:复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for(int i=0;i<10;++i){synchronized (this) {if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}}public static void main(String[] args) {hello he=new hello();Thread h1=new Thread(he);Thread h2=new Thread(he);Thread h3=new Thread(he);h1.start();h2.start();h3.start();}private int count=5;}【运行结果】:(每一秒输出一个结果)54321【同步方法】也可以采用同步方法。语法格式为synchronized 方法返回类型 方法名(参数列表){// 其他代码}现在,我们采用同步方法解决上面的问题。复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for (int i = 0; i < 10; ++i) {sale();}}public synchronized void sale() {if (count > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count--);}}public static void main(String[] args) {hello he = new hello();Thread h1 = new Thread(he);Thread h2 = new Thread(he);Thread h3 = new Thread(he);h1.start();h2.start();h3.start();}private int count = 5;}【运行结果】(每秒输出一个)54321提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。此处列举经典的生产者和消费者问题。【生产者和消费者问题】先看一段有问题的代码。复制代码代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}private String name = "Rollen";private int age = 20;}/*** 生产者* */class Producer implements Runnable{private Info info=null;Producer(Info info){this.info=info;}public void run(){boolean flag=false;for(int i=0;i<25;++i){if(flag){this.info.setName("Rollen");try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.info.setAge(20);flag=false;}else{this.info.setName("chunGe");try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.info.setAge(100);flag=true;}}}}/*** 消费者类* */class Consumer implements Runnable{private Info info=null;public Consumer(Info info){this.info=info;}public void run(){for(int i=0;i<25;++i){try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.info.getName()+"<---->"+this.info.getAge());}}}/*** 测试类* */class hello{public static void main(String[] args) {Info info=new Info();Producer pro=new Producer(info);Consumer con=new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【运行结果】:Rollen<---->100chunGe<---->20chunGe<---->100Rollen<---->100chunGe<---->20Rollen<---->100Rollen<---->100Rollen<---->100chunGe<---->20chunGe<---->20chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20大家可以从结果中看到,名字和年龄并没有对于。那么如何解决呢?<!--[if !supportLists]-->1) <!--[endif]-->加入同步<!--[if !supportLists]-->2) <!--[endif]-->加入等待和唤醒先来看看加入同步会是如何。
  • [技术干货] java多线程应用实现方法 2
    2关于选择继承Thread还是实现Runnable接口?其实Thread也是实现Runnable接口的:复制代码代码如下:class Thread implements Runnable {//…public void run() {if (target != null) {target.run();}}}其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。关于代理模式,我曾经写过一个小例子呵呵,大家有兴趣的话可以看一下:http://www.cnblogs.com/rollenholt/archive/2011/08/18/2144847.htmlThread和Runnable的区别:如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,不能资源共享* */class hello extends Thread {public void run() {for (int i = 0; i < 7; i++) {if (count > 0) {System.out.println("count= " + count--);}}}public static void main(String[] args) {hello h1 = new hello();hello h2 = new hello();hello h3 = new hello();h1.start();h2.start();h3.start();}private int count = 5;}【运行结果】:count= 5count= 4count= 3count= 2count= 1count= 5count= 4count= 3count= 2count= 1count= 5count= 4count= 3count= 2count= 1大家可以想象,如果这个是一个买票系统的话,如果count表示的是车票的数量的话,说明并没有实现资源的共享。我们换为Runnable接口:复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,不能资源共享* */class hello implements Runnable {public void run() {for (int i = 0; i < 7; i++) {if (count > 0) {System.out.println("count= " + count--);}}}public static void main(String[] args) {hello he=new hello();new Thread(he).start();}private int count = 5;}【运行结果】:count= 5count= 4count= 3count= 2count= 1总结一下吧:实现Runnable接口比继承Thread类所具有的优势:1):适合多个相同的程序代码的线程去处理同一个资源2):可以避免java中的单继承的限制3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。所以,本人建议大家劲量实现接口。复制代码代码如下:/*** @author Rollen-Holt* 取得线程的名称* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();new Thread(he,"A").start();new Thread(he,"B").start();new Thread(he).start();}}【运行结果】:AAABThread-0Thread-0Thread-0说明如果我们没有指定名字的话,系统自动提供名字。提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。判断线程是否启动复制代码代码如下:/*** @author Rollen-Holt 判断线程是否启动* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he);System.out.println("线程启动之前---》" + demo.isAlive());demo.start();System.out.println("线程启动之后---》" + demo.isAlive());}} 【运行结果】线程启动之前---》false线程启动之后---》trueThread-0Thread-0Thread-0主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。线程的强制执行:复制代码代码如下:/*** @author Rollen-Holt 线程的强制执行* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he,"线程");demo.start();for(int i=0;i<50;++i){if(i>10){try{demo.join(); //强制执行demo}catch (Exception e) {e.printStackTrace();}}System.out.println("main 线程执行-->"+i);}}}【运行的结果】:main 线程执行-->0main 线程执行-->1main 线程执行-->2main 线程执行-->3main 线程执行-->4main 线程执行-->5main 线程执行-->6main 线程执行-->7main 线程执行-->8main 线程执行-->9main 线程执行-->10线程线程线程main 线程执行-->11main 线程执行-->12main 线程执行-->13...线程的休眠:复制代码代码如下:/*** @author Rollen-Holt 线程的休眠* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {try {Thread.sleep(2000);} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + i);}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.start();}}【运行结果】:(结果每隔2s输出一个)线程0线程1线程2线程的中断:复制代码代码如下:/*** @author Rollen-Holt 线程的中断* */class hello implements Runnable {public void run() {System.out.println("执行run方法");try {Thread.sleep(10000);System.out.println("线程完成休眠");} catch (Exception e) {System.out.println("休眠被打断");return; //返回到程序的调用处}System.out.println("线程正常终止");}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.start();try{Thread.sleep(2000);}catch (Exception e) {e.printStackTrace();}demo.interrupt(); //2s后中断线程}}【运行结果】:执行run方法休眠被打断在java程序中,只要前台有一个线程在运行,整个java程序进程不会小时,所以此时可以设置一个后台线程,这样即使java进程小时了,此后台线程依然能够继续运行。复制代码代码如下:/*** @author Rollen-Holt 后台线程* */class hello implements Runnable {public void run() {while (true) {System.out.println(Thread.currentThread().getName() + "在运行");}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.setDaemon(true);demo.start();}}虽然有一个死循环,但是程序还是可以执行完的。因为在死循环中的线程操作已经设置为后台运行了。线程的优先级:复制代码代码如下:/*** @author Rollen-Holt 线程的优先级* */class hello implements Runnable {public void run() {for(int i=0;i<5;++i){System.out.println(Thread.currentThread().getName()+"运行"+i);}}public static void main(String[] args) {Thread h1=new Thread(new hello(),"A");Thread h2=new Thread(new hello(),"B");Thread h3=new Thread(new hello(),"C");h1.setPriority(8);h2.setPriority(2);h3.setPriority(6);h1.start();h2.start();h3.start();}}【运行结果】:A运行0A运行1A运行2A运行3A运行4B运行0C运行0C运行1C运行2C运行3C运行4B运行1B运行2B运行3B运行4。但是请读者不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源、另外,主线程的优先级是5.线程的礼让。在线程操作中,也可以使用yield()方法,将一个线程的操作暂时交给其他线程执行。复制代码代码如下:/*** @author Rollen-Holt 线程的优先级* */class hello implements Runnable {public void run() {for(int i=0;i<5;++i){System.out.println(Thread.currentThread().getName()+"运行"+i);if(i==3){System.out.println("线程的礼让");Thread.currentThread().yield();}}}public static void main(String[] args) {Thread h1=new Thread(new hello(),"A");Thread h2=new Thread(new hello(),"B");h1.start();h2.start();}}A运行0A运行1A运行2A运行3线程的礼让A运行4B运行0B运行1B运行2B运行3线程的礼让B运行4
  • [技术干货] java多线程应用实现方法
    这一篇文章主要关于java多线程,主要还是以例子来驱动的。因为讲解多线程的书籍和文章已经很多了,所以我也不好意思多说,呵呵、大家可以去参考一些那些书籍。我这个文章主要关于实际的一些问题。同时也算是我以后复习的资料吧,。呵呵大家多多指教。同时希望多结交一些技术上的朋友。谢谢。----------------------------------------------------------------------------------------------------------------------------------------------------java中的多线程在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。对于直接继承Thread的类来说,代码大致框架是:复制代码代码如下:class 类名 extends Thread{方法1;方法2;…public void run(){// other code…}属性1;属性2;…}先看一个简单的例子:复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,直接调用run方法* */class hello extends Thread {public hello() {}public hello(String name) {this.name = name;}public void run() {for (int i = 0; i < 5; i++) {System.out.println(name + "运行 " + i);}}public static void main(String[] args) {hello h1=new hello("A");hello h2=new hello("B");h1.run();h2.run();}private String name;}【运行结果】:A运行 0A运行 1A运行 2A运行 3A运行 4B运行 0B运行 1B运行 2B运行 3B运行 4我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。当我们把上面的主函数修改为如下所示的时候:复制代码代码如下:public static void main(String[] args) {hello h1=new hello("A");hello h2=new hello("B");h1.start();h2.start();}然后运行程序,输出的可能的结果如下:A运行 0B运行 0B运行 1B运行 2B运行 3B运行 4A运行 1A运行 2A运行 3A运行 4因为需要用到CPU的资源,所以每次的运行结果基本是都不一样的,呵呵。注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。那么:为什么我们不能直接调用run()方法呢?我的理解是:线程的运行需要本地操作系统的支持。如果你查看start的源代码的时候,会发现:复制代码代码如下:public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0 || this != me)throw new IllegalThreadStateException();group.add(this);start0();if (stopBeforeStart) {stop0(throwableFromStop);}}private native void start0();注意我用红色加粗的那一条语句,说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。通过实现Runnable接口:大致框架是:复制代码代码如下:class 类名 implements Runnable{方法1;方法2;…public void run(){// other code…}属性1;属性2;…}来先看一个小例子吧:复制代码代码如下:/*** @author Rollen-Holt 实现Runnable接口* */class hello implements Runnable {public hello() {}public hello(String name) {this.name = name;}public void run() {for (int i = 0; i < 5; i++) {System.out.println(name + "运行 " + i);}}public static void main(String[] args) {hello h1=new hello("线程A");Thread demo= new Thread(h1);hello h2=new hello("线程B");Thread demo1=new Thread(h2);demo.start();demo1.start();}private String name;}【可能的运行结果】:线程A运行 0线程B运行 0线程B运行 1线程B运行 2线程B运行 3线程B运行 4线程A运行 1线程A运行 2线程A运行 3线程A运行 4
  • [技术干货] Java - lambda 表达式入门
    概述Lambda表达式,也可称为闭包,是JDK8的新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),可以使代码变的更加简洁紧凑。Lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。名字起源是以前还没有计算机时,逻辑学家Alonzo Church想要形式化的表示能有效计算的数学函数,使用了希腊字母lambda(λ \lambdaλ)来标记参数,从那以后,带参数变量的表达式就被称为lambda表达式。lambda表达式本质是一个匿名函数,比如以下函数public int add(int x, int y) { return x + y; }可以转换为:(int x, int y) -> x + y;语法语法格式如下(parameters) -> expression或(parameters) ->{ statements; }其中()用来描述参数列表,{}用来描述方法体,->是lambda运算符,读作goes to。可以包含显示的return语句,如:(String sirst,String second)-> { if(first.length()<second.length())return -1; else if(first.length()>second.length())return 1; else return 0; }可以没有参数,但()不可缺省:()->{for(int i=0;i<10;i++)System.out.print(i);}如果可以推导出参数类型,则可以忽略其类型:Comparator<String>cmp=(first,second)->first.length()-second.length();特别注意不能只在某些分支返回一个值,这是不合法的,如:(int x)->{if(x>=0)return 1;}常用示例:ArrayList<Integer>list=new ArrayList<>(); Collections.addAll(list,1,2,3,4,5); //遍历 list.forEach(e->{System.out.println(e);}); //删除指定值 list.removeIf(e->e==3); //排序 list.sort((o1,o2)->o2-o1); //遍历(双冒号操作符) list.forEach(System.out::println);函数式接口Java中又很多封装代码块的接口,如ActionListener、Comparator等,lambda表达式与这些接口时兼容的。对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。比如Arrays.sort()方法,它的第二个参数需要一个Comparator实例,而Comparator就是只有一个方法的接口,所以可以使用lambda表达式替代,可以把lambda表达式看作一个函数,而不是一个对象,如:Arrays.sort(arrays, (first,second)->first.length()-second.length());lambda表达式还可以转换为接口,比如实现Runnable接口:new Thread(() -> System.out.println("记得一键三连")).start(); Runnable r = () -> System.out.println("(。・∀・)ノ"); r.run();再如之前提到的removeIf()方法,它的参数就是一个Predicate接口(位于java.util.function包),这个接口专门用来传递lambda表达式,如删除一个数组列表所有null值:list.removeIf(e->e==null)方法引用当在Lambda表达式中直接调用了一个方法时可以使用,其写法为目标引用::方法名称。有时候,可能已经有现成的方法可以完成你想要传递到其他代码的某个动作,如遍历打印集合:list.forEach(e->{System.out.println(e);});我们可以直接把现成的println方法传递给它:list.forEach(System.out::println);它们是等价的,是一个方法引用的写法。再如对字符串排序而不考虑大小写,可以直接传递以下方法表达式:Arrays.sort(strings,String::compareToIgnoreCase);也就是说使用双冒号操作符::来分离方法名与对象或类名:object::instanceMethodClass::staticMethodClass::instanceMethod对于前两种情况,方法引用等价于提供方法参数的lambda表达式,如Math::Pow 等价于 (x,y) -> Math.pos(x,y) 对于第三种情况,第一个参数会成为方法的目标,如String::compareToIgnoreCase 等同于 (x,y) -> x.compareToIgnoreCase(y) 也可以在方法中引用this参数,如this::equals等价于x->this.equals(x),同样的,使用super也是允许的。构造器引用构造器引用与方法引用很类似,只不过方法名为new,例如Person::new是Person类构造器的一个引用,如果有多个构造器,编译器会取决于上下文。比如有一个字符串列表,可以把他转换为一个Person对象数组,为此要在各个字符串上调用构造器。可以用数组类型建立构造器引用,如int[]::new是一个构造器引用,它有一个参数即数组长度,等价于lambda表达式:x->new int[x];在Java中无法构造泛型类型T的数组,而数组构造器引用就可克服这个限制。如表达式new T[n]会产生错误,因为这会改为new Object[n]。设我们需要一个Person对象数组,Stream接口有一个toArray方法可以返回Object数组:Object[] people=stream.toArray();以上得到的是一个Object引用数组,可以把Person[]::new传入给toArray()方法,从而得到一个Person对象数组:Object[] people=stream.toArray(Person[]::new);变量作用域有时候,我们希望能够在lambda表达式中访问外围方法或类中的变量,如下面例子:public static void printTip(String text) { Runnable r = () -> System.out.println(text); r.run(); } public static void main(String[] args) { String text="一键三连"; printTip(text); }现在来看lambda表达式中的text变量,它并不是在这个lambda表达式中定义的,实际上是printTip方法的一个参数变量。但仔细想想,lambda表达式的代码可能会在printTip调用返回很久以后才运行,而那时这个参数变量已经不存在了,如何保留text变量?首先巩固瞎lambda表达式的理解,lmabda表达式有3个部分:一个代码块、变量、自由变量的值(指非参数而且不在代码中定义的变量)。在上面的例子中,lambda表达式有一个自由变量text,表示lambda表达式的数据结构必须存储的值(这里的字符串“一键三连”),我们称它被lambda表达式捕获(captured)。关于代码块和自由变量值有一个术语:闭包(closure),在Java中,lambda表达式就是闭包。lambda表达式捕获变量必须遵循的规则:捕获的变量必须实际上必须是最终变量(effectively final),最终变量是指这个变量初始化之后就不会再为它赋新值,即在lambda表达式内外都不能在修改值。如lambda内修改:public static void printTip(String text) { Runnable r = () -> { System.out.println(text); text="修改值会报错"; }; r.run(); } public static void main(String[] args) { String text="一键三连"; printTip(text); }lambda外修改:public static void main(String[] args) { for(int i=0;i<5;i++){ //表达式外修改变量i报错 Runnable r = () -> System.out.println(i); r.run(); } }此外,在方法中不能有同名的局部变量,lambda表达式也是如此:public static void main(String[] args) { int first=666; //同名会报错 Comparator<String> cmp= (first,second)->first.length()-second.length(); }对于lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法饿this参数。处理lambda表达式使用lambda表达式的重点是延迟执行(deferred execution)。如果要立即执行代码的画完全可以直接执行而无需放到一个lambda表达式中,之所以希望以后在执行代码,这有很多原因,如:在一个单独的线程中运行代码多次运行代码在算法的适当位置运行代码发生某种情况时执行代码只有在必要时才运行代码设想要重复一个动作n次,将这个动作和重复次数,传递到一个repeat方法,要接受这个lambda表达式需要选择一个函数式接口,在这里我们可以使用Runnable接口,后面给出Java API中提供的最重要的函数式接口。public static void repeat(int n,Runnable action){ for(int i=0;i<n;i++) action.run(); } public static void main(String[] args) { repeat(10,()-> System.out.println("一键三连")); } 函数式接口 参数类型 返回类型 抽象方法名 描述 其他方法 ---------------------------------------------------------------------------------------- Runnable 无 void run 作为无参数或返回值的动作执行 Supplier<T> 无 T get 提供一个T类型的值 Consumer<T> T void accept 处理一个T类型的值 addThen BiConsumer<T,U> T,U void accept 处理T和U类型的值 addThen Function<T,R> T R apply 有一个T类型参数的函数 compose,addThen,idenity BiFunction<T,U,R> T,U R apply 有T和U类型参数的函数 addThen UnaryOperator<T> T T apply 类型T上的一元操作符 compose,addThen,identity BinaryOperator<T> T,T T apply 类型T上的二元操作符 addThen,maxBy,minBy PreDicate<T> T boolean test 布尔值函数 add,or,negate,isEqual BiPredicate T,U boolean test 有两个参数的布尔值函数 add,or,negate现在让这个例子更复杂一些,我们希望知道这个动作出现在哪一次迭代中。为此需要选择一个合适的函数式接口,其中要包含一个方法,这个方法有一个int参数而且返回类型为void,如下所示:public interface IntConsumer{ void accept(int value); } public static void repeat(int n,IntConsumer action){ for(int i=0;i<n;i++) action.accept(i); } public static void main(String[] args) { repeat(10,i-> System.out.println(9-i)); }下表列出了基本类型int、long和double的34个可能的规范,最好使用这些特殊化规范来减少自动装箱:函数式接口 参数类型 返回类型 抽象方法名 ---------------------------------------------------------- BooleanSupplier none boolean getAsBoolean P PPSupplier none p pp getAsP PP P PPConsumer p pp void accept ObjP PPConsumer<T> T,p pp void accept P PPFunction<T> p pp T apply P PPToQFunction p pp q qq applyAsQ QQ ToP PPFunction<T> T p pp applyAsP PP ToP PPBiFunction<T,U> T,U p pp applyAsP PP P PPUnaryOperator p pp p pp applyAsP PP P PPBinaryOperator p pp,p pp p pp applyAsP PP P PPPredicate p pp boolean testp pp,q qq为int,long,double;P PP,Q QQ为Int,Long,Double。————————————————
  • [技术干货] String StringBuffer StringBuilder 的区别
    1、String  String:字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。  String类的包含如下定义:[java] view plaincopy /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count;   用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。 2、StringBuffer(JDK1.0)  StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。  Java.lang.StringBuffer线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。  StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。3、StringBuilder(JDK5.0)  StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。  java.lang.StringBuilder是一个可变的字符序列,是JDK5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。  其构造方法如下:构造方法描述StringBuilder()创建一个容量为16的StringBuilder对象(16个空元素)StringBuilder(CharSequence cs)创建一个包含cs的StringBuilder对象,末尾附加16个空元素StringBuilder(int initCapacity)创建一个容量为initCapacity的StringBuilder对象StringBuilder(String s)创建一个包含s的StringBuilder对象,末尾附加16个空元素  在大部分情况下,StringBuilder > StringBuffer。这主要是由于前者不需要考虑线程安全。4、三者区别  String 类型和StringBuffer的主要性能区别:String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。  使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。  在某些特别情况下, String 对象的字符串拼接其实是被 Java Compiler 编译成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,例如:[java] view plaincopy String s1 = “This is only a” + “ simple” + “ test”; StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);   生成 String s1对象的速度并不比 StringBuffer慢。其实在Java Compiler里,自动做了如下转换:  Java Compiler直接把上述第一条语句编译为:  [java] view plaincopy String s1 = “This is only a simple test”;   所以速度很快。但要注意的是,如果拼接的字符串来自另外的String对象的话,Java Compiler就不会自动转换了,速度也就没那么快了,例如:[java] view plaincopy String s2 = “This is only a”; String s3 = “ simple”; String s4 = “ test”; String s1 = s2 + s3 + s4;   这时候,Java Compiler会规规矩矩的按照原来的方式去做,String的concatenation(即+)操作利用了StringBuilder(或StringBuffer)的append方法实现,此时,对于上述情况,若s2,s3,s4采用String定义,拼接时需要额外创建一个StringBuffer(或StringBuilder),之后将StringBuffer转换为String;若采用StringBuffer(或StringBuilder),则不需额外创建StringBuffer。5、使用策略(1)基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。(2)不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。例如:[java] view plaincopy String result = ""; for (String s : hugeArray) { result = result + s; } // 使用StringBuilder StringBuilder sb = new StringBuilder(); for (String s : hugeArray) { sb.append(s); } String result = sb.toString();   当出现上面的情况时,显然我们要采用第二种方法,因为第一种方法,每次循环都会创建一个String result用于保存结果,除此之外二者基本相同(对于jdk1.5及之后版本)。  (3)为了获得更好的性能,在构造 StirngBuffer 或 StirngBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。(4)StringBuilder一般使用在方法内部来完成类似"+"功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中。(5)相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer。————————————————
  • [全栈开发者] 【Java编程创造营】Java相关微认证打卡贴
    为了巩固大家的学习成果,紧跟学习进度请将需要完成的相关微认证考核按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~征集时间课程全阶段微认证考核要求提交微认证证书截图 阶段考核+30分通过特定场景化学习和实际操作,可以获得华为云微认证提供的具有【华为云官方微认证证书】Java编程创造营使用以下场景参与认证考核华为企业级JAVA编程规范认证链接:https://edu.huaweicloud.com/certifications/9eeea7c524514eb58ebc169186813cf4参与认证的同学请将【微认证订单截图&微认证证书截图+华为云ID】回复到本帖当中 才能算作最终考核成功,考核分数才能统计回帖示例:活动注意事项1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成微认证任务,可获得的积分上限为30分;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
  • [技术干货] Java 常见经典问答题
    1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。2.JDK和JRE的区别是什么?Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。3.”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。4.是否可以在static环境中访问非static变量?static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。5.Java支持的数据类型有哪些?什么是自动拆装箱?Java语言支持的8种基本数据类型是:byteshortintlongfloatdoublebooleanchar自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成Double,等等。反之就是自动拆箱。6.Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。7.Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。8.Java支持多继承么?Java中类不支持多继承,只支持单继承(即一个类只有一个父类)。 但是java中的接口支持多继承,,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。9.接口和抽象类的区别是什么?Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。类可以实现很多个接口,但是只能继承一个抽象类类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。抽象类可以在不提供接口方法实现的情况下实现接口。Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。也可以参考JDK8中抽象类和接口的区别10.什么是值传递和引用传递?值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.一般认为,java内的传递都是值传递.11.进程和线程的区别是什么?进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。12.创建线程有几种不同的方式?你喜欢哪一种?为什么?有三种方式可以用来创建线程:继承Thread类实现Runnable接口应用程序可以使用Executor框架来创建线程池实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。13.概括的解释下线程的几种可用状态。1. 新建( new ):新创建了一个线程对象。2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:(一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。(二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。(三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。14.同步方法和同步代码块的区别是什么?区别:同步方法默认用this或者当前类class对象作为锁;同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;15.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。16.什么是死锁(deadlock)?两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。17.如何确保N个线程可以访问N个资源同时又不导致死锁?使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。18.Java集合类框架的基本接口有哪些?集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:Collection:代表一组对象,每一个对象都是它的子元素。Set:不包含重复元素的Collection。List:有顺序的collection,并且可以包含重复元素。Map:可以把键(key)映射到值(value)的对象,键不能重复。19.为什么集合类没有实现Cloneable和Serializable接口?克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。20.什么是迭代器(Iterator)?Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的remove()方法删除。21.Iterator和ListIterator的区别是什么?下面列出了他们的区别:Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。23.Java中的HashMap的工作原理是什么?Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。24.hashCode()和equals()方法的重要性体现在什么地方?Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。25.HashMap和Hashtable有什么区别?HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:HashMap允许键和值是null,而Hashtable不允许键或者值是null。Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。一般认为Hashtable是一个遗留的类。26.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?下面列出了Array和ArrayList的不同点:Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。Array大小是固定的,ArrayList的大小是动态变化的。ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。数组适合于存储元素个数不变的数据,如果元素的个数是变化的,用ArrayList更好27.ArrayList和LinkedList有什么区别?ArrayList和LinkedList都实现了List接口,他们有以下的不同点:ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。28.Comparable和Comparator接口是干什么的?列出它们的区别。Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明已经存在的对象小于,等于,大于输入对象。Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。29.什么是Java优先级队列(Priority Queue)?PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。30.你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。31.如何权衡是使用无序的数组还是有序的数组?有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。32.Java集合类框架的最佳实践有哪些?根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。编程的时候接口优于实现。底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。33.Enumeration接口和Iterator接口的区别有哪些?Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。34.HashSet和TreeSet有什么区别?HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。35.Java中垃圾回收有什么目的?什么时候进行垃圾回收?垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。36.System.gc()和Runtime.gc()会做什么事情?这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。37.finalize()方法什么时候被调用?垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法 但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。 那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。38.如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?不会,在下一个垃圾回收周期中,这个对象将是可被回收的。39.Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)?JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。永久代是用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类,永久代中一般包含:类的方法(字节码…)类名(Sring对象).class文件读到的常量信息class对象相关的对象列表和类型列表 (e.g., 方法对象的array).JVM创建的内部对象JIT编译器优化用的信息40.串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。41.在Java中,对象什么时候可以被垃圾回收?当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。42.JVM的永久代中会发生垃圾回收么?垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。43.Java中的两种异常类型是什么?他们有什么区别?Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。44.Java中Exception和Error有什么区别?Exception和Error都是Throwable的子类。Exception用于用户程序可以捕获的异常情况。Error定义了不期望被用户程序捕获的异常。45.throw和throws有什么区别?throw关键字用来在程序中明确的抛出异常,相反,throws语句用来表明方法不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。46.异常处理完成以后,Exception对象会发生什么变化?Exception对象会在下一个垃圾回收过程中被回收掉。47.finally代码块和finalize()方法有什么区别?无论是否抛出异常,finally代码块都会执行,它主要是用来释放应用占用的资源。finalize()方法是Object类的一个protected方法,它是在对象被垃圾回收之前由Java虚拟机来调用的。48.什么是Applet?java applet是能够被包含在HTML页面中并且能被启用了java的客户端浏览器执行的程序。Applet主要用来创建动态交互的web应用程序。49.解释一下Applet的生命周期applet可以经历下面的状态:Init:每次被载入的时候都会被初始化。Start:开始执行applet。Stop:结束执行applet。Destroy:卸载applet之前,做最后的清理工作。50.当applet被载入的时候会发生什么?首先,创建applet控制类的实例,然后初始化applet,最后开始运行。51.Applet和普通的Java应用程序有什么区别?applet是运行在启用了java的浏览器中,Java应用程序是可以在浏览器之外运行的独立的Java程序。但是,它们都需要有Java虚拟机。进一步来说,Java应用程序需要一个有特定方法签名的main函数来开始执行。Java applet不需要这样的函数来开始执行。最后,Java applet一般会使用很严格的安全策略,Java应用一般使用比较宽松的安全策略。52.Java applet有哪些限制条件?主要是由于安全的原因,给applet施加了以下的限制:applet不能够载入类库或者定义本地方法。applet不能在宿主机上读写文件。applet不能读取特定的系统属性。applet不能发起网络连接,除非是跟宿主机。applet不能够开启宿主机上其他任何的程序。53.什么是不受信任的applet?不受信任的applet是不能访问或是执行本地系统文件的Java applet,默认情况下,所有下载的applet都是不受信任的。54.从网络上加载的applet和从本地文件系统加载的applet有什么区别?当applet是从网络上加载的时候,applet是由applet类加载器载入的,它受applet安全管理器的限制。当applet是从客户端的本地磁盘载入的时候,applet是由文件系统加载器载入的。从文件系统载入的applet允许在客户端读文件,写文件,加载类库,并且也允许执行其他程序,但是,却通不过字节码校验。55.applet类加载器是什么?它会做哪些工作?当applet是从网络上加载的时候,它是由applet类加载器载入的。类加载器有自己的java名称空间等级结构。类加载器会保证来自文件系统的类有唯一的名称空间,来自网络资源的类有唯一的名称空间。当浏览器通过网络载入applet的时候,applet的类被放置于和applet的源相关联的私有的名称空间中。然后,那些被类加载器载入进来的类都是通过了验证器验证的。验证器会检查类文件格式是否遵守Java语言规范,确保不会出现堆栈溢出(stack overflow)或者下溢(underflow),传递给字节码指令的参数是正确的。56.applet安全管理器是什么?它会做哪些工作?applet安全管理器是给applet施加限制条件的一种机制。浏览器可以只有一个安全管理器。安全管理器在启动的时候被创建,之后不能被替换覆盖或者是扩展。57.什么是JDBC?JDBC是允许用户在不同数据库之间做选择的一个抽象层。JDBC允许开发者用JAVA写数据库应用程序,而不需要关心底层特定数据库的细节。58.解释下驱动(Driver)在JDBC中的角色。JDBC驱动提供了特定厂商对JDBC API接口类的实现,驱动必须要提供java.sql包下面这些类的实现:Connection, Statement, PreparedStatement,CallableStatement, ResultSet和Driver。59.Class.forName()方法有什么作用?初始化参数指定的类,并且返回此类对应的Class 对象60.PreparedStatement比Statement有什么优势?PreparedStatements是预编译的,因此,性能会更好。同时,不同的查询参数值,PreparedStatement可以重用。61.什么时候使用CallableStatement?用来准备CallableStatement的方法是什么?CallableStatement用来执行存储过程。存储过程是由数据库存储和提供的。存储过程可以接受输入参数,也可以有返回结果。非常鼓励使用存储过程,因为它提供了安全性和模块化。准备一个CallableStatement的方法是:CallableStament.prepareCall();62.数据库连接池是什么意思?像打开关闭数据库连接这种和数据库的交互可能是很费时的,尤其是当客户端数量增加的时候,会消耗大量的资源,成本是非常高的。可以在应用服务器启动的时候建立很多个数据库连接并维护在一个池中。连接请求由池中的连接提供。在连接使用完毕以后,把连接归还到池中,以用于满足将来更多的请求。63.什么是RMI?Java远程方法调用(Java RMI)是Java API对远程过程调用(RPC)提供的面向对象的等价形式,支持直接传输序列化的Java对象和分布式垃圾回收。远程方法调用可以看做是激活远程正在运行的对象上的方法的步骤。RMI对调用者是位置透明的,因为调用者感觉方法是执行在本地运行的对象上的。64.RMI体系结构的基本原则是什么?RMI体系结构是基于一个非常重要的行为定义和行为实现相分离的原则。RMI允许定义行为的代码和实现行为的代码相分离,并且运行在不同的JVM上。65.RMI体系结构分哪几层?RMI体系结构分以下几层:存根和骨架层(Stub and Skeleton layer):这一层对程序员是透明的,它主要负责拦截客户端发出的方法调用请求,然后把请求重定向给远程的RMI服务。远程引用层(Remote Reference Layer):RMI体系结构的第二层用来解析客户端对服务端远程对象的引用。这一层解析并管理客户端对服务端远程对象的引用。连接是点到点的。传输层(Transport layer):这一层负责连接参与服务的两个JVM。这一层是建立在网络上机器间的TCP/IP连接之上的。它提供了基本的连接服务,还有一些防火墙穿透策略。66.RMI中的远程接口(Remote Interface)扮演了什么样的角色?远程接口用来标识哪些方法是可以被非本地虚拟机调用的接口。远程对象必须要直接或者是间接实现远程接口。实现了远程接口的类应该声明被实现的远程接口,给每一个远程对象定义构造函数,给所有远程接口的方法提供实现。67.java.rmi.Naming类扮演了什么样的角色?java.rmi.Naming类用来存储和获取在远程对象注册表里面的远程对象的引用。Naming类的每一个方法接收一个URL格式的String对象作为它的参数。68.RMI的绑定(Binding)是什么意思?绑定是为了查询找远程对象而给远程对象关联或者是注册以后会用到的名称的过程。远程对象可以使用Naming类的bind()或者rebind()方法跟名称相关联。69.Naming类的bind()和rebind()方法有什么区别?bind()方法负责把指定名称绑定给远程对象,rebind()方法负责把指定名称重新绑定到一个新的远程对象。如果那个名称已经绑定过了,先前的绑定会被替换掉。70.让RMI程序能正确运行有哪些步骤?为了让RMI程序能正确运行必须要包含以下几个步骤:编译所有的源文件。使用rmic生成stub。启动rmiregistry。启动RMI服务器。运行客户端程序。71.RMI的stub扮演了什么样的角色?远程对象的stub扮演了远程对象的代表或者代理的角色。调用者在本地stub上调用方法,它负责在远程对象上执行方法。当stub的方法被调用的时候,会经历以下几个步骤:初始化到包含了远程对象的JVM的连接。序列化参数到远程的JVM。等待方法调用和执行的结果。反序列化返回的值或者是方法没有执行成功情况下的异常。把值返回给调用者。72.什么是分布式垃圾回收(DGC)?它是如何工作的?DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。73.RMI中使用RMI安全管理器(RMISecurityManager)的目的是什么?RMISecurityManager使用下载好的代码提供可被RMI应用程序使用的安全管理器。如果没有设置安全管理器,RMI的类加载器就不会从远程下载任何的类。74.解释下Marshalling和demarshalling。当应用程序希望把内存对象跨网络传递到另一台主机或者是持久化到存储的时候,就必须要把对象在内存里面的表示转化成合适的格式。这个过程就叫做Marshalling,反之就是demarshalling。75.解释下Serialization和Deserialization。Java提供了一种叫做对象序列化的机制,他把对象表示成一连串的字节,里面包含了对象的数据,对象的类型信息,对象内部的数据的类型信息等等。因此,序列化可以看成是为了把对象存储在磁盘上或者是从磁盘上读出来并重建对象而把对象扁平化的一种方式。反序列化是把对象从扁平状态转化成活动对象的相反的步骤。76.什么是Servlet?Servlet是用来处理客户端请求并产生动态网页内容的Java类。Servlet主要是用来处理或者是存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息。77.说一下Servlet的体系结构。所有的Servlet都必须要实现的核心的接口是javax.servlet.Servlet。每一个Servlet都必须要直接或者是间接实现这个接口,或者是继承javax.servlet.GenericServlet或者javax.servlet.http.HTTPServlet。最后,Servlet使用多线程可以并行的为多个请求服务。78.Applet和Servlet有什么区别?Applet是运行在客户端主机的浏览器上的客户端Java程序。而Servlet是运行在web服务器上的服务端的组件。applet可以使用用户界面类,而Servlet没有用户界面,相反,Servlet是等待客户端的HTTP请求,然后为请求产生响应。79.GenericServlet和HttpServlet有什么区别?GenericServlet是一个通用的协议无关的Servlet,它实现了Servlet和ServletConfig接口。继承自GenericServlet的Servlet应该要覆盖service()方法。最后,为了开发一个能用在网页上服务于使用HTTP协议请求的Servlet,你的Servlet必须要继承自HttpServlet。80.解释下Servlet的生命周期。对每一个客户端的请求,Servlet引擎载入Servlet,调用它的init()方法,完成Servlet的初始化。然后,Servlet对象通过为每一个请求单独调用service()方法来处理所有随后来自客户端的请求,最后,调用Servlet的destroy()方法把Servlet销毁。81.doGet()方法和doPost()方法有什么区别?doGet:GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。doPOST:POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。82.什么是Web应用程序?Web应用程序是对Web或者是应用服务器的动态扩展。有两种类型的Web应用:面向表现的和面向服务的。面向表现的Web应用程序会产生包含了很多种标记语言和动态内容的交互的web页面作为对请求的响应。而面向服务的Web应用实现了Web服务的端点(endpoint)。一般来说,一个Web应用可以看成是一组安装在服务器URL名称空间的特定子集下面的Servlet的集合。83.什么是服务端包含(Server Side Include)?服务端包含(SSI)是一种简单的解释型服务端脚本语言,大多数时候仅用在Web上,用servlet标签嵌入进来。SSI最常用的场景把一个或多个文件包含到Web服务器的一个Web页面中。当浏览器访问Web页面的时候,Web服务器会用对应的servlet产生的文本来替换Web页面中的servlet标签。84.什么是Servlet链(Servlet Chaining)?Servlet链是把一个Servlet的输出发送给另一个Servlet的方法。第二个Servlet的输出可以发送给第三个Servlet,依次类推。链条上最后一个Servlet负责把响应发送给客户端。85**.如何知道是哪一个客户端的机器正在请求你的Servlet?**ServletRequest类可以找出客户端机器的IP地址或者是主机名。getRemoteAddr()方法获取客户端主机的IP地址,getRemoteHost()可以获取主机名86.什么是cookie?session和cookie有什么区别?cookie是Web服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个Web服务器存储cookie。以后浏览器在给特定的Web服务器发请求的时候,同时会发送所有为该服务器存储的cookie。下面列出了session和cookie的区别:无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie,但是,session仍然是能够工作的,因为客户端无法禁用服务端的session。在存储的数据量方面session和cookies也是不一样的。session能够存储任意的Java对象,cookie只能存储String类型的对象。87.什么是HTTP隧道?HTTP隧道是一种利用HTTP或者是HTTPS把多种网络协议封装起来进行通信的技术。因此,HTTP协议扮演了一个打通用于通信的网络协议的管道的包装器的角色。把其他协议的请求掩盖成HTTP的请求就是HTTP隧道。88.sendRedirect()和forward()方法有什么区别?sendRedirect()方法会创建一个新的请求,而forward()方法只是把请求转发到一个新的目标上。重定向(redirect)以后,之前请求作用域范围以内的对象就失效了,因为会产生一个新的请求,而转发(forwarding)以后,之前请求作用域范围以内的对象还是能访问的。一般认为sendRedirect()比forward()要慢。89.JSP请求是如何被处理的?浏览器首先要请求一个以.jsp扩展名结尾的页面,发起JSP请求,然后,Web服务器读取这个请求,使用JSP编译器把JSP页面转化成一个Servlet类。需要注意的是,只有当第一次请求页面或者是JSP文件发生改变的时候JSP文件才会被编译,然后服务器调用servlet类,处理浏览器的请求。一旦请求执行结束,servlet会把响应发送给客户端。这里看下如何在JSP中获取请求参数。90.什么是JSP指令(Directive)?JSP中有哪些不同类型的指令?Directive是当JSP页面被编译成Servlet的时候,JSP引擎要处理的指令。Directive用来设置页面级别的指令,从外部文件插入数据,指定自定义的标签库。Directive是定义在 <%@ 和 %>之间的。下面列出了不同类型的Directive:包含指令(Include directive):用来包含文件和合并文件内容到当前的页面。页面指令(Page directive):用来定义JSP页面中特定的属性,比如错误页面和缓冲区。Taglib指令: 用来声明页面中使用的自定义的标签库。91.什么是JSP动作(JSP action)?JSP动作以XML语法的结构来控制Servlet引擎的行为。当JSP页面被请求的时候,JSP动作会被执行。它们可以被动态的插入到文件中,重用JavaBean组件,转发用户到其他的页面,或者是给Java插件产生HTML代码。下面列出了可用的动作:jsp:include-当JSP页面被请求的时候包含一个文件。jsp:useBean-找出或者是初始化Javabean。jsp:setProperty-设置JavaBean的属性。jsp:getProperty-获取JavaBean的属性。jsp:forward-把请求转发到新的页面。jsp:plugin-产生特定浏览器的代码。92.什么是Scriptlets?JSP技术中,scriptlet是嵌入在JSP页面中的一段Java代码。scriptlet是位于标签内部的所有的东西,在标签与标签之间,用户可以添加任意有效的scriplet。93**对于JSP,隐含对象是什么意思?有哪些隐含对象?**JSP隐含对象是页面中的一些Java对象,JSP容器让这些Java对象可以为开发者所使用。开发者不用明确的声明就可以直接使用他们。JSP隐含对象也叫做预定义变量。下面列出了JSP页面中的隐含对象:applicationpagerequestresponsesessionexceptionoutconfigpageContext————————————————
  • [技术干货] java开发必备三大修饰符之final
    三、final 最终的 修饰变量 局部变量:final修饰的局部变量只能赋值一次 值一旦赋值不可以改变常量的定义:    public static final double PI = 3.14159265457;案例:public class Demo{   public static void main(String[] args) {       int a = 20;//局部变量        final int b = 10;        a++;       b++;       System.out.println(a);       System.out.println(b);            }   }public class Demo{   public static void main(String[] args) {       final int a ;//声明       a = 10;        a = 20;//报错        System.out.println(a);            }   }实例变量    特点:   1.final修饰的实例变量没有默认值   2.final修饰的实例变量只能赋值一次    3.final修饰的实例变量可以在构造方法中初始化 但是要保证每个构造都必须能够为这个变量初始化案例:public class Demo{    public static void main(String[] args) {       Animal a = new Animal(250);       //System.out.println(a.a);//0        System.out.println(a.b);                              }  }class Animal{    /*int a = 10;//实例变量   final int b = 20;*/    int a;   final int b = 20;    public  Animal(int b) {       this.b = b;   }  }public class Demo{   public static void main(String[] args) {       Animal a = new Animal(250);       //System.out.println(a.a);//0        System.out.println(a.b);                              }   }class Animal{   /*int a = 10;//实例变量    final int b = 20;*/    int a;    final int b ;   public  Animal(int b){        this.b = b;    }    public  Animal() {       b = 30;    }  }类变量特点:   1.final修饰的类变量没有默认值    2.final修饰的类变量可以通过静态代码块进行初始化案例:public class Demo{    public static void main(String[] args) {        System.out.println(Animal.a);        System.out.println(Animal.b);                               }   }class Animal{    static int a ;    final static int b ;    static{        b = 250;    }          }  修饰方法成员方法:特点:    1.final修饰的成员方法修饰类 语法:final class Animal{}特点:final修饰的类没有子类 俗称断子绝孙类jdk的类库中有哪些final修饰的类?String  Math  System案例:public class Demo{    public static void main(String[] args) {             }   }class Dog extends System{     }  
  • [技术干货] java开发必备三大修饰符之static
    二、static 静态的  static修饰成员变量:类变量 静态变量 静态属性定义语法:访问修饰符  static   数据类型  变量名 = 变量值;   static   访问修饰符     数据类型  变量名 = 变量值;访问的方式(特点)   1.类变量可以用 类名.属性名 访问    2.可以通过创建对象 用引用去访问 (不推荐)案例:public class Demo{   public static void main(String[] args) {       Test t1 = new Test();            Test t2 = new Test();        t1.a++;       t1.b++;       System.out.println(t2.a);//10             System.out.println(t2.b);//21                     }      }class Test{  int a = 10;//实例变量   static int b = 20;//类变量  }案例2:public class Demo{    public static void main(String[] args) {       /*Test t1 = new Test();       Test t2 = new Test();       t1.a++;       t1.b++;       System.out.println(t2.a);       System.out.println(t2.b);*/       System.out.println(Test.b);//20      }      }class Test{   int a = 10;//实例变量   static int b = 20;//类变量  }  static修饰成员方法:静态方法语法:   访问修饰符  static  返回值类型  方法名(形参列表){       方法的实现;    }    static 访问修饰符    返回值类型  方法名(形参列表){       方法的实现;    }特点:   1.静态的方法中 不可以直接访问非静态的成员(成员变量 和  成员方法)    2.如果要访问非静态的成员  必须创建对象 通过引用取访问   3.静态方法可以通过 类名.方法名() 访问  也可以通过引用去访问(不建议)    4.静态的方法可以被继承   静态的方法不能被非静态的方法所覆盖 只能被静态方法覆盖  但是没有多态(引用是什么类型 调用的方法就是这个类型中的方法)   5.在静态方法中是不可以使用 this 和 super 关键字  因为 this 和 super都是和对象有关的  而静态的成员和对象无关 先于对象存在案例:关于静态方法 访问非静态的成员public class Demo{    public static void main(String[] args) {       /*Test t1 = new Test();       Test t2 = new Test();       t1.a++;       t1.b++;       System.out.println(t2.a);       System.out.println(t2.b);*/       //System.out.println(Test.b);//20                              }      }class Test{    int a = 10;//实例变量    static int b
  • [技术干货] java开发必备修饰符之抽象类
    一、abstract 抽象的抽象类:被abstract 修饰的类语法:abstract class  类名{}抽象方法 :被abstract 修饰的方法定义语法:   访问修饰符  abstract 返回值类型 方法名(参数列表);   abstract 访问修饰符   返回值类型 方法名(参数列表);特点:    1.当一个类中存在抽象方法 那么这个类也必须是抽象的    2.一个抽象类中可以有抽象方法也可以有非抽象方法    3.如果一个类继承了抽象类,这个类如果不想成为抽象类,那么这个类必须实现抽象类中的所有抽象方法    4.抽象类不能创建对象,但是可以声明引用,抽象类的对象是存在的,是由子类创建子类对象时调用父类构造方法创建的,是无法自己手动去new 抽象类的对象的抽象类的好处:强制使用多态案例:public class Demo{    public static void main(String[] args) {               }     }abstract class Animal{   //抽象方法    public abstract void eat();   public abstract  void sleep();    public void m1() {       System.out.println("m1");             }}class Dog extends Animal{   public  void eat(){        System.out.println("狗吃屎");            }    public  void sleep(){        System.out.println("狗睡");   }}案例:public class Demo{   public static void main(String[] args) {       Animal a = new Dog();     }     }abstract class Animal{   public  Animal() {       System.out.println("动物类的构造被调用 创建了 对象");            }              //抽象方法    abstract public  void eat();   public abstract  void sleep();    public void m1() {       System.out.println("m1");            }}class Dog extends Animal{    public  void eat(){        System.out.println("狗吃屎");            }   public  void sleep(){       System.out.println("狗睡");    }}  
  • [技术干货] Redis 支持的 Java 客户端
    支持的 Java 客户端有 Redisson、jedis、lettuce 等。
总条数:2301 到第 页
上滑加载中