返回介绍

Object-oriented programming

发布于 2025-02-22 22:20:06 字数 29595 浏览 0 评论 0 收藏 0

In this part of the Java tutorial, we will talk about object-oriented programming in Java.

There are three widely used programming paradigms there. Procedural programming, functional programming and object-oriented programming. Java supports both procedural and object-oriented programming.

Object-oriented programming (OOP) is a programming paradigm that uses objects and their interactions to design applications and computer programs.

There are some basic programming concepts in OOP:

  • Abstraction
  • Polymorphism
  • Encapsulation
  • Inheritance

The abstraction is simplifying complex reality by modeling classes appropriate to the problem. The polymorphism is the process of using an operator or function in different ways for different data input. The encapsulation hides the implementation details of a class from other objects. The inheritance is a way to form new classes using classes that have already been defined.

Objects

Objects are basic building blocks of a Java OOP program. An object is a combination of data and methods. In an OOP program, we create objects. These objects communicate together through methods. Each object can receive messages, send messages and process data.

There are two steps in creating an object. First, we define a class. A class is a template for an object. It is a blueprint which describes the state and behavior that the objects of the class all share. A class can be used to create many objects. Objects created at runtime from a class are called instances of that particular class.

package com.zetcode;

class Being {}

public class SimpleObject {

  public static void main(String[] args) {
    
    Being b = new Being();
    System.out.println(b);
  }
}

In our first example, we create a simple object.

class Being {}

This is a simple class definition. The body of the template is empty. It does not have any data or methods.

Being b = new Being();

We create a new instance of the Being class. For this we have the new keyword. The b variable is the handle to the created object.

System.out.println(b);

We print the object to the console to get some basic description of the object. What does it mean, to print an object? When we print an object, we in fact call its toString() method. But we have not defined any method yet. It is because every object created inherits from the base Object . It has some elementary functionality which is shared among all objects created. One of this is the toString() method.

$ javac com/zetcode/SimpleObject.java
$ ls com/zetcode/
Being.class  SimpleObject.class  SimpleObject.java

The compiler creates two class files. The SimpleObject.class is the application class and the Being.class is the custom class that we work with in the application.

$ java com.zetcode.SimpleObject 
com.zetcode.Being@125ee71

We get a the name of the class of which the object is an instance, the @ character, and the unsigned hexadecimal representation of the hash code of the object.

Object attributes

Object attributes is the data bundled in an instance of a class. The object attributes are called instance variables or member fields. An instance variable is a variable defined in a class, for which each object in the class has a separate copy.

package com.zetcode;

class Person {

  public String name;
}

public class ObjectAttributes {

  public static void main(String[] args) {
    
    Person p1 = new Person();
    p1.name = "Jane";

    Person p2 = new Person();
    p2.name = "Beky";

    System.out.println(p1.name);
    System.out.println(p2.name);
  }
}

In the above Java code, we have a Person class with one member field.

class Person {

  public String name;
}

We declare a name member field. The public keyword specifies that the member field will be accessible outside the class block.

Person p1 = new Person();
p1.name = "Jane";

We create an instance of the Person class and set the name variable to "Jane". We use the dot operator to access the attributes of objects.

Person p2 = new Person();
p2.name = "Beky";

We create another instance of the Person class. Here we set the variable to "Beky".

System.out.println(p1.name);
System.out.println(p2.name);

We print the contents of the variables to the console.

$ java com.zetcode.ObjectAttributes 
Jane
Beky

We see the output of the program. Each instance of the Person class has a separate copy of the name member field.

Methods

Methods are functions defined inside the body of a class. They are used to perform operations with the attributes of our objects. Methods bring modularity to our programs.

Methods are essential in the encapsulation concept of the OOP paradigm. For example, we might have a connect() method in our AccessDatabase class. We need not to be informed how exactly the method connect() connects to the database. We only have to know that it is used to connect to a database. This is essential in dividing responsibilities in programming, especially in large applications.

