资讯专栏INFORMATION COLUMN

Java 面向对象(下)

nanchen2251 / 2908人阅读

摘要:换句话说,一共产生了两个字符串对象。类成员属于整个类,而不属于单个对象。类变量生存范围几乎等同于该类的生存范围。当通过对象来访问类变量时,系统会在底层转换为通过该类来访问类变量。

Java8增强的包装类


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

包装类实现基本类型变量和字符串之间的转换。把字符串类型的值转换为基本类型的值有两种方式:

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

利用包装类提供的Xxx(String s)构造器。

String intStr = "123";
int it1 = Integer.parseInt(inStr);
int it2 = new Integer(intStr);

String类提供了多个重载valueOf()方法,用于将基本类型变量转换成字符串:

String ftStr = String.valueOf(2.345f);
String boolStr = String.valueOf(true);

将基本类型转换成字符串的更简单方法:将基本类型变量和""进行连接运算,系统会自动把基本类型变量转换成字符串:

//intStr的值为"5"
String intStr = 5 + "";

包装类型的变量是引用数据类型,但包装类的实例可以与数据类型的值进行比较,这种比较是直接取出包装类实例所包装的数值来进行比较的。

静态compare(xxx val1, xxx val2)方法,比较两个基本类型值的大小,输出1、0、-1。
两个boolean类型值进行比较时,true>false。
Java7为Character包装类增加了大量的工具方法来对一个字符进行判断,Java8再次增强了这些包装类的功能,其中一个重要的增强就是支持无符号算术运算。

处理对象 打印对象和toString方法

toString()方法是Object类里的一个实例方法,所有的Java类都是Object类的子类,因此所有的Java对象都具有toString()方法。所有的Java对象都可以和字符串进行连接运算,当Java对象和字符串进行连接运算时,系统自动调用Java对象toString()方法的返回值和字符串进行连接运算。

toString()方法是一个非常特殊的方法,它是一个“自我描述”方法,该方法通常用于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。

Object类提供的toString()方法总是返回该对象实现类的“类名+@+hashCode”值。

==和equals方法

当使用==来判断两个变量是否相等时,如果两个变量是基本类型变量,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就将返回true。
但对于两个引用类型变量,只有它们指向同一个对象时,==判断才会返回true。==不可用于比较类型上没有父子关系的两个对象。

public class EqualTest 
{
    public static void main(String[] args) 
    {
        int it = 65;
        float f1 = 65.0f;
        //将输出true
        System.out.println("65和65.0f是否相等?" + (it == f1));
        char ch= "A";
        //将输出true
        System.out.println("65和"A"是否相等?" + (it == ch));
        String str1 = new String("hello");
        String str2 = new String("hello");
        //将输出false
        System.out.println("str1和str2是否相等?" + (str1 == str2));
        //将输出true
        System.out.println("str1是否equals str2?" + (str1.equals(str2)));
        //由于java.lang.String与EqualTest类没有继承关系
        //所以下面语句导致编译错误
        System.out.println("hello" == new EqualTest());
    }
}

