spring框架中@PostConstruct的实现原理



原文地址:https://www.jianshu.com/p/dee681eb3e86

在spring项目经常遇到@PostConstruct注解,首先介绍一下它的用途: 被注解的方法,在对象加载完依赖注入后执行。

此注解是在Java EE5规范中加入的,在Servlet生命周期中有一定作用,它通常都是一些初始化的操作,但初始化可能依赖于注入的其他组件,所以要等依赖全部加载完再执行。与之对应的还有@PreDestroy,在对象消亡之前执行,原理差不多,这里不做过多介绍。

那么首先看下源码注释

 

PostConstruct注释介绍

总体概括如上,注意其中几个点

1. 要在依赖加载后,对象使用前执行,而且只执行一次,原因在上面已经说了。

2. 所有支持依赖注入的类都要支持此方法。首先,我们可以看到这个注解是在javax.annotation包下的,也就是java拓展包定义的注解,并不是spring定义的,但至于为什么不在java包下,是因为java语言的元老们认为这个东西并不是java核心需要的工具,因此就放到扩展包里(javax中的x就是extension的意思),而spring是支持依赖注入的,因此spring必须要自己来实现@PostConstruct的功能。

3. 文档中说一个类只能有一个方法加此注解,但实际测试中,我在一个类中多个方法加了此注解,并没有报错,而且都执行了,我用的是springboot框架。

再往下看,这个注解有一些使用条件,挑一些重点的说一下

 

PostConstruct注释规则

1. 除了拦截器这个特殊情况以外,其他情况都不允许有参数,否则spring框架会报IllegalStateException;而且返回值要是void,但实际也可以有返回值,至少不会报错,只会忽略

2. 方法随便你用什么权限来修饰,public、protected、private都可以,反正功能是由反射来实现

3. 方法不可以是static的,但可以是final的

所以,综上所述,在spring项目中,在一个bean的初始化过程中,方法执行先后顺序为

Constructor > @Autowired > @PostConstruct

先执行完构造方法,再注入依赖,最后执行初始化操作,所以这个注解就避免了一些需要在构造方法里使用依赖组件的尴尬。

 

==========以上是对@PostConstruct的简单介绍,下面会从spring源码分析其具体实现原理==========

 

spring遵守了JSR-250标准,实现了javax.annotation包里面的各种注解功能,首先我们在GitHub下载spring-framework源码,我下的是5.0.x分支代码,导入到idea中,下面就开始动手分析。

首先代码中搜索”import javax.annotation.PostConstruct”,庆幸的是只有CommonAnnotationBeanPostProcessor这一个类有引用PostConstruct类,看名字八九不离十就是它了,它是在org.springframework.context.annotation包下,大致介绍如下

 

CommonAnnotationBeanPostProcessor注释

看来没什么营养,只是一些简单介绍说明了我们在什么版本,基于什么标准,实现了这几个注解,那么看代码。

 

CommonAnnotationBeanPostProcessor构造方法

看来只有CommonAnnotationBeanPostProcessor的构造方法使用了这个注解,声明了这个BeanPostProcessor要支持PostConstruct初始化注解,跟进去setInitAnnotationType这个方法,是父类InitDestroyAnnotationBeanPostProcessor中的方法,只是简单的将PostConstruct.class赋值给成员变量initAnnotationType,那么谁去使用了这个变量,再次意外的发现,只有buildLifecycleMetadata一个方法使用了这个变量。

 

buildLifecycleMetadata方法

这个方法做的事情也很简单,输入一个类,检查它或者它的祖先类是否有初始化方法以及销毁方法,如果有,把这些信息封装成一个LifecycleMetadata类,里面大概信息就是类名、初始化和销毁方法列表,方便bean注册或消亡的时候去调用。

偶然看到LifecycleMetadata中初始化方法列表是List<LifecycleElement>,LifecycleElement类里面的构造方法有限制方法不能有参数,否则报错IllegalStateException,和前文测试结果对应上了。

 

LifecycleElement构造方法

这是题外话了,接着看buildLifecycleMetadata方法中while循环里,不断遍历父类,找PostConstruct注解,每找完一个父类,往initMethods中累加,最后注册到与这个bean相应的initMethods中。

前文说了 “我在一个类中多个方法加了此注解,并没有报错,而且都执行了”,看过上述代码后就知道了,spring根本没有按照javax的要求做限制,可能认为没必要吧。那么多个PostConstruct注解或父类也有此注解,他们是什么顺序执行的呢?

1. 首先父类的初始化方法是先于子类的先执行,但注意不要被子类方法重写,那父类初始化方法就不会执行了,因为中间有一步是用LinkedHashSet存了method的名字。

2. 同一类内,多个PostConstruct注解方法不是按声明顺序执行的,看了一下代码逻辑,虽然存储方法的集合都是有序集合,看起来应该可以顺序执行,但实际上是以一种非常诡异的顺序来执行,为了看一下spring的初始化过程,在application.properties中设置trace=true,在控制台看debug日志后发现,跟存储方法的集合没声明关系,最开始反射取方法的时候顺序就打乱了,罪魁祸首就是ReflectionUtils.doWithLocalMethods 这个方法啦!看了一下JDK的API,发现它强调了Class类不能保证getDeclaredMethods()的顺序,因为JVM有权在编译时,自行决定类成员的顺序。

好了,所以现在知道了buildLifecycleMetadata这个方法,就是将bean生命周期的元数据组装一下返回,在类中也只有下面一个方法调用了

 

findLifecycleMetadata方法

它把bean的LifecycleMetadata放到一个ConcurrentHashMap保存。【说实话第一次看到对ConcurrentHashMap这么加锁的,改日写一篇文章解析一下java中锁的应用以及ConcurrentHashMap吧】然后再往上找,就是AbstractAutowireCapableBeanFactory对bean的初始化和消亡操作了,在注册完之后就会invoke方法,这是另外一个话题了,此处不再过多介绍,所以本文到此为止。

综上,通过源码来学习还是很高效的嘛,主要是学习大神们的代码精髓。


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/supercj/p/10303645.html

Java基础之IO技术(一)



—恢复内容开始—

  Java基础中的IO技术可谓是非常重要,俗话说的好,万丈高楼起于垒土之间。所以学习Java一定要把基础学好,今天我们来学习IO技术的基础。

  IO无非就是输入与输出,而其中处理的数据无非是字符与字节。字符最简单的定义就是这个数据是否是纯文本组成,除此之外所有的都是字节。Java给我们提供了字符和字节的处理,其中字符的处理有Writer和Reader两个抽象父类(分别对应的是写和读),字节的处理有OutputStream和InputStream两个抽象类(分别对应的是写和读)。今天我们就看看字符的处理。

  字符的处理

  1.字符的读(FileReader)

  首先请看一段代码:

 1 import java.io.*;  2 
 3 public class FileReader1 {  4     public static void main(String[] args){  5         FileReader fr = null;  6         try{  7             fr = new FileReader("1.txt");  8             int ch = 0;  9             //read():字符读取,如果已经达到流的结尾,则为-1 10             //这个可以作为读取文件结束的条件
11             while((ch = fr.read()) != -1){ 12                 //进行强制转换
13                 System.out.print((char)ch); 14  } 15             sop("读取结束"); 16 
17         }catch(FileNotFoundException e){ 18  sop(e.toString()); 19         }catch(IOException e){ 20  sop(e.toString()); 21         }finally{ 22             try { 23                 if(fr != null) { 24  fr.close(); 25  } 26             }catch(IOException e){ 27  sop(e.toString()); 28  } 29  } 30  } 31     public static void sop(Object obj){ 32  System.out.println(obj); 33  } 34 }

 

  这是文件读取的第一种方法,这种方法是将文件中的数据一个一个的读出来,这样操作比较耗资源,因此有了第二种读取方法,请看下面这段代码。

 1 import java.io.*;  2 
 3 public class FileReader2 {  4     public static void main(String[] args){  5         FileReader fr = null;  6         try{  7             fr = new FileReader("1.txt");  8             //read(char[] cbuf):将字符读取到数组中  9             //这个数组大小1024整数倍
10             char[] buff = new char[1024]; 11             int num = 0; 12             while((num = fr.read(buff)) != -1){ 13                 sop(new String(buff, 0, num)); 14  } 15 
16         }catch(FileNotFoundException e){ 17  sop(e.toString()); 18         }catch(IOException e){ 19  sop(e.toString()); 20         }finally{ 21             try{ 22                 if(fr != null) { 23  fr.close(); 24  } 25             }catch(IOException e){ 26  sop(e.toString()); 27  } 28  } 29 
30  } 31     public static void sop(Object obj){ 32  System.out.println(obj); 33  } 34 }

 

  这种方法是利用一个数组(这个数组大小一般定义为1024的整数倍),将文件中的数据传入到一个数组中,然后将数组中的数据一次性的处理。显而易见这种方法更好。

  文件打开是需要关闭的,通过close方法。

 

  2.字符的写(FileWriter)

  首先请看下面一段代码

 1 import java.io.*;  2 
 3 public class FileWriterDemo {  4     public static void main(String[] args){  5  fileWriter();  6 
 7  }  8     public static void fileWriter(){  9         FileWriter fw = null; 10         try { 11             fw = new FileWriter("1.txt"); 12 
13             fw.write("asdsadasdafd"); 14  fw.flush(); 15 
16             fw.append("\n"+123); 17  fw.flush(); 18 
19         }catch(IOException e){ 20  sop(e.toString()); 21         }finally{ 22             try{ 23                 if(fw != null) { 24  fw.close(); 25  } 26             }catch(IOException e){ 27  sop(e.toString()); 28  } 29  } 30  } 31     public static void sop(Object obj){ 32  System.out.println(obj); 33  } 34 }

 

  首先对象的创立,有多个构造函数。这里我介绍两种,FileWriter(String fileName)和FileWriter(String fileName, boolean append)。在第二个构造函数中有一个布尔型参数,为false就和第一个构造函数一样,每次向目的地中写入数据时都会覆盖原来的数据,若不想覆盖原来的数据,可以使用append方法;为true时,就不会覆盖原来的数据,而是在原数据后面追加数据。

  文件的写入是通过一个缓冲区(为了解决cpu和硬盘速度并不匹配的问题),因此在写入一段数据后,要通过flush方法刷新缓冲区,将数据送到目的地中(文件)。最后通过close方法关闭资源,在关闭之前也会刷新缓冲区。每次向目的地写入数据时,都会检测是否有目的文件,若没有则创建一个。

  为提高数据的读写效率,Java中提供了缓冲技术,对原来的FileWriter与FileReader进行修饰,其中涉及到了修饰设计模式,这里就不具体讲解。下面就对缓冲技术进行讲解。

 

  3.修饰后的写(BufferedWriter)

  我们还是来看看具体代码

 1 import java.io.*;  2 
 3 public class BufferedWriterDemo {  4     public static void main(String[] args){  5         //创建一个字符写入流对象
 6         FileWriter fw = null;  7         BufferedWriter bw = null;  8         try{  9             fw = new FileWriter("2.txt", true); 10             //为提高字符写入的流效率,加入了缓冲技术。 11             //只要将需要提高效率的流对象作为参数传递给缓冲区的构造函数
12             bw = new BufferedWriter(fw); 13             for(int i=0; i<5; i++) { 14                 bw.write("sdadadas"+i); 15  bw.newLine(); 16  } 17             //记住,只要用到缓冲区就要刷新
18  bw.flush(); 19 
20         }catch(IOException e){ 21  sop(e.toString()); 22 
23         }finally { 24             try{ 25                 //其实关闭缓冲区,就是在关闭与缓冲区关联的流对象
26  bw.close(); 27             }catch(IOException e){ 28  sop(e.toString()); 29  } 30  } 31 
32 
33  } 34     private static void sop(Object obj){ 35  System.out.println(obj); 36  } 37 }

 

  这一段知识尽在代码与注释中。其中有一个newLine方法,让我们看看1.8api中解释(写一行行分隔符。 行分隔符字符串由系统属性line.separator定义,并不一定是单个换行符(’\ n’)字符)。在不同系统中,换行符都不同。而使用了这个方法后,同样的代码不会在不同的系统中出现乱码的问题。

  最后的close方法,有一个疑问(到底是使用流对象的close方法还是使用缓冲技术的close方法呢),首先我们回到一开始,缓冲技术是用来修饰流操作的,必然他们操作的是流。因此使用其中任何一个close方法都会关闭这个流资源。

 

  4.修饰后的读(BufferedReader)

  请看下一段代码

 

 1 import java.io.*;  2 
 3 public class BufferedReaderDemo {  4     public static void main(String[] args){  5         FileReader fr = null;  6         BufferedReader br = null;  7         try{  8             //创建一个读取流对象并与文件关联
 9             fr = new FileReader("2.txt"); 10             //为了提高效率,加入了缓冲区技术。将字符读取流对象作为参数传入到缓冲对象的构造函数
11             br = new BufferedReader(fr); 12 
13             String line = null; 14             while((line = br.readLine()) != null){ 15  sop(line); 16  } 17 
18         }catch(FileNotFoundException e){ 19  sop(e.toString()); 20         }catch(IOException e){ 21  sop(e.toString()); 22         }finally { 23             try{ 24                 if(br != null){ 25  br.close(); 26  } 27             }catch(IOException e){ 28  sop(e.toString()); 29  } 30  } 31  } 32 
33     private static void sop(Object obj){ 34  System.out.println(obj); 35  } 36 }

  

  这段知识的原理和上一段的知识类似,就不做具体的解释了。就说说其中的readLine方法,我们来看看api文档,public String readLine(),(读一行文字。 一行被视为由换行符(’\ n’),回车符(’\ r’)中的任何一个或随后的换行符终止),也就是说把文件中的一行数据读取出来。

  我们总结一下字符的体系

 1 /**
 2  * 读:  3  * Reader  4  * |--FileReader(两种读的方法,资源的关闭)  5  * |--BufferedReader(修饰设计模式,高效的处理方式,readLine方法,close方法关闭资源)  6  *  7  * 写:  8  * Writer  9  * |--FileWriter(两种构造方法,write方法与append方法,) 10  * |--BufferedWriter(修饰设计模式,高效的处理方式,newLine方法,close方法关闭资源) 11  * 12  * 13  * */

 

 

 

  今天只讲解了字符的操作,关于字节的操作和其他IO的基础知识,在后面会相应的推出。

 

 

 

 

