第6章 面向对象下

基本数据类型的包装类

Java 是面向对象的编程语言,但它也包含了8种基本数据类型,这8个基本类型不支持面向对象的编程机制,基本数据类型的数据也不具备“对象” 的特性:没有属性方法可以被调用。为了解决8个基本数据类型不能当成 Object 类型变量使用的问题,Java 提供了包装类的概念,为8个基本类型分别定义了响应的引用类型,并称之为基本数据类型的包装类。

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean
public class Primitive2Wrapper {
    public static void main(String[] args) {
        boolean b1 = true;
        //通过构造器将b1基本类型变量包装成包装类对象
        Boolean b1Obj = new Boolean(b1);
        int it = 5;
        Integer itObj = new Integer(it);
        //把一个字符串转换成 Float 对象
        Float f1 = new Float("4.5");
        //把一个字符串转化为 Boolean 对象
        Boolean bObj = new Boolean("false");
        //异常
        //Long lObj = new Long("ddd");
    }
}

如果希望取出包装类对象中包装的基本类型变量,可以使用包装类提供的xxxValue() 方法

//取出 Boolean 对象中的 boolean 变量
boolean bb = bObj.booleanValue(); 

在这里插入图片描述

JDK1.5 提供了自动装箱和自动拆箱机制,所谓自动装箱就是可以把一个基本类型变量直接赋给对应的包装类变量,或者赋给 Object 变量;自动拆箱与之相反,允许直接把包装类对象直接赋给一个对应的基本类型变量。

public class AutoBoxingUnboxing {
    public static void main(String[] args) {
        //直接把一个基本类型变量赋给 Integer
        Integer intObj = 5;
        Object boolObj = true;
        //直接把一个 Integer 对象赋给 int 类型变量
        int it = intObj;
        if(boolObj instanceof Boolean){
            boolean b = (Boolean)boolObj;
            System.out.println(b);
        }
    }
}

除此之外,包装类还可以实现基本类型变量和字符串之间的转换,出了 Character 之外的所有包装类都提供了一个 parseXxx(String s) 静态方法,用于将一个特定字符串装换成基本类型变量;除此之外,在 String 类里也提供了多个重载
valueOf() 方法,用于将基本类型变量转换为字符串

public class Primitive2String {
    public static void main(String[] args) {
        String intStr = "123";
        //把一个特定的字符串转换为 int 变量
        int it = Integer.parseInt(intStr);
        String floatStr = "4.56";
        float ft = Float.parseFloat(floatStr);
        //把一个 float 变量转换成 String 变量
        String ftStr = String.valueOf(2.345f);
        //把一个 double 变量转换成 String 变量
        String dbStr = String.valueOf(3.456);
        //把一个 boolean 变量转换成 String 变量
        String boolStr = String.valueOf(true);
    }
}

基本类型变量和字符串之间的转换关系
在这里插入图片描述

处理对象

Java 对象都是 Object 类的实例, 都可以调用该类中定义的方法,这些方法是处理 Java 对象的通用方法。

打印对象和 toString 方法

class Person11{
    private String name;
    public Person11(String name){
        this.name = name;
    }
    public void info(){
        System.out.println("名字为:" + name);
    }
}

public class PrintObject {
    public static void main(String[] args) {
        Person11 p = new Person11("大圣");
        //打印 p 所引用的 Person11 对象
        //这里实际上输出的是Person11对象的toString()方法返回值
        System.out.println(p); 
    }
}

toString() 方法是 Object 类里的一个实例方法,所有 Java 类都是 Object 的子类,因此所有 Java 对象都具有 toString 方法。不仅如此,所有 Java 对象都可以和字符串进行连接运算,当 Java 对象和字符串进行连接运算时,系统自动调用 Java 对象的toString() 方法的返回值和字符串进行连接运算。
toString() 方法是一个非常特殊的方法,他是一个“自我描述”方法,该方法通常用于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的自我描述信息。
toString() 方法总是返回该对象实现类的类名+@+hashCode值,因此,如果想要实现自我描述功能,必须重写 Object 类的 toString() 方法。