Objects group state and behavior. Methods represent the behavioral part of the objects.

package com.zetcode;

class Circle {

  private int radius;

  public void setRadius(int radius) {
    
    this.radius = radius;
  }

  public double area() {
    
    return this.radius * this.radius * Math.PI;
  }
}

public class Methods {

  public static void main(String[] args) {
    
    Circle c = new Circle();
    c.setRadius(5);

    System.out.println(c.area());
  }
}

In the code example, we have a Circle class. In the class, we define two methods. The setRadius() method assigns a value to the radius member and the area() method computes an area of the circle from the class member and a constant.

private int radius;

We have one member field in the class. It is the radius of the circle. The private keyword is an access specifier. It tells that the variable is restricted to the outside world. If we want to modify this variable from the outside, we must use the publicly available setRadius() method. This way we protect our data.

public void setRadius(int radius) {
  
  this.radius = radius;
}

This is the setRadius() method. The this variable is a special variable which we use to access the member fields from methods. The this.radius is an instance variable, while the radius is a local variable, valid only inside the setRadius() method.

Circle c = new Circle();
c.setRadius(5);

We create an instance of the Circle class and set its radius by calling the setRadius() method on the object of the circle. We use the dot operator to call the method.

public double area() {
  
  return this.radius * this.radius * Math.PI;
}

The area() method returns the area of a circle. The Math.PI is a built-in constant.

$ java com.zetcode.Methods 
78.53981633974483

Running the example we get the above output.

Access modifiers

Access modifiers set the visibility of methods and member fields. Java has three access modifiers: public , protected and private . The public members can be accessed from anywhere. The protected members can be accessed only within the class itself, by inherited classes and other classes from the same package. Finally, the private members are limited to the containing type, e.g. only within its class or interface. If we do not specify an access modifier, we have a package-private visibility. In such a case, members and methods are accessible within the same package.

Access modifiers protect data against accidental modifications. They make the programs more robust.

package com.zetcode;

class Person {

  public String name;
  private int age;

  public int getAge() {
    
    return this.age;
  }

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

public class AccessModifiers {

  public static void main(String[] args) {

    Person p = new Person();
    p.name = "Jane";

    p.setAge(17);

    System.out.println(String.format("%s is %d years old",
        p.name, p.getAge()));
  }
}

In the above program, we have two member fields. One is declared public, the other private.

public int getAge() {
  
  return this.age;
}

If a member field is private , the only way to access it is via methods. If we want to modify an attribute outside the class, the method must be declared public . This is an important aspect of data protection.

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

The setAge() method enables us to change the private age variable from outside of the class definition.

Person p = new Person();
p.name = "Jane";

We create a new instance of the Person class. Because the name attribute is public , we can access it directly. However, this is not recommended.

p.setAge(17);

The setAge() method modifies the age member field. The age cannot be accessed or modified directly, because it is declared private .

System.out.println(String.format("%s is %d years old",
    p.name, p.getAge()));

Finally, we access both members to build a string, which is printed to the console.

$ java com.zetcode.AccessModifiers 
Jane is 17 years old

Running the example we have this output.

The following program will show how access modifiers influence the way members are inherited by subclasses.

package com.zetcode;

class Base {

  public String name = "Base";
  protected int id = 5323;
  private boolean isDefined = true;
}

class Derived extends Base {

  public void info() {
    
    System.out.println("This is Derived class");
    System.out.println("Members inherited:");
    System.out.println(this.name);
    System.out.println(this.id);
    // System.out.println(this.isDefined);
  }
}

public class ProtectedMember {

