《疯狂Java讲义》读书笔记4

文章详细讲解了Java中的初始化块、构造器的工作原理,静态和非静态初始化块的执行顺序。此外,还涉及到了包装类的自动装箱和拆箱,以及如何在字符串和基本类型之间转换。同时,文章阐述了equals()和hashCode()方法的使用,以及如何创建不可变类。
摘要由CSDN通过智能技术生成

一个 acm 的题解: 

http://t.csdn.cn/o2wI7

 初始化块

是构造器的补充,在构造器之前执行。

是一段固定的代码,不接受任何参数。

构造器其实是一个假象,编译Java类后,初始化块会消失,当中的代码被还原到构造器中,且位于构造器前面。

静态初始化块

用 static 修饰,属于类的静态成员,不能访问非静态成员。

package com.ittht.day06;
class Root{
    static {
        System.out.println("Root中的静态初始化块");
    }
    {
        System.out.println("Root中的普通初始化块");
    }
    public Root(){
        System.out.println("Root中的无参构造器");
    }
}
class Mid extends  Root{
    static {
        System.out.println("Mid中的静态初始化块");
    }
    {
        System.out.println("Mid中的普通初始化块");
    }
    public Mid(){
        System.out.println("MId中的无参构造器");
    }
    public Mid(String ch){
        this();
        System.out.println("Mid中的带参构造器,其中的参数:"+ch);
    }
}
class Leaf extends Mid{
    static {
        System.out.println("Leaf中的静态初始化块");
    }
    {
        System.out.println("Leaf中的普通初始化块");
    }
    public Leaf(){
        super("java");//使用super显式调用父类的构造器
        System.out.println("执行Leaf中的构造器");
    }
}
public class Test {
    public static void main(String[] args) {
        new Leaf();
    }
}

输出结果为:

Root中的静态初始化块
Mid中的静态初始化块
Leaf中的静态初始化块
Root中的普通初始化块
Root中的无参构造器
Mid中的普通初始化块
MId中的无参构造器
Mid中的带参构造器,其中的参数:java
Leaf中的普通初始化块
执行Leaf中的构造器

编译时,要执行其父类的静态初始化块,一直上溯至  java.lang.Object 类(如果有静态初始化块),执行其的静态初始化块,最后才执行该类的静态初始化块。

然后对于普通初始化块,是在构造器之前执行,也是上溯至 java.lang.Object 类(如果有普通初始化块或者构造器),先执行它的初始化块,然后执行它的构造器……最后才执行该类的普通初始化块和构造器。

Java 系统加载并初始化某个类时,总是保证该类的所有父类(包括直接父类和间接父类)全部加载并初始化。

 声明和静态初始化块

package com.ittht.day06;

public class StaticInitTest {
    static {
        a=9;//语句1
    }
    static int a=10;//语句2

    public static void main(String[] args) {
        System.out.println(StaticInitTest.a);
    }
}

 其中语句 1 代表的是静态初始化块,语句 2 代表的是声明,当前代码输出的为 10。

但是交换语句 1 和语句 2 的位置,输出的值会变化。

这是因为静态初始化块和声明变量指定的初始值都是该类的初始化代码,它们的执行顺序与源程序中的顺序有关。

 当 JVM 第一次主动执行某类时,系统会在类准备阶段为该类的所有静态成员变量分配内存;在初始化阶段则负责初始化这些静态成员变量,初始化静态成员变量就是执行类初始化代码或声明类变量时指定的初始值,他们的执行顺序与源代码中的排序相同。

包装类

 

包装类是将基本数据类型首字母大写,只有 int 和 char 特殊一些,int 对应的是 Integer,char 对应的是 Character。

自动装箱:把一个基本数据类型变量直接赋给对应的包装类变量,或者直接赋值给 Object 变量。(Object 类是所有类的父类,子类对象可以直接赋给父类变量)

自动拆箱:允许直接把包装类对象赋给一个对应的基本数据类型变量。

package com.ittht.day06;