—恢复内容结束—


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/dx520/p/10303104.html

jdk1.5-1.10新特性



从网上搜集摘录的jdk特性,在这里记录下来以便自我学习.

jdk5新特性
    1、自动装箱和拆箱
    2、枚举
    3、静态导入
    4、可变参数
    5、內省
       是Java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性那么,那我们可以通过getName,setName来得到其值或者设置新的值。通过getName/setName来访问name属性,这就是默认的规则。Java中提供了一套API用来访问某个属性的getter,setter方法,通过这些API可以使你不需要了解这个规则,这些API存放于包java.beans中。
       一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。
    6、泛型
    7、For-Each循环

jdk6新特性
    1、Desktop类和SystemTray类
       AWT新增加了两个雷:Desktop,SystemTray。
       Desktop可以用来打开系统默认浏览器指定的URL,打开系统默认邮件客户端给指定的邮件账号发邮件,用默认应用程序打开或编辑文件(比如,用记事本打开txt文件),用系统默认的打印机打印文档
       SystemTray可以用来在系统托盘区创建一个托盘程序
    2、使用JAXB2来实现对象与XML之间的映射
       也就是对象与XML之间的映射(OXM),也可以通过XMLBeans和Castor等来实现同样的功能。
    3、StAX
       StAX是The Streaming API for XML的缩写,一种利用拉模式解析(pull-parsing)XML文档的API.StAX通过提供一种基于事件迭代器(Iterator)的API让 程序员去控制xml文档解析过程,程序遍历这个事件迭代器去处理每一个解析事件,解析事件可以看做是程序拉出来的,也就是程序促使解析器产生一个解析事件 然后处理该事件,之后又促使解析器产生下一个解析事件,如此循环直到碰到文档结束符; 
                SAX也是基于事件处理xml文档,但却 是用推模式解析,解析器解析完整个xml文档后,才产生解析事件,然后推给程序去处理这些事件;DOM 采用的方式是将整个xml文档映射到一颗内存树,这样就可以很容易地得到父节点和子结点以及兄弟节点的数据,但如果文档很大,将会严重影响性能。
    4、使用Compiler API
                使用JDK6的Compiler API去动态的编译Java源文件,Compiler API结合反射功能就可以实现动态的产生Java代码并编译执行这些代码。
    5、轻量级Http Server API
    6、插入式注解处理API
    7、用Console开发控制台程序
    8、对脚本语言的支持如:ruby,groovy,javascript
    9、Common Annotations

jdk7新特性
    1、switch中可以使用字符串
    2、泛型的自动判断
    3、自定义自动关闭类(实现AutoCloseable接口)
    4、新增一些取环境信息的工具方法(System中的方法)
    5、Boolean类型反转,空指针安全,参数与位运算
    6、两个char间的equals
    7、安全的加减乘除

    1、对Java集合(Collections)的增强支持
       List<String> list=[“item”]; //向List集合中添加元素
       String item=list[0]; //从List集合中获取元素
       Set<String> set={“item”}; //向Set集合对象中添加元
       Map<String,Integer> map={“key”:1}; //向Map集合中添加对象
       int value=map[“key”]; //从Map集合中获取对象
       但是经过自己测试,按照上面的使用方法,并不能创建集合。
    2、int支持二进制数据
    3、在try catch异常捕捉中,一个catch可以写多个异常类型
    Connection conn = null;
    try {
        Class.forName(“com.mysql.jdbc.Driver”);
        conn = DriverManager.getConnection(“”,””,””);
    } catch(ClassNotFoundException|SQLException ex) {
        ex.printStackTrace();
    }
    4、try catch中资源定义好之后try catch自动关闭
    try (BufferedReader in  = new BufferedReader(new FileReader(“in.txt”));
         BufferedWriter out = new BufferedWriter(new FileWriter(“out.txt”))) {
    int charRead;
    while ((charRead = in.read()) != -1) {
            System.out.printf(“%c “, (char)charRead);
            out.write(charRead);
        }
    } catch (IOException ex) {
        ex.printStackTrace();
    }
jdk8新特性
    1、接口的默认方法
    Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法,示例如下:
    public interface Formula {
        double calculate(int a);
        default double sqrt(int a) {
    return Math.sqrt(a);
        }
    }
    Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
        Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
    return sqrt(a * 100);
            }
        };
        System.out.println(formula.calculate(100));  // 100.0
        System.out.println(formula.sqrt(16));  // 4.0
    文中的formula被实现为一个匿名类的实例,该代码非常
    2、Lambda表达式
    List<String> names = Arrays.asList(“tom”,”jace”,”mike”);
    Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
    return o2.compareTo(o1);
        }
    });
    只需要给静态方法Collections.sort传入一个List对象以及一个比较器来指定顺序排列。通常做法都是创建一个匿名的比较器对象,然后将其传递给sort方法。
    在Java 8中提供了更简洁的语法,lambda表达式:
    Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
    });
    还可以更简洁:
    Collections.sort(names, (String a, String b) -> b.compareTo(a));
    去掉大括号以及return关键字
    Collections.sort(names, (a,b) -> b.compareTo(a));
    Java编译器可以自动推导出参数类型,所以可以不用再写一次类型。
    3、函数式接口
    Lambda表达式是如何在java的类型系统中表示的呢?
    每一个lambda表达式都对应着一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为默认方法不算抽象方法,所以也可以给自己的函数式接口添加默认方法。
    我们可以将lambda表达式当做一个抽象方法的接口类型,确保自己的接口一定达到这个要求,你只需要给你的接口添加@FunctionalInterface注解,编译器如果发现标注了这个注解的接口有多于一个抽象方法的时候就会报错。也就是说@ FunctionalInterface注解标注的接口只能有一个抽象方法。
    例如:
    @FunctionalInterface
    public interface Converter<F, T> {
    T convert(F from);
    }
    Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
    Integer converted = converter.convert(“123”);
    System.out.println(converted);
    以上代码不需要@FunctionalInterface注解也是正确的。
    4、方法与构造函数引用
    上面的代码也可以通过静态方法引用来表示:
    Converter<String, Integer> converter = Integer::valueOf;
    Integer converted = converter.convert(“123”);
    System.out.println(converted);
    Java8允许使用::关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:
    public class Person {
        String firstName;
        String lastName;
        Person() {
        }
    public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
        }
    }
    指定一个用来创建Person对象的对象工厂接口:
    public interface PersonFactory<P extends Person> {
    P create(String fisrtName, String lastName);
    }
    创建Person对象
    PersonFactory<Person> personFactory = Person::new;
    Person person = personFactory.create(“Peter”,”Parker”);
    我们只需要使用Person::new 来获取Person类构造函数的引用,Java编译器就会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
    5、Lambda作用域
    在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
    6、访问局部变量
    我们可以直接在lambda表达式中访问外层的局部变量
    final int num = 1;
    Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
    stringConverter.convert(2);
    但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确。
    7、访问对象字段与静态变量
    和本地不良不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:
    static int outerStaticNum;
    int outerNum;
    public void testScopes() {
        Converter stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };
        Converter stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
    8、访问接口的默认方法
    9、Date API
    10、Annotation注解
    11.HashMap
    HashMap中的红黑树:HashMap中链长度大于8时采取红黑树的结构存储。红黑树,除了添加的情况外,其他时候效率高于链表结构。
    ConcurrentHashMap:底层采用node数组+链表+红黑树的存储结构,通过CAS算法(乐观锁机制)+synchronized来保证并发安全的实现。
    put()方法过程:
    1) 根据key的hash值数组中相应位置的Node还未初始化,则通过CAS插入相应的数据;
    2) 如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,如果该节点的hash不小于0,则遍历链表更新节点或插入新节点;
    3) 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;
    
