在普通的 Gradle Java/Kotlin 项目中使用 BuildConfig

简介

Android Studio 为其 Android 项目提供了十分方便的 BuildConfig 功能,该功能在运行编译时自动生成 BuildConfig.java 文件,其中储存了编译时的一些系统信息(如APP版本号、渠道、编译时间、编译器等),并可以用于条件编译。

所幸,有人已经仿照出了具有类似功能的 Gradle 插件。本文将说明如何使用这个插件。

使用方法

1. 添加依赖

build.gradle 文件中

plugins {
    id 'java'
    id 'de.fuerstenau.buildconfig' version '1.1.8' //添加 BuildConfig 插件
}

plugins 节应位于 importbuildscript 节的后面

2. 定义项目属性

例如,对于项目 com.kenvix.moecraftbot.ng

group 'com.kenvix'
version '1.0'
def applicationName = 'MoeCraftBotNG'
def versionCode = 1

archivesBaseName = 'moecraftbot.ng'
def mainSrcDir = 'src/main/java'  //项目java源代码目录
def fullPackageName = "${group}.$archivesBaseName"
def fullPackagePath = fullPackageName.replaceAll('.', '/')
def isReleaseBuild = System.getProperty("isReleaseBuild") != null //根据环境变量判断是否为正式发行版(判断是否是Release版本的构建)

3. 添加 BuildConfig 信息

/*********************************************************************/
/**                 Application Build Config Settings               **/
/*********************************************************************/

