10.方法的签名和重载
overload
方法的签名是一个类中的唯一标识
1 package com.geekbang.supermarket; 2 3 public class MerchandiseV2Overload { 4 5 public String name; 6 public String id; 7 public int count; 8 public double soldPrice; 9 public double purchasePrice; 10 11 public void init(String name, String id, int count, double soldPrice, double purchasePrice) { 12 this.name = name; 13 this.id = id; 14 this.count = count; 15 this.soldPrice = soldPrice; 16 this.purchasePrice = purchasePrice; 17 } 18 19 public void describe() { 20 System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice 21 + "。商品进价是" + purchasePrice + "。商品库存量是" + count + 22 "。销售一个的毛利润是" + (soldPrice - purchasePrice)); 23 } 24 25 public double calculateProfit() { 26 double profit = soldPrice - purchasePrice; 27 // if(profit <= 0){ 28 // return 0; 29 // } 30 return profit; 31 } 32 33 34 // >> TODO 重载的方法可以调用别的重载方法,当然也可以调用别的不重载的方法。 35 // >> TODO 实际上,像这种补充一些缺省的参数值,然后调用重载的方法,是重载的一个重要的使用场景。 36 // >> TODO 在这里我们举的例子就是这样的,但是不是语法要求一定要这样。重载的方法的方法体内代码可以随便写, 37 // TODO 可以不调用别的重载方法 38 public double buy() { 39 return buy(1); 40 } 41 42 43 public double buy(int count) { return buy(count, false); 44 } 45 46 // TODO 最后都补充好参数,调用参数最全的一个方法 47 public double buy(int count, boolean isVIP) { 48 if (this.count < count) { 49 return -1; 50 } 51 this.count -= count; 52 double totalCost = count * soldPrice; 53 if (isVIP) { 54 return totalCost * 0.95; 55 } else { 56 return totalCost; 57 } 58 } 59 60 }
// >> TODO 理解为什么返回值不是方法签名的一部分:不能帮助确定调用哪个方法。
重载的匹配规则
1 package com.geekbang; 2 3 import com.geekbang.supermarket.MerchandiseV2; 4 5 public class MerchandiseV2AppMain { 6 public static void main(String[] args) { 7 MerchandiseV2 merchandise = new MerchandiseV2(); 8 9 merchandise.init("书桌", "DESK9527", 40, 999.9, 500); 10 11 // >> TODO 使用int调用参数为double的方法 12 int count = 3; 13 // merchandise.buyDouble(count); 14 15 System.out.println("测试使用不完全匹配的参数调用重载方法"); 16 // >> TODO 依次使用byte, short, int, long, float, double 类型的参数调用buy方法,哪个方法会被调用呢? 17 // >> TODO 无论是否重载参数类型可以不完全匹配的规则是"实参数可以自动类型转换成形参类型" 18 // >> TODO 重载的特殊之处是,参数满足自动自动类型转换的方法有好几个,重载的规则是选择最"近"的去调用 19 20 byte countForOverride1 = 11; 21 merchandise.buy(countForOverride1); 22 23 double countForOverride = 11; 24 merchandise.buy(countForOverride); 25 26 short countForOverride2 = 11; 27 merchandise.buy(countForOverride2); 28 29 int countForOverride3 = 11; 30 merchandise.buy(countForOverride3); 31 32 float countForOverride4 = 11; 33 merchandise.buy(countForOverride4); 34 35 long countForOverride5 = 11; 36 merchandise.buy(countForOverride5); 37 } 38 }
测试使用不完全匹配的参数调用重载方法 buy(int)被调用了 buy(int,boolean)被调用了 buy(double)被调用了 buy(int)被调用了 buy(int,boolean)被调用了 buy(int)被调用了 buy(int,boolean)被调用了 buy(double)被调用了 buy(double)被调用了
11.构造方法
自动调用 调用哪一个方法
constructor
1 package com.geekbang.supermarket; 2 3 public class MerchandiseV2WithConstructor { 4 5 public String name; 6 public String id; 7 public int count; 8 public double soldPrice; 9 public double purchasePrice; 10 11 // >> TODO 构造方法(constructor)的方法名必须与类名一样,而且构造方法没有返回值。这样的方法才是构造方法。 12 // >> TODO 构造方法可以有参数,规则和语法于普通方法一样。使用时,参数传递给 new 语句后类名的括号后面。 13 // >> TODO 如果没有显示的添加一个构造方法,Java会给每个类都会默认自带一个无参数的构造方法。 14 // TODO 如果我们自己添加类构造方法,Java就不会再添加无参数的构造方法。这时候,就不能直接 new 一个对象不传递参数了(看例子) 15 // >> TODO 所以我们一直都在使用构造方法,这也是为什么创建对象的时候类名后面要有一个括号的原因。 16 // >> TODO 构造方法无法被点操作符调用或者在普通方法里调用,只能通过 new 语句在创建对象的时候,间接调用。 17 // TODO 理解一下为什么构造方法不能有返回值,因为有返回值也没有意义,new 语句永远返回的是创建出来的对象的引用 18 public MerchandiseV2WithConstructor(String name, String id, int count, 19 double soldPrice, double purchasePrice) { 20 this.name = name; 21 this.id = id; 22 this.count = count; 23 this.soldPrice = soldPrice; 24 this.purchasePrice = purchasePrice; 25 } 26 27 public void describe() { 28 System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice 29 + "。商品进价是" + purchasePrice + "。商品库存量是" + count + 30 "。销售一个的毛利润是" + (soldPrice - purchasePrice)); 31 } 32 33 public double calculateProfit() { 34 double profit = soldPrice - purchasePrice; 35 // if(profit <= 0){ 36 // return 0; 37 // } 38 return profit; 39 } 40 41 public double buy(int count) { 42 if (this.count < count) { 43 return -1; 44 } 45 return this.count -= count; 46 } 47 }
如果我们自己添加类构造方法,Java就不会再添加无参数的构造方法。
这时候,就不能直接 new 一个对象不传递参数了(看例子)
也可以创建多个构造函数 这也就是构造方法的重载 如下所示
1 // >> TODO 构造方法(constructor)的重载和普通方法一样 2 public MerchandiseV2(String name, String id, int count, double soldPrice, double purchasePrice) { 3 this.name = name; 4 this.id = id; 5 this.count = count; 6 this.soldPrice = soldPrice; 7 this.purchasePrice = purchasePrice; 8 // soldPrice = 9/0; 9 } 10 11 // >> TODO 在构造方法里才能调用重载的构造方法。语法为this(实参列表) 12 // >> TODO 构造方法不能自己调用自己,这会是一个死循环 13 // >> TODO 在调用重载的构造方法时,不可以使用成员变量。因为用语意上讲,这个对象还没有被初始化完成,处于中间状态。 14 // >> TODO 在构造方法里才能调用重载的构造方法时,必须是方法的第一行。后面可以继续有代码 15 public MerchandiseV2(String name, String id, int count, double soldPrice) { 16 // double purPrice = soldPrice * 0.8; 17 // this(name, id, count, soldPrice, purchasePrice); 18 this(name, id, count, soldPrice, soldPrice * 0.8); 19 // double purPrice = soldPrice * 0.8; 20 } 21 22 // >> TODO 因为我们添加了构造方法之后,Java就不会再添加无参数的构造方法。如果需要的话,我们可以自己添加这样的构造方法 23 public MerchandiseV2() { 24 this("无名", "000", 0, 1, 1.1); 25 26 }
构造方法里面可以相互调用
this(name, id, count, soldPrice, soldPrice * 0.8);
只有在构造方法里面才可以调用,不能自己调自己!
可以在构造方法之前赋值
1 public String name; 2 public String id; 3 // >> TODO 构造方法执行前,会执行给局部变量赋初始值的操作 4 // >> TODO 我们说过,所有的代码都必须在方法里,那么这种给成员变赋初始值的代码在哪个方法里?怎么看不到呢? 5 // TODO 原来构造方法在内部变成了<init>方法。学习就是要脑洞大,敢想敢试,刨根问底。 6 public int count = 999;// 999/0; 7 public double soldPrice; 8 public double purchasePrice;
类名.<init> 构造方法 成员变量赋值 的是在这里完成的
12.静态 static
Magic Number 要赋一个变量名
1 package com.geekbang.supermarket; 2 3 public class MerchandiseV2WithStaticVariable { 4 5 public String name; 6 public String id; 7 public int count; 8 public double soldPrice; 9 public double purchasePrice; 10 11 // >> TODO 静态变量使用 static 修饰符 12 // >> TODO 静态变量如果不赋值,Java也会给它赋以其类型的初始值 13 // >> TODO 静态变量一般使用全大写字母加下划线分割。这是一个习惯用法 14 // >> TODO 所有的代码都可以使用静态变量,只要根据防范控制符的规范,这个静态变量对其可见即可 15 // TODO 比如 public 的静态变量,所有的代码都可以使用它 16 public static double DISCOUNT_FOR_VIP = 0.95; 17 18 // TODO 但是如果没有public修饰符,只能当前包的代码能使用它 19 static int STATIC_VARIABLE_CURR_PACKAGE_ONLY = 100; 20 21 22 public MerchandiseV2WithStaticVariable(String name, String id, int count, double soldPrice, double purchasePrice) { 23 this.name = name; 24 this.id = id; 25 this.count = count; 26 this.soldPrice = soldPrice; 27 this.purchasePrice = purchasePrice; 28 } 29 30 public String getName() { 31 return name; 32 } 33 34 public MerchandiseV2WithStaticVariable(String name, String id, int count, double soldPrice) { 35 this(name, id, count, soldPrice, soldPrice * 0.8); 36 } 37 38 public MerchandiseV2WithStaticVariable() { 39 this("无名", "000", 0, 1, 1.1); 40 41 } 42 43 public void describe() { 44 System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice 45 + "。商品进价是" + purchasePrice + "。商品库存量是" + count + 46 "。销售一个的毛利润是" + (soldPrice - purchasePrice) + "。折扣为" + DISCOUNT_FOR_VIP); 47 } 48 49 public double calculateProfit() { 50 double profit = soldPrice - purchasePrice; 51 // if(profit <= 0){ 52 // return 0; 53 // } 54 return profit; 55 } 56 57 58 public double buy() { 59 return buy(1); 60 } 61 62 public double buy(int count) { 63 return buy(count, false); 64 } 65 66 public double buy(int count, boolean isVIP) { 67 if (this.count < count) { 68 return -1; 69 } 70 this.count -= count; 71 double totalCost = count * soldPrice; 72 if (isVIP) { 73 // >> TODO 使用自己的使用静态变量的时候,直接写静态变量名字。 74 return totalCost * DISCOUNT_FOR_VIP; 75 } else { 76 return totalCost; 77 } 78 } 79 }
静态变量就这一个,不随实例的创建而创建。
使用静态变量
import引入
1 package com.geekbang; 2 3 import com.geekbang.supermarket.MerchandiseV2WithStaticVariable; 4 import static com.geekbang.supermarket.MerchandiseV2WithStaticVariable.*; 5 6 public class MerchandiseV2DescAppMain { 7 public static void main(String[] args) { 8 MerchandiseV2WithStaticVariable merchandise = new MerchandiseV2WithStaticVariable 9 ("书桌", "DESK9527", 40, 999.9, 500); 10 11 merchandise.describe(); 12 13 // >> TODO 使用import static来引入一个静态变量,就可以直接用静态变量名访问了 14 // TODO import static也可以使用通配符*来引入一个类里所有静态变量 15 System.out.println(DISCOUNT_FOR_VIP); 16 } 17 }
静态变量一变都会变。
1 package com.geekbang; 2 3 import com.geekbang.supermarket.LittleSuperMarket; 4 import com.geekbang.supermarket.MerchandiseV2WithStaticVariable; 5 6 public class RunLittleSupperMarketAppMain { 7 public static void main(String[] args) { 8 // 创建一个小超市类 9 LittleSuperMarket littleSuperMarket = new LittleSuperMarket( 10 "有家小超市", "浦东新区世纪大道666号", 11 100, 200, 200); 12 13 System.out.println("下面请利润最高的商品自我介绍:"); 14 MerchandiseV2WithStaticVariable bigProfit = littleSuperMarket.getBiggestProfitMerchandise(); 15 bigProfit.describe(); 16 double cost1 = bigProfit.buy(10, true); 17 System.out.println("VIP 购买10个" + bigProfit.getName() + "的花费为" + cost1); 18 19 // >> TODO 使用别的类的静态变量的时候,需要使用完整形态:类名.静态变量名字 20 MerchandiseV2WithStaticVariable.DISCOUNT_FOR_VIP = 0.5; 21 bigProfit.describe(); 22 double cost2 = bigProfit.buy(10, true); 23 System.out.println("VIP 购买10个" + bigProfit.getName() + "的花费为" + cost2); 24 25 // >> TODO 静态变量在整个Java程序中只有一个(对比实例变量,是每个实例有一份 26 // TODO 所以静态变量一旦变化,所有使用这个静态变量的地方的值都会变 27 MerchandiseV2WithStaticVariable m0 = littleSuperMarket.getMerchandiseOf(0); 28 m0.describe(); 29 double cost3 = m0.buy(10, true); 30 System.out.println("VIP 购买10个" + m0.getName() + "的花费为" + cost3); 31 } 32 }
静态方法
也叫类方法:只能使用参数和静态变量,换而言之,就是没有this子引用的方法
1 // >> TODO 静态变量使用 static 修饰符 2 public static double DISCOUNT_FOR_VIP = 0.95; 3 4 // >> TODO 静态方法使用static修饰符。 5 // 静态方法的方法名没有约定俗称全大写 6 public static double getVIPDiscount() { 7 // >> TODO 静态方法可以访问静态变量,包括自己类的静态变量和在访问控制符允许的别的类的静态变量 8 return DISCOUNT_FOR_VIP; 9 } 10 11 // >> TODO 除了没有this,静态方法的定义和成员方法一样,也有方法名,返回值和参数 12 // >> TODO 静态方法没有this自引用,它不属于某个实例,调用的时候也无需引用,直接用类名调用,所以它也不能直接访问成员变量 13 // >> TODO 当然在静态方法里面,也可以自己创建对象,或者通过参数,获得对象的引用,进而调用方法和访问成员变量 14 // >> TODO 静态方法只是没有this自引用的方法而已。 15 public static double getDiscountOnDiscount(LittleSuperMarket littleSuperMarket) { 16 double activityDiscount = littleSuperMarket.activityDiscount; 17 return DISCOUNT_FOR_VIP * activityDiscount; 18 }
this-->类 and 静态---》类
用过方法的签名来重载静态方法
1 package com.geekbang.supermarket; 2 3 public class DiscountMgr { 4 5 public static double BASE_DISCOUNT = 0.99; 6 7 public static double VIP_DISCOUNT = 0.85; 8 9 public static double SVIP_DISCOUNT = 0.75; 10 11 // >> TODO 静态方法的重载也是一样的,方法签名不同即可:方法名+参数类型 12 // >> TODO 判断调用哪个方法,也是根据调用时参数匹配决定的。 13 public static double getDiscount() { 14 return BASE_DISCOUNT; 15 } 16 17 public static double getDiscount(boolean isVIP) { 18 // TODO >> 这节课这么无聊,我们顺带学一个三元操作符吧。 19 // TODO 三元操作符的返回类型就是冒号两边的类型,两边的类型要和等号左边的变量类型兼容 20 // double abc = true ? "" : 0; 21 22 double svipDiscount = (isVIP ? VIP_DISCOUNT : 1); 23 return getDiscount() * svipDiscount; 24 } 25 26 public static double getDiscount(int svipLevel) { 27 double ret = getDiscount() * VIP_DISCOUNT; 28 for (int i = 0; i < svipLevel; i++) { 29 ret *= SVIP_DISCOUNT; 30 } 31 return ret; 32 } 33 34 // 这节课这么无聊,我们顺带看几个不是那么正经的getDiscount吧 35 36 // >> TODO 返回值不算是方法签名,重载的方法可以有完全不同的返回值类型 37 public static void getDiscount(String s) { 38 System.out.println(s); 39 } 40 41 public static int getDiscount(int a, int b) { 42 return a > b ? a : b; 43 } 44 45 46 public static boolean getDiscount(int a, int b, int c) { 47 return a > b && b > c; 48 } 49 50 public static String getDiscount(long abc) { 51 return "" + abc; 52 } 53 54 public static void main(String[] args) { 55 getDiscount(1, 2); 56 } 57 58 }
静态代码块
1 package com.geekbang.supermarket; 2 3 public class DiscountMgr { 4 5 public static void main(String[] args) { 6 System.out.println("最终main 方法中使用的SVIP_DISCOUNT是" + SVIP_DISCOUNT); 7 } 8 9 public static double BASE_DISCOUNT; 10 11 public static double VIP_DISCOUNT; 12 13 // >> TODO 使用某个静态变量的代码块必须在静态变量后面 14 // >> TODO (但是仅仅赋值没有限制,很妖的语法哈,有些语法就应该在学会的第一时间忘掉它) 15 public static double SVIP_DISCOUNT; 16 17 18 static { 19 BASE_DISCOUNT = 0.99; 20 VIP_DISCOUNT = 0.85; 21 SVIP_DISCOUNT = 0.75; 22 23 // >> TODO 静态代码块里当然可以有任意的合法代码 24 System.out.println("静态代码块1里的SVIP_DISCOUNT" + SVIP_DISCOUNT); 25 26 // >> TODO 这段代码在哪个方法中呢?<clinit>,即class init。会在每个class初始化的时候被调用一次 27 // SVIP_DISCOUNT = 9/0; 28 } 29 30 // >> TODO 其实给静态变量赋值也是放在代码块里的,static代码块可以有多个,是从上向下顺序执行的。 31 // TODO 可以认为这些代码都被组织到了一个clinit方法里 32 // public static double WHERE_AM_I = 9/0; 33 34 // public static double SVIP_DISCOUNT; 35 36 static { 37 SVIP_DISCOUNT = 0.1; 38 System.out.println("静态代码块2里的SVIP_DISCOUNT" + SVIP_DISCOUNT); 39 } 40 }
<clinit> class init 静态代码块在class init
13.访问修饰符
- public:全局可见
- 缺省:当前包可见
- private:当前类可见
1 package com.geekbang.supermarket; 2 3 // >> TODO 类,静态方法,静态变量,成员变量,构造方法,成员方法都可以使用访问修饰符 4 public class MerchandiseV2 { 5 6 // >> TODO 成员变量应该都声明为private 7 // >> TODO 如果要读写这些成员变量,最好使用get set方法,这些方法应该是public的 8 // >> TODO 这样做的好处是,如果有需要,可以通过代码,检查每个属性值是否合法。 9 private String name; 10 private String id; 11 private int count; 12 private double soldPrice; 13 private double purchasePrice; 14 private NonPublicClassCanUseAnyName nonPublicClassCanUseAnyName; 15 public static double DISCOUNT = 0.1; 16 17 // >> TODO 构造方法如果是private的,那么就只有当前的类可以调用这个构造方法 18 public MerchandiseV2(String name, String id, int count, double soldPrice, double purchasePrice) { 19 this.name = name; 20 this.id = id; 21 this.count = count; 22 this.soldPrice = soldPrice; 23 this.purchasePrice = purchasePrice; 24 // soldPrice = 9/0; 25 } 26 27 // >> TODO 有些时候,会把所有的构造方法都定义成private的,然后使用静态方法调用构造方法 28 // >> TODO 同样的,这样的好处是可以通过代码,检查每个属性值是否合法。 29 public static MerchandiseV2 createMerchandise(String name, String id, int count, 30 double soldPrice, double purchasePrice) { 31 if (soldPrice < 0 || purchasePrice < 0) { 32 return null; 33 } 34 return new MerchandiseV2(name, id, count, soldPrice, purchasePrice); 35 } 36 37 public MerchandiseV2(String name, String id, int count, double soldPrice) { 38 this(name, id, count, soldPrice, soldPrice * 0.8); 39 } 40 41 public MerchandiseV2() { 42 this("无名", "000", 0, 1, 1.1); 43 } 44 45 // >> TODO public的方法类似一种约定,既然外面的代码可以使用,就意味着不能乱改。比如签名不能改之类的 46 public void describe() { 47 System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice 48 + "。商品进价是" + purchasePrice + "。商品库存量是" + count + 49 "。销售一个的毛利润是" + (soldPrice - purchasePrice)); 50 freeStyle(); 51 } 52 53 // >> TODO 对于private的方法,因为类外面掉不到,所以无论怎么改,也不会影响(直接影响)类外面的代码 54 private void freeStyle() { 55 56 } 57 58 public double calculateProfit() { 59 double profit = soldPrice - purchasePrice; 60 return profit; 61 } 62 63 public double buy(int count) { 64 if (this.count < count) { 65 return -1; 66 } 67 return this.count -= count; 68 } 69 70 public String getName() { 71 return name; 72 } 73 74 public void setName(String name) { 75 this.name = name; 76 } 77 78 public String getId() { 79 return id; 80 } 81 82 public void setId(String id) { 83 this.id = id; 84 } 85 86 public int getCount() { 87 return count; 88 } 89 90 public void setCount(int count) { 91 this.count = count; 92 } 93 94 public double getSoldPrice() { 95 return soldPrice; 96 } 97 98 public void setSoldPrice(double soldPrice) { 99 this.soldPrice = soldPrice; 100 } 101 102 public double getPurchasePrice() { 103 return purchasePrice; 104 } 105 106 public void setPurchasePrice(double purchasePrice) { 107 this.purchasePrice = purchasePrice; 108 } 109 }
private都可以配备一个static的方法。来控制实例是否非法
// >> TODO 构造方法如果是private的,那么就只有当前的类可以调用这个构造方法
private 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 有些时候,会把所有的构造方法都定义成private的,然后使用静态方法调用构造方法
// >> TODO 同样的,这样的好处是可以通过代码,检查每个属性值是否合法。
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);
}
只能在当前包里用
1 package com.geekbang.supermarket; 2 3 // >> TODO 非public的类,类名可以不和文件名相同 4 class NonPublicClassCanUseAnyName { 5 }
1 package com.geekbang.learn; 2 3 import java.math.BigDecimal; 4 import java.math.BigInteger; 5 import java.util.Scanner; 6 7 public class LearnScanner { 8 public static void main(String[] args) { 9 // TODO Scanner是一个方便的可以帮我们从标准输入读取并转换数据的类 10 // TODO 注释里 @since 1.5 表示它是从Java5才开始有的。 11 Scanner scanner = new Scanner(System.in); 12 13 // TODO 但是这并是说从Java5开始,这个类就没有变化过了 14 // TODO 在源代码里搜索一下@since,会发现很多方法是在后续的 Java 版本中加进去的 15 // TODO 但是private方法就不会有这个文档标示,因为private方法本来就不给用。 16 17 System.out.println("请输入一个巨大的正数"); 18 BigInteger bigInteger = scanner.nextBigInteger(); 19 System.out.println("请输入想给这个数加多少"); 20 BigInteger toBeAdd = scanner.nextBigInteger(); 21 System.out.println("结果为:" + bigInteger.add(toBeAdd)); 22 } 23 }
14.String
String 不可变immutable,用来存储的数据是private的,而且不提供人格修改内容的方法,string一旦生成就不可能被修改。
转自:https://blog.csdn.net/qq_40434646/article/details/81914843
Integer i = 128; Integer j = 128; System.out.println(i == j);//返回false
Integer m = 127; Integer n = 127; System.out.println(m == n);//返回true
1.如果Integer类型的两个数相等,如果范围在-128~127(默认),那么用“==”返回true,其余的范会false。
2.两个基本类型int进行相等比较,直接用==即可。
3.一个基本类型int和一个包装类型Integer比较,用==也可,比较时候,Integer类型做了拆箱操作。
4.Integer类型比较大小,要么调用Integer.intValue()转为基本类型用“==”比较,要么直接用equals比较。
1 package com.geekbang.learn; 2 3 public class LearnString { 4 public static void main(String[] args) { 5 String content = "01234567ABCDefgh"; 6 7 // 数组的length是属性,没有括号 A.length 8 // String的length()是个方法不是属性哦 9 System.out.println(content.length()); 10 11 // 其实是生成了一个新的String对象 12 System.out.println(content.toUpperCase()); 13 14 System.out.println(content.toLowerCase()); 15 16 // content指向对象的内容并没有变化 17 System.out.println(content); 18 19 System.out.println(content.charAt(1)); 20 21 // System.out.println(content.charAt(99)); 22 23 System.out.println(content.substring(5)); 24 25 System.out.println(content.substring(1, 5)); // 最后一个不包含 26 27 } 28 }
字符串用法:
package com.geekbang.learn; public class LearnString2 { public static void main(String[] args) { String content = "Orange_Apple_Banana"; char[] chars = content.toCharArray(); // 字符数组 for (int i = 0; i < chars.length; i++) { System.out.println(chars[i]); } String sp = "_"; String[] s = content.split(sp); // 分割 for (int i = 0; i < s.length; i++) { System.out.println(s[i]); } int indexOf = content.indexOf('_'); System.out.println(indexOf); System.out.println(content.substring(indexOf + 1, content.length())); int lastIndexOf = content.lastIndexOf("_"); System.out.println(lastIndexOf); System.out.println(content.substring(0, lastIndexOf)); // 是否包含字符串 System.out.println(content.contains("apple")); System.out.println(content.contains("Apple")); System.out.println(content.startsWith("Orange")); System.out.println(content.endsWith("Banana")); String content2 = "Orange_Apple_Banana"; String content3 = " orange_Apple_banana "; // TODO 两个String对象比较是否相等,一定要用equals方法 // 比较两个字符串是否相等 System.out.println(content.equals(content2)); System.out.println(content.equals(content3)); System.out.println(content3.trim()); System.out.println(content.equalsIgnoreCase(content3.trim())); // 把string前面和后面的额外空格去掉 } }
main方法也知识一个静态的,有String[]作为参数,没有返回值的方法而已,它的特殊性在于java可以吧main方法作为程序入口。
1 package com.geekbang.learn; 2 3 public class LearnMain { 4 public static void main(String[] args) { // 以空格为分隔符号 空格字符串以引号括住 5 System.out.println(args.length); 6 for (int i = 0; i < args.length; i++) { 7 System.out.println(args[i]); 8 } 9 } 10 }
1 package com.geekbang.learn; 2 3 public class InvokeMain { 4 public static void main(String[] args) { 5 System.out.println("进入了InvokeMain的main方法"); 6 LearnMain.main(args); 7 System.out.println("InvokeMain的main方法执行结束"); 8 } 9 }
System类
- in
- out
- 取系统当前时间
1 package com.geekbang.learn; 2 3 public class LearnSystem { 4 public static void main(String[] args) { 5 // 当前的毫秒数 6 long startMS = System.currentTimeMillis(); 7 8 int counter = 0; 9 for (int i = 0; i < 1000; i++) { 10 counter++; 11 } 12 13 long endMS = System.currentTimeMillis(); 14 System.out.println("程序执行使用了几个毫秒?" + (endMS - startMS)); 15 16 // 纳秒 17 long startNS = System.nanoTime(); 18 19 counter = 0; 20 for (int i = 0; i < 1000; i++) { 21 counter++; 22 } 23 24 long endNS = System.nanoTime(); 25 System.out.println("程序执行使用了几个纳秒?" + (endNS - startNS)); 26 } 27 }
StringBuild
是一个非常方便的用来拼接和处理字符串的类,和String不同的是,它是可变的。
操作不会生成新的StringBulid和String对象
1 package com.geekbang.learn; 2 3 public class LearnStringBuilder { 4 5 public static void main(String[] args) { 6 7 // TODO StringBuilder首先是可变的 8 // TODO 而且对它进行操作的方法,都会返回this自引用。这样我们就可以一直点下去,对String进行构造。 9 StringBuilder strBuilder = new StringBuilder(); 10 11 long longVal = 123456789; 12 13 // 可以append所有数据类型 14 strBuilder.append(true).append("abc").append(longVal); 15 16 System.out.println(strBuilder.toString()); 17 System.out.println(strBuilder.reverse().toString()); // 翻转 18 System.out.println(strBuilder.reverse().toString()); 19 System.out.println(strBuilder.toString()); 20 21 System.out.println(strBuilder.delete(0, 4).toString()); // 删除 22 23 System.out.println(strBuilder.insert(3,"LLLLL").toString()); // 插入 24 } 25 26 }
StringBuffer类
StringBuffer():返回一个空串,长度为16个字符,实现对字符的增删改查。
除了字符所占的空间外,另外加16个字符大小的缓冲区。
1 package com.geekbang.learn; 2 3 public class LearnStringBuffer { 4 public static void main(String[] args) { 5 String s = new String("This is A String"); 6 StringBuffer sb = new StringBuffer(s); 7 sb.insert(sb.length(), "BufferDemo").insert(sb.length(), 2); 8 System.out.println(sb.toString()); 9 } 10 }
- String : 不可变字符序列
- StringBuillder:可变字符序列、效率高、线程不安全
- StringBuffer:可变字符序列、效率低、线程安全
15.继承
- 子类继承了父类的方法和属性
- 使用子类的引用可以调用父类的共有方法
- 使用子类的引用可以访问父类的共有属性
- 就好像子类的引用可以一物体=二用,既可以当做父类的引用使用,又可以当做子类的引用使用。
// >> TODO 继承的语法就是在类名后面使用extends 加 要继承的类名
// >> TODO 被继承的类叫做父类(Parent Class),比如本例中的MerchandiseV2。
// >> TODO 继承者叫做子类(Sub Class),比如本例中的PhoneExtendsMerchandise。
// >> TODO Java中只允许一个类有一个直接的父类(Parent Class),即所谓的单继承
// >> TODO 没错,别的类也可以继承子类,比如可以有一个HuaweiPhone继承PhoneExtendsMerchandise
// TODO 这时候,HuaweiPhone就是PhoneExtendsMerchandise的子类了。
// >> TODO 子类继承了父类什么呢?所有的属性和方法。
// >> TODO 但是子类并不能访问父类的private的成员(包括方法和属性)。
组合 is-a 和 集成 has-a
package com.geekbang.supermarket;
// >> TODO 继承,其实表达的是一种"is-a"的关系,也就是说,在你用类构造的世界中,"子类是父类的一种特殊类别"
// >> TODO 组合和继承,是拿到一个问题,设计相应的Java类的时候,不得不面对的来自灵魂的拷问
// TODO "XX到底是YY的一种,还是只是组合了YY?","手机到底是手电筒的一种,还是组合了一个可以当手电的闪光灯?"
// TODO 在组合的情况下,怎么限制一次只能买五个手机呢?
// TODO 1)首先不能修改MerchandiseV2这个类,否则你会限制所有商品一次购买的数量
// TODO 2)其次,在现实情况下,这个类可能根本不受你控制,你无权修改其代码
// TODO 3)在每次调用buy方法的地方做限制,是不行的,
// TODO - 你无法控制别人怎么用你的类,
// TODO - 而且会面临到处复制代码的糟糕情况,
// TODO - 如果说限制改成10个,所有复制的代码都要改,程序员都应该很懒,这不是一个程序员该做的事情
// TODO 4)在只能修改手机类的情况下,我们可以提供一个buyPhone的方法,实现限制购买数量的逻辑。
// TODO 但是这样并不能阻止别人像下面这样调用merchandise的buy方法,这个方法是会修改库存的,所以还是无法硬性的限制一次性购买手机的数量
// TODO 我们来理清一下自己的核心诉求:针对手机,限制一次性购买的数量。必须限制死,必须不影响别的商品,必须只能改手机类的代码
// TODO 这时候,组合就无能为力了,继承可以发挥其应有的作用。
// >> TODO 继承不是组合,继承也不只是为了能简单的拿来父类的属性和方法。如果仅仅如此,原封不动的拿来主义,组合也能做到。
// TODO 继承也不是通过组合的方式来实现的。和组合相比,继承更像是"融合"
// TODO 所谓融合,即合二为一,可以互相影响。父类影响子类没问题,子类怎么影响父类呢?如何限制手机一次只能最多买五个?
public class PhoneExtendsMerchandise extends MerchandiseV2 {}
覆盖才是继承的精髓
// >> TODO 通过使用和父类方法签名一样,而且返回值也必须一样的方法,可以让子类覆盖(override)掉父类的方法
// >> TODO ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓下面才是继承的终极奥义↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// >> TODO 也就是说,子类并不是只能把父类的方法拿过来,而且可以通过覆盖来替换其中不适合子类的方法
// >> TODO ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑上面才是继承的终极奥义↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// >> TODO 题外话:属性是联动的,可能是有特殊意义的。
// TODO 所以直接给属性赋值是危险的,因为没有办法检查新的值是否有意义,也没法对这个修改做联动的修改
public double buy(int count) {
// TODO 这个方法里代码大部分和父类一样,肯定有方法解决
if (count > MAX_BUY_ONE_ORDER) {
System.out.println("购买失败,手机一次最多只能买" + MAX_BUY_ONE_ORDER + "个");
return -2;
}
if (this.count < count) {
System.out.println("购买失败,库存不够");
return -1;
}
this.count -= count;
double cost = count * soldPrice;
System.out.println("购买成功,花费为" + cost);
return cost;
}
返回值必须一样,不是类型兼容,而是必须一摸一样。
如果签名一样,但是返回值不一样,会是错误
public String getName() {
return name;
}
// >> TODO 覆盖可以覆盖掉父类的方法。同一个方法,不同的行为。
// >> TODO 这,就是多态!
// >> TODO 方法可以覆盖,属性访问不可以,所以这也是使用方法的一个原因。
// TODO 即使在父类里,只是一个简单的getName,但是这样做,子类就可以覆盖掉父类的方法
// TODO 方法不止眼前的代码,还有子类的覆盖。用方法,才能覆盖,才能多态。
public String getName() {
return this.brand + ":" + this.os + ":" + name;
}
替换父类代码 ---> super
父类可以多继承super可以是多个类的方法和访问public属性
子类:
// >> TODO 使用super可以调用父类的方法和属性(当然必须满足访问控制符的控制)
public double buy(int count) {
if (count > MAX_BUY_ONE_ORDER) {
System.out.println("购买失败,手机一次最多只能买" + MAX_BUY_ONE_ORDER + "个");
return -2;
}
return super.buy(count);
}
父类:
public double buy(int count) {
if (this.count < count) {
System.out.println("购买失败,库存不够");
return -1;
}
this.count -= count;
double cost = count * soldPrice;
System.out.println("购买成功,花费为" + cost);
return cost;
}
子类:
public void describe() {
System.out.println("此手机商品属性如下");
super.describe();
System.out.println("手机厂商为" + brand + ";系统为" + os + ";硬件配置如下:\n" +
"屏幕:" + screenSize + "寸\n" +
"cpu主频" + cpuHZ + " GHz\n" +
"内存" + memoryG + "Gb\n" +
"存储空间" + storageG + "Gb");
}
父类:
public void describe() {
System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
+ "。商品进价是" + purchasePrice + "。商品库存量是" + count +
"。销售一个的毛利润是" + (soldPrice - purchasePrice));
}
super并不会是父类的引用
// >> TODO super是子类和父类交流的桥梁,但是并不是父类的引用
// >> TODO 所以,super和this自引用不一样,不是简单可以模拟的(可以模拟的话不就成了组合了吗)
// public MerchandiseV2 getParent(){
// return super;
// }
public Phone getThisPhone(){
return this;
}
// >> TODO 使用super可以调用父类的public属性,但是super不是一个引用。
public void accessParentProps() {
System.out.println("父类里的name属性:" + super.name);
}
public void useSuper() {
// >> TODO super的用法就像是一个父类的引用。它是继承的一部分,像组合的那部分,但不是全部
super.describe();
super.buy(66);
System.out.println("父类里的count属性:" + super.count);
}
可以认为,创建子类对象的时候,也就同时创建了一个隐藏的父类对象,集成链上所有的数据
Super调用构造方法
private String name;
private String id;
private int count;
private double soldPrice;
private double purchasePrice;
public Phone(
String name, String id, int count, double soldPrice, double purchasePrice,
double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os
) {
// >> TODO 使用super调用父类的构造方法,必须是子类构造方法的第一个语句
// >> TODO 可以使用表达式
// >> TODO super调用构造方法,不可以使用super访问父类的属性和方法,不可以使用子类成员变量和方法
// >> TODO 可以使用静态变量和方法
// >> TODO 都是<init>方法,我们来看一下
super(name, id, count, soldPrice * 1.2, purchasePrice);
init(screenSize, cpuHZ, memoryG, storageG, brand, os);
}
public void init(double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os) {
this.screenSize = screenSize;
this.cpuHZ = cpuHZ;
this.memoryG = memoryG;
this.storageG = storageG;
this.brand = brand;
this.os = os;
}
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;
}
super this 都要放第一个 矛盾 可是使用init方法来解决,init是自己写的。
public Phone() {
super("无名", "000", 0, 1, 1.1); 父类有参数的构造方法
init(4.5, 4.6, 6, 128, "Uknown", "Uknown");
}
子类的构造方法会调用父类的构造方法,默认调用父类无参的构造方法
public Phone() {
init(4.5, 4.6, 6, 128, "Uknown", "Uknown");
}
默认调用父类方法
public MerchandiseV2() {
this("无名", "000", 0, 1, 1.1);
}
// >> TODO 可以用子类的引用给父类的引用赋值,也就是说,父类的引用可以指向子类的对象
MerchandiseV2 m = ph;
MerchandiseV2 m2 = new Phone(
"手机002", "Phone002", 100, 1999, 999,
4.5, 3.5, 4, 128, "索尼", "安卓"
);
// >> TODO 但是反之则不行,不能让子类的引用指向父类的对象。因为父类并没有子类的属性和方法呀
// >> TODO 重点
// >> TODO 因为子类继承了父类的方法和属性,所以父类的对象能做到的,子类的对象肯定能做到
// TODO 换句话说,我们可以在子类的对象上,执行父类的方法
// >> TODO 当父类的引用指向子类的实例(或者父类的实例),只能通过父类的引用,像父类一样操作子类的对象
// TODO 也就是说"名"的类型,决定了能执行哪些操作
// >> TODO ph和m都指向同一个对象,通过ph可以调用getBrand方法
// TODO 因为ph的类型是Phone,Phone里定义了getBrand方法
ph.getBrand();
只能根据引用类型来决定对象的操作。
// TODO 如果确定一个父类的引用指向的对象,实际上就是一个子类的对象(或者子类的子类的对象),可以强制类型转换
Phone aPhone = (Phone) m2;
// MerchandiseV2是Phone的父类,Phone是shellColorChangePhone的父类
ShellColorChangePhone shellColorChangePhone = new ShellColorChangePhone(
"手机002", "Phone002", 100, 1999, 999,
4.5, 3.5, 4, 128, "索尼", "安卓"
);
// TODO 父类的引用,可以指向子类的对象,即可以用子类(以及子类的子类)的引用给父类的引用赋值
MerchandiseV2 ccm = shellColorChangePhone;
// TODO 父类的引用,可以指向子类的对象。
// TODO 确定MerchandiseV2的引用ccm是指向的是Phone或者Phone的子类对象,那么可以强制类型转换
Phone ccp = (Phone) ccm;
// TODO 确定MerchandiseV2的引用ccm是指向的是ShellColorChangePhone或者ShellColorChangePhone的子类对象
// TODO 那么可以强制类型转换
ShellColorChangePhone scp = (ShellColorChangePhone) ccm;
// TODO 会出错,因为m2指向的是一个Phone类型的对象,不是ShellColorChangePhone的对象
ShellColorChangePhone notCCP = (ShellColorChangePhone) m2;
1 package com.geekbang; 2 3 import com.geekbang.supermarket.LittleSuperMarket; 4 5 public class PolymorphismAppMainSimple { 6 public static void main(String[] args) { 7 LittleSuperMarket superMarket = new LittleSuperMarket("大卖场", 8 "世纪大道1号", 500, 600, 100); 9 10 // >> TODO 虽然是用的父类的引用指向的不同类型的对象,调用getName方法时,实际执行的方法取决于对象的类型,而非引用的类型 11 // >> TODO 也就是说,能调用哪些方法,是引用决定的,具体执行哪个类的方法,是引用指向的对象决定的。 12 // TODO 这就是覆盖的精髓。覆盖是多态的一种,是最重要的一种。 13 // >> TODO 以getName为例,父类里有这个方法,所以肯定都可以调用,但是Phone覆盖了父类的getName方法 14 // TODO 之前我们使用子类的引用指向子类的对象,调用子类里覆盖父类的方法,比如getName,执行的是子类的getName方法,我们觉得很自然。 15 // TODO 这里变换的是,我们用父类的引用指向子类的对象,调用被子类覆盖的方法,实际执行的还是子类里的getName方法 16 // TODO 当我们用父类的引用指向一个Phone的实例,并调用getName方法时,实际调用的就是Phone类里定义的getName方法 17 System.out.println(superMarket.getMerchandiseOf(0).getName()); 18 System.out.println(); 19 System.out.println(superMarket.getMerchandiseOf(10).getName()); 20 // TODO 如果子类里没有覆盖这个方法,就去父类里找,父类里没有,就去父类的父类找。反之只要能让一个引用指向这个对象 21 // TODO 就说明这个对象肯定是这个类型或者其子类的的一个实例(否则赋值会发生ClassCastException),总归有父类兜底。 22 System.out.println(); 23 System.out.println(superMarket.getMerchandiseOf(100).getName()); 24 25 } 26 }
1 package com.geekbang; 2 3 import com.geekbang.supermarket.LittleSuperMarket; 4 5 public class PolymorphismAppMainComplex { 6 public static void main(String[] args) { 7 LittleSuperMarket superMarket = new LittleSuperMarket("大卖场", 8 "世纪大道1号", 500, 600, 100); 9 10 // >> TODO 不仅如此,我们看一个更复杂的例子,describe方法。 11 // TODO MerchandiseV2里的describe方法,调用了calculateProfit方法 12 // TODO Phone里的describe方法,覆盖了直接父类MerchandiseV2里的describe方法,并且使用super调用了父类的describe方法 13 // TODO ShellColorChangePhone里的describe方法,覆盖了直接父类Phone里的describe方法,并且使用super调用了父类的describe方法 14 // TODO 更复杂的是,ShellColorChangePhone还覆盖了间接父类MerchandiseV2里的calculateProfit方法 15 superMarket.getMerchandiseOf(0).describe(); 16 System.out.println(); 17 superMarket.getMerchandiseOf(10).describe(); 18 System.out.println(); 19 superMarket.getMerchandiseOf(100).describe(); 20 21 // >> TODO 总结:无论一个方法是使用哪个引用被调用的,"它都是在实际的对象上执行的"。执行的任何一个方法,都是这个对象所属的类的方法。 22 // TODO 如果没有,就去父类找,再没有,就去父类的父类找,依次寻找,知道找到。 23 24 // TODO 换个角度理解。我们一直说子类里又一个(特殊的)父类的对象。这时候,这个特殊的父类的对象里的this自引用,是子类的引用。 25 // TODO 那么自然的,即使是在继承自父类的代码里,去调用一个方法,也是先从子类开始,一层层继承关系的找。 26 27 // TODO 这也是Java选择单继承的重要原因之一。在多继承的情况下,如果使用不当,多态可能会非常复杂,以至于使用的代价超过其带来的好处。 28 29 } 30 }
静态多态 重载 Overload
动态多态 覆盖 Override
之前重载的时候,参数是用的自定义类型。现在理解了父类和子类的引用赋值关系,重载又多了一层复杂性
1 package com.geekbang; 2 3 import com.geekbang.supermarket.LittleSuperMarket; 4 import com.geekbang.supermarket.MerchandiseV2; 5 import com.geekbang.supermarket.Phone; 6 import com.geekbang.supermarket.ShellColorChangePhone; 7 8 public class OverloadTestAppMain { 9 public static void main(String[] args) { 10 LittleSuperMarket superMarket = new LittleSuperMarket("大卖场", 11 "世纪大道1号", 500, 600, 100); 12 MerchandiseV2 m = superMarket.getMerchandiseOf(100); 13 14 MerchandiseTest merchandiseTest = new MerchandiseTest(); 15 16 System.out.println("-----------1-------------"); 17 // TODO 重载调用哪个方法,和参数的引用类型相关,和引用实际指向的类型无关 18 merchandiseTest.testMerchandiseOverload(m); 19 merchandiseTest.testMerchandiseOverload((Phone) m); 20 merchandiseTest.testMerchandiseOverload((ShellColorChangePhone) m); 21 // TODO 甚至是个null也可以,但是要用强制类型转换,告诉Java这个类型是什么,否则找不到一个唯一的方法去调用 22 // TODO 重载的参数类型,相同位置,不一定要有继承或者兼容的关系,完全free style 23 merchandiseTest.testMerchandiseOverload(""); 24 25 System.out.println("-----------2-------------"); 26 27 System.out.println(); 28 // >> TODO 引用本身是null没关系,确定调用哪个方法只需要引用的类型。这叫做静态多态。即在编译期就知道该调用哪个方法 29 m = null; 30 merchandiseTest.testMerchandiseOverload(m); 31 merchandiseTest.testMerchandiseOverload((Phone) m); 32 merchandiseTest.testMerchandiseOverload((ShellColorChangePhone) m); 33 34 System.out.println("-----------3-------------"); 35 36 // >> TODO 如果引用类型没有完全匹配的,则会根据继承关系,沿着参数当前类型,向下撸 37 merchandiseTest.testMerchandiseOverloadNotExactlyMatchType((ShellColorChangePhone) null); 38 39 40 // >> TODO 重载总结:静态多态,调用的方法和参数实际指向的对象无关,只和引用本身的类型相关。 41 // >> TODO 因为调用时参数类型是确定的,所以,在编译期间就可以明确的知道哪个方法会被调用。如果有多种可能,则会有编译错误 42 // >> TODO 如果没有类型完全匹配的候选,则根据类型的继承关系向下撸着找。找到最贴近参数类型的那个方法 43 // >> TODO 无论是静态方法,还是成员方法,重载寻找方法的顺序是一样的,在这里就不赘述了 44 // TODO (再提一句多继承,Java没有多继承,是前辈给我们的馈赠。保护了发际线。心疼隔壁CPP的程序员) 45 46 47 } 48 }
1 package com.geekbang; 2 3 import com.geekbang.supermarket.MerchandiseV2; 4 import com.geekbang.supermarket.Phone; 5 import com.geekbang.supermarket.ShellColorChangePhone; 6 7 public class MerchandiseTest { 8 9 // TODO 之前重载的时候,参数是用的自定义类型。现在理解了父类和子类的引用赋值关系,重载又多了一层复杂性 10 11 public void testMerchandiseOverload(MerchandiseV2 me) { 12 System.out.println("参数为MerchandiseV2的testMerchandiseOverload 被调用了"); 13 } 14 15 public void testMerchandiseOverload(Phone ph) { 16 System.out.println("参数为Phone的testMerchandiseOverload 被调用了"); 17 } 18 19 public void testMerchandiseOverload(ShellColorChangePhone shellColorChangePhone) { 20 System.out.println("参数为ShellColorChangePhone的testMerchandiseOverload 被调用了"); 21 } 22 23 public void testMerchandiseOverload(String str) { 24 System.out.println("参数为String的testMerchandiseOverload 被调用了"); 25 } 26 27 public void testMerchandiseOverloadNotExactlyMatchType(MerchandiseV2 me) { 28 System.out.println("参数为MerchandiseV2的testMerchandiseOverloadNotExactlyMatchType 被调用了"); 29 } 30 31 // public void testMerchandiseOverloadNotExactlyMatchType(Phone ph) { 32 // System.out.println("参数为Phone的testMerchandiseOverloadNotExactlyMatchType 被调用了"); 33 // } 34 35 public void testMerchandiseOverloadNotExactlyMatchType(String str) { 36 System.out.println("参数为String的testMerchandiseOverloadNotExactlyMatchType 被调用了"); 37 } 38 39 }
instanceof操作
1 package com.geekbang; 2 3 import com.geekbang.supermarket.LittleSuperMarket; 4 import com.geekbang.supermarket.MerchandiseV2; 5 import com.geekbang.supermarket.Phone; 6 import com.geekbang.supermarket.ShellColorChangePhone; 7 8 public class InstanceOfTestAppMain { 9 public static void main(String[] args) { 10 int merchandiseCount = 600; 11 LittleSuperMarket superMarket = new LittleSuperMarket("大卖场", 12 "世纪大道1号", 500, merchandiseCount, 100); 13 14 // >> TODO instanceof 操作符,可以判断一个引用指向的对象是否是某一个类型或者其子类 15 // TODO 是则返回true,否则返回false 16 for(int i =0;i<merchandiseCount;i++){ 17 MerchandiseV2 m = null;// superMarket.getMerchandiseOf(i); 18 if(m instanceof MerchandiseV2){ 19 // TODO 先判断,再强制类型转换,比较安全 20 MerchandiseV2 ph = (MerchandiseV2)m; 21 System.out.println(ph.getName()); 22 }else { 23 System.out.println("not an instance"); 24 } 25 } 26 27 // >> TODO 如果引用是null,则肯定返回false 28 } 29 }
Protected 继承专属的访问控制
同一个包可见+子类可见
1 package com.geekbang.onlinemarket; 2 3 import com.geekbang.supermarket.Phone; 4 5 public class OnlineSpecialPhone extends Phone { 6 7 public OnlineSpecialPhone(String name, String id, int count, double soldPrice, double purchasePrice, double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os) { 8 super(name, id, count, soldPrice, purchasePrice, screenSize, cpuHZ, memoryG, storageG, brand, os); 9 this.screenSize = 99; 10 } 11 12 // >> TODO 子类覆盖父类的方法,不可以用可见性更低的修饰符,但是可以用更高的修饰符 13 // >> TODO 原因是什么呢? 14 public String getNameOfPhone(){ 15 return super.getNameOfPhone(); 16 } 17 }
final
final修饰类:不可被继承
final修饰方法: 不可被子类覆盖
final修饰变量:不可被赋值(修改)
final修饰变量 在 static块赋值只能赋值一次。
final只能指向这个引用,引用的值不能修改,但是引用本身的值可以修改。
静态方法的继承
没有的静态方法可以话执行的话,执行父类的静态方法
签名和返回值必须一致
1 package com.geekbang; 2 3 import com.geekbang.supermarket.LittleSuperMarket; 4 import com.geekbang.supermarket.MerchandiseV2; 5 import com.geekbang.supermarket.Phone; 6 import com.geekbang.supermarket.ShellColorChangePhone; 7 8 public class StaticMethodDoesNotBelieveOverride { 9 public static void main(String[] args) { 10 LittleSuperMarket superMarket = new LittleSuperMarket("大卖场", 11 "世纪大道1号", 500, 600, 100); 12 13 14 System.out.println("-----1------"); 15 16 17 // >> TODO 静态方法可以被继承 18 MerchandiseV2.staticMethod(); 19 Phone.staticMethod(); 20 ShellColorChangePhone.staticMethod(); 21 22 System.out.println("-----2------"); 23 24 MerchandiseV2 m = superMarket.getMerchandiseOf(100); 25 26 // >> TODO 用引用调用静态方法没有覆盖 27 m.staticMethod(); 28 ((Phone) m).staticMethod(); 29 ((ShellColorChangePhone) m).staticMethod(); 30 // TODO 有些东西,学会就应该马上忘掉,比如上面使用引用调用静态方法的内容 31 32 System.out.println("-----3------"); 33 34 ((MerchandiseV2) null).staticMethod(); 35 ((Phone) null).staticMethod(); 36 ((ShellColorChangePhone) null).staticMethod(); 37 // TODO 有些东西,学会就应该马上忘掉,比如上面使用有类型的null引用调用静态方法的内容 38 39 } 40 }
父类可以强转成子类
null是引用的缺省值,强转给null一个类型信息
Object
没有成员变量
hashcode 表示对象的特征值的int整数
哈希吗 散列吗
equals判断两个对象从逻辑上是否相对
需要覆盖
// >> TODO hashCode 和 equals是我们最常覆盖的两个方法
// >> TODO 覆盖的原则是,equals为true,hashCode就应该相等。这是一种约定俗成的规范
// >> TODO 即equals为true是hashCode相等的充分非必要条件,hashCode相等是equals为true的必要不充分条件
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MerchandiseV2)) return false;
MerchandiseV2 that = (MerchandiseV2) o;
return this.getCount() == that.getCount() &&
Double.compare(that.getSoldPrice(), getSoldPrice()) == 0 &&
Double.compare(that.getPurchasePrice(), getPurchasePrice()) == 0 &&
getName().equals(that.getName()) &&
getId().equals(that.getId());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getId(), getCount(), getSoldPrice(), getPurchasePrice());
}