public class AutoBoxingUnboxing {
    public static void main(String[] args) {
        //把一个基本数据类型赋给Integer对象
        Integer in=5;
        //直接把一个boolean类型变量赋给Object类型的变量
        Object ob=true;
        //把一个Integer变量赋给int类型的变量
        int it=in;
        if(ob instanceof Boolean)//判断转换能不能实现
        {
            //把Object对象强制转换为Boolean类型,再赋给boolean(有先后)
            boolean oc=(boolean) ob;
            System.out.println(ob);
        }
    }
}

对于包装类的作用还不是很清楚,所以去查了一下:

  • 集合只能存放引用数据类型,不能存放基本数据类型,如 add(Object o)
  • 基本数据类型和包装类可以互相转换(自动装箱拆箱)
  • 包装类的 parse 方法可以实现基本数据类型+string 类型的相互转换
  • 函数需要传递进去的参数为 Object 类型,传入基本数据类型不行

把字符串类型转换为基本类型的值:

  • 利用包装类提供的 parseXxx(String s)静态方法(除 Character 之外的所有包装类都提供了该方法)
  • 利用包装类提供的 valueOf(String s)静态方法

将基本数据类型转换为字符串:

  • String.valueOf(primitive) 转换
  • 在基本数据类型后+“”,会自动转换为字符串
package com.ittht.day06;

public class Primitive {
    public static void main(String[] args) {
        //把字符串类型转换成基本数据类型
        String str1="123";
        int it1=Integer.parseInt(str1);
        int it2=Integer.valueOf(str1);
        System.out.println(it1);
        System.out.println(it2);
        String str2="5.69";
        float ft1=Float.parseFloat(str2);
        float ft2=Float.valueOf(str2);
        System.out.println(ft1);
        System.out.println(ft2);
        //把基本数据类型转换成字符串类型
        String ftStr=String.valueOf(2.25f);
        String dtStr=String.valueOf(8.33);
        System.out.println(ftStr);
        System.out.println(dtStr);
        //把一个boolean类型的变量转换为String变量
        boolean bool=true;
        String st=String.valueOf(bool);
        System.out.println(st);

        //直接在要转化为字符串类型的基本数据类型后+""
        System.out.println(bool+"");//输出的是字符串类型
    }
}

两个包装类进行比较比较复杂,因为包装类实际上是一个引用类型,只有两个包装类指向同一个对象的时候,才会返回 true。

 处理对象

打印对象会输出什么样的结果?它输出的实际上是对象的 toString 的返回值,“包名.类名+@+hashCode“”(八位十六进制的数字)。

所以下面这两行代码的效果一样:

System.out.println(p);
System.out.println(p.toString());

 toString() 方法是 Object 类里面的一个实例方法,所以所有 Java 类都有 toString 方法。

重写 toString() 方法:

package com.ittht.day06;
class Apple{
    private String color;
    private double weight;
    public Apple(){}
    public Apple(String color,double weight){
        this.color=color;
        this.weight=weight;
    }
    //color和weight的getter和setter方法
    public String getColor(){
        return color;
    }
    public void setColor(String color){
        this.color=color;
    }
    public double getWeight(){
        return weight;
    }
    public void setWeight(){
        this.weight=weight;
    }
    public String toString(){
        return "color:"+color+",weight:"+weight+"g";
    }
}
public class ToStringTest {
    public static void main(String[] args) {
        Apple a=new Apple("红色",5.55);
        System.out.println(a);
    }
}

 == 和 equals 方法

测试两个变量是否相等有两种方式:

  • == 运算符

对于两个变量都是基本数据类型(不要求数据严格相同),且值相等,就会返回 true;

