分类 Java 下的文章

智能类型推断

在Kotlin中可以使用var来定义变量,此关键字可以进行智能类型推断。效果和JS中的var类似。

//i的类型为Int类型
var i = 18;
//此时会发生报错,因为赋值的数值已经超出Int的取值范围           
i = 9999999999999;    

//j被自动判断为是一个Long型的变量
var j = 9999999999999;   

变量在初始化时就会自动判断类型,并且起到绑定作用。再次对此变量做赋值时不能赋予与其不相符的数据类型。

定义的时候也可以显示的声明此变量是什么类型:

var i : Int = 18;                         
var j : Long = 9999999999999;              

注:在变量被定义时如果没有初始化的值时,需要手动声明其类型,否则编译器无法得知此变量是哪种数据类型

var x;              //报错,原因为无预先设置初始化值。
var y : Int;        //通过声明数据类型告知编译器。

var与val

  • var 可以声明一个可读可写的变量,相当于java中的普通变量。
  • val 用于声明一个只读变量,相当于java中用final声明的变量。

两者都能用于定义变量时的智能类型判断。


不可隐式转换

在Kotlin中,并不支持类似java中的自动拆装箱。Int型不可自动转成Long型:

var a : Int = 1;
var b : Long = a.toLong();

使用Kotlin编写一个HelloWorld


fun main(args:Array<String>){
    println("Hello World");
}

fun 函数的声明
args:Array< String > 接受参数名为args,数据类型为String
println("Hello World"); 向控制台输出HelloWorld


定义变量与输出


fun main(args: Array<String>) {
    var name = "111";     //定义一个变量name并赋值为"111"
    name = "222";         //给name重新赋值为"222"  
    println(name);
}

注:Kotlin里的变量定义可使用var,它会根据后面的赋值来自动判断所定义的变量类型。如上述的name就为一个字符串。且需要注意的是如果一开始赋值的是字符串的话则此变量就不能被重新赋值为别的类型。如:

    

var name = "111";     //定义一个变量name并赋值为"111"
name = 2;         //重新赋值为2,此时会报错

在Kotlin中,var用来定义可改变的变量,而val用来定义不可改变的变量,相当于java中的final。

前言

最近在学习如何实现后台定时处理订单过期的效果。这里使用Quartz框架去做定时器的操作。


Quartz

使用Quartz作为定时器,它的原理是通过设置一个触发器和一个触发后所需要执行的方法(job类)。设定一个触发的时间,当然还有其他可以设置的参数,这里的触发器在官方文档中分为两种。这里我只用到了一个规定触发时间的:CronTrigger。


配置

这里我做的定时器是一个全局定时器,即在服务器启动的时候就会开始工作。配置的内容在xml中,当然也可以通过java代码配置,java代码的配置可以百度w3cschool的Quartz文档。

  • 创建一个定时工作执行的方法实体:

    @DisallowConcurrentExecution
    public class ResWholeTimer{
    public void executeCancel() throws JobExecutionException{

      System.out.println("取消订单"+new DateTime().toString());

    }
    }

  • 在spring配置文件中配置:

    <!-- 配置取消订单job类  -->

    <bean id="handleResWhole" class="com.eooker.lafite.modules.veh.entity.ResWholeTimer"/>

    <!-- 配置触发执行的类型以及所要执行的方法 -->

    <bean id="cancel"

      class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject">
        <ref bean="handleResWhole" />
    </property>
    <property name="targetMethod">  <!-- 要执行的方法名称 -->
        <value>executeCancel</value>
    </property>

    </bean>

    <!-- 配置取消订单触发器trigger -->
    <bean id="cancelTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean ">

    <property name="jobDetail" ref="cancel"></property>
    <property name="cronExpression" value="* * 2 * * ?"></property>

    </bean>

    <!-- 配置调度工厂 -->
    <bean id="SpringJobSchedulerFactoryBean"

      class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <!-- 取消订单的两个触发器 -->
            <ref bean="cancelTrigger"/>
            <!--<ref bean="remindTrigger" />-->
        </list>
    </property>

    </bean>

