1、Object类
Object类是所有类的最终父类。
还记得父类的由来吗?是由于很多类有很多公共的东西,我们把它们的公共属性提取出来作为基类,其他类继承,就不能每个类都写一样的东西了。
除了我们提取的公共方法,反过来思考,如果在基类中定义非私有方法,那么这个方法就会被继承。这可以用来干什么呢?
可以用来定标准。JAVA设计者就可以规定,凡是类就必须有xxx方法。因为所有类继承了Object类,只需要在Object类中定义这个方法,其他类就都有了。
所以,Object中的方法的由来有2方面的原因,一种是大家共有的,抽取出来。另一种是要求大家必须有的。不管什么原因,结果是一样的,就是所有类都有Object类中的非私有方法。
那么我们来看看,JAVA设计者给类定义了那些通用的方法?
为什么不谈属性?因为继承的属性不能被重写。而方法可以被重写,Object类中的方法最主要是定义一种规范。具体的实现,具体的类自己来干。如果你实现,就是继承Object中的方法体,即默认实现。而属性,不能被重写,不具备规范的作用。
Object类中的方法不多,除了构造方法外,只有这么11个方法。
其中有2 个protected 权限的方法 clone、finalize。 protected权限是什么?
跨包的子类可以访问方法,跨包的非子类不能访问。
-
clone方法返回值还是 Object 类型: 创建并返回此对象的一个副本。
这是一个native方法,说明这个方法的实现不是在java中,而是由C/C++实现,并编译成.dll文件,由java调用。registerNatives主要是将C/C++的方法映射到java中的native方法,实现方法命名的解耦。
native 关键字后面会讲。这个方法的详解 涉及到接口,学到接口部分再对这个方法进行详细解析。
-
finalize方法:垃圾回收涉及到的方法
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
通常情况下,我们不应该主动调用这个方法。新的知识又来了,什么是垃圾回收!等到 JVM 课程再介绍。
其余都是public方法。
-
equals 方法: 判断2个对象是否相等
public boolean equals(Object obj) {
return (this == obj);
}
默认实现是 使用 == ,==是判断2个对象的内存地址是否一样,内存地址一样 那么2个对象就是同一个对象。当然相等。
但是更多的时候,我们需要判断2个对象的 属性 是否一样。这个时候,就需要子类自己去重写这个方法。
你现在应该明白 == 与equals 方法的区别了。如果没有重写,这2个就是一样的。比较内存地址。如果equals 被重写了,这2个方法就不一样了。equals方法就是自定义的 比较相等的逻辑。
== 与equals 的异同:
1、== 是运算符 而 equals 是Object的一个方法
2、equals是方法,所以必须是对象才可以调用。而运算符都能用。
3、equals 不重写,与== 的结果一样,默认实现逻辑就是 ==
重写equals方法的原则:
1、先用 == 看内存地址是否一致
2、不一致,再用 instanceof 看类型是否一致
3、如果类型一致,再强制转换类型,变量属性,比较属性值是否一致
// idea 生成的 重写Animal类的方法。看看
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Animal)) {
return false;
}
Animal animal = (Animal) o;
return age == animal.age && high == animal.high && Objects.equals(type, animal.type) && Objects.equals(sex, animal.sex);
}
-
getClass 方法: 这个方法是为了获取 类文件。在反射中有很大的用处。
public final native Class<?> getClass();
这也是一个native方法。
-
hashCode 方法 :返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
public native int hashCode();
-
notify 方法:唤醒在此对象监视器上等待的单个线程。
public final native void notify();
-
notifyAll 方法: 唤醒在此对象监视器上等待的所有线程。
public final native void notifyAll();
-
toString 方法:返回该对象的字符串表示。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
这个方法不是 native 方法!但是他的实现却是调用 getClass 和hashCode方法。默认是返回地址字符串。名称与哈希值用@隔开。
直接输入一个类的对象时,就是调用这个类的toString方法
一般我们需要打印一个类,都会重写这个方法。
JDK自带的 类 String、Date、File、包装类等都重写了这个toString方法。
-
wait()方法 :在其他线程调用此对象的
notify()
方法或notifyAll()
方法前,导致当前线程等待
public final void wait() throws InterruptedException {
wait(0);
}
内部是调用另一个重载的wait方法。参数是0
-
wait(long timeout ) : 在其他线程调用此对象的
notify()
方法或notifyAll()
方法,或者超过指定的时间量前,导致当前线程等待。
public final native void wait(long timeout) throws InterruptedException;
-
wait(long timeout, int nanos) : 在其他线程调用此对象的
notify()
方法或notifyAll()
方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
Object里面 native 方法居多,native方法是调用C语言写的代码,不是java代码。执行更底层。
Object中最常用的方法是 equals 与toString
2、 JUnit 单元测试
单元测试类:
1、这个类是公共的public
2、有公共的无参构造器
3、声明单元测试方法:权限public、返回值为void,没有参数。
4、加上@Test注解
public class TestMain {
@Test
public void test1(){
System.out.println("单元测试");
}
}
3、包装类的使用
包装类,还是类。
是对基本数据类型的包装。但是基本数据类型不太好用,他不能调用方法。
有了包装类,基本数据类型变成包装类之后也可以像普通类一样,进行方法调用了。
自此,JAVA才真正可以说是面向对象了。基本数据类型以包装类的形式加入了类的大家庭。
3.1 相互转换
基本数据类型转包装类:
-
通过包装类的构造器:
int i = 5;
Integer bzInt = new Integer(i);
-
通过字符串参数构造
Float f = new Float("4.56");
Long i = new Long("55");
获得包装类对象中包装的基本类型变量:
Integer.valueOf(5);
Float.valueOf(5.1f);
Double.valueOf(5);
Boolean.valueOf(true);
等
包装类 转基本类:调用xxxValue
Integer in = Integer.valueOf(5);
int i1 = in.intValue();
3.2 自动装箱与拆箱
在JDK 5.0之后,有一个新特性,就是 基本数据类型与对应的包装类型的自动类型转换。叫做自动装箱与拆箱
看下面的代码:
@Test
public void test1(){
System.out.println("单元测试");
int a = 5;
automaticDisassembly(a);
double b = 10;
automaticDisassembly2(b);
}
private void automaticDisassembly(Integer i){
System.out.println(i);
}
private void automaticDisassembly2(Object i){
System.out.println(i);
}
automaticDisassembly方法的形式参数是 Integer 类型,即包装类,但是调用时却可以直接传入 int 型的 变量。
automaticDisassembly2 方法参数类型是Object,是类。但是调用时也是传入了 基本数据类型中的double,同样可以正确输出。
这不以为了 基本数据类型是个类或者可以赋值给类,而是 JAVA 替我们做了类型转换。
Integer a = 10;
int b = a;
上面的代码第一句是自动装箱,将基本数据类型 10 变成了包装类型 a;
第二句相反,将包装类型 a 直接赋值给了 基本数据类型int,即拆箱。
不用记忆什么是装什么是拆,因为都是自动的,需要什么直接赋值就好了。
可以看做是一个整体。
3.3 包装类(基本类型)与String 类型的转换
有了自动类型转换,包装类和基本类可以看做一个整体。
要转换为String,可以用基本类型 + ""
也可以用包装类的toString方法。
还可以用String类自己的valueof方法。
Integer a = 10;
String s = a.toString();
String s1 = String.valueOf(a);
String s2 = a + "";
int b = 5;
String b1 = String.valueOf(b);
String b2 = b + "";
上面是 转String,下面再看String类型的字符串转基本类型(包装类)
String a = "123";
Integer a1 = Integer.valueOf(a);
int a2 = Integer.parseInt(a);
Double a3 = Double.valueOf(a);
double a4 = Double.parseDouble(a);
用包装类的valueof是转成包装类,用parsexxx就是转基本类型。
看题。
Object o = true ? new Integer(1): new Double(2.0);
System.out.println(o);
输入1.0 原因是三元运算符 : 两边的类型必须一致。然后就有了自动类型提升。而且是先类型提升,再进行 三元判断。最终输入1.0;
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
Integer m = 1;
Integer n = 1;
System.out.println(m == n); //true
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false
按道理来说,应该全部都是false。为什么第二个是true呢?
因为JAVA 多干了点事。Integer 类里面定义了一个 内部类 IntegerCache
这里类里面有一个Integer类型的 数组cache,存储了 -128到127
每次用的时候,都去里面取,而不是造新的。是为了提升性能。这也就是为什么
第二组合第三组不一样,因为128超了 -128到127的范围。
而第一组本身就是new出来的对象,地址当然不相同。
4、Native关键字
使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++等非Java 语言实现的,并且被编译成了 DLL,由 java 去调用。
( 1)为什么要用 native 方法 java 使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,问题就来了。
例如: 有时 java 应用需要与 java 外面的环境交互。这是本地方法存在的主要原因,你可以想想 java 需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制: 它为我们提供了一个非常简洁的接口,而且我们无需去了解 java 应用之外的繁 琐的细节。
( 2) native 声明的方法,对于调用者,可以当做和其他 Java 方法一样使用一个 native method 方法可以返回任何 java 类型,包括非基本类型,而且同样可以进行异常控制。 native method 的存在并不会对其他类调用这些本地方法产生任何影响.
实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。
JVM 将控制调用本地方法的所有细节。如果一个含有本地方法的类被继承,子类会继承这个本地方法并且可以用 java语言重写这个方法(如果需要的话)。