目录
数组
1.引用
创建数组的时候,要指明数组的长度。 如: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;
//创建一个长度是5的数组,并且使用引用a指向该数组
a = new int[5];
int[] b = new int[5]; //声明的同时,指向一个数组
}
}
2.增强型for循环
只能用来取值,不能用来修改数组里的值
//增强型for循环遍历
int values [] = new int[]{18,62,68,82,65,9};
for (int each : values) {
System.out.println(each);
}
3.复制数组 arraycopy()
把一个数组的值,复制到另一个数组中
System.arraycopy(src, srcPos, dest, destPos, length)
src: 源数组
srcPos: 从源数组复制数据的起始位置
dest: 目标数组
destPos: 复制到目标数组的起始位置
length: 复制的长度
public class Test{
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)
System.arraycopy(a, 0, b, 0, 3);
}
}
4.针对数组的工具类 Arrays
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int a[] = new int[] { 18, 62, 68, 82, 65, 9 };
int b[] = Arrays.copyOfRange(a, 1, 3);
int c[] = new int[10];
System.out.println("b数组:"+Arrays.toString(b));
Arrays.sort(a);
System.out.println("排序后的a数组:"+Arrays.toString(a));
System.out.println("数字 62出现的位置:"+Arrays.binarySearch(a, 62));
System.out.println("数组a和数组b是否相同:"+Arrays.equals(a, b));
Arrays.fill(c, 5);
System.out.println("用5填充后的数组c:"+Arrays.toString(c));
}
}
- 数组复制 copyOfRange
与使用System.arraycopy进行数组复制类似的, Arrays提供了一个copyOfRange方法进行数组复制。
不同的是System.arraycopy,需要事先准备好目标数组,并分配长度。 copyOfRange 只需要源数组就就可以了,通过返回值,就能够得到目标数组了。
除此之外,需要注意的是 copyOfRange 的第3个参数,表示源数组的结束位置,该位置的值是取不到的。
- 转换为字符串 toString
如果要打印一个数组的内容,就需要通过for循环来挨个遍历逐一打印,很麻烦。
但是Arrays提供了一个toString()方法,直接把一个数组,转换为字符串,这样方便观察数组的内容
Arrays.toString(a);
- 排序 sort
Arrays.sort(a);
- 搜索 binarySearch
binarySearch用于查询元素出现的位置。
需要注意的是,使用binarySearch进行查找之前,必须使用sort进行排序
如果数组中有多个相同的元素,查找结果是不确定的
Arrays.sort(a);
Arrays.binarySearch(a, 62);
- 判断是否相同 equals
比较两个数组的内容是否一样
第二个数组的最后一个元素是8,和第一个数组不一样,所以比较结果是false
Arrays.equals(a, b);
- 填充 fill
使用同一个值,填充整个数组
Arrays.fill(a, 5);
5.选择法排序
public class Test {
public static void main(String[] args) {
for(int i = 0; i < a.length-1; i++){
for(int j = i+1; j < a.length; j++){
if(a[j] < a[i]){
int temp = a[j];
a[j] = a[i];
a[i] = temp;
}
}
}
}
}
6.冒泡排序
public class Test {
public static void main(String[] args) {
for(int i = 0; i < a.length; i++){
for(int j = 0; j < a.length-i-1; j++){
if(a[j+1] < a[j]){
int temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
}
}
}
}
}
7.练习-二维数组中的查找(剑指offer)
题目描述:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解决思路: 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增。 因此从左下角开始查找,当要查找数字比左下角数字大时,右移; 要查找数字比左下角数字小时,上移。
public class Solution {
public boolean Find(int target, int [][] array) {
int rowCount = array.length;
int colCount = array[0].length;
int i,j;
for(i=rowCount-1,j=0;i>=0&&j<colCount;)
{
if(target == array[i][j])
return true;
if(target < array[i][j])
{
i--;
continue;
}
if(target > array[i][j])
{
j++;
continue;
}
}
return false;
}
}
类与对象
1.方法重载
- 概念
方法的重载指的是方法名一样,但是参数类型不一样。在调用同名方法的时候,会根据传递的参数类型以及数量,自动调用对应的方法
有一种英雄,叫做物理攻击英雄 ADHero,为ADHero 提供三种方法
public void attack()
public void attack(Hero h1)
public void attack(Hero h1, Hero h2)
- 可变数量的参数
如果要攻击更多的英雄,就需要设计更多的方法,这样类会显得很累赘
这时,可以采用可变数量的参数,只需要设计一个方法
public void attack(Hero ...heros)
在方法里,用操作数组的方式处理参数heros即可。
2.构造函数
- 概念
方法名和类名一样(包括大小写),没有返回类型,实例化一个对象的时候,必然调用构造方法。
无参的构造方法,如果不写,就会默认提供一个
一旦提供了一个有参的构造方法,同时又没有显式的提供一个无参的构造方法 ,那么默认的无参的构造方法,就失效了
3.JAVA中的this
- 通过this关键字访问对象的属性
- 通过this调用其他的构造方法
如果要在一个构造方法中,调用另一个构造方法,可以使用this()
4.包
- 把比较接近的类,规划在同一个包下
- 使用其他包下的类,必须import
使用同一个包下的其他类,直接使用即可
但是要使用其他包下的类,必须import
5.访问修饰符
- 类之间的关系
- 什么情况该用什么修饰符?
从作用域来看,public能够使用所有的情况。 但是大家在工作的时候,又不会真正全部都使用public,那么到底什么情况该用什么修饰符呢?
1. 属性通常使用private封装起来
2. 方法一般使用public用于被调用
3. 会被子类继承的方法,通常使用protected
4. package用的不多,一般新手会用package,因为还不知道有修饰符这个东西
再就是作用范围最小原则
简单说,能用private就用private,不行就放大一级,用package,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了
6.类属性/静态属性
- 定义
类属性: 又叫做静态属性
对象属性: 又叫实例属性,非静态属性
当一个属性被static修饰的时候,就叫做类属性,又叫做静态属性
当一个属性被声明成类属性,那么所有的对象,都共享一个值
与对象属性对比:
不同对象的 对象属性 的值都可能不一样,比如盖伦的hp 和 提莫的hp 是不一样的。
但是所有对象的类属性的值,都是一样的
给英雄设置一个类属性叫做“版权" (copyright), 无论有多少个具体的英雄,所有的英雄的版权都属于 Riot Games公司。
- 访问类属性
7.类方法/静态方法
- 定义
- 调用类方法
- 对象方法和类方法的使用场景
8.属性初始化
- 对象属性初始化
对象属性初始化有3种
1. 声明该属性的时候初始化
2. 构造方法中初始化
3. 初始化块
- 类属性初始化
类属性初始化有2种
1. 声明该属性的时候初始化
2. 静态初始化块
只有一个类时,执行顺序为:属性声明-->初始化块-->构造方法
9.饿汉式与懒汉式单例模式
LOL里有一个怪叫大龙GiantDragon,只有一只,所以该类,只能被实例化一次
- 单例模式
单例模式又叫做 Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在。
(面试题)什么是单例模式?
回答的时候,要答到三元素
- 构造方法私有化
- 引用字段属性私有化并且共用唯一
- 通过对外提供公共的get方法调用,返回这个唯一的对象
- 饿汉式单例模式
GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new 得到新的实例。
GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取12行定义的对象,而且每一次都是获取同一个对象。 从而达到单例的目的。
这种单例模式又叫做饿汉式单例模式,无论如何都会创建一个实例
。
- 懒汉式单例模式
懒汉式单例模式与饿汉式单例模式不同,只有在调用getInstance的时候,才会创建实例
- 饿汉式和懒汉式的使用场景
10.枚举类型
- 预先定义的常量
枚举enum是一种特殊的类(还是类),使用枚举可以很方便的定义常量
比如设计一个枚举类型 季节,里面有4种常量
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER
}
一个常用的场合就是switch语句中,使用枚举来进行判断
注:因为是常量,所以一般都是全大写
- 使用枚举的好处
假设在使用switch的时候,不是使用枚举,而是使用int,而int的取值范围就不只是1-4,有可能取一个超出1-4之间的值,这样判断结果就似是而非了。(因为只有4个季节)
但是使用枚举,就能把范围死死的限定在这四个当中
- 遍历枚举
借助增强型for循环,可以很方便的遍历一个枚举都有哪些常量
接口与继承
1.接口
在设计LOL的时候,进攻类英雄有两种,一种是进行物理系攻击,一种是进行魔法系攻击 ,这时候,就可以使用接口来实现这个效果。
接口就像是一种约定,我们约定某些英雄是物理系英雄,那么他们就一定能够进行物理攻击。
- 设计一类英雄,能够使用物理攻击
创建一个接口 File->New->Interface
AD ,声明一个方法 physicAttack 物理攻击,但是没有方法体,是一个“空”方法
- 设计一类英雄,能够使用物理攻击
同理,设计一类英雄,只能使用魔法攻击,这类英雄在LOL中被叫做AP
- 设计一类英雄,既能进行物理攻击,又能进行魔法攻击
一类英雄,能够同时进行物理攻击和魔法攻击 ,比如伊泽瑞尔,皮城女警凯特琳
2.对象转型
- 引用类型与对象类型的概念
在这个例子里,有一个对象 new ADHero(), 同时也有一个引用ad
对象是有类型的, 是ADHero
引用也是有类型的,是ADHero
通常情况下,引用类型和对象类型是一样的
接下来要讨论的类型转换的问题,指的是引用类型和对象类型不一致的情况下的转换问题
- 子类转父类(向上转型)
所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换
类型转换有时候会成功,有时候会失败(参考基本类型的类型转换)
到底能否转换成功? 很简单的判别办法:把右边的当做左边来用,看说得通不
Hero h = new Hero();
ADHero ad = new ADHero();
h = ad;
右边ad引用所指向的对象的类型是 物理攻击英雄
左边h引用的类型是 普通英雄
把物理攻击英雄 当做 普通英雄,说不说得通? 说得通,就可以转
所有的子类转换为父类,都是说得通的。比如:
苹果手机 继承了 手机,把苹果手机当做普通手机使用
- 父类转子类(向下转型)
- 没有继承关系的两个类,互相转换
没有继承关系的两个类,互相转换,一定会失败
虽然ADHero和APHero都继承了Hero,但是彼此没有互相继承关系
"把魔法英雄当做物理英雄来用",在语义上也是说不通的
- 实现类转换成接口(向上转型)
引用ad指向的对象是ADHero类型,这个类型实现了AD接口
10行: 把一个ADHero类型转换为AD接口
从语义上来讲,把一个ADHero当做AD来使用,而AD接口只有一个physicAttack方法,这就意味着转换后就有可能要调用physicAttack方法,而ADHero一定是有physicAttack方法的,所以转换是能成功的。
- 接口转换成实现类(向下转型)
10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
12行: adi实际上是指向一个ADHero的,所以能够转换成功
14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。
假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的。
- instanceof
instanceof Hero 判断一个引用所指向的对象,是否是Hero类型,或者Hero的子类
3.重写方法
子类可以继承父类的对象方法 ,在继承后,重复提供该方法,就叫做方法的重写 ,又叫覆盖 override
- 概念
父类Item有一个方法,叫做effect
子类LifePotion继承Item,同时也提供了方法effect
- 调用重写的方法
调用就会执行重写的方法,而不是从父类的方法。所以LifePotion的effect会打印:"血瓶使用后,可以回血"
- 如果没有重写这样的机制怎么样?
如果没有重写这样的机制,也就是说LifePotion这个类一旦继承了Item,所有方法都不能修改了。
但是LifePotion又希望提供一点不同的功能,为了达到这个目的,只能放弃继承Item,重新编写所有的属性和方法,然后在编写effect的时候,做一点小改动,这样就增加了开发时间和维护成本
4.多态
- 概念
操作符的多态 | 类的多态 |
如果+号两侧都是整型,那么+代表 数字相加 如果+号两侧,任意一个是字符串,那么+代表字符串连接 | 父类引用指向子类对象 (都是同一个类型,调用同一个方法,却能呈现不同的状态) |
观察类的多态现象:
1. i1和i2都是Item类型
2. 都调用effect方法
3. 输出不同的结果
- 类的多态条件
要实现类的多态,需要如下条件
1. 父类(接口)引用指向子类对象
2. 调用的方法有重写
- 类的多态-不使用多态
如果不使用多态,假设英雄要使用血瓶和魔瓶,就需要为Hero设计两个方法
useLifePotion
useMagicPotion
除了血瓶和魔瓶还有很多种物品,那么就需要设计很多很多个方法,比如
usePurityPotion 净化药水
useGuard 守卫
useInvisiblePotion 使用隐形药水,等等等等
如果物品的种类特别多,那么就需要设计很多的方法
这个时候采用多态来解决这个问题。设计一个方法叫做useItem,其参数类型是Item
如果是使用血瓶,调用该方法,如果是使用魔瓶,还是调用该方法
无论英雄要使用什么样的物品,只需要一个方法即可
5.隐藏
- 概念
重写:子类覆盖父类的对象方法
隐藏:子类覆盖父类的类方法
父类有一个类方法 :battleWin
子类隐藏父类的类方法
- 练习
Hero h =new ADHero();
h.battleWin(); //battleWin是一个类方法
h是父类类型的引用但是指向一个子类对象,h.battleWin(); 会调用父类的方法?还是子类的方法?
答:一个指向子类对象的父类引用变量来调用父子同名的静态方法时,只会调用父类的静态方法。
6.super
- 子类显式调用父类带参构造方法
父类显式提供两个构造方法,分别是无参的构造方法和带一个参数的构造方法
子类显式调用父类带参构造方法,使用关键字 super 显式调用父类带参的构造方法
- 调用父类属性
通过super调用父类的moveSpeed属性,ADHero也提供了属性moveSpeed
- 调用父类方法
ADHero重写了useItem方法,并且在useItem中通过super调用父类的useItem方法
7.Object
Object类是所有类的父类。声明一个类的时候,默认是继承了Object :
public class Hero extends Object
- toString()
Object类提供一个toString方法,所以所有的类都有toString方法。toString()的意思是返回当前对象的字符串表达
通过 System.out.println 打印对象就是打印该对象的toString()返回值
- finalize()
当一个对象没有任何引用指向的时候,它就满足垃圾回收的条件。当它被垃圾回收的时候,它的finalize() 方法就会被调用。
finalize() 不是开发人员主动调用的方法,而是由虚拟机JVM调用的。
- equals()
equals() 用于判断两个对象的内容是否相同
假设,当两个英雄的hp相同的时候,我们就认为这两个英雄相同
- ==
这不是Object的方法,但是用于判断两个对象是否相同,更准确的讲,用于判断两个引用,是否指向了同一个对象
- hashCode()
hashCode方法返回一个对象的哈希值
- 线程同步相关方法
Object还提供线程同步相关方法
wait(),notify(),notifyAll()
- getClass()
getClass()会返回一个对象的类对象,关于类对象的详细内容请参考反射机制
8.final
- final修饰类
当Hero被修饰成final的时候,表示Hero不能够被继承,其子类会出现编译错误
- final修饰方法
Hero的useItem方法被修饰成final,那么该方法在ADHero中,不能够被重写
- final修饰基本类型变量
final修饰基本类型变量,表示该变量只有一次赋值机会 。16行进行了赋值,17行就不可以再进行赋值了
- final修饰引用
h引用被修饰成final,表示该引用只有1次指向对象的机会,所以17行会出现编译错误
但是,依然可以通过h引用修改对象的属性值hp,因为hp并没有final修饰
- 常量
常量指的是可以公开,直接访问,不会变化的值 。比如 itemTotalNumber 物品栏的数量是6个
9.抽象类
在类中声明一个方法,这个方法没有实现体,是一个“空”方法,这样的方法就叫抽象方法,使用修饰符“abstract"
当一个类有抽象方法的时候,该类必须被声明为抽象类
- 概念
为Hero增加一个抽象方法 attack,并且把Hero声明为abstract的。
APHero,ADHero,ADAPHero是Hero的子类,继承了Hero的属性和方法。
但是各自的攻击手段是不一样的,所以继承Hero类后,这些子类就必须提供不一样的attack方法实现。
- 抽象类可以没有抽象方法
Hero类可以在不提供抽象方法的前提下,声明为抽象类
一旦一个类被声明为抽象类,就不能够被直接实例化
- 抽象类和接口的区别
抽象类 | 接口 |
子类只能继承一个抽象类,不能继承多个 | 子类可以实现多个接口 |
抽象类可以定义 public,protected,package,private 静态和非静态属性 final和非final属性 | 但是接口中声明的属性,只能是 public static final |
注: 抽象类和接口都可以有实体方法。 接口中的实体方法,叫做默认方法
![](https://i-blog.csdnimg.cn/blog_migrate/d15ba20e385812475ee9f0969a1e692a.png)
10.内部类
内部类分为四种:
非静态内部类、 静态内部类、 匿名类、本地类
- 非静态内部类
非静态内部类 BattleScore “战斗成绩”
非静态内部类可以直接在一个类里面定义
比如:
战斗成绩只有在一个英雄对象存在的时候才有意义
所以实例化BattleScore 的时候,必须建立在一个存在的英雄的基础上
语法: new 外部类().new 内部类()
作为Hero的非静态内部类,是可以直接访问外部类的private实例属性name的
- 静态内部类
在一个类里面声明一个静态内部类
比如敌方水晶,当敌方水晶没有血的时候,己方所有英雄都取得胜利,而不只是某一个具体的英雄取得胜利。
与非静态内部类不同,静态内部类水晶类的实例化 不需要一个外部类的实例为基础,可以直接实例化
语法:new 外部类.静态内部类();
因为没有一个外部类的实例,所以在静态内部类里面不可以访问外部类的实例属性和方法
除了可以访问外部类的私有静态成员外,静态内部类和普通类没什么大的区别
- 匿名类
匿名类指的是在声明一个类的同时实例化它,使代码更加简洁精练
通常情况下,要使用一个接口或者抽象类,都必须创建一个子类
有的时候,为了快速使用,直接实例化一个抽象类,并“当场”实现其抽象方法。
既然实现了抽象方法,那么就是一个新的类,只是这个类,没有命名。
这样的类,叫做匿名类
- 本地类
本地类可以理解为有名字的匿名类
内部类与匿名类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置。
本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for循环里等等地方
- 在匿名类中使用外部的局部变量
在匿名类中使用外部的局部变量,外部的局部变量必须修饰为final
为什么要声明为final,请参考第二个Hero代码中的解释
注:在jdk8中,已经不需要强制修饰成final了,如果没有写final,不会报错,因为编译器偷偷的帮你加上了看不见的final
11.默认方法
- 什么是默认方法
默认方法是JDK8新特性,指的是接口也可以提供具体方法了,而不像以前,只能提供抽象方法
Mortal 这个接口,增加了一个默认方法 revive,这个方法有实现体,并且被声明为了default
- 为什么会有默认方法
假设没有默认方法这种机制,那么如果要为Mortal增加一个新的方法revive,那么所有实现了Mortal接口的类,都需要做改动。
但是引入了默认方法后,原来的类,不需要做任何改动,并且还能得到这个默认方法
通过这种手段,就能够很好的扩展新的类,并且做到不影响原来的类
- 练习-默认方法
为AD接口增加一个默认方法 attack()
为AP接口也增加一个默认方法 attack()
问: ADAPHero同时实现了AD,AP接口,那么 ADAPHero 对象调用attack()的时候,是调用哪个接口的attack()?
答:接口有默认方法的话, 那么实现类,就可以说继承了(默认方法可以理解为继承)这个方法 既然是AD,AP两个接口内的都是同样的默认方法, 那么实现类,就必须重写这个同名的方法,不然不知道调用的是谁的方法(本身就会有冲突报错,必须重写这个方法) 所以根本不需要考虑这个方法是从哪个接口获取的(因为同名的必须重写)
类、抽象类、接口 简化概括
类继承--->子类完全继承父类特点
抽象类继承--->继承时抽象的部分不同的子类可以有不同的实现
接口继承--->所有成员在子类都可以有不同的实现
至于为什么要用接口而不是抽象类 这要看二者适用的情况
当个性大于共性时,适合接口,如鸟和飞机,适合抽象出一个飞的接口
当共性大于个性时,适合抽象类,如老鹰和麻雀,适合抽象出一个鸟的父类
另外接口可以实现多重继承,这也是一个特点
StringBuffer
- 概念
StringBuffer是可变长的字符串,为什么StringBuffer可以变长?
和String内部是一个字符数组一样,StringBuffer也维护了一个字符数组。 但是,这个字符数组,留有冗余长度
比如说new StringBuffer("the"),其内部的字符数组的长度,是19,而不是3,这样调用插入和追加,在现成的数组的基础上就可以完成了。
如果追加的长度超过了19,就会分配一个新的数组,长度比原来多一些,把原来的数据复制到新的数组中,看上去 数组长度就变长了
length: “the”的长度 3
capacity: 分配的总空间 19
注: 19这个数量,不同的JDK数量是不一样的
- 方法
append() | 追加 |
delete() | 删除 |
insert() | 插入 |
reverse() | 反转 |
- String与StringBuffer的性能区别
StringBuffer的效率要大大的优于字符串拼接