注:所配置的触发器和job类他们之间通过id来建立连接。


最后

通过这个定时器可以简单实现一些定时操作,注意这样配置的话是会在服务器启动的时候定时器就会生效,而触发的时间是按照你所设置的参数决定的(上述的value=" 2 ?"),这个配置可以查阅官方文档。如果是使用java进行配置的话可以参考一下文章:
Quartz+SpringMVC实现web定时管理任务(java)

数据格式校验的重要性

关于数据格式完整的重要性相信各位都有足够的认识,很多时候我们被要求这个类的这个字段不可为空,这个框应该填入的是数字,这个框应该填入的是一个邮箱格式/手机号码格式...,面对一连串的格式要求传统的办法是自行写一个类对传入的这个类进行一一判断审查,但是作为一名程序员必定不能容忍这种方法,故而在这里给各位介绍一种方便的检验方法。

框架&导入包

SSM、SSH框架
基本校验器:javax.validation
hibernate校验器:org.hibernate.validator
以上两个包版本最好选择比较新的,否则一些注解不支持

格式校验配置顺序

1.新建错误信息文件
2.绑定校验器
3.编写pojo类
4.根据要求给pojo类属性添加格式注解
以下是详解

第一步:新建错误信息文件

该文件是非必须的
该文件存在的意义仅仅是为了不要暴露设计者的设计思想罢了,并不是一个硬需求,到后面的注解编写就会理解这里说的是什么。
一般建议使用properties文件方便XML的读入。

第二步:绑定校验器

这一步一定要做,否则配置出来的校验器将无法工作。那么绑定是怎么个绑定法?以SSM框架为例,必然有一配置文件spring-mvc.xml,虽然每个人自己搭的SSM框架可能名字起的不是很一致,但是大概都是这种格式的名称,在其中添加进去如下代码:

<mvc:annotation-driven  validator="validator"/>
<!-- 校验器 -->
<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    <!-- hibernate校验器 -->
    <property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
    <!-- 指定校验使用的资源文件,在文件中配置校验错误信息,如果不指定则默认使用classpath下的 
            ValidationMessages.properties -->
    <property name="validationMessageSource" ref="messageSource" />
</bean>

这个配置是什么意思呢?也就是定义一数据校验器,class为:org.hibernate.validator.HibernateValidator
而下面的这个validationMessageSource是我们写的那个错误信息文件,之前说了这个是非必须的所以这个配置也是可以省略的。
假如你新建了错误信息文件,那么需要新添加这个配置:

    <!-- 校验错误信息配置文件   xxx.properties -->
<bean id="messageSource"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <!-- 资源文件名 基名-->
    <property name="basenames">
        <list>
            <value>classpath:你的文件名称,后缀可以省略</value>
        </list>
    </property>
    <!-- 资源文件编码格式 -->
    <property name="fileEncodings" value="utf-8" />
    <!-- 对资源文件内容缓存时间,单位秒 -->
    <property name="cacheSeconds" value="120" />
</bean>

这样配置就完成了。

第三,四步:编写pojo类,根据要求编写错误信息

这是本是非常简单的一件事,但是由于我们的校验器的存在使得这个类承担了更多的责任,他需要在属性上面加上注解,如下:

import javax.validation.constraints.Pattern;

import javax.validation.constraints.Size;

public class Auctionuser {
private Integer userid;

@Size(min=3,max=6,message="{register.username.length.error}")
private String username;
@Size(min=6,message="{register.password.length.error}")
private String userpassword;
@Pattern(regexp="[0-9]{18}",message="{register.usercardno.length.error}")
private String usercardno;
@Size(min=7,max=8,message="{register.usertel.length.error}")
private String usertel;

private String useraddress;

private String userpostnumber;

private Integer userisadmin;

private String userquestion;

private String useranswer;

public Integer getUserid() {
    return userid;
}

public void setUserid(Integer userid) {
    this.userid = userid;
}

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username == null ? null : username.trim();
}

public String getUserpassword() {
    return userpassword;
}