对于两个引用类型变量,只有它们指向同一个对象时,才返回 true(不可以用于比较类型上没有父子关系的两个对象

  • equals 方法

这个方法可以根据需要进行重写,是 Object 类提供的方法,所有引用变量都可以调用该方法来判断是否与其他变量相等,也是要求两个变量指向同一对象才返回 true。


当使用 new 关键字创建对象。

例如 new String(“hello”);        

JVM 会先试用常量池来管理“hello”直接量,在调用 String 类的构造器来创建一个新的 String 对象,新创建的对象被保存在堆内存中。(一共产生了两个字符串对象)

所以用 == 运算符判断两个 new 关键字创建的相同的字符串对象,也会输出 false。


package com.ittht.day06;

public class EqualTest {
    public static void main(String[] args) {
        int it=65;
        float fl=65.0f;
        System.out.println(it==fl);//将输出true
        char ch='a';
        System.out.println(ch==it);//将输出true(a的ASCII码值为65)
        
        String p1=new String("hello");
        String p2=new String("hello");
        System.out.println(p1==p2);//输出false
        System.out.println(p1.equals(p2));//输出true
        
        //==运算符不能用于比较两个没有继承关系的变量
    }
}

String 已经重新了 Object 的 equals 方法,String 的 equals()方法的判断条件是:两个字符串包含的字符序列是否相同。

重写 equals 方法(根据需求重写)

package com.ittht.day06;
class Person2{
    private String str;
    private int num;
    public Person2(){}
    public Person2(String str,int num){
        this.str=str;
        this.num=num;
    }
    public void setStr(String str){
        this.str=str;
    }
    public String getStr(){
        return str;
    }
    public void setNum(int num){
        this.num=num;
    }
    public int getNum(){
        return num;
    }
    //重写判断相等的条件是对象不为空,两者属于同一个类,对象中的str相等
    public boolean equals(Object o){//注意重写equals方法的返回值是boolean类型的
        if(this==o)
            return true;
        if(o!=null&&o.getClass()==Person2.class)
        {
            if(((Person2) o).str.equals(this.getStr()))
                return true;
        }
        return false;

    }
}
public class OverrideEqualsRight {
    public static void main(String[] args) {
        Person2 p1=new Person2("孙悟空",123);
        Person2 p2=new Person2("孙行者",123);
        Person2 p3=new Person2("孙悟空",456);
        System.out.println(p1.equals(p2));//输出false
        System.out.println(p1.equals(p3));//输出true
    }
}

 

类成员

Java 中只能包含五种成员:变量,方法,构造器,初始化块,内部类(包括接口和枚举)。

其中能被 static 修饰的成员:变量,方法,初始化块,内部类

类成员是由 static 修饰的,属于整个类,而不属于单个对象。

类变量可以通过来访问,也可以通过对象来访问。

但是通过类的对象来访问类变量时,实际上不能访问该变量拥有的变量,因为在创建该类的对象时,系统不会再为类变量分配内存,所以对象不拥有对应类的类变量,当通过对象类访问类变量时,类变量必须通过类来访问。

当用对象来访问类成员时,实际上还是为它该类来访问类成员,所以即使某个实例为 null,他也可以访问它所属类的类成员。

package com.ittht.day06;

public class NullAccessStatic {
    private static void test5(){
        System.out.println("static修饰的类方法");
    }

    public static void main(String[] args) {
        NullAccessStatic n=null;
        n.test5();
    }
}

单例类

一些情况下,不用写自由创建该类的对象,只允许为该类创建一个对象,为了避免其他类重复创建该类的实例,应该把该类的构造器用 private 修饰,从而把该类所有的构造器隐藏。

还需要提供一个 public 方法作为该类的访问点,用于创建该类的对象,而且要用 static 修饰(调用它之前没有创建过对象,所以调用它的只能是类)

而且该类还需要缓存已经创建的对象,否则不能知道之前是否创建过对象,所以就不能保证是不是创建的只有一个对象。可以用一个静态成员变量来保存曾经创建的对象,因为该成员变量需要上面的静态方法访问。

package com.ittht.day06;
class Singleton{
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance==null)
            return new Singleton();
        return instance;
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();
        //如果两者返回的变量不一致,用“==”应该输出false,但结果输出的是true,说明值创建了一个对象
        System.out.println(s1==s2);
    }
}

 final修饰符

