[Java] everything is an object -- object oriented programming

⭐ package

🏹 Import classes in package

Java has provided many ready-made classes for us to use. For example
You can use java.util.Date to introduce the date class in the java.util package

public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        // Get a millisecond timestamp
        System.out.println(date.getTime());
   }
}

However, this writing method is more troublesome. You can use the import statement to import the package

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        // Get a millisecond timestamp
        System.out.println(date.getTime());
   }
}

If you need to use other classes in java.util, you can use the * wildcard of import java.util. *, which can be used to use all classes in the package

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        // Get a millisecond timestamp
        System.out.println(date.getTime());
   }
}

However, it is more recommended to specify the class name to be imported when importing the classes in the package. Otherwise, conflicts are likely to occur

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        // There is a class such as Date in util and sql. At this time, ambiguity will occur and compilation error will occur
        Date date = new Date();
        System.out.println(date.getTime());
   }
}
// Compilation error
Error:(5, 9) java: yes Date Ambiguous reference to
  java.sql Classes in java.sql.Date and java.util Classes in java.util.Date All match

In this case, you need to use the full class name

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        System.out.println(date.getTime());
   }
}

matters needing attention:
β‘  import is very different from #include of C + +. C + + must #include to introduce other file contents, but Java does not need. import is just for the convenience of writing code. import is more similar to C + + namespace and using
β‘‘ Use wildcards to import all classes under the package. java does not import all files like include, but which class needs to be used when processing, and he will get that class

🏹 Static import

Use import static to import static methods and fields in the package

import static java.lang.System.*;
public class Test {
    public static void main(String[] args) {
        out.println("hello");
   }
}

In this way, it is more convenient to write some code, such as

import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
        double x = 30;
        double y = 40;
        // Static import is more convenient to write 
        // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        double result = sqrt(pow(x, 2) + pow(y, 2));
        System.out.println(result);
   }
}

🏹 Putting classes in packages

Basic rules

  • Add a package statement at the top of the file to specify which package the code is in
  • The package name needs to be specified as a unique name as far as possible, usually in the inverted form of the company's domain name (e.g. com.bit.demo1)
  • The package name should match the code path. For example, if you create a package of com.bit.demo1, there will be a corresponding path ecom/bit/demo1 to store the code
  • If a class does not have a package statement, the class is placed in a default package
  • Package name must be lowercase

Operation steps
1) Create a new package in IDEA: right click SRC - > New - > package

2) Enter the package name in the pop-up dialog box, such as kobe.nba

3) Create a class in the package, right click package name - > New - > class, and then enter the class name

4) At this point, you can see that the directory structure on our disk has been automatically created by IDEA

5) At the same time, we can see that a package statement appears at the top of the newly created Test.java file

🏹 Access control for packages

We have learned about public and private in the class. Members in private can only be used inside the class

If a member does not contain public and private keywords, this member can be used in other classes inside the package, but not in classes outside the package

The following code gives an example. Demo1 and Demo2 are in the same package, and Test is in other packages

Demo1.java

package com.bit.demo;
public class Demo1 {
    int value = 0; }

Demo2.java

package com.bit.demo; 
public class Demo2 { 
 public static void Main(String[] args) { 
 Demo1 demo = new Demo1(); 
 System.out.println(demo.value); 
 } 
} 
// The execution result can access the value variable
10

Test.java

import com.bit.demo.Demo1; 
public class Test { 
 public static void main(String[] args) { 
 Demo1 demo = new Demo1(); 
 System.out.println(demo.value); 
 } 
} 
// Compilation error
Error:(6, 32) java: value stay com.bit.demo.Demo1 Is not public; It cannot be accessed from an external package

🏹 Common system packages

1. java.lang: system common basic classes (String, Object). This package is automatically imported from JDK1.1.
2. java.lang.reflect:java reflection programming package;
3. java.net: network programming development package.
4. java.sql: support package for database development.
5. java.util: a tool package provided by java. (collection class, etc.) is very important
6. java.io:I/O programming development kit.

⭐ inherit

Classes created in code are mainly used to abstract some things in reality (including attributes and methods)
Sometimes there are some relations between objective things, so there will be some relations when they are expressed as classes and objects

For example, design a class to represent animals
Note that we can create a separate java file for each class. The class name must match the. Java file name (case sensitive)

// Animal.java 
public class Animal { 
 public String name; 
 
 public Animal(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "I am eating" + food); 
 } 
} 
// Cat.java 
class Cat { 
 public String name; 
 
 public Cat(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "I am eating" + food); 
 } 
} 
// Bird.java 
class Bird { 
 public String name; 
 