public void setUserpassword(String userpassword) {
    this.userpassword = userpassword == null ? null : userpassword.trim();
}

public String getUsercardno() {
    return usercardno;
}

public void setUsercardno(String usercardno) {
    this.usercardno = usercardno == null ? null : usercardno.trim();
}

public String getUsertel() {
    return usertel;
}

public void setUsertel(String usertel) {
    this.usertel = usertel == null ? null : usertel.trim();
}

public String getUseraddress() {
    return useraddress;
}

public void setUseraddress(String useraddress) {
    this.useraddress = useraddress == null ? null : useraddress.trim();
}

public String getUserpostnumber() {
    return userpostnumber;
}

public void setUserpostnumber(String userpostnumber) {
    this.userpostnumber = userpostnumber == null ? null : userpostnumber.trim();
}

public Integer getUserisadmin() {
    return userisadmin;
}

public void setUserisadmin(Integer userisadmin) {
    this.userisadmin = userisadmin;
}

public String getUserquestion() {
    return userquestion;
}

public void setUserquestion(String userquestion) {
    this.userquestion = userquestion == null ? null : userquestion.trim();
}

public String getUseranswer() {
    return useranswer;
}

public void setUseranswer(String useranswer) {
    this.useranswer = useranswer == null ? null : useranswer.trim();
}
}

可以看到其实它与一般的bean差不多但是多了一些注解例如@Size,@Pattern等,英文水平稍微好点的直接就理解这是什么了,这两个注解一个是属于javax.validation.constraints.Size另外一个属于javax.validation.constraints.Pattern,答案很明显了这两个就是承担校验的注解。以下是大部分注解的名称与使用方法:
827161-20161022150209967-1863141332.png
图片来源于:https://www.cnblogs.com/xiaogangfan/p/5987659.html
如@Size(min=3,max=6,message="{register.username.length.error}")这句注释的意思就是这个属性的最低长度为3,最大长度为6,一旦错误后就会产生register.username.length.error这个错误,但是我们有个问题,register.username.length.error到底是个什么?还记得我们前面新建的错误信息文件吗,properties文件是一个以K-V形式存储信息的一个文件,下面是这个文件的截图:
TIM截图20180626155112.png
我们可以在其中找到
register.username.length.error=u7528u6237u540Du957Fu5EA6u5FC5u987Bu4E3A3u52306u4F4D
后面的那个unicode编码是中文字符串,把鼠标移动到这串unicode编码中就能查看当前信息:
TIM截图20180626155243.png
当然这个方法不是唯一的,你也可以直接在message中直接写入中文也是支持的,但是使用错误信息文件的方法一方面不会导致我们的错误信息暴露,另外一方面也易于维护,本人强烈推荐。

测试

做测试以前需要我们在我们的controller加入一些注解:

@RequestMapping("/register")
public String register(Model model,@Validated Auctionuser user,BindingResult bindResult) {
    if(bindResult.hasErrors()) {
        List<FieldError> errors = bindResult.getFieldErrors();
        for(FieldError error:errors) {
            model.addAttribute(error.getField(), error.getDefaultMessage());
        }
        model.addAttribute("registerUser", user);
        return "register";
    }
    userservice.insertAuctionUser(user);
    System.out.println("--------------->添加用户成功");
    return "login";
}

这个controller很简单,用户在前端页面填写信息提交到这个方法,但是我们还是出现了一些陌生的东西例如:@Validated,这个注解就是告诉容器在传入这个方法前先对这个Auctionuser类的这个user对象进行一次数据格式校验,校验的就是刚刚我们写的那些@Size和@Pattern,假如出现了错误信息那么就把错误信息填充到BindingResult类中,一次校验对应一次BindingResult。
error.getField()获取属性名称(username,password等)
error.getDefaultMessage()获取错误信息message
这代码也不是很难所以也就不怎么解释了。
运行结果截图:
before:
before.png
after:
after.png
是不是挺有趣?


Jeesite数据格式校验

