2018年8月27日 星期一

@ComponentScan 排除或包括 @Component (Spring 3.x 二十六)


AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Apple.class);
for (String s : app.getBeanDefinitionNames()) {
    System.out.println(s);
}
app.close();

※Apple.class 不管有沒有加 annotation 一定會有



※Apple.class

// @ComponentScan(excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Component.class) })
// @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Component.class) })
    
// @ComponentScan(excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Repository.class) })
// @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = Controller.class) })
// @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Chicken.class) })
@ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class) })
// @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class) })

※如果使用 includeFilters 時,一定要將 useDefaultFilters 設為 false,否則無效,表示只包括什麼

※第一個和第二個是有問題的,使用 excludeFilters 和 Component,結果是什麼都沒有;
使用 includeFilters 和 Component,結果是什麼都有



public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException {
        // System.out.println(metadataReader.getAnnotationMetadata().getAnnotationTypes());
        // System.out.println(metadataReader.getClassMetadata().getClassName());//
        // package.class
        // System.out.println(metadataReader.getResource().getFilename());// name.class
        // System.out.println(metadataReaderFactory.getMetadataReader(metadataReader.getResource()).getAnnotationMetadata().getSuperClassName());
    
        Set<String> set = metadataReader.getAnnotationMetadata().getAnnotationTypes();
        if (new ArrayList<String>(set).contains("org.springframework.stereotype.Component")) {
            return false;
        }
        return true;
    }
}

※使用這個 class,配合上面的 FilterType.CUSTOM,可以排除或包括 @Component

2018年8月25日 星期六

Quartz 2.x 整合 SpringBoot 2.x


buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
    
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
    
repositories {
    mavenCentral()
}
    
ext {
    springVersion = '4.3.18.RELEASE'
    springBootVersion = '2.0.3.RELEASE'
    quartzVersion = '2.2.1'
}
    
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile group: 'org.springframework', name: 'spring-context-support', version: springVersion
    compile group: 'org.springframework', name: 'spring-tx', version: springVersion
    compile group: 'org.quartz-scheduler', name: 'quartz', version: quartzVersion
    compile group: 'org.quartz-scheduler', name: 'quartz-jobs', version: quartzVersion
}




@SpringBootApplication
@ComponentScan("annotation")
public class Library {
    public static void main(String[] args) {
        SpringApplication.run(Library.class, args);
    }
}

※@ComponentScan 的程式碼可看我的另一篇,最下面的 annotation

2018年8月22日 星期三

Quartz 2.x 整合 Spring 3/4

※整合 Spring

官網連結

※build.gradle

ext {
    springVersion = '4.3.18.RELEASE'
    quartzVersion = '2.2.1'
}
    
dependencies {
    compile("org.springframework:spring-context:$springVersion")
    compile group: 'org.springframework', name: 'spring-context-support', version: springVersion
    compile group: 'org.springframework', name: 'spring-tx', version: springVersion
    compile group: 'org.quartz-scheduler', name: 'quartz', version: quartzVersion
    compile group: 'org.quartz-scheduler', name: 'quartz-jobs', version: quartzVersion
}

※一定要有 quartz 的 jar 包,否則編譯時居然是報找不到 spring jar 包的編譯錯誤,很奇怪


官網有說明,提供了 JobDetailFactoryBean 和 MethodInvokingJobDetailFactoryBean
而每一個又分成使用 SimpleTriggerFactoryBean 和 SchedulerFactoryBean


※XML設定


※使用 JobDetailFactoryBean

public class MyJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
        System.out.println("xxxxxxxxxxxxxxxxxxxxx");
        // ctx.getJobDetail().getJobDataMap().get("aaa");
    }
}




※quartz.xml 之 SimpleTriggerFactoryBean

<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="job.MyJob" />
    <!-- <property name="durability" value="true" /> -->
    <property name="jobDataAsMap">
        <map>
            <entry key="aaa" value="111"/>
        </map>
    </property>
</bean>
    
<bean id="simpleTrigger"
    class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="startDelay" value="10" />
    <property name="repeatInterval" value="2000" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTrigger" />
        </list>
    </property>
</bean>

※durability 預設為 false,Spring 3.x 要為 true 才可以,否則會出「Jobs added with no trigger must be durable」的錯
原文是 Specify the job's durability, i.e. whether it should remain stored in the job store even if no triggers point to it anymore.
大概是說不管有沒有 trigger 指向它,是否應該保留在 job store 裡,反正 3.x 不是 true 就會報這個錯
關於 jobStore,可看官網第九篇,有三種


※quartz.xml 之 SchedulerFactoryBean

<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="job.MyJob" />
</bean>
    
<bean id="cronTrigger"
    class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="cronExpression" value="0/2 0 0 * * ?" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger" />
        </list>
    </property>
</bean>





※MethodInvokingJobDetailFactoryBean

package job;
    
public class MyJob {
    public void getXxx() {
        System.out.println("xxxxxxxxxxxxxxxxxxxxx");
    }
}

※使用 JobDetailFactoryBean 要繼承;使用 MethodInvokingJobDetailFactoryBean 不用


※quartz.xml 之 SimpleTriggerFactoryBean

<bean id="myJob" class="job.MyJob" />
    
<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="myJob" />
    <property name="targetMethod" value="getXxx" />
    <!-- <property name="concurrent" value="false"/> -->
</bean>
    
<bean id="simpleTrigger"
    class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="startDelay" value="10" />
    <property name="repeatInterval" value="2000" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTrigger" />
        </list>
    </property>
</bean>

※concurrent 預設為 true,如果改成 false,和 @DisallowConcurrentExecution 是一樣的意思,可在 MyJob 加這個註解,和 concurrent 不寫或寫 true 比較看看,程式碼可看這篇


※quartz.xml 之 SchedulerFactoryBean

<bean id="myJob" class="job.MyJob" />
    
<bean id="jobDetail"
    class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="myJob" />
    <property name="targetMethod" value="getXxx" />
    <!-- <property name="concurrent" value="false" /> -->
</bean>
    
<bean id="cronTrigger"
    class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="cronExpression" value="0/2 0 0 * * ?" />
</bean>
    
<bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger" />
        </list>
    </property>
</bean>




※測試類

new ClassPathXmlApplicationContext("quartz.xml");





※整合 Spring Web

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/quartz.xml</param-value>
</context-param>
    
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

※gradle 增加 compile("org.springframework:spring-web:$springVersion"),伺服器一啟動就開始跑了



※整合 SpringWebMVC

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/quartz.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

※gradle 增加 compile("org.springframework:spring-webmvc:$springVersion"),伺服器一啟動就開始跑了




※annotation 設定



@Component
public class MyAnnotationJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
        System.out.println("xxxxxxxxxxxxxxxxxxxxx");
    }
    
    // public void getXxx() {
    //     System.out.println("xxxxxxxxxxxxxxxxxxxxx");
    // }
}




@Configuration
@ComponentScan("annotation")
public class QuartzConfiguration {
    @Autowired
    private MyAnnotationJob myJob;
    
    // @Bean
    // public JobDetailFactoryBean jobDetail() {
    // JobDetailFactoryBean job = new JobDetailFactoryBean();
    // job.setJobClass(MyAnnotationJob.class);
    // job.setDurability(true);
    // return job;
    // }
    
    @Bean
    public MethodInvokingJobDetailFactoryBean jobDetail() {
        MethodInvokingJobDetailFactoryBean job = new MethodInvokingJobDetailFactoryBean();
        job.setTargetObject(myJob);
        job.setTargetMethod("getXxx");
        return job;
    }
    
    @Bean
    public SimpleTriggerFactoryBean simpleTrigger() {
        SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
        trigger.setJobDetail(jobDetail().getObject());
        trigger.setStartDelay(10);
        trigger.setRepeatInterval(2000);
        return trigger;
    }
    
    // @Bean
    // public CronTriggerFactoryBean cronTriggerBean() {
    // CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
    // cronTriggerFactoryBean.setJobDetail(jobDetail().getObject());
    // cronTriggerFactoryBean.setCronExpression("*/2 * * * * ?");
    // return cronTriggerFactoryBean;
    // }
    
    @Bean
    public SchedulerFactoryBean schedulerFactory() {
        SchedulerFactoryBean schedule = new SchedulerFactoryBean();
        schedule.setTriggers(simpleTrigger().getObject());
        return schedule;
    }
}




※spring 測試

new AnnotationConfigApplicationContext(annotation.QuartzConfiguration.class);



※SpringWeb的 web.xml 設定

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>annotation.QuartzConfiguration</param-value>
</context-param>
    
<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
    
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>




※SpringWebMVC的 web.xml 設定

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>annotation.QuartzConfiguration</param-value>
    </init-param>
    
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>



2018年8月20日 星期一

Hello Quartz (Quartz 2.x 一)

※文檔

官網教學連結
官網設定教學連結

W3Cschool Quartz 官網