 public Bird(String name) { 
 this.name = name; 
 } 
 
 public void eat(String food) { 
 System.out.println(this.name + "I am eating" + food); 
 } 
 
 public void fly() { 
 System.out.println(this.name + "Flying οΈΏ(οΏ£οΈΆοΏ£)οΈΏ"); 
 } 
}

In this code, we found a lot of redundant code
After careful analysis, we find that Animal is related to Cat and Bird:

  • All three classes have the same eat method and behave exactly the same
  • All three classes have the same name attribute and have exactly the same meaning
  • Logically, Cat and Bird are both Animal (is - a semantics)

At this point, we can let Cat and Bird inherit the Animal class respectively to achieve the effect of code reuse

At this time, inherited classes such as Animal are called parent classes, base classes or superclasses. For classes such as Cat and Bird, we are called subclasses. Derived classes are similar to real sons inheriting their father's property. Subclasses will also inherit the fields and methods of the parent class to achieve the effect of code reuse

🏹 Basic grammar

class Subclass extends Parent class { 

} 
  • Use extends to specify the parent class
  • A subclass in Java can only inherit one parent class (while languages such as C++/Python support multiple inheritance)
  • Subclasses inherit all public fields and methods of the parent class
  • private fields and methods of the parent class are inaccessible in subclasses
  • The instance of a subclass also contains the instance of the parent class. You can use the super keyword to get a reference to the instance of the parent class

The above code can be improved by inheritance. At this time, we let Cat and Bird inherit from the Animal class, so Cat does not have to write the name field and eat method when defining

class Animal { 
 public String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println(this.name + "I am eating" + food); 
 } 
} 
class Cat extends Animal { 
 public Cat(String name) { 
 // Use super to call the constructor of the parent class 
 super(name); 
 } 
} 
class Bird extends Animal { 
 public Bird(String name) {
 super(name); 
 } 
 public void fly() { 
 System.out.println(this.name + "Flying οΈΏ(οΏ£οΈΆοΏ£)οΈΏ"); 
 } 
} 
public class Test { 
 public static void main(String[] args) { 
 Cat cat = new Cat("Xiao Hei"); 
 cat.eat("Cat food"); 
 Bird bird = new Bird("round"); 
 bird.fly(); 
 } 
}

Extensions originally means "extension" in English, and the inheritance of the class we write can also be understood as "extension" on the code based on the parent class
For example, the Bird class we wrote extends the fly method on the basis of Animal

If we change the name to private, the subclass cannot be accessed at this time

class Bird extends Animal { 
public Bird(String name) { 
super(name); 
} 
public void fly() { 
System.out.println(this.name + "Flying οΈΏ(οΏ£οΈΆοΏ£)οΈΏ"); 
} 
} 
// Compilation error
Error:(19, 32) java: name stay Animal Medium is private access control 

🏹 protected keyword

Just now, we found that if the field is set to private, the subclass cannot be accessed. However, setting it to public violates our original intention of "encapsulation"
The best of both worlds is the protected keyword
The fields and methods modified by protected are inaccessible to the caller of the class
For subclasses of classes and other classes in the same package, the fields and methods modified by protected are accessible

// Animal.java 
public class Animal { 
 protected String name; 
 public Animal(String name) {
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println(this.name + "I am eating" + food); 
 } 
} 
// Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void fly() { 
 // For the protected field of the parent class, the child class can access it correctly
 System.out.println(this.name + "Flying οΈΏ(οΏ£οΈΆοΏ£)οΈΏ"); 
 } 
} 
// Test.java and Animal.java are not in the same package 
public class Test { 
 public static void main(String[] args) { 
 Animal animal = new Animal("Small animals"); 
 System.out.println(animal.name); // There is a compilation error at this time. name cannot be accessed 
 } 
}

Summary: there are four access permissions for fields and methods in Java

  • private: it can be accessed inside the class, but not outside the class
  • Default (also called package access permission): it can be accessed inside a class. Classes in the same package can be accessed, but other classes cannot
  • protected: it can be accessed inside a class. Subclasses and classes in the same package can be accessed. Other classes cannot be accessed
  • public: both inside the class and the caller of the class can access it
    When and which one?
    We hope that the class should be "encapsulated" as much as possible, that is, hide the internal implementation details and only expose the necessary information to the caller of the class
    Therefore, we should use strict access rights as much as possible
    For example, if a method can use private, try not to use public. In addition, there is a simple and crude method: set all fields to private and all methods to public. However, this method is an abuse of access rights, or do you want to think carefully when writing code about who the field methods provided by this class are used for (whether it is used internally by the class, by the caller of the class, or by subclasses)

🏹 More complex inheritance relationships

