关键字native,关键字final,Object根父类,Object的部分方法,枚举(十二)

351 阅读15分钟

native关键字

1.native的意思是本地的,内置的,原生的。

在Java中表示本地的xx

2.适用范围

只能用于修饰某个方法。

3.用它修饰的方法有什么特点?

用它修饰的方法体,是用非Java语言实现的,主要是C语言。

在Java层面看不到它的方法体。

【其他修饰符】 native 返回值类型  方法名(【形参列表】);  //没有方法体

用C实现的方法会编译为xx.dll以备调用。

//例如:
//System类中的部分代码:
public final class System {
    //修饰符:private static native, private表示私有的,仅限于System类自己使用
    //                               static表示静态的,只有静态的,它才可以在静态代码块中直接调用
    //                               native表示该方法是本地方法,它的方法体不是Java语言实现的,是由C语言实现的。
    private static native void registerNatives();
    static {//静态代码块
        registerNatives(); //调用了本类的一个静态方法
    }
    ...
}

4.用Java语言实现的方法调用时有入栈和出栈的过程,在Java虚拟机栈分配内存,用C语言实现的方法调用时也有入栈和出栈。在本地方法栈分配内存。

5、在Java中如何使用native修饰的方法呢?

(1)和普通的Java方法一样调用。

(2)如果子类需要对native方法进行重写,也是可以的。

//例如:Object类:所有类的根父类
public class Object {
    public native int hashCode(); //它也是一个native方法
    ...
}
 */
public class TestNative {
    public static void main(String[] args) {
        MyData my = new MyData();
        System.out.println(my.hashCode());
    }
}
//这个类没有显示声明它的父类,它的父类默认就是Object
class MyData{
    //重写方法的快捷键Ctrl + O

    @Override
    public int hashCode() {
        return 1234;//这里随便写的,符合语法的一个整数
    }
}

final关键字

1、关键字的意思:最终的

2、可以用在哪里呢?

​ 可以用它来修饰:类、方法、变量

3、用它修饰的类有什么不同?

​ 用它修饰的类不能被继承,即不能有子类,我们称为“太监类”。例如:System类,String类,Math类....这些类都非常重要,它们是Java中最最核心的类型。

4、用它修饰的方法有什么不同?

​ 用它修饰的方法可以被继承不能被子类重写。

例如:Object类不是final类,可以被子类继承。但是某个方法不希望被子类重写,那么就给这个方法单独加final。

5、用它修改的变量有什么不同?

​ 用它修改的变量的值不能修改,称为最终变量,或者常量。这个变量可以是局部变量、静态变量、实例变量。

​ 如果使用final修饰的静态变量或实例变量,必须有显式的初始化语句。

​ 如果使用final修饰的静态变量或实例变量,没有set方法,只有get方法。

Object根父类

1、API:Application Program Interface,应用程序编程接口,可以被使用的一组类库。

Java SE API文档:核心类库的文档

Java SE API文档,每一个JDK版本都会在Oracle官网提供下载。都是一组网页文件(.html),并且都是英文的。 很多中国的程序员,就将它进行了翻译(汉化)(有的翻译的不是很好,需要用英文原文文档辅助理解)。 又因为一直网页文件不方便携带和分享,所以有人就把它们用工具合成了.chm的文件。

2、Object类是核心类库的其中一个类。

我们可以通过: (1)查看API文档简单快速的了解它 (2)查看src.zip的源码详细的了解它

3、API文档长什么样?

(1)包 (2)包下的类、接口等 (3)某个类、接口的详细解释。

4、Object类在java.lang包

Object类是类层次结构的根父类。

如何解读这句话?

(1)所有类都会直接或间接的继承Object类

(2)如果一个类没有明确它的父类是谁,那么默认它的直接父类是Object。

(3)Object中的成员方法、成员变量都会继承到子类中。所有对象(包括数组)都可以调用Object中的方法。

(4)所有对象的创建都会调用到Object的无参构造。

(5)Object类型的变量可以接收任意类型的对象,这个变量与其他类型的对象构成多态引用。

(6)Object类型的数组,可以存储任意类型的对象

​ 注意:Object类型的变量可以接收一个基本数据类型的数组对象,

​ 但是Object[]类型的变量,不能接收一个基本数据类型的数组对象。

​ ==>很多API的重载方法,需要并存

​ public static void sort(int[] arr)

​ public static void sort(double[] arr)

​ public static void sort(Object[] arr) //Object[]数组的形参不能接收int[]类型的实参

5、Object类中有哪些方法?11个

Object之toString方法