好心網友翻譯的連結:
第一課 使用 Quartz
第二課 Quartz API,Jobs 和 Triggers 介紹
第三課 關於更多的 Jobs和 JobDetails
第四課 關於更多的 Triggers
第五課 SimpleTriggers
第六課 CronTriggers
第七課 TriggerListeners & JobListeners
第八課 SchedulerListeners
第九課 JobStores
第十課 Configuration, Resource Usage和SchedulerFactory
第十一課 進階(企業)功能
第十二課 Quartz 其他功能


※Hello Quartz

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
    
public class TestQuartz {
    public static void main(String[] args) {
        JobDetail job = JobBuilder.newJob(MyJob.class)
            .withIdentity("myJob1", "group1")
            .build();
    
        Trigger trigger = TriggerBuilder
            .newTrigger()
            .withIdentity("myTrigger1", "group2")
            .startNow()
            .withSchedule(SimpleScheduleBuilder
                .simpleSchedule()
                .withIntervalInSeconds(3)
                .repeatForever())
            .build();
    
        System.out.println(job.getKey()); // group1.myJob1
        System.out.println(trigger.getKey());// group2.myTrigger1
    
        try {
            SchedulerFactory schedFact = new StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();
            System.out.println(trigger.getJobKey()); // null
            sched.scheduleJob(job, trigger);
            System.out.println(trigger.getJobKey()); // 註冊之後才能取得 group1.myJob1
            sched.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

※注意官網有靜態 import,如下:
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
之後的代碼,我也會用這種方式

※假設 SchedulerFactory 和 Scheduler 寫在最上面,然後馬上就 start(),最後在呼叫 JobDetail、
Trigger並註冊 scheduleJob,也是可以運行的


public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("xxxxxxxxxxxxx");
    }
}

※Job 就是時間到後要執行的方法



※JobDataMap

JobDetail job = newJob(MyJob.class)
    .withIdentity("myJob", "group1")
    .usingJobData("aaa", "111")
    .usingJobData("bbb", 222)
    .build();
    
Trigger trigger = newTrigger()
    .withIdentity("myTrigger1", "group2")
    .startNow()
    .withSchedule(simpleSchedule()
    .withIntervalInSeconds(3)
    .repeatForever())
    .build();
    
try {
    SchedulerFactory schedFact = new StdSchedulerFactory();
    Scheduler sched = schedFact.getScheduler();
    sched.scheduleJob(job, trigger);
    sched.start();
} catch (SchedulerException e) {
    e.printStackTrace();
}

※使用 JobDataMap 可以塞值



public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
    
        // JobDataMap dataMap = context.getMergedJobDataMap();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String a = dataMap.getString("aaa");
        int b = dataMap.getInt("bbb");
    
        System.out.println("key=" + key); // group1.myJob
        System.out.println("a=" + a); // 111
        System.out.println("b=" + b);// 222
    }
}

※取值時,可以使用 getMergedJobDataMap() 或 getJobDetail().getJobDataMap(),但還是有區別的,看下面的 @PersistJobDataAfterExecution



※@DisallowConcurrentExecution

JobDetail job = newJob(MyJob.class).withIdentity("myJob", "group1").build();
Trigger trigger = newTrigger()
    .withIdentity("myTrigger1", "group2")
    .startNow()
    .withSchedule(simpleSchedule()
        .withIntervalInSeconds(3)
        .repeatForever())
    .build();
    
try {
    SchedulerFactory schedFact = new StdSchedulerFactory();
    Scheduler sched = schedFact.getScheduler();
    sched.scheduleJob(job, trigger);
    sched.start();
} catch (SchedulerException e) {
    e.printStackTrace();
}




@DisallowConcurrentExecution
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            System.out.println("前=" + new Date());
            Thread.sleep(5000);
            System.out.println("後=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

※我設定每3秒執行一次,但 Job 裡會睡 5 秒,所以我還沒加這個註解前會是如下的結果:
前=Tue Aug 21 14:17:44 SGT 2018
前=Tue Aug 21 14:17:47 SGT 2018
後=Tue Aug 21 14:17:49 SGT 2018
前=Tue Aug 21 14:17:50 SGT 2018
後=Tue Aug 21 14:17:52 SGT 2018
前=Tue Aug 21 14:17:53 SGT 2018
後=Tue Aug 21 14:17:55 SGT 2018
前=Tue Aug 21 14:17:56 SGT 2018
後=Tue Aug 21 14:17:58 SGT 2018
前=Tue Aug 21 14:17:59 SGT 2018
後=Tue Aug 21 14:18:01 SGT 2018
前=Tue Aug 21 14:18:02 SGT 2018
後=Tue Aug 21 14:18:04 SGT 2018

※44~49 才是一組,中間會有下一次的執行,但如果加上這個註解,結果如下:

前=Tue Aug 21 14:19:12 SGT 2018
後=Tue Aug 21 14:19:17 SGT 2018
前=Tue Aug 21 14:19:17 SGT 2018
後=Tue Aug 21 14:19:22 SGT 2018
前=Tue Aug 21 14:19:22 SGT 2018
後=Tue Aug 21 14:19:27 SGT 2018
前=Tue Aug 21 14:19:27 SGT 2018
後=Tue Aug 21 14:19:32 SGT 2018
前=Tue Aug 21 14:19:32 SGT 2018
後=Tue Aug 21 14:19:37 SGT 2018
前=Tue Aug 21 14:19:37 SGT 2018

※此時中間沒有插入下一次的執行,是以執行完為主的



※@PersistJobDataAfterExecution

JobDetail job = newJob(MyJob.class)
    .withIdentity("myJob", "group1")
    .usingJobData("aaa", "111")
    .usingJobData("bbb", 222)
    .build();
    
Trigger trigger = newTrigger()
    .withIdentity("myTrigger1", "group2")
    .startNow()
    .withSchedule(simpleSchedule()
        .withIntervalInSeconds(3)
        .repeatForever())
    .build();
    
try {
    SchedulerFactory schedFact = new StdSchedulerFactory();
    Scheduler sched = schedFact.getScheduler();
    sched.scheduleJob(job, trigger);
    sched.start();
} catch (SchedulerException e) {
    e.printStackTrace();
}




@PersistJobDataAfterExecution
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // JobDataMap dataMap = context.getMergedJobDataMap(); // 不會更新的
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        System.out.println("a=" + dataMap.getString("aaa")); // 111|333
        System.out.println("b=" + dataMap.getInt("bbb"));// 222|444
    
        dataMap.put("aaa", "333");
        dataMap.put("bbb", 444);
    }
}

※註解的意思是執行後可保存資料,如範例的 333 和 444,有加上這個註解以及使用的是 getJobDetail().getJobDataMap() 才可以,使用 getMergedJobDataMap() 不會

※第一次執行後是 111 和 222,之後就都會是 333 和 444 了

※官網有說明加上這個註解後,要考慮是不是也要加上 @DisallowConcurrentExecution

2018年8月14日 星期二

JUnit 4 整合 SpringBoot 2.x

※build.gradle

compile 'org.springframework.boot:spring-boot-starter:2.0.3.RELEASE'
testCompile 'org.springframework.boot:spring-boot-starter-test:2.0.3.RELEASE'




※啟動類

@SpringBootApplication
@ComponentScan("ooo.xxx")
public class Xxx {
    public static void main(String[] args) {
        SpringApplication.run(Xxx.class, args);
    }
}




※測試類

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { Xxx.class })
public class LibraryTest {
    @Autowired
    Animal animal;
    
    @Autowired
    Bird bird;
    
    @Test
    public void testXxx() {
        System.out.println(animal);
        System.out.println(bird);
    }
}

※Animal、Bird 類別在 ooo.xxx 的 package,如果和 LibraryTest 不同 package,那就要在啟動類加 @ComponentScan

2018年8月12日 星期日

Task (Gradle 4.x 六)

※三種宣告

task 'xxx' { // 「'」可省略
    println 'xxxxx'
}
    
tasks.create('zzz') {
    println 'zzzzz'
}
    
tasks.create(name: 'ooo') {
    println 'ooooo'
}
    
task findTask {
    // println tasks.findByPath('xxx')?.name
    try {
        println tasks.getByPath('xxx').name // 有Exception時,? 無效
    } catch (UnknownTaskException e) {
        println '我錯了'
    }
}

※findByPath 找不到回傳 null; getByPath 找不到拋例外


※task 群組

task setInfo1(group: 'aaa', description: 'I am 1') {
    println tasks.findByPath('setInfo1')?.group // group 即可抓到,但 this.group 是抓這個專案的
    println tasks.findByPath('setInfo1')?.description
}
    
task setInfo2 {
    setGroup('aaa')
    setDescription('I am 2')
    println tasks.findByPath('setInfo2')?.group
    println tasks.findByPath('setInfo2')?.description
}

※gradle tasks 會看到,參數可看 Task


※task 的執行順序

task taskOrder1() {
    println 'taskOrder1'
    
    doLast {
        println 'a'
    }
    
    doFirst {
        println '1'
    }
    
    doLast {
        println 'b'
    }
    
    doFirst {
        println '2'
    }
}
    
    
task taskOrder2 {
    println 'taskOrder2'
    
    doLast {
        println 'c'
    }
    
    doFirst {
        println '3'
    }
    
    doLast {
        println 'd'
    }
    
    doFirst {
        println '4'
    }
}