class Apple11{
    private String color;
    private double weight;
    public Apple11(String color, double weight){
        this.color = color;
        this.weight = weight;
    }
    public String toSring(){
        return "一个苹果的颜色是:" + color + "重量是:" + weight;
    }
}

public class TestToString {
    public static void main(String[] args) {
        Apple11 a = new Apple11();
        System.out.println(a);
    }
}

== 和 equals 比较运算符

Java 程序中测试两个变量是否相等有量中方式,一种是利用 == ,另一种是利用 equals 方法。
当使用 == 来判断两个变量是否相等时,如果两个变量时基本类型变量,且时数值型,则只要两个变量相等,则使用 == 判断就会返回 true。
对于两个引用类型的变量,必须它们指向同一个对象时,== 判断才会返回 true。
equals() 方法也是 Object 类提供的一种实例方法,因此,所有引用变量都可以调用该方法来判单是否与其他引用变量相等。但这个方法判断两个对象相等的标准与==没有区别,同样要求两个引用变量指向同一个对象才会返回 true。但是可以通过重写 equals 方法来实现其它的功能。

equals() 方法的重写

重写 equals() 方法使得Person5对象和任何对象都相等:

class Person5{
    public boolean equals(Object obj){
        //不加判断,总是返回true,即Person对象与任何对象都相等
        return true;
    }
}

class Dog1{}

public class OverrideEqualsError {
    public static void main(String[] args) {
        Person5 person5 = new Person5();
        System.out.println("Person5对象是否equals Dog1对象?" + (person5.equals(new Dog1())));
        System.out.println("Person5对象是否equals String对象?" + (person5.equals(new String("hello"))));
    }
}

类成员

static 修饰的成员就是类成员,static 修饰的类成员属于整个类,不属于单个实例。

理解类成员

在 Java 类里,只能包含属性、方法、构造器、初始化块、内部类和枚举类等六中成员。其中 static 可以修饰属性、方法、初始化块、内部类和枚举类。
类属性既可以通过类来访问,也可以通过对象来访问。但通过类的对象来访问类属性时,实际上并不是访问该对象所具有的属性,因为当系统创建该类的对象时,系统不会再为类属性分配内存,也不会再次为类属性进行初始化,也就是说对象根本不包括对应类的类属性。可以这样理解,通过对象访问类属性时,系统会在底层转换为通过该类来访问类属性。
对于 static 关键字而言,有一条非常重要的规则:**类成员不能访问实例成员。**因为类成员是属于类的,类成员的作用域比实例成员的作用域更大。完全可能出现类成员已经初始化完成,但实例成员还不曾初始化,如果允许类成员来访问实例成员将会引起大量的错误。

单例类

如果一个类始终只能创建一个实例,则这个类被称为单例类。在一些特殊的场景下,要求不允许自由地创建该类的实例,而是只允许为该类创建一个对象。为了避免其它类自由地创建该类的实例,我们把该类的构造器使用 private 修饰,从而把该类的构造器隐藏起来。
根据良好的封装的原则:一旦把该类的构造器隐藏起来,则需要提供一个 public 方法作为该类的访问点,用于创建该类的对象,且该类必须使用 static 修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。