Jeesite是一个高度封装的框架,很多东西thinkgem老哥已经给我们封装好了,但是Jeesite的官方文档只能说内容太少太少了,很多地方只能看他的源码才能知道它的流程,最近更新了一下项目,把以前的校验方法都删除变成了上面这种格式,下面说一下Jeesite是如何使用数据校验的:
首先pojo类还是需要我们写注解,错误信息文件操作照常,接下来是重点:在controller中不要使用@Validated对bean进行校验!!!,Jeesite有自己的错误检查框架,每一个controller都会继承了一个名为BaseController的类,一旦出错后他会拦截异常直接跳转到对应的方法,代码如下,你可以通过ctrl进去basecontroller进行查看:

/**
 * 参数绑定异常
 */
@ExceptionHandler({BindException.class, ConstraintViolationException.class, 
ValidationException.class})
public String bindException() {  
    return "error/400";
}

而这个拦截后就直接跳转到了400页面我们就无法和上面那样对错误信息进行操作了,所以我们应该采用thinkgem老哥给我们留下的另外一条路:

/**
 * 服务端参数有效性验证
 * @param object 验证的实体对象
 * @param groups 验证组
 * @return 验证成功:返回true;严重失败:将错误信息添加到 message 中
 */
protected boolean beanValidator(Model model, Object object, Class<?>... groups) {
    try{
        BeanValidators.validateWithException(validator, object, groups);
    }catch(ConstraintViolationException ex){
        List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
        list.add(0, "数据验证失败:");
        addMessage(model, list.toArray(new String[]{}));
        return false;
    }
    return true;
}

/**
 * 服务端参数有效性验证
 * @param object 验证的实体对象
 * @param groups 验证组
 * @return 验证成功:返回true;严重失败:将错误信息添加到 flash message 中
 */
protected boolean beanValidator(RedirectAttributes redirectAttributes, Object object, Class<?>... 
groups) {
    try{
        BeanValidators.validateWithException(validator, object, groups);
    }catch(ConstraintViolationException ex){
        List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
        list.add(0, "数据验证失败:");
        addMessage(redirectAttributes, list.toArray(new String[]{}));
        return false;
    }
    return true;
}

这两个方法都是可以进行数据校验的方法,只要在controller中调用然后根据需要选择使用哪一个就好:
需要跳转到另外一个页面的使用上面的那个,需要重定向到原来页面的使用下面的那个。
但是thinkgem老哥还是留了点小瑕疵,他的错误信息把我们的属性暴露出来了,所以我把他的信息方法改成了:

/**
 * 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+ message>.
 */
@SuppressWarnings("rawtypes")
public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> 
constraintViolations,
        String separator) {
    List<String> errorMessages = Lists.newArrayList();
    for (ConstraintViolation violation : constraintViolations) {
        //errorMessages.add(violation.getPropertyPath() + separator + 
violation.getMessage());
        errorMessages.add(violation.getMessage());
    }
    return errorMessages;
}

注释的那句是原来的,而我们只需要错误信息不需要属性名称,除非你知道你在做什么而且你觉得有必要你可以不改。
先码到这,后面有更多研究会进行修改。

什么是Supplier

supplier接口是JAVA8以后配合lambda表达式和函数式接口编程(FunctionInterface,以下简称FI)组合使用的一个接口,对外表现为双冒号"::",顺便说下"->"符号对应的是Function接口中的Reply方法例如:

Supplier<Person> persionSupplier = Person::new;
Arrays.asList("a","b","c").forEach(e->System.out.println(e));

其中的双冒号点击进去就是这个Supplier接口:

package java.util.function;

/**
 * Represents a supplier of results.
 *
 * <p>There is no requirement that a new or distinct result be returned each
 * time the supplier is invoked.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #get()}.
 *
 * @param <T> the type of results supplied by this supplier
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {

/**
 * Gets a result.
 *
 * @return a result
 */
T get();
}

可以看到这份代码中,有一个比较奇特的注解@FunctionalInterface,这是一个函数式接口的声明。该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

Supplier的使用