jdk1.9新特性
    1. Java 平台级模块系统
    Java 9 的定义功能是一套全新的模块系统。当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈指数级的增长。这时候就得面对两个基础的问题: 很难真正地对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到, 这样就会导致无意中使用了并不想被公开访问的 API。此外,类路径本身也存在问题: 你怎么知晓所有需要的 JAR 都已经有了, 或者是不是会有重复的项呢? 模块系统把这俩个问题都给解决了。

    模块化的 JAR 文件都包含一个额外的模块描述器。在这个模块描述器中, 对其它模块的依赖是通过 “requires” 来表示的。另外, “exports” 语句控制着哪些包是可以被其它模块访问到的。所有不被导出的包默认都封装在模块的里面。如下是一个模块描述器的示例,存在于 “module-info.java” 文件中:

    module blog {
      exports com.pluralsight.blog;
     
      requires cms;
    }
    我们可以如下展示模块:

    请注意,两个模块都包含封装的包,因为它们没有被导出(使用橙色盾牌可视化)。 没有人会偶然地使用来自这些包中的类。Java 平台本身也使用自己的模块系统进行了模块化。通过封装 JDK 的内部类,平台更安全,持续改进也更容易。

    当启动一个模块化应用时, JVM 会验证是否所有的模块都能使用,这基于 `requires` 语句——比脆弱的类路径迈进了一大步。模块允许你更好地强制结构化封装你的应用并明确依赖。你可以在这个课程中学习更多关于 Java 9 中模块工作的信息 。

    2. Linking
    当你使用具有显式依赖关系的模块和模块化的 JDK 时,新的可能性出现了。你的应用程序模块现在将声明其对其他应用程序模块的依赖以及对其所使用的 JDK 模块的依赖。为什么不使用这些信息创建一个最小的运行时环境,其中只包含运行应用程序所需的那些模块呢? 这可以通过 Java 9 中的新的 jlink 工具实现。你可以创建针对应用程序进行优化的最小运行时映像而不需要使用完全加载 JDK 安装版本。

    3. JShell : 交互式 Java REPL
    许多语言已经具有交互式编程环境,Java 现在加入了这个俱乐部。您可以从控制台启动 jshell ,并直接启动输入和执行 Java 代码。 jshell 的即时反馈使它成为探索 API 和尝试语言特性的好工具。

    测试一个 Java 正则表达式是一个很好的说明 jshell 如何使您的生活更轻松的例子。 交互式 shell 还可以提供良好的教学环境以及提高生产力,您可以在此了解更多信息。在教人们如何编写 Java 的过程中,不再需要解释 “public static void main(String [] args)” 这句废话。

    4. 改进的 Javadoc
    有时一些小事情可以带来很大的不同。你是否就像我一样在一直使用 Google 来查找正确的 Javadoc 页面呢? 这不再需要了。Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。此外,你会注意到,每个 Javadoc 页面都包含有关 JDK 模块类或接口来源的信息。

    5. 集合工厂方法
    通常,您希望在代码中创建一个集合(例如,List 或 Set ),并直接用一些元素填充它。 实例化集合,几个 “add” 调用,使得代码重复。 Java 9,添加了几种集合工厂方法:

    Set<Integer> ints = Set.of(1, 2, 3);
    List<String> strings = List.of(“first”, “second”);
    除了更短和更好阅读之外,这些方法也可以避免您选择特定的集合实现。 事实上,从工厂方法返回已放入数个元素的集合实现是高度优化的。这是可能的,因为它们是不可变的:在创建后,继续添加元素到这些集合会导致 “UnsupportedOperationException” 。

    6. 改进的 Stream API
    长期以来,Stream API 都是 Java 标准库最好的改进之一。通过这套 API 可以在集合上建立用于转换的申明管道。在 Java 9 中它会变得更好。Stream 接口中添加了 4 个新的方法:dropWhile, takeWhile, ofNullable。还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代:

    IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);
    第二个参数是一个 Lambda,它会在当前 IntStream 中的元素到达 100 的时候返回 true。因此这个简单的示例是向控制台打印 1 到 99。

    除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 `stram` 将一个 Optional 对象转换为一个(可能是空的) Stream 对象:

    Stream<Integer> s = Optional.of(1).stream();
    在组合复杂的 Stream 管道时,将 Optional 转换为 Stream 非常有用。

    7. 私有接口方法
    Java 8 为我们带来了接口的默认方法。 接口现在也可以包含行为,而不仅仅是方法签名。 但是,如果在接口上有几个默认方法,代码几乎相同,会发生什么情况? 通常,您将重构这些方法,调用一个可复用的私有方法。 但默认方法不能是私有的。 将复用代码创建为一个默认方法不是一个解决方案,因为该辅助方法会成为公共API的一部分。 使用 Java 9,您可以向接口添加私有辅助方法来解决此问题:

    public interface MyInterface {
     
        void normalInterfaceMethod();
     
        default void interfaceMethodWithDefault() {  init(); }
     
        default void anotherDefaultMethod() { init(); }
     
        // This method is not part of the public API exposed by MyInterface
        private void init() { System.out.println(“Initializing”); }
    }
    如果您使用默认方法开发 API ,那么私有接口方法可能有助于构建其实现。

    8. HTTP/2
    Java 9 中有新的方式来处理 HTTP 调用。这个迟到的特性用于代替老旧的 `HttpURLConnection` API,并提供对 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。不过你可以在 Java 9 中开始使用这套 API:

    HttpClient client = HttpClient.newHttpClient();
     
    HttpRequest req =
       HttpRequest.newBuilder(URI.create(“http://www.google.com”))
                  .header(“User-Agent”,”Java”)
                  .GET()
                  .build();
     
     
    HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());
    HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());
    除了这个简单的请求/响应模型之外,HttpClient 还提供了新的 API 来处理 HTTP/2 的特性,比如流和服务端推送。

    9. 多版本兼容 JAR
    我们最后要来着重介绍的这个特性对于库的维护者而言是个特别好的消息。当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着库得去向后兼容你想要支持的最老的 Java 版本 (许多情况下就是 Java 6 或者 7)。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本:

    multirelease.jar
    ├── META-INF
    │   └── versions
    │       └── 9
    │           └── multirelease
    │               └── Helper.class
    ├── multirelease
        ├── Helper.class
        └── Main.class
    在上述场景中, multirelease.jar 可以在 Java 9 中使用, 不过 Helper 这个类使用的不是顶层的 multirelease.Helper 这个 class, 而是处在“META-INF/versions/9”下面的这个。这是特别为 Java 9 准备的 class 版本,可以运用 Java 9 所提供的特性和库。同时,在早期的 Java 诸版本中使用这个 JAR 也是能运行的,因为较老版本的 Java 只会看到顶层的这个 Helper 类。

jdk1.10新特性
    1.局部变量类型推断:局部变量类型推断将引入”var”关键字,也就是你可以随意定义变量而不必指定变量的类型.
    原本需要这样:  
    List <String> list = new ArrayList <String>(); 
    Stream <String> stream = getStream();
     
    现在你可以这样:
     
    var list = new ArrayList <String>(); 
    var stream = getStream();
     
    说到类型推断,从JDK 5引进泛型,到JDK 7的”<>”操作符允许不绑定类型而初始化List,再到JDK 8的Lambda表达式,再到现在JDK 10的局部变量类型推断,Java类型推断正大刀阔斧的向前发展。
    2.GC改进和内存管理

    JDK 10中有2个JEP专门用于改进当前的垃圾收集元素。
     
    第一个垃圾收集器接口是(JEP 304),它将引入一个纯净的垃圾收集器接口,以帮助改进不同垃圾收集器的源代码隔离。
     
    预定用于Java 10的第二个JEP是针对G1的并行完全GC(JEP 307),其重点在于通过完全GC并行来改善G1最坏情况的等待时间。G1是Java 9中的默认GC,并且此JEP的目标是使G1平行。
    3.线程本地握手(JEP 312)

    JDK 10将引入一种在线程上执行回调的新方法,因此这将会很方便能停止单个线程而不是停止全部线程或者一个都不停。
    4.备用内存设备上的堆分配(JEP 316)

    允许HotSpot VM在备用内存设备上分配Java对象堆内存,该内存设备将由用户指定。
    5.其他Unicode语言 – 标记扩展(JEP 314)

    目标是增强java.util.Locale及其相关的API,以便实现语言标记语法的其他Unicode扩展(BCP 47)。
    6.基于Java的实验性JIT编译器

    Oracle希望将其Java JIT编译器Graal用作Linux / x64平台上的实验性JIT编译器。
    7.根证书(JEP 319)

    这个的目标是在Oracle的Java SE中开源根证书。
    8.根证书颁发认证(CA)

    这将使OpenJDK对开发人员更具吸引力,它还旨在减少OpenJDK和Oracle JDK构建之间的差异。
    9.将JDK生态整合单个存储库(JEP 296)

    此JEP的主要目标是执行一些内存管理,并将JDK生态的众多存储库组合到一个存储库中。
    10.删除工具javah(JEP 313)

    从JDK中移除了javah工具,这个很简单并且很重要。


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/chaoqun/p/10302624.html

java项目升级spring4.3.x 、jdk1.8 、tomcat8.5遇到的坑及解决方案



在将spring3.x 升级为4.3.x,jdk1.7 tomcat7升级到jdk1.8、tomcat8.5过程中,碰到了很多问题,也学习到了很多东西,现将这些问题分享出来,方便大家后续遇到同样问题时快速定位处理。

1、tomcat8.5不可在类似.test.com域名下写cookie

之前代码类似如下:

Cookie cookie = new Cookie("__admin__" ,"");
cookie.setDomain(".baidu.com");
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);

这样写在tomcat8.0上是没问题的,但是把它放到tomcat8.5上就报错了,tomcat启动报错信息如下:java.lang.IllegalArgumentException: An invalid domain [.baidu.com] was specified for this cookie
网上查阅资料,问题根源在于tomcat8.5本身的规则限制:
问题解决及原因分析帖子地址如下:
https://blog.csdn.net/cml_blog/article/details/52135115
https://blog.csdn.net/cml_blog/article/details/52135397

2、jackson-all包

之前项目用到了jackson-all的jar包,maven依赖如下:

<dependency>
     <groupId>jackson-all</groupId>
     <artifactId>jackson-all</artifactId>
     <version>2.0.1</version>
</dependency>

在springmvc4.x以上版本中会出现兼容问题,需升级jackson版本 2.7以上,且Jackson 2.x版提供了三个JAR包供下载:

  1. Core库:streaming parser/generator,即流式的解析器和生成器。
    下载:
    http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.1.0/jackson-core-2.1.0.jar

  2. Annotations库:databinding annotations,即带注释的数据绑定包。
    下载:
    http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.1.0/jackson-annotations-2.1.0.jar

  3. Databind库:ObjectMapper, Json Tree Model,即对象映射器,JSON树模型。
    下载:
    http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.1.0/jackson-databind-2.1.0.jar

从Jackson 2.0起,
核心组件包括:jackson-annotations、jackson-core、jackson-databind。
数据格式模块包括:Smile、CSV、XML、YAML。

替换maven依赖如下:

<jackson.version>2.8.11</jackson.version>

 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>${jackson.version}</version>
  </dependency>
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
  </dependency>
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson.version}</version>
  </dependency>

3、.html请求返回application/json数据报406问题

之前代码存在.html结尾请求json数据的代码,升级之后报406 error,代码如下:

    @ResponseBody
    @RequestMapping(value = "/test/captcha.html", produces = "application/json;charset=UTF-8")
    public String captcha(String callback) {
        logger.warn("/test/captcha.html 验证码 ");
    .....

我的解决方案是将produces = “application/json;charset=UTF-8″后面注解去掉,然后在spring-mvc.xml中添加一下代码如下:

<mvc:annotation-driven>
        <mvc:message-converters>
            <bean id="mappingJacksonHttpMessageConverter"
                  class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

网上参考解决方案参考:https://www.jianshu.com/p/eea6e2551749

4、tomcat 请求出现RFC 7230 and RFC3986的错误

在一些get请求中,带有{}的请求报400 error,tomcat控制台出现了RFC 7230 and RFC3986错误,网上查阅资料,发现是tomcat8.5对于一些特殊字符有限制,这个问题是高版本tomcat中的新特性:就是严格按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。而我们的系统在通过地址传参时,在url中传了一段json,传入的参数中有”{“不在RFC3986中的保留字段中,所以会报这个错。

解决方案有两种:

  1. 修改get请求,将path中的特殊字符进行转码后再传到后台 encodeURIComponent()
  2. 修改tomcat中的配置…/conf/catalina.properties,找到最后注释掉的一行 #tomcat.util.http.parser.HttpParser.requestTargetAllow=|  ,改成tomcat.util.http.parser.HttpParser.requestTargetAllow=|{},表示把{}放行

参考解决方案:https://blog.csdn.net/weixin_41986096/article/details/82785118


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/aixw/p/10302559.html

Connection 对象简介 方法解读 JDBC简介(四)



通过驱动管理器DriverManager的getConnection方法,可以创建到指定URL的连接
    Connection conn = DriverManager.getConnection(url, user, password);

看得出来,
在JDBC中连接被抽象为Connection
表示:与特定数据库的连接(会话)
在连接上下文中执行 SQL 语句并返回结果
image_5c46846f_5994

方法梗概

对于应用程序开发者来说,连接Connection主要用于执行对象的获取从而进一步执行SQL,这是
应用程序与数据库交互的主要途径
然后提供了数据库事务相关信息的设置以及其他信息的设置与获取

image_5c46846f_4ffd

执行对象

用于将 SQL 语句发送到数据库中
对象有三种
Statement
* 作用:用于执行不带参数的简单 SQL 语句
* 特点:
每次执行 SQL 语句,数据库都要执行 SQL 语句的编译,仅执行一次查询并返回结果的情形建议使用这个,此时效率高于 PreparedStatement 

PreparedStatement
* 作用:用于执行带 或 不带参数的预编译 SQL 语句
* 特点:是
预编译的, 在执行可变参数的一条 SQL 语句时,比 Statement 的效率高,安全性好,有效防止 SQL 注入等问题,对于多次重复执行的语句,效率会更高
CallableStatement
* 作用:用于执行对数据库
存储过程 的调用

事务

Connection提供了对于事务相关操作的支持
事务有自动提交的特性可以设置,自动提交默认每条SQL将会单独一个事务,Connection提供了自动提交属性的查询方法
如果不是自动提交,那么将会延续到手动COMMIT或者ROLLBACK
还能够设置和获取事务的隔离性
另外还可以灵活的设置保存点,从而让一个事务分割成几部分,可以部分提交

连接属性

连接本身有一些属性信息,比如目录等
其中最重要的就是Connection的关闭,数据库的连接是有限的,Connection在使用完毕后需要进行关闭
另外还提供了连接状态的测试方法

小结

Connection最为基础的方法就是执行对象的创建以及事务相关以及连接属性相关的 
其他方法属于对于数据库本身能力的API支持。

重点方法简介

执行对象

静态执行对象创建createStatement
Statement createStatement()
          创建一个 Statement 对象来将 SQL 语句发送到数据库。
Statement createStatement(int resultSetType, int resultSetConcurrency)
          创建一个 Statement 对象,该对象将生成具有给定类型和并发性的 ResultSet 对象。
Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
          创建一个 Statement 对象,该对象将生成具有给定类型、并发性和可保存性的 ResultSet 对象。
 

createStatement的核心是为了创建Statement,不带参数的 SQL 语句通常使用 Statement 对象执行;
如果多次执行相同的 SQL 语句,使用 PreparedStatement 对象可能更有效。
三个版本的createStatement核心是一样的,
区别在于参数的设置,参数的设置是针对于结果集的
空参数的createStatement返回的Statement 对象,创建的结果集在默认情况下类型为 TYPE_FORWARD_ONLY,并带有 CONCUR_READ_ONLY 并发级别
已创建结果集的可保存性可调用
getHoldability() 确定
 

resultSetType – 以下 ResultSet 常量之一:
ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE 或 ResultSet.TYPE_SCROLL_SENSITIVE
 
resultSetConcurrency – 以下 ResultSet 常量之一:
ResultSet.CONCUR_READ_ONLY 或 ResultSet.CONCUR_UPDATABLE
 
resultSetHoldability – 以下 ResultSet 常量之一:
ResultSet.HOLD_CURSORS_OVER_COMMIT 或 ResultSet.CLOSE_CURSORS_AT_COMMIT 

简言之,createStatement系列用于创建Statement–通常用来执行不带参数的SQL,并且附带的可以设置结果集的类型、并发性、可保存性
 
动态执行对象创建prepareStatement
PreparedStatement prepareStatement(String sql)
          创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库。
PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
          创建一个默认 PreparedStatement 对象,该对象能获取自动生成的键。
PreparedStatement prepareStatement(String sql, int[] columnIndexes)
          创建一个能返回由给定数组指定的自动生成键的默认 PreparedStatement 对象。
PreparedStatement prepareStatement(String sql, String[] columnNames)
          创建一个能返回由给定数组指定的自动生成键的默认 PreparedStatement 对象。 

PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
          创建一个 PreparedStatement 对象,该对象将生成具有给定类型和并发性的 ResultSet 对象。
PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
          创建一个 PreparedStatement 对象,该对象将生成具有给定类型、并发性和可保存性的 ResultSet 对象。
 
 

prepareStatement系列方法创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库。
 
1. prepareStatement(String sql) 最为基础的创建方法
带有 IN 参数或不带有 IN 参数的 SQL 语句都可以被预编译并存储在 PreparedStatement 对象中。然后
可以有效地使用此对象来多次执行该语句
结果集属性在默认情况下类型为 TYPE_FORWARD_ONLY,并带有 CONCUR_READ_ONLY 并发级别。
已创建结果集的可保存性可调用
getHoldability() 确定。
image_5c46846f_41a9
2. 返回键值数据
prepareStatement(String sql, int autoGeneratedKeys)
创建一个默认 PreparedStatement 对象,
该对象能获取自动生成的键
autoGeneratedKeys – 指示是否应该返回自动生成的键的标志,它是 Statement.RETURN_GENERATED_KEYS 或 Statement.NO_GENERATED_KEYS 之一
通过设置Statement.RETURN_GENERATED_KEYS要求返回主键
通过getGeneratedKeys方法,获取结果集,进而可以获取主键
此处插入一条记录,所以主键返回必然只有一个,当试图获取不存在的下标时,将会抛出异常,如下面所示,读取rs.getInt(2);时就报错了

image_5c468470_351b
另外还可以使用int数组指定列下标,或者String数组指定列名,指明用于返回的键
prepareStatement(String sql, int[] columnIndexes)
prepareStatement(String sql, String[] columnNames)
注意:
这几个方法并不是说所有的数据库都支持都一样,比如对于MYSQL来说,都是一回事
image_5c468470_6bce
 
通过测试代码我们也可以看得出来,随便设置的数组,也都无所谓,因为mysql压根就没关注数组的内容(上面的代码为mysql的实现)
image_5c468470_154d
 
类似createStatement 也有可以设置结果集类型、并发性、可保存性的方法
PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)

PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
 

resultSetType – 以下 ResultSet 常量之一:
ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE 或 ResultSet.TYPE_SCROLL_SENSITIVE
 
resultSetConcurrency – 以下 ResultSet 常量之一:
ResultSet.CONCUR_READ_ONLY 或 ResultSet.CONCUR_UPDATABLE
 
resultSetHoldability – 以下 ResultSet 常量之一:
ResultSet.HOLD_CURSORS_OVER_COMMIT 或 ResultSet.CLOSE_CURSORS_AT_COMMIT 

 

默认情况下类型为 TYPE_FORWARD_ONLY,并带有 CONCUR_READ_ONLY 并发级别。
已创建结果集的可保存性可调用
getHoldability() 确定。
这一点还是那样子,如果没设置的,存在默认值
 
存储过程执行对象创建CallableStatement
CallableStatement prepareCall(String sql)
          创建一个 CallableStatement 对象来调用数据库存储过程。
 
CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
          创建一个 CallableStatement 对象,该对象将生成具有给定类型和并发性的 ResultSet 对象。
 
CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
          创建一个 CallableStatement 对象,该对象将生成具有给定类型和并发性的 ResultSet 对象。 

 
核心为创建存储过程的执行对象,另外与createStatement和prepareStatement方法类似,可以设置结果集的类型、并发性、可保存性。

事务

支持事务的数据库一般都有自动提交,提交、回滚、保存点、事务隔离级别这几个基本属性。
Connection中提供了对他们的支持。
 
boolean getAutoCommit()
          获取此 Connection 对象的当前自动提交模式。 
 

void commit()
          使所有上一次提交/回滚后进行的更改成为持久更改,并释放此 Connection 对象当前持有的所有数据库锁。
 
void rollback()
          取消在当前事务中进行的所有更改,并释放此 Connection 对象当前持有的所有数据库锁。
 
void rollback(Savepoint savepoint)
          取消所有设置给定 Savepoint 对象之后进行的更改。
 
void setAutoCommit(boolean autoCommit)
          将此连接的自动提交模式设置为给定状态。          
 
void setTransactionIsolation(int level)
          试图将此 Connection 对象的事务隔离级别更改为给定的级别。
 
int getTransactionIsolation()
          获取此 Connection 对象的当前事务隔离级别。
 
Savepoint setSavepoint()
          在当前事务中创建一个未命名的保存点 (savepoint),并返回表示它的新 Savepoint 对象。
 
Savepoint setSavepoint(String name)
          在当前事务中创建一个具有给定名称的保存点,并返回表示它的新 Savepoint 对象。  
 
void releaseSavepoint(Savepoint savepoint)
          从当前事务中移除指定的 Savepoint 和后续 Savepoint 对象。

 

连接自身属性状态

Connection最重要的一个状态就是打开与关闭,通过getConnection方法如果连接成功,那么该连接被打开
在使用结束之后你需要手动进行关闭
void close()
          立即释放此 Connection 对象的数据库和 JDBC 资源,而不是等待它们被自动释放。

JDBC还提供了查询方法用于检测该连接是否已经被关闭。
boolean isClosed()
          查询此 Connection 对象是否已经被关闭。
 
另外还有检测连接是否有效的方法
boolean isValid(int timeout)
          如果连接尚未关闭并且仍然有效,则返回 true。   

总结

Connection主要用于创建SQL的执行对象,而连接通过驱动管理器DriverManager的getConnection方法进行获取。
Connection并不是所有的方法都如API描述的一模一样,要看具体的驱动程序的支持情况
比如前面提到的MYSQL对于prepareStatement(String sql, String[] columnNames)和prepareStatement(String sql, int[] columnIndexes)的情况
通过连接对执行对象的创建,决定了很多事情
比如执行语句的特质,是用来执行静态SQL还是预编译带参数的动态的SQL还是存储过程?
也可以对结果集的参数进行设置
事务的相关处理也是在连接中操作的。
通过连接还能够获得数据库的元数据信息
我们所有的查询都是在一个数据库连接中进行的,所以此次操作所需要的东西,比如sql设置、结果集属性设置再或者数据库相关的元数据信息等都可以通过Connection获得。
 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/noteless/p/10302526.html

java 集合stream操作



分组

Map<Integer, List<T>> group = List.stream().collect(Collectors.groupingBy(T::getField));

排序

// 升序
List<T> list = List.stream().sorted(Comparator.comparing(T::getSize)).collect(Collectors.toList());
// 倒序
List<T> list = List.stream().sorted(Comparator.comparing(T::getSize).reversed()).collect(Collectors.toList());

条件查询

//    非boolean
ListUtil.where(List, x -> "LL".equals("LL"));
//    boolean
ListUtil.where(List, T::isLL);

封装


import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ListUtil {
    /**
     * 判断集合是否有值
     *
     * @param list
     * @return
     */
    public static <TOrigin> boolean isAny(List<TOrigin> list) {
        return list != null && list.size() > 0;
    }

    /**
     * 判断map是否有值
     *
     * @param map
     * @return
     */
    public static <Key, Value> boolean isAny(Map<Key, Value> map) {
        return map != null && map.size() > 0;
    }

    /**
     * 获取范围集合行
     *
     * @param list 集合
     * @param skip 跳过多少行:pagesiz*pageindex
     * @param take 获取多少行:pagesize
     */
    public static <TOrigin> ArrayList<TOrigin> getRange(List<TOrigin> list, Integer skip, Integer take) {
        ArrayList<TOrigin> itemList = new ArrayList<>();
        if (skip < list.size()) {
            Integer max = skip + take;
            if (max > list.size()) {
                max = list.size();
            }
            for (Integer i = skip; i < max; i++) {
                itemList.add(list.get(i));
            }
        }
        return itemList;
    }

    /**
     * 扩展where方法
     *
     * @param source 数据源
     * @param predicate 表达式
     */
    public static <TOrigin> List<TOrigin> where(List<TOrigin> source, Predicate<? super TOrigin> predicate) {
        //  判断当前关键字缓存是否已存在
        Stream<TOrigin> stream = source.stream().filter(predicate);
        //将Stream转化为List
        List<TOrigin> list = stream.collect(Collectors.toList());
        return list;
    }

    /**
     * 扩展 去重 方法
     *
     * @param source 数据源
     */
    public static <TOrigin> List<TOrigin> distinct(List<TOrigin> source) {
        Set set = new  HashSet();
        List newList = new  ArrayList();
        set.addAll(source);
        newList.addAll(set);
        return newList;
    }

    /**
     * 获取最小值
     * @param sourceList 数据源集合
     * @param keyExtractor 需要获取最小值的属性
     * */
    public static <TOrigin, TAttribute extends Comparable<? super TAttribute>> TOrigin min(List<TOrigin> sourceList, Function<? super TOrigin, ? extends TAttribute> keyExtractor)
    {
        return sourceList.stream().min(Comparator.comparing(keyExtractor)).get();
    }
}

View Code

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/Cailf/p/10302527.html

类与对象简单介绍



类与对象简单介绍

  1. 什么是类与对象?
  2. 创建一个类和创建一个对象的语法。
  3. 类与对象中重要的两种关系(继承、接口)。
  4. 简单使用类与对象的基础知识来实现两个小程序(ui登录界面,画图板)

 

 

===========================================================

 

一、什么是类与对象?

1、类与对象与生活实际:

在我们的日常生活中,其实有很多类与对象的实例。例如,一个具体的学生(中南大学的某一个学生)就可以是一个具体的对象,而学生(所有学生的象征性名词)就是指的一个类。

那么关于类与对象就可以有个这样的定义。类就是这一个类所有对象的蓝图或者原型,而对象就是这个类的一个具体的实例。

再回到学生这个例子来,想想如果我们想要描述这个类或者说这个类的一个具体的实例的话,我们应该如何去描述呢?答案是,我们可以从特征和功能两个大的方面来描述一个类或者一个对象。

例如,大学生就可以从特征方面来描述这个学生的姓名、学号、身高、体重、成绩等等。也可以从功能方面来描述这个学生的学习、运动、做人等等。因此在java中也有类似这样的两个方面来描述一个类或者这个类的对象,这两个方面就是属性和方法。其中属性对应实际生活中的特征,方法对应功能。

 

二、创建一个类和创建一个对象的基本语法。

1、创建一个类的基本语法:

Public class 类名{

类里面的方法和属性

}

 

其中花括号里面的是类里面的方法和属性,而创建相应类的方法和属性也有特定的语法如下:

2、创建一个方法:

访问权限(public/private/protected) 方法类型 方法名(传递的参数列表){

方法体

}

 

3、创建一个属性:

访问权限 属性类型 属性名(这里可以初始化也可以不初始化,要根据这个类具体的类型)

 

在创建过程中呢有一个开始没有提到的概念就是访问权限。访问权限顾名思义就是访问的权限,而访问指的是访问者去访问被访问者,我们说的被访问者就是访问权限后面的方法或者属性。当一个类的方法和属性被加以不同的访问权限的时候,能访问它的访问者也会因为访问权限的问题发生不同的变化。那么接下来就讲java中的几个常见的访问权限。

Public:

如果一个类的属性或方法前面有public关键词就带表他的访问权限是公共的,也就是说这个类本身和与这个类同在一个java项目下的所有包都可以通过创建对象然后用“.”成员运算符来直接访问这个类所对应的对象的属性和方法。

 

Private:

如果一个类的属性或方法前面有public关键词就带表他的访问权限是私有的,也就是说除了这个类在类定义时可以自己用自己的属性,其他情况统统都不能访问这个类的方法或者属性。

 

Protected:

如果一个类的属性或方法前面有public关键词就带表他的访问权限是保护的。保护权限也叫子权限,顾名思义,只有这个类的子类可以访问它的属性和方法(子类的概念后面会提到)。其它权限方面和private一样。

 

缺省(无关键词):

如果一个类的属性或方法前面没有关键词就带表他的访问权限是缺省的。缺省不代表没有访问权限的限制,而是带表它的访问权限没有明确表达出来而已。缺省的访问权限一般是与这个类在同一个java项目的同一个包下的其他文件可以访问这个类的属性和方法,所以一般把缺省权限叫做包权限。

 

4、创建一个对象的基本语法:

类名 对象名  = new 类名();

后面在继承中还会讲到一种特殊的创建方法

 

三、类与对象中重要的两种关系(继承、接口)。

继承:

1、继承的含义:

继承是指创建一个新的类,但是这个类建立在一个已有的类以上,含有已有类的所有属性和方法。这个新创建的类就叫子类,这个已有类就叫父类,这种关系就叫继承关系。

 

2、创建一个子类的基本语法:

Public class 子类名 extends 父类名{

类里面的内容

}

 

3、定义子类里面内容时需注意的点:

因为子类是从父类那里继承了所有的属性和方法,所以对于父类已有的属性和方法,在子类中不需要再写一遍,这样大大节省了代码的空间。但如果想要在子类中增加属性方法或者要改写方法也是可以的,增加属性方法就像之前所说的属性方法的创建方式一样正常创建就行了,但改写父类的方法就必须重新复制父类除其方法体以外的所有东西,然后只能改写方法体内的内容。

 

4、自动转型与强制转型:

前面有提到创建对象还有另外的语法,那么接下来就说一下这几个语法。

语法1:

父类名 对象名 = new 子类名();

这种叫做自动转型,也就是创建的对象会自动从子类转型到父类,那么这个对象会抹去子类中新创建的属性而保留了子类中新创建与重写的方法。

 

语法2:

子类名 对象名 = (子类名)父类名;

这种语法叫做强制转换,即把父类强制转换为子类。

 

那么有人会问了,这个强制转换和自动转型有什么用呢?

试想如果在一个类中的一个方法的参数列表是面有一个参数是一个自定义的类的类型,那么如果这个自定义的类里面有很多子类,你每往这个方法传一种自定义类型的参数时就得新定义一个基本上重复的方法。但是如果有了自动转型就不同了,你直接在方法体里面定义一个最基本的父类,然后当你往方法里面传的参数是任何一个子类类型的时候,系统就会帮你自动转型为父类,这样就不需要重复定义多个方法了。

 

接口:

1、接口的含义:

正如现实生活中的接口一样,接口是提供一定的标准来帮助人们执行某些特定的事情。如果没有借口,就拿我们的电脑USB接口来说,不同的电脑生产商和鼠标键盘生产商就会头大了,因为他们不知道对方生产出来的鼠标键盘或者电脑接口会是多大的,那这样就会导致很多低效率问题。所以人们就发明了接口这个东西,大家都按照这个标准来生产,就不会出现乱七八糟的产品了。而java中的接口正是如此。

 

2、创建的基本语法:

Public interface 接口名{

Public static final 属性名 = (初始化值);

Public abstract 方法类型 方法名();

}

需要特别注意的点是,创建一个接口类,第一,必须要初始化其属性;第二其方法为抽象方法不可写方法体,连花括号都不能打;第三public static final 和 public abstract都可以省去,因为缺省值就是这两个。

 

3、接口的实现的基本语法:

Public 子类名 implements 接口名{

①相比接口新加的属性和方法

②所有接口方法的重写(必须重写)

}

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/782687539-nanfu/p/10302188.html

构造方法私有化与单例模式



所谓单例模式,即一个类只有一个实例化对象。如果不希望一个类产生很多对象,就要使用单例设计模式。比如:使用打印机时,只需要一个打印机实例对象,多个打印机对象会造成内存浪费;windows任务管理器只能打开一个,多个任务管理器窗口是无意义的;windows回收站也只有一个…

单例模式的核心是构造方法的私有化(即在入口处限制了对象的实例化),之后在类的内部实例化对象,并通过静态方法返回实例化对象的引用。

具体代码如下:

 1 class Singleton {
 2     private static Singleton instance = new Singleton(); // 在内部产生本类的实例化对象
 3 
 4     public static Singleton getInstance() { // 通过静态方法取得instance对象
 5         return instance;
 6     }
 7 
 8     private Singleton() { // 将构造方法进行了封装,私有化
 9     }
10 
11     public void print() {
12         System.out.println("Hello World!!!");
13     }
14 }
15 
16 public class SingletonDemo {
17     public static void main(String args[]) {
18         Singleton s1 = null; // 声明对象
19         Singleton s2 = null; // 声明对象
20         Singleton s3 = null; // 声明对象
21         s1 = Singleton.getInstance(); // 取得实例化对象
22         s2 = Singleton.getInstance(); // 取得实例化对象
23         s3 = Singleton.getInstance(); // 取得实例化对象
24         s1.print(); // 调用方法
25         s2.print(); // 调用方法
26         s3.print(); // 调用方法
27     }
28 }

 

输出结果:

在以上例子中,instance、s1、s2、s3在内存中实际上都是指向同一个对象的

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/voidchar/p/10302107.html

并发concurrent—3



背景:并发知识是一个程序员段位升级的体现,同样也是进入BAT的必经之路,有必要把并发知识重新梳理一遍。

ConcurrentHashMap
在有了并发的基础知识以后,再来研究concurrent包。普通的HashMap为非线程安全的,在高并发场景下要使用线程安全版本的ConcurrentHashMap;

众所周知HashTable可以保证线程安全但却效率低下,而HashMap是非线程安全但效率却高于HashTable,于是ConcurrentHashMap就孕育而生成为二者的结合体,为了更好的理解ConcurrentHashMap先看下这两个Map。

HashMap

HashMap之所以具有很快的访问速度,因为它是根据键的hashCode值来存储数据,在大多数情况下可以直接定位到它的值,但遍历的顺序是不确定的;HashMap的key可以为null,但是最多只允许一条记录的键为null,另外允许多条记录(value)的值为null,key为null的键值对永远都放在一table[0]为头节点的链表中;HashMap为非线程安全的,适用于单线程环境下,即在任一时刻都可以有多个线程同时对HashMap进行读或写操作,可以会导致数据的不一致;如果一定要使用HashMap又要保证线程安全,则可以用Collection的synchronizedMap方法或ConcurrentHashMap都OK;HashMap是基于哈希实表实现的,每一个元素是一个key-value对,其内部通过单链表结局冲突问题的,当Map容量不足(超过了阀值)时链表会自动增长;HashMap实现了Serializable接口,因此其支持序列化,并且实现了Cloneable接口,可以被克隆;

HashMap存储数据的过程:

HashMap内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上其实是一个单向链表;当要添加一个key-value对时,首先会通过hash(key)方法技术hash值,然后通过indexFor(hash,length)求该key-value对的存储位置,其计算方法是先用Hash&0x7FFFFFFF后,再对length取模,这就保证了每一个key-value对都能存入HashMap,当计算出相同的位置是,由于存入位置是一个链表,所以把这个key-value对插入链表头。

如上图1 所示,最左边竖列排的多个方格就代表哈希表,也叫哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中;HashMap内存储数据的Entry数组默认是16,如果没有对Entry扩容机制的话,当存储的数据一多,Entry内部的链表会很长,这就失去了HashMap的存储意义了,所以HasnMap内部有自己的扩容机制(当size大于threshold时,对HashMap进行扩容)。 上图2 是HashMap的链表存储结构,其中E*代表一个Node节点,每个Node节点就对应着一个key-value的mapping映射;每个Node除了保存了key和value的映射之外,还保存了它下一Node的引用(Eb保存了Ebb的引用,而Ebb保存了Ebbb的引用);图2中,每一个链表如Ec–>Ecc–>Eccc,这三个节点的key是不相等的。

分析HashMap源码会发现其内部有几个重要的变量如:size用于记录HashMap的底层数组中已用槽的数量、threshold用于HashMap的阈值判断,看是否需要调整HashMap的容量(threshold = 容量*加载因子)、DEFAULT_LOAD_FACTOR = 0.75f,即加载因子默认0.75。 HashMap的扩容是是新建了一个HashMap的底层数组,通过调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(此步需要重新计算元素在新的数组中的索引位置,导致HashMap扩容成为一个相当耗时的操作),So我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能

HashTable
HashMap的功能与HashMap类似,如同样是基于哈希表实现的、内部也是通过单链表解决冲突问题、容量不足时也会自动增加、同样实现了Seriablizable接口支持序列化、实现了Cloneable接口可克隆;不同的是HashTable继承自Dictionary类且为线程安全的(任一时间只有一个线程可以写HashTable,但性能不如

ConcurrentHashMap),而HashMap继承AbstractMap类且非线程安全。

如图3,HashTable只有一把锁,当一个线程访问HashTable的同步方法时,会将整张table 锁住,当其他线程也想访问HashTable 同步方法时,就会进入阻塞或轮询状态。也就是确保同一时间只有一个线程对同步方法的占用,避免多个线程同时对数据的修改,由此确保线程的安全性;但HashTable 对get,put,remove 方法都使用了同步操作,这就造成如果两个线程都只想使用get 方法去读取数据时,因为一个线程先到进行了锁操作,另一个线程就不得不等待,这样必然导致效率低下,而且竞争越激烈,效率越低下。

ConcurrentHashMap(并发且线程安全)

ConcourrentHashMap是通过分段锁技术来保证线程安全的[case:一个人到酒店开房可直接在前台办理入住,三个陌生人到酒店开房登记入住,另外两个则要先排队等第一个办理结束(普通的Map),要是三个人所住的每个楼层都有一个可以办理入住的前台就无需排队了(ConcurrentHashMap)];ConcurrentHashMap主要由Segment(桶)和HashEntry(节点)两大数据组成,如下图:

在hashMap 的基础上,ConcurrentHashMap将数据分为多个segment(默认16个),然后每次操作对一个segment 加锁,HashTable 在竞争激烈的并发环境下表现出效率低下的原因是由于所有访问HashTable的线程都必须竞争同一把锁,而ConcurrentHashMap将数据分到多个segment 中(默认16,也可在申明时自己设置,不过一旦设定就不能更改,扩容都是扩充各个segment 的容量),由于每个segment 都有一个自己的锁,只要多个线程访问的不是同一个segment 就没有锁争用,就没有堵塞,也就是允许16个线程并发的更新并且不存在锁争用现象。除此之外,ConcurrentHashMap的segment就类似一个HashTable,但比HashTable又更进一步优化,因为HashTable对get,put,remove方法都会使用锁,而ConcurrnetHashMap中get方法是不涉及到锁的;并且ConcurrentHashMap内部在并发读取时,除了key 对应的value为null的情况下会用到锁,其它的场景下都没有用到锁,所以对于读操作无论多少线程并发都是安全高效的。

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/taojietaoge/p/10301711.html

java 使用链表来模拟栈的入栈出栈操作



栈:后进先出;最后一个放入堆栈中的物体总是被最先拿出来。

使用链表来模拟栈的入栈出栈操作。

1.节点类代码

public class Entry<T> {
private T value;
private Entry<T> next;
public Entry() {
    this(null);
}
public Entry(T value) {
    this.value=value;
    this.next=null;
}
    
public void setValue(T value) {
    this.value=value;
}
public void setNext(Entry<T> next) { this.next=next; } public T getValue() { return value; } public Entry<T> getNext(){ return next; } }

2.节点的入栈出栈方法代码

public class Link<T> {//链表实现栈,先进后出
private Entry<T> headEntry;
private int size=0;
public Link() {
    headEntry =new Entry<>();
}
public void pop() {//出栈
    if(headEntry.getNext()!=null) {
    headEntry.getNext().setValue(null);
    headEntry.setNext(headEntry.getNext().getNext());
    size--;
    }else {
        return;
    }
    
}
public void push(T value) {//入栈
    Entry<T> newEntry=new Entry<>(value);
    if(headEntry.getNext()!=null) {
        newEntry.setNext(headEntry.getNext());
        
    }
        headEntry.setNext(newEntry);
        size++;
    
}


public void show(){//打印节点
    if(headEntry.getNext()==null) {
        return;
    }
    for(Entry<T> p = headEntry.getNext();p!=null;p=p.getNext()){
        System.out.print(p.getValue()+" ");
    }
    System.out.println();
}
}

3.测试类代码

public class Main {
public static void main(String args[]) {
    Link<String> ll=new Link<>();
    ll.push("1");//入栈
    ll.push("2");
    ll.push("3");
    ll.push("4");
    ll.push("5");
    ll.push("6");
    ll.push("7");
    ll.push("8");
    ll.show();//打印栈内元素
    ll.pop();//弹出栈顶元素
    ll.show();
    ll.pop();
    ll.show();
}
}

4.测试结果

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/zunzunzunshen/p/10301415.html

JVM(一) —JVM的数据模型



JVM的逻辑内存模型图

[逻辑内存模型图]

—–

JVM内部分区

其实JVM内部不仅仅只有栈和堆 
包括 程序计数器 、 Java 虚拟机栈 、本地方法栈、Java 堆、方法区等

1. 程序计数器

线程私有,较小的内存空间,如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节

码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。此

内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

2. Java 虚拟机栈(栈区) 
线程私有,每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态

链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在

虚拟机栈中从入栈到出栈的过程。

3.本地方法栈 
与虚拟机栈所发挥的作用是非常相似的,区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,
而本地方法栈则是为虚拟机使用到的Native 方法服务,有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError

4.Java 堆(堆区) 
线程共享,此内存区域的唯一目的就是创建并存放对象实例,也是GC区。分代收集算法:内存区大概分为新生代,老年代,永久代。
新生代从Eden区创建,复制到Survivor区(2个 from 和 to)。 GC分为minor GC 和 Full GC ,
minor GC: Eden满了就触发minor GC,minorGC会将Eden区仍然存活的会复制到ToSurvivor
,FromSurvivor一部分复制到老年代,一部分复制到ToSurvivor,此时原Eden和From的数据清空,from和to互换,这样的过程直到To被填满,复制到老年代。
FullGC:(1)年老代内存不足;(2)持久代内存不足;(3)统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间(4)调用System.gc()方法的时候,

5. 方法区(类级/静态) 
线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它别名叫做Non-Heap(非堆)
即“永久代”,不进行GC,只是针对常量池的回收和对类型的卸载 
运行时常量池:是方法区的一部分,Class常量池存放编译期生成的各种字面量和符号引用,
运行时常量池相对于Class 文件常量池的另外一个重要特征是具备动态性,运行期间也可能将新的常量放入池中,这种特性被开发
人员利用得比较多的便是String 类的intern() 方法(这个方法会首先检查字符串池中是否有”ab”这个字符串,如果存在则返回这个字符串的引用,
否则就将这个字符串添加到字符串常量池中,然会返回这个字符串的引用,这可以实现字符串的”= =”比较。new String 不进入常量池,直接赋值会进入常量池)。


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/liumz0323/p/10301332.html

源码分析–ConcurrentHashMap与HashTable(JDK1.8)



  ConcurrentHashMap和Hashtable都是线程安全的K-V型容器。本篇从源码入手,简要说明它们两者的实现原理和区别。

 

  与HashMap类似,ConcurrentHashMap底层也是以数组+链表+红黑树实现的,以Node节点封装K-V和hash。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
}

  val和next以volatile关键字进行修饰,保证内存可见性。