class Singleton {
    //使用一个变量来缓存曾经创建过的实例
    private static Singleton instance;
    //将构造器使用 private 修饰,隐藏构造器
    private Singleton(){}
    /*提供一个静态方法用于返回Singleton实例
     *该方法可以加入自定义控制,保证只产生一个Singleton对象
     * */
    public static Singleton getInstance(){
        //如果instance为null,说明还不曾创建Singleton对象
        //如果instance不为null,说明已经创建了Singleton对象,将不会执行该方法
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

public class TestSingleton{
    public static void main(String[] args) {
        //创建Singleton对象不能通过构造器,只能通过getInstance()方法
        //Singleton s1 = new Singleton();
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

final 修饰符

final 关键字可用于修饰类、方法和变量,它用于表示修饰的类、方法和变量不可改变。

final 变量

final 修饰变量时,表示该变量一旦获得了初始值后就不可改变。final 既可以修饰成员变量,也可以修饰局部变量、形参。因为 final 变量获得初始值后不能被重新赋值,因此 final 修饰局部变量和成员变量时有一定的不同。

  • final 修饰成员变量
public class TestFinalVariable {
    //定义成员变量时指定默认值
    final int a = 6;
    //下面变量将在构造器或初始化块中分配初始值
    final String str;
    final int c;
    final static double d;
    //既没有默认值,又没有在初始化块、构造器中指定初始值,下面定义的char属性是不合法的
    //final char ch;
    //初始化块,可对没有指定默认值的实例属性指定初始值
    {
        str = "Hello";
        //a已经有默认值,不能再为a赋值
        //a = 9;
    }

    //静态初始化块,可对没有指定默认值的类属性赋初值
    static
    {
        d = 5.6;
    }

    //构造器,可对没有指定默认值、并且没有在初始化块中指定初始值的实例属性赋初值
    public TestFinalVariable(){
        c = 5;
    }

    public void changeFinal(){
        //d = 1.2;
    }
}

与普通成员变量不同的是,final 成员变量必须由程序员显示地初始化,系统不会对 final 成员进行隐式初始化。

  • final 修饰局部变量
    系统不会对局部变量进行初始化,局部变量必须由程序员进行显式初始化。因此,使用 final 修饰局部变量时,既可以在定义时指定默认值,也可以不指定。如果 final 修饰的局部变量在定义时没有指定默认值,则可以在后边的代码中对该 final 变量赋初值,但是只能一次。
public class TestFinalLocalVariable {
    public void test(final int a){
        //a = 5;
    }

    public static void main(String[] args) {
        final String str = "Hello";
        //str = "Java";
        final double d;
        d = 5.6;
        //d = 3.4;
    }
}

  • final 修饰基本类型和引用类型的区别
    当 final 修饰基本类型变量时,不能对基本类型变量重新赋值,因此,基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final 只保证这个引用的引用地址不会改变,即一直引用同一个对象,但这个对象完全可以改变。
class Person5{

    private int age;

    public int getAge() {
        return age;
    }

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

    public Person5(int age){
        this.age = age;
    }
}

public class TestFinalReference {
    public static void main(String[] args) {
        //final修饰数组变量
        final int[] iArr = {5, 6, 12, 9};
        System.out.println(Arrays.toString(iArr));
        //对数组进行排序
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        //对数组元素进行赋值,合法
        iArr[2] = 4;
        //对iArr进行赋值,非法
        //iArr = null;

        final Person5 p = new Person5(45);
        p.setAge(23);
        //改变p的地址,非法
        //p = null;
    }
}

从上面的程序中可以看出,使用 final 修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。

final 方法

final 修饰的方法不可以被重写。如果出于某些原因,不希望子类重写父类的某个方法,可以用 final 修饰该方法。

final 类

final 修饰的类不可以有子类。当子类继承父类时,就可以访问到父类内部的数据,并通过重写父类的方法来改变父类方法的实现细节,这可能导致一些不安全的因素。为了保证某个类不可以被继承,则可以使用 final 修饰这个类。

不可变类

不可变类的意思是创建该类的实例后,该实例的属性是不可改变的。
如果需要创建自定义的不可变类,可遵循如下规则:

  • 使用 private 和 final 修饰符来修饰类的属性
  • 提供带参构造器,用于根据传入参数来初始化类里的参数
  • 仅为该类的属性提供 getter 方法,不提供 setter 方法,因为普通方法无法修改 final 修饰的属性
  • 如果有必要,重写 Object 中的hashCode 和 equals 方法
public class Address {
    private final String detail;

    public String getDetail() {
        return detail;
    }

    public String getPostCode() {
        return postCode;
    }

    private final String postCode;
    public  Address(){
        this.detail = "";
        this.postCode = "";
    }
    public Address(String detail, String postCode){
        this.detail = detail;
        this.postCode = postCode;
    }

    //重写 equals 方法判断两个对象是否相等
    public boolean equals(Object obj){
        if(obj instanceof Address){
            Address ad = (Address) obj;
            if(this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode()))
                return true;
        }
        return false;
    }

    public int hashCode(){
        return detail.hashCode() + postCode.hashCode();
    }
}

对于上面的Address类,我们创建了它的对象后同样无法修改Address对象的 detail 和 postCode 属性。
与不可变类对应的是可变类,我们大部分时候创建的类都是可变类,特别是 JavaBean,我们总是为其属性提供 getter 和 setter 方法。

缓存实例的不可变类

抽象类

在这里插入图片描述

抽象方法和抽象类

抽象方法和抽象类必须使用 abstract 修饰符来定义,有抽象方法的类只能被定义为抽象类,抽象类里可以没有抽象方法。
抽象方法和抽象类的规则如下:

  • 抽象类和抽象方法必须使用 abstract 修饰符来修饰,抽象方法不能有方法体
  • 抽象类不能被实例化,无法使用 new 关键字来调用抽象类的构造器创建抽象类的实例
  • 抽象类的构造器不能用于创建实例,主要用于被其子类调用
  • 含抽象方法的类(包括直接定义了一个抽象方法;继承一个抽象父类,但没有完全实现父类包含的抽象方法;以及实现了一个接口,但没有完全实现接口的抽象方法三种情况)只能被定义成抽象类。
    当 abstract 修饰某个类时,表明这个类只能被继承,当 abstract 修饰方法时,表明这个方法只必须由子类提供实现(重写)。而 final 修饰的类不能被继承,final 修饰的方法不能被重写。因此,final 和 abstract 永远不能同时使用。
    当使用 static 来修饰一个方法时,表明这个方法属于当前类,即该方法可以通过类来调用,如果该方法被定义为抽象方法,则将导致通过该类来调用该方法时出现错误,因此static 和 abstract 不能同时修饰某个方法。
    abstract 关键字修饰的方法必须被其子类重写才有意义,否则这个方法永远不会有方法体,因此 abstract 方法不能定义为 private 访问权限,即 private 和 abstract 不能同时使用。

抽象类的作用

抽象类不能创建实例,只能当成父类来被继承。抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。

更彻底的抽象:接口

抽象类是从多个类中抽象出来的模板,如果将这种类进行得更彻底,则可以提取出一种更加特殊的“抽象类” —— 接口(interface),接口里不能包含普通方法,接口里所有的方法都是抽象方法。

接口的概念

类是一种具体实现体,而接口定义了一种规范,接口定义某一批类所要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节。它之规定这些类里必须提供某些方法。可见,接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学。

接口的定义

定义接口使用 interface 关键字。一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
由于接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。接口里可以包含属性(只能是常量)、方法(只能是抽象实例方法)、内部类(包括内部接口)和枚举类定义。
对于接口里定义的常量属性而言,它们是接口相关的,而且它们只能是常量,因此系统会自动为这些属性增加 static 和 final 两个修饰符,**接口里的属性总是使用public、static 和 final 修饰符来修饰。**接口里没有构造器和初始化块,因此接口只能在定义时指定默认值。

// 系统自动为接口定义属性增加 public  static  final 修饰符
int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;

对于接口里定义的方法而言,它们只能是抽象方法,因此系统自动为其增加 abstract 修饰符:由于接口里的方法全部时抽象方法,因此接口里不允许定义静态方法(原因在上面的抽象方法里有说明),即不可以使用 static 来修饰接口里的方法。不管定义接口里的方法是否使用 public abstract 修饰,接口里的方法总是使用 public abstract 来修饰。

接口的继承

接口的继承和类的继承不一样,接口完全支持多继承,即一个接口可以有多个完全父接口。和类继承相似,子接口扩展某个父接口,将会获得夫接口里定义的所有抽象方法、常量属性、内部类和枚举类定义。

使用接口

接口的主要用途就是被实现类实现。一个类可以实现一个或多个接口,继承使用 extends 关键字,而实现则使用 implements 关键字。一个类实现一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法,否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。

实现接口方法时,必须使用 public 访问权限修饰符,因为接口里定义的方法都是 public 的,而子类重写父类方法时访问权限只能更大或者相等。

猜你喜欢

转载自blog.csdn.net/qq_32682177/article/details/83475615