Java教程6:实用的标准方法、访问修饰符和抽象类

作者 : 慕源网 本文共10970个字,预计阅读时间需要28分钟 发布时间: 2022-04-14 共378人阅读

在本系列的上一篇教程中,我们讨论了Java中的继承。在本教程中,我们将继续介绍继承,此外,我们还将介绍Java中的一些标准方法和其他一些有趣的零碎内容。

访问修饰符

请看下面的代码。

class Device {
    String name;
    
    public Device(String name) {
        this.name = name;
    }
}

class Camera extends Device {
    public Camera(String name) {
        super(name);
    }
}


public class Application {
    public static void main(String[] args) {
        
        Camera camera = new Camera("Camera 1");
        
        System.out.println(camera.name);
    }
}

我们创建一个Device类;然后,我们从Device派生一个Camera子类。

请注意,为了便于阅读,我们在同一个文件中声明了所有的类。这是合法的,事实上,您可以在Java中的任何地方声明类。但是,顶级公共类(如本例中的Application)只能在与该类同名的文件中定义。

Device类有一个名为name的String类型的实例变量,Camera类继承了该变量。但是,没有什么可以阻止我们访问主程序中的name变量。这是不希望的;我们希望特定于对象的数据被封装在该对象中,并且不能被外部干扰访问。

Java中的三个访问修饰符(以及默认访问级别)

如果我们声明一个没有访问修饰符的类的实例变量,默认情况下,可以从同一个包中的任何地方访问它。我们还没有查看包,但由于我们没有为该文件声明任何包名称,所有这些类都存在于(相同的)默认包中。

所以如果我们写

class Something {
    String name = "Hello";
}

并从该类创建对象,则可以从同一包中的任何方法或类访问name变量。

我们可以通过添加显式访问修饰符来改变这一点。

private

private访问修饰符表示只有声明变量或方法的类才能访问该变量或方法。

在下面的代码中,我们向name变量添加了private访问修饰符。现在,除了在设备类本身中之外,不能在任何地方访问名称。它甚至不能在Camera类中访问(尽管我们可以通过使用关键字super调用公共设备构造函数来设置它)。

class Device {
    private String name;
    
    public Device(String name) {
        this.name = name;
    }
}

class Camera extends Device {
    public Camera(String name) {
        
        // Note, at no point do we access
        // Device::name directly; we 
        // CANNOT now write
        // this.name = name
        super(name);
    }
}


public class Application {
    public static void main(String[] args) {
        
        Camera camera = new Camera("Camera 1");
        
        // Won't work
        // System.out.println(camera.name);
    }
}
public
public关键字与private相反。这意味着其他类可以自由访问该方法或变量。您可以在上面的代码中看到这方面的示例。构造函数被声明为公共的,以便其他类可以调用它们(通过从类创建对象)。Main方法也声明为Public,以便操作系统可以运行它来启动程序。

当然,我们也可以将变量声明为公共变量。通常这是不好的做法;实例变量应设置为private 或protected。此外,仅用于类本身的任何方法都应该是private 或protected的。但是,有时您确实需要从类外部访问变量。通常,这只是最终(常量)静态(可通过类访问)变量的情况。按照惯例,这些常量通常用大写字母书写。

class Device {
    private String name;
    
    Device(String name) {
        this.name = name;
    }
}

class Camera extends Device {
    
    // A public class constant
    public final static String DEVICE_CLASS = "CAMERA 1.23";
    
    Camera(String name) {
        super(name);
    }
}


public class Application {
    public static void main(String[] args) {
        
        System.out.println(Camera.DEVICE_CLASS);
    }
}
CAMERA 1.23

上面的代码将“device class”存储在常量公共静态(类)变量中。

protected

那么protected的呢?此关键字允许您创建在类本身中可访问的方法或变量,但也可以在所有派生类和同一包中的任何类中访问(与private不同,它将方法或变量限制为一个特定类)。

假设我们更改device 类以使名称受保护(并且我们还将其移动到其自己的名为Device.Java的文件中)。

public class Device {
    protected String name;
    
    Device(String name) {
        this.name = name;
    }
}
name变量现在不能被其他类访问,除非它们在同一个包中(通常它们不会在同一个包中——但在以后的教程中会有更多关于包的内容)。但任何派生类都可以访问它。下面的代码显示了Camera子类可以自由访问父类中的Name。

Camera.java:

