• 我的首页
  • 星空画廊

My thought on web

Think what ? think

java

对Java的另一点抱怨

2017年4月3日 by caoli5288 Leave a Comment

最近长期沉迷于win,无心切到Linux下面码代码(各种IDE都装在Linux里面),昨天突然想做个模型,就随手打开个记事本开始写了。第一直觉是用最近几年用得最多的Java写,但是很快就发现脱离IDE要写出合法的Java代码其实是一件很麻烦的事情。

Java的语法和默认的一些原则的确是又臭又长。不得不说跑在JVM上的其他一些语言的语法糖的确有可取之处,至少当我用记事本写代码的时候是深深的这么觉得的。

比如给成员变量加上private修饰,仅仅访问就需要用getter和setter,这种约定俗成的做法就很不科学。因为Java无修饰的可访问范围是包,这实际上是一个很安全的范围,完全可以大大方方的使用,同包内直接访问。或者使用类似c#的get和set这样的修饰,把public修饰拆分成两部分,达到受限的公共访问目的(c#在变量后接一个花括号等于引入了新的语法,没必要)。事实上lombok已经实现了看起来差不多的@Getter和@Setter修饰。

另外类继承和接口实现使用两个不同的关键字也很啰嗦,因为类只能继承,接口只能实现,共用一个关键字并不会产生任何语义方面的问题。比如——

class BlueCat : Cat, Pet {
}

这样就声明了一个BlueCat类,继承自Cat类且实现Pet接口,语义上完全不会有问题(但一些其他语言广泛的拓展冒号的语义则很混乱)。类似这样可以简化语法而不对语义产生负面影响的方面还有很多,有些其他语言的特性的确值得学习。

另一点抱怨则是我在记事本上把大体上的接口和结构定义好,重启到Linux下去做实现的时候,觉得很多地方实现起来很“咯手”。

因为对象其实是一种很死板的结构,你不能对一个对象在内存中的数据做任何的变形,仅仅将对象当做一种数据结构来看待的话,其实远不如c的结构体使用起来灵活。由于这一限制,一些时候需要把一个对象的成员变量赋到另一个对象上来完成结构体强转就能完成的事情,实际上在各种方面都不友好。

怀念起结构体来的同时还怀念起了函数指针。虽然JDK8带来的Lambda使回调写更容易了,但是比起c里面函数指针的方式还是差得很远。函数(或者说方法)本质上就是内存上的一段可执行区域,既然是内存在c里面当然可以用指针随便胡搞瞎搞。而Java则把细节给隐藏起来,方法成了一种很特殊的东西,完全与变量区别开来。即使是JDK8的Lambda也没有改变这一本质,回调只能传入接口或类的实例,因为方法是始终与对象绑定的。

public class Cat {

    interface Moe {
        void moe();
    }

    Moe moe;

    void setMoe(Moe moe) {...}

    void meo() { moe.moe(); }

    static Cat newFatCat() {
        return new Cat().setMoe(() -> {...});
    }
}

而且Java存在的几个特性本质上就与函数指针有隔阂,抽象类和方法名重载。方法名重载决定了要在Java上重新实现函数指针也会是个很丑陋的东西,也会把原本对用户隐藏起来的方法签名给暴露出来。而抽象类带来的可实例化的类不允许存在未实现方法这一设定则封死了方法指针出现的余地。

Posted in: 笔记 Tagged: c, java

Java中的尾递归优化

2016年9月13日 by caoli5288 Leave a Comment

方法或者说函数的调用和返回使栈的大小增长或者收缩,而递归这种操作使得栈大小爆炸性增长,存在栈空间溢出的风险。在很多种编程语言中存在对尾递归这种特殊递归的优化机制,比如GCC编译器会把C语言中的尾递归优化成循环。而Java语言环境中无论在编译时还是在运行时都没有针对尾递归的优化,一个很简单的验证如下。

public static void main(String[] args) {
  func(1);
}

public static int func(int i) {
  return func(i + 1);
}

这段代码在我的笔记本上大概会调用一万多次,然后抛出一个‵StackOverflowError‵。是不是Java中就没有办法进行大规模递归了呢?是。但是有很多可以取巧的办法可以把递归转换成其他什么操作避免栈溢出,而代码看起来仍像递归。比如这篇文章的做法(文章本身说法有问题,实际上是Stream把递归转换成了循环+对象方法调用)。

Lambda表达式对递归的优化

再比如Groovy中的Trampoline。顺着这个思路来看其实可以达到类似的优化的办法有很多,并不是说一定要用到Stream这个东西。比如实现一个迭代器,稍微包装一下照样可以使代码看起来很像递归。

Posted in: 笔记 Tagged: java, 优化

为什么Java反射慢

2016年9月11日 by caoli5288 Leave a Comment

为什么Java反射慢?Java反射慢好像成了一个不言自明的命题,人们不经思考就会说Java反射慢而从未研究里面的原理。在互联网上搜索这个问题只发现了零星的一些答案,零碎不全面而没什么参考价值。首先需要知道的是反射执行方法的情景中,慢的并不是单纯的执行效率,而是其他地方。

