【Java基础复习3】- final、static等关键字

float 与 double

Java 不能隐式执行向下转型,因为这会使得精度降低。
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
// float f = 1.1
1.1f 字面量才是 float 类型
float f = 1.1f

隐式类型转换

因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。
short s1 = 1;
// s1 = s1 + 1;
但是使用 += 或者 ++ 运算符会执行隐式类型转换。
s1 += 1;
s1++;
上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);

final关键字

  1. 数据
    声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
  • 对于基本类型,final 使数值不变;
  • 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
样例 ``` final int x = 1; // x = 2; // cannot assign value to final variable 'x' final A y = new A(); y.a = 1; ```
  1. 方法
    声明方法不能被子类重写。

    private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。


  2. 声明类不允许被继承。

static 关键字

  1. 静态变量
  • 静态属性:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量随着类的加载而加载;静态变量的加载早于对象的创建。由于类只会加载一次,则静态变量在内存中也只会存在一份,存在方法区的静态域中
    与之对应的是非静态属性(实例变量):每创建一个实例就会产生一个实例变量,它与该实例同生共死。
示例,点击查看代码
public class A {

  private int x;         // 实例变量
  private static int y;  // 静态变量
}
  1. 静态方法:
    静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
  • 只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。
示例,点击查看代码
public class A {

    private static int x;
    private int y;

    public static void func1(){
        int a = x;
        // int b = y;  // Non-static field 'y' cannot be referenced from a static context
        // int b = this.y;     // 'A.this' cannot be referenced from a static context
    }
}

  1. 静态语句块
    静态语句块在类初始化时运行一次。
示例,点击查看代码
public class A {
    static {
        System.out.println("123");
    }

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
    }
}
  1. 静态内部
    (这种常常被我忘记)
    非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
    静态内部类不能访问外部类的非静态的变量和方法。
示例,点击查看代码
public class OuterClass {

    class InnerClass {
    }

    static class StaticInnerClass {
    }

    public static void main(String[] args) {
        // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
       ** StaticInnerClass staticInnerClass = new StaticInnerClass();**
    }
}
  1. 静态导包
    在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
    import static com.xxx.ClassName.*

  2. 初始化顺序
    静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

存在继承的情况下,初始化顺序为:

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)
  • 子类(构造函数)

Object父类的通用方法

概览

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native Class<?> getClass()

protected void finalize() throws Throwable {}

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException

equals()

等价与相等

对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。

Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y);      // false

如何重写

  1. 检查是否为同一个对象的引用,如果是直接返回 true;
  2. 检查是否是同一个类型,如果不是,直接返回 false;
  3. 将 Object 对象进行转型;
  4. 判断每个关键域是否相等。
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    EqualExample that = (EqualExample) o;

    if (x != that.x) return false;
    if (y != that.y) return false;
    return z == that.z;
}

hashCode()

  • hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值.
  • 在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象哈希值也相等。
  • 对象按照自己不同的特征尽量的有不同的哈希码,作用是用于快速查找
  • 另一个应用就是hash集合的使用
  • HashSet 和 HashMap 等集合类使用了 hashCode() 方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现 hashCode() 方法。

如何重写

理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。

**原因:**R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化。

toString()

默认返回 对象名@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。

clone()

  • clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
  • 实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法。
  • 应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。

引用的拷贝

    //引用拷贝
   public static void main(String[] args) {
        Teacher teacher = new Teacher("riemann", 28);
        Teacher otherTeacher = teacher;
        System.out.println(teacher);
        System.out.println(otherTeacher);
    }

这里打印的结果:

com.test.Teacher@28a418fc
com.test.Teacher@28a418fc

可以看到,打印的结果是一样的,也就是说,二者的引用是同一个对象,并没有创建出一个新的对象。teacher和otherTeacher的只是引用而已,他们都指向了一个相同的对象Teacher(“riemann”,28)。 这就叫做引用拷贝。
【Java基础复习3】- final、static等关键字

因此要区分引用拷贝和对象拷贝的区别,下面要介绍的就是对象拷贝。

对象的拷贝分为浅拷贝与深拷贝

浅拷贝就是返回同一个引用的对象。

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象

定义:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。

简而言之,浅拷贝仅仅复制所考虑的对象,而不复制考虑的对象所引用的对象。

结果分析: 两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。
【Java基础复习3】- final、static等关键字

深拷贝就是返回和原始对象的引用类型引用不同对象。
定义:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
深拷贝把要复制的对象所引用的对象都复制了一遍。
结果分析:两个引用student1和student2指向不同的两个对象,两个引用student1和student2中的两个teacher引用指向的是两个对象,但对teacher对象的修改只能影响student1对象,所以说是深拷贝。
【Java基础复习3】- final、static等关键字

注意:使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

上一篇:读取和导出数据


下一篇:static final 还是 final static