用于修饰类、变量和方法,它修饰的类、变量和方法不可以改变。final 获得初始值后,不能被重新赋值。

final 成员变量

必须由程序员显式地指定初始值。

  • 类变量:在静态初始化块中指定初始值或者声明该变量时指定初始值(二选一)
  • 实例变量:必须在非静态初始化块、声明该实例变量或者构造器中指定初始值(三选一)
package com.ittht.day06;

public class FinalVariableTest {
    //定义成员变量时指定初始值,合法
    final int a=6;
    final String str;
    final int c;
    final static double d;
    {
        //在初始化块中指定初始值,合法
        str="Hello";
        //a=9;/*不能为a重复赋值*/
    }
    static{
        //在静态初始化块中为类变量指定初始值,合法
        d=6.5;
    }
    public FinalVariableTest(){
        //在构造器中队str指定了初始值,合法
        c=5;
    }
    public void changFinal(){
        //不能在普通方法中给final修饰的变量赋值
        //d=1.2;
    }

    public static void main(String[] args) {
        FinalVariableTest f=new FinalVariableTest();
        System.out.println(f.a);
        System.out.println(f.c);
        System.out.println(f.d);
    }
}

 如果打算在构造器、初始化块中队 final 成员变量进行初始化,就不要再初始化之前直接访问 final 成员变量;但 Java 允许通过方法来访问暂时未初始化的 final 成员变量,此时系统将 final 成员变量默认初始为 0(或‘\u000’或 false 或 null)

package com.ittht.day06;

public class FinalError {
    final int age;
    {
        //age未初始化,所以不能被访问
        //System.out.println(age);
        print();//但是可以用方法来访问暂时未被初始化的age
        age=6;
    }
    public void print(){
        System.out.println(age);
    }

    public static void main(String[] args) {
        new FinalError();
    }
}

 final 修饰引用类型变量

当使用 final 修饰基本类型变量时,不能对基本类型变量重新赋值。

但是对于引用类型变量,它保存的只是一个引用,final 只能保证这个引用类型变量所引用的地址不会被改变(相当于一直引用同一个对象),其中这个对象完全可以发生改变。

接下来的代码,将说明 final 修饰数组和 Person 对象情况。

package com.ittht.day06;

import java.util.Arrays;

class Person3{
    private int age;
    public Person3(){}
    public Person3(int age){
        this.age=age;
    }
    public void setAge(int age){
        this.age=age;
    }
    public int getAge(){
        return age;
    }

}
public class FinalReference {
    public static void main(String[] args) {
        final int []arr={8,10,3,4};
        System.out.println(Arrays.toString(arr));

        //对数组中元素赋值,是合法的
        arr[1]=15;

        Arrays.sort(arr);//对数组中的值进行排序是合法的
        System.out.println(Arrays.toString(arr));

        //arr=null;/*重新赋值,不合法*/

        //改变Person对象的age实例变量,合法
        final Person3 p=new Person3(25);
        p.setAge(55);

        //p=null;/*重新对p赋值,非法*/
    }
}

可执行"宏替换"的 fianl 变量

 需要满足三个条件:

  • 使用 final 修饰
  • 定义该 final 变量时指点了初始值
  • 该初始值可以在编译时就被确定下来

编译器会把所有用到该变量的地方,直接替换成该变量的值。

 Java 会使用常量池来管理曾经用过的字符串直接量,例如执行 String a=“java”;语句的时候,常量池中会缓存一个字符串 “java”,如果再次执行 String b=“java” 时,系统会让 b 直接指向常量池中的 “java” 字符串,所以 a==b 会返回 true

 对于下面这段代码,s1 是一个普通的字符串直接量“疯狂java”,s2 是有两个字符串直接量进行谅解运算,由于编译器可以子啊编译阶段就确定 s2 的值,所以 s1==s2 返回 true。

但是对于 s3 ,它的值有 str1 和 str2 连接二次,由于 str1 和 str2 是普通变量,编译器不会执行“宏替换”,在编译时无法确定 s3 的值,就不能让 s3 指向字符串池中缓存的“疯狂java”,所以 s1==s3 输出 false。