※doFirst、doLast 都是執行階段才會執行,所以下 gradle clean 或 gradle task 名稱只會執行設定階段,並不會只有執行指定的 task,所有 task 都會執行
要 run 執行階段,要下 gradle task 名稱,但要有 doFirst 或 doLast,且只會執行指定的 task

※task taskOrder1(dependsOn: 'taskOrder2') // 只針對執行階段

※taskOrder1.dependsOn('taskOrder2'),也可用這種方式,寫在括號裡是被依賴的,被依賴的會先執行

※也可以 mustRunAfter 方法


※動態依賴

task taskOrder1 {
    dependsOn tasks.findAll {
        it.name.startsWith('xxx')
    }
}

※xxx 開頭的 task 才會被依賴


※task 的其他寫法

task taskOrder1 {
    doFirst {
        println '1'
    }
    
    doFirst {
        println '2'
    }
}
    
taskOrder1.doFirst {
    println '3'
}




※doLast 等同 <<

task taskOrder2 << {
    println 'taskOrder2'
}
    
task taskOrder2 {
    println 'taskOrder2'
    
    doLast {
        println '3'
    }
    
    doFirst {
        println '2'
    }
} << {
    println '4'
}

※最後的 << 只能一次

2018年8月11日 星期六

Executors

※基本的執行緒池

// ExecutorService es = Executors.newFixedThreadPool(3);
// ExecutorService es = Executors.newCachedThreadPool();
ExecutorService es = Executors.newSingleThreadExecutor();
    
for (int i = 1; i <= 7; i++) {
    es.execute(() -> {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int j = 1; j <= 5; j++) {
            System.out.println(Thread.currentThread().getName() + "->" + j);
        }
    });
}
es.shutdown();

※newFixedThreadPool:固定大小的執行緒
newCachedThreadPool:無限大小的執行緒
newSingleThreadExecutor:單一的執行緒

※shutdown():不增加新的執行緒,當前執行緒不會關閉
shutdownNow():不增加新的執行緒,當前執行緒也會關閉


※排程

ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
ses.schedule(() -> {
    System.out.println("yeah");
}, 2L, TimeUnit.SECONDS);
ses.shutdown();
    
ScheduledExecutorService sess = Executors.newScheduledThreadPool(3);
sess.scheduleAtFixedRate(() -> {
    System.out.println("xxx");
}, 2L, 1L, TimeUnit.SECONDS);
try {
    Thread.sleep(5000L);
    sess.shutdown();
} catch (InterruptedException e) {
    e.printStackTrace();
}
    
ScheduledExecutorService seas = Executors.newSingleThreadScheduledExecutor();
seas.scheduleAtFixedRate(() -> {
    System.out.println("xxx");
}, 2L, 1L, TimeUnit.SECONDS);
try {
    Thread.sleep(5000L);
    seas.shutdown();
} catch (InterruptedException e) {
    e.printStackTrace();
}

※newScheduledThreadPool、newSingleThreadScheduledExecutor 回傳 ScheduledExecutorService,裡面有執行一次的 schedule() 和重覆執行的 scheduleAtFixedRate()

※重覆執行要注意太早 shutdown() 就不執行了



※1.8 的 newWorkStealingPool

Executors.newWorkStealingPool();

※底層第一行就是 new ForkJoinPool,ForkJoinPool 是 1.7 增加的類別,但這個執行緒池是 1.8 才新增的,可參考這裡


※ThreadPoolExecutor

Executors 底層使用的就是這個,共有7個參數
new ThreadPoolExecutor(5,
20,
10,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

表示一開始就有 5 個執行緒可以用,最多20個
假設有30個 request 進來,5個先執行,然後25個會放在第5個參數的 queue 裡
再來15個執行緒去 queue 抓來執行,最後10個看第7個參數的放棄策略
執行完的15個執行緒,過10毫秒 (第3和4的參數) 就關閉
第六個參數是將 Runnable 包裝成 Thread

以下是第 5~7 可用的參數
BlockingQueue
ArrayBlockingQueue
DelayedWorkQueue 基於 heap 的資料結構,按照時間順序將每個任務進行排序
BlockingDeque
LinkedBlockingDeque 預設是 Integer.MAX_VALUE,所以有 OOM 的風險
SynchronousQueue 一生產就消費,如沒有消費者就阻塞,所以沒有容量大小
DelayQueue 只能消費已過期的
TransferQueue
LinkedTransferQueue 很像 SynchronousQueue,但有容量,消費者沒取到就會阻塞
LinkedBlockingQueue 預設是 Integer.MAX_VALUE,所以有 OOM 的風險
PriorityBlockingQueue 按照自定義的優先級消費
ThreadFactory
DaemonThreadFactory 創建背景執行的執行緒,之後出來的執行緒都是背景執行緒
DefaultThreadFactory 預設的執行緒工廠
PrivilegedThreadFactory 創建與當前執行緒具有相同權限的新執行緒
RejectedExecutionHandler
DiscardOldestPolicy 丟棄最老的任務,就是在 queue 裡面的
AbortPolicy 新來的任務直接丟棄並報異常
CallerRunsPolicy 同步調用,start() 調用後執行 run(),但這個策略是直接調用 run()
DiscardPolicy 新來的任務直接丟棄但不報異常 

ThreadLocal


public class Test {
    // private Map<Thread, Double> map = new HashMap<>();
    private ThreadLocal<Double> threadLocal = new ThreadLocal<>();
    
    public static void main(String[] args) {
        new Test().test();
    }
    
    private void test() {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                Double v = Math.random();
                System.out.println(Thread.currentThread().getName() + " 的 值 是 =" + v);
                // map.put(Thread.currentThread(), v);
                threadLocal.set(v);
    
                new innerAClass().ia();
                new innerBClass().ib();
            }).start();
    
        }
    }
    
    class innerAClass {
        public void ia() {
            // System.out.println(Thread.currentThread().getName() + "(a)取出的值是=" +
            // map.get(Thread.currentThread()));
            System.out.println(Thread.currentThread().getName() + "(a)取出的值是=" + threadLocal.get());
        }
    }
    
    class innerBClass {
        public void ib() {
            // System.out.println(Thread.currentThread().getName() + "(b)取出的值是=" +
            // map.get(Thread.currentThread()));
            System.out.println(Thread.currentThread().getName() + "(b)取出的值是=" + threadLocal.get());
        }
    }
}



Callable、Future、CompletionService

※Callable、Future

ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> future = es.submit(() -> "yeah!");
    
