Java 拾遗(之二)

Java 拾遗(之二)

背景

上一篇《拾遗》只是对Java语言基础知识的一个笼统的梳理,有些之前接触不太多的,或者有待深入学习的内容,如垃圾回收、多线程、序列化等,就略过去了;这是第二遍,将遇到的、遗漏的内容「补全」。其中有些主题还是会暂时只列出个大概,留待接下来一一深入学习。

算了算了,不看不知道,一看吓一跳,当初仗着对C++的映像来学Java,很多以为和C++大同小异的内容就直接跳过去了,结果现在翻书,一翻一个懵逼。没办法,只能一页一页的仔细扫描了。

前言

2011年7月28日,Oracle发布Java SE 7(2009年Oracle收购Sun后发布的第一个版本),引入了二进制整数、支持字符串的switch语句、菱形语法、多异常捕获、支持自动关闭资源的try语句等新特性;

2014年3月18日,Oracle发布Java SE 8,这次版本升级为Java带来了全新的Lambda表达式等等等等新特性;

(说这些是因为公司还在用jdk 7,估计到时候会踩很多坑(>﹏<))

正文

JVM

Java源文件——javac编译——.class字节码文件——java解释执行

Java语言既是编译型语言,又是解释型语言;或者说,Java既不是纯粹的编译型语言,也不是纯粹的解释型语言;Java语言程序的执行要经过编译和解释两个步骤;

负责解释、执行.class文件的是Java虚拟机,即JVM(Java Virtual Machine);字节码是平台无关的,只面向JVM;不同平台上的JVM都是不同的,但它们都提供了相同的接口;JVM是Java程序跨平台的关键,只要为不同的平台实现了相应的JVM,Java字节码就能在该平台运行;JVM向上给字节码的运行提供接口,向下适应所在的平台;

Oracle制定的Java虚拟机规范在技术上规定了JVM的统一标准,具体定义了如下细节:指令集、寄存器、类文件格式、栈、垃圾回收堆、存储区。

CLASSPATH

当使用“java java类名”来运行Java程序时,JRE根据CLASSPATH环境变量来搜索Java类(即使该Java类就在当前路径下,JRE也不理会);另,编译和运行Java程序还需要dt.jar和tools.jar两个文件,所以JDK 1.4之前的CLASSPATH通常长这样:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar

JDK 1.5开始,JRE会自动搜索当前路径下的类文件,并自动加载dt.jar和tools.jar;所以不再需要设置CLASSPATH;

如果你又手动设置了CLASSPATH,那么这些默认的行为就会失去,JRE会向1.4之前那样完全根据CLASSPATH来搜索类文件;

如果在运行程序时想临时指定搜索路径,那么JRE又会完全按照临时路径来搜索,而忽略CLASSPATH或默认行为;

一般可以用如下格式指定临时搜索路径:java -classpath %CLASSPATH%;.;dir1;dir2;...dirN java类名

数组

Java数组也是一种类型(和类、接口、枚举、基本类型等并列),它是一种引用类型。

内存模型:

面向对象

子类重写父类的方法时,要遵循“两同两小一大”原则:方法名、参数列表相同;返回值类型、声明抛出异常类型要小(即子类);方法的访问控制权限要更大;

抽象方法&抽象类

有抽象方法的类只能被定义为抽象类,抽象类却不一定含有抽象方法。
抽象类和普通类一样,也可以含有类方法 。
static和abstract不能同时修饰一个方法,但可以同时修饰内部类;
abstract和private不能同时修饰方法。

接口

接口中的成员变量默认默认以public、static、final修饰(都可省略),必须显式指定初值(接口不能含有初始化块);
接口中的普通方法默认以public abstract修饰;
接口不能继承类,只能继承其他接口;
接口中的类方法和默认方法(JDK 8)可以有方法实现;
实现接口方法时,只能用public访问控制符(“两同两小一大”原则);

内部类&外部类的相互访问

非静态内部类

在非静态内部类里可以直接访问外部类的private成员;方法是直接用变量名weight或OuterClass.this.weight;

在非静态内部类的方法里访问某个变量时,查找顺序是:本地(即方法内)> 内部类中 > 外部类;

外部类想要访问内部类的(private)成员,必须通过内部类的对象来访问;

class OuterClass {
    private int iOutPrivate = 0;

    private class InnerClass {
        private int iInPrivate = 1;

        private InnerClass() {
        }

        private void accessOuterclass() {
            // 直接访问外部类的private成员;
            System.out.println(OuterClass.this.iOutPrivate);
        }
    }

    public void accessInnerClass() {
        // 必须先实例化内部类对象;
        InnerClass innerInstance = new InnerClass();
        // 再通过内部类对象访问内部类的private成员;
        System.out.println(innerInstance.iInPrivate);
        innerInstance.accessOuterclass();
    }
}