buildConfig {
    appName = project.name       // sets value of NAME field
    version = project.version   // sets value of VERSION field,
    // 'unspecified' if project.version is not set

    clsName = 'BuildConfig'      // sets the name of the BuildConfig class
    packageName = fullPackageName  // sets the package of the BuildConfig class,
    // 'de.fuerstenau.buildconfig' if project.group is not set
    charset = 'UTF-8'            // sets charset of the generated class,
    // 'UTF-8' if not set otherwise

    buildConfigField 'String', 'APPLICATION_NAME', applicationName
    buildConfigField 'String', 'VERSION_NAME',     version as String
    buildConfigField 'int',    'VERSION_CODE',     versionCode as String
    buildConfigField 'long',   'BUILD_UNIXTIME',   System.currentTimeMillis() + 'L'
    buildConfigField 'java.util.Date', 'BUILD_DATE', 'new java.util.Date(' + System.currentTimeMillis() + 'L)'

    buildConfigField 'String', 'BUILD_USER',       System.getProperty("user.name")
    buildConfigField 'String', 'BUILD_JDK',        System.getProperty("java.version")
    buildConfigField 'String', 'BUILD_OS',         System.getProperty("os.name")
    buildConfigField 'boolean','IS_RELEASE_BUILD', isReleaseBuild as String

其中,buildConfigField 表示这是自定义字段,后面紧随的是字段类型,要用字符串书写类名。(例如 'String'

其后是字段名称 'APPLICATION_NAME'(同样用字符串),其后是内容,也必须是字符串。如果内容为 intboolean 等类型,则必须强制转换。

内容部分可以书写代码,以字符串形式书写即可。

4. 让 IDE 识别代码

默认 IDE 不会识别 BuildConfig 生成的代码,为此要手动将其加入 sourceSets

// Add generated build-config directories to the main source set, so that the
// IDE doesn't complain when the app references BuildConfig classes
sourceSets.main.java {
    srcDirs += new File(mainSrcDir) //项目本身源代码
    srcDirs += new File(buildDir, 'gen/buildconfig/src') //BuildConfig
}

5. 生成项目

刚才我有提到“判断是否是Release版本的构建”,可以这样使用它:

例如,在打包 jar 时,可以在 gradle 命令上添加 JVM 参数 -DisReleaseBuild=true 来将 isReleaseBuild 设置为 true,让项目执行在 BuildConfig.IS_RELEASE_BUILD == true 时的代码,从而达到条件编译的效果。

项目生成后, BuildConfig 效果应该如下图所示:

配置到此结束。

我们还可以在项目启动时打印一下版本信息:

println("${BuildConfig.APPLICATION_NAME} Ver.${BuildConfig.VERSION_NAME} By Kenvix")

println("Built at ${BuildConfig.BUILD_DATE.format()} By ${BuildConfig.BUILD_USER} @ ${BuildConfig.BUILD_OS} JDK ${BuildConfig.BUILD_JDK}")

if (!BuildConfig.IS_RELEASE_BUILD)
    println("Debug build")

扔鸡蛋问题

img 菜鸡第一次看算法题,这篇笔记还是不要看比较好

计算机幻觉从入门到入土

[NOTICE] 本文仅为一篇我个人学习笔记,不适合作为其他人的学习引导。

因为博主写这篇笔记就是这个状态 ↓

img

↑ 为此专门查了怎么用more标签隐藏文章内容

Java 注解预处理 Annotation Processing & 代码生成

关于 Java 的注解预处理的资料实在是过于稀少,连stackoverflow上都没多少人研究,以致于我这个萌新在尝试使用注解预处理来生成代码时踩了不少坑,正好博客也快长草了,遂决定留一篇文章,希望能够对后来者有所帮助。

本文章同时对一般 Java 项目和 Android 项目适用。

为何使用 Java 注解预处理

诚然,用反射处理注解来替代代码的复制粘贴可以让代码更加简洁、易懂(优雅),但是,反射实在是太了。

啥?反射不慢?来来来,一个 Activity 就用几十次反射,要不要和复制粘贴做一下对比?(手动阴险)

那反射这么慢,有没有什么办法?当然就是今天的主题了——代码生成: 让编译器来给你“复制粘贴”,既优雅,又高效(反正生成的代码你也不看)。

如何使用 Java 注解预处理

关于注解预处理的基本使用方法的资料还是很多的,这里就不细说了,概括一下就是:

  1. 建一个类,继承并实现 javax.annotation.processing.AbstractProcessor
  2. 建一个 META-INF.services.javax.annotation.processing.Processor
  3. 在这里写上你的预处理器的完整类名(带包名)

注意:对于 Android 项目,你需要单独建立一个 “Java 类” 项目,不可以直接在原 Android 项目中使用 注解预处理,否则你会发现没有 javax 这个包。
然后,在 Android 项目的 build.gradle 中的 dependencies 添加 annotationProcessor project(':项目名')

处理我们的注解

假定我们要处理的注解名为 ViewAutoLoad,定义为:

@Retention(RetentionPolicy.CLASS) //保留此注解到编译期
@Target(ElementType.FIELD) //此注解只适用于“字段”
public @interface ViewAutoLoad {
}

本文通过介绍对字段注解的处理来讲述如何实现注解预处理,对于方法,用法其实没啥区别。

然后,重写 process 方法:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    return true;
}

为啥要留个 return true? true表示这个注解已经被我们处理过了,编译器不用再调用其他注解处理器了。

然后开始写我们的处理代码,这里就有两种处理注解的办法了:

办法1:一次性全局处理注解

这种方法 不能 知道这个字段(方法)到底是哪个类的,自然也不能获取除了你正在处理的字段(方法)所在类的其他信息,但是用起来方便一些。

获取 全局所有 具有此注解的字段,然后用 processAnnotation 方法逐一处理它们:

roundEnv.getElementsAnnotatedWith(ViewAutoLoad.class).forEach(this::processAnnotation);

这里先讲一些常用操作,假定我们现在在实现上文的 processAnnotation 方法,它的方法签名为:

private void processFormNotEmpty(Element annotatedElement)

获取字段的类型

如果你将来要生成代码或者将注解用作编译时检查,十有八九要用到这个字段的类型。

TypeMirror fieldType = annotatedElement.asType(); 

获取这个字段的注解,或者注解的值

ViewAutoLoad annotation = annotatedElement.getAnnotation(ViewAutoLoad.class);

现在,你可以直接使用你在注解接口定义的方法了,虽然作为示例的 ViewAutoLoad 没定义任何方法。
假装定义了 value()annotation.value()

获取这个字段(方法)的名字

我觉得这个肯定会用吧

Name fieldVarName = annotatedElement.getSimpleName();
//string: fieldVarName.toString();

获取这个方法的修饰符

annotatedElement.getModifiers()

返回一个集合,这个集合装着 javax.lang.model.element.Modifier 这个枚举

办法2:逐类处理注解

虽然麻烦了点,但是这个办法让我们可以知道我们在处理哪个类了。

我们回到 process 方法:

Set<? extends Element> rootElements = roundEnv.getRootElements();

这次我们直接拿到所有编译器处理的类的基础信息了,嗯,没有过滤器。

现在我们得手撸过滤器了,既然是 Set,先遍历走起。

然后怎么过滤呢?这里有一些思路:

  • 给字段(方法)上注解的时候就指定好这个类的名称,比如 @Example("com.kenvix.test.TestClass")
    注意:不要指定成 TestClass.class,在编译期无法这样读取类名,因为类尚未编译。
  • 遍历所有类,通过字段(方法)的一些特征查找这个类

第一种思路

第一种可以是十分简单粗暴了。

String targetName = "com.kenvix.test.TestClass"; 
Element targetClass = null;
for (Element element : rootElements) {
    if(element.toString().equals(targetName)) {
        targetClass = element;
        break;
    }
}
//这里只拿到了类,注解处理方法暂时省略,见下文。

第二种思路

显然,第一种实在是不怎么优雅,第二种方法又有这些思路:

  • 先通过包名,滤掉所有绝对不相关的东西,比如 Android 项目中,包名符合 android.* 就可以去掉了。或者只看包名以自己项目开头的。
  • 字段(方法)的名称有一些我们可以利用的特征,例如都以类名开头
  • 一个类中所有字段(方法)的数量和名称是唯一的。也就是说,不存在两个类同时有一样数量的同名字段。
  • 找到有目标注解的类后,将这个类和目标注解直接加到 Map
Map<Element, List<Element>> tasks = new HashMap<>();

for (Element classElement : rootElements) {
    if(classElement.toString().startsWith(Environment.TargetAppPackage)) {
        List<? extends Element> enclosedElements = classElement.getEnclosedElements();

        for(Element enclosedElement : enclosedElements) {
            List<? extends AnnotationMirror> annotationMirrors = enclosedElement.getAnnotationMirrors();

            for (AnnotationMirror annotationMirror : annotationMirrors) {
                if(ViewAutoLoad.class.getName().equals(annotationMirror.getAnnotationType().toString())) { //好像没有其他办法在这里判断是否是目标注解了
                    if(!tasks.containsKey(classElement))
                        tasks.put(classElement, new LinkedList<>());

                     tasks.get(classElement).add(enclosedElement);
                }
            }
        }
    }
}

这样,这个 Map<> 中就包含了我们需要的类和这个类持有的字段了,接下来进行处理即可

嗯?效率低?这是编译期,加钱换CPU或用第一种,请(手动滑稽)

生成代码

这里需要用到 javapoet 这个依赖,编辑gradle配置,加入依赖:

implementation 'com.squareup:javapoet:1.8.0'

然后重写 init 方法:

    protected Types typeUtil;
    protected Elements elementUtil;
    protected Filer filer;
    protected Messager messager;
    protected ProcessingEnvironment processingEnv;

    @Override
    public synchronized final void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.processingEnv = processingEnv;
        typeUtil = processingEnv.getTypeUtils();
        elementUtil = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
        onPreprocessorInit();

        messager.printMessage(Diagnostic.Kind.NOTE, "Preprocessor: " + this.getClass().getSimpleName() + " Initialized");
    }