  public static void main(String[] args) {
    
    Derived drv = new Derived();
    drv.info();
  }
}

In this program, we have a Derived class which inherits from the Base class. The Base class has three member fields. All with different access modifiers. The isDefined member is not inherited. The private modifier prevents this.

class Derived extends Base {

The Derived class inherits from the Base class. To inherit from another class, we use the extends keyword.

System.out.println(this.name);
System.out.println(this.id);
// System.out.println(this.isDefined);

The public and the protected members are inherited by the Derived class. They can be accessed. The private member is not inherited. The line accessing the member field is commented. If we uncommented the line, the code would not compile.

$ java com.zetcode.ProtectedMember 
This is Derived class
Members inherited:
Base
5323

Running the program, we receive this output.

The constructor

A constructor is a special kind of a method. It is automatically called when the object is created. Constructors do not return values and also do not use the void keyword. The purpose of the constructor is to initiate the state of an object. Constructors have the same name as the class. The constructors are methods, so they can be overloaded too. Constructors cannot be directly invoked. The new keyword invokes them. Constructors cannot be declared synchronized, final, abstract, native, or static.

Constructors cannot be inherited. They are called in the order of inheritance. If we do not write any constructor for a class, Java provides an implicit default constructor. If we provide any kind of a constructor then the default is not supplied.

package com.zetcode;

class Being {

  public Being() {
    
    System.out.println("Being is created");
  }

  public Being(String being) {
    
    System.out.println(String.format("Being %s is created", being));
  }
}

public class Constructor {

  @SuppressWarnings("ResultOfObjectAllocationIgnored")
  public static void main(String[] args) {
    
    new Being();
    new Being("Tom");
  }
}

We have a Being class. This class has two constructors. The first one does not take parameters, the second one takes one parameter.

public Being() {
  
  System.out.println("Being is created");
}

This constructor does not take any parameters.

public Being(String being) {
  
  System.out.println(String.format("Being %s is created", being));
}

This constructor takes one string parameter.

@SuppressWarnings("ResultOfObjectAllocationIgnored")

This annotation will suppress a warning that we do not assign our created objects to any variables. Normally this would be a suspicious activity.

new Being();

An instance of the Being class is created. The no-argument constructor is called upon object creation.

new Being("Tom");

Another instance of the Being class is created. This time the constructor with a parameter is called upon object creation.

$ java com.zetcode.Constructor 
Being is created
Being Tom is created

This is the output of the program.

In the next example, we initiate data members of the class. Initiation of variables is a typical job for constructors.

package com.zetcode;

import java.util.Calendar;
import java.util.GregorianCalendar;

class MyFriend {

  private GregorianCalendar born;
  private String name;

  public MyFriend(String name, GregorianCalendar born) {
    
    this.name = name;
    this.born = born;
  }

  public void info() {
    
    System.out.format("%s was born on %s/%s/%s\n",
        this.name, this.born.get(Calendar.DATE),
        this.born.get(Calendar.MONTH),
        this.born.get(Calendar.YEAR));
  }
}

public class MemberInit {

  public static void main(String[] args) {
    
    String name = "Lenka";
    GregorianCalendar born = new GregorianCalendar(1990, 3, 5);

    MyFriend fr = new MyFriend(name, born);
    fr.info();
  }
}

We have a MyFriend class with data members and methods.

private GregorianCalendar born;
private String name;

We have two private variables in the class definition.

public MyFriend(String name, GregorianCalendar born) {
  
  this.name = name;
  this.born = born;
}

In the constructor, we initiate the two data members. The this variable is a handler used to reference the object variables. When the names of constructor parameters and the names of members are equal, using this keyword is required. Otherwise the usage is optional.

MyFriend fr = new MyFriend(name, born);
fr.info();

We create a MyFriend object with two arguments. Then we call the info() method of the object.

$ java com.zetcode.MemberInit 
Lenka was born on 5/3/1990

This is the output of the com.zetcode.MemberInit program.

Constructor chaining

Constructor chaining is the ability to call another constructor from a constructor. To call another constructor from the same class, we use the this keyword. To call another constructor from a parent class, we use the super keyword.

package com.zetcode;

class Shape {
  
  private int x;
  private int y;   
  