看一下它的put()方法:

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

  • 与HashMap不同,不允许空键值
  • 计算hashCode
  • 判断是否初始化
  • 如果当前位置为空,利用CAS算法,放置节点
  • 如果当前hashCode == MOVED,进行扩容
  • 利用synchronized锁,进行链表或者红黑树的节点放置
  • 链表数量大于8,转为红黑树

ConcurrentHashMap的get()方法没有使用同步锁机制。

JDK1.8以后,ConcurrentHashMap的线程安全都是利用CAS + synchronized来实现的。效率较高。

 

对于HashTable,它底层为数组+链表结构,也不允许空键值。以Entry封装K-V和hash。

主要get()和put()方法:

public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

  HashTable不仅因为没有红黑树,对于数据遍历的效率就比较低,而且在get()方法都加了synchronized关键字,而且get()和put()方法都是直接加在方法上。这样一来效率就比ConcurrentHashMap低得多了。所以,如果要在并发情况下使用K-V容器,使用ConcurrentHashMap更好。

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/sunshine-ground-poems/p/10301118.html

Java基础学习-Collection



package Collection;

import java.util.ArrayList;
import java.util.Scanner;

/*集合类的特点:
 * 大小可变
 * 
 * ArrayList实现:
 * 大小可变数组的实现
 * 
 * 
 * <E>这是泛型
 *       怎么用
 *         在出现<E>的地方可以使用引用遍历替换
 *       
 * java.util 
 * 类 ArrayList<E>
 * 从类 java.util.AbstractList 继承的方法
 * 从类 java.util.AbstractCollection 继承的方法
 * 从类 java.lang.Object 继承的方法
 * 从接口 java.util.List 继承的方法
 
 
  构造方法:
  ArrayList() 
   构造一个初始容量为 10 的空列表。 
  ArrayList(Collection<? extends E> c) 
   构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。 
  ArrayList(int initialCapacity) 
    构造一个具有指定初始容量的空列表。
    
   方法:
     boolean add(E e) 
                 将指定的元素添加到此列表的尾部。 
     void add(int index, E element) 
              将指定的元素插入此列表中的指定位置。 
     boolean addAll(Collection<? extends E> c) 
              按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。 
     boolean addAll(int index, Collection<? extends E> c) 
              从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。 
     void clear() 
              移除此列表中的所有元素。 
     Object clone() 
              返回此 ArrayList 实例的浅表副本。 
     boolean contains(Object o) 
              如果此列表中包含指定的元素,则返回 true。 
     void ensureCapacity(int minCapacity) 
              如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。 
     E get(int index) 
              返回此列表中指定位置上的元素。 
     int indexOf(Object o) 
              返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。 
     boolean isEmpty() 
              如果此列表中没有元素,则返回 true 
     int lastIndexOf(Object o) 
              返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。 
     E remove(int index) 
              移除此列表中指定位置上的元素。 
     boolean remove(Object o) 
              移除此列表中首次出现的指定元素(如果存在)。 
    protected  void removeRange(int fromIndex, int toIndex) 
              移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。 
     E set(int index, E element) 
              用指定的元素替代此列表中指定位置上的元素。 
     int size() 
              返回此列表中的元素数。 
     Object[] toArray() 
              按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。 
    <T> T[] 
     toArray(T[] a) 
              按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 
     void trimToSize() 
              将此 ArrayList 实例的容量调整为列表的当前大小。 

 */