In our example just now, only Animal, Cat and Bird are involved. But what if the situation is more complicated?

In this case, we may need to represent more kinds of cats~

At this time, the use of inheritance will involve a more complex system

// Animal.java 
public Animal { 
 ... 
} 
// Cat.java 
public Cat extends Animal { 
 ... 
} 
// ChineseGardenCat.java 
public ChineseGardenCat extends Cat { 
 ... 
} 
// OrangeCat.java 
public Orange extends ChineseGardenCat { 
 ... 
} 
...... 

The inheritance method just now is called multi-layer inheritance, that is, subclasses can further derive new subclasses

Always bear in mind that the classes we write are abstractions of real things. However, the projects we really encounter in the company often have complex business and may involve a series of complex concepts, which need to be represented by code. Therefore, there will be many classes written in our real projects, and the relationship between classes will be more complex
But even so, we don't want the inheritance level between classes to be too complex. Generally, we don't want more than three levels of inheritance
If there are too many inheritance levels, you need to consider refactoring the code
If you want to restrict inheritance from syntax, you can use the final keyword

🏹 final keyword

We once learned that when the final keyword modifies a variable or field, it represents a constant (which cannot be modified)

final int a = 10; 
a = 20; // Compilation error

The final keyword can also modify a class, which means that the modified class cannot be inherited

final public class Animal { 
 ... 
} 
public class Bird extends Animal { 
 ... 
} 
// Compilation error
Error:(3, 27) java: Unable to start from the final com.bit.Animal Inherit

The function of the final keyword is to restrict the class from being inherited

"Limitation" means "Inflexibility". In programming, flexibility is often not a good thing. Flexibility may mean more error prone
When a class decorated with final is inherited, it will compile and report an error. At this time, it can prompt us that such inheritance is contrary to the original intention of the class design

For example, the String class we usually use is decorated with final and cannot be inherited

⭐ combination

Similar to inheritance, composition is also a way to express the relationship between classes and achieve the effect of code reuse
For example, it means a school:

public class Student { 
 ... 
} 
public class Teacher { 
 ... 
} 
public class School { 
 public Student[] students; 
 public Teacher[] teachers; 
} 

Composition does not involve special syntax (keywords such as extends), but only takes an instance of one class as a field of another class

This is one of the common ways we design classes

Combinatorial representation has - a semantics
In the example just now, we can understand that a school "contains" several students and teachers
Inheritance represents is - a semantics
In the above example of "animal and cat", we can understand that a cat is also an animal
We should pay attention to the difference between the two semantics

⭐ polymorphic

🏹 Upward transformation

Like the following code

Bird bird = new Bird("round"); 

This code can also be written like this

Bird bird = new Bird("round"); 
Animal bird2 = bird; 

Or write it in the following way

Animal bird2 = new Bird("round"); 

At this time, bird2 is a reference to the parent class (Animal) and points to an instance of a child class (Bird). This writing is called upward transformation

[why is "upward transformation"
In object-oriented programming, for some complex scenarios (many classes, very complex inheritance relationships), the program ape will draw a UML diagram to represent the relationship between classes. At this time, the parent class is usually drawn above the child class. Therefore, we call it "upward transformation" to show the direction of the parent class

Timing of upward Transformation:

β‘  Direct assignment:

Bird bird = new Bird("round"); 

β‘‘ Method parameters:

public class Test { 
public static void main(String[] args) { 
Bird bird = new Bird("round"); 
feed(bird); 
} 
public static void feed(Animal animal) { 
animal.eat("millet"); 
} 
} 
// results of enforcement
 Yuanyuan is eating millet

At this time, the type of formal parameter Animal is Animal (parent class), which actually corresponds to the instance of Bird (child class)

β‘’ Method returns:

public class Test { 
public static void main(String[] args) { 
Animal animal = findMyAnimal(); 
} 
public static Animal findMyAnimal() { 
Bird bird = new Bird("round"); 
return bird; 
} 
}

At this time, the method findMyAnimal returns a reference of Animal (parent class), but actually corresponds to the instance of Bird (child class)

🏹 Dynamic binding

What happens when a method with the same name appears in the subclass and parent class?
Modify the previous code slightly, add the eat method with the same name to the Bird class, and add different logs in the two eat

// Animal.java 
public class Animal { 
 protected String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println("I am a small animal"); 
 System.out.println(this.name + "I am eating" + food); 
 }
 } 
// Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("I am a bird"); 
 System.out.println(this.name + "I am eating" + food); 
 } 
} 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Animal animal1 = new Animal("round"); 
 animal1.eat("millet"); 
 Animal animal2 = new Bird("flat "); 
 animal2.eat("millet"); 
 } 
} 
// results of enforcement
 I am a small animal
 Yuanyuan is eating millet
 I am a bird
 Bian Bian is eating millet