public class Camera extends Device {
    
    // A public class constant
    public final static String DEVICE_CLASS = "CAMERA 1.23";
    
    Camera(String name) {
        super(name);
    }
    
    String getName() {
        return name;
    }

强烈推荐

海量程序代码,编程资源,无论你是小白还是大神研究借鉴别人优秀的源码产品学习成熟的专业技术强势助力帮你提高技巧与技能。在此处获取,给你一个全面升级的机会。只有你更值钱,才能更赚钱

如果你是初级程序员可以研究别人的代码提高技术,如果你喜欢搞网盟或者外包,可以让你快速建站,还等什么赶快关注吧,我们会持续输出相关资源

海量源码程序,学习别人的产品设计思维与技术实践

访问修饰符摘要

总之:尽可能使用private 。private访问修饰符将限制您的变量或方法在定义它的类中使用。

如果不能使用private,请尝试使用protected。这会将您的变量或方法限制为派生类和同一包中的类。

最后,当您确实希望从任何地方都可以访问某个方法(有时是常量变量)时,请使用public。

如果您不使用其中的任何一个(通常应该使用),则默认的访问级别是同一包中的任何代码都可以访问您的变量或方法。

IS-A VS HAS-A

有时,当您想要在另一个类中使用一个类的功能时,您不知道是否要从该类派生一个子类,或者只是使用该类来定义该类的实例变量(对象)。

在下面的示例中,Camera类和Car类都具有Machine类的功能。Camera有一个Machine类型的实例变量,而Car类是一个Machine(继承自它)。

class Machine {
    public void start() {
        System.out.println("Starting!");
    }
}

// Camera "has a" machine.
class Camera {
    Machine machine = new Machine();
    
    public void start() {
        machine.start();
    }
}

// Car "is a" machine.
class Car extends Machine {
    
}

public class Application {
    public static void main(String[] args) {
        
        Camera camera = new Camera();
        Car car = new Car();
        
        camera.start();
        car.start();
        
    }
}
通常,如果您想要的子类是父类的一种,则创建一个子类。例如,car 实际上是一种machine。还要确保您可以控制父类,或者父类在被覆盖和记录时考虑到了这一点。否则,如果父类在将来的修改中发生变化,您将会感到头痛。

在所有其他情况下,使用has关系来获得类的功能是安全的。只需使用该类创建实例变量(甚至是方法的本地变量)。

向上转型(upcasting)与向下转型(downcasting)

只要有子类,多态性就意味着可以将其存储在父类类型的变量中。这就是所谓的向上转换,因为您是隐式地将子类“转换”为父类类型。

尽管可以使用父类变量调用子类中被覆盖的方法,但不能使用父类变量调用仅存在于子类中的方法。

举个例子会让事情更清楚。

class Machine {
    public void start() {
        System.out.println("Start!");
    }
    
    public void stop() {
        System.out.println("Machine stopping.");
    }
}

class Camera extends Machine {
    public void snapshot() {
        System.out.println("Snap!");
    }
    
    @Override
    public void stop() {
        System.out.println("Camera stopping");
    }
}

public class Application {
    public static void main(String[] args) {
        
        Camera camera = new Camera();
        
        Machine machine = camera;
        machine.start();
        
        // Note: the camera version of stop() is invoked.
        machine.stop();
        
        // We can't do this
        // machine.snapshot();
        
    }
}
Start!
Camera stopping

向下转型

向下转型可能比向上转型更常见,而且更多的是你实际注意到的那种事情,而不是隐含发生的事情。

向下转换是指将存储在父类型变量中的对象转换为子类型。

通常,出于某种原因,您会将子类存储在父类变量中,然后想要访问特定的子类功能。让我们举个例子。

class Machine {
    public void start() {
        System.out.println("Start!");
    }
    
    public void stop() {
        System.out.println("Machine stopping.");
    }
}

class Camera extends Machine {
    public void snapshot() {
        System.out.println("Snap!");
    }
    