System.out.println("等一兮呢");
try {
    System.out.println(future.get());
    es.shutdown();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

※submit 裡的參數就是 Callable

※Callable 和 Runnable 類似,但有回傳值

※Future 可以用 get() 取得 Runnable 的回傳值


※get 參數

ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> future = es.submit(() -> {
    Thread.sleep(1000L);
    return "yeah!";
});
    
System.out.println("等一兮呢");
try {
    System.out.println(future.get(500L, TimeUnit.MILLISECONDS));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    e.printStackTrace();
} finally {            
    es.shutdown();
}

※如果超過 get 設定的時間,就會出現 java.util.concurrent.TimeoutException



※CompletionService

ExecutorService 有多個 Callable 時,每個 Callable 都會回傳一個Future,使用 get 取得結果時,不一定會是第一個 Future,使用 CompletionServce 可以解決這個問題

ExecutorService es = Executors.newFixedThreadPool(3);
CompletionService<String> ecs = new ExecutorCompletionService<>(es);
    
for (int i = 0; i < 3; i++) {
    int temp = i;
    ecs.submit(() -> {
        Thread.sleep(new Random().nextInt(10000));
        return "嘻嘻嘻" + temp;
    });
}
    
for (int i = 0; i < 3; i++) {
    Future<String> future;
    try {
        future = ecs.take();
        System.out.println(future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    
    if(i == 2) {
        es.shutdown();
    }
}

※一組執行緒,誰先執行完誰就先執行

Project 的依賴 (Gradle 4.x 五)

官網依賴連結
官網 repository 連結

buildscript { ScriptHandler sh ->
    sh.repositories { RepositoryHandler rh ->
        rh.jcenter()
        rh.mavenLocal()
        rh.mavenCentral()
    
        rh.maven { MavenArtifactRepository mar ->
            mar.url "http://repo.mycompany.com/maven2"
            mar.credentials { PasswordCredentials pc ->
                pc.username "user"
                pc.password "password"
            }
        }
    }
    
    sh.dependencies {
        classpath group: 'junit', name: 'junit', version: '4.12'
    }
}

※這是還不熟練的寫法,可以將閉包參數去掉,因為已經有預設 import 了



buildscript {
    repositories {
        jcenter()
        mavenLocal()
        mavenCentral()
    
        maven {
            url "http://repo.mycompany.com/maven2"
            credentials {
                username "user"
                password "password"
            }
        }
    }
    
    dependencies {
        classpath project(':subGradle1') // 依賴其他專案
        classpath fileTree(includes: ['*.jar', '*.war'], dir: 'libs')
    }
}

※在 buildscript 同層,也有 repositories 和 dependencies
寫在 buildscript 裡是針對寫 gradle 時用的 jar 包; 寫在 buildscript 同層是針專案要用的 jar 包

Could not find method xxx() for arguments:不寫 classpath 會出的錯
Cannot resolve external dependency xxx because no repositories are defined:在buildscript 裡沒有repositories 出現的錯

※依賴裡,舊版的
https://docs.gradle.org/4.10.3/userguide/building_java_projects.html
https://docs.gradle.org/5.6.4/userguide/building_java_projects.html
https://docs.gradle.org/current/userguide/java_library_plugin.html

4~6 版
compileOnly
implementation (取代 compile)
runtimeOnly (取代 runtime)
testCompileOnly
testImplementation
testRuntimeOnly

只有第 6 版有
api



※依賴衝突


maven 的依賴是最短路徑優先,路徑一樣就是先寫的優先
gradle 則是最新的版本優先
以 hibernate 3.6.3 為例,有以下三種方法

configurations.all {
    resolutionStrategy {
        failOnVersionConflict() // 不要自動處理版本衝突
    }
}



configurations.all {
    resolutionStrategy {
        force 'org.slf4j:slf4j-api:1.7.24' // 強制使用某一個版本
    }
}



dependencies {
    implementation (group: 'org.hibernate', name: 'hibernate-core', version: '3.6.3.Final') {
        exclude group:"org.slf4j" , module:"slf4j-api" // 排除依賴後,還要自己在下載
        // transitive=false // 所有依賴都不要,debug 用
    }
}

2018年8月10日 星期五

檔案/目錄操作 (Gradle 4.x 四)

官網連結

GradleTest 是父專案,SubGradle1 是子專案, Project 裡的方法有很多都是寫在父或子專案效果會不同的

※父專案

task getAbsoluteOfRoot {
    println getRootDir().absolutePath // D:\GradleTest
    println getProjectDir().absolutePath // D:\GradleTest
    println getBuildDir().absolutePath // D:\GradleTest\build
}
    
getFileContent('xxx.txt')
def getFileContent(String filePath) {
    // println file(filePath).text
    files(filePath).each {
        println it.text
    }
}



※子專案

task getAbsoluteOfSub {
    println getRootDir().absolutePath // D:\GradleTest
    println getProjectDir().absolutePath // D:\GradleTest\SubGradle1
    println getBuildDir().absolutePath // D:\GradleTest\SubGradle1\build
}



※複製檔案

task copyTaskDir(type: Copy) {
    from 'D:/testCopy'
    into 'D:/result/xxx'
    exclude { it.file.name.endsWith('apk') }
    delete 'D:/result/xxx/abc.txt'
}
    
copy {
    from file('xxx.txt')
    // println getSubprojects().getAt(0).getBuildDir()
    into getSubprojects().getAt(0).getBuildDir()
    rename { 'ooo.txt' }
}

※以上兩種方式都可以,但要注意 delete 會先執行,再做複製的動作


※複製目錄

copy {
    from files('testCopy/')
    println getSubprojects().getAt(0).getBuildDir()
    into getSubprojects().getAt(0).getBuildDir()
    exclude { it.file.name.endsWith('apk') }
}

task copyFile (type: Copy) {
    from files('testCopy/')
    into getSubprojects().getAt(0).getBuildDir()
    exclude { it.file.name.endsWith('apk') }
}




※fileTree

// fileTree('')
// fileTree.visit {}
    
fileTree('D:/testCopy') {
    it.visit {
        print it.directory ? "目錄" : "|--檔案"
        println "為:$it.name"
        // copy {}
    }
}

※註解的兩行就等於它下面的寫法,所以不用寫的這麼麻煩

2018年8月9日 星期四

Project 屬性 (Gradle 4.x 三)


※使用 def、ext

def springVersion = '5.0.8.RELEASE'
    
ext {
    mybatisVersion = '3.4.6'
}
    
dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    compile group: 'org.springframework', name: 'spring-context', version: springVersion
    compile group: 'org.mybatis', name: 'mybatis', version: mybatisVersion
}

※如果 def 和 ext 都寫一樣的 key,會以 ext 為主


※ext 進階使用

ext {
    xxx = [
        mybatisVersion : '3.4.6',
        springVersion : '5.0.8.RELEASE'
    ]
}
    
dependencies {
    compile group: 'org.springframework', name: 'spring-context', version: rootProject.ext.xxx.springVersion
    compile group: 'org.mybatis', name: 'mybatis', version: rootProject.ext.xxx.mybatisVersion
}

※可以定義 Map,Map 裡還能再寫 Map



※使用 gradle.properties


官網連結

這個檔案預設沒有,可以新增在 GRADLE_USER_HOME 或者專案目錄下,Project 有一個寫死的變數,就是這一個,所以名字不能改,如果兩個地方都有這個檔案且 key 相同,會以 GRADLE_USER_HOME 為主


預設的 GRADLE_USER_HOME 在 %username%\.gradle,但可以臨時改變這個目錄,如 gradle -Dgradle.user.home=D:\ooo,就是將目錄變成 D 槽的 ooo 資料夾,將檔案新增在此即可

屬性流程:

初始化由專案的 gradle.properties -> GRADLE_USER_HOME 的 gradle.properties -> build.gradle 的 def -> build.gradle 的 ext,後者蓋前者
所以這 4 個地方如果有 key 一樣,最後會被 ext 裡的 key 覆蓋

※gradle.properties

isDebug=false
xxx='x2'




※settings.gradle

// if (isDebug.toBoolean()) {
//    include 'SubGradle2'
// }
 
if (hasProperty('isDebug') ? isDebug.toBoolean() : false) {
    include 'SubGradle2'
}

※hasProperty 判斷有沒有這個 key,只要有,不管內容是什麼,就算是空的也一樣,都會回傳 true

※因為怕沒有這個 key 會報錯,所以用了 hasProperty

※我試過寫了版本號當字串,如 spring 的版本,然後用在 dependencies
四個地方都設定,但只有 def 和 ext 有用,而且也沒報錯,不知道為什麼


倉庫使用私服

repositories {
    maven {
        credentials {
            username "$user"
            password "$passwd"
        }
        url '私服網址'
    }
}

※user 和 passwd 變數就可以寫在 gradle.properties 裡,當然在這裡寫死也是可以

Project 常用方法 (Gradle 4.x 二)

有幾個命令也可以撈出一些資訊
gradle projects
gradle tasks
gradle tasks --all 不加 all,other 就看不到
官網連結


String getName();
Object getGroup();
Object getVersion();
    
/* 主要是以下幾個方法
Project getRootProject();
Project getParent();
Project getProject();
Set<Project> getAllprojects();
Set<Project> getSubprojects();
Map<String, Project> getChildProjects();
Project findProject(String var1);
Project project(String var1, Closure var2);
Map<Project, Set<Task>> getAllTasks(boolean var1);
*/
    
    
task gradleInfo {
    println gradle.gradleVersion
    println gradle.gradleHomeDir
    println gradle.gradleUserHomeDir
}
    
task getCoordinate {
    println '--- 自己的 group-name-version ---'
    print group
    println "-$name-$version"
    
    subprojects.eachWithIndex { Project entry, int i ->
        i++
        println "--- 第 $i 個兒子的 group-name-version ---"
        println "$entry.group-$entry.name-$entry.version"
        println()
    }
}
    
project('SubGradle2') {
    println it.name
}
    
// 設定子專案
project 'SubGradle1', {
    println it.name
    group 'x.x.xxx'
    version '9.9.999'
    // apply from:'../xxx.txt'
    
    this.subprojects.eachWithIndex { Project entry, int i ->
    i++
    println "--- 第 $i 個兒子的 group-name-version ---"
    println "$entry.group-$entry.name-$entry.version"
    println()
    }
}

※getCoordinate 不是子專案的 group,$group 這個居然是 null,不知道為什麼

※apply from 的路徑是以 project('xxx') 裡面為準的,內容寫對 project('xxx') 的設定

2018年8月8日 星期三

build 生命週期和監聽方法 (Gradle 4.x 一)

官網連結

.初始化階段:
Gradle 支持單項目和多項目構建
在初始化階段,Gradle 確定哪些項目將參與構建,並為每個項目創建一個 Project 實例

.設定階段:
在此階段,配置專案物件
將執行構建的所有專案的構建腳本
Gradle 1.4 引入了一種稱為按需配置的孵化選擇功能。在此模式下,Gradle 僅配置相關專案

.執行階段:
Gradle 確定要在執行期間創建和設定任務的子集,然後子集傳遞給 gradle 命令和當前目錄。最後 Gradle 執行每個選定的任務


其中設定的部分還有四種狀態,Internal、Incubating、Public、Deprecated,最後兩個大家都知道,就不說明了
官網連結

※Internal:
內部功能不是為公共使用而設計的,僅供Gradle本身使用
可以在任何時候以任何方式改變而不另行通知
所以,儘量避免使用此類功能
內部功能可能演變為公共功能

※Incubating:
孵化狀態下的功能可能會在將來的 Gradle 版本中發生變化,直到它不再孵化為止
孵化的所有方法/屬性/類都使用 @Incubating

gradle 預設 import 的套件非常多,看官網連結


this.gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    @Override
    void beforeEvaluate(Project project) {
        println '無'
    }
    
    @Override
    void afterEvaluate(Project project, ProjectState projectState) {
        println '設定後,執行前1'
    }
})
    