回到 process 方法,刚才我们已经拿到了要处理的注解,接下来开始处理这些注解:

JavaPoet 资料到处都是啊,要写还不容易?

我咋取一个不可能导入的包的类型?

这问题还是很常见的,比如我们没法在一个 Java 项目中用 Android 包的东西,但是却需要生成相关的代码.

例如,我们需要用到一个类 AppCompatActivity,它在 android.support.v7.app 这个包,则可以这样写:

ClassName appCompatClass = ClassName.get("android.support.v7.app", "AppCompatActivity");

我咋表示类型通配符、泛型限定?

接上,我们还想表示 ? extends AppCompatActivity,可以这样写:

MethodSpec.Builder builder = code; //这里是你的方法builder

builder.addTypeVariable(TypeVariableName.get("T", appCompatClass)).addParameter(TypeVariableName.get("T"), "target")

保存我们的生成的代码,并在下一步编译生成的代码

回到 process 方法,加上:

if(roundEnv.processingOver()) {
    //创建FormChecker这个类
    TypeSpec formChecker = TypeSpec.classBuilder("FormChecker")
        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
        .addMethods(methods)
        .build();

    //创建类文件
    JavaFile javaFile = JavaFile.builder("com.kenvix.eg.generated", formChecker)
            .addFileComment(getFileHeader())
            .build();

    try {
        javaFile.writeTo(filer); 
    } catch (IOException ex) {
        throw new IllegalStateException(ex.toString());
    }    
}