    @Override
    public void stop() {
        System.out.println("Camera stopping");
    }
}

public class Application {
    public static void main(String[] args) {
        
        Machine machine = new Camera();
        
        Camera camera = (Camera)machine;
        
        camera.snapshot();
        
    }
}
Snap!

这里,Camera 对象存储在Machine 类型变量中。然后,我们决定将其转换回Camera ,这样我们就可以调用特定于Camera 的方法。我们可以通过在括号中使用“(Camera)”来转换为Camera类型。

对象:所有对象的父对象中的标准方法

Java中的所有对象最终都派生自一个名为Object的类。当你创建一个新类时,即使这个类看起来没有扩展其他类,但实际上它扩展了对象类。

Object类有一些方法,重写这些方法通常很有用。通常,您可以使用IDE覆盖这些。在Eclipse中,右键单击您的类,转到Source->Override/Implement Methods,或者转到Source->Generate HashCode()and Equals()或Source->Generate ToString()。

所有这些方法都是做什么的?

ToString()

ToString()方法用于创建类的字符串表示形式。创建新类时,通常需要重写ToString()。

在这里,我们重写ToString()来为Machine类提供其自身的字符串表示,当我们想要区分一台机器和另一台机器时,可以调用它。

class Machine {
    private String name;
    
    // The constructor sets the machine's name.
    public Machine(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "This machine is named: " + name;
    }
}



public class Application {
    public static void main(String[] args) {
        
        Machine machine = new Machine("My Machine");
        
        System.out.println(machine.toString());
        
    }
}

equals()和hashCode()

equals()是一个将你的对象与另一个对象进行比较的方法,只有当它与另一个对象“equal”时才返回true。我们所说的“equal”是什么意思?

在Java中,如果要检查两个对象是否相等,通常会使用==操作符。但实际上,这会检查对对象的两个引用是否指向同一个对象。

这里有一个例子。

class Machine {
    private String name;
    
    public Machine(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "Machine name: " + name;
    }
}

public class Application {
    public static void main(String[] args) {
        
        Machine machine1 = new Machine("Big machine");
        Machine machine2 = new Machine("Big machine");
        Machine machine3 = machine2;
        
        if(machine1 == machine2) {
            System.out.println("Machine1 equals Machine2");
        }
        else {
            System.out.println("Machine1 does not equal Machine2");
        }
        
        if(machine2 == machine3) {
            System.out.println("Machine2 equals Machine3");
        }
        else {
            System.out.println("Machine2 does not equal Machine3");
        }
        
    }
}
Machine1 does not equal Machine2
Machine2 equals Machine3

您可以看到,只有当两个变量指向同一对象时,==才认为它们相等。

然而,我们经常想知道两个对象在它们所表示的内容方面是否等价。

例如,在上面的示例中,Machine1和Machine2都是Machine类型的对象;更重要的是,它们都包含完全相同的数据–它们的名称实例变量被设置为相同的内容,并且它们不包含其他实例变量。它们不是同一个对象,但这两个对象在行为和内在状态方面是相同的。

这就是equals()的作用。我们重写equals(),这样如果两个不同的对象是等价的,它就会返回true。在这种情况下,如果两台机器具有相同的名称,我们可以通过返回true来实现这一点。

最简单的方法是让IDE实现equals()。您所要做的就是在测试相等性时选择您认为重要的实例变量。同时,您可以让它实现hashCode()方法。理想情况下,无论何时实现equals(),都应该实现hashCode()。hashCode()的功能是返回一个整数,当两个对象等价时,该整数相同,但当它们不等价时,该整数不同。

在Eclipse中,您可以通过右键单击,选择“Source”,然后选择“Generate hashCode()and equals()”来实现这两个方法。然后,您所要做的就是在测试相等性时选择对您重要的实例变量。Eclipse将为您生成一个优化的equals()方法。如果添加更多重要的实例变量,请不要忘记删除它并重新生成它。

仅当您将对象添加到Map(我们尚未介绍)时才使用hashCode()。如果你完全确定你永远不会把你的对象添加到Map中,你就不用担心了。但最好是让IDE为您生成它。

在下面的代码中,我们自动添加了equals()和hashCode()方法,并使用它们代替==来测试对象是否相等。请注意,M

class Machine {
    private String name;
    
