代表创建了一个Hero对象但是也仅仅是创建了一个对象,没有办法访问它为了访问这个对象,会使用引用来代表这个对象
Hero h = new Hero();h这个变量是Hero类型,又叫做引用 =的意思指的h这个引用代表右侧创建的对象 “代表” 在面向对象里,又叫做“指向”[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zY0qEn1Y-1599114930145)(en-resource://database/930:1)]
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 public static void main(String[] args) { //创建一个对象 new Hero(); //使用一个引用来指向这个对象 Hero h = new Hero(); } }引用有多个,但是对象只有一个。 在这个例子里,所有引用都指向了同一个对象。 对象就像 “房产”, 引用就像"房产证" 房产证的复印件可以有多张,但是真正的"房产" 只有这么一处[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEYLIMXJ-1599114930159)(en-resource://database/932:1)]
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 public static void main(String[] args) { //使用一个引用来指向这个对象 Hero h1 = new Hero(); Hero h2 = h1; //h2指向h1所指向的对象 Hero h3 = h1; Hero h4 = h1; Hero h5 = h4; //h1,h2,h3,h4,h5 五个引用,都指向同一个对象 } }第8行,引用garen指向新创建的对象(对象1) 第9行,同一个引用garen指向新创建的对象(对象2)这个时候,对象1,就没有任何引用指向了换句话说,就没有任何手段控制和访问该对象,那么该对象就变得没有意义。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TjMgA74S-1599114930164)(en-resource://database/934:1)]
package charactor; public class Hero { public String name; protected float hp; public static void main(String[] args) { Hero garen = new Hero(); garen = new Hero(); } }如代码,问题:h4所指向的对象和h2所指向的对象,是否是同一个对象?
package charactor; public class Hero { public String name; protected float hp; public static void main(String[] args) { Hero h1 = new Hero(); Hero h2 = new Hero(); Hero h3; Hero h4; h3 = h1; h4 = h3; } }如果所示,h4指向h3,最终间接地指向了对象1而h2指向的是对象2,所以h4和h2不是指向同一个对象[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MAfbcVzX-1599114930168)(en-resource://database/936:1)]
在LOL中,武器是物品的一种,也是有名称和价格的所以在设计类的时候,可以让武器继承物品,从而继承名称和价格属性
物品类Item 有属性 name,price
public class Item { String name; int price; }武器类: Weapon不继承Item的写法 独立设计 name和price属性 同时多了一个属性 damage 攻击力
public class Weapon{ String name; int price; int damage; //攻击力 }这一次Weapon继承Item虽然Weapon自己没有设计name和price,但是通过继承Item类,也具备了name和price属性
public class Weapon extends Item{ int damage; //攻击力 public static void main(String[] args) { Weapon infinityEdge = new Weapon(); infinityEdge.damage = 65; //damage属性在类Weapon中新设计的 infinityEdge.name = "无尽之刃";//name属性,是从Item中继承来的,就不需要重复设计了 infinityEdge.price = 3600; } }方法的重载指的是方法名一样,但是参数类型不一样
有一种英雄,叫做物理攻击英雄 ADHero为ADHero 提供三种方法
public void attack() public void attack(Hero h1) public void attack(Hero h1, Hero h2)方法名是一样的,但是参数类型不一样在调用方法attack的时候,会根据传递的参数类型以及数量,自动调用对应的方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K0D40o9L-1599114930172)(en-resource://database/942:1)]
public class ADHero extends Hero { public void attack() { System.out.println(name + " 进行了一次攻击 ,但是不确定打中谁了"); } public void attack(Hero h1) { System.out.println(name + "对" + h1.name + "进行了一次攻击 "); } public void attack(Hero h1, Hero h2) { System.out.println(name + "同时对" + h1.name + "和" + h2.name + "进行了攻击 "); } public static void main(String[] args) { ADHero bh = new ADHero(); bh.name = "赏金猎人"; Hero h1 = new Hero(); h1.name = "盖伦"; Hero h2 = new Hero(); h2.name = "提莫"; bh.attack(h1); bh.attack(h1, h2); } }如果要攻击更多的英雄,就需要设计更多的方法,这样类会显得很累赘,像这样:
public void attack(Hero h1) public void attack(Hero h1,Hero h2) public void attack(Hero h1,Hero h2,Hero h3)这时,可以采用可变数量的参数只需要设计一个方法
public void attack(Hero ...heros)即可代表上述所有的方法了在方法里,使用操作数组的方式处理参数heros即可
public class ADHero extends Hero { public void attack() { System.out.println(name + " 进行了一次攻击 ,但是不确定打中谁了"); } // 可变数量的参数 public void attack(Hero... heros) { for (int i = 0; i < heros.length; i++) { System.out.println(name + " 攻击了 " + heros[i].name); } } public static void main(String[] args) { ADHero bh = new ADHero(); bh.name = "赏金猎人"; Hero h1 = new Hero(); h1.name = "盖伦"; Hero h2 = new Hero(); h2.name = "提莫"; bh.attack(h1); bh.attack(h1, h2); } }通过一个类创建一个对象,这个过程叫做实例化实例化是通过调用构造方法(又叫做构造器)实现的
方法名和类名一样(包括大小写) 没有返回类型 实例化一个对象的时候,必然调用构造方法
public class Hero { String name; float hp; float armor; int moveSpeed; // 方法名和类名一样(包括大小写) // 没有返回类型 public Hero() { System.out.println("实例化一个对象的时候,必然调用构造方法"); } public static void main(String[] args) { //实例化一个对象的时候,必然调用构造方法 Hero h = new Hero(); } }Hero类的构造方法是
public Hero(){ }这个无参的构造方法,如果不写,就会默认提供一个
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 //这个无参的构造方法,如果不写, //就会默认提供一个无参的构造方法 // public Hero(){ // System.out.println("调用Hero的构造方法"); // } public static void main(String[] args) { Hero garen = new Hero(); garen.name = "盖伦"; garen.hp = 616.28f; garen.armor = 27.536f; garen.moveSpeed = 350; Hero teemo = new Hero(); teemo.name = "提莫"; teemo.hp = 383f; teemo.armor = 14f; teemo.moveSpeed = 330; } }一旦提供了一个有参的构造方法 同时又没有显式的提供一个无参的构造方法 那么默认的无参的构造方法,就“木有了“
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 //有参的构造方法 //默认的无参的构造方法就失效了 public Hero(String heroname){ name = heroname; } public static void main(String[] args) { Hero garen = new Hero("盖伦"); Hero teemo = new Hero(); //无参的构造方法“木有了” } }和普通方法一样,构造方法也可以重载
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 //带一个参数的构造方法 public Hero(String heroname){ name = heroname; } //带两个参数的构造方法 public Hero(String heroname,float herohp){ name = heroname; hp = herohp; } public static void main(String[] args) { Hero garen = new Hero("盖伦"); Hero teemo = new Hero("提莫",383); } }this这个关键字,相当于普通话里的“我” 小明说 “我吃了” 这个时候,“我” 代表小明 小红说 “我吃了” 这个时候,“我” 代表小红 "我"代表当前人物this这个关键字,相当于普通话里的“我” this即代表当前对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3i9BRar-1599114930175)(en-resource://database/1269:1)]
通过this关键字访问对象的属性
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 //参数名和属性名一样 //在方法体中,只能访问到参数name public void setName1(String name){ name = name; } //为了避免setName1中的问题,参数名不得不使用其他变量名 public void setName2(String heroName){ name = heroName; } //通过this访问属性 public void setName3(String name){ //name代表的是参数name //this.name代表的是属性name this.name = name; } public static void main(String[] args) { Hero h =new Hero(); h.setName1("teemo"); System.out.println(h.name); h.setName2("garen"); System.out.println(h.name); h.setName3("死歌"); System.out.println(h.name); } }变量有两种类型 基本类型 和类类型 参数也是变量,所以传参分为 基本类型传参 类类型传参
基本类型传参在方法内,无法修改方法外的基本类型参数
public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 public Hero(){ } //回血 public void huixue(int xp){ hp = hp + xp; //回血完毕后,血瓶=0 xp=0; } public Hero(String name,float hp){ this.name = name; this.hp = hp; } public static void main(String[] args) { Hero teemo = new Hero("提莫",383); //血瓶,其值是100 int xueping = 100; //提莫通过这个血瓶回血 teemo.huixue(xueping); System.out.println(xueping); } }如果一个变量是基本类型比如 int hp = 50; 我们就直接管hp叫变量=表示赋值的意思。 如果一个变量是类类型比如 Hero h = new Hero(); 我们就管h叫做引用。 =不再是赋值的意思 =表示指向的意思 比如 Hero h = new Hero(); 这句话的意思是引用h,指向一个Hero对象
类类型又叫引用 第24行的引用 teemo与 第17行的引用hero,是不同的引用 通过调用garen.attack(teemo, 100); 使得这两个引用都指向了同一个对象 所以在第18行hero.hp = hero.hp - damage; 就使得该对象的hp值,发生了变化 因此第25行,打印该对象的Hp值就是变化后的值
public class Hero { String name; // 姓名 float hp; // 血量 float armor; // 护甲 int moveSpeed; // 移动速度 public Hero(String name, float hp) { this.name = name; this.hp = hp; } // 攻击一个英雄,并让他掉damage点血 public void attack(Hero hero, int damage) { hero.hp = hero.hp - damage; } public static void main(String[] args) { Hero teemo = new Hero("提莫", 383); Hero garen = new Hero("盖伦", 616); garen.attack(teemo, 100); System.out.println(teemo.hp); } }包: package 把比较接近的类,规划在同一个包下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6AlURq5w-1599114930177)(en-resource://database/1271:1)]
package charactor; //在最开始的地方声明该类所处于的包名 public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 }使用同一个包下的其他类,直接使用即可 但是要使用其他包下的类,必须import
package charactor; //Weapon类在其他包里,使用必须进行import import property.Weapon; public class Hero { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 //装备一把武器 public void equip(Weapon w){ } }成员变量有四种修饰符 private 私有的 package/friendly/default 不写 protected 受保护的 public 公共的
比如public 表示公共的
public String name;而maxHP 没有修饰符即代package/friendly/default
float maxHP类和类之间的关系有如下几种: 以Hero为例
自身:指的是Hero自己
同包子类:ADHero这个类是Hero的子类,并且和Hero处于同一个包下
不同包子类:Support这个类是Hero的子类,但是在另一个包下
同包类: GiantDragon 这个类和Hero是同一个包,但是彼此没有继承关系
其他类:Item这个类,在不同包,也没有继承关系的类 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-th34iBZf-1599114930178)(en-resource://database/1273:1)]
使用private修饰属性 自身:是可以访问的 同包子类:不能继承 不同包子类:不能继承 同包类:不能访问 其他包类:不能访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-97fF5bvL-1599114930179)(en-resource://database/1275:1)]
package charactor; import property.Weapon; public class Hero { //属性id是private的,只有Hero自己可以访问 //子类不能继承 //其他类也不能访问 private int id; String name; float hp; float armor; int moveSpeed; public void equip(Weapon w) { } }没有修饰符即代表package/friendly/defaultfloat maxHP; 血量上限 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJbyW58a-1599114930180)(en-resource://database/1277:1)]
package charactor; import property.Weapon; public class Hero { private int id; String name; // 无修饰符的属性 hp // 自己可以访问 // 同包子类可以继承 // 不同包子类不能继承 // 同包类可以访问 // 不同包类不能访问 float hp; float armor; int moveSpeed; public void equip(Weapon w) { } }受保护的修饰符protected float hp; 血量 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gn4kHDTB-1599114930181)(en-resource://database/1279:1)]
注: 红色字体,表示不可行
package charactor; import property.Weapon; public class Hero { private int id; String name; // protected饰符的属性 hp // 自己可以访问 // 同包子类可以继承 // 不同包子类可以继承 // 同包类可以访问 // 不同包类不能访问 protected float hp; float armor; int moveSpeed; public void equip(Weapon w) { } }公共的修饰符 public String name; 姓名 任何地方,都可以访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fms5P3Yd-1599114930183)(en-resource://database/1281:1)]
package charactor; import property.Weapon; public class Hero { private int id; // public的属性 name // 自己可以访问 // 同包子类可以继承 // 不同包子类可以继承 // 同包类可以访问 // 不同包类可以访问 public String name; protected float hp; float armor; int moveSpeed; public void equip(Weapon w) { } }[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uUNhsSVJ-1599114930184)(en-resource://database/1283:1)]
那么什么情况该用什么修饰符呢? 从作用域来看,public能够使用所有的情况。 但是大家在工作的时候,又不会真正全部都使用public,那么到底什么情况该用什么修饰符呢?
属性通常使用private封装起来方法一般使用public用于被调用会被子类继承的方法,通常使用protectedpackage用的不多,一般新手会用package,因为还不知道有修饰符这个东西再就是 作用范围最小原则 简单说,能用private就用private,不行就放大一级,用package,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了
当一个属性被static修饰的时候,就叫做类属性,又叫做静态属性
当一个属性被声明成类属性,那么所有的对象,都共享一个值
与对象属性对比: 不同对象的 对象属性 的值都可能不一样。 比如盖伦的hp 和 提莫的hp 是不一样的。 但是所有对象的类属性的值,都是一样的
类属性: 又叫做静态属性 对象属性: 又叫实例属性,非静态属性
如果一个属性声明成类属性,那么所有的对象,都共享这么一个值给英雄设置一个类属性叫做“版权" (copyright), 无论有多少个具体的英雄,所有的英雄的版权都属于 Riot Games公司
package charactor; public class Hero { public String name; //实例属性,对象属性,非静态属性 protected float hp; static String copyright;//类属性,静态属性 public static void main(String[] args) { Hero garen = new Hero(); garen.name = "盖伦"; Hero.copyright = "版权由Riot Games公司所有"; System.out.println(garen.name); System.out.println(garen.copyright); Hero teemo = new Hero(); teemo.name = "提莫"; System.out.println(teemo.name); System.out.println(teemo.copyright); } }访问类属性有两种方式
对象.类属性 teemo.copyright 类.类属性 Hero.copyright这两种方式都可以访问类属性,访问即修改和获取,但是建议使用第二种 类.类属性 的方式进行,这样更符合语义上的理解
如果一个属性,每个英雄都不一样,比如name,这样的属性就应该设计为对象属性,因为它是跟着对象走的,每个对象的name都是不同的
如果一个属性,所有的英雄都共享,都是一样的,那么就应该设计为类属性。比如血量上限,所有的英雄的血量上限都是 9999,不会因为英雄不同,而取不同的值。 这样的属性,就适合设计为类属性
类方法: 又叫做静态方法
对象方法: 又叫实例方法,非静态方法
访问一个对象方法,必须建立在有一个对象的前提的基础上访问类方法,不需要对象的存在,直接就访问
类方法: 又叫做静态方法
对象方法: 又叫实例方法,非静态方法
访问一个对象方法,必须建立在有一个对象的前提的基础上访问类方法,不需要对象的存在,直接就访问
和访问类属性一样,调用类方法也有两种方式
对象.类方法 garen.battleWin(); 类.类方法 Hero.battleWin();这两种方式都可以调用类方法,但是建议使用第二种 类.类方法 的方式进行,这样更符合语义上的理解。并且在很多时候,并没有实例,比如在前面练习的时候用到的随机数的获取办法
Math.random()random()就是一个类方法,直接通过类Math进行调用,并没有一个Math的实例存在。
如果在某一个方法里,调用了对象属性,比如
public String getName(){ return name; }name属性是对象属性,只有存在一个具体对象的时候,name才有意义。 如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法
如果一个方法,没有调用任何对象属性,那么就可以考虑设计为类方法,比如
public static void printGameDuration(){ System.out.println("已经玩了10分50秒"); }printGameDuration 打印当前玩了多长时间了,不和某一个具体的英雄关联起来,所有的英雄都是一样的。 这样的方法,更带有功能性色彩就像取随机数一样,random()是一个功能用途的方法
Math.random()对象属性初始化有3种
声明该属性的时候初始化构造方法中初始化初始化块 package charactor; public class Hero { public String name = "some hero"; //声明该属性的时候初始化 protected float hp; float maxHP; { maxHP = 200; //初始化块 } public Hero(){ hp = 100; //构造方法中初始化 } }类属性初始化有2种
声明该属性的时候初始化静态初始化块 package charactor; public class Hero { public String name; protected float hp; float maxHP; //物品栏的容量 public static int itemCapacity=8; //声明的时候 初始化 static{ itemCapacity = 6;//静态初始化块 初始化 } public Hero(){ } public static void main(String[] args) { System.out.println(Hero.itemCapacity); } }对象属性的初始化有三种方式故意把初始化块,放在构造方法下面,问题:这三种方式,谁先执行?谁后执行?
package charactor; public class Hero { public String name = "some hero"; public Hero(){ name = "one hero"; } { name = "the hero"; } }声明 块 构造方法 one hero
LOL里有一个怪叫大龙GiantDragon,只有一只,所以该类,只能被实例化一次
单例模式又叫做 Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在。
GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new 得到新的实例。
GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取12行定义的对象,而且每一次都是获取同一个对象。 从而达到单例的目的。
这种单例模式又叫做饿汉式单例模式,无论如何都会创建一个实例
package charactor; public class GiantDragon { //私有化构造方法使得该类无法在外部通过new 进行实例化 private GiantDragon(){ } //准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个 private static GiantDragon instance = new GiantDragon(); //public static 方法,提供给调用者获取12行定义的对象 public static GiantDragon getInstance(){ return instance; } } package charactor; public class TestGiantDragon { public static void main(String[] args) { //通过new实例化会报错 // GiantDragon g = new GiantDragon(); //只能通过getInstance得到对象 GiantDragon g1 = GiantDragon.getInstance(); GiantDragon g2 = GiantDragon.getInstance(); GiantDragon g3 = GiantDragon.getInstance(); //都是同一个对象 System.out.println(g1==g2); System.out.println(g1==g3); } }懒汉式单例模式与饿汉式单例模式不同,只有在调用getInstance的时候,才会创建实例
package charactor; public class GiantDragon { //私有化构造方法使得该类无法在外部通过new 进行实例化 private GiantDragon(){ } //准备一个类属性,用于指向一个实例化对象,但是暂时指向null private static GiantDragon instance; //public static 方法,返回实例对象 public static GiantDragon getInstance(){ //第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象 if(null==instance){ instance = new GiantDragon(); } //返回 instance指向的对象 return instance; } }package charactor; public class GiantDragon { //私有化构造方法使得该类无法在外部通过new 进行实例化 private GiantDragon(){ } //准备一个类属性,用于指向一个实例化对象,但是暂时指向null private static GiantDragon instance; //public static 方法,返回实例对象 public static GiantDragon getInstance(){ //第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象 if(null==instance){ instance = new GiantDragon(); } //返回 instance指向的对象 return instance; } } package charactor; public class TestGiantDragon { public static void main(String[] args) { //通过new实例化会报错 // GiantDragon g = new GiantDragon(); //只能通过getInstance得到对象 GiantDragon g1 = GiantDragon.getInstance(); GiantDragon g2 = GiantDragon.getInstance(); GiantDragon g3 = GiantDragon.getInstance(); //都是同一个对象 System.out.println(g1==g2); System.out.println(g1==g3); } }饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载。如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式,是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量(鉴于同学们学习的进度,暂时不对线程的章节做展开)。使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
这个是面试的时候经常会考的点,面试题通常的问法是: 什么是单例模式?
回答的时候,要答到三元素
构造方法私有化
静态属性指向实例
public static的 getInstance方法,返回第二步的静态属性
枚举enum是一种特殊的类(还是类),使用枚举可以很方便的定义常量
比如设计一个枚举类型 季节,里面有4种常量
public enum Season { SPRING,SUMMER,AUTUMN,WINTER }一个常用的场合就是switch语句中,使用枚举来进行判断
注:因为是常量,所以一般都是全大写
public class HelloWorld { public static void main(String[] args) { Season season = Season.SPRING; switch (season) { case SPRING: System.out.println("春天"); break; case SUMMER: System.out.println("夏天"); break; case AUTUMN: System.out.println("秋天"); break; case WINTER: System.out.println("冬天"); break; } } } public enum Season { SPRING,SUMMER,AUTUMN,WINTER }假设在使用switch的时候,不是使用枚举,而是使用int,而int的取值范围就不只是1-4,有可能取一个超出1-4之间的值,这样判断结果就似是而非了。(因为只有4个季节)
但是使用枚举,就能把范围死死的限定在这四个当中
SPRING,SUMMER,AUTUMN,WINTER
public class HelloWorld { public static void main(String[] args) { int season = 5; switch (season) { case 1: System.out.println("春天"); break; case 2: System.out.println("夏天"); break; case 3: System.out.println("秋天"); break; case 4: System.out.println("冬天"); break; } } }借助增强型for循环,可以很方便的遍历一个枚举都有哪些常量
public class HelloWorld { public static void main(String[] args) { for (Season s : Season.values()) { System.out.println(s); } } }