当Java程序直接使用形如"hello"的字符串直接量(包括可以在编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串;当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String("hello")一共产生了两个字符串对象。

常量池(constant pool)专门用于管理在编译时被确定并被保存在已编译的.class文件中的一些数据。包括了关于类、方法、接口中的常量,还包括字符串常量。

下面程序示范了JVM使用常量池管理字符串直接量的情形

public class StringCompareTest 
{
    public static void main(String[] args) 
    {
        //s1直接引用常量池的"克利夫兰骑士"
        String s1 = "克利夫兰骑士";
        String s2 = "克利夫兰";
        String s3 = "骑士";
        //s4后面的字符串值可以在编译时就确定下来
        //s4直接引用常量池中的"克利夫兰骑士"
        String s4 = "克利" + "夫兰" + "骑士";
        //s5后面的字符串值可以在编译时就确定下来
        //s5直接引用常量池中的"克利夫兰骑士"
        String s5 = "克利夫兰" + "骑士";
        //s6后面的字符串值不能在编译时就确定下来
        //不能引用常量池中的字符串
        String s6 = s2 +s3;
        //使用new调用构造器将会创建一个新的String对象
        //s7引用堆内存中新创建的String对象
        String s7 = new String("克利夫兰骑士");
        System.out.println(s1 == s4);        //输出true
        System.out.println(s1 == s5);        //输出true
        System.out.println(s1 == s6);        //输出false
        System.out.println(s1 == s7);        //输出false
    }
}

JVM常量池保证相同的字符串直接量只有一个,不会产生多个副本。例子中的s1、s4、s5所引用的字符串可以在编译期就确定下来,因此它们都将引用常量池中的同一个字符串对象。

使用new String()创建的字符串对象是运行时创建处理的,它被保存在运行时内存区(即堆内存)内,不会放入常量池中。

equals()方法是Object类提供的一个实例方法,因此所有引用变量都可调用该方法来判断是否与其他引用变量相等。String类以及重写了Object的equals()方法,String的equals()方法判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过equals()比较将返回true,否则将返回false。

class Person
{
    private String name;
    private String idStr;
    public Person(){}
    public Person(String name , String idStr)
    {
        this.name = name;
        this.idStr = idStr;
    }
    // 此处省略name和idStr的setter和getter方法。
    // name的setter和getter方法
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }

    // idStr的setter和getter方法
    public void setIdStr(String idStr)
    {
        this.idStr = idStr;
    }
    public String getIdStr()
    {
        return this.idStr;
    }
    // 重写equals()方法,提供自定义的相等标准
    public boolean equals(Object obj)
    {
        // 如果两个对象为同一个对象
        if (this == obj)
            return true;
        // 只有当obj是Person对象
        if (obj != null && obj.getClass() == Person.class)
        {
            Person personObj = (Person)obj;
            // 并且当前对象的idStr与obj对象的idStr相等才可判断两个对象相等
            if (this.getIdStr().equals(personObj.getIdStr()))
            {
                return true;
            }
        }
        return false;
    }
}
public class OverrideEqualsRight
{
    public static void main(String[] args)
    {
        Person p1 = new Person("孙悟空" , "12343433433");
        Person p2 = new Person("孙行者" , "12343433433");
        Person p3 = new Person("孙悟饭" , "99933433");
        // p1和p2的idStr相等,所以输出true
        System.out.println("p1和p2是否相等?"
            + p1.equals(p2));
        // p2和p3的idStr不相等,所以输出false
        System.out.println("p2和p3是否相等?"
            + p2.equals(p3));
    }
}

重写equals()方法应该满足下列条件:

1.自反性:对任意x,x.equals(X)一定返回true。
2.对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。
3.传递性:对任意x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)也返回true。
4.一致性:对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false。
5.对任何不是null的x,x.equals(null)一定返回false。

Object默认提供的equals()只是比较对象的地址,即Object类的equals()方法比较的结果与==运算符比较的结果完全相同。在实际应用中常常需要重写equals()方法,重写equals()方法时,相等条件是由业务要求决定的,因此equals()方法的实现也是由业务要求决定的。

类成员 理解类成员

在Java类里只能包含成员变量、方法、构造器、初始化块、内部类(包括接口、枚举)5种成员。static可以修饰成员变量、方法、初始化块、内部类(包括接口、枚举),以static修饰的成员就是类成员。类成员属于整个类,而不属于单个对象。

类变量属于整个类,当系统第一次准备使用该类时,系统会为该类变量分配内存空间,类变量开始生效,直到该类被卸载,该类的类变量所占有的内存才被系统的垃圾回收机制回收。类变量生存范围几乎等同于该类的生存范围。当类初始化完成后,类变量也被初始化完成。
当通过对象来访问类变量时,系统会在底层转换为通过该类来访问类变量。

当使用实例来访问类成员时,实际上依然是委托给该类来访问类成员,因此即使某个实例为null,它也可以访问它所属类的类成员。
如果一个null对象访问实例成员(包括实例变量和实例方法),将会引发NullPointException异常,因为null表明该实例根本不存在,既然实例不存在,那么它的实例变量和实例方法自然也不存在。

单例(Singleton)类

