JAVA学习笔记

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2018/09/12/JAVA学习笔记/#more

毕业后就没怎么碰Java了,为了后面Android的学习,将Java的基础知识整理一下。

0. 开发环境的搭建

在后面Java学习的过程中,要敲写示例代码在Linux上测试,因此需要先用虚拟机安装一个Linux发行版和Java。
准备使用的Linux发行版为Ubuntu18.04.1,考虑到后期Android学习也会在该虚拟机上,因此在安装过程就得先设置好硬盘大小和交换分区,感觉还是有必要记录一下。

0.1 为Ubuntu设置两个分区

首先是安装虚拟机VMware Workstation Pro和下载Ubuntu 18.04.1 LTS就不废话了。
然后新建虚拟机,选择“自定义(高级)”,再默认“下一步”,然后选择下载好的Ubuntu镜像文件ubuntu-18.04.1-desktop-amd64.iso,设置名字、密码等,再设置虚拟机文件放置位置(选择一个剩余空间大于100G的盘),选择核心数,设置内存,设置为“使用桥接网络”,默认IO控制器类型,默认磁盘类型,再选择“创建新虚拟磁盘”,磁盘大小为20G(作为root分区,存放Ubuntu自身系统等),磁盘文件名也改为“root.vmdk”,然后取消“创建后开启虚拟机”再点击“完成”,此时再选择“编辑虚拟机设置”,选中“CD/DVD (SATA) 正在使用文件autoinst.iso”,将其移除,再点击“添加”,选择“硬盘”,再一直点默认“下一步”,直到设置磁盘大小界面,设置为100G(作为work分区,存放后面的Android源码等),名字也改为“work.vmdk”,完成后,即可“开启此虚拟机”。

  • 这里解释下为什么这样繁琐的设置两个分区。
    假如只有一个分区,在以后的使用中,假如Ubuntu不小心被损坏,不能进入系统,那么系统里保存的数据就无法获取,资料也就没了。
    假如分成了两个分区,一个作为系统分区,一个作为工作数据分区,即使Ubuntu系统崩溃,只需重新安装一个Ubuntu,挂接原来的工作数据分区即可,所有的文件仍然会保留。此外,扩展分区大小,也相对比较方便。

0.2 为Ubuntu设置交换分区

首次启动虚拟机后,进入系统配置界面,选择默认语言“English”,点击“Install Ubuntu”,选择默认键盘布局,选择最小安装“Minimal installation”,去掉“Download update while installing Ubuntu”,不然要安装很久,然后点击“Continue”,选择“Someting else”,选中“/dev/sda”(20G的root分区),右键点击“New Partition Table……”,在弹出的窗口选择“Continue”,再选中新出现的“free space”,右键点击“Add……”,在“Use as:”选项卡里选择“swap area”,大小改为10240(Android编译要求16G的内存+交换分区,内存不够交换分区凑),完成后再点击该“free space”,把剩下的大小作为“Ext4 journaling file system”,“Mount point:”设置为根目录“/”,然后以类似的方式设置“/dev/sdb”,全部空间作为“Ext4 journaling file system”,“Mount point:”设置为手动编辑的“/work”,分区完成效果如下:

最后点击“Install Now”,选择时区为“Shanghai”,设置主机名字、密码等,再根据提示重启。

0.3 为Ubuntu安装基本软件

前面的博客Linux开发环境配置及shell script对嵌入式开发所需使用到的软件有过一次分析,另外博客搭建嵌入式Linux开发环境有如何安装和配置,参考博客,完成对ftp、ssh、nfs、smaba、vim的安装和配置,这些就不废话了,目前只是学习Java,至于其它的软件,后面需要再安装。

接下来就是安装本次的最主要的Java:

sudo apt-get install openjdk-8-jdk

这里只安装了JDK,因为JDK包含了JRE:

  • JDK (Java Development Kit):JAVA开发环境,包含JRE;
  • JRE (Java Runtime Environment):JAVA运行环境,包含虚拟机但不包含编译器;

以上就完成了Java学习环境的准备。

1. Java基础

1.1 第一个Java程序

{% codeblock lang:java [Hello.java] %}
public class Hello {
public static void main(String args[]) {
System.out.println(“Hello world!”);
}
}
{% endcodeblock %}

编译、执行:

javac Hello.java  //编译
java Hello        //执行

这里Java文件名首字母大写是因为:
在Java编程规范里,类的首字母要大写,而类的名字要和文件名一致;

1.2 数据类型

Java的数据类型分为8个基本数据类型3个引用数据类型

1.2.1 基本数据类型

序号数据类型关键字占用比特数/字节数取值范围缺省数值
1布尔型boolean8/1true,falsefalse
2字节型byte8/1-128~1270
3字符型char16/20~65535‘\u0’
4短整数short16/2-32768~327670
5整型int32/4-2147483648~21474836470
6长整型long64/8-9.22E-45~9.22E+180
7单精度浮点型float32/41.4013E-45~3.4028E+380.0F
8双精度浮点型double64/82.22551E-208~1.7977E+3080.0D

Java中所有的基本数据类型都有固定的存储范围和所占内存空间的大小,而不受具体操作系统的影响,来保证Java程序的可移植性。
整形数据默认为int数据类型,浮点型默认为double数据类型,如果要表示long型数据或float型数据,要在相应的数值后面加上l、L或f、F,否则会出现编译问题。

1.2.2 引用数据类型

Java的引用数据类型包括:数组(array)、类(class)、接口(interface),其缺省值都为null。
基本数据类型的变量名指向具体的数值,而引用数据类型的变量名指向存数据对象的内存地址,有点类型C语言的指针。
当引用指向null时,Java会自动释放对应的空间。

1.2.3 数据转换

Java在不丢失数据的前提下,可以实现自动转换,比如int型转换为long型,即由小范围变大范围可以,大范围变小范围不行

  • 示例:
    {% codeblock lang:java [Var.java] %}
    public class Var {
    public static void main(String args[]) {
    //基本数据类型:变量名指向具体的数值(数据在栈)
    boolean a = true; //布尔型
    byte b = 1; //字节型
    char c = ‘h’; //字符型
    short d = 2; //短整数
    int e = 3; //整型
    long f = 4L; //长整型
    float g = 3.14F; //单精度浮点型
    double h = 3.14D; //双精度浮点型

    //引用数据类型:变量名指向存数据对象的内存地址(引用在栈,数据在堆)
    int p1[] = new int[10]; //分配整数数组,类似C语言int p1[10];或int* p1 = malloc(10*sizeof(int));
    int p2[] = {1, 2, 3}; //分配并定义,类似C语言int p2[10] = {1, 2, 3};

      String str = "hceng";  //类引用,分配并定义,类似C语言char str[] = "hceng";
      
      p1  = null; //自动释放
      p2  = null;
      str = null;
    

    //数据转换
    int a1 = 30;
    long b1 = a1; //自动转换,因为long的表示范围比int大
    float c1 = 3.1f;
    int d1 = (int)c1; //必须使用强制转换,因为数据会丢失

      short s = 1;
      //s = s + 1;  //出错,因为s+1自动将s变成了int型与1相加,再赋值给short型的s,大范围变小范围不行
      //s = s + s;  //出错,因为s+s也会自动变成int型赋值给short型的s,大范围变小范围不行
      s = (short)(s + 1);
      s = (short)(s + s);
    

    }
    }
    {% endcodeblock %}

1.3 运算符、表达式、语句

Java的分支if, if...else, if...else if...else, switch和循环while, do...while, for; break, continue都和C语言是一样的。

1.4 方法

在C语言里函数的的叫法,在Java中没有了,类似的东西叫方法,所谓的方法就是用来解决一类问题的代码的有序组合,是一个功能模块。

1.4.1 方法的格式

public static 返回值类型 方法名称 (类型 参数1, 类型 参数2, ...) {
	程序语句;
   [return 表达式];
}
  • 示例:
    {% codeblock lang:java %}
    public static int add(int x, int y) {
    int sum;
    sum = x + y;
    return sum;
    }
    {% endcodeblock %}

1.4.2 方法的重载