1.public String toString():

​ 修饰符:public,在任意位置可见​

​ 没有static,必须通过“对象."才能访问

​ 没有final,说明子类可以重写

​ 没有native,在源码中可以看到它的方法体实现

​ 返回值类型:String

​ 方法名:toString,表示用字符串表示xxx,这里xxx表示的是对象的信息。

2.默认toString方法的返回值

默认toString方法返回的是 getClass().getName() + "@" + Integer.toHexString(hashCode());

​ getClass().getName():当前对象的运行时类型的名称

​ Integer.toHexString(hashCode()):把对象的hashCode值,用十六进制形式表示

例如:com.atguigu.api.Student@4554617c

​ com.atguigu.api.Student:对象的运行时类型的名称

​ 4554617c:对象的hashCode值,十六进制表示形式。

2.如果我们直接打印一个引用数据类型的变量(包括数组类型),默认就会自动调用它的toString方法。

​ 因为引用数据类型的变量中存储的是对象的地址值,Java是不能对外暴露内存地址的, ​ 它就用能代表对象信息的toString方法的返回值代替输出。

3.结果应是一个简明但易于读懂的信息表达式,建议所有子类都重写此方法。

4.如何重写?

idea有模板,Alt + Insert

Object之getClass()方法

1.public final native Class<?> getClass():功能,返回对象的运行时类型。

​ 修饰符:public 公共的,任意位置可见 ​ final 表示子类不能重写 ​ 没有static,必须通过“对象."才能访问 ​ 返回值类型:Class 其中暂时先不管,它是泛型

Class:是一种数据类型,首字母是大写的,区别于关键字class。这种数据类型的对象代表一种数据类型。Java会把所有数据类型加载到方法区的内存,并且每一种数据类型信息用一个Class对象存储起来。

2作用

与类的反射有关。

public class TestGetClass {
    public static void main(String[] args) {
        Object obj = "hello";
        //obj的编译时类型是Object类型
        //obj的运行时类型是String
        System.out.println(obj.getClass());//class java.lang.String

        Object arr = new int[5];
//        arr[0] = 12;//错误
//        arr的编译时类型是Object,相当于new int[5]被向上转型,所以只能调用父类Object有的成员方法,成员变量,
        //即失去了数组的特点。
    }
}

Object方法之 finalize()

1.protected void finalize() throws Throwable:

​ 修饰符:protected 可见性范围是 本类、本包(包括子类和非子类)、其他包的子类

换句话说,除了其他包的非子类都可以访问。

​ 抛出异常:throws Throwable 后面异常章节再说

2.功能

功能是用于垃圾回收器在回收垃圾对象之前做一些清理工作的。

比喻:给垃圾对象留临终遗言或交代后事的。

在实际开发中,用于资源对象做释放资源等清理工作。

比如:IO流的类型的对象,在IO流对象使用完成之后,需要释放资源。

​ IO流是和文件等系统资源传输数据用,读文件,写文件,都需要用到IO流。

​ 这里释放资源,不仅仅是JVM中对象的内存回收问题,还有操作系统层面的资源释放问题。

3.是否可以重写

子类可以重写,通常是资源类需要重写,IO流,数据库连接,网络连接等等资源类需要重写,普通的业务类是不需要重写它的。

4.面试题:final,finalize,finally的区别?

final:关键字

finally:抛出异常后的操作

finalize:它是Object类的方法,由GC来调用,用于配置系统资源或执行其他清除。

​ 这个方法每一个对象只会被调用一次。我们可以在finalize方法中,让对象复活。

​ 即在finalize方法中让一个有效的引用指向了当前准备被回收的垃圾对象时,对象就复活了。

​ 当这个对象再次称为垃圾后,就不会再调用finalize方法。

5.了解finalize的调用情况