非静态内部类对象存在时,外部类对象一定已经存在——“非静态”成员(内部类),要实例化才能使用;而外部类对象存在时,并不一定在内部持有一个内部类对象;

这里可以想象,一个类的静态部分是深色的,非静态部分是浅色的;非静态部分必须实例化才能使用;实例化外部类之后,它包含的非静态内部类必须再实例化一次才能使用。

根据“静态成员不能访问非静态成员”的规则,外部类的静态方法不能访问非静态内部类;非静态内部类中不允许定义静态成员。

静态内部类

根据“静态成员不能访问非静态成员”的规则,静态内部类不能访问外部类的非静态成员,只能访问静态成员;

外部类可以通过类名访问静态内部类的静态成员;或实例化静态内部类后,通过对象访问器非静态成员;

class OuterClass {
    private int iOutPrivate = 0;

    public static class InnerClass {
        private int iInPrivate;

        public InnerClass(int iInPrivate) {
            this.iInPrivate = iInPrivate;
        }

        public OuterClass createOuterClassInstance() {
            // 静态内部类不能访问外部类的非静态成员,
            // 但却可以访问外部类的构造器!
            // 所以构造器是静态的??
            return new OuterClass(iInPrivate);
        }
    }

    private OuterClass(int iOutPrivate) {
        this.iOutPrivate = iOutPrivate;
    }
}

静态内部类不能访问外部类的非静态成员,却可以访问外部类的构造器!

(这一小节好拗口( ̄. ̄))

局部内部类:在方法里定义的类;这是一个很“鸡肋”的语法,因为局部内部类的作用域太小了,仅限于在当前方法中。实际开发中很少使用。

匿名内部类,只使用一次的类;匿名内部类必须继承一个父类或实现一个接口;

confirmBtn.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {    
                System.out.println("Button Clicked!");
            }    
        });    

被匿名内部类访问的外部类的变量自动变成final修饰(Java 8之前需显式final);

可执行“宏替换”的final变量

满足下列三个条件的变量可被宏替换:

  1. 首要&必须条件:final修饰;
  2. 在定义时就制定了初始值;
  3. 初始值在编译时就可确定;

final修饰符一个重要的作用就是定义“宏变量”,编译器会把程序中所有用到该变量的地方直接替换为该变量的值。

如下都是宏变量:

final int a = 5;
final int b = 2 + 3;
final String s1 = "ab" + "cd";
final String s2 = "abc" + 99;
// 下面调用了方法,所以值无法在编译阶段确定下来,也就不是宏变量;
// final String s3 = "abc" + String.valueOf(99);

// 宏替换 & 常量池
System.out.println(s1 == "abcd");// true

看下面代码:

String s1 = "abc99";

String str1 = "abc";
String str2 = "99";
String s2 = str1 + str2;

System.out.println(s1 == s2);// false

要让结果为true,只要将str1和str2改为final修饰即可,这样编译器在编译阶段就能确定s2的值,就会让s2指向常量池中的"abc99";

Lambda表达式(Java 8)

(形参列表) -> {代码块}
stringList.stream().forEach(System.out::println);

Lambda表达式是一个对象,实现了某个函数式接口的对象;
以前大都作为参数(这时候其实就是对象),但还可以这样写:

Runnable r = () -> {
    for (int i = 0; i < 100; i++) {
        System.out.println(i);
    }
};

“显式”作为对象使用;(我)很少这样写。

方法引用&构造器引用:

类名::类方法
类名::实例方法
某个(任意)对象::实例方法

类名::new

stringList.stream().mapToInt(String::length).forEach(System.out::println);
stringList.stream().mapToInt("hello, world"::indexOf).forEach(System.out::print);

枚举类

Java 5新增了enum关键字(和class、interface地位相同),用来定义枚举类。

枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或多个接口,也可以定义自己的构造器。一个Java文件中最多只有一个public修饰的枚举类,且该文件必须和该public枚举类同名。

但枚举类终究不是普通类,它与普通类有如下区别:

  • 枚举类默认继承了java.lang.Enum(Enum继承了Object类)类,所以不能再【显式】继承其他类;Enum实现了Serializable和Comparable两个接口;
  • 使用enum定义、【非抽象】的枚举类默认使用final修饰,所以枚举类不能派生子类;(注意这句话);
  • 枚举类的构造器默认以private修饰,显式也只能用private修饰;
  • 枚举类的所有实例必须在第一行显式列出,否则这个枚举类永远不能产生实例;列出这些实例时,系统会自动添加public static final修饰,无需显式添加;

枚举类提供了一个values()方法,可以方便的遍历所有的枚举值。

