编程语言
首页 > 编程语言> > 狂Java讲义(读书笔记)(第五章)

狂Java讲义(读书笔记)(第五章)

作者:互联网

第五章面向对象(下)

5.1 Java 8 增强的包装类

把字符串类型的值转换为基本类型的值有两种方式。

String类提供了多个重载valueOf()方法,用于将基本类型变量转换成字符串,下面程序示范了这种各类型转换关系。

public class Student{
    public static void main(String[] args) {
        String intStr = "123";
        //把一个特定字符串转换成int变量
        int it1 = Integer.parseInt(intStr);
        int it2 = new Integer(intStr);
        System.out.println(it2);

        String floatStr = "4.56";
        //把一个特定字符串转换成float变量
        float ft1 = Float.parseFloat(floatStr);
        float ft2 = new Float(floatStr);
        System.out.println(ft2);

        //把一个double变量转换成String变量
        String dbStr = String.valueOf(3.344);
        System.out.println(dbStr);

        //把一个boolean变量转换成String变量
        String boolStr = String.valueOf(true);
        System.out.println(boolStr);
    }
}

Java8为Integer、Long增加了如下方法。

5.2 处理对象

5.2.1 打印对象和toString方法

object类提供的toString()方法总是返回该对象实现类的“类名+@+hashCode”值,这个返回值并不能真正实现“自我描述”的功能,因此如果用户需要自定义类能实现“自我描述”的功能,就必须重写Object类的toString()方法。例如下面程序。

class Apple{
    private String color;
    private double weight;
    public Apple(){}
    //提供没有参数的构造器
    public Apple(String color, double weight){
        this.color = color;
        this.weight = weight;
    }
    //省略color、weight的setter和getter方法
    ...
    //重写toString()方法,用于实现Apple对象的“自我描述”
    public String toString(){
        return "一个苹果,颜色是:" + color
                + ",重量是:" + weight;
    }
}
public class ToStringTest{
    public static void main(String[] args) {
        Apple a = new Apple("红色", 5.68);
        //打印Apple对象System.out.println(a);
    }

5.2.2 ==和equals方法

通常而言,正确地重写equals()方法应该满足下列条件

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

5.3 类成员

static关键字修饰的成员就是类成员。

5.3.1 理解类成员

5.3.2 单例(Singleton)类

如果一个类始终只能创建一个实例,则这个类被称为单例类。
根据良好封装的原则:一旦把该类的构造器隐藏起来,就需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。
除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证之创建一个对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,故成员变量必须使用static修饰。
基于上面的介绍,下面程序创建了一个单例类。

class Singleton{
    //使用一个类变量来缓存曾经创建的实例
    private static Singleton instance;
    //对构造器使用private修饰,隐藏该构造器
    private Singleton(){}
    //提供一个静态方法,用于返回Singletion实例
    //该方法可以加入自定义控制,保证只产生一个Singleton对象
    public static Singleton getInstance(){
        //如果instance为null,则表明还不曾创建Singleton对象
        //如果instance不为null,则表明已经创建了Singletion对象
        //将不会重新创建新的实例
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
public class Student {
    public static void main(String[] args) {
        //创建Singleton对象不能通过构造器
        //只能通过geInstance方法来得到实例
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);//将输出true

    }
}

5.4 final修饰符

final关键字可用于修饰类、成员变量和方法,final关键字有点类似C#里面的sealed关键字,用于表示它修饰的类、方法和变量不可变。

5.4.1 final成员变量

对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值,如果既没有在定义成员变量时指定初始值,也没有在初始化块、构造器中为成员变量指定初始值,那么这些成员变量的值将一直时系统默认分配的0、’\u0000’、false或null,这些成员变量也就完全失去了存在的意义。因此Java语法规定:final修饰的成员变量必须由程序员显式地指定初始值。
归纳起来,final修饰的类变量、实例变量能指定初始值的地方如下:

5.4.2 final局部变量

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

public class Student {
    public void test(final int a){
        //不能对final修饰的形参赋值,下面语法非法
        // a = 5
    }