如果一个类始终只能创建一个实例,则这个类被称为单例类。
在一些特殊场景下,要求不允许自由创建该类的对象,而只允许为该类创建一个对象。为了避免其他类自由创建该类的实例,应该把该类的构造器使用private修饰,从而把该类的所有构造器隐藏起来。
根据良好封装的原则:一旦把该类的构造器隐藏起来,就需要一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。
除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建了一个对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,故该成员变量必须使用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)
        {
            // 创建一个Singleton对象,并将其缓存起来
            instance = new Singleton();
        }
        return instance;
    }
}
public class SingletonTest
{
    public static void main(String[] args)
    {
        // 创建Singleton对象不能通过构造器,
        // 只能通过getInstance方法来得到实例
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2); // 将输出true
    }
}
final修饰符

final关键字可用于修饰类、变量和方法

final成员变量

final修饰的成员变量必须由程序员显式地指定初始值。

final修饰的类变量、实例变量能指定初始值的地方如下:
1.类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
2.实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。

与普通成员变量不同的是,final成员变量(包括实例变量和类变量)必须由程序员显式初始化,系统不会对final成员进行隐式初始化。如果打算在构造器、初始化块中对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值。

final局部变量

系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不知道默认值。如果final修饰的局部变量在定义时已经指定默认值,则后面代码中不能再对该变量赋值。

final修饰基本类型变量和引用类型变量的区别

当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用联系不了所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

可执行“宏替换”的final变量

对一个final变量来说,不管它是类变量、实例变量,还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。

使用final修饰符修饰

在定义该final变量时指定了初始值

该初始值可以在编译时就被确定下来

final修饰符的一个重要用途就是定义“宏变量”。当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。
如果被赋的表达式只是基本的算术表达式或字符串连接运算,没有访问普通变量,调用方法,Java编译器同样会将这种final变量当成“宏变量”处理。

Java会使用常量池来管理曾经用过的字符串直接量,例如执行String a = "java";语句之后,常量池中就会缓存一个字符串"java";如果程序再次执行String b = "java";,系统将会让b直接指向常量池中的"java"字符串,因此a==b将会返回true。

final方法

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

如果子类中定义一个与父类private方法用相同方法名、相同形参列表、相同返回值类型的方法,不是方法重写,只是重新定义了一个新方法。因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。private final

final修饰的方法不能被重写,可以被重载

final类

final修饰的类不可被继承。

不可变类

不可变(immutable)类的意思是创建该类的实例后,该实例的实例变量是不可改变的。Java的8个包装类和java.lang.String类都是不可变类,当创建它们的实例后,其实例的实例变量是不可改变。

如果需要创建自定义的不可变类,可遵守如下规则

使用private和final修饰符来修饰该类的成员变量

提供带参数构造器,用于根据传入参数来初始化类里的成员变量

仅为该类的成员变量提供getter方法,不要为该类的成员变量提供setter方法,因为普通方法无法修改final修饰的成员变量

如果有必要,重写Object类的hashCode()和equals()方法。equals()方法根据关键成员变量来作为两个对象是否相等的标准,除此之外,还应该保证两个用equals()方法判断为相等的对象的hashCode()也相等。

缓存实例的不可变类
class CacheImmutable
{
    private static int MAX_SIZE = 10;
    //使用数组来缓存已有的实例
    private static CacheImmutable[] cache = new CacheImmutable[MAX_SIZE];
    //记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例
    private static int pos = 0;
    private final String name;
    private CacheImmutable(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public static CacheImmutable ValueOf(String name) 
    {
        //遍历已缓存的对象,
        for (int i = 0; i < cache.length; i++) 
        {
            //如果已有相同实例,则直接返回该缓存的实例
            if (cache[i] != null && cache[i].getName().equals(name)) 
            {
                return cache[i];
            }
        }
        //如果缓存池已满
        if (pos == MAX_SIZE) 
        {
            //把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置
            cache[0] = new CacheImmutable(name);
            //把pos设为1
            pos = 1;
        }
        else {
            //把新创建的对象缓存起来,pos加1
            cache[pos++] = new CacheImmutable(name);
        }
        return cache[pos-1];
    }
    public boolean equals(Object obj) 
    {
        if (this == obj) 
        {
            return true;
        }
        if (obj !=null && obj.getClass() == CacheImmutable.class) 
        {
            CacheImmutable ci = (CacheImmutable)obj;
            return name.equals(ci.getName());
        }
        return false;
    }
    public int hashCode() 
    {
        return name.hashCode();
    }
}
public class CacheImmutableTest {