当 str1 和 str2 用 final 修饰时,就可以让 s3 指向字符串池中缓存的“疯狂java”,就可以让 s1==s3 输出 true。

package com.ittht.day06;

public class StringJoinTest {
    public static void main(String[] args) {
        String s1="疯狂java";
        String s2="疯狂"+"java";
        String str1="疯狂";
        String str2="java";
        String s3=str1+str2;

        System.out.println(s1==s2);//输出true
        System.out.println(s1==s3);//输出false
    }
}

final 方法

被修饰的方法不能被重写

注意下面两段代码的区别:

package com.ittht.day06;

public class FinalMethodTest {
    public final void test(){}
}
class Sub extends FinalMethodTest{
    public void text(){}
}
package com.ittht.day06;

public class PrivateFinalMethodTest {
    private final void test(){}
}
class Sub extends PrivateFinalMethodTest{
    public void text(){}
}

 两段代码长得很像,但是第一个是不合法的,第二个合法。它们的主要区别就是第一个父类中的 test 方法是用 public 修饰的,而第二个父类中的 text 方法是就用 private 修饰的。

被 final 修饰的方法不能被重写,第一个就明显重写了。

但是第二个,子类是不能访问父类中用 private 修饰的方法的,所以定义的 test 方法不算重写。

final 类

被修饰的类不能有子类,例如 java.lang.Math 类是一个 final 类,不可以有子类。

当子类继承父类时,可以访问到父类内部数据,而且可以重新父类的方法,就会导致很多不确定因素,此时可以用 final 修饰这个类。

 不可变类

创建该类的实例后,该实例的实例变量不可改变。

如果需要定义不可变类,需要遵守下面的规则:

  • 使用 private 和 final 修饰符来修饰该类的成员变量
  • 提供带参数的构造器,用于根据关键成员变量来初始化类里的成员变量
  • 仅为该类提供 getter 方法,不提供 setter 方法,因为普通方法中不能修改 final 的值
  • 如果有必要,重新 Object 类中的 hashCode() 方法和 equals 方法,重新需要保证两个用 equals() 方法判断外相等的对象,他们的 hashCode() 也相等。

 测试 java.lang.String 类的 equals() 方法和 hashCode() 方法:

package com.ittht.day06;

public class ImmutableStringTest {
    public static void main(String[] args) {
        String str1=new String("Hello");//输出false
        String str2=new String("Hello");//输出true
        System.out.println(str1==str2);
        System.out.println(str1.equals(str2));
        //hashCode的值是根据字符串的序列计算来的,所以这两个输出的值相等
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());
    }
}

 程序创建 Address 对象后,不能修改 Address 对象的 detail 和 postCode 实例变量。

package com.ittht.day06;

public class Address {
    private final String detail;
    private final String postCode;
    public Address(){
        this.detail="";
        this.postCode="";
    }
    public String getDetail(){
        return this.detail;
    }
    public String getPostCode(){
        return this.postCode;
    }
    public boolean equals(Object ob){
        if(this== ob)
            return true;
        if(ob!=null&&ob.getClass()==Address.class){
            if(this.postCode.equals(((Address) ob).postCode)&&this.detail.equals(((Address) ob).detail))
                return true;
        }
        return false;
    }
    public int hashCode(){
        return detail.hashCode()+postCode.hashCode()*31;
    }
    public static void main(String[] args) {
        Address a=new Address();
        Address a1=new Address();
        Object a3="";
        System.out.println(a.equals(a1));
        System.out.println(a.equals(a3));
    }
}

下面这段代码试图定义一个不可变的 Person 类,但是 Person 类包含一个引用类型的成员变量,求这个引用类是可变类,所以导致 Person 类也变成了可变类。

总结:

学得有些慢,而且感觉接口有些不懂,但是因为学习进度太慢的原因,还是先赶进度,做项目的时候用到的地方,再仔细研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明里灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值