即方法名相同,参数类型或个数不同,返回值也可相同也可以不同,就会调用到不同的方法。

  • 示例:
    {% codeblock lang:java [Method.java] %}
    public class Method {
    public static void main(String args[]) {
    System.out.println(add(1, 2));
    System.out.println(add(1, 2, 3));
    System.out.println(add(1.0f, 2.0f));
    }

    public static int add (int x, int y) {
    return x + y;
    }

    public static int add (int x, int y, int z) {
    return x + y + z;
    }

    public static float add (float x, float y) {
    return x + y;
    }
    }
    {% endcodeblock %}

  • 结果:

3
6
3.0

三个方法名字都一样,第一个和第二个参数个数不同,第一个和第三个参数类型不同,在调用方法时,传入的参数和哪一个匹配,就会调用到哪一个方法。

1.4.3 方法的参数

  • 示例:
    {% codeblock lang:java [Param.java] %}
    public class Param {
    public static void main(String args[]) {
    int x = 1;
    System.out.println(“Before x=” + x);
    fun(x);
    System.out.println(“After x=” + x);

      int p[] = new int[1];
      p[0] = 2;
      System.out.println("Before p=" + p[0]);
      fun(p);
      System.out.println("After  p=" + p[0]);
    

    }

    public static void fun(int x) {
    x = 100;
    }

    public static void fun(int[] p) {
    p[0] = 200;
    }
    }
    {% endcodeblock %}

  • 结果:

Before x=1
After  x=1
Before p=2
After  p=200

与C语言类型,如果传入的为形参,在方法里修改变量值,外部的方法的参数不会被改变,可以通过引用数组(类型C语言的指针)的方式,实现对传入参数的修改。
基本数据作为参数,方法内部对参数的修改不影响调用者;
引用数据作为参数,方法内部修改了堆,结果会保留下来;

2. Java面向对象编程

在Java中,方法类似C语言中函数,类似C语言中结构体。
Java面向对象编程有三大特性:封装性、继承性、多态性

2.1 类的引入

**在Java面向对象的思维里,把拥有共同特征的事物抽象出来叫做类,把符合这个类特征的个体叫做对象。**比如“人”就是类,具体的某个人“张三”就是对象,类的共同特征包含“人的名字”、“人的年龄”等。

如下面例子,定义了一个类Person,在main里,通过new创建具体的对象,再通过对象调用成员方法,或者直接访问类方法或类变量。

  • 示例:
    {% codeblock lang:java [Oop.java] %}
    //类的定义(类是创建对象的模板,创建对象也叫类的实例化)
    class Person {
    //类变量(调用不需要实例化对象)
    static int count;

    //类方法(调用不需要实例化对象)
    static void printPerson () {
    System.out.println(“This is a class of Person”);
    }

    //静态代码块(只会被调用一次,且先于构造代码块、构造方法执行)
    static {
    System.out.println(“Execute only once”);
    }

    //构造代码块(每次创建对象时都会被调用,且先于构造方法执行)
    {
    System.out.println(“Every call is executed”);
    count ++;
    }

    //类的成员
    String name; //成员变量
    int age; //成员变量
    //成员方法
    String getName() {
    return name;
    }

    //构造方法(没有返回值,方法名必须和类名一样)
    //实现new时传入参数,重载实现传入的参数多样化
    public Person () {
    name = “null”;
    age = 0;
    }

    public Person (String name) {
    this.name = name; //name是局部变量
    age = 0;
    }

    public Person (String name, int age) {
    this.name = name;
    this.age = age;
    }
    }

//一个源文件中只能有一个public类,且与文件名相同
public class Oop {
public static void main(String args[]) {
Person p0 = new Person(); //创建对象,即类的实例化
Person p1 = new Person(“hceng”);
Person p2 = new Person(“hceng, 23”);

    System.out.println(p0.getName()); //调用成员方法
    System.out.println(p1.getName());
    System.out.println(p2.getName());
    
    Person.printPerson();             //访问类方法
    System.out.println("Person number is " + Person.count); //访问类变量
}

}
{% endcodeblock %}

  • 结果:
Execute only once
Every call is executed
Every call is executed
Every call is executed
null
hceng
hceng, 23
This is a class of Person
Person number is 3

对该示例进行分析,涉及了好几个知识点。

  1. Person通过class关键词定义为类;
     
    2.1 通过static修饰的变量叫类变量,可以不通过new创建对象进行访问;
    2.2 通过static修饰的方法叫类方法,可以不通过new创建对象进行访问;
     
    3.1 通过static { }修饰的代码块叫静态代码块,在类被创建的时候调用,且只会被调用一次,先于构造代码块、构造方法执行;
    3.2 通过{ }修饰的代码块叫构造代码块,在每次类被创建的时候调用,先于构造方法执行;
     
    4.1 接下来是类的成员,包含成员变量成员方法
    4.2 成员方法:实现对类中成员变量的操作,提供某些功能,成员方法通过对象调用;
     
    5.1 接下来是
    构造方法
    ,没有返回值,方法名必须和类名一样,如果没实现构造方法,编译器自动加上一个无参数的空构造方法;
    5.2 **构造方法:**用于创建类的实例并对实例的成员变量进行初始化,构造方法通过new运算符调用;
    5.3 这里利用重载实现了传入参数的多样化,this关键字表示当前类;
     
    6.1 接下来是public修饰的类,也是唯一的一个,和文件名相同,里面有main成员方法;
    6.2 在main里,首先创建了三个对象,每个对象传入的参数不同,调用的构造方法也会不同;
    6.3 然后调用对象的成员方法;
    6.4 最后直接对类变量、类方法进行访问;

2.2 封装性

在Java中,将属性(变量)和方法封装成一个整体(也就是类),就是封装性的体现。

对于这个整体,里面有些属性外部可以直接访问,有些可能期望按要求访问,对于特殊的属性,可以先设置为私有的权限,再通过属性的方法进行访问,就属性的方法里就是我们期望访问的方式。

就比如下例中的年龄,我们期望年龄为非负的值,假如外部直接修改年龄就有被设置为负的风险。现在将年龄私有化,使外部无法访问,只能通过公共的类的方法进行访问,这个方法里就对传入的参数进行判断纠正,从而满足年龄非负的要求。

  • 示例:
    {% codeblock lang:java [Enc.java] %}
    class Person {
    /* 成员变量(属性) */
    //int age; //default
    private int age; //私有,只能供类内部访问

    /* 成员方法 */
    public void setAge(int age) {
    if (age < 0 || age > 200)
    age = 0;
    else
    this.age = age;
    }

    public int getAge() {
    return age;
    }
    }

public class Enc {
public static void main(String args[]) {
Person per = new Person();
//per.age = -1; //外部直接访问,不好控制
per.setAge(-1);

    System.out.println(per.getAge());
}

}
{% endcodeblock %}

  • 结果:
0

Java中四种权限:
private: 被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问;
**default:**默认访问权限,只允许在同一个包中进行访问;
**protected:**被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问;
public: 被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问;

权限类内同包不同包子类不同包非子类
private×××
default××
protected×
public

2.3 继承性

2.3.1 引入

为了实现代码的复用,Java中引入了继承性。
如下例子,子类Student继承了父类Person,就可以访问父类的属性(非私有)和方法(非私有)。

  • 示例:
    {% codeblock lang:java [Ext.java] %}
    class Person {
    //变量/属性
    private int age;

    //方法
    public void setAge(int age) {
    if (age < 0 || age > 200)
    age = 0;
    else
    this.age = age;
    }

    public int getAge() {
    return age;
    }

    public void printfInfo() {
    System.out.println("age = " + age);
    }

    final public void testFinal() {

    }

    //构造方法
    public Person() { //系统默认的构造函数,如果没有,编译器会自动生成
    System.out.println(“for test Person() call”);
    }

    public Person(int age) {
    this.age = age;
    System.out.println(“for test Person(int age) call”);
    }
    }

