一、实例和静态
1、实例
类中的实例都有自己的字段,再每个类中都可以创建一系列实例对象,并将值储存在实例字段中。
box.setLength(10);
这段代码将10储存在一个被box引用的实例的字段中,你可以把实例字段看作类中某个特定实例的一部分。当你调用一个实例方法时,它也只会在特定的实例的实例字段中执行操作。
2、静态
我们也可以创建不属于类的实例的字段或方法,称为静态字段和静态方法。当值存储在静态字段中时,就不会存储在类的实例当中,甚至当类的实例不存在时,就可以生成值存储在类的静态字段中。静态方法不会对任何实例执行操作,它们只能在静态字段上运行。
(1)静态字段
当字段前有关键字static时,就说明这是一个实例字段。在同一个类中,静态字段可以被所有的实例所共享。
/**
This class demonstrates a static field.
*/
public class Countable
{
private static int instanceCount = 0;
/**
The constructor increments the static
field instanceCount. This keeps track
of the number of instances of this
class that are created.
*/
public Countable()
{
instanceCount++;
}
/**
The getInstanceCount method returns
the number of instances of this class
that have been created.
@return The value in the instanceCount field.
*/
public int getInstanceCount()
{
return instanceCount;
}
}
注意“private static int instanceCount = 0”永远只会赋值一次
接下来,每当构造器执行一次(也就是每当countable类的对象被创建一次),instancecount字段就将增加一次,增加后的instancecount字段的值将储存在类中,也就是说,instancecount字段的值并不会随着countable类的对象的不同而改变,也不会创建一个countable对象就重新变成0,基于这种特性,可以用instancecount字段的值表示创建的countable对象的数量。
/**
This program demonstrates the Countable class.
*/
public class StaticDemo
{
public static void main(String[] args)
{
int objectCount;
// Create three instances of the
// Countable class.
Countable object1 = new Countable();
Countable object2 = new Countable();
Countable object3 = new Countable();
// Get the number of instances from
// the class's static field.
objectCount = object1.getInstanceCount();
System.out.println(objectCount +
" instances of the class " +
"were created.");
}
}
得到的输出为:
3 instances of the class were created.
虽然创建了三个countable对象,但是静态字段的值并不会复制三次,它永远只会赋值一次。
虽然最后返回的是“object1.getInstanceCount()”,但是无论是object1还是object2,得到的结果都是一样的。
(2)静态方法
当一个类有静态方法的时候,就不一定要为这个类创建对象了,在调用静态方法的时候,只需要写类的名字就行了,而调用实例方法的时候,则需要写类中对象的名字(比如上面的getInstanceCount就是实例方法,所以调用的时候要写object1,这个object1就是对象而非类)。
/**
This class demonstrates static methods.
*/
public class Metric
{
/**
The milesToKilometers method converts a
distance in miles to kilometers.
@param m The distance in miles.
@return The distance in kilometers.
*/
public static double milesToKilometers(double m)
{
return m * 1.609;
}
/**
The kilometersToMiles method converts
a distance in kilometers to miles.
@param k The distance in kilometers.
@return The distance in miles.
*/
public static double kilometersToMiles(double k)
{
return k / 1.609;
}
}
调用milesToKilometers方法:
kilometers = Metric.milesToKilometers(10.0);
这里的Metric就是类的名字,而非实例的名字。
小结
静态方法常用于对数据进行操作而不需要储存数据的情况,比如上面的公里英里转换器,只需要执行“转换”这个操作,而不需要对数据进行储存,所以就适合使用静态方法。
静态方法的局限就是它的所有成员都必须是静态的,比如静态方法调用的方法也必须是静态方法。反过来,如果一个方法使用了任何的静态字段,那么这个方法也应该是静态方法。
静态方法内部不能直接调用非静态方法,但是可以在调用之前实例化非静态方法所在的类,再用类的方法来调用。
public static void changeRectangle(Rectangle r)
{
r.setLength(0.0);
r.setWidth(0.0);
}
}
二、将对象作为参数传入方法
1、一个地址两个名字
下面这个程序将一个三角形对象传入方法:
/**
This program passes an object as an argument.
*/
public class PassObject
{
public static void main(String[] args)
{
// Create a Rectangle object.
Rectangle box = new Rectangle(12.0, 5.0);
// Pass a reference to the object to
// the displayRectangle method.
displayRectangle(box);
}
/**
The displayRectangle method displays the
length and width of a rectangle.
@param r A reference to a Rectangle
object.
*/
public static void displayRectangle(Rectangle r)
{
// Display the length and width.
System.out.println("Length : " + r.getLength() +
" Width : " + r.getWidth());
}
}
值得注意的是,在这个程序中,主函数里的box是一个对rectangle类的对象的引用,而传参的时候的形参r也是一个对rectangle类的对象的引用,box和r本质上是一样的,它们都是这个对象的地址,只不过叫两个名字。
2、传实例与传基本数据类型的区别
当我们传一个基本数据类型的参数的时候,我们是将原来的值复制了一遍,然后再传递给方法,当方法想改变参数的值的时候,改变的只是复制以后的值,对于原来的值并不会有影响。
但是当我们传一个实例的时候,我们传递的就是实例的地址,这意味着方法是有权限去访问该实例的,所以方法是可以修改原来实例的内容的。
/**
This program passes an object as an argument.
The object is modified by the receiving method.
*/
public class PassObject2
{
public static void main(String[] args)
{
// Create a Rectangle object.
Rectangle box = new Rectangle(12.0, 5.0);
// Display the object's contents.
System.out.println("Contents of the box object:");
System.out.println("Length : " + box.getLength() +
" Width : " + box.getWidth());
// Pass a reference to the object to the
// changeRectangle method.
changeRectangle(box);
// Display the object's contents again.
System.out.println("\nNow the contents of the " +
"box object are:");
System.out.println("Length : " + box.getLength() +
" Width : " + box.getWidth());
}
/**
The changeRectangle method sets a Rectangle
object's length and width to 0.
@param r The Rectangle object to change.
*/
public static void changeRectangle(Rectangle r)
{
r.setLength(0.0);
r.setWidth(0.0);
}
}
Contents of the box object: Length :
2.0 Width : 5.0
Now the contents of the box object are:
Length : 0.0 Width : 0.0
三、从方法中返回一个对象
import javax.swing.JOptionPane;
/**
This program demonstrates how a method
can return a reference to an object.
*/
public class ReturnObject
{
public static void main(String[] args)
{
BankAccount account;
// Get a reference to a BankAccount object.
account = getAccount();
// Display the account's balance.
JOptionPane.showMessageDialog(null,
"The account has a balance of $" +
account.getBalance());
System.exit(0);
}
/**
The getAccount method creates a BankAccount
object with the balance specified by the
user.
@return A reference to the object.
*/
public static BankAccount getAccount()
{
String input; // To hold input
double balance; // Account balance
// Get the balance from the user.
input = JOptionPane.showInputDialog("Enter " +
"the account balance.");
balance = Double.parseDouble(input);
// Create a BankAccount object and return
// a reference to it.
return new BankAccount(balance);
}
}
getAccount方法返回了一个对象,在主函数里,将这个对象赋给了变量account,然后对account变量(也就是bankaccount类对象的引用)调用bankaccount类的方法,得到了余额。
ps:(1)在方法头中要写明返回值的类型,就和int,double一样
(2)返回对象的时候,要注意写关键字new
return new BankAccount(balance);
此语句使用new关键字创建BankAccount对象,并将balence作为参数传递给构造器。然后从方法返回对象的地址。
四、toString方法
我们经常需要输出一个对象的状态,比如对bankaccount类:
BankAccount account = new BankAccount(1500.0);
System.out.println("The account balance is $" +
account.getBalance());
因为输出一个对象的状态的这种需求太过常见了,所以在java中,这种方法被统称为toString方法,我们可以尝试自己写一个toString方法。
1、准备工作
此类有两个字段:symbol(符号)和sharePrice(股价)。“符号”字段保存交易的交易符号公司的股票。这是一系列短字符,用于标识股票上的股票交易。例如,XYZ公司的股票可能具有交易符号XYZ。股价字段保存股票的当前每股价格。
2、代码
/**
The Stock class holds data about a stock.
*/
public class Stock
{
private String symbol; // Trading symbol of stock
private double sharePrice; // Current price per share
/**
Constructor
@param sym The stock's trading symbol.
@param price The stock's share price.
*/
public Stock(String sym, double price)
{
symbol = sym;
sharePrice = price;
}
/**
getSymbol method
@return The stock's trading symbol.
*/
public String getSymbol()
{
return symbol;
}
/**
getSharePrice method
@return The stock's share price
*/
public double getSharePrice()
{
return sharePrice;
}
/**
toString method
@return A string indicating the object's
trading symbol and share price.
*/
public String toString()
{
// Create a string describing the stock.
String str = "Trading symbol: " + symbol +
"\nShare price: " + sharePrice;
// Return the string.
return str;
}
}
可以这样调用:
Stock xyzCompany = new Stock ("XYZ", 9.62);
System.out.println(xyzCompany.toString());
Trading symbol: XYZShare price: 9.62
但是事实上,没有必要显式调用toString方法。如果你写了一个toString方法,当你将这个对象传递给print或者println方法的时候,java将自动调用toString方法。所以,可以省略 “.toString”:
System.out.println(xyzCompany);
输出的结果是一样的。
System.out.println("The stock data is:\n" + xyzCompany);
上面的代码也会调用toString方法:
The stock data is:Trading symbol: XYZShare price: 9.62
五、equals方法
如果你想比较两个对象的各项属性是否相等,不能用==来比较,它只会比较两个对实例的引用变量的地址是否相同,所以此时你需要自己的equals方法。
public boolean equals(Stock object2)
{
boolean status;
// Determine whether this object's symbol and
// sharePrice fields are equal to object2's
// symbol and sharePrice fields.
if (symbol.equals(object2.symbol) &&
sharePrice == object2.sharePrice)
status = true; // Yes, the objects are equal.
else
status = false; // No, the objects are not equal.
// Return the value in status.
return status;
}
这个方法返回一个布尔类型的值,并接受一个stock类型(就是之前股票的例子)的对象object2作为参数。因为是进行两者的比较,所以自然需要比较两个stock对象,这里已经将object2当参数传进来了,我们就可以对object1使用这个方法,这样equals方法本身就可以访问object1,也可以访问作为参数传入的object2。
if语句后的“symbol.equals(object2.symbol)”中,第一个symbol是object1的symbol变量,而object2.symbol指的就是object2的symbol变量。在同一个类中(这里都是stock类型的实例),可以直接用“对象名.变量名”的语法来访问同类中另外一个实例的属性。而如果希望在另一个类中访问这个symbol变量,则只能使用get方法了。下面是另一个“对象名.变量名”语法的例子:
public static void main (String[] args){
Rectangle box=new Rectangle(2,1);
Rectangle box1=new Rectangle(4,1);
if(box.length==box1.length)
System.out.println("yes");
}
还是“symbol.equals(object2.symbol)”,这里的equals是string类的equals方法,因为symbol是string类型的变量。
对equals方法的调用:
/**
This program uses the Stock class's equals
method to compare two Stock objects.
*/
public class StockCompare
{
public static void main(String[] args)
{
// Create two Stock objects with the same values.
Stock company1 = new Stock("XYZ", 9.62);
Stock company2 = new Stock("XYZ", 9.62);
// Use the equals method to compare the objects.
if (company1.equals(company2))
System.out.println("Both objects are the same.");
else
System.out.println("The objects are different.");
}
}
ps:每个类在一开始都有自己的equals方法,初始的equals方法和==操作符的用法一模一样。如果你没有写自己的equals方法,那么当你调用equals方法的时候就会调用这个方法了,后面还会详细介绍。
六、复制对象的方法
1、简介
当我们简单地用 = 将一个对象赋值给另一个对象的时候,只是将一个对象的地址传递给了第二个对象,只是一个对象有两个名字而已。
所以如果希望再创建一个相同的对象,需要新创建一个对象(new),然后将旧变量的每一个字段都赋值给新变量的相应字段,介绍两种方法。
2、copy方法
copy方法将旧对象的每一个字段当做新创建的对象的构造器的参数(注意不是copy方法本身的参数)。在copy方法内部,读取旧对象的各字段,然后调用这个对象所在类的构造器,将旧对象的字段当做构造器的参数,创建一个和旧对象一样的新对象。
public Stock copy()
{
// Create a new Stock object and initialize it
// with the same data held by the calling object.
Stock copyObject = new Stock(symbol, sharePrice);
// Return a reference to the new object.
return copyObject;
}
调用如下:
/**
This program uses the Stock class's copy method
to create a copy of a Stock object.
*/
public class ObjectCopy
{
public static void main(String[] args)
{
// Create a Stock object.
Stock company1 = new Stock("XYZ", 9.62);
// Declare a Stock variable
Stock company2;
// Make company2 reference a copy of the object
// referenced by company1.
company2 = company1.copy();
}
}
3、Copy Constructors
另一个创建新的复制对象的方法就是使用复制构造器。复制构造器直接将原来的一整个对象当做参数传入构造器,然后在构造器内部读取传入的这个对象的各字段,将各字段赋值给新的对象。
public Stock(Stock object2)
{
symbol = object2.symbol;
sharePrice = object2.sharePrice;
}
调用如下:
// Create a Stock object.
Stock company1 = new Stock("XYZ", 9.62);
// Create another Stock object that is a copy of the company1 object.
Stock company2 = new Stock(company1);
如果写了复制构造器,在调用构造器的时候到底调用哪一个就取决于传入的参数类型了(重载)。