对同一个 javaFilejavaFile.writeTo(filer) 只能调用一次,故需要判断是否为最后一轮注解预处理。

其他的可以看看这篇文章,虽然标题挺扯的(够你🐴)

其他小问题

我咋调试啊

显然这个时候按 IDE 的断点按钮是莫得了。
直接 System.outLogger 也不太好,分分钟被一堆垃圾编译消息淹没。用着还麻烦。
好吧,其实有个简单粗暴的方法,抛个运行时异常嘛,这样就能直接停止编译然后让 IDE 显示我们想要的东西了。

throw new IllegalStateException("something");

IDEA 对 addModifiers(), javaFile.writeTo(filer) 报错

IDEA bug
别理他,编译就行了

想用 getClass() 反射处理?

别想了,类都没编译好呢你get个啥

配置用于 Gradle + SQLite 的 jOOQ 3.11 代码自动生成

为什么要写这篇文章

关于如何配置用于 Gradle + SQLite 的 jOOQ 3.11 代码自动生成的文档可谓少之又少,网络上大部分文档,要么是使用 Maven,要么是针对于早期版本的 jOOQ(并不向后兼容),而 jOOQ 官方文档又很不全面,以致于许多像我这样的萌新在初次接触 jooq 时踩了不少坑,浪费了不少宝贵的时间 :)

为何一定要使用 jOOQ

复制一下 别人对此的介绍

JOOQ,全称Java Object Oriented Querying,即面向Java对象查询。它是Data Geekery公司研发的DA方案(Data Access Layer),主要解决两个问题:

  • Hibernate 的抽象使得我们离SQL太远,对SQL的掌控力度弱
  • JDBC 过于嘈杂,需要干的事情太多

JOOQ希望干的就是在上述两者中寻找一个最佳的平衡。它依据数据库中的表生成DA相关的代码,开发者将生成的代码引入项目中即可使用。

配置办法

官方文档 只贴了个代码,可以说是十分”友善”了

编辑 build.gradle

// Configure the Java plugin and the dependencies
apply plugin: 'java'

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    //在此处放置你的项目的原有依赖
    //添加jooq依赖
    compile group: 'org.jooq', name: 'jooq', version: '3.11.5'
    //<!> 一定要添加所用数据库的依赖,否则会报错而且不告诉你原因
    runtime group: 'org.xerial', name: 'sqlite-jdbc', version: '3.25.2'
}

buildscript {
    repositories {
        mavenLocal()
        mavenCentral()
    }

    dependencies {
        //添加jooq依赖
        classpath 'org.jooq:jooq-codegen:3.11.5'
        //<!> 一定要添加所用数据库的依赖,否则会报错而且不告诉你原因
        classpath group: 'org.xerial', name: 'sqlite-jdbc', version: '3.25.2'
    }
}