At this point, we find that:

  • Although both animal1 and animal2 are references of Animal type, animal1 points to instances of Animal type and animal2 points to instances of Bird type

  • Call eat methods for animal1 and animal2 respectively. It is found that animal1.eat() actually calls the methods of the parent class, while animal2.eat() actually calls the methods of the child class

Therefore, in Java, which code (which is the code of the parent class method or the code of the subclass method) is executed by calling a class method, depends on whether the reference refers to the parent class object or the subclass object. This procedure is determined by the program runtime (instead of the compile period), so it is called dynamic binding.

🏹 Method rewrite

For the eat method just now:
The subclass implements the method with the same name as the parent class, and the type and number of parameters are exactly the same. This situation is called override

Notes on rewriting
1. Rewriting and overloading are completely different. Don't confuse (think about it, what are the rules of overloading?)
2. Ordinary methods can be overridden, but static methods modified by static cannot be overridden
3. The access permission of the method overriding the subclass cannot be lower than that of the parent class
4. The return value type of the overridden method may not be the same as that of the parent class (but it is recommended to write it the same, except in special cases, such as covariant type)

Example of method permission: change eat of subclass to private

// Animal.java 
public class Animal { 
 public void eat(String food) { 
 ... 
 } 
} 
// Bird.java 
public class Bird extends Animal { 
 // Change the eat of the subclass to private 
 private void eat(String food) { 
 ... 
 } 
} 
// Compilation error
Error:(8, 10) java: com.bit.Bird Medium eat(java.lang.String)Cannot overwrite com.bit.Animal Medium
eat(java.lang.String) 
 Attempting to assign lower access rights; Previously public 

In addition, for overridden methods, you can use the @ Override annotation to specify them explicitly

// Bird.java 
public class Bird extends Animal { 
 @Override 
 private void eat(String food) { 
 ... 
 } 
} 

With this annotation, we can check the validity. For example, if we accidentally misspell the method name (for example, write aet), the compiler will find that there is no aet method in the parent class, and the compiler will report an error, indicating that rewriting cannot be formed
I strongly recommend explicitly adding @ Override annotation when rewriting methods in code

The difference between rewriting and overloading

🏹 Downward transformation

Upward transformation means that a child object is transformed into a parent object. Downward transformation means that a parent object is transformed into a child object. Compared with upward transformation, downward transformation is less common, but it also has certain uses

// Animal.java 
public class Animal { 
 protected String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println("I am a small animal"); 
 System.out.println(this.name + "I am eating" + food); 
 } 
} 
// Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("I am a bird"); 
 System.out.println(this.name + "I am eating" + food); 
 } 
 
 public void fly() { 
 System.out.println(this.name + "Flying");
 } 
}

Next is the familiar operation

Animal animal = new Bird("round"); 
animal.eat("millet"); 
// results of enforcement
 Yuanyuan is eating millet

Next, let's try to make Yuanyuan fly

animal.fly(); 
// Compilation error
 can't find fly method

matters needing attention
During compilation, the type of Animal is Animal. At this time, the compiler only knows that there is an eat method in this class, not a fly method. Although Animal actually refers to a Bird object, the compiler uses the type of Animal to see which methods are available. For codes such as Animal animal = new Bird("circle"),

  • The compiler checks which methods exist and looks at the type Animal
  • Bird is the type that determines whether the parent method or the subclass method is executed

If you want to achieve the effect just now, you need to transform downward

// (Bird) indicates cast
Bird bird = (Bird)animal; 
bird.fly(); 
// results of enforcement
 The circle is flying

But such a downward transformation is sometimes unreliable. For example

Animal animal = new Cat("kitten"); 
Bird bird = (Bird)animal; 
bird.fly(); 
// The execution result throws an exception
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Bird 
 at Test.main(Test.java:35) 

animal essentially refers to a Cat object and cannot be converted into a Bird object. An exception will be thrown at runtime

Therefore, in order to make the downward transformation safer, we can first determine whether animal is essentially a Bird instance, and then convert

Animal animal = new Cat("kitten"); 
if (animal instanceof Bird) { 
Bird bird = (Bird)animal; 
bird.fly(); 
} 

instanceof can determine whether a reference is an instance of a class. If so, it returns true. At this time, it is safer to carry out downward transformation

🏹 super keyword

In the previous code, due to the rewriting mechanism, the methods of the subclass are called. What if you need to call the parent method inside the subclass? You can use the super keyword