    public static void main(String[] args) 
    {
        CacheImmutable c1 = CacheImmutable.ValueOf("hello");
        CacheImmutable c2 = CacheImmutable.ValueOf("hello");
        //下面代码将输出true
        System.out.println(c1 == c2);
        System.out.println(c1.equals(c2));
    }

}
修饰符的适应范围

4个访问控制符是互斥的,最多只能出现其中之一

abstract和final永远不能同时使用

abstract和static不能同时修饰方法,可以同时修饰内部类

abstract和private不能同时修饰方法,可以同时修饰内部类

private和final同时修饰方法虽然语法是合法的,但没有太大的意义——由于private修饰的方法不可能被子类重写,因此使用final修饰没什么意义。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66251.html

相关文章

  • 谈谈我所理解的面向对象

    摘要:众多面向对象的编程思想虽不尽一致,但是无论哪种面向对象编程语言都具有以下的共通功能。原型编程以类为中心的传统面向对象编程,是以类为基础生成新对象。而原型模式的面向对象编程语言没有类这样一个概念。 什么是面向对象?这个问题往往会问到刚毕业的新手or实习生上,也是往往作为一个技术面试的开头题。在这里我们不去谈如何答(fu)好(yan)问(guo)题(qu),仅谈谈我所理解的面向对象。 从历...

    avwu 评论0 收藏0
  • 7. 初步理解面向对象 【连载 7】

    摘要:是一种典型的面向对象编程语言。这篇文章主要是来初步理解一下面向对象的思维为下面的内容先给一个基础。针对面向对象编程的更多内容,会在后面的文章里面详细解释。他们都称之为对象。之后,我们再用编程语言,把这种映射编写出来,就是的面向对象编程啦。 showImg(https://segmentfault.com/img/remote/1460000012983458?w=900&h=500);...

    keelii 评论0 收藏0
  • Java编程需要注意的地方

    摘要:学编程真的不是一件容易的事不管你多喜欢或是多会编程,在学习和解决问题上总会碰到障碍。熟练掌握核心内容,特别是和多线程初步具备面向对象设计和编程的能力掌握基本的优化策略。   学Java编程真的不是一件容易的事,不管你多喜欢或是多会Java编程,在学习和解决问题上总会碰到障碍。工作的时间越久就越能明白这个道理。不过这倒是一个让人进步的机会,因为你要一直不断的学习才能很好的解决你面前的难题...

    leanxi 评论0 收藏0
  • Java面试题:面向对象,类加载器,JDBC, Spring 基础概念

    摘要:为什么不是面向对象不是面向对象,因为它包含个原始数据类型,例如。自定义类加载器继承的自定义类加载器。可以像下面这样指定参数面试题面向对象,类加载器,基础概念它们的关系如下启动类加载器,实现,没有父类。自定义类加载器,父类加载器为。 1. 为什么说Java是一门平台无关语言? 平台无关实际的含义是一次编写到处运行。Java 能够做到是因为它的字节码(byte code)可以运行在任何操作...

    Euphoria 评论0 收藏0
  • Java面试题:面向对象,类加载器,JDBC, Spring 基础概念

    摘要:为什么不是面向对象不是面向对象,因为它包含个原始数据类型,例如。自定义类加载器继承的自定义类加载器。可以像下面这样指定参数面试题面向对象,类加载器,基础概念它们的关系如下启动类加载器,实现,没有父类。自定义类加载器,父类加载器为。 1. 为什么说Java是一门平台无关语言? 平台无关实际的含义是一次编写到处运行。Java 能够做到是因为它的字节码(byte code)可以运行在任何操作...

    walterrwu 评论0 收藏0
  • Java面试题:面向对象,类加载器,JDBC, Spring 基础概念

    摘要:为什么不是面向对象不是面向对象,因为它包含个原始数据类型,例如。自定义类加载器继承的自定义类加载器。可以像下面这样指定参数面试题面向对象,类加载器,基础概念它们的关系如下启动类加载器,实现,没有父类。自定义类加载器,父类加载器为。 1. 为什么说Java是一门平台无关语言? 平台无关实际的含义是一次编写到处运行。Java 能够做到是因为它的字节码(byte code)可以运行在任何操作...

    longmon 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<