this.gradle.addListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println '無'
    }
    
    @Override
    void settingsEvaluated(Settings settings) {
        println '無'
    }
    
    @Override
    void projectsLoaded(Gradle gradle) {
        println '無'
    }
    
    @Override
    void projectsEvaluated(Gradle gradle) {
        println '設定後,執行前4-1'
    }
    
    @Override
    void buildFinished(BuildResult buildResult) {
        println '生命週期完成1-2'
    }
})
    
this.gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println '無'
    }
    
    @Override
    void settingsEvaluated(Settings settings) {
        println '無'
    }
    
    @Override
    void projectsLoaded(Gradle gradle) {
        println '無'
    }
    
    @Override
    void projectsEvaluated(Gradle gradle) {
        println '設定後,執行前4-2'
    }
    
    @Override
    void buildFinished(BuildResult buildResult) {
        println '生命週期完成1-1'
    }
})
    
this.beforeEvaluate {
    println '無'
}
    
this.gradle.beforeProject {
    println '無'
}
    
this.afterEvaluate {
    println '設定後,執行前3'
}
    
this.gradle.afterProject {
    println '設定後,執行前2'
}
    
this.gradle.buildFinished {
    println '生命週期完成2'
}

※在 build.gradle 同層,有一個檔案叫 settings.gradle,再裡面寫 println,會發現它是最早印出來的,因為初始化就在這裡

2018年8月2日 星期四

JSON、XML、IO (groovy 2.x 六)

官網 JSON 教學官網 XML 教學官網 IO 教學


※JSON

package script
    
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
    
def json = JsonOutput.toJson([1, 'a', 3.2, [a: 1]]) // 轉成 JSON 字串
println json
// println JsonOutput.prettyPrint(json) // 印出格式化後的 JSON 字串
List<Object> a = new JsonSlurper().parse(json.getBytes()) // 將 JSON 字串轉成物件
println a
    
class Animal {
    Integer id
    String name
}
    
def animalList = [ new Animal(id: 1, name: 'tiger'),
                   new Animal(id: 2, name: 'turtle') ]
def animalJson = JsonOutput.toJson(animalList)
println animalJson
// println JsonOutput.prettyPrint(animalJson)
List<Animal> la = new JsonSlurper().parse(animalJson.getBytes())
println la





※XML

final def xml = '''<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <h1>YEAH</h1>
        <ul>
            <li class=\'aaa\'>AA</li>
            <li class=\'bbb\'>BB</li>
            <li class=\'ccc\'>CC</li>
        </ul>
        <ul>
            <li class=\'add\'>AD</li>
            <li class=\'aee\'>AE</li>
            <li class=\'aff\'>AF</li>
        </ul>
    </body>    
</html>
'''





def rtn = new XmlSlurper().parseText(xml) // 根只能一個,也就是最外層的 html 只能有一個
println rtn.name() // html
println rtn.body[0].h1[0].text() // YEAH
println rtn.body.h1.text() // YEAH
println rtn.body.ul.li[1].text() // B
println rtn.body.ul.li[1]["@class"] // bbb
println rtn.body.ul.li[1].'@class' // bbb
println rtn.body.ul.li[1].@class // bbb
println rtn.body.ul.li.text() // AABBCCADAEAF
println rtn.body.ul.li.@class // aaabbbcccaddaeeaff
    
def list = []
rtn.body.ul.each { ul ->
    ul.li.each {
        list << it
    }
    println()
}
println list // [AA, BB, CC, AD, AE, AF]

※取屬性有三種寫法



※depthFirst、children()

def filter = rtn.body.ul.depthFirst().findAll {
    it.@class in ['aaa', 'add']
}/*.collect { //如果想輸出非 text,可再加 collect
    it.@class
}*/
println "filter= $filter" [AA, AD]
    
def filter0 = rtn.body.ul.children().findAll {
    println it
}
println filter0
    
    
def filter1 = rtn.body.ul.'*'.find {
    println it.@class // aaa
    println it.name() // li
    it
}
println "filter1= $filter1" // AA
    
def filter2 = rtn.body.ul.'**'.find {
    println it.@class //
    println it.name() // ul
    it
}
println "filter2= $filter2" // AABBCC
    
def filter3 = rtn.body.ul.'*'.findAll {
    println it.@class // aaa\r\nli\r\nbbb\r\nli...
    println it.name() // li
    it
}
println "filter3= $filter3" // AABBCCADAEAF
    
def filter4 = rtn.body.ul.'**'.findAll {
    println it.@class //
    println it.name() // ul\r\naaa\r\nli\r\nbbb...
    it
}
println "filter4= $filter4" // [AABBCC, AA, BB, CC, ADAEAF, AD, AE, AF]

※depthFirst() 可用 '**' 代替;children() 可用 '*' 代替



※產生 XML

import groovy.xml.MarkupBuilder
    
//<aaa display="true">
//  <bbb xxx="x" ppp="1">123</bbb>
//  <bbb xxx="o" ppp="2">
//      456
//      <ddd>sub</ddd>
//  </bbb>
//  <ccc xxx="x" ppp="3">789</ccc>
//  <ccc xxx="o" ppp="4">012</ccc>
//</aaa>
    
def sw = new StringWriter()
def xml = new MarkupBuilder(sw)
xml.aaa(display: 'true') {
    bbb(xxx: 'x', ppp: '1', value: 123)
    bbb(xxx: 'o', ppp: '2', value: 456) {
        ddd(value: 'sub')
    }
    ccc(xxx: 'x', ppp: '3', value: 789)
    ccc(xxx: 'o', ppp: '4', value: '012')
}
println sw




※使用bean,產生XML

class Aaa {
    String display
    List<Bbb> b
    List<Ccc> c
}
    
class Bbb {
    String xxx
    String ppp
    String value
    Ddd d
}
    
class Ccc {
    String xxx
    String ppp
    String value
}
    
class Ddd {
    String value
}
    
def d = new Ddd(value: 'sub')
def b1 = new Bbb(xxx: 'x', ppp: 1, value: 123)
def b2 = new Bbb(xxx: 'o', ppp: 2, value: 456, d: d)
def c1 = new Bbb(xxx: 'x', ppp: 3, value: 789)
def c2 = new Bbb(xxx: 'o', ppp: 4, value: '012')
def aaa = new Aaa(display: true, b: [b1, b2], c: [c1, c2])
    
def sw = new StringWriter()
def xml = new MarkupBuilder(sw)
    
xml.aaa(display: aaa.display) {
    for (def bc : aaa) {
        /*
        for (def bd : bc.b) {
            bbb(xxx: bd.xxx, ppp: bd.ppp, value: bd.value) {
                if (bd.d != null) {
                    ddd(value: bd.d.value)
                }
            }
        }
        */
    
        aaa.b.each { ab ->
            bbb(xxx: ab.xxx, ppp: ab.ppp, value: ab.value) {
                ab.d.each {
                    ddd(value: it.value)
                }
            }
        }
    
        bc.c.each {
            ccc(xxx: it.xxx, ppp: it.ppp, value: it.value)
        }
    }
}
println sw

※註解是我一開始用閉包時,ddd 這一層包不進去 bbb,所以只好使用 for 迴圈的方式,後來發現 aaa.b 不用 it 才有辦法做到



※IO

假設已有一個檔案 xxx.txt,內容是:
123
abc
456
def

def file = new File('xxx.txt')
println file.getText()
println file.readLines() // [123, abc, 456, def]
println file.readLines('UTF-8') // [123, abc, 456, def]
    
file.each {
    println it
}
    
file.eachLine { content, num ->
    println "$num $content"
}
    
println file.withReader {
    def buffer = new char[2]
    it.read(buffer)
    buffer
}
    
def out = new File('ooo.txt')
out.withWriter {
    String enter = System.getProperty('line.separator')
    it.append('abc').append(enter).append('123').append(enter)
    it.append('def').append(enter).append('456')
}
    
def copyFile(File src, File dest) {
    if (!src.exists()) {
        src.createNewFile()
    }
    
    src.withReader { r ->
        def lines = r.readLines()
        dest.withWriter { w ->
            lines.eachWithIndex { String content, int i ->
                w.append("$i $content" + System.getProperty('line.separator'))
            }
        }
    }
    println 'copy success'
}
copyFile(new File('ooo.txt'), new File('copy.txt'))



2018年8月1日 星期三

呼叫方法的流程 (groovy 2.x 五)

官網連結
當你呼叫一個方法,你沒寫過這個方法,而且也不是 jar 檔裡的方法時,java 會出錯;但 groovy 不一定會出錯,是有一定的流程的

官網的圖,一開始就分有沒有實作 GroovyInterceptable,有實作就呼叫 invokeMethod,但如果沒有 invokeMethod,官網就沒畫了

