1、成员变量
在Java中对象的属性也称为成员变量。
为了了解成员变量,首先定义一个图书类,成员变量对应于类对象的属性,
在 Book类中设置3个成员变量,分别为id、name和 category,分别对应于图书编号、图书名称和图书类别3个图书属性。
/**
* 在java中。对象的属性也被称为成员变量
*/
public class Book {
private String Name;//定义一个String类型的成员变量
public String GetName(){ // 定义一个GetName 方法
int id = 0; // 局部变量
setName("Java"); // 调用类中的其他方法
return id + this.Name; //设置方法返回值
}
/**
* 定义一个SetName方法
* @param name
*/
private void SetName(String name){
this.Name = name; // 将参数值赋予类中的一个成员变量
}
public Book getBook(){
return this; // 返回Book的类引用
}
}
2、成员方法
在Java语言中使用成员方法对应于类对象的行为。
以Book类为例,它包含getName()和setName()两个方法,这两个成员方法分别为获取图书名称和设置图书名称的方法。
定义成员方法的语法格式如下:
权限修饰符 返回值类型 方法名(参数类型参数名){
.//方法体
return 返回值;
}
一个成员方法可以有参数,这个参数可以是对象,
也可以是基本数据类型的变量,
同时成员方法有返回值和不返回任何值的选择,如果方法需要返回值,可以在方法体中使用return 关键字,使用这个关键字后,方法的执行将被终止。
Java中的成员方法无返回值,可以使用 void关键字表示。
成员方法的返回值可以是计算结果,也可以是其他想要的数值和对象,返回值类型要与方法返回的值类型一致。
// >> TODO 在方法定义中指定方法的返回值类型
// >> TODO Java中一个方法只能有一种返回值,如果不需要返回值则用void表示
// >> TODO 如果定义了返回值,则必须使用 return 语句返回方法的返回值,return 是 Java 的关键字
// >> TODO 可以认为,返回值必须要能够用来给返回值类型的变量赋值
public double calculateProfit(){
double profit = soldPrice - purchasePrice;
// >> TODO 这个return是代码块里的return,是return所在代码块的最后一个语句
if (profit <= 0) {
return 0;
}
// >> TODO return 语句必须是所在代码块的最后一个语句,否则就是语法错误
return profit;
// >> TODO 一个方法可以有多个返回语句。
}
// >> TODO 返回值如果是基本类型,则要类型完全相同,或者符合类型自动转换规则
public double getCurrentCount(){
return count;
}
// >> TODO 如果不符合规则,可以使用强制类型转换
public int getIntSoldPrice(){
return (int) soldPrice;
}
2.1、方法参数
public class MerchandiseV2 {
public String name;
public String id;
public int count;
public double soldPrice;
public double purchasePrice;
public void describe() {
System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
+ "。商品进价是" + purchasePrice + "。商品库存量是" + count +
"。销售一个的毛利润是" + (soldPrice - purchasePrice));
}
public double calculateProfit() {
double profit = soldPrice - purchasePrice;
if(profit <= 0){
return 0;
}
return profit;
}
// >> TODO 参数是定义在方法名字后面的括号里的
// >> TODO 参数定义的规范和变量一样,都是类型名字加标识符,这里的标识符我们叫做参数名。
// >> TODO 方法体中的代码可以使用参数
// >> TODO 参数的值在调用方法的时候需要给出,有的资料叫做实参(实际参数)
// TODO 对应的,方法定义这里的参数,叫做形参(形式参数)
// 如果返回值是负数,就代表购买失败,比如库存不足
public double buy(int countToBuy) {
if (count < countToBuy) {
System.out.println("商品库存不够");
return -1;
}
System.out.println("商品单价为" + purchasePrice);
int fullPriceCount = countToBuy / 2 + countToBuy % 2;
int halfPriceCount = countToBuy - fullPriceCount;
double totalCost = purchasePrice * fullPriceCount + halfPriceCount * purchasePrice / 2;
count -= countToBuy;
return totalCost;
}
// >> TODO 一个方法可以有多个参数,多个参数之间用逗号隔开
public double buyAndPrintLeft(int countToBuy, boolean printLeft) {
if (count < countToBuy) {
System.out.println("商品库存不够");
if (printLeft) {
System.out.println("商品剩余库存为" + count);
}
return -1;
}
System.out.println("商品单价为" + purchasePrice);
int fullPriceCount = countToBuy / 2 + countToBuy % 2;
int halfPriceCount = countToBuy - fullPriceCount;
double totalCost = purchasePrice * fullPriceCount + halfPriceCount * purchasePrice / 2;
count -= countToBuy;
if (printLeft) {
System.out.println("商品剩余库存为:" + count);
}
return totalCost;
}
// >> TODO 参数可以是任何类型,包括自定义类型,甚至是自己的类型都没问题
public boolean totalValueBiggerThan(MerchandiseV2 merchandiseV2) {
return count * purchasePrice > merchandiseV2.purchasePrice * merchandiseV2.count;
}
// >> TODO 参数可以是任何类型,包括自定义类型
public boolean isTheBiggestTotalValueOne(LittleSuperMarket littleSuperMarket) {
double totalValue = count * purchasePrice;
for (int i = 0; i < littleSuperMarket.merchandises.length; i++) {
MerchandiseV2 m = littleSuperMarket.merchandises[i];
double newTotalValue = m.count * m.purchasePrice;
if (totalValue < newTotalValue) {
// 执行到return的时候,方法直接结束,不管是不是在循环中,是在第几层循环中。
return false;
}
}
return true;
}
}
2.2、参数传递
参数本身可以是一个表达式,只要表达式的值类型可以和参数类型匹配就可以
方法里使用的参数相当于一个局部变量。使用方法前,会用实参给局部变量赋值。
2.3、方法签名和重载
重载的方法可以调用别的重载方法,当然也可以调用别的不重载的方法。
实际上,像这种补充一些缺省的参数值,然后调用重载的方法,是重载的一个重要的使用场景。
在这里我们举的例子就是这样的,但是不是语法要求一定要这样。重载的方法的方法体内代码可以随便写,
可以不调用别的重载方法
// 买一个、买多个、VIP购买
public double buy() {
return buy(1);
}
public double buy(int count) { return buy(count, false);
}
// TODO 最后都补充好参数,调用参数最全的一个方法
public double buy(int count, boolean isVIP) {
if (this.count < count) {
return -1;
}
this.count -= count;
double totalCost = count * soldPrice;
if (isVIP) {
return totalCost * 0.95;
} else {
return totalCost;
}
}
无论是否重载参数类型可以不完全匹配的规则是"实参数可以自动类型转换成形参类型"
重载的特殊之处是,参数满足自动自动类型转换的方法有好几个,重载的规则是选择最"近"的去调用
3、权限修饰符
Java中的权限修饰符主要包括private、 public和 protected,
这些修饰符控制着对类和类的成员变量以及成员方法的访问。
如果一个类的成员变量或成员方法被修饰为private,则该成员变量只能在本类中被使用,在子类中是不可见的,并且对其他包的类也是不可见的。
如果将类的成员变量和成员方法的访问权限设置为public,那么除了可以在本类使用这些数据之外,还可以在子类和其他包的类中使用。
如果一个类的访问权限被设置为private,这个类将隐藏其内的所有数据,以免用户直接访问它。
如果需要使类中的数据被子类或其他包中的类使用,可以将这个类设置为 public 访问权限。
如果一个类使用protected修饰符,那么只有本包内的该类的子类或其他类可以访问此类中的成员变量和成员方法。
这么看来,public和 protected修饰的类可以由子类访问,如果子类和父类不在同一包中,那么只有修饰符为public的类可以被子类进行访问。
如果父类不允许通过继承产生的子类访问它的成员变量,那么必须使用private声明父类的这个成员变量。
表中描述了private、protected和 public修饰符的修饰权限。
当声明类时不使用public、protected和private修饰符设置类的权限,则这个类预设为包存取范围,即只有一个包中的类可以调用这个类的成员变量或成员方法。
类,静态方法,静态变量,成员变量,构造方法,成员方法都可以使用访问修饰符
成员变量应该都声明为private
如果要读写这些成员变量,最好使用get set方法,这些方法应该是public的
这样做的好处是,如果有需要,可以通过代码,检查每个属性值是否合法。
private String name;
private String id;
private int count;
public的方法类似一种约定,既然外面的代码可以使用,就意味着不能乱改。比如签名不能改之类的
public void describe() {
System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
+ "。商品进价是" + purchasePrice + "。商品库存量是" + count +
"。销售一个的毛利润是" + (soldPrice - purchasePrice));
freeStyle();
}
对于private的方法,因为类外面掉不到,所以无论怎么改,也不会影响(直接影响)类外面的代码
private void freeStyle() {
}
4、局部变量==在成员方法内部定义的变量
局部变量是在方法被执行时创建,在方法执行结束时被销毁。局部变量在使用时必须进行赋值操
作或被初始化,否则会出现编译错误。
5、局部变量的有效范围
可以将局部变量的有效范围称为变量的作用域,局部变量的有效范围从该变量的声明开始到该变量的结束为止。
6、this关键字
方法里隐藏着一个this自引用,指向调用这个方法的对象。
使用一个对象调用方法,也叫做在这个对象上调用方法。因为方法可以访问这个对象的值。
访问一个成员变量的完整形态,是"this.成员变量的名字"
public class MerchandiseV2 {
public String name;
public String id;
public int count;
public void addCount(int count) {
this.count += count;
System.out.println("MerchandiseV2的addCount方法使用的对象是:" + this);
}
}
7、类的构造方法
在类中除了成员方法之外,还存在种特殊类型的方法, 那就是构造方法。
构造方法是一个 与类同名的方法,对象的创建就是通过构造方法完成的。每当类实例化一个对象时,类都会自动调用构造方法。
构造方法的特点如下:
构造方法没有返回值。
构造方法的名称要与本类的名称相同。
在定义构造方法时,构造方法没有返回值,但这与普通没有返回值的方法不同,普通没有返回值的方法使用public void methodEx()这种形式进行定义,
但构造方法并不需要使用void关键字进行修饰。
在构造方法中可以为成员变量赋值,这样当实例化一个本类的对象时,相应的成员变量也将被初始化。
如果类中没有明确定义构造方法,编译器会自动创建一个不带参数的默认构造方法。
如果在类中定义的构造方法都不是无参的构造方法,那么编译器也不会为类设置一个默认的无参构造方法,
当试图调用无参构造方法实例化一个对象时,编译器会报错。所以只有在类中没有定义任何构造方法时,编译器才会在该类中自动创建一个不带参数的构造方法。
构造方法(constructor)的方法名必须与类名一样,而且构造方法没有返回值。这样的方法才是构造方法。
构造方法可以有参数,规则和语法于普通方法一样。使用时,参数传递给 new 语句后类名的括号后面。
如果没有显示的添加一个构造方法,Java会给每个类都会默认自带一个无参数的构造方法。
如果我们自己添加类构造方法,Java就不会再添加无参数的构造方法。这时候,就不能直接 new 一个对象不传递参数了(看例子)
所以我们一直都在使用构造方法,这也是为什么创建对象的时候类名后面要有一个括号的原因。
构造方法无法被点操作符调用或者在普通方法里调用,只能通过 new 语句在创建对象的时候,间接调用。
理解一下为什么构造方法不能有返回值,因为有返回值也没有意义,new 语句永远返回的是创建出来的对象的引用
package supermarket;
public class MerchandiseV2WithConstructor {
public String name;
public String id;
public int count;
public double soldPrice;
public double purchasePrice;
public MerchandiseV2WithConstructor(String name, String id, int count,
double soldPrice, double purchasePrice) {
this.name = name;
this.id = id;
this.count = count;
this.soldPrice = soldPrice;
this.purchasePrice = purchasePrice;
}
public void describe() {
System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
+ "。商品进价是" + purchasePrice + "。商品库存量是" + count +
"。销售一个的毛利润是" + (soldPrice - purchasePrice));
}
public double calculateProfit() {
double profit = soldPrice - purchasePrice;
// if(profit <= 0){
// return 0;
// }
return profit;
}
public double buy(int count) {
if (this.count < count) {
return -1;
}
return this.count -= count;
}
}
构造方法如果是private的,那么就只有当前的类可以调用这个构造方法
有些时候,会把所有的构造方法都定义成private的,然后使用静态方法调用构造方法
同样的,这样的好处是可以通过代码,检查每个属性值是否合法。
public static MerchandiseV2 createMerchandise(String name, String id, int count,double soldPrice, double purchasePrice) {
if (soldPrice < 0 || purchasePrice < 0) {
return null;
}
return new MerchandiseV2(name, id, count, soldPrice, purchasePrice);
}
8、构造方法的重载和相互调用
package supermarket;
public class MerchandiseV2 {
public String name;
public String id;
// >> TODO 构造方法执行前,会执行给局部变量赋初始值的操作
// >> TODO 我们说过,所有的代码都必须在方法里,那么这种给成员变赋初始值的代码在哪个方法里?怎么看不到呢?
// TODO 原来构造方法在内部变成了<init>方法。学习就是要脑洞大,敢想敢试,刨根问底。
public int count = 999;// 999/0;
public double soldPrice;
public double purchasePrice;
// >> TODO 构造方法(constructor)的重载和普通方法一样
public MerchandiseV2(String name, String id, int count, double soldPrice, double purchasePrice) {
this.name = name;
this.id = id;
this.count = count;
this.soldPrice = soldPrice;
this.purchasePrice = purchasePrice;
// soldPrice = 9/0;
}
// >> TODO 在构造方法里才能调用重载的构造方法。语法为this(实参列表)
// >> TODO 构造方法不能自己调用自己,这会是一个死循环
// >> TODO 在调用重载的构造方法时,不可以使用成员变量。因为用语意上讲,这个对象还没有被初始化完成,处于中间状态。
// >> TODO 在构造方法里才能调用重载的构造方法时,必须是方法的第一行。后面可以继续有代码
public MerchandiseV2(String name, String id, int count, double soldPrice) {
// double purPrice = soldPrice * 0.8;
// this(name, id, count, soldPrice, purchasePrice);
this(name, id, count, soldPrice, soldPrice * 0.8);
// double purPrice = soldPrice * 0.8;
}
// >> TODO 因为我们添加了构造方法之后,Java就不会再添加无参数的构造方法。如果需要的话,我们可以自己添加这样的构造方法
public MerchandiseV2() {
this("无名", "000", 0, 1, 1.1);
}
public void describe() {
System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
+ "。商品进价是" + purchasePrice + "。商品库存量是" + count +
"。销售一个的毛利润是" + (soldPrice - purchasePrice));
}
public double calculateProfit() {
double profit = soldPrice - purchasePrice;
// if(profit <= 0){
// return 0;
// }
return profit;
}
public double buy(int count) {
if (this.count < count) {
return -1;
}
return this.count -= count;
}
}
9、静态变量、常量和方法
静态变量使用 static 修饰符
静态变量如果不赋值,Java也会给它赋以其类型的初始值
静态变量一般使用全大写字母加下划线分割。这是一个习惯用法
所有的代码都可以使用静态变量,只要根据防范控制符的规范,这个静态变量对其可见即可
比如 public 的静态变量,所有的代码都可以使用它
但是如果没有public修饰符,只能当前包的代码能使用它
使用自己的使用静态变量的时候,直接写静态变量名字。
虽然静态成员也可以使用“对象.静态成员”的形式进行调用,但通常不建议用这样的形式,因为这样容易混淆静态成员和非静态成员。
静态数据与静态方法的作用通常是为了提供共享数据或方法,如数学计算公式等,以static声明并实现,这样当需要使用时,直接使用类名调用这些静态成员即可。尽管使用这种方式调用静态成员比
较方便,但静态成员同样遵循着public、private 和protected修饰符的约束。
在静态方法中不可以使用 this关键字。
在静态方法中不可以直接调用非静态方法。
import com.geekbang.supermarket.MerchandiseV2WithStaticVariable;
import static com.geekbang.supermarket.MerchandiseV2WithStaticVariable.*;
public class MerchandiseV2DescAppMain {
public static void main(String[] args) {
MerchandiseV2WithStaticVariable merchandise = new MerchandiseV2WithStaticVariable
("书桌", "DESK9527", 40, 999.9, 500);
merchandise.describe();
// >> TODO 使用import static来引入一个静态变量,就可以直接用静态变量名访问了
// TODO import static也可以使用通配符*来引入一个类里所有静态变量
System.out.println(DISCOUNT_FOR_VIP);
}
}
packagesupermarket;
public class MerchandiseV2WithStaticVariable {
public String name;
public String id;
public int count;
public double soldPrice;
public double purchasePrice;
// >> TODO 静态变量使用 static 修饰符
// >> TODO 静态变量如果不赋值,Java也会给它赋以其类型的初始值
// >> TODO 静态变量一般使用全大写字母加下划线分割。这是一个习惯用法
// >> TODO 所有的代码都可以使用静态变量,只要根据防范控制符的规范,这个静态变量对其可见即可
// TODO 比如 public 的静态变量,所有的代码都可以使用它
public static double DISCOUNT_FOR_VIP = 0.95;
// TODO 但是如果没有public修饰符,只能当前包的代码能使用它
static int STATIC_VARIABLE_CURR_PACKAGE_ONLY = 100;
public MerchandiseV2WithStaticVariable(String name, String id, int count, double soldPrice, double purchasePrice) {
this.name = name;
this.id = id;
this.count = count;
this.soldPrice = soldPrice;
this.purchasePrice = purchasePrice;
}
public String getName() {
return name;
}
public MerchandiseV2WithStaticVariable(String name, String id, int count, double soldPrice) {
this(name, id, count, soldPrice, soldPrice * 0.8);
}
public MerchandiseV2WithStaticVariable() {
this("无名", "000", 0, 1, 1.1);
}
public void describe() {
System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
+ "。商品进价是" + purchasePrice + "。商品库存量是" + count +
"。销售一个的毛利润是" + (soldPrice - purchasePrice) + "。折扣为" + DISCOUNT_FOR_VIP);
}
public double calculateProfit() {
double profit = soldPrice - purchasePrice;
// if(profit <= 0){
// return 0;
// }
return profit;
}
public double buy() {
return buy(1);
}
public double buy(int count) {
return buy(count, false);
}
public double buy(int count, boolean isVIP) {
if (this.count < count) {
return -1;
}
this.count -= count;
double totalCost = count * soldPrice;
if (isVIP) {
// >> TODO 使用自己的使用静态变量的时候,直接写静态变量名字。
return totalCost * DISCOUNT_FOR_VIP;
} else {
return totalCost;
}
}
}
9.1、静态方法
使用import static来引入一个静态方法,就可以直接用静态变量名访问了
import static也可以使用通配符*来引入一个类里所有静态变量
import static com.geekbang.supermarket.MerchandiseV2.getVIPDiscount;
System.out.println(getVIPDiscount());
// >> TODO 静态方法使用static修饰符。
// 静态方法的方法名没有约定全大写
public static double getVIPDiscount() {
// >> TODO 静态方法可以访问静态变量,包括自己类的静态变量和在访问控制符允许的别的类的静态变量
return DISCOUNT_FOR_VIP;
}
// >> TODO 静态变量使用 static 修饰符
public static double DISCOUNT_FOR_VIP = 0.95;
除了没有this,静态方法的定义和成员方法一样,也有方法名,返回值和参数
静态方法没有this自引用,它不属于某个实例,调用的时候也无需引用,直接用类名调用,所以它也不能直接访问成员变量
当然在静态方法里面,也可以自己创建对象,或者通过参数,获得对象的引用,进而调用方法和访问成员变量
静态方法只是没有this自引用的方法而已。
public static double getDiscountOnDiscount(LittleSuperMarket littleSuperMarket) {
double activityDiscount = littleSuperMarket.activityDiscount;
return DISCOUNT_FOR_VIP * activityDiscount;
}
9.2、静态方法 的重载
// >> TODO 静态方法的重载也是一样的,方法签名不同即可:方法名+参数类型
// >> TODO 判断调用哪个方法,也是根据调用时参数匹配决定的。
public static double getDiscount() {
return BASE_DISCOUNT;
}
public static double getDiscount(boolean isVIP) {
// double abc = true ? "" : 0;
double svipDiscount = (isVIP ? VIP_DISCOUNT : 1);
return getDiscount() * svipDiscount;
}
9.3、static 代码块和 static 变量初始化
使用某个静态变量的代码块必须在静态变量后面
public static double SVIP_DISCOUNT;
static {
BASE_DISCOUNT = 0.99;
VIP_DISCOUNT = 0.85;
SVIP_DISCOUNT = 0.75;
// >> TODO 静态代码块里当然可以有任意的合法代码
System.out.println("静态代码块1里的SVIP_DISCOUNT" + SVIP_DISCOUNT);
// >> TODO 这段代码在哪个方法中呢?<clinit>,即class init。会在每个class初始化的时候被调用一次
// SVIP_DISCOUNT = 9/0;
}
// >> TODO 其实给静态变量赋值也是放在代码块里的,static代码块可以有多个,是从上向下顺序执行的。
// TODO 可以认为这些代码都被组织到了一个clinit方法里
// public static double WHERE_AM_I = 9/0;
static {
SVIP_DISCOUNT = 0.1;
System.out.println("静态代码块2里的SVIP_DISCOUNT" + SVIP_DISCOUNT);
}
10、类的主方法
主方法是类的入口点,它定义了程序从何处开始;主方法提供对程序流向的控制,Java 编译器通过主方法来执行程序。主方法的语法如下:
public static void main(StringD args){
//方法体
}
在主方法的定义中可以看到其具有以下特性:
主方法是静态的,所以如要直接在主方法中调用其他方法,则该方法必须也是静态的。
主方法没有返回值。
主方法的形参为数组。其中args[0]~args[n]分别代表程序的第一个 参数到第n个参数,可以使用args.length获取参数的个数。