// Use your favourite XML builder to construct the code generation configuration file
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
        .configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.11.0.xsd') {
    jdbc() {
        url('jdbc:sqlite:src/main/resources/database.db') // src/main/resources/database.db为数据库路径
        //user() //不需要用户名,省略
        //password() //不需要密码,省略
    }
    generator() {
        database() {
            includes('.*') //包括的数据表
            excludes() //排除的数据表
            inputSchema() //默认数据库
        }

        target() {
            packageName('com.kenvix.pixiv.generated.jooq') //计划用于存储生成结果的包名
            directory('src/main/java') //将生成结果储存于src/main/java
        }
    }
}

// Run the code generator
// ----------------------
org.jooq.codegen.GenerationTool.generate(writer.toString())

然后运行 gradlew buildEnvironment 看看jooq有没有什么警告,同时,你会发现代码生成好了。

USBCopyer: 插上U盘自动按需复制文件

注:本文介绍的是带来巨量更新的USBCopyer V5

USBCopyer: 插上U盘自动按需复制文件

用于在插上目标U盘后自动按需复制该U盘的文件。”备份&偷U盘文件的神器”
特点:

  1. 支持设置冲突解决方案
  2. 支持选择性复制,包括扩展名黑白名单、磁盘黑白名单。磁盘黑白名单支持序列号
    白名单模式结合版本控制可以方便地备份U盘
  3. 支持隐藏模式及开机启动
  4. 支持设置回调,可以自己编写代码实现某种高级的、程序没有提供的功能。
    默认提供Git版本控制支持
  5. 支持限制文件大小,防止复制大文件造成延误
  6. 支持延迟复制,对“偷”课件类的场景提升用户体验十分有用

Screenshot

本程序有两个图标,默认图标表示当前处于空闲状态,红色图标表示当前正在复制文件(工作中)

现已提供三个版本(标准版/兼容版)可供下载,见“已编译版本下载”节

已编译版本下载

【推荐】.Net Framework 4.0 版本(标准版)

适用操作系统:Windows 8 / Windows 10
点击从[email protected]下载(中国大陆用户推荐) 点击从GitHub下载

.Net Framework 3.5 版本(兼容版)

适用操作系统:Windows 7 / Windows Vista / Windows XP
WinXP请注意:XP可能需要手动安装 .NET Framework 3.5,没有安装请点此
点击从[email protected]下载(中国大陆用户推荐) 点击从GitHub下载

自 V5.0 开始,标准版即为低权限版。不会弹出UAC警告。亦支持高DPI缩放。

适用场景

  1. 获取授课教师的课件
  2. 获取插到某台公用电脑上的U盘的数据
  3. 快捷地备份U盘(从V5.0开始)
  4. 备份U盘的同时进行版本控制

如何使用

1.下载
2.双击 USBCopyer.exe
3.程序将在托盘区运行,右击图标可以调整其参数
点击 “隐藏图标” 将彻底隐藏程序,只能使用任务管理器停止,或按Win+R输入taskkill /f /im USBCopyer.exe
点击 “设置” 可以设置程序

命令行

USBCopyer.exe [/hide] [/gui] [/reset]
/hide 以隐藏模式启动,只能通过任务管理器结束进程
/gui 除非使用 /hide 参数,否则无论如何都不要使用隐藏模式启动。可用来解除隐藏模式
/reset 恢复默认设置并退出,若失败,返回退出码1。可用来解除隐藏模式,但会丢掉所有设置

常见问题

USBCopyer的复制行为造成U盘的文件操作卡顿

请设置一个延迟。通常推荐在使用者开始放映幻灯片时开始复制

使用者在使用该电脑前重启了此电脑

将本程序设为开机启动即可。设置方法如下:

  1. 直接在本程序的“设置”中设置
  2. 若上面的方法无效,则可:
    创建一个快捷方式,然后加上参数,拖到 “开始菜单” 的 “启动” 文件夹即可
可否复制手机的文件?

若为MTP/PTP则无法实现(V6.0也许会实现),但如果是以Mass Storage(大容量存储)模式挂载到电脑上的设备则可以复制

