JAVA学习笔记
总结自how2j.cn
JAVA基础
HelloWorld
下载JDK→配置环境变量→检查配置是否成功java -version
创建源文件→编写基础输出命令System.out.println("hello world");
→编译javac
面向对象
类:
java所有的代码都是运行在类里面的
定义:类相当于参数和方法的集合
public class HelloWorld{}
-
public
表示这是一个可以公开访问的类 -
class
表示这是一个类 -
HelloWorld
表示类的名字,每个单词的首字母大写
方法
主方法:
你会写很多代码,总有第一行执行的代码,这就是主方法,每个类都可以有主方法
args 表示运行参数,在本例中没有使用到。
public static void main(String[] args){
System.out.println("hello world");
}
变量
基本变量类型:
整型:
类型 | 长度 |
---|---|
byte | 8位 |
short | 16位 |
int | 32位 |
long | 64位 |
字符型:
类型 | 长度 |
---|---|
char | 16位 |
浮点型:
类型 | 长度 |
---|---|
float | 32位 |
double | 64位 |
布尔型:
类型 | 长度 |
---|---|
boolean | 1位 |
String类型:
String类型其实并不是基本类型,但是它因广泛的被使用,常常被误以为是一种基本类型。
类型转换:
转换规则:
精度高的数据类型就像容量大的杯子,可以放更大的数据
精度低的数据类型就像容量小的杯子,只能放更小的数据
小杯子往大杯子里倒东西,大杯子怎么都放得下
大杯子往小杯子里倒东西,有的时候放的下,有的时候就会有溢出
需要注意的一点是
虽然short和char都是16位的,长度是一样的
但是彼此之间,依然需要进行强制转换
变量命名规则:
注:在命名的时候,尽量使用完整的单词进行命名,比如name,moveSpeed,而不是使用缩写 n,m。
1. 变量命名只能使用字母 数字 $ _
2. 变量第一个字符 只能使用 字母 $ _
3. 变量第一个字符 不能使用数字
4. 不能只使用关键字,但是可以包含关键字
作用域:
字段,属性,Field:
当一个变量被声明在类下面
变量就叫做字段 或者属性、成员变量、Field
比如变量i,就是一个属性。
那么从第2行这个变量声明的位置开始,整个类都可以访问得到
所以其作用域就是从其声明的位置开始的整个类
参数:
如果一个变量,是声明在一个方法上的,就叫做参数
参数的作用域即为该方法内的所有代码
其他方法不能访问该参数
类里面也不能访问该参数
局部变量:
声明在方法内的变量,叫做局部变量
其作用域在声明开始的位置,到其所处于的块结束位置
final:
当一个变量被final修饰的时候,该变量只有一次赋值的机会
数组
定义:数组是一个固定长度的,包含了相同类型数据的容器
声明数组:
public class HelloWorld {
public static void main(String[] args) {
// 声明一个数组
int[] a;
}
}
- int[] a; 声明了一个数组变量。
- []表示该变量是一个数组
- int 表示数组里的每一个元素都是一个整数
- a 是变量名
- 但是,仅仅是这一句声明,不会创建数组
- 有时候也会写成int a[]; 没有任何区别,就是你看哪种顺眼的问题
创建数组:
public class HelloWorld {
public static void main(String[] args) {
int[] a;//声明一个引用
a = new int[5]; //创建一个长度是5的数组,并且使用引用a指向该数组
int[] b = new int[5]; //声明的同时,指向一个数组
}
}
创建数组的时候,要指明数组的长度。new int[5]
引用概念:
- 如果变量代表一个数组,比如a,我们把a叫做引用
- 与基本类型不同 ,
int c = 5;
这叫给c赋值为5 - 声明一个引用
int[] a; a = new int[5];
让a这个引用,指向数组
数组空间分配与赋值:
分配空间与赋值分步进行:
public class HelloWorld {
public static void main(String[] args) {
int[] a = new int[5]; //分配了长度是5的数组,但是没有赋值
//没有赋值,那么就会使用默认值
//作为int类型的数组,默认值是0
System.out.println(a[0]);
//进行赋值
a[0] = 100;
a[1] = 101;
a[2] = 103;
a[3] = 120;
a[4] = 140;
}
}
分配空间,同时赋值 :
public class HelloWorld {
public static void main(String[] args) {
//写法一: 分配空间同时赋值
int[] a = new int[]{100,102,444,836,3236};
//写法二: 省略了new int[],效果一样
int[] b = {100,102,444,836,3236};
//写法三:同时分配空间,和指定内容
//在这个例子里,长度是3,内容是5个,产生矛盾了
//所以如果指定了数组的内容,就不能同时设置数组的长度
int[] c = new int[3]{100,102,444,836,3236};
}
}
增强型for循环:
注:增强型for循环只能用来取值,却不能用来修改数组里的值
public class HelloWorld {
public static void main(String[] args) {
int values [] = new int[]{18,62,68,82,65,9};
//常规遍历
for (int i = 0; i < values.length; i++) {
int each = values[i];
System.out.println(each);
}
//增强型for循环遍历
for (int each : values) {
System.out.println(each);
}
}
}
复制数组:
把一个数组的值,复制到另一个数组中
System.arraycopy(src, srcPos, dest, destPos, length)
- src: 源数组
- srcPos: 从源数组复制数据的起始位置
- dest: 目标数组
- destPos: 复制到目标数组的起始位置
- length: 复制的长度
public class HelloWorld {
public static void main(String[] args) {
int a [] = new int[]{18,62,68,82,65,9};
int b[] = new int[3];//分配了长度是3的空间,但是没有赋值
//通过数组赋值把,a数组的前3位赋值到b数组
//方法一: for循环
for (int i = 0; i < b.length; i++) {
b[i] = a[i];
}
//方法二: System.arraycopy(src, srcPos, dest, destPos, length)
//src: 源数组
//srcPos: 从源数组复制数据的起始位置
//dest: 目标数组
//destPos: 复制到目标数组的启始位置
//length: 复制的长度
System.arraycopy(a, 0, b, 0, 3);
//把内容打印出来
for (int i = 0; i < b.length; i++) {
System.out.print(b[i] + " ");
}
}
}
Arrays:
Arrays是针对数组的工具类,可以进行 排序,查找,复制填充等功能。
关键字 | 简介 |
---|---|
copyOfRange | 数组复制 |
toString() | 转换为字符串 |
sort | 排序 |
binarySearch | 搜索 |
equals | 判断是否相同 |
fill | 填充 |
数组复制:
copyOfRange(int[] original, int from, int to);
- 第一个参数表示源数组
- 第二个参数表示开始位置(取得到)
- 第三个参数表示结束位置(取不到)
与使用System.arraycopy进行数组复制类似的, Arrays提供了一个copyOfRange方法进行数组复制。不同的是System.arraycopy,需要事先准备好目标数组,并分配长度。 copyOfRange 只需要源数组就就可以了,通过返回值,就能够得到目标数组了。
转换为字符串:
String content = Arrays.toString(int[] original);
- 参数表示源数组
如果要打印一个数组的内容,就需要通过for循环来挨个遍历,逐一打印
但是Arrays提供了一个toString()方法,直接把一个数组,转换为字符串,这样方便观察数组的内容
排序
Arrays.sort(int[] original);
- 参数表示源数组
搜索:
Arrays.binarySearch(int[] original,int targ));
- 第一个参数表示源数组
- 第二个参数表示搜索目标
查询元素出现的位置
需要注意的是,使用binarySearch进行查找之前,必须使用sort进行排序
如果数组中有多个相同的元素,查找结果是不确定的
判断是否相同:
Arrays.equals(int[] original1, int[] original2)
- 两个参数表示需要判断的数组
比较两个数组的内容是否一样
第二个数组的最后一个元素是8,和第一个数组不一样,所以比较结果是false
填充:
Arrays.fill(int[] original, int targ);
- 第一个参数表示源数组
- 第二个参数表示填充内容
使用同一个值,填充整个数组,会覆盖原有的所有数据。
类和对象
引用:
概念:如果一个变量的类型是 类类型,而非基本类型,那么该变量又叫做引用。
引用和指向:
new Hero();
代表创建了一个Hero对象
但是也仅仅是创建了一个对象,没有办法访问它
为了访问这个对象,会使用引用来代表这个对象
Hero h = new Hero();
h这个变量是Hero类型,又叫做引用
=的意思指的h这个引用代表右侧创建的对象
“代表” 在面向对象里,又叫做“指向”
引用和对象:
多个引用,一个对象
- 引用有多个,但是对象只有一个。
- 对象就像 “房产”, 引用就像"房产证"。房产证的复印件可以有多张,但是真正的"房产" 只有这么一处
注:如果一个对象没有任何引用指向了换句话说,就没有任何手段控制和访问该对象,那么该对象就变得没有意义。
继承
extends
虽然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;
}
}
注:一个子类只能继承一个父类,但一个父类可以有多个子类
方法重载
方法名一样的,但参数类型不一样
在调用方法attack的时候,会根据传递的参数类型以及数量,自动调用对应的方法
可变数量的参数:
如果要攻击更多的英雄,就需要设计更多的方法,这样类会显得很累赘,像这样:
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(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访问属性
- 通过this关键字访问对象的属性
-
通过this调用其他的构造方法
- 如果要在一个构造方法中,调用另一个构造方法,可以使用this()
包:package
- 把比较接近的类,规划在同一个包下
- 使用其他包下的类,必须import
- 使用同一个包下的其他类,直接使用即可
- 但是要使用其他包下的类,必须import
修饰符
修饰符 | 自身 | 同子包类 | 不同子包类 | 同包类 | 其他类 |
---|---|---|---|---|---|
private私有的 | 访问 | ||||
package不写 | 访问 | 继承 | 访问 | ||
protected受保护的 | 访问 | 继承 | 继承 | 访问 | |
public公共的 | 访问 | 继承 | 继承 | 访问 | 访问 |
那么什么情况该用什么修饰符呢?
从作用域来看,public能够使用所有的情况。 但是大家在工作的时候,又不会真正全部都使用public,那么到底什么情况该用什么修饰符呢?
- 属性通常使用private封装起来
- 方法一般使用public用于被调用
- 会被子类继承的方法,通常使用protected
- package用的不多,一般新手会用package,因为还不知道有修饰符这个东西
再就是作用范围最小原则
简单说,能用private就用private,不行就放大一级,用package,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了
类属性
当一个属性被static修饰的时候,就叫做类属性,又叫做静态属性
当一个属性被声明成类属性,那么所有的对象,都共享一个值
访问类属性:
访问类属性有两种方式
- 对象.类属性
teemo.copyright
- 类.类属性
Hero.copyright
这两种方式都可以访问类属性,访问即修改和获取,但是建议使用第二种 类.类属性 的方式进行,这样更符合语义上的理解
类方法
类方法: 又叫做静态方法
对象方法: 又叫实例方法,非静态方法
访问一个对象方法,必须建立在有一个对象的前提的基础上
访问类方法,不需要对象的存在,直接就访问
访问类方法:
和访问类属性一样,调用类方法也有两种方式
- 对象.类方法
garen.battleWin();
- 类.类方法
Hero.battleWin();
这两种方式都可以调用类方法,但是建议使用第二种 类.类方法 的方式进行,这样更符合语义上的理解。
-
如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法
-
如果一个方法,没有调用任何对象属性,那么就可以考虑设计为类方法
-
这样的方法,更带有功能性色彩
单例模式
定义:单例模式又叫做 Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在
饿汉式:
GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new 得到新的实例。
GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取8行定义的对象,而且每一次都是获取同一个对象。 从而达到单例的目的。
这种单例模式又叫做饿汉式单例模式,无论如何都会创建一个实例
package charactor;
public class GiantDragon {
//私有化构造方法使得该类无法在外部通过new 进行实例化
private GiantDragon(){
}
//准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个
private static GiantDragon instance = new GiantDragon();
//public static 方法,提供给调用者获取8行定义的对象
public static GiantDragon getInstance(){
return instance;
}
}
懒汉式:
懒汉式单例模式与饿汉式单例模式不同,只有在调用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;
}
}
什么时候使用饿汉式,什么时候使用懒汉式?
饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载。
如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式,是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量。
使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
单例模式三元素 :
什么是单例模式?
回答的时候,要答到三元素
- 构造方法私有化
- 静态属性指向实例
- public static的 getInstance方法,返回第二步的静态属性
枚举类型
预先定义的常量:
枚举enum是一种特殊的类(还是类),使用枚举可以很方便的定义常量
比如设计一个枚举类型 季节,里面有4种常量
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER
}
注:因为是常量,所以一般都是全大写
使用枚举的好处:
假设在使用switch的时候,不是使用枚举,而是使用int,而int的取值范围就不只是1-4,有可能取一个超出1-4之间的值,这样判断结果就似是而非了。(因为只有4个季节)
但是使用枚举,就能把范围死死的限定在这四个当中而不会出现奇怪的 第5季
遍历枚举 :
借助增强型for循环,可以很方便的遍历一个枚举都有哪些常量
public class HelloWorld {
public static void main(String[] args) {
for (Season s : Season.values()) {
System.out.println(s);
}
}
}
接口与继承
接口:
AD接口 ,声明一个方法 physicAttack 物理攻击,但是没有方法体,是一个“空”方法
package charactor;
public interface AD {
//物理伤害
public void physicAttack();
}
实现某个接口,就相当于承诺了某种约定
所以,实现了AD这个接口,就必须提供AD接口中声明的方法physicAttack()
实现 在语法上使用关键字 implements
package charactor;
public class ADHero extends Hero implements AD{
@Override
public void physicAttack() {
System.out.println("进行物理攻击");
}
}
对象转型:
所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换
类型转换有时候会成功,有时候会失败
子类转父类(向上转型) :
所有的子类转换为父类,都是说得通的
Hero h = new Hero();
ADHero ad = new ADHero();
h = ad;
父类转子类(向下转型) :
父类转子类,有的时候行,有的时候不行,所以必须进行强制转换。
强制转换的意思就是 转换有风险,风险自担。
能够转换的案列:
1. Hero h =new Hero();
2. ADHero ad = new ADHero();
3. h = ad;
4. ad = (ADHero) h;
- 第3行,是子类转父类,一定可以的
- 第4行,就是父类转子类,所以要进行强转。
- h这个引用,所指向的对象是ADHero, 所以第4行,就会把ADHero转换为ADHero,就能转换成功。
不能转换的案例:
1. Hero h =new Hero();
2. ADHero ad = new ADHero();
3. Support s =new Support();
4. h = s;
5. ad = (ADHero)h;
- 第4行,是子类转父类,是可以转换成功的
- 第5行,是把h引用所指向的对象 Support,转换为ad引用的类型ADHero。 从语义上讲,把物理攻击英雄,当成辅助英雄来用,说不通,所以会强制转换失败,并且抛出异常
注:没有继承关系的两个类,不能互相转换
实现类转换成接口(向上转型) :
引用ad指向的对象是ADHero类型,这个类型实现了AD接口
从语义上来讲,把一个ADHero当做AD来使用,而AD接口只有一个physicAttack方法,这就意味着转换后就有可能要调用physicAttack方法,而ADHero一定是有physicAttack方法的,所以转换是能成功的。
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;
}
}
- 7行: 把一个ADHero类型转换为AD接口
接口转换成实现类(向下转型) :
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;
ADHero adHero = (ADHero) adi;
ADAPHero adapHero = (ADAPHero) adi;
adapHero.magicAttack();
}
}
- 10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
- 12行: adi实际上是指向一个ADHero的,所以能够转换成功
- 14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。
注:假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的
instanceof :
instanceof Hero 判断一个引用所指向的对象,是否是Hero类型,或者Hero的子类
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
APHero ap = new APHero();
Hero h1= ad;
Hero h2= ap;
//判断引用h1指向的对象,是否是ADHero类型
System.out.println(h1 instanceof ADHero);
//判断引用h2指向的对象,是否是APHero类型
System.out.println(h2 instanceof APHero);
//判断引用h1指向的对象,是否是Hero的子类型
System.out.println(h1 instanceof Hero);
}
}
重写:
- 子类可以继承父类的对象方法
- 在继承后,重复提供该方法,就叫做方法的重写 又叫覆盖 override
如果没有重写这样的机制,也就是说LifePotion这个类,一旦继承了Item,所有方法都不能修改了。
但是LifePotion又希望提供一点不同的功能,为了达到这个目的,只能放弃继承Item,重新编写所有的属性和方法,然后在编写effect的时候,做一点小改动.
这样就增加了开发时间和维护成本
多态:
-
操作符的多态
“+” 可以作为算数运算,也可以作为字符串连接 -
类的多态
父类引用指向子类对象
类的多态条件 :
- 父类(接口)引用指向子类对象
- 调用的方法有重写
使用多态 :
如果物品的种类特别多,那么就需要设计很多的方法比如useArmor,useWeapon等等
这个时候采用多态来解决这个问题
设计一个方法叫做useItem,其参数类型是Item
- 如果是使用血瓶,调用该方法
- 如果是使用魔瓶,还是调用该方法
无论英雄要使用什么样的物品,只需要一个方法即可
package charactor;
import property.Item;
import property.LifePotion;
import property.MagicPotion;
public class Hero {
public String name;
protected float hp;
public void useItem(Item i){
i.effect();
}
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
LifePotion lp =new LifePotion();
MagicPotion mp =new MagicPotion();
garen.useItem(lp);
garen.useItem(mp);
}
}
隐藏:
与重写类似,方法的重写是子类覆盖父类的对象方法
隐藏,就是子类覆盖父类的类方法
super:
super能够调用父类方法与属性
public ADHero(String name){
super(name);
System.out.println("AD Hero的构造方法");
}
public int getMoveSpeed(){
return this.moveSpeed;
}
public int getMoveSpeed2(){
return super.moveSpeed;
}
Object:
Object类是所有类的父类
Object提供的方法:
-
toString()
- Object类提供一个toString方法,所以所有的类都有toString方法
toString()的意思是返回当前对象的字符串表达
通过 System.out.println 打印对象就是打印该对象的toString()返回值
- Object类提供一个toString方法,所以所有的类都有toString方法
-
finalize()
-
当一个对象没有任何引用指向的时候,它就满足垃圾回收的条件
当它被垃圾回收的时候,它的finalize() 方法就会被调用。
finalize() 不是开发人员主动调用的方法,而是由虚拟机JVM调用的。
-
-
equals()
-
equals() 用于判断两个对象的内容(值)是否相同
-
这不是Object的方法,但是用于判断两个对象是否相同
更准确的讲,用于判断两个引用,是否指向了同一个对象 -
hashCode()
-
hashCode方法返回一个对象的哈希值
-
线程同步相关方法
- wait()
- notify()
- notifyAll()
-
getClass()
-
getClass()会返回一个对象的类对象
final:
final修饰类 :
当Hero被修饰成final的时候,表示Hero不能够被继承
其子类会出现编译错误
final修饰方法 :
Hero的useItem方法被修饰成final,那么该方法在ADHero中,不能够被重写
final修饰基本类型变量 :
final修饰基本类型变量,表示该变量只有一次赋值机会
16行进行了赋值,17行就不可以再进行赋值了
final修饰引用 :
final修饰引用
h引用被修饰成final,表示该引用只有1次指向对象的机会
所以17行会出现编译错误
但是,依然通过h引用修改对象的属性值hp,因为hp并没有final修饰
package charactor;
public class Hero extends Object {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
public static void main(String[] args) {
final Hero h;
h =new Hero();
h =new Hero();
h.hp = 5;
}
}
常量:
常量指的是可以公开,直接访问,不会变化的值
比如 itemTotalNumber 物品栏的数量是6个
抽象类:
在类中声明一个方法,这个方法没有实现体,是一个“空”方法
这样的方法就叫抽象方法,使用修饰符“abstract"
当一个类有抽象方法的时候,该类必须被声明为抽象类
为Hero增加一个抽象方法 attack,并且把Hero声明为abstract的。
APHero,ADHero,ADAPHero是Hero的子类,继承了Hero的属性和方法。
但是各自的攻击手段是不一样的,所以继承Hero类后,这些子类就必须提供不一样的attack方法实现。
//Hero.java
package charactor;
public abstract class Hero {
String name;
float hp;
float armor;
int moveSpeed;
public static void main(String[] args) {
}
// 抽象方法attack
// Hero的子类会被要求实现attack方法
public abstract void attack();
}
//ADAPHero.java
package charactor;
public class ADAPHero extends Hero implements AD, AP {
@Override
public void attack() {
System.out.println("既可以进行物理攻击,也可以进行魔法攻击");
}
public void magicAttack() {
System.out.println("进行魔法攻击");
}
public void physicAttack() {
System.out.println("进行物理攻击");
}
}
抽象类可以没有抽象方法 :
Hero类可以在不提供抽象方法的前提下,声明为抽象类
一旦一个类被声明为抽象类,就不能够被直接实例化
package charactor;
public abstract class Hero {
String name;
float hp;
float armor;
int moveSpeed;
public static void main(String[] args) {
//虽然没有抽象方法,但是一旦被声明为了抽象类,就不能够直接被实例化
Hero h= new Hero();
}
}
抽象类和接口的区别 :
区别1:
- 子类只能继承一个抽象类,不能继承多个
- 子类可以实现多个接口
区别2:
- 抽象类可以定义
- public,protected,package,private
静态和非静态属性
final和非final属性
- public,protected,package,private
- 但是接口中声明的属性,只能是
- public
静态
final的
即便没有显式的声明
- public
注: 抽象类和接口都可以有实体方法。 接口中的实体方法,叫做默认方法
内部类:
内部类分为四种:
- 非静态内部类
- 静态内部类
- 匿名类
- 本地类
非静态内部类:
非静态内部类 BattleScore “战斗成绩”
非静态内部类可以直接在一个类里面定义
比如:
- 战斗成绩只有在一个英雄对象存在的时候才有意义
- 所以实例化BattleScore 的时候,必须建立在一个存在的英雄的基础上
- 语法: new 外部类().new 内部类()
- 作为Hero的非静态内部类,是可以直接访问外部类的private实例属性name的
package charactor;
public class Hero {
private String name; // 姓名
float hp; // 血量
float armor; // 护甲
int moveSpeed; // 移动速度
// 非静态内部类,只有一个外部类对象存在的时候,才有意义
// 战斗成绩只有在一个英雄对象存在的时候才有意义
class BattleScore {
int kill;
int die;
int assit;
public void legendary() {
if (kill >= 8)
System.out.println(name + "超神!");
else
System.out.println(name + "尚未超神!");
}
}
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
// 实例化内部类
// BattleScore对象只有在一个英雄对象存在的时候才有意义
// 所以其实例化必须建立在一个外部类对象的基础之上
BattleScore score = garen.new BattleScore();
score.kill = 9;
score.legendary();
}
}
静态内部类 :
在一个类里面声明一个静态内部类
比如敌方水晶,当敌方水晶没有血的时候,己方所有英雄都取得胜利,而不只是某一个具体的英雄取得胜利。
与非静态内部类不同,静态内部类水晶类的实例化 不需要一个外部类的实例为基础,可以直接实例化
语法:new 外部类.静态内部类();
因为没有一个外部类的实例,所以在静态内部类里面不可以访问外部类的实例属性和方法
除了可以访问外部类的私有静态成员外,静态内部类和普通类没什么大的区别
匿名类 :
匿名类指的是在声明一个类的同时实例化它,使代码更加简洁精练
通常情况下,要使用一个接口或者抽象类,都必须创建一个子类
有的时候,为了快速使用,直接实例化一个抽象类,并“当场”实现其抽象方法。
既然实现了抽象方法,那么就是一个新的类,只是这个类,没有命名。
这样的类,叫做匿名类
package charactor;
public abstract class Hero {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
public abstract void attack();
public static void main(String[] args) {
ADHero adh=new ADHero();
//通过打印adh,可以看到adh这个对象属于ADHero类
adh.attack();
System.out.println(adh);
Hero h = new Hero(){
//当场实现attack方法
public void attack() {
System.out.println("新的进攻手段");
}
};
h.attack();
//通过打印h,可以看到h这个对象属于Hero$1这么一个系统自动分配的类名
System.out.println(h);
}
}
本地类 :
本地类可以理解为有名字的匿名类
内部类与匿名类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置。
本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for循环里等等地方
package charactor;
public abstract class Hero {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
public abstract void attack();
public static void main(String[] args) {
//与匿名类的区别在于,本地类有了自定义的类名
class SomeHero extends Hero{
public void attack() {
System.out.println( name+ " 新的进攻手段");
}
}
SomeHero h =new SomeHero();
h.name ="地卜师";
h.attack();
}
}
默认方法:
默认方法是JDK8新特性,指的是接口也可以提供具体方法了,而不像以前,只能提供抽象方法
Mortal 这个接口,增加了一个默认方法 revive,这个方法有实现体,并且被声明为了default
package charactor;
public interface Mortal {
public void die();
default public void revive() {
System.out.println("本英雄复活了");
}
}
假设没有默认方法这种机制,那么如果要为Mortal增加一个新的方法revive,那么所有实现了Mortal接口的类,都需要做改动。
但是引入了默认方法后,原来的类,不需要做任何改动,并且还能得到这个默认方法
通过这种手段,就能够很好的扩展新的类,并且做到不影响原来的类
数字与字符串
装箱拆箱:
封装类:
所有的基本类型,都有对应的类类型
比如int对应的类是Integer
这种类就叫做封装类
Number类 :
数字封装类有
Byte,Short,Integer,Long,Float,Double
这些类都是抽象类Number的子类
自动装箱 自动拆箱 :
不需要调用构造方法,通过=符号自动把 基本类型 转换为 类类型 就叫装箱
不需要调用Integer的intValue方法,通过=就自动转换成int类型,就叫拆箱
package digit;
public class TestNumber {
public static void main(String[] args) {
int i = 5;
//基本类型转换成封装类型
Integer it = new Integer(i);
//自动转换就叫装箱
Integer it2 = i;
//封装类型转换成基本类型
int i2 = it.intValue();
//自动转换就叫拆箱
int i3 = it;
}
}
int的最大值,最小值 :
int的最大值可以通过其对应的封装类Integer.MAX_VALUE获取
package digit;
public class TestNumber {
public static void main(String[] args) {
//int的最大值
System.out.println(Integer.MAX_VALUE);
//int的最小值
System.out.println(Integer.MIN_VALUE);
}
}
字符串转换:
数字转字符串 :
方法1: 使用String类的静态方法valueOf
方法2: 先把基本类型装箱为对象,然后调用对象的toString
package digit;
public class TestNumber {
public static void main(String[] args) {
int i = 5;
//方法1
String str = String.valueOf(i);
//方法2
Integer it = i;
String str2 = it.toString();
}
}
字符串转数字 :
调用Integer的静态方法parseInt
package digit;
public class TestNumber {
public static void main(String[] args) {
String str = "999";
int i= Integer.parseInt(str);
System.out.println(i);
}
}