我们可以从源代码中看出来,Supplier接口中只有一个方法,就是一个get方法且无需参数,返回的是泛型T。一般的类强制转换为一个接口编译器(eclipse)不会报错但是运行时会报错。假设现在我们新建一个普通类Person:

public class Person{
    Integer id;
    String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    public Person() {
        this.name="mike";
                    System.out.println("创建了一个对象");
    }
    public Person(Integer x,String y) {
        // TODO Auto-generated constructor stub
        this.id = x;
        this.name=y;
    }
//        public String toString() {
//            return this.name;
//        }
    
}

Person类中只有基本的属性与get/set方法,最最常见的对象创建方法就是直接一个new,这是正确的我们也恰好使用的是这个特性

Person::new

在编译器中这句代码相当于声明一个Person类型的Supplier,返回一个Supplier<Person>接口,接下来是重点:使用get方法会执行Person类的无参构造方法,每一次使用get方法都会新建一个对象,所以不是Supplier保存了Person对象,而是需要使用时创建一个,在main中运行以下代码:

    Supplier<Person> persionSupplier = Person::new;
    System.out.println("-------------分割线-------------");
    System.out.println("supplier中的实体类地址为:"+persionSupplier.get().toString());
    persionSupplier.get().setName("coda");
    System.out.println("更改后supplier中的实体类地址为:"+persionSupplier.get().toString());

而输出的结果为:
-------------分割线-------------
创建了一个对象
supplier中的实体类地址为:Person@87aac27
创建了一个对象
创建了一个对象
更改后supplier中的实体类地址为:Person@3e3abc88

可以得知:
第一句代码并没有调用构造方法
第三句代码调用了构造方法所以给出了地址 87aac27
第四句代码也调用了构造方法所以他其实已经不是上面的那个地址为87aac27的Person对象而是一个新的对象了
第五句代码也调用了构造方法所以他是一个全新的对象,地址为:3e3abc88

lambda表达式与Supplier的组合使用

看了以上的同学可能会问,至于吗,我只是想新建一个对象而已,我一个new就能解决的事需要搞这么多花里花俏的?
冷静老哥。JAVA8说了是扩展对象的用法,而不是替代传统的new方式,以下贴代码,注释不需要管,但是也是个学习的东西所以顺便贴上去了,可以解除注释试一下输出:

List<Person> list = new ArrayList();  
list.add(new Person(1, "haha"));  
list.add(new Person(2, "rere"));  
list.add(new Person(3, "fefe"));   
//Map<Integer, Person> mapp = list.stream().collect(Collectors.toMap(Person::getId, Function.identity())); 
//Map<Integer, Person> mapp = list.stream().collect(Collectors.toMap(x -> x.getId(), x->x));
//System.out.println(mapp);
Map<Integer, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName)); 
//Map<Integer, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::getName,(x1,x2)->x1));  

以下的功能很简单,把list中的三个Person对象变成一个Map。Map是一个K-V形式的集合,他要求key不可以重复,所以当我们把Person中的构造方法的123改成相同的那就会出现报错,注意下面那句注释(x1,x2)->x1意味着当第一个和第二个参数的key一致时选用第一个元素,这样即算有两个相同的KEY也不会影响到代码的运行,->是什么我会在另外一篇博文中详细介绍,现在我们只关心::,也就是Supplier在这段代码中起什么作用。Person::getId, Person::getName这句话到底是什么意思,这个我们直接看map的输出就好:
{1=haha, 2=rere, 3=fefe}
看到这个结果我们就知道了Person::getId指的是使用Person对象中的ID作为键,使用Person::getName作为Map中元素的值,getId,getName指的是list中的元素使用的方法名字。虽然可能这段代码还是太过于复杂但是这是我觉得比较适合用来诠释JAVA8的操作拓展改变的所以想了一下还是用的这个例子。

总结

Supplier单独的使用并没有什么意义,但是如果要使用JAVA8的其他新特性例如lambda表达式,Function,stream,Collectors等则需要花点时间看一下,否则会发现同样是JAVA为啥他写的像外星文,我写的像是幼儿园的这种闹剧。