//Student从父类(Person)继承
class Student extends Person {
private String school;

public void setSchool(String school) {
    this.school = school; 
}

public String getSchool() {
    return this.school;
}

//覆写(方法名在父类中有同名方法)
public void printfInfo() {
    System.out.println("school = " + school);
}

//父类方法用了final修饰,子类无法覆写
//public void testFinal() {
//  
//}

//构造方法
public Student() { //系统默认的构造函数,如果没有,编译器会自动生成
    //super(); //调用父类的构造函数(无参数),默认调用,可不写
    super(15); //调用父类的构造函数(有参数)
    super.printfInfo(); //super就指代的父类
    System.out.println("for test Student() call");
}

}

public class Ext {
public static void main(String args[]) {
Student per = new Student();

    per.setAge(10); //方法来源于父类
    System.out.println(per.getAge());
    
    per.setSchool("ShenZhen"); //方法来源于自己扩展
    System.out.println(per.getSchool());
    
    per.printfInfo(); //覆写的父类方法
}

}
{% endcodeblock %}

  • 结果:
for test Person(int age) call
age = 15
for test Student() call
10
ShenZhen
school = ShenZhen

对该示例进行分析:

1.1 定义了一个类Person,包含一个变量(私有)、四个方法、两个构造方法;
1.2 第四个构造方法使用了final修饰,后面子类将不能对其覆写;
1.3 两个构造方法,一个不带参数,一个带参数;
 
2.1 子类Student从父类Person通过关键词extends继承,包含一个新变量(私有)、三个方法、一个构造方法;;
2.2 方法printfInfo和父类的方法printfInfo名字一样,父类被覆写,调用printfInfo会调用子类的printfInfo
2.3 方法testFinal被注释,因为父类使用final修饰了同名的方法,子类不能再覆写;
2.4 子类的构造方法被调用时,会先默认调用父类的构造方法,即用super表示,如果super带参数,表示调用父类带参数的构造方法,同时还可以通过super访问父类的属性(非私有)和方法(非私有);
 
3.1 在main里,首先new实例化了一个Student类;
3.2 调用Student类的setAge方法,这个方法是从父类继承过来的;
3.3 调用Student类的setSchool方法,这个方法是子类自己扩展的;
3.4 调用Student类的printfInfo方法,这个方法父类和子类都有,子类覆写父类的方法;

2.3.2 继承的限制

前面引入了继承,子类从父类继承过来,也就拥有了父类的一些特性,但继承也是有限制的:

  • 父类的私有属性不能被子类访问;

  • 父类的私有方法不能被子类访问;

  • 子类覆写的方法不能缩小权限,即父类public,子类不能private

  • 示例:
    {% codeblock lang:java [Limit.java] %}
    class Father {
    private int money;

    public void setMoney(int money) {
    this.money = money;
    }

    public void printfInfo1() {
    System.out.println(“This is Father printfInfo1()”);
    }

    private void printfInfo2() {
    System.out.println(“This is Father printfInfo2()”);
    }
    }

class Son extends Father {
//private void printfInfo1() {
//System.out.println(“This is Son printfInfo1()”);
//}

public void printfInfo2() {
    System.out.println("This is Son printfInfo2()");
}

}

public class Limit {
public static void main(String args[]) {
Son son = new Son();

    //son.money = 100;
    son.setMoney(100);
    
    son.printfInfo1();
    son.printfInfo2();
}

}
{% endcodeblock %}

  • 结果:
This is Father printfInfo1()
This is Son printfInfo2()

对该示例进行分析:

1 定义了一个类Father,包含一个变量(私有)、三个方法(其中一个私有);
 
2.1 子类Son从父类Father继承,只有一个方法;
2.2 方法printfInfo1private修饰,表示私有,而父类同名方法是公共的,不能覆写,无法编译通过;
2.3 方法printfInfo2public修饰,表示公共,父类也有个同名方法,但却是私有的,因此这里不是覆写,而是定义的新方法;
 
3.1 main里面实例化了一个类Son
3.2 Son从父类Father继承过来,父类的私有变量不能直接访问,只能通过父类提供的公共方法来访问;
3.3 父类的printfInfo1是公共的,子类的printfInfo1也只能是公共的;
3.4 父类的printfInfo2是私有的,子类无法访问,子类的printfInfo2是新定义的;

2.3.3 抽象类

**抽象类作用:**规定子类必须实现的方法,起“模板”作用;

  • 示例:
    {% codeblock lang:java [Abstract.java] %}
    //抽象类
    abstract class Father {
    public abstract void study(); //规定了子类必须实现的方法
    }

//子类
class Son extends Father {
public void study() { //实现抽象类定义的方法
System.out.println(“Son study”);
}
}

public class Abstract {
public static void main(String args[]) {
//Father father = new Father(); //抽象类不能实例化对象
Son son = new Son();

    son.study();
}

}
{% endcodeblock %}

  • 结果:
Son study

对该示例进行分析:

1 通过abstract关键字定义了一个抽象类Father,里面有一个abstract修饰的方法,但没有具体的实现内容;
 
2 子类Son从父类Father继承,实现了父类的方法;
 
3 main里不能对抽象类进行实例化,只能对抽象类的子类进行实例化,访问其方法

2.3.4 接口

**接口作用:**跟抽象类相似,起“模板”作用;子类可以继承多个接口,突破“单继承”的限制;

  • 示例:
    {% codeblock lang:java [Interface.java] %}
    //接口
    interface A {
    public static final int i = 10; //接口只能定义常量
    public abstract void printNum();
    }

interface B {
public static final String name = “hceng”;
public abstract void printString();
}

//子类
class Son implements A,B {
public void printNum() { //实现接口定义的方法
System.out.println("Num = " + i);
}

public void printString() { 
    System.out.println("String = " + name); 
}

}

public class Interface {
public static void main(String args[]) {

    Son son = new Son();
    
    son.printNum();
    son.printString();
}

}
{% endcodeblock %}

  • 结果:
Num = 10
String = hceng

对该示例进行分析:

1.1 通过interface关键字定义两个接口AB,里面定义了常量和抽象方法;
1.2 接口里面只能定义常量,抽象类可以定义常量和变量;
 
2.1 子类Son同时从接口AB继承,突破了抽象类突破“单继承”的限制;
2.2 子类Son依旧得实现接口里面所有的抽象方法;
 
3 main实例化Son,访问实现的方法;

2.4 多态性

多态性体现在方法和对象上。

在前面的例子中,方法的多态性已经接触过了,体现在方法的重载与覆写

  • **方法的重载(overload)?*定义多个同名方法,其参数类型、个数不同;
  • **方法的覆写(override)?*子类里实现跟父类同样的方法,覆盖掉父类;

对象的多态性就是父对象和子对象之前的转换,转换分为向上转换和向下转换。

  • 向上转型:子对象向父对象转型的过程,例如猫类转换为动物类(小范围转大范围自动),子对象独有的成员将不可访问(只能识别父对象中的内容);
  • 向下转型:父对象强制转换为子对象的过程,例如动物类强制转换为猫类(大范围转小范围强制),;

另外,可以通过引用变量 instanceof 类名的方式来判断引用变量所指向的对象是否属于某个类;

  • 示例:
    {% codeblock lang:java [Limit.java] %}
    class Father {
    public void printfInfo() {
    System.out.println(“This is Father”);
    }

    public void work() {
    System.out.println(“Father is working……”);
    }
    }

class Son extends Father {
public void printfInfo() {
System.out.println(“This is Son”);
}

public void palyGame() {
    System.out.println("Son is playing games……");
} 

}

class Daughter extends Father {
public void printfInfo() {
System.out.println(“This is Daughter”);
}

public void dance() {
    System.out.println("Daughter is dancing……");
} 

}

public class Cnv {
public static void main(String args[]) {
Father father1 = new Father();
Son son = new Son();
father1 = son; //向上转换
father1.printfInfo();
//father1.palyGame(); //只能调用被子类覆写的方法,不能调用只在子类中定义的方法

    Father father2  = new Daughter(); //创建子类的实例化对象(先向上转换)
    Daughter daughter = (Daughter)father2; //向下转换 (Daughter daughter = new Daughter();)
    daughter.printfInfo();  
    
    System.out.println("--------------------");
    
    Father f = new Father();
    Son s = new Son();
    Daughter d = new Daughter();
    //向上转换 示例
    printf(f);
    printf(s);
    printf(d);      
    //向下转换 示例       
    printAction(f);
    printAction(s);
    printAction(d); 
} 

public static void printf(Father f) {
    f.printfInfo();
}

public static void printAction(Father f) {
    if (f instanceof Son) {
        Son s = (Son)f;
        s.palyGame();
    }
    else if (f instanceof Daughter) {
        Daughter d = (Daughter)f;
        d.dance();
    }
    else if (f instanceof Father) { //Father要在最后,每个对象都属于Father
        f.work();
    }
}

}
{% endcodeblock %}

  • 结果:
This is Son
This is Daughter
--------------------
This is Father
This is Son
This is Daughter
Father is working……
Son is playing games……
Daughter is dancing……

对该示例进行分析:

1.1 首先定义了三个类,其中SonDaughter继承于Father
1.2 每个类里面都有一个同名的printfInfo方法和各自的独有的方法;
 
2.1 main方法里,首先创建了father1son这两个对象,然后将子类赋值给父类,进行向上转换,父类只能调用被子类覆写的方法printfInfo,最后结果也是调用的子类覆写的方法;
2.2 首先创建子类的实例化对象father2(即先向上转换,不然后面无法向下转换),此时father2的类型是Father,再用Daughter强制向下转换,得到daughter,此时可以调用子类的所有方法;
 
3.1 先分别实例化了三个类对应的对象;
3.2 调用方法printf,传入不同类的对象,自动向上转换,调用各自同名的方法;
3.3 调用方法printAction,传入不同类的对象,自动向上转换,再判断是对象是否属于对应类,再进行向下强制转换,从而调用其私有的方法(因为sd都属于f,所以对f的判断要放在最后);

2.5 异常

程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。
异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?。
Java提供了更加优秀的解决办法:异常处理机制
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类,Throwable又派生出Error类和Exception类。

  • Error(错误)Error类以及他的子类的实例,代表了JVM本身的错误,不能被程序员通过代码处理,所以不用管;
  • Exception(异常)Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件,可以被Java异常处理机制使用;

针对Javac对异常的处理要求,将异常类分为2类:

  • checked exception(可查异常):IOException以及它子类的异常,这些异常必须处理,否则编译不会通过;
  • unckecked exception(不可查异常):RuntimeException以及它的子类,不要求必须处理,发生异常时程序退出;

针对要处理的异常,可以自己处理,也可也通过throws抛出异常,让别人处理:

  • 示例:
    {% codeblock lang:java [Div.java] %}
    /* 除法运算: java Div 6 2 -> 6/2=3*/

public class Div {
public static void main(String args[]) {
int m, n, r;
m=n=r=0; //初始化

    try {
        m = Integer.parseInt(args[0]);
        n = Integer.parseInt(args[1]);
        r = div(m, n);
        craetException();
    } catch (NumberFormatException exception1) { //自己处理:数字格式异常(java Div 6 a)
        System.out.println("====An exception occurs1:"+exception1+"=====");
    } catch (ArithmeticException exception2) { //处理扔过来的异常:除零错误异常(java Div 6 0)
        System.out.println("====An exception occurs2:"+exception2+"=====");
    } catch (RuntimeException runtimeexception) { //处理所有不可查异常,比如位数不够(java Div 6)
        System.out.println("====An exception occurs:"+runtimeexception+"=====");
    } catch (Exception exception) { //处理自己通过throw创建的可查异常
        System.out.println("====An exception occurs:"+exception+"=====");
    } 
    finally {
        //无论是否发生异常,finally代码块中的代码总会被执行,完成清理类型等收尾善后性质的功能
        System.out.println("finally"); //finally中不要包含return
    }
    
    System.out.println(m+"/"+n+"="+r);
}

//扔给调用者处理:除零错误异常(java Div 6 0)
//public static int div(int m, int n) throws ArithmeticException {
public static int div(int m, int n) {  //ArithmeticException是不可查异常,出现后会自动抛出,可以不要throws
    int r;
    r = m / n;
    return r;
}
//自己创建的可查异常NullPointerException,必须处理,这里将其抛出,因此必须要throws
public static void craetException() throws Exception {
    throw new Exception("My Craeted Exception");
}

}
{% endcodeblock %}

  • 结果:
hceng@android:/work/java_learn/09th_exception$ java Div 6 3
====An exception occurs:java.lang.Exception: My Craeted Exception=====
finally
6/3=2
hceng@android:/work/java_learn/09th_exception$ java Div 6 a
====An exception occurs1:java.lang.NumberFormatException: For input string: "a"=====
finally
6/0=0
hceng@android:/work/java_learn/09th_exception$ java Div 6 0
====An exception occurs2:java.lang.ArithmeticException: / by zero=====
finally
6/0=0
hceng@android:/work/java_learn/09th_exception$ java Div 6 
====An exception occurs:java.lang.ArrayIndexOutOfBoundsException: 1=====
finally
6/0=0

对该示例进行分析:

1 该示例实现了一个整数除法运算,同时对输入参数的合法性进行了异常处理;
 
2.1 main方法里,首先定义了三个变量并初始化;
2.2 try { }代码块包含了可能出现异常的代码;
2.3 通过Integer.parseInt方法将字符args[0]args[1]转换成了整型,这里可能出现NumberFormatExceptionArrayIndexOutOfBoundsException异常,比如传入a就无法转换成对应数字,传入的参数少一个也会转换出错;
2.4 方法div的定义在后面,可能出现ArithmeticException异常,即初始不能为零;
2.5 方法craetException的定义在后面,在里面自己产生了一个可查异常,必须处理的异常;
2.6 接下来的catch (异常类 异常变量名) { }代码块,捕获到对应异常,即进行代码块代码;
2.7 无论是否发生异常,finally { }代码块的内容总会被执行,一般在里面做一些清理类型的善后收尾工作,注意不要在里面写return代码,因为如果前面的trycatch块中有returnthrow语句,会先执行finally块,此时finally中有return就直接返回了,无法再返回来执行trycatch块中returnthrow语句里面的内容;
2.8 接下来是打印出结果;
 
3.1 定义了Div方法,因为可能发生ArithmeticException异常,该异常是不可查异常,出现后可以自动抛出,所以在方法名定义那可以不加throws ArithmeticException
3.2 定义了craetException方法,该方法是通过new建立,throw抛出,从前面的图中可以知道Exception类异常包含IOException可查异常和RuntimeException不可查异常,因为是可查异常的关系,要么自己处理,要么手动抛出,这里抛出的话,方法名定义得加上throws Exception,否则编译都无法通过;
3.3 这里的两个方法,都是抛出异常,也可以自己通过try {} catch {}处理掉;在选择抛出时,前者因为是不可查异常,在方法名定义时可以不加throws 异常类型,后者反之得加上;
 
4 运行代码时,分别列举了正常运行、传入参数为字母错误、传入参数除数为零、传入参数少一个的情况,异常都被捕获到,程序没有当时直接退出,而是执行了异常处理代码块的内容,继续执行;

2.6 包及访问权限

为了更好地组织类,Java提供了包机制,解决类的同名冲突问题;
如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

假设有如下情况,hceng和jack两个程序员,实现了同一个Math.java类,实现了同名方法add,但内容不同,源码组织情况如下:

├── hceng
│   ├── Math.java
│   └── Permission.java
├── jack
│   ├── Math.java
│   └── TestAccess.java
└── Pack.java

使用如下命令编译:

javac -d . hceng/*.java jack/*.java *.java

编译后文件组织如下:

├── hceng
│   ├── Math.java
│   └── Permission.java
├── jack
│   ├── Math.java
│   └── TestAccess.java
├── pack
│   ├── hceng
│   │   ├── Math.class
│   │   ├── packagePermission.class
│   │   └── Permission.class
│   └── jack
│       ├── Math.class
│       └── TestAccess.class
├── Pack.class
└── Pack.java

执行java Pack效果如下:

add1:3
add2:4
add2:-1
Can only be accessed by this class: a = 1
Access in the same package: b = 2
Access in different packages: c = 3
Accessible anywhere: d = 4

源码见文末的Github链接,在10th_package里面。

先从文件组织的角度进行分析:

1.1 hceng路径下有个Math.java,里面有个Math方法和sub方法,通过package关键字指定了打包的路径为pack/hceng
1.2 jack路径下有个Math.java,里面有个Math方法,通过package关键字指定了打包的路径为pack/jack
 
2.1 编译时加入了-d参数,表示指定生成的包文件路径;
2.2 根据编译参数和类文件package的定义,就在当前路径在生成了pack/hcengpack/jack存放了对应的class
 
3.1 当前路径下有个Pack.java,通过import关键字将前面两个包导入了该类里面;
3.2 main里面,通过指定包的路径调用对应的方法,比如pack.jack.Math.addpack.hceng.Math.add,解决调用同名类的冲突;

再从访问权限的角度进行分析:

1.1 hceng路径下有个Permission.java,里面有个公共的Permission类和非公共的packagePermission类,外部只能访问公共的Permission类;
1.2 从类的权限角度可知:public类可以被外包访问,非public类只能在本包访问,另外,一个文件只能有一个public类;
 
2.1 hceng路径下有个Permission.java,在类Permission里定义了四种权限的属性(属性加static是因为可以不通过new创建,直接访问);
2.2 针对private的属性,只能在本类里访问,因此在本类的方法可以直接调用(方法加static是因为可以不通过new创建,直接访问);
2.3 针对default的属性,只能在同包里访问,因此在同包的另一个方法里实现了访问;
2.4 针对protected的属性,只能在不同包子类里访问,因此在jack/路径下创建了TestAccess.java,它属于pack.jack包,里面通过继承类访问了该属性;
2.5 针对public的属性,在任何地方都可以访问,因此在Pack里直接进行了访问;
2.6 从属性权限角度可知:类成员的访问权限如下:

权限类内同包不同包子类不同包非子类
private×××
default××
protected×
public

最后再补充一下jar,对于前面生成的pack目录,我们可以将其压缩打包,相关命令如下:

jar -cvf pack.jar ./pack   //打包
jar -tvf pack.jar          //查看包内容
jar -xvf pack.jar          //解包

操作逻辑和tar压缩一样的,很好记。

此时在当前目录生成pack.jar,删除pack文件夹,执行export CLASSPATH=.:pack.jar指定运行查找包的路径,执行java Pack仍可正常运行。

2.7 内部类

内部类,在类的内部定义一个类,通过内部这个类,可以访问到该类的私有属性。
内部类又分一般内部类、静态内部类、匿名内部类。

  • 示例:
    {% codeblock lang:java [Inner.java] %}
    class OutClass {
    //一般内部类
    private int a = 1;
    class InnerClass1 {
    public void printInfo() {
    System.out.println("a = " + a);
    }
    }

    //静态内部类
    private static int b = 2;
    static class InnerClass2 {
    public void printInfo() {
    System.out.println("b = " + b);
    }
    }
    }

interface PrintInterface {
public void printInfo();
}

class MyInterface implements PrintInterface {
public void printInfo() {
System.out.println(“MyInterface”);
}
}

public class Inner {
public static void main(String args[]) {
OutClass o = new OutClass(); //依次定义外部类
OutClass.InnerClass1 i1 = o.new InnerClass1(); //内部类
i1.printInfo();

    OutClass.InnerClass2 i2 = new OutClass.InnerClass2(); //直接定义内部类
    i2.printInfo();
    
    //正常调用继承于接口的类
    MyInterface m = new MyInterface();
    m.printInfo();
    
    //匿名内部类(没有名字的内部类,必须继承一个父类或实现一个接口)
    PrintInterface p = new PrintInterface() { 
        public void printInfo() {
            System.out.println("PrintInterface");
        }
    };
    p.printInfo();
}

}

{% endcodeblock %}

  • 结果:
a = 1
b = 2
hello MyInterface
hello PrintInterface

对该示例进行分析:

1.1 定义了一个OutClass类,里面包含两个私有变量和两个内部类,再通过内部类的方法访问私有变量;
1.2 两者区别是后者使用static修饰变量和类,使得可以不通过new直接访问类方法;
 
2.1 定义了一个接口PrintInterface,接口里定义了一个printInfo方法;
2.2 定义了类MyInterface继承于接口PrintInterface,并实现了printInfo方法;
 
3.1 在main里,先实例化外部类,再实例化内部类,才能方法问一般内部类的方法;
3.2 针对静态内部类,可以直接定义内部类,再访问其方法;
 
4.1 针对继承于接口/父类的子类,一般先实例化,再访问其方法;
4.2 也可以在使用时,再定义方法内容,再访问其方法,这就是匿名内部类,一个没有名字的内部类;

3. JNI

JNI(Java Native Interface)就是JAVA本地接口,它允许Java代码和Native代码进行交互,这里的Native代码指C、C++语言等编程语言。
无论是Android还是Linux,其底层都是用C语言编写的,因此很多程序和库都也是用C、C++来写的,重复利用这些Native语言编写的库是十分有必要的,而且一般Native语言编写的库具有更好的性能。
这样就产生了一个问题,Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。

3.1 JAVA调用C

3.1.1 Java访问C库的方法

Java访问C库的的步骤有三步:
1.Java中加载C库(System.loadLibrary);
2.建立Java函数名与C库函数名的映射关系;
3.在Java程序里调用C库的函数;

其中,建立Java函数名与C库函数名映射关系的方法有两种:隐式建立显式建立

  • 示例:
    {% codeblock lang:java [Jni.java] %}
    public class Jni {
    /* 1.加载 /
    static { //静态代码块(只会被调用一次)
    System.loadLibrary(“native”); //C语言会编译生成libnative.so,这里加载它
    }
    /
    2.声明 /
    public native static void hello_implicit(); //声明(static静态方法,可直接调用)
    public native static void hello_explicit();
    /
    3.调用 */
    public static void main(String args[]) {
    hello_implicit();
    hello_explicit();
    }
    }
    {% endcodeblock %}

{% codeblock lang:c [native.c] %}
#include <stdio.h>
#include “Jni.h”

//隐式建立:函数名必须固定格式,参考生成的Jni.h
void Java_Jni_hello_1implicit(JNIEnv *env, jobject cls)
{
printf(“hello java, I am from C language(implicit)\n”);
}

//显式建立,函数名自定义
void c_hello(JNIEnv *env, jobject cls)
{
printf(“hello java, I am from C language(explicit)\n”);
}

static const JNINativeMethod methods[] = {
{“hello_explicit”, “()V”, (void *)c_hello}, //Java里调用的函数名;JNI字段描述符(参数、返回值);C语言实现的本地函数
};

//一旦Java调用System.loadLibrary,就会先调用JNI_OnLoad
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;

//根据版本获得env,为后面提供函数
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) 
    return JNI_ERR; 

//查获取调用本程序的类
cls = (*env)->FindClass(env, "Jni"); 
if (cls == NULL) 
    return JNI_ERR;

//使用RegisterNatives将C和Java建立联系
if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)
    return JNI_ERR;

return JNI_VERSION_1_4;

}
{% endcodeblock %}

编译及设置:

javac Jni.java
javah -jni Jni

gcc -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux -fPIC -shared -o libnative.so native.c

export LD_LIBRARY_PATH=.

java Jni
  • 结果:
hello java, I am from C language(implicit)
hello java, I am from C language(explicit)

对该示例进行分析:

1.1 在Java里,先在静态代码块里加载对应的库;
1.2 然后声明要使用的方法,使用static修饰的方法不用实例化;
1.3 main里调用方法;
 
2.1 在C语言里,先包含一个头文件,这个头文件通过javac Jni.javajavah -jni Jni生成,里面有根据Java声明的方法自动生成的C语言函数定义;
2.2 隐式建立:只需函数名和生成的Jni.h一致就行,Java执行方法时就自动调用函数;
2.3 显式建立:函数名自定义,但需要创建JNI_OnLoad方法将Java和C建立联系;

3.1.2 Java和C库传递数据