  public Shape(int x, int y) {
    
    this.x = x;
    this.y = y;
  }
  
  protected int getX() {
    
    return this.x;
  }
  
  protected int getY() {
    
    return this.y;
  }      
}

class Circle extends Shape {
  
  private int r;

  public Circle(int r, int x, int y) {
    
    super(x, y);    
    this.r = r;
  }

  public Circle() {
    
    this(1, 1, 1);
  }
  
  @Override
  public String toString() {
    
    return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY());
  }
}

public class ConstructorChaining {

  public static void main(String[] args) {
    
    Circle c1 = new Circle(5, 10, 10);
    Circle c2 = new Circle();
    
    System.out.println(c1);
    System.out.println(c2);
  }
}

We have a Circle class. The class has two constructors. One that takes one parameter and one that does not take any parameters.

class Shape {
  
  private int x;
  private int y;
...
}

The Shape class is responsible for dealing with the x and y coordinates of various shapes.

public Shape(int x, int y) {
  
  this.x = x;
  this.y = y;
}

The constructor of the Shape class initiates the x and y coordinates with the given parameters.

protected int getX() {
  
  return this.x;
}

protected int getY() {
  
  return this.y;
}  

We have defined two methods to retrieve the values of the coordinates. The members are private, so the only access possible is through methods.

class Circle extends Shape {
  
  private int r;
...
}

The Circle class inherits from the Shape class. It defines the radius member which is specific to this shape.

public Circle(int r, int x, int y) {
  
  super(x, y);    
  this.r = r;
}

The first constructor of the Circle class takes three parameters. The radius and the x and y coordinates. With the super keyword, we call the parent's constructor passing the coordinates. Note that the super keyword must be the first statement in the constructor. The second statement initiates the radius member of the circle class.

public Circle() {
  
  this(1, 1, 1);
}

The second constructor takes no parameters. In such a case, we provide some default values. The this keyword is used to call the three-parameter constructor of the same class, passing three default values.

@Override
public String toString() {
  
  return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY());
}

Inside the toString() method, we provide a string representation of the Circle class. To determine the x and y coordinates, we use the inherited getX() and getY() methods.

$ java com.zetcode.ConstructorChaining 
Circle: r:5, x:10, y:10
Circle: r:1, x:1, y:1

This is the output of the example.

Class constants

It is possible to create class constants. These constants do not belong to a concrete object. They belong to the class. By convention, constants are written in uppercase letters.

package com.zetcode;

class Math {

  public static final double PI = 3.14159265359;
}

public class ClassConstant {

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

We have a Math class with a PI constant.

public static final double PI = 3.14159265359;

The final keyword is used to define a constant. The static keyword enables to reference the member without creating an instance of the class. The public keyword makes it accessible outside the body of the class.

$ java com.zetcode.ClassConstant 
3.14159265359

Running the example we get the above output.

The toString() method

Each object has a toString() method. It returns a human-readable representation of the object. The default implementation returns the fully qualified name of the type of the Object . When we call the System.out.println() method with an object as a parameter, the toString() is being called.

package com.zetcode;

class Being {

  @Override
  public String toString() {

    return "This is Being class";
  }
}

public class ThetoStringMethod {

  public static void main(String[] args) {
    
    Being b = new Being();
    Object o = new Object();

    System.out.println(o.toString());
    System.out.println(b.toString());
    System.out.println(b);
  }
}

We have a Being class in which we override the default implementation of the toString() method.

@Override
public String toString() {

  return "This is Being class";
}

Each class created inherits from the base Object . The toString() method belongs to this object class. The @Override annotation informs the compiler that the element is meant to override an element declared in a superclass. The compiler will then check that we did not create any error.

Being b = new Being();
Object o = new Object();

We create two objects. One custom defined and one built-in.

System.out.println(o.toString());
System.out.println(b.toString());

We call the toString() method explicitly on these two objects.

System.out.println(b);

As we have specified earlier, placing an object as a parameter to the System.out.println() will call its toString() method. This time, we have called the method implicitly.

$ java com.zetcode.ThetoStringMethod 
java.lang.Object@125ee71
This is Being class
This is Being class

This is what we get when we run the example.

Inheritance

The inheritance is a way to form new classes using classes that have already been defined. The newly formed classes are called derived classes, the classes that we derive from are called base classes. Important benefits of inheritance are code reuse and reduction of complexity of a program. The derived classes (descendants) override or extend the functionality of base classes (ancestors).

package com.zetcode;

class Being {