    public Machine(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "Machine name: " + name;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Machine other = (Machine) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    
    
}

public class Application {
    public static void main(String[] args) {
        
        Machine machine1 = new Machine("Big machine");
        Machine machine2 = new Machine("Big machine");
        Machine machine3 = machine2;
        
        if(machine1.equals(machine2)) {
            System.out.println("Machine1 equals Machine2");
        }
        else {
            System.out.println("Machine1 does not equal Machine2");
        }
        
        if(machine2.equals(machine3)) {
            System.out.println("Machine2 equals Machine3");
        }
        else {
            System.out.println("Machine2 does not equal Machine3");
        }
        
    }
}
Machine1 equals Machine2
Machine2 equals Machine3
关于字符串的重要说明:在测试两个字符串对象是否相等时,应该始终使用equals()而不是==。有时,==实际上会像您期望的那样在字符串上工作;这是因为如果您在Java中声明两个相同的String对象,Java将通过将变量设置为指向相同的String对象来进行幕后优化。但你不应该依赖这个。如果要检查两个字符串是否相等,即它们是否包含相同的字符。始终使用equals()而不是==。

clone()

这里的“copy”意味着clone()返回的对象应该是一个新对象,它等于创建它的对象,也就是说,如果使用equals()方法比较两个对象,则返回true。

一个例子应该清楚地说明这一点。请注意,clone()返回的对象必须向下转换为正确的对象类型。

class Machine {
    private String name;
    
    public Machine(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "Machine name: " + name;
    }
    
    @Override 
    public Object clone() {
        return new Machine(name);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Machine other = (Machine) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    
    
}

public class Application {
    public static void main(String[] args) {
        
        Machine machine1 = new Machine("New Machine");
        Machine machine2 = (Machine)machine1.clone();
        
        if(machine1.equals(machine2)) {
            System.out.println("Machine1 equals Machine2");
        }
        else {
            System.out.println("Machine1 does not equal Machine2");
        }
        
        if(machine1 == machine2) {
            System.out.println("machine1 and machine2 point to the same objects");
        }
        else {
            System.out.println("machine1 and machine2 point to different objects");
        }
        
    }
}
Machine1 equals Machine2
machine1 and machine2 point to different objects

返回类型协方差

在上面的例子中,clone()方法返回一个对象,而不是Machine。这是clone()的常见行为。但是,我们可以使用一个有用的技巧来返回Machine类型的对象。

通常,Java中overridden 方法必须具有与父方法相同的“signature”(相同的参数和返回类型)。然而,这条规则有一个例外。如果父方法返回类型A的对象,则当(且仅当)B恰好是A的子类时,被覆盖的版本可能返回类型B的对象。

在上面的示例中,clone()返回一个Object类型的对象。但是,所有对象都是内置对象类的子对象,包括Machine类型的对象。这称为返回类型协方差(大概是因为返回类型的变化方向与派生类的变化方向相同)。

class Machine {
    private String name;
    
    public Machine(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "Machine name: " + name;
    }
    
    // Make use of return type covariance.
    // This is still a legitimate override.
    @Override 
    public Machine clone() {
        return new Machine(name);
    }   
}

public class Application {
    public static void main(String[] args) {
        
        Machine machine1 = new Machine("New Machine");
        Machine machine2 = machine1.clone();
        
    }
}

抽象类

本教程的最后一个主题。我们在以前的教程中看到,在Java中,您可以使用接口来强制类实现某些方法。一个类可以实现多个接口。还有另一种方法可以强制类实现某些方法。这是从一个抽象类派生出来的。

抽象类包含必须在子类中重写和实现的抽象方法。如果一个类包含一个抽象方法,它就不能被实例化(你不能从它创建对象)。你只能覆盖它。一旦你在一个类中声明了一个抽象方法,你也必须声明这个类的抽象。

与使用接口相比,这样做的优势在于,您可以将实现放在子类可以使用的抽象类中。当然,一个类只能从一个抽象类继承。在Java中,没有多重继承。

// The Machine class is declare abstract
// because it has an abstract method.
// It now can't be instantiated.
abstract class Machine {
    private String name;
    
    public Machine(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "Machine name: " + name;
    }

    // The abstract method must be overridden
    // in child classes, or else they too
    // must be declared abstract and you can't
    // create objects from them.
    abstract public void start();
}

class Car extends Machine {
    public Car(String name) {
        super(name);
    }
    
    @Override
    public void start() {
        System.out.println("Starting the car.");
    }
}

public class Application {
    public static void main(String[] args) {
        
        // We cannot do the following, because
        // Machine is abstract.
        //Machine machine = new Machine("A machine");
        
        Car car = new Car("A car");
        
        car.start();
        
    }
}
Starting the car.

慕源网 » Java教程6:实用的标准方法、访问修饰符和抽象类

常见问题FAQ

程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!

发表评论

开通VIP 享更多特权,建议使用QQ登录