U盘在操作完成前被拔出

请使用心理学方法防止U盘在操作完成前被拔出

如何防止拷到病毒?

安装杀毒软件,监控 USBCopyerData 目录即可

目标电脑带有还原机制

两种方法:
1.插上你的U盘,然后启动本程序,加入该U盘到黑名单,然后设置输出目录到你的U盘
2.用 PCHunter 之类的程序干掉还原程序

如何使用回调&如何配置Git版本控制?

参见:https://kenvix.com/post/usbcopyer-callback/

代码仓库

[email protected] GitHub
我的博客文章

捐赠

USBCopyer 是一个开源的,非盈利的项目。如果你喜欢这个项目,请捐赠它:

支付宝捐赠

Screenshot

微信支付捐赠

Screenshot

C# 实现自定义"应用程序设置"的配置文件(user.config)存储路径

关于“应用程序设置”: 前往MSDN查看

默认提供的 SettingsProvider 不允许我们修改应用程序设置的配置文件的路径,这就导致了以下问题:

  1. 设置保存在了 %appdata% 目录下,使应用程序不够绿色化
  2. 当用户把程序拷贝到其他电脑上时,设置将丢失
  3. 当用户升级程序时,设置将丢失(自带的 Upgrade() 过于复杂)

要解决上述问题,就需要由我们自己来定义在哪里存储应用程序设置
MSDN给出的方法是:自己实现一个 SettingsProvider

Java 学习笔记 (仍在更新)

Java 学习笔记 #4

by kenvix @ 2018-11-13 pm

内部类

内部类提供了更好的封装
例如,ConsoleOutput单独存在是没有意义的,将Output封装到Console则更具意义,即我们System.out

内部类可以访问外部类的私有成员,但外部类不可以。

枚举

java中,enum是类,但并不继承Object,因此具有类的特性。
当switch表达式传入enum时,case可以省略枚举名称。
个人原则:类是类 枚举是枚举 就算java枚举是类也不当类用

垃圾回收回调

Object类提供finalize()方法,重写此方法可以在被GC时让GC调用此方法

Java 学习笔记 #3

by kenvix @ 2018-11-10 pm

概念这种东西就是应付考试,实际生产中懂概念的不如有经验的。

多态

概念: 相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,称为多态。

Java 中的多态与 TypeScript, C# 中的多态

这三种语言的处理策略不同,涉猎语言较多的用户可能会因此写出 Bug。

Upcasting in C

考虑代码:

    class Father
    {
        public void Test()
        {
            Console.WriteLine("father");
        }
    }
    class Child : Father
    {
        public void Test() //equals: public new void Test()
        {
            Console.WriteLine("child");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Father father = new Child();
            father.Test();
        }
    }

输出:father

Upcasting in java

考虑代码:


class Sunzi extends Test {
    public void child() {
        System.out.println("child!");
    }

    @Override
    public void nmbd() {
        System.out.println("extend!sunzi");
    }
}

public class Test {
    public void nmbd() {
        System.out.println("super!");
    }

    public void father() {
        System.out.println("father!");
    }
}

Test test = (Test)new Sunzi();
test.nmbd();

输出:extend!sunzi

Java 中,test 的 运行时类型是 Sunzi 而不是 Test,这就导致其方法特征始终呈现为子类的特征,即实际调用了子类的方法
而在编译其则呈现父类特征,即你不能调用 test.child();

Note: 实例变量与 C# 相同,均为父类的实例变量。

Upcasting in TypeScript

考虑以下 TypeScript 代码:

class father {
    public test() {
        console.log("father");
    }
}

class child extends father{
    public test() {
        console.log("child");
    }
}

const f: father = new child();
f.test();

输出:child
TypeScript 最终是要类型擦除输出JavaScript的,这种类型注解没有任何意义。

拆装箱

即对 基本数据类型 和 包装类型 的直接赋值操作,等效过时的new, getValue。

++ Object 是它们的子类,因此有这种智障操作:

Object obj = 666; //自动 upcasting
int i = (Integer)obj; //用作编译期