public class Array_list {
    public static void main(String[] args) {
        ArrayList<String> array=new ArrayList<String>();
        array.add("Demo");
        /*add();*/
        /*get(array);*/
        /*array.size();获取list中元素长度(个数)*/
        /*remove(array);*/
        /*数组的遍历 size get traverse(array) 同时配合实现*/
        /*test1():
         *         给定一个字符串数组:{"zsf","syq","zwj","ylt","zcs","msg"}
         *         将元素添加到集合,并且打印出来*/
        
        
    }

    public static void test1() {
        String [] a={"zsf","syq","zwj","ylt","zcs","msg"};
        ArrayList<String> arraylist=new ArrayList<String>();
        for (int i = 0; i < a.length; i++) {
            arraylist.add(a[i]);
        }
        for (int i = 0; i < arraylist.size(); i++) {
            String s=arraylist.get(i);
            System.out.println(s);
        }
    }

    public static void traverse(ArrayList<String> array) {
        array.add("name");
        
        for (int i = 0; i < array.size(); i++) {
            String s=array.get(i);
            System.out.println(s);
        }
    }

    public static void remove(ArrayList<String> array) {
        System.out.println(array.remove("Demo"));/*返回是否成功删除元素*/
        /*删除指定元素*/
        array.add("Demo");
        /*删除指定索引元素*/
        System.out.println(array);
        System.out.println(array.remove(0));/*返回删除的元素*/
        System.out.println(array);
    }