※我參考官網的圖,有一個部分我並不十分了解,以下說明
Method exists in MetaClass or class,就這個不是很懂
Property exists in MetaClass or class 且還要是閉包才會呼叫,因為 property 是使用在屬性的,而閉包算是比較特殊的

以上這兩個我試的結果,感覺是兩個合起來,所以我認為是「閉包方法或 class」,這兩個好像沒有誰一定會先執行,我有等 30 分鐘再執行,有可能是另外一個執行

所以以下我就以這兩個合併的前提寫了以下的代碼,看圖除了 exception 外,有 5 個結果,但 invokeMethod 重複了,這要看有沒有實作 GroovyInterceptable 而定,所以實際上有 4 個結果


※有實作 GroovyInterceptable

class MyGroovy implements GroovyInterceptable {
    //  @Override
    //  Object invokeMethod(String methodName, Object args) {
    //      "1.方法名:${methodName},參數:${args}"
    //  }
    
    //  def abc(String p1, Object p2) {
    //        "2-1.參數一:${p1},參數二:${p2}"
    //  }
    
    def methodMissing(String methodName, Object args) {
        "3.方法名:${methodName},參數:${args}"
    }
    
    static void main(String[] args) {
    //  MyGroovy.metaClass.abc = { p1, p2 ->
    //      "2-2.參數一:${p1},參數二:${p2}"
    //  }
        MyGroovy m = new MyGroovy()
        println m.abc('xyz', 123)
    }
}

※全部註解打開,會先執行1,1如果註解,就會執行2,依此類推

※有2-1、2-2 這種的,不一定是 2-1 先執行,也有可能 2-2 先執行


※沒實作 GroovyInterceptable

class MyGroovy2 {
    //  def abc(String p1, Object p2) {
    //      "1-1.參數一:${p1},參數二:${p2}"
    //  }
    
    //  def methodMissing(String methodName, Object args) {
    //      "2.方法名:${methodName},參數:${args}"
    //  }
    
    @Override
    Object invokeMethod(String methodName, Object args) {
        "3.方法名:${methodName},參數:${args}"
    }
    
    static void main(String[] args) {
    //  MyGroovy2.metaClass.abc = { p1, p2 ->
    //      "1-2.參數一:${p1},參數二:${p2}"
    //  }
        
        MyGroovy2 m2 = new MyGroovy2()
        println m2.abc('xyz', 123)
    }
}




以上主要是有個類別是 final 類,如 String,沒辦法繼承它,也沒辦法改它,所以可以使用這個功能來新增

※測試

class Animal {
    static void main(String... aaa) {
        // ExpandoMetaClass.enableGlobally() // 據說 1.8.1 版就不需要也能成功了
        // new Animal().nonStaticMethod() // 還沒動態加載,所以找不到方法
    
        Animal.metaClass.constructor = { String s ->  // = 可改成 <<
            println "String constructor"
        } << { Integer i -> // 使用 << 可以增加 overloading 方法,= 不可以
            println "Integer constructor"
        }
    
        Animal.metaClass.static.staticMethod = { // = 可改成 <<
            println 'static method'
        }
    
        Animal.metaClass.nonStaticMethod = { // = 可改成 <<
            println 'nonStatic method'
        }
    
        Animal a = new Animal(8)
        Animal.staticMethod() // 不能使用 call()
        new Animal().nonStaticMethod() // 不能使用 call()
    }
}

※在新增方法後,在別的地方使用,還是沒有新增的方法,所以有了 ExpandoMetaClass.enableGlobally(),在新增方法前呼叫即可,但我沒試出來,一直都可以,找了網路上的文章,聽說是 1.8.1 不需要的樣子


2018年7月31日 星期二

this、owner、delegate (Groovy 2.x 四)


class MyGroovy {
    class InnerGroovy {
        static def staticClouser = {
            println "a ${this}" // 閉包定義的類
            println "b ${owner}" // 閉包定義的類或物件
            println "c ${delegate}" // 預設和 owner 一樣
        }
    
        def static methodStaticClouser() {
            def xxx = {
                println "d ${this}"
                println "e ${owner}"
                println "f ${delegate}"
            }
            xxx.call()
        }
    }
    
    static void main(def args) {
        MyGroovy.InnerGroovy.staticClouser.call()
        MyGroovy.InnerGroovy.methodStaticClouser()
    }
}

※結果:
a class MyGroovy
b class MyGroovy$InnerGroovy
c class MyGroovy$InnerGroovy
d class MyGroovy
e class MyGroovy$InnerGroovy
f class MyGroovy$InnerGroovy



class Animal{}
    
class MyGroovy {
    static def staticClouser = {
        println "a ${this}" // 閉包定義的類
        println "b ${owner}" // 閉包定義的類或物件
        println "c ${delegate}" // 預設和 owner 一樣
    }
    
    static def methodStaticClouser() {
        def xxx = {
            println "d ${this}"
            println "e ${owner}"
            println "f ${delegate}"
        }
        xxx.delegate = new Animal()
        xxx.call()
    }
    
    static void main(def args) {
        MyGroovy.staticClouser.call()
        MyGroovy.methodStaticClouser()
    }
}

※結果:
a class MyGroovy
b class MyGroovy
c class MyGroovy
d class MyGroovy
e class MyGroovy
f Animal@3745e5c6



class Animal {
    String name
}
    
class MyGroovy {
    Integer id
    String name
    Double price
    
    def show = {
        "編號${id},名稱${name},價錢為${price}"
    }
    
    String display() {
        println show.call()
    }
    
    static void main(String[] args) {
        MyGroovy m1 = new MyGroovy(price: 50.2, name: '西洋棋', id: 1)
        MyGroovy m2 = new MyGroovy(name: '象棋', id: 2)
        Animal a = new Animal(name: '老虎')
        m1.display()
        m1.show.delegate = a
        m1.show.resolveStrategy = Closure.DELEGATE_FIRST // 預設為 0,也就是 OWNER_FIRST
        m1.display()
        m2.display()
    }
}

※有點像 javascript 的 call、apply、bind

2018年7月30日 星期一

閉包與相關方法 (Groovy 2.x 三)

※數字與閉包的相關方法

※upto、downto

package script
    
def up(int n) {
    def sum = 0
    // 1.upto(n) { num -> println num }
    // 1.upto(n, {num -> rtn += num})
    1.upto(n) { num ->
        sum += num
    }
    sum
}
println up(10)
    
    
def down(int n) {
    // 10.downto(n) { num -> println num }
    // 10.downto(n, {num -> n += num})
    10.downto(n) { num -> n += num }
    n
}
println down(0)

※因為在參數使用 def 會有 cannot infer argument type ...,所以改成了 int

※閉包在最後一個參數,可以將閉包獨立拿到 () 後面


※times

def add(n) {
    def rtn = 0
    n = ++n // 因為底層的 size 是 <,不是 <=
    // n.times({ num -> rtn += num })
    n.times { num -> rtn += num }
    rtn
}
println add(10)





※字串與閉包的相關方法

※each、eachLine

// each
def s = 'abc'
def rtn = s.each {
    st -> print st.multiply(2) // aabbcc
}
println '\r\n' + rtn // abc
    
    
// eachLine
def s2 = '''abc
xyz
OGC
'''
def rt = s2.eachLine(0, {
    st, i -> println "${i} = ${st}"
})
println rt // null

※eachLine 第一個參數預設為0,index的意思,可以改變起始數字


※find、findAll、any、every、collect、collectReplacements

final def s = 'a1b2c3'
println s.find {
    st -> !st.isNumber() // a,找到第一個不是數字的,沒有是 null
}
    
println s.findAll {
    st -> !st.isNumber() // [a, b, c],找到全部都不是數字的,沒有是 null
}
    
println s.any {
    st -> st.isNumber() // true,其中一個是數字就是 true
}
    
println s.every {
    st -> st.isNumber() // false,全部是數字才是 true
}
    
println s.collect { // 循環每一個 index
    st -> st.center(3, "&") // [&a&, &1&, &b&, &2&, &c&, &3&]
}
    
println s.collectReplacements { // 循環每一個 index 後,判斷是否取代
    it == 'a' ? 'A' : it
}
    
    
// public static Object find(Object self, Closure closure) {...}

※最後一行註解的部分是原碼,第一個參數是自己去點的內容,可以假裝沒看到



※集合與閉包的相關方法


def list = ['xxx', 'x', 'xxxxx', 'xx', 'xxxx']
    
list.sort {
    it.size() // it -$gt; return it.size()
}
println list // [x, xx, xxx, xxxx, xxxxx]
    
def list2 = [9, -5, 2, 7]
println list2.min() // -5
println list2.max { // 9
    Math.abs(it)
}
    
println list2.count {
    it % 2 == 0 // 偶數有幾個
}




※排序

def list = [1, -7, 2, 3, -6, 8, -4, 5]
/*
Collections.sort(list) { a, b ->
    if (a == b) {
        0
    } else {
        Math.abs(a) < Math.abs(b) ? -1 : 1
    }
}
*/
    
list.sort() { a, b ->
    if (a == b) {
        0
    } else {
        Math.abs(a) < Math.abs(b) ? -1 : 1
    }
}
println list



