目录
Java 类实例
Java面向对象设计 - Java类实例
null引用类型
访问类的字段的点表示法
字段的默认初始化
Java 访问级别
Java面向对象设计 - Java访问级别
Java 导入
Java面向对象设计 - Java导入
单类型导入声明
按需导入声明
静态导入声明
例子
Java 方法
Java面向对象设计 - Java方法
方法参数
局部变量
Java 方法返回
Java面向对象设计 - Java方法返回
返回
Java 方法重载
Java面向对象设计 - Java方法重载
例子
例2
Java 实例/静态方法
Java面向对象设计 - Java实例/静态方法
例子
注意
调用方法
Java 主方法
Java面向对象设计 - Java主要方法
Java 参数传递
Java面向对象设计 - Java参数传递
例子
注意
Java varargs方法
Java面向对象设计 - Java varargs方法
声明varargs方法
例子
使用varargs方法
重载Varargs方法
Varargs方法和main()方法
Java this关键字
Java面向对象设计 - Java this关键字
什么是 this?
使用 this 来区分当前对象。
说明:
例子
注意
Java 访问级别成员
Java面向对象设计 - Java访问级别成员
例子
注意
Java final关键字
Java面向对象设计 - Java final关键字
final 变量
final 类
final 方法
Java 构造函数
Java面向对象设计 - Java构造函数
声明构造函数
使用构造函数
重载构造函数
从另一个构造函数调用构造函数
从构造函数返回
构造函数的访问级别修饰符
默认构造函数
Java 初始化块
Java面向对象设计 - Java初始化块
实例初始化块
例子
多重实例初始化
静态初始化块
Java Object类
Java面向对象设计 - Java Object类
方法
例子
什么是对象类?
Java HashCode(哈希码)
Java面向对象设计 - Java哈希码
Object的哈希码
例子
Java Object.Equals方法
Java面向对象设计 - Java Object.Equals方法
注意
Java Object.toString方法
Java面向对象设计 - Java Object.toString方法
例子
例2
注意
Java Object.Clone方法
Java面向对象设计 - Java Object.Clone方法
例子
例2
例3
Java Object.Finalize方法
Java面向对象设计 - Java Object.Finalize方法
例子
Java Immutable(不可变)对象
Java面向对象设计 - Java不可变对象
例子
注意
Java Objects类
Java面向对象设计 - Java Objects类
HashCode
equals
toString
requireNonNull
Java 类实例
Java面向对象设计 - Java类实例
以下是创建类的实例的一般语法:
new <Class Constructor>;
new
运算符后面是对构造函数的调用。
new
运算符通过分配堆上的内存来创建类的实例。以下语句创建Dog类的实例:
new Dog();
Dog()是对Dog类的构造函数的调用。
当我们不向类添加构造函数时,Java编译器为我们添加一个。
Java编译器添加的构造函数称为默认构造函数。默认构造函数不接受参数。
类的构造函数的名称与类名称相同。
new运算符为类的每个实例字段分配内存。类静态变量在创建类的实例时不会分配内存。
要访问类的实例的实例变量,我们必须有它的引用。
类的名称在Java中定义了一个新的引用类型。特定引用类型的变量可以存储相同引用类型的实例的引用。
声明一个引用变量,它将存储Dog类的实例的引用。
Dog anInstance;
Dog是类名,它也是一个引用类型,并且 anInstance
是该类型的变量。
anInstance是Dog类型的引用变量。anInstance变量可用于存储Dog类的实例的引用。
new运算符为类的新实例分配内存,并返回对该实例的引用。
我们需要将由新运算符返回的引用存储在引用变量中。
anInstance = new Dog();
null引用类型
我们可以为任何引用类型的变量分配一个空值。空值意味着引用变量是指没有对象。
Dog obj = null; // obj is not referring to any object obj = new Dog(); // Now, obj is referring to a valid Dog object
您可以使用一个空文字与比较运算符来检查是否相等和不等。
if (obj == null) { //obj is null } if (obj != null) { //obj is not null }
Java不会混合引用类型和原始类型。我们不能给一个原始类型变量赋null。
访问类的字段的点表示法
点符号用于引用实例变量。
点符号语法的一般形式是
<Reference Variable Name>.<Instance Variable Name>
obj.name引用obj引用变量引用的实例的名称实例变量。
要为名称实例变量分配值,请使用
obj.name = "Rectangle";
以下语句将name实例变量的值分配给String变量aName:
String aName = obj.name;
要引用类变量,请使用类的名称。
ClassName.ClassVariableName
例如,我们可以使用Dog.count来引用Dog类的计数类变量。
向计数类变量分配新值
Dog.count = 1;
要将count类变量的值读取到变量中
long count = Dog.count;
以下代码显示如何使用类字段
class Dog { static int count = 0; String name; String gender; } public class Main { public static void main(String[] args) { Dog obj = new Dog(); // Increase count by one Dog.count++; obj.name = "Java"; obj.gender = "Male"; obj.name = "XML"; String changedName = obj.name; } }
字段的默认初始化
类的所有字段(静态以及非静态)都将初始化为默认值。
字段的默认值取决于其数据类型。
数字字段(字节,短,char,int,long,float和double)初始化为零。布尔字段初始化为false。引用类型字段初始化为null。
下面的代码演示了字段的默认初始化。
public class Main { byte b; short s; int i; long l; float f; double d; boolean bool; String str; public static void main(String[] args) { Main obj = new Main(); System.out.println("byte is initialized to " + obj.l); System.out.println("short is initialized to " + obj.s); System.out.println("int is initialized to " + obj.i); System.out.println("long is initialized to " + obj.l); System.out.println("float is initialized to " + obj.f); System.out.println("double is initialized to " + obj.d); System.out.println("boolean is initialized to " + obj.bool); System.out.println("String is initialized to " + obj.str); } }
上面的代码生成以下结果。
Java 访问级别
Java面向对象设计 - Java访问级别
类简单名称是 class
关键字和 {
)之间的名称。
当我们通过简单的名称引用一个类时,编译器在引用类所在的同一个包中查找该类声明。
我们可以使用全名来引用一个类如下。
com.w3cschool.Dog aDog;
指定类的访问级别的一般语法是
<access level modifier>class <class name> { // Body of the class }
类声明中<access level modifier>只有两个有效值:
- no value
- public
没有值被称为包级别访问。具有包级别访问的类只能在声明它的包中访问。
具有公共访问级别修改器的类可以从应用程序中的任何包访问。
package com.w3cschool; public class Dog { }
Java 导入
Java面向对象设计 - Java导入
导入声明用于将任何类型导入编译单元。
导入声明出现在包声明之后,第一个类型声明之前。
有两种类型的导入声明:
- 单类型导入声明
- 按需导入声明
单类型导入声明
单类型导入声明用于从包导入单个类型。它是形式。
import <fully qualified name of a type>;
以下导入声明从com.w3cschool包导入Dog类:
import com.w3cschool.Dog;
单类型的导入声明只从包中导入一个类型(一个类)。
要从包中导入多个类型,请为每个类型使用单独的导入声明。
以下导入声明从pkg1包导入ClassOne,从pkg2包导入ClassTwo和ClassThree,以及从pkg3包导入ClassFour:
import pkg1.ClassOne; import pkg2.ClassTwo; import pkg2.ClassThree; import pkg3.ClassFour;
以下代码使用Dog类的完全限定名称。
public class Main{ public static void main(String[] args) { com.w3cschool.Dog jack; // Uses full qualified name for the Dog class } }
以下代码显示如何使用单类型import语句将com.w3cschool.Dog类导入到其简单名称。
修改后的Main类声明如下:
import com.w3cschool.Dog; // Import the Dog class public class Main { public static void main(String[] args) { Dog jack; // Use simple name of the Dog class } }
当编译器在语句中遇到Dog类的简单名称时,就像
Dog jack;
它将通过所有导入声明将简单名称解析为完全限定名称。
当它尝试解析简单名称Dog时,它会找到导入声明import com.java2s.Dog,它将导入Dog类。
它假定您打算在上面的语句中使用简单名称Dog时使用com.java2s.Dog类。
编译器用以下语句替换上面的语句:
com.w3cschool.Dog jack;
导入声明允许您在代码中使用类型的简单名称,从而使您的代码更具可读性。
当编译代码时,编译器用其完全限定名替换类型的简单名称。
它使用导入声明将类型的简单名称转换为其完全限定名称。
按需导入声明
按需导入声明使用一个导入声明从包导入多个类型。
按需导入声明的语法是
import <package name>.*;
这里,包名称后面跟着一个点和一个星号(*)。
例如,以下需要的import-on-demand声明从com.java2s包导入所有类型:
import com.w3cschool.*;
您可以使用按需导入声明重写Main类的代码。
import com.w3cschool.*; public class Main { public static void main(String[] args) { Dog jack; // Use simple name of the Dog class } }
静态导入声明
静态导入声明将类型的静态成员(变量/方法)导入到编译单元中。
静态导入声明有两种风格:单静态导入和静态 - 按需导入。
单静态导入声明从类型中导入一个静态成员。静态导入请求声明导入类型的所有静态成员。
静态导入声明的一般语法如下:
单静态导入语句:
import static <package name>.<type name>.<static member name>;
静态导入需求声明:
import static <package name>>.<type name>.*;
系统是java.lang包中的一个类,它有一个名为out的静态变量。
当你使用System.out时,我们指的是System类中的静态变量。
我们可以使用静态导入声明从System类导入out静态变量,如下所示:
import static java.lang.System.out;
下面的代码导入了System类的out静态变量。
import static java.lang.System.out; public class StaticImportTest { public static void main(String[] args) { out.println("Hello static import!"); } }
上面的代码生成以下结果。
java.lang包中的Math类有许多实用程序常量和静态方法。
例如,它有一个类变量名为PI。
要使用Math类的任何静态变量或方法,我们需要使用类名Math来限定它们。
例如,您可以将PI静态变量称为Math.PI,将sqrt()方法称为Math.sqrt()。
我们可以使用以下static-import-on-demand声明来导入Math类的所有静态成员:
import static java.lang.Math.*;
例子
以下代码通过导入其静态成员来演示使用Math类。
import static java.lang.Math.PI; import static java.lang.Math.sqrt; import static java.lang.System.out; public class Main { public static void main(String[] args) { double radius = 6.9; double area = PI * radius * radius; out.println("Value of PI is: " + PI); out.println("Radius of circle: " + radius); out.println("Area of circle: " + area); out.println("Square root of 2.0: " + sqrt(2.0)); } }
上面的代码生成以下结果。
以下是关于静态导入声明的一些重要规则。
- 如果导入具有相同简单名称的两个静态成员,一个使用单静态导入声明,另一个使用静态import-on-demand声明,则使用单静态import声明导入的静态成员优先。
- 使用单静态导入声明来导入具有相同简单名称的两个静态成员是不允许的。
- 如果使用单静态导入声明导入静态成员,并且在同一类中存在具有相同名称的静态成员,则使用类中的静态成员。
Java 方法
Java面向对象设计 - Java方法
类中的方法定义对象的行为。
方法是一个命名的代码块。
调用方法的代码是方法的调用者。
可选地,方法可以接受来自呼叫者的输入值,并且它可以向呼叫者返回值。
输入值的列表称为参数。方法可以具有零参数。如果一个方法有零个参数,我们说该方法没有任何参数或方法不采取任何参数。
方法总是在类的主体内定义。
方法声明的一般语法是形式
<modifiers> <return type> <method name> (<parameters list>) <throws clause>{ // Body of the method goes here }
- <modifiers>是修饰符的可选列表;
- <return type>是从方法返回的值的数据类型;
- <method name>是方法的名称。
方法参数
方法名称后面是一对开头和结尾的括号。
或者,我们可以在括号内为方法指定一个或多个参数。
多个参数用逗号分隔。
右括号可以后跟一个throws子句。
最后,我们为方法的开头和结尾括号指定代码。
方法声明中的四个部分是必需的:
- 返回类型,
- 方法名称,
- 一对开和关括号,和
- 一对开和关大括号。
以下是一个方法的示例:
- 它被命名为add;
- 它需要两个参数类型int命名为n1和n2,和
- 它返回它们的和:
int add(int n1, int n2) { int sum = n1 + n2; return sum; }
有时,方法不会向其调用者返回值。如果方法不向调用者返回任何值,则使用关键字void作为返回类型。
方法名称必须是有效的Java标识符。
通常,Java方法从小写开始,随后使用字冠。
例如,getName,setName,getDogCount和createDog是有效的方法名称。
方法可以从其调用者获取输入值。参数用于从调用者获取输入值。
参数由两部分组成:数据类型和变量名称。方法参数是变量声明。
变量用于保存从方法的调用者传递的输入值。逗号分隔方法的两个参数。
在以下示例中,add方法声明两个参数n1和n2。两个参数都是int数据类型。
int add(int n1, int n2) { int sum = n1 + n2; return sum; }
当调用add方法时,调用者必须传递两个int值。
从调用者传递的第一个值存储在n1中,从调用者传递的第二个值存储在n2中。
参数n1和n2也称为形式参数。
方法通过其在特定上下文中的签名唯一地标识。方法的签名是其名称及其参数的数量,类型和顺序的组合。
局部变量
在方法,构造函数或块中声明的变量称为局部变量。
在方法中声明的局部变量仅在执行方法的持续时间内存在。
因为局部变量只存在一个临时持续时间,所以不能在方法,构造函数或声明它的块之外使用。
方法的形式参数被视为局部变量。当调用方法时,在方法的正文执行之前,它们用实际的参数值初始化。
您需要遵守关于局部变量的使用的以下规则。
- 默认情况下不会初始化局部变量。
- 在向程序分配值之前,不能在程序中访问局部变量。
- 局部变量可以在方法体中的任何地方声明。但是,它必须在使用之前声明。
- 局部变量隐藏实例变量的名称和具有相同名称的类变量。
Java 方法返回
Java面向对象设计 - Java方法返回
修饰符,返回类型和参数名称不是签名的一部分。
方法的签名唯一地标识类中的方法。不允许在具有相同签名的类中有多个方法。
方法的代码在方法的主体中指定,方法的主体用大括号括起来。
使用其名称及其参数的值(如果有)在括号中调用方法。
要调用add方法,请使用以下语句:
add(10, 12);
上述对add方法的调用分别将10和12作为参数n1和n2的值。
用于调用add方法的两个值(10和12)称为实际参数。
Java将实际参数复制到形式参数,然后再在方法体内执行代码。
return语句用于从方法返回值。它以return关键字开始。
如果方法返回一个值,则return关键字后面必须跟有一个表达式,该表达式将计算返回的值。
如果方法不返回值,则其返回类型指定为void。如果方法的返回类型为void,则该方法不必包括return语句。
如果一个带有void返回类型的方法想要包括一个return语句,return关键字后面不能跟任何表达式; return关键字后紧跟一个分号,以标记语句的结尾。
返回
return语句将控制权返回给方法的调用者。返回语句是在方法的主体中执行的最后一个语句。
要捕获方法调用的值,请在可以使用值的任何位置使用方法调用表达式。
例如,以下代码将从add方法返回的值分配给变量调用sum:
int sum = add(10, 12); // sum variable will be assigned 22
以下方法声明为方法printMessage;
void printMessage() { System.out.println("test"); }
printMessage方法将void指定为其返回类型,这意味着它不会向其调用者返回值。
它不指定任何参数,这意味着它不接受来自其调用者的任何输入值。
要调用printMessage方法,请编写以下语句:
printMessage();
由于printMessage()方法不返回任何值,因此您不能将该方法的调用用作任何需要值的表达式的一部分。
当方法的返回类型为void时,没有必要使用return语句,因为我们没有从方法返回的值。
Java 方法重载
Java面向对象设计 - Java方法重载
在同一类中具有多个具有相同名称的方法称为方法重载。
类中具有相同名称的方法可以是声明的方法,继承的方法或两者的组合。
重载方法必须具有不同数量的参数,不同类型的参数或两者。
方法的返回类型,访问级别和throws子句对使其成为重载方法没有任何影响。
import java.io.IOException; class MyClass { public void m1(int a) { // Code goes here } public void m1(int a, int b) { // Code goes here } public int m1(String a) { // Code goes here return 0; } public int m1(String a, int b) throws IOException { // Code goes here return 0; } }
例子
下面的代码显示了如何使用重载。
public class Main { public double add(int a, int b) { System.out.println("Inside add(int a, int b)"); double s = a + b; return s; } public double add(double a, double b) { System.out.println("Inside add(double a, double b)"); double s = a + b; return s; } public static void main(String[] args) { Main ot = new Main(); int i = 1; int j = 1; double d1 = 10.42; float f1 = 22.3F; float f2 = 24.5F; short s1 = 22; short s2 = 26; ot.add(i, j); ot.add(d1, j); ot.add(i, s1); ot.add(s1, s2); ot.add(f1, f2); ot.add(f1, s2); } }
上面的代码生成以下结果。
例2
有时,重载方法和自动类型扩展可能会混淆编译器,导致编译器错误。
class Adder { public double add(int a, double b) { return a + b; } public double add(double a, int b) { return a + b; } } public class Main { public static void main(String[] args) { Adder a = new Adder(); // double d = a.add(2, 3); // A compile-time error double d1 = a.add((double) 2, 3); // OK. Will use add(double, int) double d2 = a.add(2, (double) 3); // OK. Will use add(int, double) } }
Java 实例/静态方法
Java面向对象设计 - Java实例/静态方法
类可以有两种类型的方法:实例方法和类方法。实例方法和类方法也分别称为非静态方法和静态方法。
实例方法用于实现类的实例的行为。实例方法只能在类的实例的上下文中调用。
类方法用于实现类本身的行为。类方法总是在类的上下文中执行。
静态修饰符用于定义类方法。方法声明中缺少静态修饰符,使得该方法成为一个实例方法。
例子
以下是声明一些静态和非静态方法的示例:
// A static or class method static void aClassMethod() { } // A non-static or instance method void anInstanceMethod() { }
注意
当调用类的静态方法时,该类的实例可能不存在。因此,不允许从静态方法内部引用实例变量。
类定义一加载到内存中,类变量就存在。类定义在创建类的第一个实例之前加载到内存中。
类方法或静态方法只能引用类的变量或类的静态变量。实例方法或非静态方法可以引用类变量以及类的实例变量。
以下代码演示了在方法中可访问的类字段的类型。
public class Main { static int m = 100; // A static variable int n = 200; // An instance variable // Declare a static method static void printM() { /* * We can refer to only static variable m in this method because you are * inside a static method */ System.out.println("printM() - m = " + m); } // Declare an instance method void printMN() { /* We can refer to both static and instance variables m and n in this method */ System.out.println("printMN() - m = " + m); System.out.println("printMN() - n = " + n); } }
调用方法
在方法的主体中执行代码称为调用(或调用)方法。
实例方法和类方法以不同方式调用。
使用点表示法在类的实例上调用实例方法。
<instance reference>.<instance method name>(<actual parameters>)
在调用该类的实例方法之前,我们必须引用一个类的实例。
以下代码显示如何调用Main类的printMN()实例方法:
// Create an instance of Main class and // store its reference in mt reference variable Main mt = new Main(); // Invoke the printMN() instance method using the mt reference variable mt.printMN();
要调用类方法,请使用带有名称的点表示法。
下面的代码调用Main类的printM()类方法:
// Invoke the printM() class method Main.printM();
属于一个类的属性也属于该类的所有实例。我们还可以使用该类的实例的引用来调用类方法。
Main mt = new Main(); mt.printM(); // Call the class method using an instance mt
使用类名调用类方法比使用实例引用更直观。
Java 主方法
Java面向对象设计 - Java主要方法
让我们讨论我们用来运行我们的类的main()方法。
main()方法声明如下:
public static void main(String[] args) { }
在main()方法的声明中使用了两个修饰符public和static。
public修饰符使得它可以从应用程序中的任何地方访问,只要它被声明的类是可访问的。
静态修饰符使它成为一个类方法,因此可以使用类名来调用它。
它的返回类型是void,这意味着它不返回一个值给它的调用者。
它的名称是main,它接受一个类型为String array(String [])的参数。
main()方法是Java应用程序的入口方法。例如,您可以使用以下命令运行Main类:
java com.w3cschool.Main
当你运行一个类时,JVM调用main()方法。
Java 参数传递
Java面向对象设计 - Java参数传递
Java支持两种数据类型:基本数据类型和引用数据类型。
原始数据类型是一个简单的数据结构,它只有一个与之相关的值。引用数据类型是一个复杂的数据结构,它表示一个对象。
原始数据类型的变量将该值直接存储在其存储器地址处。
使用对象和引用变量时,事情会有所不同。
Java中的所有参数都通过值传递。
当参数是原始数据类型时,实际参数的值将复制到参数中。
对方法主体中的参数值进行的任何更改只会更改形式参数的副本,而不会更改实际参数的值。
当参数通过参考值传递时,存储在实际参数中的参考被复制到形式参数。实际参数和形式参数都指向内存中的相同对象。
您可以将另一个对象的引用分配给方法主体中的形式参数。
例子
下面的代码演示了Java中的引用传递机制。
class Phone { public String model = "Unknown"; public int year = 2014; public double price = 0.0; } public class Main { public static void main(String[] args) { Phone myPhone = new Phone(); myPhone.model = "iPhone"; myPhone.year = 2009; myPhone.price = 16000.0; System.out.println("#1: model = " + myPhone.model + ", year = " + myPhone.year + ", price = " + myPhone.price); Main.test(myPhone); System.out.println("#4: model = " + myPhone.model + ", year = " + myPhone.year + ", price = " + myPhone.price); } public static void test(Phone xPhone) { System.out.println("#2: model = " + xPhone.model + ", year = " + xPhone.year + ", price = " + xPhone.price); // Let"s make xyCar refer to a new object xPhone = new Phone(); System.out.println("#3: model = " + xPhone.model + ", year = " + xPhone.year + ", price = " + xPhone.price); } }
上面的代码生成以下结果。
注意
当引用类型参数传递给方法时,形式参数可以访问对象,实际参数可以访问该对象。
形式参数可以通过直接更改实例变量的值或通过调用对象上的方法来修改对象。
通过形式参数对对象进行的任何修改都可以通过实际参数立即可见,因为它们都保存对内存中同一对象的引用。
形式参数本身可以被修改以引用方法内的另一个对象。
要禁用将引用类型形式参数更改为引用不同对象的方法,请在引用类型形式参数声明中使用关键字final
。
public class Main { public static void main(String[] args) { Phone myPhone = new Phone(); myPhone.model = "iPhone"; myPhone.year = 2009; myPhone.price = 16000.0; Main.test(myPhone); } public static void test(final Phone xPhone) { System.out.println("#2: model = " + xPhone.model + ", year = " + xPhone.year + ", price = " + xPhone.price); // Let"s make xyCar refer to a new object //xPhone = new Phone(); } } class Phone { public String model = "Unknown"; public int year = 2014; public double price = 0.0; }
上面的代码生成以下结果。
Java varargs方法
Java面向对象设计 - Java varargs方法
术语“varargs"是“可变长度参数"的缩写。
varargs声明一个接受可变数量的参数(或参数)的方法或构造函数。
声明varargs方法
要声明varargs,在方法参数的数据类型之后添加一个省略号 ...
。
下面的代码显示了一个带有一个可变长度参数num的max()方法声明,它是int数据类型。
public static int max(int... num) { }
省略号之前和之后添加空格是可选的。
varargs方法可以有多个参数。下面的代码显示aMethod()接受三个参数,其中一个是可变长度参数:
public static int aMethod(String str, double d1, int...num) { }
varargs方法最多可以有一个可变长度参数。
varargs方法的variable-length参数必须是参数列表中的最后一个参数。
void m2(String str, int...n1) { }
例子
让我们重写max()方法,使其成为varargs方法:
public class Main { public static int max(int... num) { int max = Integer.MIN_VALUE; for (int i = 0; i < num.length; i++) { if (num[i] > max) { max = num[i]; } } return max; } }
使用varargs方法
我们可以使用for循环来处理可变长度参数的参数列表。
length属性给出了为variable-length参数传递的值的数量。
要获取可变长度参数中的第n个值,您需要使用varArgsName [n-1]。
我们可以使用foreach循环来处理可变长度参数。
public class Main { public static int max2(int... num) { int max = Integer.MIN_VALUE; for (int currentNumber : num) { if (currentNumber > max) { max = currentNumber; } } return max; } }
我们可以调用Main.max()方法如下:
int max1 = Main.max(1, 8); int max2 = Main.max(1, 1, 3);
对于方法中的可变长度参数,可以使用零个或多个参数。以下代码是对max()方法的有效调用:
int max = Main.max(); // Passing no argument is ok
max()方法的以下声明将强制其调用者传递至少两个整数:
// Argumenets n1 and n2 are mandatory public static int max(int n1, int n2, int... num) { }
编译器会将前两个参数n1和n2视为强制性参数,将第三个参数num作为可选参数。
public class Main { public static int max(int n1, int n2, int... num) { // Initialize max to the maximum of n1 and n2 int max = (n1 > n2 ? n1 : n2); for (int i = 0; i < num.length; i++) { if (num[i] > max) { max = num[i]; } } return max; } public static void main(String[] args) { System.out.println(max(7, 9)); System.out.println(max(7, 9, 10)); System.out.println(max(7, 9, 10, 13)); } }
上面的代码生成以下结果。
重载Varargs方法
方法的相同重载规则适用于varargs方法。
我们可以使用可变长度参数重载一个方法,只要方法的参数在类型,顺序或数字上不同。
例如,以下是一个重载的max()方法的有效示例:
public class Main { public static int max(int x, int y) { } public static int max(int...num) { } }
考虑下面的代码:
int max = Main.max(12, 13); // which max() will be called?
Java将调用max(int x,int y)。 Java首先尝试使用对参数数量的精确匹配来找到方法声明。如果没有找到完全匹配,它将使用可变长度参数查找匹配。
如果varargs方法重载,Java使用该方法的更特定版本,而不使用varargs方法。 java使用varargs方法作为解决方法调用的最后手段。
方法本身的重载可能是有效的。但是,调用它可能会导致问题。
public class Main { public static int max(int... num) { return 0; } public static int max(double... num) { return 0; } }
下面的代码调用哪个max()?
int max = Main.max(); // Which max() to call?
上面的语句将生成一个编译时间错误。
Varargs方法和main()方法
main()方法的签名必须是main(String [] args)。
Main类的main()方法的以下声明是有效的。
public class Main { public static void main(String... args) { System.out.println("Hello from varargs main()..."); } }
Java this关键字
Java面向对象设计 - Java this关键字
什么是 this?
Java有一个名为 this
的关键字。它是对类的当前实例的引用。
它只能在实例的上下文中使用。
以下代码显示如何使用this
关键字。
public class Main { int varA = 1; int varB = varA; // Assign value of varA to varB int varC = this.varA; // Assign value of varA to varC }
当实例变量或类变量被具有相同名称的另一个变量隐藏时,我们需要使用关键字this
限定一个实例变量,并使用类名称定义一个类变量。
使用 this 来区分当前对象。
Java 中为解决变量的命名冲突和不确定性问题,引入关键字 this 代表其所在方法的当前对象的引用:
- 构造方法中指该构造器所创建的新对象;
- 方法中指调用该方法的对象;
- 在类本身的方法或构造器中引用该类的实例变量(全局变量)和方法。
this 只能用在构造器或者方法中,用于获得调用当前的构造器方法的对象引用。可以和任何的对象引用一样来处理这个this对象。
说明:
- 当实例变量和局部变量重名,JAVA 平台会按照先局部变量、后实例变量的顺序寻找。即,方法中使用到的变量的寻找规律是先找局部变量,再找实例变量。如果没用找到,将会有一个编译错误而无法通过编译。
- 如果使用 this.a,则不会在方法(局部变量)中寻找变量 a ,而是直接去实例变量中去寻找,如果寻找不到,则会有一个编译错误。
- 在一个方法内,如果没有出现局部变量和实例变量重名的情况下,是否使用 this 关键字是没有区别的。
- 在同一个类中,Java 普通方法的互相调用可以省略
this.
,而直接使用方法名 + 参数。因为 Java 编译器会帮我们加上。
例子
下面的代码显示了如何使用 this 关键字来引用一个实例变量,它的名字被一个局部变量隐藏。
public class Main { int num = 2014; // An instance variable void printNum(int num) { System.out.println("Parameter num: " + num); System.out.println("Instance variable num: " + this.num); } public static void main(String[] args) { Main tt6 = new Main(); tt6.printNum(2000); } }
上面的代码生成以下结果。
注意
下面的代码显示了如何使用 this 关键字来引用一个实例变量,它的名字被一个局部变量隐藏。
例如,以下代码非常常见:
Student 类声明了一个实例变量 id。在其 setId() 方法中,它还命名参数 id,并使用 this.id 引用实例变量。
它还使用 this.id 在其 getId() 方法中引用实例变量id。
public class Student { private int id; // An instance variable public void setId(int id) { this.id = id; } public int getId() { return this.id; } }
我们可以使用关键字 this
来限定实例方法名称。以下代码显示使用关键字 this 调用 m2() 方法的 m1() 方法。
public class Main { void m1() { // Invoke the m2() method this.m2(); // same as "m2();" } void m2() { // do something } }
Java 访问级别成员
Java面向对象设计 - Java访问级别成员
类可以是public或default(或包级别)。
类成员的访问级别确定程序的哪个区域可以访问它。以下四个访问级别修饰符之一可以用于类成员:
- public
- private
- protected
- Default 或者 package-level访问
前三种类型的访问级别使用以下三个关键字之一指定:public,private或protected。
第四种类型称为默认访问级别(或包级别),并且通过不使用访问修饰符来指定。
如果使用 public
关键字将类成员声明为public,则可以从Java代码中的任何位置访问它,如果类本身是可访问的。
如果使用 private
关键字将类成员声明为private,则只能在声明类的主体内访问,而在其他任何地方都不能访问。
如果使用 protected
关键字将类成员声明为protected,则可以从同一个包或从类的后代访问,即使后代位于不同的包中也是如此。
如果我们不为类成员使用任何访问级别修改器,则它具有包级别访问权限。具有包级别访问权限的类成员可以从同一个包访问。
类成员的访问级别可以从最严格到最不严格,作为private,package-level,protected和public。
例子
以下代码显示如何使用不同的访问级别:
public class Main { private int num1; // private access level int num2; // package-level access protected int num3; // protected access level public int num4; // public access level public static int count = 1; // public access level // private access level private void m1() { } // package-level access void m2() { } // protected access level protected void m3() { } // public access level public void m4() { } // private access level private static void doSometing() { } }
注意
可以为类的实例和静态成员指定访问级别。
它是一个约定,指定访问级别修改器作为声明中的第一个修饰符。
要声明静态公共字段,请首先使用 public
修饰符,然后使用 static
修饰符作为约定。
我们必须考虑类及其成员的访问级别,以确定类成员是否可访问。
以下代码显示了如何在创建Java bean时使用访问级别修饰符。
class Account { private double balance; public double getBalance() { return this.balance; } public int save(double amount) { if (amount < 0.0 || Double.isNaN(amount) || Double.isInfinite(amount)) { System.out.println("Invalid credit amount: " + amount); return -1; } this.balance = this.balance + amount; return 1; } public int spend(double amount) { if (amount < 0.0 || Double.isNaN(amount) || Double.isInfinite(amount)) { System.out.println("Invalid debit amount: " + amount); return -1; } if (this.balance < amount) { System.out.println("Insufficient fund. Debit attempted: " + amount); return -1; } this.balance = this.balance - amount; return 1; } } public class Main { public static void main(String[] args) { Account ac = new Account(); double balance = ac.getBalance(); System.out.println("Balance = " + balance); ac.save(2); ac.spend(1); balance = ac.getBalance(); System.out.println("Balance = " + balance); // Attempt to credit and debit invalid amounts ac.save(-2); ac.spend(Double.POSITIVE_INFINITY); balance = ac.getBalance(); System.out.println("Balance = " + balance); // Attempt to debit more than the balance ac.spend(200.00); balance = ac.getBalance(); System.out.println("Balance = " + balance); } }
上面的代码生成以下结果。
Java final关键字
Java面向对象设计 - Java final关键字
final关键字不允许修改或替换其原始值或定义。
final关键字可以在以下三个上下文中使用:
- 变量声明
- 类声明
- 方法声明
final 变量
如果一个变量被声明为final,它只能被赋值一次。最终变量的值在设置后不能修改。
变量声明包括局部变量的声明,方法/构造函数的形式参数,实例变量和类变量。
变量声明包括局部变量的声明,方法/构造函数的形式参数,实例变量和类变量。
final int YES = 1;
我们可以只设置一次final变量的值。
有两种方法来初始化final变量:
- 在声明时予以初始化。
- 将其初始化延迟到稍后的时间。
但是,我们必须在第一次读取最终变量之前初始化它。
- final局部变量
你可以声明一个局部变量final。如果将局部变量声明为空的最终变量,则必须在使用前初始化它。
- final参数
我们可以声明一个参数final。当调用方法或构造函数时,参数将使用实际参数的值自动初始化。
因此,您不能更改方法或构造函数体内的最终形式参数的值。
- final实例变量
我们可以声明一个实例变量final和blank final。
空白最终实例变量必须初始化一次,并且只有在调用类的任何构造函数时才初始化一次。
- final类变量
我们可以声明一个类变量final和blank final。我们必须在其中一个静态初始化器中初始化一个空的最终类变量。
- final引用变量
引用变量存储对象的引用。最终引用变量意味着,一旦引用一个对象(或null),它就不能被修改以引用另一个对象。
以下代码显示了test2()方法的最终形式参数x:
public void test2(final int x) {
如果我们有一个类的多个静态初始化器,我们必须在一个静态初始化器中初始化所有空的最终类变量一次。
public class Main { public static final int YES = 1; public static final int NO = 2; public static final String MSG; static { MSG = "final static variable"; } }
final 类
如果一个类被声明为final,它不能被扩展(或子类化)。
final 方法
如果一个方法声明为final,它不能在包含该方法的类的子类中重新定义(覆盖或隐藏)。
Java 构造函数
Java面向对象设计 - Java构造函数
构造函数是用于在对象创建后立即初始化对象的代码块。
构造函数的结构看起来类似于一个方法。
声明构造函数
构造函数声明的一般语法是
<Modifiers> <Constructor Name>(<parameters list>) throws <Exceptions list> { }
构造函数的声明以修饰符开头。
构造函数可以将其访问修饰符作为public,private,protected或package-level(无修饰符)。
构造函数名称与类的简单名称相同。
构造函数名称后面是一对括号,可能包括参数。
可选地,右括号后面可以是关键字throws,其后面是逗号分隔的异常列表。
以下代码显示了声明类Test的构造函数的示例。类的名称和构造函数的名称必须匹配。
public class Test { public Test() { // Code goes here } }
与方法不同,构造函数没有返回类型。
使用构造函数
我们使用一个带有new操作符的构造函数来在创建新实例之后初始化类的实例。
new运算符创建一个对象,构造函数初始化该对象。
以下语句使用Test类的构造函数来初始化Test类的对象:
Test t = new Test();
以下代码显示了如何使用构造函数
class Cat { public Cat() { System.out.println("in constructor..."); } } public class Main { public static void main(String[] args) { // Create a Cat object and ignore its reference new Cat(); // Create another Cat object and store its reference in c Cat c = new Cat(); } }
上面的代码生成以下结果。
重载构造函数
一个类可以有多个构造函数。它们称为重载构造函数。
如果一个类有多个构造函数,它们的数量,顺序或参数类型都必须与其他构造函数不同。
下面的代码声明两个构造函数。一个构造函数不接受参数,另一个接受String参数。
class Car { // Constructor #1 public Car() { System.out.println("A car is created."); } // Constructor #2 public Car(String name) { System.out.println("A car named " + name + " is created."); } } public class Main { public static void main(String[] args) { Car d1 = new Car(); // Uses Constructor #1 Car d2 = new Car("My Car"); // Uses Constructor #2 } }
上面的代码生成以下结果。
每个对象创建表达式调用一次构造函数。
我们可以在对象创建的过程中只执行一个构造函数的代码一次。
从另一个构造函数调用构造函数
构造函数可以调用同一类的另一个构造函数。让我们考虑下面的Test类。它声明两个构造函数;一个不接受参数,一个接受一个int参数。
我们必须使用关键字this从另一个构造函数调用构造函数。
下面的代码使用语句“this(1);”从没有参数的构造函数中调用具有int参数的构造函数。
class Test { Test() { this(1); // OK. Note the use of the keyword this. } Test(int x) { } }
如果构造函数调用另一个构造函数,它必须是构造函数体中的第一个可执行语句。
构造函数不能调用自身,因为它将导致递归调用。
从构造函数返回
构造函数在其声明中不能有返回类型。
我们可以在构造函数体中使用没有返回表达式的return语句。
当一个构造函数中的返回语句被执行时,控制返回给调用者,忽略构造函数的其余代码。
以下代码显示了在构造函数中使用return语句的示例。
class Test { public Test(int x) { if (x < 0) { return; } System.out.println("here"); } }
构造函数的访问级别修饰符
构造函数的访问级别决定了可以在对象创建表达式中使用该构造函数的程序。
我们可以为构造函数指定四个访问级别之一:public,private,protected和package-level。
下面的代码声明了Test类的四个构造函数。
// Class Test has public access level public class Test { // Constructor #1 - Package-level access Test() { } // Constructor #2 - public access level public Test(int x) { } // Constructor #3 - private access level private Test(int x, int y) { } // Constructor #4 - protected access level protected Test(int x, int y, int z) { } }
具有公共访问级别的构造函数可以在程序的任何部分中使用。
具有私有访问级别的构造函数只能在声明它的同一类中使用。
具有受保护访问级别的构造函数可以在具有在其中声明类的相同包的程序中以及在任何包中的任何后代类内使用。
具有包级访问权限的构造函数可以在声明其类的同一个包中使用。
默认构造函数
具有包级访问权限的构造函数可以在声明其类的同一个包中使用。
编译器添加的构造函数称为默认构造函数。
默认构造函数没有任何参数。
默认构造函数也称为无参数构造函数。
如果类已经有一个构造函数,编译器不会添加任何构造函数。
Java 初始化块
Java面向对象设计 - Java初始化块
实例初始化块
实例初始化块用于初始化类的对象。
一个实例初始化程序只是一个类的代码块,但在任何方法或构造函数之外。
实例初始值设定程序没有名称。它的代码只是放置在一个开放大括号和闭包。
例子
下面的代码展示了如何为Test类声明一个实例初始化器。
注意,实例初始化程序在实例上下文中执行,并且关键字this在实例初始化程序中可用。
class Test { private int num; // An instance initializer { this.num = 101; /* Other code for the instance initializer*/ } /* Other code for Test class*/ }
多重实例初始化
我们可以有一个类的多个实例初始化器。对于我们创建的每个对象,它们都以文本顺序自动执行。
所有实例初始值的代码在任何构造函数之前执行。
下面的代码演示了构造函数和实例初始化函数的执行顺序。
public class Main { { System.out.println("Inside instance initializer 1."); } { System.out.println("Inside instance initializer 2."); } public Main() { System.out.println("Inside no-args constructor."); } public static void main(String[] args) { Main m = new Main(); } }
上面的代码生成以下结果。
实例初始化程序不能有return语句。
静态初始化块
静态初始化块也称为静态初始化器。它类似于实例初始化块。
它用于初始化一个类。每个对象执行一个实例初始化器,而当类定义被加载到JVM中时,只对一个类执行一次静态初始化器。
我们需要在其声明的开头使用static关键字。
我们可以在类中有多个静态初始化器。所有静态初始化器都按文本出现的顺序执行,并在任何实例初始化器之前执行。
以下代码演示了何时执行静态初始化程序。
public class Main { private static int num; {// An instance initializer System.out.println("Inside instance initializer."); } // A static initializer. Note the use of the keyword static below. static { num = 2014; System.out.println("Inside static initializer."); } // Constructor public Main() { System.out.println("Inside constructor."); } public static void main(String[] args) { System.out.println("Inside main() #1. num: " + num); // Declare a reference variable of the class Main si; System.out.println("Inside main() #2. num: " + num); new Main(); // Create an object System.out.println("Inside main() #3. num: " + num); new Main();// Create another object } }
上面的代码生成以下结果。
静态初始化器不能抛出检查的异常,它不能有一个return语句。
Java Object类
Java面向对象设计 - Java Object类
Java在java.lang包中有一个Object类。
所有Java类都直接或间接扩展Object类。
所有Java类都是Object类的子类Object类是所有类的超类。
Object类本身没有超类。
Object类的引用变量可以保存任何类的对象的引用。
以下代码声明对象类型的引用变量obj:
Object obj;
方法
Object类有九个方法,可以在Java中的所有类中使用。
- public String toString()
它是实现在Object类中,我们可以自定义它。
它返回对象的字符串表示形式。
通常,它用于调试目的。 - public boolean equals(Object obj)
它在Object类中实现,我们可以自定义它。
它用于比较两个对象的相等性。 - public int hashCode()
它在Object类中实现,我们可以自定义它。
它返回对象的哈希码(整数)值。 - protected Object clone() throws
CloneNotSupportedException
它不在Object类中实现,我们可以通过覆盖克隆方法来自定义它。
它用于创建对象的副本。 - protected void finalize() throws Throwable
它不是在Object类中实现,我们可以自定义它
它在对象被销毁之前被垃圾收集器调用。 - public final Class getClass()
它在Object类中实现,我们不能自定义它。
它返回对对象的Class对象的引用。 - public final void notify()
它是在Object类中实现的,我们不能自定义它。
此方法通知对象的等待队列中的一个线程。 - public final void notifyAll()
它是在Object类中实现的,我们不能自定义它。
此方法通知对象的等待队列中的所有线程。 - public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait (long timeout, int nanos) throws InterruptedException
它是在Object类中实现的,我们不能自定义它。
使对象的等待队列中的线程等待,无论是否超时。
例子
以下代码显示如何重新实现Object类的toString()方法。
public class Test { public String toString() { return "Here is a string"; } }
什么是对象类?
Java中的每个对象都属于一个类。
Object类的getClass()方法返回Class对象的引用。
以下代码显示了如何获取Cat对象的Class对象的引用:
Cat c = new Cat(); Class catClass = c.getClass();
Class类是通用的,其形式类型参数是由其对象表示的类的名称。
我们可以使用泛型重写上面的语句。
Class<Cat> catClass = c.getClass();
Java HashCode(哈希码)
Java面向对象设计 - Java哈希码
Object的哈希码
哈希码是一个整数值。计算整数的算法称为散列函数。
Java使用散列码从基于散列的集合中有效地检索数据。
Object类有一个返回int的hashCode()方法,它是对象的哈希码。
该方法的默认实现通过将对象的内存地址转换为整数来计算对象的哈希码。
下面是我们在类中重写hashCode()方法时必须遵循的规则。
假设有两个对象引用,x和y。
如果x.equals(y)返回true,x.hashCode()必须返回一个整数,它等于y.hashCode()。
如果两个对象使用equals()方法相等,则它们必须具有相同的哈希码。
如果x.hashCode()等于y.hashCode(),则x.equals(y)不必返回true。
如果对同一个对象多次调用hashCode()方法,则该方法必须返回相同的整数值。
如果一个类覆盖这两个方法中的任何一个,它必须覆盖该类的对象在基于散列的集合中正确工作。
Java 7添加了一个实用程序类java.lang.Objects。它包含一个hash()方法,用于计算任意数量值的哈希码。
从java 7,使用Objects.hash()方法来计算对象的哈希码。
例子
以下代码显示如何计算哈希值。
class Book { private String title; private String author; public int hashCode() { int hash = 37; int code = 0; // Use title code = (title == null ? 0 : title.hashCode()); hash = hash * 59 + code; // Use author code = (author == null ? 0 : author.hashCode()); hash = hash * 59 + code; return hash; } }
Java Object.Equals方法
Java面向对象设计 - Java Object.Equals方法
以下代码显示如何实现equals()和hashCode()方法
class Point { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } /* implement the equals() method */ public boolean equals(Object otherObject) { // Are the same? if (this == otherObject) { return true; } // Is otherObject a null reference? if (otherObject == null) { return false; } // Do they belong to the same class? if (this.getClass() != otherObject.getClass()) { return false; } // Get the reference of otherObject in a SmartPoint variable Point otherPoint = (Point) otherObject; // Do they have the same x and y co-ordinates boolean isSamePoint = (this.x == otherPoint.x && this.y == otherPoint.y); return isSamePoint; } /* * implement hashCode() method of the Object class, which is a requirement * when you implement equals() method */ public int hashCode() { return (this.x + this.y); } } public class Main { public static void main(String[] args) { Point pt1 = new Point(10, 10); Point pt2 = new Point(10, 10); Point pt3 = new Point(12, 19); Point pt4 = pt1; System.out.println("pt1 == pt1: " + (pt1 == pt1)); System.out.println("pt1.equals(pt1): " + pt1.equals(pt1)); System.out.println("pt1 == pt2: " + (pt1 == pt2)); System.out.println("pt1.equals(pt2): " + pt1.equals(pt2)); System.out.println("pt1 == pt3: " + (pt1 == pt3)); System.out.println("pt1.equals(pt3): " + pt1.equals(pt3)); System.out.println("pt1 == pt4: " + (pt1 == pt4)); System.out.println("pt1.equals(pt4): " + pt1.equals(pt4)); } }
上面的代码生成以下结果。
注意
这里是equals()方法的实现的规范。假设x,y和z是三个对象的非空引用。
- 自反性。表达式x.equals(x)应该返回true。
- 对称性。如果x.equals(y)返回true,y.equals(x)必须返回true。
- 传递性。如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)必须返回true。
- 一致性。如果x.equals(y)返回true,它应该保持返回true,直到x或y的状态被修改。如果x.equals(y)返回false,它应该保持返回false,直到x或y的状态被修改。
- 与空引用的比较:任何类的对象不应等于空引用。表达式x.equals(null)应始终返回false。
- 与hashCode()方法的关系:如果x.equals(y)返回true,x.hashCode()必须返回与y.hashCode()相同的值。
Java Object.toString方法
Java面向对象设计 - Java Object.toString方法
对象的字符串表示应以可读格式包含有关对象状态的足够信息。
Object类的toString()方法表示字符串中类的对象。
Object类提供了toString()方法的默认实现。它返回一个以下格式的字符串:
<fully qualified class name>@<hash code of object in hexadecimal>
例子
考虑下面的代码及其输出。您可能会得到不同的输出。
public class Main{ public static void main(String[] argv){ Object obj = new Object(); String objStr = obj.toString(); System.out.println(objStr); } }
上面的代码生成以下结果。
例2
以下代码显示了如何创建自己的toString方法。
public class Main{ public static void main(String[] argv){ MyClass obj = new MyClass(123); String objStr = obj.toString(); System.out.println(objStr); } } class MyClass { private int value; public MyClass(int value) { this.value = value; } public void setValue(int value) { this.value = value; } public int getValue() { return value; } /* override toString() method of the Object class */ public String toString() { // Return the stored value as a string String str = String.valueOf(this.value); return str; } }
上面的代码生成以下结果。
注意
您需要确保它被声明为public,它的返回类型是String,并且它不接受任何参数。
类的toString()方法非常重要。当需要对象的字符串表示时,Java会自动调用toString()方法。
有两种情况值得一提:
当你连接一个字符串和一个对象
String str = "Hello" + new Point(10, 20);
Java在Point对象上调用toString()方法,并将返回的值连接到“Hello"字符串。
上述语句与以下语句相同:
String str = "Hello" + new Point(10, 20).toString();
Java Object.Clone方法
Java面向对象设计 - Java Object.Clone方法
Java不提供克隆(复制)对象的自动机制。
克隆对象意味着逐位复制对象的内容。
要支持克隆操作,请在类中实现clone()方法。
Object类中的clone()方法的声明如下:
protected Object clone() throws CloneNotSupportedException
clone()方法声明为protected。因此,我们不能从客户端代码调用它。以下代码无效:
Object obj = new Object(); Object clone = obj.clone(); // Error. Cannot access protected clone() method
我们需要在类中声明clone()方法public克隆类的对象。
它的返回类型是Object。这意味着您将需要转换clone()方法的返回值。
假设MyClass是可克隆的。克隆代码将如下所示:
MyClass mc = new MyClass(); MyClass clone = (MyClass)mc.clone(); // Need to use a cast
Object类中的clone()方法会抛出CloneNotSupportedException。
要调用clone()方法,我们需要将调用放在try-catch块中,或者重新抛出异常。
例子
以下代码显示了如何实现克隆方法。
class MyClass implements Cloneable { private double value; public MyClass(double value) { this.value = value; } public void setValue(double value) { this.value = value; } public double getValue() { return this.value; } public Object clone() { MyClass copy = null; try { copy = (MyClass) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return copy; } } public class Main { public static void main(String[] args) { MyClass dh = new MyClass(100.00); MyClass dhClone = (MyClass) dh.clone(); System.out.println("Original:" + dh.getValue()); System.out.println("Clone :" + dhClone.getValue()); dh.setValue(200.00); dhClone.setValue(400.00); System.out.println("Original:" + dh.getValue()); System.out.println("Clone :" + dhClone.getValue()); } }
上面的代码生成以下结果。
Original:100.0
Clone :100.0
Original:200.0
Clone :400.0
例2
以下代码不从clone方法返回对象类型,该方法仅在Java 5或更高版本中编译。
class MyClass implements Cloneable { public MyClass clone() { Object copy = null; return (MyClass)copy; } }
下面的代码展示了如何做浅层克隆。
class MyClass implements Cloneable { private double value; public MyClass(double value) { this.value = value; } public void setValue(double value) { this.value = value; } public double getValue() { return this.value; } public Object clone() { MyClass copy = null; try { copy = (MyClass) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return copy; } } class ShallowClone implements Cloneable { private MyClass holder = new MyClass(0.0); public ShallowClone(double value) { this.holder.setValue(value); } public void setValue(double value) { this.holder.setValue(value); } public double getValue() { return this.holder.getValue(); } public Object clone() { ShallowClone copy = null; try { copy = (ShallowClone) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return copy; } } public class Main { public static void main(String[] args) { ShallowClone sc = new ShallowClone(100.00); ShallowClone scClone = (ShallowClone) sc.clone(); System.out.println("Original:" + sc.getValue()); System.out.println("Clone :" + scClone.getValue()); sc.setValue(200.00); System.out.println("Original:" + sc.getValue()); System.out.println("Clone :" + scClone.getValue()); } }
上面的代码生成以下结果。
例3
ShallowClone类的clone()方法中的代码与MyClass类的clone()方法相同。
当ShallowClone类使用super.clone()调用Object类的clone()方法时,它会接收自身的浅拷贝。也就是说,它与其克隆共享其实例变量中使用的DoubleHolder对象。
在深层克隆中,您需要克隆对象的所有引用实例变量引用的所有对象。
class MyClass implements Cloneable { private double value; public MyClass(double value) { this.value = value; } public void setValue(double value) { this.value = value; } public double getValue() { return this.value; } public Object clone() { MyClass copy = null; try { copy = (MyClass) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return copy; } } class DeepClone implements Cloneable { private MyClass holder = new MyClass(0.0); public DeepClone(double value) { this.holder.setValue(value); } public void setValue(double value) { this.holder.setValue(value); } public double getValue() { return this.holder.getValue(); } public Object clone() { DeepClone copy = null; try { copy = (DeepClone) super.clone(); copy.holder = (MyClass) this.holder.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return copy; } } public class Main { public static void main(String[] args) { DeepClone sc = new DeepClone(100.00); DeepClone scClone = (DeepClone) sc.clone(); System.out.println("Original:" + sc.getValue()); System.out.println("Clone :" + scClone.getValue()); sc.setValue(200.00); System.out.println("Original:" + sc.getValue()); System.out.println("Clone :" + scClone.getValue()); } }
上面的代码生成以下结果。
Java Object.Finalize方法
Java面向对象设计 - Java Object.Finalize方法
Java提供了一种在对象即将被销毁时执行资源释放的方法。
在Java中,我们创建对象,但是我们不能销毁对象。
JVM运行一个称为垃圾收集器的低优先级特殊任务来销毁不再引用的所有对象。
垃圾回收器给我们一个机会,在对象被销毁之前执行清理代码。
Object类有一个finalize()方法,声明如下:
protected void finalize() throws Throwable { }
Object类中的finalize()方法不会做任何事情。
你需要覆盖你的类中的方法。
您的类的finalize()方法将在您的类的对象销毁之前由垃圾回收器调用。
例子
以下代码显示了如何创建一个Finalize类覆盖对象类的finalize()方法。
class Finalize { private int x; public Finalize(int x) { this.x = x; } public void finalize() { System.out.println("Finalizing " + this.x); } } public class Main { public static void main(String[] args) { for (int i = 0; i < 20000; i++) { new Finalize(i); } } }
上面的代码生成以下结果。
Java Immutable(不可变)对象
Java面向对象设计 - Java不可变对象
在创建状态后无法更改其状态的对象称为不可变对象。
一个对象不可变的类称为不可变类。
不变的对象可以由程序的不同区域共享而不用担心其状态改变。
不可变对象本质上是线程安全的。
例子
以下代码创建了不可变类的示例。
public class IntWrapper { private final int value; public IntWrapper(int value) { this.value = value; } public int getValue() { return value; } }
注意
这是如何创建IntWrapper类的对象:
IntWrapper wrapper = new IntWrapper(101);
在这一点上,包装器对象保持101,并且没有办法改变它。
因此,IntWrapper类是一个不可变的类,它的对象是不可变的对象。
最好将所有实例变量声明为final,这样Java编译器将在编译期间强制实现不可变性。
Java Objects类
Java面向对象设计 - Java Objects类
Java在java.util包中有一个实用程序类Objects用于处理对象。
它由所有静态方法组成。 Objects类中的大多数方法都会优雅地处理空值。
以下是类中的方法列表。他们的描述遵循列表。
- int compare(T a, T b, Comparator c)
如果参数相同,则返回0,否则返回c.compare(a,b)。因此,如果两个参数都为null,则返回0。 - boolean deepEquals(Object a, Object b)
检查两个对象是否相等。如果两个参数都相等,则返回true。否则,它返回false。如果两个参数都为null,则返回true。 - boolean equals(Object a, Object b)
比较两个对象是否相等。如果两个参数相等,则返回true。否则,它返回false。如果两个参数都为null,则返回true。 - int hash(Object... values)
为所有指定的对象生成哈希码。它可以用于计算对象的哈希码,该哈希码基于多个实例字段。 - int hashCode(Object o)
返回指定对象的哈希码值。如果参数为null,则返回0。 - boolean isNull(Object obj)
如果指定的对象为null,isNull()方法返回true。否则,它返回false。您还可以使用比较运算符==检查对象是否为null,例如,obj == null返回obj的true为null。 - boolean nonNull(Object obj)
执行与isNull()方法相反的检查。 - T requireNonNull(T obj)
T requireNonNull(T obj, String message)
T requireNonNull(T obj, Supplier messageSupplier)
检查参数是否为null。如果参数为null,它会抛出一个NullPointerException异常。此方法设计用于验证方法和构造函数的参数。
第二个版本可以指定当参数为null时抛出的NullPointerException的消息。
第三个版本的方法将一个Supplier作为第二个参数。 - String toString(Object o)
String toString(Object o, String nullDefault)
如果参数为null,则toString()方法返回一个“null”字符串。对于非空参数,它返回通过调用参数的toString()方法返回的值。
HashCode
下面的代码演示了如何使用来自Objects类的方法来计算哈希码。
import java.util.Objects; public class Main { public static void main(String[] args) { // Compute hash code for two integers, a char, and a string int hash = Objects.hash(10, 800, "\u20b9", "Hello"); System.out.println("Hash Code is " + hash); } }
上面的代码生成以下结果。
equals
以下代码显示了如何使用Objects类中的equals方法来比较两个对象。
import java.util.Objects; public class Main { public static void main(String[] args) { // Test for equality boolean isEqual = Objects.equals(null, null); System.out.println("null is equal to null: " + isEqual); isEqual = Objects.equals(null, "XYZ"); System.out.println("null is equal to XYZ: " + isEqual); } }
上面的代码生成以下结果。
toString
以下代码显示如何使用toString方法从对象将对象转换为字符串。
import java.util.Objects; public class Main { public static void main(String[] args) { // toString() method test System.out.println("toString(null) is " + Objects.toString(null)); System.out.println("toString(null, \"XXX\") is " + Objects.toString(null, "XXX")); } }
上面的代码生成以下结果。
requireNonNull
以下代码显示如何使用Objects类中的requireNonNull。
import java.time.Instant; import java.util.Objects; import java.util.function.Supplier; public class Main { public static void main(String[] args) { try { printName("A"); printName(null); } catch (NullPointerException e) { System.out.println(e.getMessage()); } try { Supplier<String> messageSupplier = () -> "Name is required. Error generated on " + Instant.now(); printNameWithSuplier("asdf", messageSupplier); printNameWithSuplier(null, messageSupplier); } catch (NullPointerException e) { System.out.println(e.getMessage()); } } public static void printName(String name) { Objects.requireNonNull(name, "Name is required."); System.out.println("Name is " + name); } public static void printNameWithSuplier(String name, Supplier<String> messageSupplier) { Objects.requireNonNull(name, messageSupplier); } }
上面的代码生成以下结果。