public class ReviewJava {
    public static void main(String[] args) {
        for (SeasonEnum season : SeasonEnum.values()) {
            System.out.println(season);
        }
        System.out.println(SeasonEnum.SPRING.compareTo(SeasonEnum.FALL));
    }
}

enum SeasonEnum {
    SPRING, SUMMER, FALL, WINTER;
}

output:

SPRING
SUMMER
FALL
WINTER
-2

编译上面的Java程序,将生成一个SeasonEnum.class文件,这说明枚举类确实是一个特殊的Java类。

因为Enum实现了Comparable接口,所以同一个类的枚举值是可以比较的,后面的大。

interface GenderDesc {
    void info();
}

enum Gender implements GenderDesc {
    MALE("男") {
        @Override
        public void info() {
            System.out.println("这是男");
        }
    }, FEMALE("女") {
        @Override
        public void info() {
            System.out.println("这是女");
        }
    };
    private final String name;

    private Gender(String name) {
        this.name = name;
    }
}
  1. MALE("男")是调用私有构造器实例化枚举值;前面SeasonEnum里SPRING等没有是因为默认的构造器没有参数,所以省略了;
  2. FEMALE("女"){...}是实现接口方法,语法类似于匿名内部类;
  3. 所以这里的MALE,FEMALE并不是Gender枚举类的实例,而是其匿名子类的实例;
  4. 上面说“非抽象枚举类不能被继承”,这里包含抽象方法(接口),默认是以abstract修饰;

Stream流式编程

java.util.stream

看印象笔记 :)

其他

Java 7新增的二进制整型:int binVal = 0B011;

Java的左移(<<)、右移(>>)都是算术移位,要考虑符号位的;无符号右移(>>>)是逻辑移位,不考虑符号位(即带符号位一起移动);

不要被溢出的情形迷惑了!

逻辑移位,不管左移右移都添0;

算术移位,正数,左移右移都添0;负数,左移添0右移添1;

Integer.toBinaryString(int i)/toHexString/toOctalString输出二进制/十六进制/八进制形式整数;

Java中方法的参数传递方式为值传递,即传递的是实际参数的副本。(好久好久没碰到这个词了,上一次还是在大一学C的时候吧?)

静态导入“import static”用于导入某个指定类的某个静态变量、方法或全部的静态变量、方法。

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);

false

Java最基本的东西,想想发生了什么。

Java规范并没有指定boolean类型所占用的内存空间大小;但由于大部分计算机允许分配的最小内存单位是字节,因此boolean大部分时候实际上占用8位。

除了break outer,还有continue outer:

outer:
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 4; j++) {
        if (j == 2)
            continue outer;
    }
}

术语:类变量、类方法;实例变量、实例方法;静态变量、静态方法;

术语区分:重载vs重写

重载发生在同一个类的多个同名方法之间;返回类型或参数不同;
重写发生在子类和父类的同名方法之间,方法签名必须完全一致,否则就变成了重载从父类继承来的方法;

如果子类定义了和父类同名的实例变量,则会发生子类实例变量 隐 藏 父类实例变量的情形。注意是「隐藏」,而不是覆盖!可以通过super.变量名来访问到。

看下面的代码:

public class ReviewJava {
    public static void main(String[] args) {
        Derived d = new Derived();
        // 编译错误
        // System.out.println(d.tag);
        // 访问Parent类的tag;
        System.out.println(((Parent) d).tag);
    }
}

class Parent {
    // 注意是public;如果是private,这个变量就跟子类没关系了;
    public String tag = "Parent";
}

class Derived extends Parent {
    private String tag = "Derived";
}

实际上初始化块是一个假象,使用javac编译Java类后,该Java类中的初始化块会消失——初始化块中的代码被“还原”到每个构造器中,且位于构造器所有代码的前面。

以前没有注意区分的点:final修饰基本类型变量和引用类型变量的区别——修饰基本类型时,基本类型的值不可变;修饰引用类型时,引用不可变,即该引用不可指向其他地方;但引用所指的对象不受影响


对于一个private方法,因为它仅在当前类中可见,子类无法访问该方法——但子类可以定义一个和它完全一样的方法——这不是重写,这是完全独立的重新定义!这时用final修饰该private方法也毫无意义or影响,private屏蔽了final的作用。


接口,体现的是规范和实现分离的设计哲学。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,505评论 18 399
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,030评论 0 62
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,607评论 0 11
  • 今天七点四十就回家了 买了很多好吃的东西 自主招生该报哪个学校呢
    商波子阅读 222评论 0 0
  • -#import"GPUImageBrightnessFilter.h"//亮度 #import"GPUImage...
    baihualinxin阅读 410评论 0 0