String 方法、邏輯控制 (Groovy 2.x 二)

※String 方法

package script
    
String x = 'xyz';
String r1 = x.charAt(1)
String r2 = x[1..2]
println x // xyz
println r1 // y
println r2 // yz
    
String c = x.center(6, '?') // 沒有第二個參數,預設是空格
println x // xyz
println c // ?xyz??
    
String p = x.padLeft(6, '?')
println x // xyz
println p // ???xyz
    
println 'abc'
println 'abc' > 'abd' // 前二個一樣,第三個 ascii 99 > 100 嗎? false
    
println 'axbxc'.minus('x') // abxc
println 'axbxc' - 'x' // abxc
    
println 'axbxc'.plus('x') // axbxcx
println 'axbxc' + 'x' // axbxcx
    
println 'abc'.reverse() // 從後往前讀,cba
println 'abc'.capitalize() // 首字母大寫,Abc
println 'ABC'.uncapitalize() // 首字母小寫,aBC
    
println ''.isBlank() // true
println ' '.isBlank() // true
println ''.isEmpty() // true
println ' '.isEmpty() // false
    
println Integer.MAX_VALUE // 2147483647
println '2147483648'.isInteger() // false
println '2147483648'.isLong() // true
println '2147483648'.isFloat() // true
println '2147483648'.isDouble() // true
println '2147483648'.isBigInteger() // true
println '2147483648'.isNumber() // 會呼叫 isBigDecimal(),true
println '2147483648'.isBigDecimal() // true
// 沒有 isByte isShort isCharacter isBoolean
    
println '2147483647'.toInteger() // 超過會報錯
// 沒有 toByte





※if

if(new ArrayList<>()) {
    println 'true'
} else { // 0、0.0、null、''、new空物件、
    println 'false'
}

※java 一定要是 boolean 才可以


※不支援範圍操作

int i = 59
if(i == 1..59) {
    println '不及格'
} else {
    println '及格' // 永遠都是及格
}




※switch

Integer a = 7
switch (a){
    case 7:
        println 'a'
        break
    
    case '7':
        println 'b'
        break
    
    case Number.class:
        println 'c'
        break
    
    case Integer.class:
        println 'd'
        break
    
    default:
        println 'e'
}

※java 的 switch 型別要全部一樣才可以,且只能是 char 和 int,java 7 又多個 String

※a b c d 都會被呼叫到,看誰寫前面就先印



def list = [1,2,3]
switch (list){
    case ArrayList:
        println 'a'
        break
    
    case [1,2,3]: // 相當於是 new,不可能進來
        println 'b'
        break
    
    case List.class:
        println 'c'
        break
    
    default:
        println 'd'
}

※switch 可以放其他東西,可以有類似 instanceof 的寫法


※支援範圍操作

def i = 61
switch (i){
    case 0..59:
        println '不及格'
        break
    
    case 60..100:
        println '及格'
        break
    
    default:
        println '超過範圍'
}




※迴圈

def sum = 0
for (def i in 1..10) { // in 寫「:」也行
    sum += i
}
println sum





for (def m : [a: 1, b: 2, c: 3]) { // in 寫「:」也行
    print "${m.key} "
    println m.value
}

※List 、Set 就直接拿來用,而 Map,使用內鍵的 key 和 value 即可

2018年7月25日 星期三

基本概念 (Groovy 2.x 一)

主要寫的是 Java 和 Groovy 的差異,和 Java 一樣就不寫了

※Eclipse 安裝環境

Help -> About Eclipse 查自己的版本
然後到這裡,標題為Releases,複製對應自己版本的 Release Update Site
然後回到 Eclipse,選 Help - > Install New Software,然後如下操作:


※選左上角的 Add 後,會跳出一個視窗, Name 隨便打,Location 將剛剛複製的貼進去


※詳細差在哪我不知道,反正我是全選了,安裝要一段時間,安裝好後會提示重啟 Eclipse



※IntelliJ、Android Studio

安裝好 IntelliJ 或 Android Studio 就有環境可測試了(較舊的沒有),如下圖:


※但是這只是測試用而已,如果想使用 Groovy 的專案,還要去官網下載 groovy 的 SDK 來用,如果安裝好了,可以 New -> Groovy Script,如 xxx.Ooo,這將會創建一個 package 為 xxx,而 Ooo.groovy 為檔名

※要注意 jvm 版本和安裝時預設雖然有勾環境變數,但會有「~」的路徑,我沒試過可不可以,反正我是改成正確的路徑,最後 New -> Project 如下設定即可(但 Android Studio 我不知道):

如果忘記選 Groovy library,還可以如下設定:
※有成功才會在左邊的 External Libraries 有 groovy-xxx的東西,沒有就要在右邊的 Add Framework Support 裡面去勾選了,右邊的是按 TestGroovy 按右鍵出來的



※預設import

java.io.*
java.lang.*
java.math.BigDecimal
java.math.BigInteger
java.net.*
java.util.*
groovy.lang.*
groovy.util.*
    
import static Calendar.getInstance as now

※import static 比 Java 多個 as 的別名功能


※class 的省略

class Person {
    String name
    
    String greet(String otherPerson) {
        Integer i = 88;
        def j = 11;
        println i + j
        "Hello ${otherPerson}"
    }
    
    static void main(String... args){
        println new Person().greet("xxxxxxxxxxxxxxxxxx")
    }
}

※最後可不加「;」
1.回傳值可不用寫 return 關鍵字
2.宣告多一個 def,如 def i = 0;、def s = 'xxx',轉換後為 Object

/* class */
3.如果屬性前面沒有修飾子(同package),轉換後的 class 其實會幫我們加上 private,並給 setter/getter,但如果自己加 private,就不會提供 setter/getter,不管是不是 static 都會這樣
4.方法、class、建構子,修飾子不寫和寫 public 一樣,而方法不管是不是 static 也是一樣,所以 main 方法可以不寫 public
5.雖然已安裝 java8,還是不能寫 lambda 表達式和 reference method(::)

/* interface */
6.只能定義 public 和不寫,但轉換後都是不寫的
7.雖然已安裝 java8,也是不能在裡面定義 static 方法和 default 關鍵字,我試的是 2.5.1 版本
如果一定要使用 java8 的 default,groovy 有一種很像 interface 的 Type,叫做 trait,轉換後也是 interface,可參考官網的教學,或者這篇簡而有力的教學

8.System.out.println 只寫 println 即可,() 可省略(在 groovy 中,只要是有參數的都可省略),但 println 是 groovy 的,可按 ctrl + 滑鼠左鍵進去看
9.預設改 private 後,轉換後也確實是 private,但還是抓的到
10.看 groovy 幫我們的代碼轉成什麼樣子
※在 out 資料夾裡有轉變後的 class,注意如果有修改,要執行完才會重新生成
可以看到都會實作一個叫 GroovyObject 的介面


※六種字串

// 1.單引號
println 'ab' == 'a' + 'b' // true,這個才是和java的 「"」一樣
    
// 2.雙引號
def x = 'xxx'
println "x is ${x}" /* x is xxx,多了打變數的功能,「{}」可省略
類似ES6的「`」,想打「$」,用跳脫字元「\」*/
    
// 3.三個單引號
def o = '''a
b
c
d'''
println o // 有換行效果,類似HTML的 <pre>
    
// 4.三個雙引號
def o = """a
b
${x}
d""" // 三個「'」加上抓變數的功能
    
// 5.正斜線
/.../
用「/」包起來,可以有以上的所有功能
    
// 6.錢符號正斜線
$/.../$
和/../很像,差在跳脫字元的不同,這個跳脫字元是「$」,其他都是「\」
    
println /\//; // /
println $///$; // /
    
println /$/; // $
println $/$$/$; // $

※使用雙引號,而且有使用到變數,會自動轉為 org.codehaus.groovy.runtime.GStringImpl,可以印出 .class 查看

※如果有一個方法定義參數為 String,GString 可以傳的進來,但相反不行


※方法

package script
    
final def me = "me!"
final def test = "look ${me}"
    
println "1 + 1 = ${1+1}" // 1 + 1 = 2
println echo('xxx') // a xxx
println echo("xxx") // a xxx
println echo(test) // b look me!
    
String echo(m) {
    "a ${m}"
}
    
String echo(GString m) {
    "b ${m}"
}
    
String echo(String m) {
    "c ${m}"
}

※a 方法省略了 def 關鍵字,而這三個方法都省略了 return 關鍵字

※a 方法在沒有明確的型態對應時才會呼叫的到,如將 c 刪除就呼叫的到

※如果 test 變數沒有使用到變數(${me}),那會是 String,有用到變數才是 GString

※groovy 就算寫 void,還是會有回傳值 null,但 void 關鍵字不能省略



※方法的括號可省略

echo 'xxx'
    
def echo(String m) {
    println "${m}"
}
    
getList([1, 2, 3])
getList Arrays.asList(1, 2, 3)
getList Stream.of(1, 2, 3).collect(Collectors.toList())
    