首先是访问速度的问题。这里涉及到少许JVM和字节码层次的知识,静态代码中访问一个字段或者方法是直接根据字节偏移访问的,一切在编译时已经确定,而反射代码中访问则是需要在运行时通过遍历来查找,性能差别之大就类似已知下标从数组中取出对象和已知对象部分属性从数组中取出对象。这并非全部,随后还需要进行访问控制检查以确定取到的字段或方法是否可访问可执行,方法执行还要进行类型检查,而这些在静态执行中都是不存在的,这就带来了巨大的访问速度差异(其实还有一点是反射是通过JNI来执行的,JVM环境与Native环境之间跳转也需要时间)。

如果只是访问速度慢的话,是不是对于比较重的方法而言就不存在明显的性能差异了呢?这显然跟事实相违。反射还存在一些JIT优化的问题。要知道脱离JIT谈Java性能都是耍流氓,有没有JIT的Java性能差距能达到好几个数量级。关于反射的JIT优化并非像有些博客和知乎答主所说是一点也无,优化是当然存在的,比如de-reflection-optimization就是一项很好的优化,但是比起静态代码而言还是要少了非常多的重要优化,(比如方法内联。关于为什么方法内联可以显著提高性能这里就不展开了。)以至于最终还是带来了非常巨大的性能差异。

顺便提一下JDK1.7引入的MethodHandler这个东西。甲骨文设计了一个新的JVM指令(invokedynamic)来支持这个东西,这个东西第一眼看上去像是反射的替代品,但是实际上是被设计用来更好的支持动态语言的,JDK1.8上的Lambda和Nashorn就是它支持下的产物。但是不要以为这个东西速度就很快了,无数多个性能测试表明这东西速度比反射还慢,它访问资源同样要遍历,要经过几道检查。同样的,它支持下的Lambda也快不到哪里去,如果只是遍历数组这种操作还是省省不要用Lambda了,得不偿失。

Posted in: 笔记 Tagged: java, 反射

Nashorn脚本引擎初步体验

2016年3月14日 by caoli5288 Leave a Comment

Java8发布这么久,很多人因为工作重心的关系可能完全没有关注过,无可厚非,但是我觉得其中有个东西没有在发布的第一时间体验是一件很遗憾的事情,那就是Nashorn脚本引擎。

Java的动态语言支持是件老生常谈的事情。早些时候曾经把目光放在Groovy上面过,但是Groovy令人讨厌的地方实在太多所以不久就全然对它失去了兴趣。

早期内嵌在Java的JavaScript脚本引擎是使用的Mozilla的实现,这个实现性能实在是太慢了,但是Nashorn脚本引擎则完全不同。

其他过多的介绍就省略了,最关键的地方就是,这个引擎把js脚本编译成字节码,由于invokedynamic指令的作用使得方法调用效率接近原生代码,再加上原本的ScriptEngine接口就有Java接口绑定的功能,使得我们可以更愉悦的进行动态代码生成与访问。

// 注意。这里说的效率接近原生代码是指接近MethodHandle,实际上MethodHandle与直接方法调用相比非常的慢。

但是高兴得别太早,经过查阅资料和一些简单的试验,这个引擎并不是想象中那么的特别完美,一个简单的示例代码如下

var MyObj = Java.extend(java.lang.Object, {
        text: "A sample",
        show: function() {
                print(this.text);
        },
        toString: function() {
                return this.text;
        }
});

var obj = new MyObj();
print(typeof obj.text);
print(typeof obj.show);
print(typeof obj.toString);

print(obj.toString());

执行输出结果如下

undefined
undefined
function
A sample

也就是说,目前脚本中继承类\实现接口时,无法定义接口中不存在的新的方法,也无法直接访问新的字段。在脚本后台插入如下语句

for each (var m in MyObj.class.getDeclaredMethods()) {
        print(m.toString());
}

执行输出结果如下

public boolean jdk.nashorn.javaadapters.java.lang.Object.super$equals(java.lang.Object)
public java.lang.String jdk.nashorn.javaadapters.java.lang.Object.super$toString()
public int jdk.nashorn.javaadapters.java.lang.Object.super$hashCode()
public boolean jdk.nashorn.javaadapters.java.lang.Object.equals(java.lang.Object)
public java.lang.String jdk.nashorn.javaadapters.java.lang.Object.toString()
public int jdk.nashorn.javaadapters.java.lang.Object.hashCode()

可以证实确实没有对应方法生成。查阅相关资料得知目前有新的JSR正在做接口相关的工作,只能期待以后了。另外,局限于js的语言特性,接口的设计也不是随心所欲的。

Posted in: 笔记 Tagged: dynamic, java

标签

c docker dynamic fedora ffmpeg hevc java libmfx linux mingw mount mp4 NAS parallel qsv rime x264 x265 ZFS 事件 互联网 交叉编译 优化 分布式 反射 性能 文件系统 杂谈 游戏 站点 网卡 网络 自由 调优 透明代理

近期文章

  • lxc安装openwrt
  • NAS 文件系统方案的选择
  • 透明代理踩坑指北
  • QSV编码HEVC视频参数测试
  • FFmpeg中的几个新视频降噪滤镜

友情链接

  • 梦之地
  • Vultr

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

文章归档

Copyright © 2021 My thought on web.

Omega WordPress Theme by ThemeHall