super means to get the reference of the parent class instance. It involves two common usages

1) super is used to call the constructor of the parent class (this code has been written earlier)

public Bird(String name) { 
super(name); 
} 

2) Use super to call the normal methods of the parent class

public class Bird extends Animal { 
public Bird(String name) { 
super(name); 
} 
@Override 
public void eat(String food) { 
// Modify the code so that the child calls the interface of the parent class 
super.eat(food); 
System.out.println("I am a bird"); 
System.out.println(this.name + "I am eating" + food); 
} 
} 

In this code, if you directly call eat (without super) in the eat method of the subclass, it is considered to call the eat of the subclass itself (that is, recursion). Adding the super keyword is the method of calling the parent class

Note that the functions of super and this are somewhat similar, but pay attention to the differences

🏹 summary

Polymorphism is a difficult part of object-oriented programming. We will further experience the use of polymorphism in the following abstract classes and interfaces. The focus is on the coding benefits brought by polymorphism
On the other hand, if you put aside Java, polymorphism is actually a broader concept, which is not necessarily related to the syntax of "inheritance"

  • The "dynamic polymorphism" in C + + is similar to that in Java. However, C + + also has a "static polymorphism" (template), which has nothing to do with the inheritance system
  • Polymorphism in Python embodies "duck type" and has nothing to do with inheritance system
  • There is no concept of "inheritance" in Go language, which can also express polymorphism

No matter what kind of programming language, the core of polymorphism is to let the caller not pay attention to the specific type of object, which is an important way to reduce the user's use cost

⭐ abstract class

🏹 rule of grammar

For example, in the analysis and design process of a graphics editing software, it will be found that there are some specific concepts such as circle and triangle in the problem field. They are different, but they all belong to the concept of shape. The concept of shape does not exist directly in the problem field, it is an abstract concept. Because the abstract concept has no corresponding concrete concept in the problem field, the abstract class used to represent the abstract concept cannot be instantiated.

For a method that does not actually work, we can design it as an abstract method. The class containing the abstract method is called abstract class

abstract class Shape { 
 abstract public void draw(); 
} 
  • Add the abstract keyword before the draw method to indicate that it is an abstract method. At the same time, the abstract method has no method body (without {}, it cannot execute concrete code)
  • For a class containing abstract methods, the abstract keyword must be added to indicate that it is an abstract class

matters needing attention
1) Abstract classes cannot be instantiated directly

Shape shape = new Shape(); 
// Compilation error
Error:(30, 23) java: Shape It's abstract; Cannot instantiate

2) Abstract methods cannot be private

abstract class Shape { 
abstract private void draw(); 
} 
// Compilation error
Error:(4, 27) java: Illegal modifier combination: abstract and private 

3) Abstract classes can contain other non abstract methods or fields. The rules of this non abstract method are the same as those of ordinary methods. It can be overridden or called directly by subclasses

abstract class Shape { 
abstract public void draw(); 
void func() { 
System.out.println("func"); 
} 
} 
class Rect extends Shape { 
... 
} 
public class Test { 
public static void main(String[] args) { 
Shape shape = new Rect(); 
shape.func(); 
} 
} 
// results of enforcement
func

🏹 Role of abstract classes

Abstract classes exist in order to be inherited
The abstract class itself cannot be instantiated. If you want to use it, you can only create a subclass of the abstract class. Then let the subclass override the abstract methods in the abstract class

Some students may say that ordinary classes can also be inherited and ordinary methods can also be rewritten. Why do you have to use abstract classes and abstract methods?

That's true. But using abstract classes is equivalent to a double compiler check
The scenario of using abstract classes is like the above code. The actual work should not be completed by the parent class, but by the child class. At this time, if you accidentally misuse the parent class,
Using a normal class compiler will not report an error. But if the parent class is an abstract class, it will prompt an error when instantiating, so that we can find the problem as soon as possible

The meaning of many grammars is to "prevent errors". For example, the final we used is similar. If the user does not modify the created variables, they are equivalent to constants? But in addition, final can let the compiler remind us in time when it is accidentally modified
Making full use of compiler verification is very meaningful in practical development

⭐ Interface

❀ I plan to put the content of the interface into the next blog dedicated to sorting out the interface. Please pay attention to the latest articles whenever necessary ❀

πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™
        ❀ Originality is not easy. If there is any error, please leave a message in the comment area. Thank you very much ❀
        ❀                 If you think the content is good, it's not too much to give a three company~        ❀
        ❀                              I'll pay a return visit when I see it~                                      ❀
πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™πŸŒ™

Tags: Java Back-end

Posted on Thu, 18 Nov 2021 20:14:08 -0500 by kh411dz