public class TestFinalize {
    public static void main(String[] args) {
        for (int i=1; i<=10; i++){
            Example e = new Example(); //每一次循环 e都是新的变量,上一次创建的对象,就称为垃圾对象
        }

        //正常情况下,GC要等到内存吃紧的时候 或系统空闲的时候才开始工作。
        //我们要让GC来工作一下。
        System.gc(); //呼唤GC工作一下

        //为了让大家看到更多的对象被回收,我让main线程先睡一会,不要着急结束,因为main结束了JVM就关了。
        try {
            Thread.sleep(10000);//睡10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Example{
    @Override
    protected void finalize() throws Throwable {
        System.out.println("轻轻的我走了,正如我轻轻的来,不带走一段代码!");
    }
}

Object方法之 equals()

1.public boolean equals(Object obj):

指示其他某个对象(实参传入对象)是否与此对象(调用equals方法的对象)“相等”。

​ 修饰符:public 任意位置可见

​ 没有static,必须通过“对象."才能访问

​ 没有final,说明子类可以重写

​ 没有native,在源码中可以看到它的方法体实现

​ 返回值类型:boolean 结果只有两种true,false

​ 形参列表: (Object obj) 子类重写equals方法时,形参类型不能修改

​ Object类型的形参表示可以接收任意类型的实参对象。

​ 即当前对象 可以 和任意类型的对象做equals比较。即比较时,要考虑对象的类型。

2.怎样工作的

默认的equals的方法体实现: return (this == obj); 即默认equals方法也是比较两个对象地址。

​ 比较当前对象 和 其他指定对象的地址。

​ 例如:p1.equals(p2) p1是this,p2是obj

3,能否重写

子类可以选择进行重写。如何重写?idea有模板 Alt + Insert

4.重写的原则

如果不用模板,手动重写equals,那么需要遵循一些原则:

自反性:x.equals(x)一定要返回true

对称性:x.equals(y) 如果返回true,那么要求y.equals(x)也一定返回true

传递性:x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也一定返回true

一致性:x.equals(y)如果返回true,只要下次调用equals时,x和y的对象没有修改,那么也还是返回true,一个非空对象与null做equals比较一定是返回false

public class TestEquals {
    public static void main(String[] args) {
        Person p1 = new Person("110123199501011234","张三");
        Person p2 = new Person("110123199501011234","张三");
        System.out.println(p1 == p2);//false  ==比较的是对象的内存地址
        System.out.println(p1.equals(p2));//重写之前:false

        System.out.println(p1.equals(p1));//自己和自己比较,内存地址一样,直接返回true
        System.out.println(p1.equals(null));//直接返回false  因为p1如果也为null,那么这个代码就不能执行,报NullPointerException空指针异常
                                                    //如果能执行equals方法,p1一定是非空的,非空与null肯定是false
        System.out.println("-------------------");
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        System.out.println(str1 == str2);//"hello" 和  "hello"都是常量对象,是同一个,内存地址是一样
        System.out.println(str1.equals(str2));

        System.out.println(str1 == str3);
        System.out.println(str1.equals(str3));
    }
}
class Person{
    private String id;
    private String name;

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

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;  //比较内存地址,如果两个对象的内存地址一样,就直接返回true

        if (o == null || getClass() != o.getClass()) return false;
        //||短路或,如果左边的是true。右边就不看。
        //o == null  直接返回false
        //getClass() != o.getClass() :左边 getClass()表示获取的当前对象的运行时类型
                                    //右边o.getClass() 获取的是实参对象的运行时类型
                                    //类型如果不相等,就返回false

        Person person = (Person) o; //向下转型 ,因为方法的形参是Object,在实参传给形参o时,发生了向上转型
                                //按理说, Person类的equals方法是用于两个Person对象做比较,形参应该(Person p)更合适
                                //但是equals是重写Object的方法,所以形参列表补不能修改
                                //这里必须向下转型,因为我们想要比较对象的属性
                                //如果o按照Object类型处理,就无法访问 Person类的属性了。
        return Objects.equals(id, person.id) &&
                Objects.equals(name, person.name);
            //使用java.util.Objects工具类的equals方法,比较两个对象的id和name是否一样
            //&&,表示必须id和name一样才会返回true
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

Object方法之 hashCode()

1.public native int hashCode();

​ public :公共的

​ native;表示方法体不是Java语言实现的

​ int:返回值类型是int,表示一个整数

​ 功能:返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。如果对象没有存到哈希结构的容器中的话,那么hashCode没什么太多作用。

2.返回的是什么?

​ hashCode:是一个整数

​ 数字摘要:用一个数字代表xx详细信息, ​

例如:身份证号码,代表一个人的基本信息

在Java中hashCode值用来表示一个对象的信息。

hashCode是否是内存地址?

有的虚拟机确实直接用内存地址来作为hashCode值。

但是绝大多数都不是用内存地址来作为hashCode值。

它是用一些散列函数来对对象的实例变量进行计算得到一个int值作为hashCode。

为什么要用散列函数?

因为散列函数目标是每一个不同的输入可以获得一个不同的输出。

希望,如果对象不同,它的hashCode值就不同。

但是实际中,两个不同的对象,hashCode值可能相同。

3.能否重写

子类可以选择进行重写,idea模板 和equals一起重写。

4.为什么要和equals()一起重写呢?

因为Java要求:

​ ①如果两个对象equals相同,要求hashCode值必须相同

​ ②如果两个对象的hashCode值不同,那么这两个对象equals方法一定不同(false)

​ ③如果两个对象的hashCode值一样,那么这两个对象equals方法可能是true,false

枚举

1.什么是枚举?

​ 所谓的枚举就是一种特殊的类,特殊在于这种类的对象个数是固定的有限的几个。

​ 在实际开发中,会有要求:

​ 例如:星期类、月份类.......

2.如何实现枚举?

jdk1.5之前:

​ 考虑:如何限制对象的个数?

​ (1)构造器私有化;

​ (2)在枚举类的内部,提前创建好n个对象供外边使用;

​ (3)创建的对象应该是个成员变量(类中方法外).为了不创建对象也能调用成员变量,需要设置为静态变量.同时,为了保证对象不被篡改,用final修饰.

public static final Week MONDAY = new Week();

jdk1.5之后:

​ 可以简化声明枚举类型,新增关键字enum,可以使用它来声明枚举.

语法:

修饰符 enum 枚举类型名{
	常量对象列表;
	其他成员;
}

注意:

(1)如果常量对象列表后面还有其他成员,需要加;结束

(2)枚举类中常量对象列表必须在首行

(3)枚举类型如果没有声明构造器,也会有一个默认的无参构造

(4)枚举的构造器无论如何只能是private的,不用声明也会默认是private

(5)枚举类型可以声明多个构造器,当然多个构造器也都是private

​ 通过调用无参构造创建对象时,()可以省略

​ 通过调用有参构造创建对象时,在常量对象名后面直接加(实参列表),参数类型和个数一定要对应.

(6)语法上枚举类的其他成员变量没有要求必须时final的,但是习惯上也会声明为final,但这个时候,就必须要为这个参数赋值,否则会报错.

(7)用enum声明的枚举类型,默认会继承一个枚举公共父类,java.lang.Enum类,根父类仍然时Object类.这意味着使用enum声明的枚举类不能再继承别的类了.

(8)因为Enum类中重写了Object类中的toString()等方法.

toString返回的是常量对象的名称,当然如果需要的话,还可以再次重写.

(9)从父类Enum中还继承了别的新的方法

String name():用来返回枚举常量对象的名称;

int ordinal():用来返回枚举常量对象的索引,索引下标从0开始;

枚举类型名[] values:API中没有的一个方法,用来获取枚举常量中的所有对象.

(10)从jdk1.5之后,switch的case后可以直接写枚举类型的常量对象.(直接写就行,不用带引号或别的符号)

public enum Month {
    JANUARY(1,"一月"),//等价于调用有参构造器生成一个对象.
    FEBRUARY(2,"二月"),
    MARCH(3,"三月"),
    APRIL(4,"四月"),
    MAY(5,"五月"),
    JUNE(6,"六月"),
    JULY(7,"七月"),
    AUGUST(8,"八月"),
    SEPTEMBER(9,"九月"),
    OCTOBER(10,"十月"),
    NOVEMBER(11,"十一月"),
    DECEMBER(12,"十二月");

    private final int num ;
    private String description;

    public int getNum() {
        return num;
    }

    Month(int num, String description) {
        this.num = num;
        this.description = description;
    }
    public static Month getByValue(int value){
        for (int i = 0; i < Month.values().length; i++) {
            if(Month.values()[i].num == value){
                return Month.values()[i];
            }
        }
        return null;
    }


    @Override
    public String toString() {
        return num+"->"+super.toString()+"->"+description;
    }
}

包装类

1.什么是包装类

包装类是为了解决Java的byte,short等八种基本数据类型和void这些类型不属于面向对象范畴的问题提出来的概念.

Java为这些类型提供了对应的引用数据类型

byte --> Byte

short --> Short

int --> Integer

float --> Float

double --> Double

char --> Character

boolean --> Boolean

void --> Void

2.为什么要有包装类?

​ 八种基本数据类型和void这些类型不属于面向对象,导致我们后面很多为对象而设计的API,这些类型都不能使用.Java很多新的语法是为面向对象设计的,例如:泛型等,这些类型也不支持.

​ 为了解决这些兼容的问题,增加了包装类

为什么Java一开始不做"纯"面向对象语言?

因为Java发明的时候,受到了C语言的影响,C语言中是有这些类型的,并且有好处:(1)有很丰富的运算符(指令)支持;(2)不同的平台有统一的宽度标准.

3.这些包装类都在java.lang包中

4.关于包装类的操作:装箱和拆箱

装箱:把基本数据类型--->包装为---->包装类的对象

拆箱:把包装类的对象--->拆解为--->基本数据类型

jdk1.5之前:

​ 只能手动装箱和拆箱.

jdk1.5之后:

​ 支持自动装箱和自动拆箱.

但是自动装箱和拆箱只能发生在对应的类型之间 如 int <---->Integer

int b = 1;
Integer obj1 = b;//自动装箱

Double obj2 = new Double(1.0);
double d = obj2;//自动拆箱

5.API(部分)

(1)获取每一种类型的最大值和最小值(包装类.MAX_VALUE,包装类.MIN_VALUE)

//Integer.MAX_VALUE
//Integer.MIN_VALUE

(2)获取某个十进制的二进制形式,八进制形式等.

包装类.toBinaryString(十进制整数):转为二进制表达形式.

包装类.toOctalString(十进制整数):转为八进制

包装类.toHexString(十进制整数):转为十六进制

(3)大小写转化

Character.toUpperCase(char类型的变量):转大写

Character.toLowerCase(char类型的变量):转小写

(4)比较两个小数的大小

尽量不要使用'==',推荐使用:

Double.compare(参数1,参数2);它的结果是一个整数,0代表相等,1代表前一个参数比后一个参数大,-1表示前一个参数比后一个参数小.

(5)支持各种基本数据类型与字符串之间的转换(使用频率很高)

Integer.parseInt(字符串)返回的是int值

Double.parseInt(字符串)返回的是Double值

Boolean.parseInt(字符串)返回的是Boolean值

Long.parseInt(字符串)返回的是Long值

对象名.charAt(下标,从零开始)返回char类型

6.包装类对象的缓存

Byte -128~127

Short -128~127

Integer -128~127

Long -128~127

Float,Double没有缓存对象(小数,使用频率高的值范围很大,存起来很占内存)

Boolean:true,flase

Character: 0~127 正好是早期ASCII表中的字符.

这个范围的值使用频率很高,包装类对象的值在这个范围内的话,且没有主动使用new创建对象的话,那么是指向常量池中的缓存对象.

public class TestCache {
    public static void main(String[] args) {
        int a = 1;
        int b = 1;
        System.out.println(a == b);//true  比较数据值

        Integer i1 = 1; //右边的1自动装箱为Integer的对象
        Integer i2 = 1;//i1和i2指向了同一个缓存对象
        System.out.println(i1 == i2);//true  比较地址值

        int c = 128;
        int d = 128;
        System.out.println(c == d);//true  比较数据值

        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//false  比较地址值

        Integer i5 = new Integer(1);
        Integer i6 = new Integer(1);
        System.out.println(i5 == i6);//false

        Integer i7 = Integer.valueOf(1);
        Integer i8 = Integer.valueOf(1);
        System.out.println(i7 == i8);//true
    }
}

7.补充

当包装类与基本数据类型进行运算时,会自动拆箱为基本数据类型再进行比较;如果是两个基本数据类型,那么还会进行自动类型转换,变成同一种数据类型后比较.

public class TestExam {
    public static void main(String[] args) {
        Integer i = 1000;
        int j = 1000;
        System.out.println(i==j);//true

        System.out.println("----------------------");

        Integer m = 1000;
        double n = 1000;
        System.out.println(m == n);//true       (1)m先自动拆箱为int(2)int再自动类型提升为double

        System.out.println("---------------------------");
        Double d1 = 1.0;
        double d2 = 1.0;
        System.out.println(d1 == d2);//true  d1自动拆箱为double
    }
}

包装类一种特殊的引用数据类型,它的对象是不可变对象,一旦修改就会产生新对象。有相同性质的还有String类.

public class TestExam2 {
    public static void main(String[] args) {
        int num = 1;
        Integer obj = 1;
        Data data = new Data();
        data.x = 1;

        change(num, obj, data);

        System.out.println("num = " + num);//1
        System.out.println("obj = " + obj);//1
        System.out.println("data.x = " + data.x);//11
    }

    /*
    形参a是基本数据类型,它的修改和实参无关
    形参Data d ,是引用数据类型,它对成员变量的修改和实参有关。
    Integer b,是引用数据类型,Integer是特殊的引用数据类型,它的对象是不可变对象,一旦修改就会产生新对象。
         b+=10,会使得b指向新的对象,就和实参obj无关了
     */
    public static void change(int a , Integer b, Data d ){
        a += 10;
        b += 10;
        d.x += 10;
    }
}
class Data{
    int x;
}