Java笔记(对象与类)


类(class)是构造对象的模板或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance)。在java程序中创建一些自己的类,以便描述应用程序所对应的问题域中的对象。

封装(encapsulation)是与对象有关的概念,从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域(instance field),操纵数据的过程称为方法(method)。
实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域,程序仅通过对象的方法与对象数据进行交互。

对象
每个对象都有唯一的身份。对象的这些关键特性在彼此之间相互影响着。

识别类
首先从设计类开始,然后再往每个类中添加方法。

类之间的关系
如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能地将相互依赖的类减至最少。用软件工程的术语来说,就是让类之间的耦合度最小。

构造Date对象:new Date()
将一个方法应用于刚创建的对象,Date类有一个toString方法如:
String s = new Date().toString();
以上构造的对象仅使用了一次,通常希望构造的对象可以多次使用,因此,需要将对象存放在一个变量中:
Date birthday = new Date();

注: 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
new操作符的返回值也是一个引用:Date deadline = new Date();
表达式new Date()构造了一个Date类型的对象,并且它的值是对新创建对象的引用。
局部变量不会自动地初始化为null,而必须通过调用new或将他们设置为null进行初始化。
所有的Java对象都存储在堆中,当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。

LocalDate类
类库设计者决定将保存时间与给时间点命名分开。因此java类库分别包含:一个是用来表示时间点的Date类,另一个是用来表示大家熟悉的日历表示法的LocalDate类。通常使用静态工厂方法(factory method)代表你调用构造器。如:

LocalDate.now();表示构造这个对象的日期
可以提供年月日构造一个特定日期对象:
LocalDate.of(1999,12,31)
当然,通常希望将构造的对象保存在一个对象变量中:
LocalDate newYearsEve = LocalDate.of(1999,12,31);
一旦有了一个LocalDate对象,可以用方法getYear,getMonthValue和getDayOfMonth得到年月日:
int year = newYearsEve.getYear();//1999
int month = newYearsEve.getMonthValue();//12
int day = newYearsEve.getDayOfMonth();//31

更改器方法与访问器方法
前者更改原对象,后者不更改重新生成一个对象。

首先构造日历对象:
LocalDate date = LocalDate.now();
下面获得当前的月和日:
int month = date.getMonthValue();
int today = date.getDayOfMonth();
然后,将date设置为这个月的第一天,并得到这一天为星期几:
date = date.minusDay(today-1);//set to start of month
DayOfWeek weekday = date.getDayOfWeek();
int value = weekday.getValue();//1=monday...7=sunday
变量weekday设置为DayOfWeek类型的对象。我们调用这个对象的getValue方法来得到星期几的一个数值。
System.out.println("mon tue wed thu fri sat sun");
for(int i=1;i<value;i++)
    System.out.print(" ");
现在我们打印日历主体,进入一个循环,其中date遍历一个月中的每一天。
每次迭代时,打印日期值。若date是当前日期,这个日期则用一个*标记。接下来,把date推进到下一天。如果到达新的一周的第一天,则换行打印:
while (date.getMonthValue()== month)
{
  System.out.printf("%3d",date.getDayOfMonth());
  if (date.getDayOfMonth()==today)
     System.out.print("*");
  else
     System.out.print(" ");
  date = date.plusDays(1);
  if (date.getDayOfWeek().getValue()==1) System.out,println();
 }

Employee类

class Employee
{
  //instance fields
  private String name;
  private double salary;
  private LocalDate hireDay;
  //constructor
  public Employee(String n,double s, int month ,int day)
  {
    name = n;
    salary = s;
    hireDay = LocalDate.of(year,month,day);
  }
  //a method
  public String getName()
  {
      return name;
  }
  //more methods
  ...
}
构造一个Employee数组,并填入三个雇员对象:
Employee[] staff = new Employee[3];

staff[0] = new Employee("carl cracker",...);
staff[1] = new Employee("harry hacker",...);
staff[2] = new Employee("tony tester",...);
接下来,利用Employee类的raiseSalary方法将每个雇员的薪水提高5%:
for (Employee e : staff)
    e.raiseSalary(5);
最后,调用getName方法,getSalary,getHireDay方法将每个雇员信息打印出来:

for (Employee e : staff)
    System.out.println("name="+e.getName()+",salary="+e.getSalary()+",hireDay="+e.getHireDay());

构造器

public Employee(String n, double s,int year, int month, int day)
{
 name=n;
 salary=s;
 LocalDate hireDay = LocalDate.of(year, month, day);
 }

构造器与类同名
每个类可以有一个以上的构造器
构造器可以有0个、1个或多个参数
构造器没有返回值
构造器总是伴随着new操作一起调用

隐式参数与显式参数
方法用于操作对象以及存取它们的实例域如:

public void raiseSalary(double byPercent)
{
  double raise = salary*byPercent / 100;
  salary += raise;
}
将调用这个方法的对象的salary实例域设置为新值。如:
number007.raiseSalary(5);
它的结果将number007.salary域的值增加5%。具体地说,这个调用将执行下列指令:
double raise = number007.salary*5/100;
number007.salary +=raise;
raiseSalary方法有两个参数。第一个参数称为隐式(implicit)参数,是出现在方法名前的Employee类对象。第二个参数位于方法名后面括号中的数值,这是一个显式(explicit)参数。
显式参数是明显列在方法声明中的,例如double byPercent。隐式参数没有出现在方法声明中。

封装的优点

public String getName()
{
  return name;
}

public double getSalary()
{
  return salary;
}
public LocalDate getHireDay()
{
  return hireDay;
}
这些都是典型的访问器方法,由于它们只返回实例域值,因此又称为域访问器。

**注:**如果需要返回一个可变对象的引用,应该首先对它进行克隆,对象clone是指存放在另一个位置上的对象副本。

class Employee
{
  ...
  public Date getHireDay()
  {
    return (Date) hireDay.clone();//ok
  }
  ...
}
凭经验可知,如果需要返回一个可变数据域的拷贝,就应该使用clone。

基于类的访问权限
一个方法可以访问所属类的所有对象的私有数据。如比较两个雇员的equals方法:

class Employee
{
  ...
  public boolean equals(Employee other)
  {
    return name.equals(other.name);
    }
 }
 典型的调用方式是:
 if (harry.equals(boss))...
 这个方法访问harry的私有域,它还访问了boss的私有域。

私有方法
由于共有数据非常危险,所以应该将所有的数据域都设置为私有的。在Java中,为了实现一个私有的方法,只需将关键字public改为private即可。

fianl实例域
可以将实例域定义为final。构建对象时必须初始化这样的域。并且在之后的操作中,不能够在对他进行修改。
final修饰符大都应用于基本(primitive)类型域,或不可变(immutable)类的域。
对于可变的类,使用final修饰符可能会对读者造成混乱。如:

private final StringBuilder evaluations;
在Employee构造器中会初始化为
evaluations = new StringBuilder();
final关键字只是表示存储在evaluations变量中的对象引用不会在指示其他StringBuilder对象。不过这个对象可以更改:(StringBuilder是一个可变的类)
public void giveGoldStar()
{
   evaluations.append(LocalDate.now()+":Gold star!\n");
}

静态域:
例:

这里给Employee类添加一个实例域id和一个静态域nextId:
class Employee
{
  private static int nextId=1;
  
  private int id;
  ...
}
若有1000个Employee类的对象,则有1000个实例域id,但是,只有一个静态域nextId,即使没有一个雇员对象,静态域nextId也存在,它属于类而不属于任何独立的对象。
下面实现一个简单的方法:
public void setId()
{
  id = nextId;
  nextId++;
}
假定为harry设定雇员标识码:
harry.setId();
harry的id域被设置为静态域nextId当前的值,并且静态域nextId的值加1:
harry.id = Employee.nextId;
Employee.nextId++;

静态常量
静态变量使用得比较少,但静态常量却使用得比较多。
例如,在Math类中定义了一个静态常量:

public class Math
{
  ...
  public static final double PI = 3.1415926;
  ...
}
另一个多次使用的静态常量是System.out,它在System类中声明:
public class System
{
  ...
  public static final PrintStream out=...;
  ...
}

静态方法
是一种不能向对象实施操作的方法。例如,Math类的pow方法就是一个方法。

表达式:Math.pow(x,a) 计算幂x三次方。

在运算时,不适用任何Math对象,换句话说,没有隐式的参数。
Employee类的静态方法不能访问Id实例域,因为它不能操作对象。但是,静态方法可以访问自身类中的静态域。实例:

public static int getNextId()
{
  return nextId;//return static field
}
可以通过类名调用这个方法:
int n = Employee.getNextId();

**注:**在下面两种情况使用静态方法:
1.一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如:Math.pow)
2.一个方法只需要访问类的静态域(例:Employee.getNextId)。

工厂方法
类似LocalDate和NumberFormat的类使用静态工厂方法(factory method)来构造对象。

NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x= 0.1;
System.out.println(currencyFormatter.format(x));//print $0.10
System.out.println(percentFormatter.format(x));//print 10%

main方法
需要注意,不需要使用对象调用静态方法。例如,不需要构造Math类对象就可以调用Math.pow。
同理·,main方法也是一个静态方法。

public class Application
{
  public static void main(String[] args)
  {
    //construct objects here
    ...
    }
 }

main方法不对任何对象进行操作。在启动程序时还没任何一个对象,静态main方法将执行并创建程序所需要的对象。
Employee类的main方法永远不会执行。
方法参数
值传递是拷贝一个新的值
引用传递是拷贝一个新的引用