    public static void get(ArrayList<String> array) {
        array.add("xiaobai");
        System.out.println(array.get(0));
    }

    public static void add(ArrayList<String> array) {
        /*添加元素
         * boolean add(E e) 
                   将指定的元素添加到此列表的尾部。 
            void add(int index, E element) 
                将指定的元素插入此列表中的指定位置。
        */
        System.out.println("array:"+array);
        array.add("xiaobai");
        System.out.println("array:"+array);
        array.add(0, "hello");
        System.out.println("array:"+array);
        array.add(0, "hi");
        System.out.println("array:"+array);
    }
}

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/bai-boy/p/10300934.html

源码分析–HashSet(JDK1.8)



  HashSet为无序不可重复集合。底层几乎全部借助HashMap实现,比较简单。本篇简要分析一下HashSet源码。

 

首先是成员变量:

  1、真正保存数据的HashMap实例

private transient HashMap<E,Object> map;

  2、map实例的值

private static final Object PRESENT = new Object();

 

常用方法:

  1、add()

 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

  从这个HashSet的add()方法中,可以看出。HashSet的不可重复,主要就是利用Map相同键会进行值覆盖的特性完成的。

 

  2、迭代方法

public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

  直接就是返回Map键的迭代器。


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/sunshine-ground-poems/p/10300407.html

源码分析–HashMap(JDK1.8)



  在JDK1.8中对HashMap的底层实现做了修改。本篇对HashMap源码从核心成员变量到常用方法进行分析。

  HashMap数据结构如下:

 

  先看成员变量:

  1、底层存放数据的是Node<K,V>[]数组,数组初始化大小为16。

/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

  2、Node<K,V>[]数组最大容量