Java调用C语言,一般都要进行数据的传递,包括Java传入数据,C语言返回数据,这里对基本数据类、字符串、数据进行传递示例。

  • 示例:
    {% codeblock lang:java [Jni.java] %}
    public class Jni {

    static {
    System.loadLibrary(“native”);
    }

    //基本类型数据
    public native static float typeData1(int a);

    //字符串
    public native static String typeData2(String str);

    //数组
    public native static int[] typeData3(int[] a);

    public static void main(String args[]) {
    int [] a = {1, 2, 3, 4};
    int [] b = null;
    int i;

      System.out.println(typeData1(4));
      System.out.println(typeData2("hceng"));
      
      b = typeData3(a);
      for (i = 0; i < b.length; i++)      
          System.out.print(b[i]+" ");
      System.out.println();
    

    }
    }
    {% endcodeblock %}

{% codeblock lang:c [native.c] %}
#include <stdio.h>
#include “Jni.h”
#include <stdlib.h>

#if 0
//隐式建立
jfloat Java_Jni_typeData1(JNIEnv *env, jclass cls, jint a)
{
printf(“C: get val: %d, will return (float)%d \n”, a, a);
return (float)a;
}

jstring Java_Jni_typeData2(JNIEnv *env, jclass cls, jstring str)
{
const jbyte *cstr;
cstr = (*env)->GetStringUTFChars(env, str, NULL);
if (cstr == NULL)
return NULL;

printf("C: get str: %s, will return jack \n", cstr);
(*env)->ReleaseStringUTFChars(env, str, cstr);

return (*env)->NewStringUTF(env, "jack");

}

jintArray JNICALL Java_Jni_typeData3(JNIEnv *env, jclass cls, jintArray arr)
{
jint *carr;
jint *oarr;
jintArray rarr;
jint i, n = 0;

carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) 
    return 0; 

n = (*env)->GetArrayLength(env, arr);

printf("C: get number: ");
for (i = 0; i < n; i++)     
    printf("%d ", carr[i]);
printf(", will return opposite number\n");


oarr = malloc(sizeof(jint) * n);
if (oarr == NULL)
{
    (*env)->ReleaseIntArrayElements(env, arr, carr, 0);
    return 0;
}

for (i = 0; i < n; i++)
    oarr[i] = carr[n-1-i];

(*env)->ReleaseIntArrayElements(env, arr, carr, 0);

/* create jintArray */
rarr = (*env)->NewIntArray(env, n);
if (rarr == NULL)
    return 0;

(*env)->SetIntArrayRegion(env, rarr, 0, n, oarr);
free(oarr);

return rarr;

}

#else

//显式建立
jfloat JNICALL c_typeData1(JNIEnv *env, jclass cls, jint a)
{
printf(“C: get val = %d, will return (float)%d \n”, a, a);
return (float)a;
}

jstring JNICALL c_typeData2(JNIEnv *env, jclass cls, jstring str)
{
const jbyte *cstr;
cstr = (*env)->GetStringUTFChars(env, str, NULL);
if (cstr == NULL)
return NULL;

printf("C: get str = %s, will return jack \n", cstr);
(*env)->ReleaseStringUTFChars(env, str, cstr);

return (*env)->NewStringUTF(env, "jack");

}

jintArray JNICALL c_typeData3(JNIEnv *env, jclass cls, jintArray arr)
{
jint *carr;
jint *oarr;
jintArray rarr;
jint i, n = 0;

carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) 
    return 0; 

n = (*env)->GetArrayLength(env, arr);

printf("C: get number: ");
for (i = 0; i < n; i++)     
    printf("%d ", carr[i]);
printf(", will return opposite number\n");


oarr = malloc(sizeof(jint) * n);
if (oarr == NULL)
{
    (*env)->ReleaseIntArrayElements(env, arr, carr, 0);
    return 0;
}

for (i = 0; i < n; i++)
    oarr[i] = carr[n-1-i];

(*env)->ReleaseIntArrayElements(env, arr, carr, 0);

/* create jintArray */
rarr = (*env)->NewIntArray(env, n);
if (rarr == NULL)
    return 0;

(*env)->SetIntArrayRegion(env, rarr, 0, n, oarr);
free(oarr);

return rarr;

}

static const JNINativeMethod methods[] = {
{“typeData1”, “(I)F”, (void *)c_typeData1},
{“typeData2”, “(Ljava/lang/String;)Ljava/lang/String;”, (void *)c_typeData2},
{“typeData3”, “([I)[I”, (void *)c_typeData3},
};

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;

if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) 
    return JNI_ERR; 

cls = (*env)->FindClass(env, "Jni"); 
if (cls == NULL) 
    return JNI_ERR;

if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)
    return JNI_ERR;

return JNI_VERSION_1_4;

}
#endif
{% endcodeblock %}

  • 结果:
C: get val: 4, will return (float)4 
4.0
C: get str: hceng, will return jack 
jack
C: get number: 1 2 3 4 , will return opposite number
4 3 2 1 

对该示例进行分析:

1.1 在Java中,依次声明了以基本数据类型、字符串、数组为参数和返回值的方法;
1.2 然后调用方法,传入参数,打印返回结果;
 
2.1 C语言里,分别隐式建立和显式建立编写了函数,两者主要区别在函数名和关系的建立上;
2.2 以显式建立为例,函数c_typeData1接收Java传入的基本数据类型,直接处理,返回相应数据;
2.3 函数c_typeData2接收Java传入的字符串,不能直接处理,使用GetStringUTFChars()获取字符串指针,用完后使用ReleaseStringUTFChars()释放,使用NewStringUTF()返回字符串;
2.4 函数c_typeData3接收Java传入的数据,不能直接处理,使用GetIntArrayElements()获取数据,GetArrayLength()获得数组长度,用完后使用ReleaseIntArrayElements()释放,使用NewIntArray()创建JNI数组,通过SetIntArrayRegion()将数组保存到JNI数组;
2.5 修改methods[],使Java的方法和C语言的函数对应,以及输入输出参数(参考生成的Jni.h);

3.2 C调用JAVA

C语言调用Java相对简单一点,流程大致如下:

1.创建一个Java虚拟机
2.找到要调用的类;
3.获取/设置属性(非必须):
 3.1获取属性ID;
 3.2获取/设置属性;
4.对于静态方法不需要示例化对象:
 4.1获取方法ID;
 4.2准备传入参数(非必需);
 4.3调用方法;
5.对于非静态方法需要实例化对象:
 5.1获取构造方法()ID;
 5.2创建对象;
 5.3获取方法ID;
 5.4准备传入参数(非必需);
 5.5调用方法;
6.销毁创建的Java虚拟机:

  • 示例:
    {% codeblock lang:java [Hello.java] %}
    public class Hello {
    private static String name;
    private static int age;

    public static void main(String args[]) { //静态方法
    System.out.println("java: name: "+ name + ", age: " + age);
    }

    public int typeData(String str) { //非静态方法
    System.out.println("java: get str: “+ str + " ,will return 100”);
    return 100;
    }
    }
    {% endcodeblock %}

{% codeblock lang:c [caller.c] %}
#include <stdio.h>
#include <jni.h>

/* create java virtual machine*/
jint create_vm(JavaVM** jvm, JNIEnv** env)
{
JavaVMInitArgs args;
JavaVMOption options[1];
args.version = JNI_VERSION_1_6;
args.nOptions = 1;
options[0].optionString = “-Djava.class.path=./”;
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
return JNI_CreateJavaVM(jvm, (void **)env, &args);
}