许多智障面试官也许会出这种问题

Integer a = 1;
Integer b = 1;
a == b // true
b = 199;
a == b //false (unlinked)
Integer c = 666;
Integer d = 666;
c == d //false

Java 对 -128~127 的 Integer 进行了缓存,故 a b 指向同一个数据。

立刻转 C# 保平安。

字符串常量池

        String str1 = new String("fuck");
        String str2 = new String("fuck");
        String str3 = "fuck";
        String str4 = "fuck";
        System.out.println(str1==str2); //false
        System.out.println(str1==str3); //false
        System.out.println(str4==str3); //true

直接使用 String x = “” 表达式赋值的字符串由 JVM 常量池直接接管,相同的字符串引用将指向池中相同的值。
而 String str1 = new String(“fuck”); 则创建了字符串对象到 heap。

IDEA 明确告诉你这种方法很垃圾,但是sb面试官还是要问。

Java 学习笔记 #2

by kenvix @ 2018-11-07 am

java学得越深,越发觉得kotlin香

数据类型

参数传递

java 仅有值传递。传递一个对象时,传递该引用该对象的变量的拷贝(stack中产生引用变量的副本),但引用相同的对象,heap没有改变。
Python和java相反,只有引用传递。不过,Python的引用传递很有意思,改变实参基本类型的值不会影响形参,这是因为指了个新的。

数据类型和内存分配

基础数据类型(Value type)直接在栈(stack)空间分配,方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收。

引用数据类型,需要用new来创建,既在栈空间分配一个地址空间(reference),又在堆空间分配对象的类变量(object)。方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完成后从栈空间回收。

静态变量在heap的方法区储存。

类成员实例变量储存在heap,证明了之前成员变量性能低的猜想。

JVM细节

每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆,而每个线程包含一个栈区

类 包 对象

java的getter setter就是狗屎

import

实现import as的技巧 实现导入同名类
import com.text.Formatter;
private Formatter textFormatter;
private com.json.Formatter jsonFormatter;

import static导入可以导入类的静态成员,但垃圾java缺少as的支持使得污染问题十分严重

构造方法

如果签名一致,若子类构造器没有显式调用父类构造器,将隐式调用。

初始化顺序: 静态初始化块-普通初始化块-构造器

Java 学习笔记 #1

by kenvix @ 2018/11/06

Lambda (#1) ->

变量作用域: lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

声明和使用

    @FunctionalInterface
    interface excited {
        void gg(String message);
    }

    public static void main(String[] args) {
        excited fff = message -> System.out.println("lambda block called");

        fff.gg("fuck!!!");
    }

此接口要求必须是函数式接口,如果其中有两个方法则lambda表达式会编译错误。但java8的新特性如许实现如下写法:

interface MathOperation {
    int operation(int a, int b);
        default int addition(int a, int b){
        return a+b;
    }
}

泛型

基本类型不能作为类型参数

泛形要求能包容的是对象类型,而基本类型在java里不属于对象
但是基本类型都有其包装类型,也就是对象类型

大括号

1. 作用域

创建一个新的作用于防止变量污染

2. 声明匿名类

        new Object() {
            //content of anymous class
        };

3. 类的初始化块

import java.util.HashMap;

public class Test {
    private static HashMap<String, String> map = new HashMap<String, String>() {
        {
            put("Name", "June");
            put("QQ", "2572073701");
        }
    };  //可以在初始化时为HASHMAP赋值

    public int nonStatic;

    static { //静态块
        System.out.println("Static block called");
    }

    { //类的初始化块1
        System.out.println("Init block called");
    }

    Test() {
        System.out.println("Constrator block called");
    }

    { //类的初始化块2
        nonStatic = 555;
        System.out.println("Init2 block called");
    }

    public void nmbd() {}

    static {
        System.out.println("Static2 block called");
    }
}

OUTPUT:

Static block called
Static2 block called
Init block called
Init2 block called
Constrator block called

结合匿名类使用:

        new Object() {
            {
                System.out.println("called!");
            }
        };