/**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

  3、负载因子0.75。也就是如果默认初始化,HashMap在size = 16*0.75 = 12时,进行扩容。

/**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

  4、将链表转化为红黑数的阀值。

 /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;

  5、红黑树节点转换为链表的阀值

/**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
    static final int UNTREEIFY_THRESHOLD = 6;

  6、转红黑树时,table的最小长度

/**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

 

介绍一下HashMap用hash值定位数组index的过程:

//HahsMap中的静态方法
static
final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

//定位计算
int index = (table.length - 1) & hash

  • 先得到key的hashCode值
  • 再将hashCode值与hashCode无符号右移16位的值进行按位异或运算。得到hash值
  • 将(table.length – 1) 与 hash值进行与运算。定位数组index

给一个长度为16的数组,以”TestHash”为key,进行定位的过程实例:

HashMap中Node就是放入的数据节点,代码定义为:

  static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
}

  Node节点保存key的hash值和K–V,借助next可实现链表。

 

红黑树封装为TreeNode节点:

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }

 

介绍get()方法:

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

  • index定位,得到该索引上的Node节点,赋值给first
  • 对first节点进行判断,如果是要找的元素,直接返回
  • first节点的next不为空,继续找
  • 如果first节点是红黑树,调用getTreeNode()获取值。
  • 不是红黑树,只能是链表。从头遍历,找到就返回。

  上面对于红黑树取值的getTreeNode()方法,看一下红黑树的遍历做法:

final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
            TreeNode<K,V> p = this;
            do {
                int ph, dir; K pk;
                TreeNode<K,V> pl = p.left, pr = p.right, q;
                if ((ph = p.hash) > h)
                    p = pl;
                else if (ph < h)
                    p = pr;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                else if (pl == null)
                    p = pr;
                else if (pr == null)
                    p = pl;
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                else if ((q = pr.find(h, k, kc)) != null)
                    return q;
                else
                    p = pl;
            } while (p != null);
            return null;
        }

  • 从do-while循环里的第一个if开始。如果当前节点的hash比传入的hash大,往p节点的左边遍历
  • 如果当前节点的hash比传入的hash小,往p节点的右边遍历
  • 如果key值相同,就找到节点了。返回
  • 左节点为空,转到右边遍历
  • 右节点为空,转到左边
  • 如果传入key实现了Comparable接口。就将传入key与p节点key进行比较,根据比较结果选择向左或向右遍历
  • 没有实现接口,直接向右遍历,找到就返回
  • 没找到,向左遍历

 

介绍put()方法:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

  • table为null,初始化
  • 定位到数组index,若该位置为空,直接放
  • 如果该位置上不为空,且hash和key与传入的值相同,说明key重复,直接将该节点赋值给e,结束循环
  • 如果该index上的节点是红黑树,调用putTreeVal()方法
  • 不是红黑树,只能是链表,遍历整个链表
  • 找到最后一个节点,在这个节点后面以k–v新增一个节点。
  • 判断链表长度,binCount达到7,也就是长度达到8。转为红黑树。
  • 遍历过程中,如果找到了相同key,就跳出循环。
  • 如果e不为空,说明遍历结束后存在key重复的节点。做值覆盖
  • 扩容判断

分析红黑树插入方法putTreeVal():

final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            TreeNode<K,V> root = (parent != null) ? root() : this;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk);
                }

                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

  • 查找root根节点
  • 从root节点开始遍历
  • 如果当前节点p的hash大于传入的hash值,记dir为-1,代表向左遍历。
  • 小于,记1,代表向右遍历
  • 如果key相同,直接返回
  • 如果key所属的类实现Comparable接口,或者key相等。先从p的左节点、右节点分别调用find(),找到就返回。
  • 没找到,比较p和传入的key值,结果记为dir
  • 根据dir选择向左或向右遍历
  • 依次遍历,直到为null,表示达到最后一个节点,插入新节点
  • 调整位置

 

分析HashMap扩容方法:

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

  • 通过一系列判断,确认新table的容量
  • 构造一个新容量的Node数组,赋值给table
  • 遍历旧table数组
  • 如果节点是单节点,直接定位到新数组对应的index位置下
  • 如果是红黑树,调用split方法
  • 遍历链表。
  • 如果e的hash值与老数组容量取与运算,值为0。索引位置不变
  • 如果e的hash值与老数组容量取与运算,值为1。这在新数组中索引的位置为老数组索引 + 老数组容量。
  • 链表放置

 

简要分析多线程下HashMap死循环问题:

  JDK1.7HashMap扩容时,对于链表位置变化,采用头插法进行操作。多线程下容易形成环形链表,造成死循环。

  JDK1.8时,会对于链表hash值与容量的计算结果。分成两部分,并改为插入到链表尾部。1.8以后不会再有死循环问题,只是有可能重复放置导致数据丢失。HashMap本身线程不安全的特性并没有改变。


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/sunshine-ground-poems/p/10283302.html

springboot 项目中获取默认注入的序列化对象 ObjectMapper



在 springboot 项目中使用 @SpringBootApplication 会自动标记 @EnableAutoConfiguration

在接口中经常需要使用时间类型,Date ,如果想要格式化成指定格式需要在 application.yml 配置文件中配置

 

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss.SSS
    time-zone: GMT+8
    defaultPropertyInclusion: non_null   #非空属性才序列化
    deserialization:
      FAIL_ON_UNKNOWN_PROPERTIES: false #未定义的key不序列化

 

AutoConfiguration 会将 ObjectMapper 注入到 spring 环境中,具体是在 JacksonAutoConfiguration 类中

 

 2 如果在 spring 环境中想要使用配置的 ObjectMapper ,使用自动注入方法即可

@Autowired
private ObjectMapper objectMapper;

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/zhaopengcheng/p/10300279.html

JDK安装、java环境配置



 

JDK是Java语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境,JAVA工具和JAVA基础的类库。

JRE(Java Runtime Environment,Java运行环境),运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库.它包括Java虚拟机(jvm)、Java核心类库和支持文件。它不包含开发工具(JDK)–编译器、调试器和其它工具。如果只需要运行Java程序或Applet,下载并安装它即可。如果要自行开发 Java软件,要下载JDK(JRE和JDK的区别,没有JDK的话,无法编译Java程序,如果想只运行Java程序,要确保已安装相应的JRE)。在JDK中附带有JRE。SE(J2SE),standard edition,标准版,是我们通常用的一个版本。EE(J2EE),enterprise edition,企业版,使用这种JDK开发J2EE应用程序。ME(J2ME),micro edition,主要用于移动设备、嵌入式设备上的java应用程序

一、JDK下载

官网:http://www.oracle.com/technetwork/java/javase/downloads/

进入官网后下载界面如下:

 

点击JDK Download进入详细下载页如下:把Accepet License Agreement勾上,系统是32位的就选windowsx86.系统64为的两个都可选择,建议选windowsx61的。

下载之后,就是傻瓜式的安装了,一直下一步。

记住你下面的安装路径,后面配置环境变量会用到。

 

二、环境变量的配置:

为了方便java程序的开发,需要配置一下环境变量,右击我的电脑->属性->高级->环境变量->用户变量中单击[新建(N)]添加以下环境变量

1.配置JAVA_HOME

新建JAVA_HOME

变量名        JAVA_HOME

变量值        C:\Program Files\Java\jdk1.8.0_131   你的jdk安装地址

             

2.配置PATH                              

变量名  Path

变量值  %JAVA_HOME%\bin;

3.配置CLASSPATH                         

新建CALSSPATH

 

变量名        CLASSPATH

变量值        .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;(注意的是最前面的“.;”  因为WINDOWS默认的搜索顺序是先搜索当前目录的,再搜索系统目录的,再搜索PATH环境变量设定的 )

 4.测试是否配置成功

window+R打开cmd窗口 输入javac出现如下说明配置成功

三、编写第一个java程序

1.在一个目录下创建一个后缀为.java的文件

2.在里面写入测试代码:

1
2
3
4
5
6
7
public 
class 
Test{
  
public 
static 
void 
main(String args[]){
 
    
System.out.println(
"Hello Java!"
);
 
  
}
}

  

3.在Test.java目录下打开cmd命令行

按住Shift+右键

4.编译程序

javac Test.java

生成java.class

5.运行程序

java Test

就这样java环境就搭建好了。

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/it89/p/10299664.html

单例设计模式的核心



单例模式,即一个类只有一个对象实例。

 

class Singleton{//没有明确定义构造方法。编译时也会默认存在一个构造方法

private static Singleton instance ;
private Singleton() {
//构造方法私有化
}
public void print() {
System.out.println(“hello world”);
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class TestDemo03092 {
public static void main(String[] args) {
//s=new Singleton(); 构造方法私有化后无法实例化对象
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
Singleton s3 = Singleton.getInstance();

System.out.println(s1);
System.out.println(s2);
System.out.println(s2);//通过输出s1,s2,s3发现均是Singleton@9e89d68。表明指向同一处。

s1.print();
s2.print();

}
}
//控制一个类中的实例化对象的产生个数。所以要先锁定类的构造方法。

//若只要一个实例化对象,那么就可以在类的内部使用static方式来定义一个对象。

//这种就叫单例设计模式

//单例模式的作用确保所有对象都访问唯一实例

 


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/abullet/p/10299343.html

手把手教你写一个java的orm(三)



使用反射解析class

上一篇我们完成了class到表映射关系的建立,但是这个并不能被代码正确处理,我们还需要让程序能够正确的识别这些映射关系。

这一篇主要讲的是建立一个从class到表的模型,使我们在class上添加的注解能够正确的被识别并处理。这里主要用到的是java中的反射相关的知识。不了解的同学请自行百度一下,不是很难~,另外这一篇也会稍微的提到一点反射的用法。

现在开始。

我们主要的需求是根绝我们添加的注解,生成各种类型的sql语句,所以我们首先要能够获取添加在java类名,属性,方法上的注解,并获取注解中的值。所以第一步:

获取特定的注解

  1. 获取class上的注解

    在Java的Class中提供了.getAnnotation(annotationClass)的方法,

    这里我在这个方法的基础上包了一层,主要是使用断言做了一些验证,在验证不通过的时候抛出我认识的异常信息。下面的方法都是如此处理的。

    /**
     * 获取类上的注解
     *
     * @param clz
     * @param annotationClass
     * @param <T>
     * @return
     */
    public static <T extends Annotation> T getAnnotation(Class<?> clz, Class<T> annotationClass) {
        Assert.notNull(clz, CLASS_NOT_NULL);
        Assert.notNull(annotationClass, ANNOTATIONCLASS_NOT_NULL);
        return clz.getAnnotation(annotationClass);
    }
  2. 获取属性上的注解

    /**
     * 获取属性上的注解
     *
     * @param field
     * @param annotationClass
     * @param <T>
     * @return
     */
    public static <T extends Annotation> T getAnnotation(Field field, Class<T> annotationClass) {
        Assert.notNull(field, FIELD_NOT_NULL);
        Assert.notNull(annotationClass, ANNOTATIONCLASS_NOT_NULL);
        return field.getAnnotation(annotationClass);
    }

这里我们获取注解的方法就写完了,可以通过这些方法获取我们需要的注解,并通过获取到的注解拿到其中的值。

大致是这样的:(这里的User.class)可以看 上一篇。

//代码(获取class上的注解)
@Test
public void getClassAnnotation() {
    Table annotation = EntityUtils.getAnnotation(User.class, Table.class);
    System.out.println(annotation.name());
}
//输出结果
user
//代码(获取field上的注解)
@Test
public void getFieldAnnotation() throws NoSuchFieldException {
    Class userClass = User.class;
    //getDeclaredField和getField是有一定区别的,这里用getDeclaredField
    Field field = userClass.getDeclaredField("createDate");
    Column annotation = EntityUtils.getAnnotation(field, Column.class);
    System.out.println(annotation.name());
}
//输出结果
create_date

这样就可以获取到我们在class上添加的注解,以及注解中的值了。下面是第二步

获取Id以及Column

这里依然是通过反射实现,主要就是获取到这个class中的所有的属性(只属于这个class的,不包括父类)后,循环遍历一遍,根据每个属性上不同的注解加以区分就好了。这里为了简单,我定义了几个方法:

  1. boolean isTable(Class aClass)

    是不是添加了@Table

  2. boolean isColumn(Field field)

    是不是添加了@Column

  3. boolean isId(Field field)

    是不是添加了@Id

这几个方法的具体代码我就不贴出来了,很简单的。下面是取出一个class中所有属性的代码:

for (Field field : clz.getDeclaredFields()) {
    if (isColumn(field)) {
        //执行需要的操作。
    }
}

在这个遍历个过程中我们可以新建一个类,里面用来存放表和class的各种对应关系。比如:

  1. class属性名称与表字段名称的对应。
  2. 表中的id是class中哪一个属性。
  3. 表的字段名称对应的是class里的那个个属性。
  4. 等等~~

代码大致是这样的 EntityTableRowMapper.java

    /**
     * id的字段名称
     */
    private String idName = null;

    /**
     * table对应的class
     */
    private Class<T> tableClass = null;

    /**
     * 对应的数据库名称
     */
    private String tableName = null;

    /**
     * 表中所有的字段
     */
    private Set<String> columnNames = null;

    /**
     * 表中所有的字段对应的属性名称
     */
    private Set<String> fieldNames = null;

    /**
     * 属性名称和数据库字段名的映射
     * K: 属性名
     * V:表字段名称
     */
    private Map<String, String> fieldNameColumnMapper = null;

    /**
     * 数据库字段名和class属性的映射
     * K:表字段名称
     * V:class属性
     */
    private Map<String, Field> columnFieldMapper = null;

这些用来描述表和class之间的关系就已经够用了。只要按照关系将里面的数据一一填充完毕就好。我写了一个方法来填充这些数据,代码是这样的:

//这里只是示例
Class clz = User.class();
//这里是主要代码
EntityTableRowMapper mapper = new EntityTableRowMapper();
Map<String, Field> columnFieldMap = EntityUtils.columnFieldMap(clz);
int size = columnFieldMap.size();
Map<String, String> fieldNameColumnMapper = new HashMap<>(size);
Set<String> columnNames = new HashSet<>(size);
Set<String> fieldNames = new HashSet<>(size);
mapper.setTableClass(clz);
mapper.setTableName(EntityUtils.tableName(clz));
mapper.setIdName(EntityUtils.idColumnName(clz));
mapper.setColumnFieldMapper(columnFieldMap);
for (Map.Entry<String, Field> entry : columnFieldMap.entrySet()) {
    String columnName = entry.getKey();
    Field field = entry.getValue();
    String fieldName = field.getName();
    fieldNameColumnMapper.put(fieldName, columnName);
    fieldNames.add(fieldName);
    columnNames.add(columnName);
}
mapper.setColumnNames(columnNames);
mapper.setFieldNameColumnMapper(fieldNameColumnMapper);
mapper.setFieldNames(fieldNames);

这时候,解析class里面的工作就完成了,下一步就是要通过拿到的数据来拼装sql了。

我下一篇再写^_^~


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/hebaibai/p/10299265.html

修复Gradle CreateProcess error=206



插件地址:https://plugins.gradle.org/plugin/ua.eshepelyuk.ManifestClasspath

修复Window系统中Gradle 路径太长问题,

Fix for Windows Gradle long classpath issue. Fixes JavaExec/Test tasks that error with message “CreateProcess error=206, The filename or extension is too long”

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.ua.eshepelyuk:ManifestClasspath:1.0.0"
  }
}

apply plugin: "ua.eshepelyuk.ManifestClasspath"


文章来源:http://www.cnblogs.com

原文地址:https://www.cnblogs.com/lowezheng/p/10298404.html