int main(int argc, char **argv)
{
int r;
int ret = 0;

JavaVM* jvm;
JNIEnv* env;
jclass cls;

jfieldID nameID, ageID;
jmethodID mid, cid;

jobject jobj;
jstring jstr;

/* 1. create java virtual machine */
if (create_vm(&jvm, &env)) {
    printf("can not create jvm\n");
    return -1;
}

/* 2. get class */
cls = (*env)->FindClass(env, "Hello");
if (cls == NULL) {
    printf("can not find hello class\n");
    ret = -1;
    goto destroy;
}

/----------------Non-generic part------------------/

/* 3.get/set field */
// 3.1 get field id (GetFieldID, GetStaticFieldID)
// 3.2 get/set field (Get<Type>Field,GetStatic<Type>Field / Set<Type>Field,SetStatic<Type>Field)

//java: private static String name;
nameID = (*env)->GetStaticFieldID(env, cls, "name", "Ljava/lang/String;"); //3.1
if (nameID == NULL) {
    ret = -1;
    printf("can not get field name\n"); //3.2
    goto destroy;
}
jstr = (*env)->NewStringUTF(env, "hceng");
(*env)->SetStaticObjectField(env, jobj, nameID, jstr);

//java: private static int age;
ageID = (*env)->GetStaticFieldID(env, cls, "age", "I"); //3.1
if (ageID == NULL) {
    ret = -1;
    printf("can not get field age\n");
    goto destroy;
}
(*env)->SetStaticIntField(env, jobj, ageID, 23); //3.2

/Branch1:------for static method, No need create object-------/
/* 4. call method */
// 4.1 get method id (GetMethodID, GetStaticMethodID)
// 4.2 Preparation parameter
// 4.3 call method (CallVoidMethod, CallStaticVoidMethod)
mid = (*env)->GetStaticMethodID(env, cls, “main”,"([Ljava/lang/String;)V"); //4.1
if (mid == NULL) {
ret = -1;
printf(“can not get method\n”);
goto destroy;
}
(*env)->CallStaticVoidMethod(env, cls, mid, NULL); //4.3

/Branch2:------for no static method, Need create object-------/
/* 4. create object */
// 4.1 get constructor method id (GetMethodID)
// 4.2 create new object (NewObject)
cid = (*env)->GetMethodID(env, cls, “”, “()V”); //4.1
if (cid == NULL) {
ret = -1;
printf(“can not get constructor method\n”);
goto destroy;
}

jobj = (*env)->NewObject(env, cls, cid); //4.2
if (jobj == NULL) {
    ret = -1;
    printf("can not create object\n");
    goto destroy;
}

/* 5. call method */
// 5.1 get method id (GetMethodID, GetStaticMethodID)
// 5.2 Preparation parameter
// 5.2 call method (CallVoidMethod, CallStaticVoidMethod)
mid = (*env)->GetMethodID(env, cls, "typeData","(Ljava/lang/String;)I"); //5.1
if (mid == NULL) {
    ret = -1;
    printf("can not get method\n");
    goto destroy;
}
jstr = (*env)->NewStringUTF(env, "www.hceng.cn"); //5.2
r = (*env)->CallIntMethod(env, jobj, mid, jstr);  //5.3
printf("%d\n", r);

destroy:
(*jvm)->DestroyJavaVM(jvm);
return ret;
}

{% endcodeblock %}

  • 编译、设置环境变量:
javac Hello.java
javap -p -s Hello.class // get Signature

gcc -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux -o caller caller.c -L /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/ -ljvm

LD_LIBRARY_PATH=/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/ 

./caller
  • 结果:
java: name: hceng, age: 23
java: get str: www.hceng.cn ,will return 100
100

对该示例进行分析:

1.1 Java文件里有个Hello类,里面有两个静态变量、一个静态方法、一个非静态方法;
1.2 非静态方法typeData 需要传入一个参数和会返回一个整型;
 
2.1 将创建Java虚拟机封装成函数create_vm(),以后有需要直接调用;
2.2 调用FindClass()找到Java中要调用的类;
 
3.1 假如要修改属性,需要先得到属性的ID,针对静态和非静态属性,分别调用GetStaticFieldID()GetFieldID()
3.2 通过NewStringUTF()得到可以在Java中使用的字符串;
3.3 通过SetStaticObjectField()SetObjectField()设置Java中的字符串;
3.4 通过SetStaticIntFieldSetIntField()设置Java中的整型;
 
4.1 如果要调用静态方法,就需要实例化对象;
4.2 通过GetStaticMethodID()获得静态方法的ID;
4.3 通过CallStaticVoidMethod()调用静态无返回值方法,最后一个参数是需要传入的参数;
 
5.1 如果要调用非静态方法,就需要先实例化对象;
5.2 通过GetMethodID()获得构造方法的ID,对于构造方法,参数名字始终为<init>
5.3 通过NewObject()实例化对象;
5.4 通过GetMethodID()获得非静态方法ID;
5.5 通过CallIntMethod()调用非静态方法,返回值就是Java方法的返回值;
 
6.销毁创建的Java虚拟机;

可以发现,要操作/调用Java中的属性或方法,都需要通过函数得到其ID,获取的函数又分为静态和非静态,然后再设置/调用等;
另外,获取ID的函数需要传入Signature(JNI字段描述符),可通过javap -p -s Hello.class命令,可以打印出对应类的Signature

4. Java的高级应用

4.1 泛型

泛型(Generics)是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
通俗的讲,泛型就是操作类型的占位符,即:假设占位符为T,那么此次声明的数据结构操作的数据类型为T类型。
比如下面的例子,在实例化对象时,可以指定不同数据类型,既可以是整型,也可以是字符串类型,从而打印的结果就完全不同。

  • 示例:
    {% codeblock lang:java [Generics.java] %}
    //普通类
    class Person1 {
    private int age;

    public void setAge(int age) {
    this.age = age;
    }

    public int getAge() {
    return this.age;
    }
    }

//泛型
class Person2 {
private T age;

public void setAge(T age) {
    this.age = age;
}

public T getAge() {
    return this.age;
}

}

//泛型接口
interface Person3 {
public void setAge(T age);
public T getAge();
}

//一般继承
class Student1 extends Person2 {

}

class Student2 extends Person2 {

}

//接口继承
class Student3 implements Person3 {
T age;

public void setAge(T age) {
    this.age = age;
}

public T getAge() {
    return this.age;
} 

}

class Student4 implements Person3 {
String age;

public void setAge(String age) {
    this.age = age;
}

public String getAge() {
    return this.age;
} 

}

public class Generics {
public static void main(String args[]) {
//常规方法,传入参数数据类型固定
Person1 p1 = new Person1();
p1.setAge(23);
System.out.println(p1.getAge());
System.out.println("--------------------------");

    //泛型,传入参数数据类型可以不固定
    Person2<Integer> p2 = new Person2<Integer>();
    p2.setAge(24);
    printInfo(p2);
    genericsMethod(p2);
    
    Person2<String> p3 = new Person2<String>();
    p3.setAge("24 years old");
    printInfo(p3);
    genericsMethod(p3);
    System.out.println("--------------------------");
    
    //定义数据类型通用的对象
    Person2<?> p4;
    p4 = p2; 
    printInfo(p4); //无法 p4.setAge();
    p4 = p3; 
    printInfo(p4);
    System.out.println("--------------------------");
    
    //子类继承:子类也泛型
    Student1<Integer> s1 = new Student1<Integer>();
    s1.setAge(10);
    printInfo(s1);
    genericsMethod(s1);
    
    //子类继承:子类不泛型
    Student2 s2 = new Student2();
    s2.setAge("10 years old");
    printInfo(s2);
    genericsMethod(s2);
    System.out.println("--------------------------");
    
    //接口子类继承:子类也泛型
    Student3<Integer> s3 = new Student3<Integer>();
    s3.setAge(10);
    System.out.println("interface:" + s3.getAge());
    
    //子类继承:子类不泛型
    Student4 s4 = new Student4();
    s4.setAge("10 years old");
    System.out.println("interface:" + s4.getAge());
    System.out.println("--------------------------");
    
    //受限泛型
    printInfo1(s1); //参数限制了只能是Number类或其子类
    //printInfo1(s2);
    //printInfo2(s1);
    printInfo2(s2); //参数限制了只能是String类或其父类
}

//方法参数通用
public static void printInfo(Person2<?> p) {
    System.out.println("printInfo:" + p.getAge());
}

//方法的参数泛型
public static <T> void genericsMethod(Person2<T> p) {
    System.out.println("genericsMethod:" + p.getAge());
}

//受限泛型
//上限:参数只能是Number类或其子类
public static void printInfo1(Person2<? extends Number> p) {
    System.out.println("extends:" + p.getAge());
}
//下限:参数只能是String类或其父类
public static void printInfo2(Person2<? super String> p) {
    System.out.println("extends:" + p.getAge());
}

}
{% endcodeblock %}

  • 结果:
23
--------------------------
printInfo:24
genericsMethod:24
printInfo:24 years old
genericsMethod:24 years old
--------------------------
printInfo:24
printInfo:24 years old
--------------------------
printInfo:10
genericsMethod:10
printInfo:10 years old
genericsMethod:10 years old
--------------------------
interface:10
interface:10 years old
--------------------------
extends:10
extends:10 years old

对该示例进行分析:

1.1 首先是一个普通类Person1,包含一个私有int类型数据,两个方法;
1.2 然后是一个泛型类Person2,包含的元素同上,把int换成了T
1.3 之后是一个泛型接口Person3,也是使用T代替数据类型,里面有两个方法模板;
1.4 接下来是两个类Student1Student2继承于Person2,前者数据类型是T,后者是String
1.5 最后是两个类Student3Student4继承于接口Person3,前者数据类型是T,后者是String
1.6 从这几个例子中,可以看到泛型可以用在类、接口和方法的创建中,称之为泛型类、泛型接口、泛型方法
 
2.1 main里,首先是普通的类的实例化,传入int类型数据,再打印;
2.2 接着是泛型的实例化,实例化时可以指定类里面的数据类型,这里的例子前者是int类型数据,后者是String类型数据;
2.3 接下来实例化对象p4,使用通配符?来代替数据类型,可以被其它数据类型赋值,但只能读取,不能设置;
2.4 然后是子类继承的实例化,前者定义类的时候类型是T,仍有泛型的特性,实例化的时候指定为int类型,后者定义类的时候类型是String,为普通的类型,只能为字符串;
2.5 接口的继承也是,前面定义的时候,可以选择子类继续保持类泛型,或者变成普通类;
2.6 受限泛型,方法printInfo1参数里有? extends Number,表示上限,参数限制了只能是Number类或其子类;方法printInfo2参数里有? super String,表示下限,参数限制了只能是String类或其父类;
2.7 从这几个例子中,可以看到子类从父类继承,或者从接口继承,仍可以保持泛型的特性,也可以设置为普通类;此外还有受限泛型,限制泛型支持的数据类型范围

4.2 反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射就是把java类中的各种成分映射成一个个的Java对象,例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

  • 示例:
    {% codeblock lang:java [Reflect.java] %}
    package hceng;
    import java.lang.reflect.Method;
    import java.lang.reflect.Field;
    import java.lang.reflect.Constructor;

class Person {
private String name;

void setName(String name) {
    this.name = name;
}

String getName() {
    return this.name;
}

};

public class Reflect {
public static void main(String args[]) throws Exception {

    //获得class的方法一
    Class<?> c1 = Class.forName("hceng.Person");  //must be caught or declared to be thrown

    //获得class的方法二
    Person p = new Person();
    Class<?> c2 = p.getClass();

    //获得class的方法三
    Class<?> c3 = Person.class;
    
    System.out.println(c1.getName());
    System.out.println(c2.getName());
    System.out.println(c3.getName());
    
    System.out.println("--------------------------");       

    int arr1[] = {1, 2, 3};
    int arr2[][] = { {1, 2, 3}, {1,2} };
    
    Class<?> a1 = arr1.getClass();
    Class<?> a2 = arr2.getClass();
    Class<?> a3 = int.class;
    
    System.out.println(a1.getName());
    System.out.println(a2.getName());
    System.out.println(a3.getName());
    
    System.out.println("--------------------------");   
    //类的实例化
    Class<?> j = Class.forName("jack.Person"); 
    
    Object p1 = j.newInstance(); //无参构造方法
    
    Constructor<?> con = j.getConstructor(String.class); //有参构造方法
    Object p2 =  con.newInstance("hceng");
    
    System.out.println("--------------------------");   
    
    //通过反射调用方法
    Method set =  j.getMethod("setName", String.class);
    set.invoke(p1, "jack1");
    set.invoke(p2, "jack2");

    Method get = j.getMethod("getName");
    System.out.println(get.invoke(p1));
    System.out.println(get.invoke(p2)); 
    
    System.out.println("--------------------------");   
    
    //通过修改属性
    Field name = j.getDeclaredField("name"); //公共、私有的都可以访问
    name.setAccessible(true); //因为name是私有的,需要先修改访问权限
    name.set(p1, "hello, hceng");
    name.set(p2, "hello, jack");
    System.out.println(name.get(p1));
    System.out.println(name.get(p2));
    
    System.out.println("--------------------------");   
    //通过传入参数,灵活调用不同的包
    Class<?> h = Class.forName(args[0]); 
    Constructor<?> c = h.getConstructor(String.class);
    Object o =  c.newInstance("hceng");
}

}
{% endcodeblock %}

{% codeblock lang:java [Person.java] %}
package jack;

public class Person {
private String name;

public void setName(String name) {
    this.name = name;
}

public String getName() {
    return this.name;
}

//构造方法
public Person() {
    System.out.println("Constructor1 of Person");
}

public Person(String name) {
    this.name = name;
    System.out.println("Constructor2 of Person, name is "+this.name);
}

};
{% endcodeblock %}

{% codeblock lang:java [Student.java] %}
package jack;

public class Student {
public String name;

public void setName(String name) {
    this.name = name;
}

public String getName() {
    return this.name;
}

//构造方法
public Student() {
    System.out.println("Constructor1 of Student");
}

public Student(String name) {
    this.name = name;
    System.out.println("Constructor2 of Student, name is "+this.name);
}

};
{% endcodeblock %}

编译:

javac -d . *.java
  • 结果:
hceng@android:/work/java_learn/14th_reflect$ java hceng.Reflect jack.Person
hceng.Person
hceng.Person
hceng.Person
--------------------------
[I
[[I
int
--------------------------
Constructor1 of Person
Constructor2 of Person, name is hceng
--------------------------
jack1
jack2
--------------------------
hello, hceng
hello, jack
--------------------------
Constructor2 of Person, name is hceng
hceng@android:/work/java_learn/14th_reflect$ java hceng.Reflect jack.Student
hceng.Person
hceng.Person
hceng.Person
--------------------------
[I
[[I
int
--------------------------
Constructor1 of Person
Constructor2 of Person, name is hceng
--------------------------
jack1
jack2
--------------------------
hello, hceng
hello, jack
--------------------------
Constructor2 of Student, name is hceng

对该示例进行分析:

1.1 整个示例有三个文件Reflect.javaPerson.javaStudent.java,其中Reflect.java在包hceng里,其余两个在包jack里;
1.2 在Person.java里,定义了一个类Person,包含一个私有变量、两个公共方法、两个构造方法(一个含参,一个不含);
1.3 在Student.java里,定义了一个类Student,包含一个私有变量、两个公共方法、两个构造方法(一个含参,一个不含);
 
2.1 在main里,示例了三种获取类的方法:通过包名字、通过实例化的对象、直接通过类名字,注意每种方法都会产生异常,这里为了简洁,直接将异常抛出;
2.2 得到了类,可以通过getName方法得到类完整名字;
 
3.1 定义一个一维数组,一个二维数组,一个整型数据类,得到其类名;
3.2 可以看到每种数据类型的类名都不一样;
 
4.1 通过包名,得到包jack.Person里类;
4.2 对于无参的构造方法,直接调用newInstance进行实例化;
4.3 有参的构造方法,先getConstructor得到构造函数,再调用newInstance传入参数实例化;
 
5.1 先通过getMethod,传入参数为方法名和数据类型,得到类里面的方法;
5.2 再通过invoke调用该方法,传入参数为实例化的对象和调用方法的参数;
 
6.1 通过getDeclaredField得到类属性;
6.2 如果属性是私有属性,需要使用setAccessible修改访问权限;
6.3 通过set修改属性内容,通过get获取属性内容;
 
7.1 之前通过import导入需要的包,从而调用不同的类,在程序中固定了,现在可以将包名作为参数传入,实现灵活的调用;,

在Java运行中,可以通过三种方式得到运行期间的类:通过包名、通过实例化的对象、通过类名字;
通过这个类再实例化对象,修改属性或者调用方法等操作;

5. 其它

所有示例源码:
Github

参考资料:
韦东山第四期Android驱动_Java快速入门

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值