考虑下面的调用:
double percent =10;
harry.raiseSalary(percent);
在方法调用之后,percent的值还是10.
假定一个方法试图将一个参数值增加至3倍:
public static void tripleValue(double x)
{
  x=x*3;
}
然后调用这个方法:
double percent = 10;
tripleValue(percent);
然而调用这个方法后,percent的值还是10.因此,一个方法不可能修改一个基本数据类型的参数。
相反,对象引用作为参数就不同了,它可以轻松利用下面这个方法实现一个雇员薪金提高两倍的操作:
public static void tripleSalary(Employee x)
{
  x.raiseSalary(200);
}
当调用
  harry = new Employee(...);
  tripleSalary(harry);
  具体执行为:x被初始化为harry值拷贝,raiseSalary方法应用于这个对象引用,x和harry同时引用的那个Employee对象的薪金提高200%,方法结束后,参数变量x不再使用。

总结:
1.一个方法不能修改一个基本数据类型的参数。
2.一个方法可以改变一个对象参数的状态。
3.一个方法不能让对象参数引用一个新的对象。

对象构造
重载:有些类有多个构造器,这种特征叫重载(overloading)。

例如构造一个空的StringBuilder对象:
StringBuilder messages = new StringBuilder();
或者可以指定一个初始字符串:
StringBuilder todoList = new StringBuilder("To do:\n");

注: 要完整地描述一个方法,需要指出方法名以及参数类型,这叫做方法的签名(signature)。如,String类有四个称为indexOf的公有方法,他们的签名分别是

indexOf(int)
indexOf(int,int)
indexOf(String)
indexOf(String,int)

默认域初始化: 如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值:数值为0、布尔值为false、对象引用为null。
例如Employee类,假定没有在构造器中对某些域进行初始化,就会默认地将salary域初始化为0,将name和hireDay域初始化为null。

无参数的构造器: 很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时,其状态会设置为适当的默认值。

例如以下式Employee类的无参数构造函数:
public Employee()
{
  name = "";
  salary = 0;
  hireDay = LocalDate.now();
}
如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数构造器。它将会使所有的实例域设置为默认值,因此实例域中的数值型数据设置为0、布尔型数据设置为false、所有对象变量将设置为null。

显式域初始化
通过重载类的构造器方法,可以采用多种形式设置类的实例域的初始状态。

可以在类定义中,直接将一个值赋给任何域,如:
class Employee
{
  private String name = "";
  ...
}
初始值不一定是常量值。
class Employee
{
  private static int nextId;
  private int id = assignId();
  ...
  private static int assignId()
  {
    int r = nextId;
    nextId++;
    return r;
   }
   ...
 }  

参数变量用同样的名字将实例域屏蔽起来,例如,如果将参数命名为salary,salary将引用这个参数,而不是实例域。但是可以采用this.salary的形式访问实例域。
public Employee(String name,double salary)
{
  this.name = name;
  this.salary = salary;
}

调用另一个构造器
关键字this引用方法的隐式参数。
如果构造器的第一个语句形如this(…),这个构造器将调用同一个类的另一个构造器。如:

public Employee(double s)
{
 this("Employee #"+nextId,s);
 nextId++;
}

初始化块
前面已有两种初始化数据域的方法:在构造器中设置值、在声明中赋值。

Java中还有第三种机制,即初始化块,在一个类的声明中,可以包含多个代码块。只要构造类的对象,这些块就会被执行,如:
class Employee
{
  private static int nextId;
  
  private int id;
  private String name;
  private double salary;

{
  id = nextId;
  nextId++;
 }
 public Employee(String m ,double s)
 {
  name = n;
  salary = s;
 }
 public Employee()
 {
   name = "";
   salary = 0;
 }
 ...
}
在这个实例中,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。

如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块。
将代码放在一个块中,并标记关键字static,如下:其功能是将雇员ID的起始值赋予一个小于10000的随机整数。

static
{
  Random generator = new Random();
  nextId = generator.nextInt(10000);
}


Java允许使用包(package)将类组织起来。

类的导入: 一个类可以使用所属包中的所有类,以及其他包中的公有类(public class)

1.在每个类名之前添加完整的包名来访问另一个包中的公有类。
java.time.LocalDate today = java.time.LocalDate.now();
显然繁琐,一般常用import语句导入一个特定的类或整个包:
import java.util.*;
就可以使用:
LocalDate today = LocalDate.now();

包作用域: 标记为public的部分可以被任意的类使用;标记为private的部分只能被定义它们的类使用。

方法注释:
@param变量描述:这个标记将对当前方法的“param"(参数)部分添加一个条目。这个描述可以占据多行,并可使用HTML标记。一个方法的所有@param标记必须放在一起。
@return描述:这个标记将对当前方法添加”return"(返回)部分。这个描述可以跨越多行,并可以使用HTML标记。
@throws类描述:这个标记将添加一个注释,用于表示这个方法有可能抛出异常。

通用注释:
@author姓名:将产生一个“author”(作者)条目。每一个对应一个作者。
@version文本:将产生一个“version”(版本)条目。这里的文本可以是对当前版本的任何描述。
@since文本:将产生一个“since”(始于)条目。这里的text可以是对引入特性的版本描述。
@deprecated文本:这个标记将对类、方法或变量添加一个不再使用的注释。文本中给出了取代的建议。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值