getArray(['a', 'b', 'c'] as String[])
getArray 'a', 'b', 'c'
String[] array = new String[3]
array[0] = 'a'
array[1] = 'b'
array[2] = 'c'
getArray array
    
getMap([a: 1, b: 2, c: 3])
getMap(a: 1, b: 2, c: 3)
getMap a: 1, b: 2, c: 3
    
def getList(List<Integer> list) {
    println "a ${list}"
}
    
def getArray(String[] array) {
    println "b ${array}"
}
    
def getMap(Map<String, Integer> map) {
    println "c ${map}"
}

※只有完全沒參數才一定要(),否則都可省略,但要注意閉包可以提出去的問題,假設有個方法有兩個參數,最後一個是閉包,提出去時不能省略(),想省略只能想在()裡面

※集合使用到 groovy 的寫法時,只有 Map 可省略() 和 []

※給陣列傳參數時,不能寫 new String[] {xxx, xxx} 的方式,因為 groovy 會以為是閉包

※集合

//   List、Set、Array 
def list = [1, 2, 3]
println list
println list instanceof List
println list instanceof LinkedList
println list.size()
list << 4 // 除了add方法外,用這個符號也能增加元素
    
def linkList = [1, 2, 3] as LinkedList
println linkList instanceof List
println linkList instanceof LinkedList
    
def set = [3, 2, 3] as Set
println set instanceof Set
    
String[] sArray =['Ananas', 'Banana', 'Kiwi']
def sArray =['Ananas', 'Banana', 'Kiwi'] as String[]    
def intArray = [1, 2, 3] as int[]
    
    
//   Map 
def map = [:] // 宣告空的 map
println map // [:]
println map.isEmpty() // true
map."a" = "aaa" // key 有沒有用「"」包起來都是可以的
map << [c:"ccc"]
map.put("d", "ddd") // 這個和之前一樣,key還是要用「"」包起來
println map // [a:aaa, c:ccc, d:ddd]
println map.a // aaa
println map['a'] // aaa
println map["a"] // aaa
    
def m = [b:"bbb"] // 非空的 map
println m // [b:bbb]
println m.b // bbb
m.xxx = [b:"bbb"]
println m.toMapString() // [b:bbb, xxx:[b:bbb]],Map 中的 Map
println m.xxx.b // bbb
println m.class // null,因為沒有class這個key
println m.getClass() // class java.util.LinkedHashMap

※增加元素都可以用「<<」


※運算符、三元運算子

println 2 ** 10 // 1024,2的10次方
    
def f = 2
println f ** 5 // 32,2的5次方
println f **= 5 // 32,f = f ** 5
    
def x = 1;
println x == 1 ?: "sucess" // 「?:」不能分開,?:之間沒寫預設是 true

※?:最後的 else 一定要寫


※基本型態的後綴

Java 只有 L、F、D 來代表 Long、Float、Double,大小寫都可以
Groovy 多兩個 I 和 G,一樣大小寫都可以,i 表示 Integer;G 代表有兩個 BigInteger、BigDecimal
例:
def long = 999L
def xxx = 555I
def ooo = 548G

※最好用大寫,避免和數字混淆

※groovy 中沒有基本型態,雖然可以宣告,但用 class 去查看,會發現都是 Wrapper 類別

※使用 def 時,預設宣告整數為 Integer,有小數點的為 BigDecimal(就算是1.0也是一樣)


※?.

String result = null
println result?.toUpperCase()

※可防止 NullPointerException,回傳 null


※@field

class User {
    String name = 'xxx'
    String getName() {
        println 'ooooooooooooo'
        this.name
    }
}
println new User().@name

※可以直接專取屬性,不加 @ 會呼叫 getter

※&method

def str = 'xxx'
def result = str.&toUpperCase //一定要用def接
println result()
    
def xxx(String str) { str.toLowerCase()}
def xxx(Integer x) { 2 * x }
def ref = this.&xxx
println ref('xOx') // xox
println ref(123) // 246

※使用「.&」宣告完,可以當方法呼叫,但要注意 &後面的方法沒有 ()

class Ooo {
    int count = 0
    def xxx() {
        return ++this.count
    }
}
    
def ref = new Ooo().&xxx
println ref() // 1
println ref() // 2

※這個有點像 javascript 的閉包功能

※*.

class Car {
    String make
    String model
}
def cars = [
    new Car(make: 'Peugeot', model: '508'),
    new Car(make: 'Renault', model: 'Clio')
]
    
def makes = cars*.make
println makes == ['Peugeot', 'Renault'] // true
println makes instanceof List // true

※只取集合的某個屬性


※延伸方法參數

int function(int x, int y, int z) {
    x*y+z
}
    
def args = [4,5,6]
println function(*args) // 26




※延伸操作-集合

def items = [4,5,2]
def list = [1,2,3,*items,6]
println list == [1,2,3,4,5,2,6] // true
    
def set = [1,2,3,*items,6] as Set
println set == [1,2,3,4,5,6] as Set // true
    
def m1 = [c:3, d:4]
def map = [a:1, b:2, *:m1]
println map == [a:1, b:2, c:3, d:4]




※範圍操作

def range = 0..3
println range // [0, 1, 2, 3]
println (1..3).collect() //  [1, 2, 3]
println (1..<3).collect() // [1, 2],只有小於,也只能寫在後面
println ('a'..'d').collect() //[a, b, c, d]
println range.contains(3) // true,包括3這個元素嗎?
println range.contains('3') // false,包括3這個元素嗎?
println range.contains(4) // false
println range.from // 0,第一個元素
println range.to // 3,最後一個元素
    
    
def r = 0..30
def n = r.by(3)
n.each {
    print "${it} " // 0 3 6 9 12 15 18 21 24 27 30
}
    
/* 簡化
(0..30).by(3).each {
    print "${it} "
}
*/

※<和3中間可以空格

※Range 為 List 的子類,都是 interface,Range 有四個實作類 EmptyRange, IntRange, NumberRange, ObjectRange


※太空船操作

println 1 <=> 1 //  0,相等
println 1 <=> 3 //  -1,左邊小於右邊
println 3 <=> 1  // 1,左邊大於右邊
println 'a' <=> 'z'//  -1
    
println 1.compareTo(1) // 0
println 1.compareTo(3) // -1
println 3.compareTo(1) // 1
println 'a'.compareTo('z') // -25

※可能 <==> 長得像太空船吧!

※1就是 true;-1就是 false


※正則

package script
    
import java.util.regex.Matcher
import java.util.regex.Pattern
    
    
def p1 = ~/^foo.*/ // 以 foo 開頭的
def p2 = ~'.*foo$' // 以 foo 結尾的
def p3 = ~"(foo){2}" // 剛好是兩個 foo 的
def p4 = ~$/foo/$ // 只能是 foo 的
println p1 instanceof Pattern
println p1.matcher('foo123').matches()
println p2.matcher('123foo').matches()
println p3.matcher('foofoo').matches()
println p4.matcher('foo').matches()
    
    
def text = "xxx"
def m = text =~ /xxx/
println m instanceof Matcher
println m.matches()
if (m.matches()) { // 官網直接判斷m而已,說會呼叫find(),但我試的結果永遠為true
    println '有匹配到'
} else {
    println '沒匹配到'
}
println m.matches() ? 'o' : 'x'
    
    
def t = 'ooo'
def mat = t ==~ /ooo/
println mat instanceof Boolean
println mat
if (mat) {
    println '有匹配到'
} else {
    println '沒匹配到'
}
println mat ?:false

※主要就是 ~/.../ 為 Pattern; =~ 為 Matcher; ==~ 為 Boolean

※/.../ 後面和 javascript 不一樣,沒有什麼 gim 的


※範圍操作-集合

def list = [0,1,2,3,4]
println list[2] == 2 // true
list[2] = 4
println list[0..2] == [0,1,4] // true
list[0..2] = [6,6,6]
println list == [6,6,6,3,4] // true




※閉包

def closure1 = {
    -> println "xxx" // xxx
}
closure1.call() // call 可以省略
    
def closure2 = { i, p ->
    println "${i}, ${p}" // 5, ooo
}
closure2(5, "ooo")
    
def closure3 = {
    println it // 是我, it為內鍵變數
}
closure3("是我")

※一定要用 def 宣告

※使用 -> 將參數和實作隔開,但如果沒有參數,可以省略 ->

※和 java8 的 lambda 不太一樣,參數永遠沒有「()」

※如果只有一個參數,可以使用 it 這個內鍵變數

※參數的型態和 lambda 一樣,通通可以省略,相當於是 def,但如果有寫,如 String,那就要乖乖傳 String 進去

※一定有回傳值,沒有就是 null,像 println 回傳 void,所以是 null,return 關鍵字可以不寫,所以假設最後一行寫 'xxx',那回傳的就是 xxx

※return 可以不寫在最後一行,但 return 之後的程式碼不會執行


※閉包的循環

[1,2,3,4,5].each { print "${it}"} // 12345
println ''
['a':1,'b':2,'c':3].each { print "${it}-"} // a=1-b=2-c=3-
println ''
['a':1,'b':2,'c':3].each { print it.key+":"+it.value} // a:1b:2c:3
println ''