    public static void main(String[] args) {
        //定义final局部变量时指定默认值,则str变量无法重新赋值
        final String str = "hello";
        //下面赋值语句非法
        //str = "Java";
        //定义final局部变量时没有指定默认值,则d变量可被赋值一次
        final double d;
        //第一次赋初始值,成功
        d = 5.6;
        // 对final变量重复赋值。下面语句非法
        // d = 3.4;
    }
}

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

import java.util.Arrays;

class Person{
    private int age;
    public Person(){}
    
    public Person(int age){
        this.age = age;
    }
    //省略age的setter和getter方法
    ...
}
public class Student {
    public static void main(String[] args) {
        //final修饰数组变量,iArr时一个引用变量
        final int[] iArr = {5, 6, 12, 9};
        System.out.println(Arrays.toString(iArr));
        //对数组元素进行排序,合法
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        //对数组元素赋值,合法
        iArr[2] = -8;
        System.out.println(Arrays.toString(iArr));
        //下面语句对iArr重新赋值,非法
        // iArr = null;
        // final修饰Person变量,p是一个引用变量
        final Person p = new Person(45);
        //改变Person对象的age实例变量,合法
        p.setAge(23);
        System.out.println(p.getAge());
        //下面语句对p重新赋值,非法
        // p = null;
    }
}

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

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

5.4.5 final方法

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

5.4.6 final 类

final修饰的类不可以有子类。

5.5 抽象类

5.5.1 抽象方法和抽象类

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

5.5.2 抽象类的作用

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为方式。

5.6 Java 8 改进的接口

5.6.1 接口的概念

5.6.2 Java 8 中接口的定义

和类定义不同,定义接口不再使用class关键字,而是使用interface关键字。定义接口的基本语法格式如下:

[修饰符] interface 接口名 extends 父接口1,父接口2...
{
    零个到多个常量定义...
    零个到多个抽象方法定义...
    零个到多个内部类、接口、枚举定义...
    零个到多个默认方法或类方法定义...
}

对上面语句的详细说明如下。

下面定义一个接口

package lee;
public interface Output{
    //接口里定义的成员变量只能是常量
    int MAX_CACHE_LINE = 50;
    //接口里定义的普通方法只能是public的抽象方法
    void out();
    void getData(String... msgs);
    //在接口中定义默认方法,需要使用default修饰
    default void print(String... msgs){
        for (String msg : msgs){
            System.out.println(msg);
        }
    }
    //在接口中定义默认方法,需要使用default修饰
    default void test(){
        System.out.println("默认的test()方法");
    }
    //在接口中定义类方法,需要使用static修饰
    static String staticTest(){
        return "接口里的方法";
    }
}

5.6.3 接口的继承

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

5.6.4 使用接口

归纳起来,接口主要有如下用途。

类实现接口的语法格式如下。

[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{
    类体部分
}

5.6.5 接口和抽象类

接口和抽象类很像,它们都具有如下特征。

5.7 内部类

内部类住哟啊有如下作用。

从语法角度来看,定义内部类与定义外部类的语法大致相同,内部类除了需要定义在其他类里面之外,还存在如下两点区别。

5.7.1 非静态内部类

内部类定义语法格式如下。

public class OuterClass{
    //此处可以定义内部类
}

非静态内部类里不能有静态方法、静态成员变量、静态初始化块。

5.7.2 静态内部类

如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方也成为静态内部类。

public class StaticInnerClassTest{
    private int prop1 = 5;
    private static int pro2 = 9;
    static class StaticInnerClass{
        //静态内部类里可以包含静态成员
        private static int age;
        public void accessOuterProp(){
            //下面代码出现错误
            //静态内部类无法访问外部类的实例变量
            System.out.println(pro1);
            //下面代码正常
            System.out.println(pro2);
        }
    }
}

外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。下面程序示范了这条规则。

public class AccessStaticInnerClass{
    static class StaticInnerClass{
        private static int prop1 = 5;
        private int prop2 = 9;
    }
    public void accessInnerProp(){
        //System.out.println(pro1);
        //上面代码出现错误,应改为如下形式
        //通过类名访问静态内部类的类成员
        System.out.println(StaticInnerClass.prop1);
        // System.out.println(prop2);
        // 上面代码出现错误,应该为如下形式
        // 通过实例访问静态内部类的实例变量
        System.out.println(new StaticInnerClass().prop2);
    }
}

5.7.3 使用内部类

  1. 在外部类内部使用内部类
  2. 在外部类以外使用非静态内部类
OuterClass.InnerClass varName

在外部类以外的地方创建非静态内部类实例的语法如下:

OuterInstance.new InnerConstructor()
class Out{
    //定义一个内部类,不适用访问控制符
    //即只有同一个包中的其他类可以访问该内部类
    class In{
        public In(String msg){
            System.out.println(msg);
        }
    }
}
public class CreateInnerInstance {
    public static void main(String[] args) {
        Out.In in = new Out().new In("测试信息");
        /*
        上面代码可改为如下三行代码
        使用OutterClass.InnerClass的形式定义内部类变量
        Out.In in
        创建外部类实例,非静态内部类实例将寄生在该实例中
        Out out = new Out();
        通过外部实例和new来调用内部类构造器创建非静态内部类实例
        in = out.new In("测试信息");
         */
    }
}

上面程序粗体代码行创建了一个非静态内部类的对象。从上面代码可以看出,非静态内部类的构造器必须使用外部类对象来调用。
3.在外部类以外使用静态内部类
在外部类以外的地方创建静态内部类实例的语法如下:

new OuterClass.InnerConstructor()

下面程序示范了如何在外部类以外的地方创建静态内部类的实例。

class StaticOut{
    // 定义一个静态内部类,不适用访问控制符
    // 即同一个包中的其他类可以访问该类内部类
    static class StaticIn{
        public StaticIn(){
            System.out.println("静态内部类的构造器");
        }
    }
}
public class CreateStaticInnerInstance {
    public static void main(String[] args) {
        StaticOut.StaticIn in = new StaticOut.StaticIn();
        /*
        上面代码可改为如下两行代码
        使用OuterClass.InnerClass的形式定义内部类变量
        StaticOut.StaticIn in;
        通过new来调用内部类构造器创建静态内部类实例
        in = new StaticOut.StaticIn();
         */
    }
}

5.7.4 局部内部类

如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。

public class LocalInnerClass {
    public static void main(String[] args) {
        //定义局部内部类
        class InnerBase{
            int a;
        }
        //定义局部内部类的子类
        class InnerSub extends InnerBase{
            int b;
        }
        //创建局部内部类的对象
        InnerSub is = new InnerSub();
        is.a = 5;
        is.b = 8;
        System.out.println("InnerSub对象的a和b实例变量是:"
                + is.a + "," + is.b);
    }
}

5.7.5 Java 8 改进的匿名内部类

定义匿名内部类的格式如下:

new 实现接口() | 父类构造器(实参列表){
    //匿名内部类的类体部分
}

关于匿名内部类还有如下两条规则:

interface Product{
    public double getPrice();
    public String getName();
}
public class AnonymousTest {
    public void test(Product p){
        System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
    }

    public static void main(String[] args) {
        AnonymousTest ta = new AnonymousTest();
        //调用test()方法时,需要传入一个Product参数
        // 此处传入其匿名实现类的实例
        ta.test(new Product() {
            @Override
            public double getPrice() {
                return 567.8;
            }

            @Override
            public String getName() {
                return "AGP显卡";
            }
        });
    }
}

5.8 Java 8 新增的Lambda表达式

Lamdba表达式的主要作用就是代替匿名内部类的繁琐语句。它由三部分组成。

interface Eatable{
    void taste();
}
interface Flyable{
    void fly(String weather);
}
interface Addable{
    int add(int a, int b);
}
public class LambdaQs {
    //调用该方法需要Eatable对象
    public void eat(Eatable e){
        System.out.println(e);
        e.taste();
    }
    //调用该方法需要Flyable对象
    public void drive(Flyable f){
        System.out.println("我正在驾驶:" + f);
        f.fly("【碧空如洗的晴日】");
    }
    //调用该方法需要Addable对象
    public void test(Addable add){
        System.out.println("5和3的和为:" + add.add(5, 3));
    }

    public static void main(String[] args) {
        LambdaQs lq = new LambdaQs();
        //Lambda表达式的代码块只有一条语句,可以省略花括号
        lq.eat(()-> System.out.println("苹果味道不错!"));
        //Lambda表达式的形参列表只有一个形参,可以省略圆括号
        lq.drive(weather ->{
            System.out.println("今天天气是:" + weather);
            System.out.println("直升机飞行平稳");
        });
        // Lambda表达式的代码块只有一条语句,可以省略花括号
        // 代码块中只有一条语句,及时该表达式需要返回值,也可以省略return关键字
        lq.test(((a, b) -> a + b));
    }
}

5.8.2 Lambda表达式与函数式接口

Lambda表达式实现的是匿名方法——因此他只能实现特定函数式接口中的唯一方法。这意味着Lambda表达式有如下两个限制:

5.8.3 方法引用和构造器引用

  1. 引用类方法
  2. 引用特定对象的实例方法
  3. 引用某类对象的实例方法
  4. 引用构造器

5.8.4 Lambda表达式与匿名内部类的联系和区别

Lambda表达式是匿名内部类的一种简化,因此它可以部分提取匿名内部类的作用,Lambda表达式与匿名内部类存在如下相同点:

5.8.5 使用Lambda表达式调用Arrays的类方法

5.9 枚举类

在某些情况下,一个类的对象是有限而且固定的,比如季节类,它只有4个对象;再比如行星类,目前只有8个对象。这种实例有限而且固定的类,在Java里被称为枚举类。

5.9.1 手动实现枚举类

在早期代码中,可能会直接使用简单的静态常量来表示枚举,例如:

public static final int SEASON_SPRING = 1;
public static final int SEASON_SUMMER = 2;
public static final int SEASON_FALL = 3;
public static final int SEASON_WINTER = 4;

这种方法简单明了,但存在如下几个问题:

但枚举又确实有存在的意义,因此早期也可采用通过定义类的方式来实现,可以采用如下设计方式:

5.9.2 枚举类入门

Java 5 新增了一个enum关键字(它与class、interface关键字的地位相同),用于定义枚举类。
但枚举类终究不是普通类,它与普通类有如下简单区别:

java.lang.Enum类中提供了如下几个方法:

5.9.3 枚举类的成员变量、方法和构造器

枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可以定义成员变量,方法和构造器。

5.9.4 实现接口的枚举类

5.9.5 包含抽象方法的枚举类

5.10 修饰符的使用范围

本章练习

1.通过抽象类定义车类的模板,然后通过抽象的车类来派生拖拉机、卡车、小轿车。
2.定义一个接口,并使用匿名内部类方式创建接口的实例。
3.定义一个函数式接口,并使用Lambda表达式创建函数式接口的实例。
4.定义一个类,该类用于封装一桌梭哈游戏,这个类应该包含桌上剩下的牌的信息,并包含5个玩家的状态信息:他们各自的位置、游戏状态(正在游戏或已放弃)、手上已有的牌等信息。如果有可能,这个类还应该实现发牌方法,这个方法需要控制从谁开始发牌,不要发牌给放弃的人,并修改桌上剩下的牌。

标签:Java,内部,读书笔记,final,接口,讲义,String,public,变量
来源: https://blog.csdn.net/iamaowu/article/details/119299863