  public Being() {

    System.out.println("Being is created");
  }
}

class Human extends Being {

  public Human() {

    System.out.println("Human is created");
  }
}

public class Inheritance {

  @SuppressWarnings("ResultOfObjectAllocationIgnored")
  public static void main(String[] args) {
    
    new Human();
  }
}

In this program, we have two classes. A base Being class and a derived Human class. The derived class inherits from the base class.

class Human extends Being {

In Java, we use the extends keyword to create inheritance relations.

new Human();

We instantiate the derived Human class.

$ java com.zetcode.Inheritance
Being is created
Human is created

We can see that both constructors were called. First, the constructor of the base class is called, then the constructor of the derived class.

A more complex example follows.

package com.zetcode;

class Being {

  static int count = 0;

  public Being() {
    
    count++;
    System.out.println("Being is created");
  }

  public void getCount() {
    
    System.out.format("There are %d Beings%n", count);
  }
}

class Human extends Being {

  public Human() {
    
    System.out.println("Human is created");
  }
}

class Animal extends Being {

  public Animal() {
    
    System.out.println("Animal is created");
  }
}

class Dog extends Animal {

  public Dog() {
    
    System.out.println("Dog is created");
  }
}

public class Inheritance2 {

  @SuppressWarnings("ResultOfObjectAllocationIgnored")
  public static void main(String[] args) {
    
    new Human();
    Dog dog = new Dog();
    dog.getCount();
  }
}

We have four classes. The inheritance hierarchy is more complicated. The Human and the Animal classes inherit from the Being class. And the Dog class inherits directly from the Animal class and indirectly from the Being class.

static int count = 0;

We define a static variable. Static members are shared by all instances of a class.

public Being() {
  
  count++;
  System.out.println("Being is created");
}

Each time the Being class is instantiated, we increase the count variable by one. This way we keep track of the number of instances created.

class Animal extends Being {
...

class Dog extends Animal {
...

The Animal inherits from the Being and the Dog inherits from the Animal . Indirectly, the Dog inherits from the Being as well.

new Human();
Dog dog = new Dog();
dog.getCount();

We create instances from the Human and from the Dog classes. We call the getCount() method of the Dog object.

$ java com.zetcode.Inheritance2 
Being is created
Human is created
Being is created
Animal is created
Dog is created
There are 2 Beings

The Human object calls two constructors. The Dog object calls three constructors. There are two Beings instantiated.

Final class, private constructor

A class with a final modifier cannot be subclassed. A class with a constructor that has a private modifier cannot be instantiated.

package com.zetcode;

final class MyMath {

  public static final double PI = 3.14159265358979323846;
  
  // other static members and methods
}


public class FinalClass {

  public static void main(String[] args) {

    System.out.println(MyMath.PI);
  }
}

We have a MyMath class. This class has some static members and methods. We do not want anyone to inherit from our class. Therefore, we declare it to be final .

Furthermore, we also do not want to allow creation of instances from our class. We decide it to be used only from a static context. Declaring a private constructor, the class cannot be instantiated.

package com.zetcode;

final class MyMath {
  
  private MyMath() {}

  public static final double PI = 3.14159265358979323846;
  
  // other static members and methods
}


public class PrivateConstructor {

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

Our MyMath class cannot be instantiated and cannot be subclassed. This is how java.lang.Math is designed in Java language.

This was the first part of the description of OOP in Java.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文