数组的声明与初始化
-
-
动态初始化:数组对象的创建与元素的赋值分开进行
数据类型[] 数组名 = new 数组类型[长度];
数组元素的默认值,和对象属性的默认值相同
原生态赋值:
数组名[下标] = 元素值;
有规律的赋值:
for(int i=0;i<数组名.length;i++){
数组名[i] = 元素值;
}
(2)静态初始化:数组对象的创建与元素的赋值同时进行
数据类型[] 数组名 = new 数据类型[]{元素值1,元素值2,......};
数据类型[] 数组名 = {元素值1,元素值2,......};
注意:
-
无论是静态初始化还是动态初始化必须确定数组长度。
动态初始化在创建数组对象时,显式指明长度,静态初始化,由列出的元素个数确定数组的长度
-
int[] arr = new int[5]{1,2,3,4,5};//错误
-
int[5] arr ;//错误
-
数组的长度一旦确定,不可改变
二维数组的声明
例如:int[][] arr;
理解:把arr当做一个一维数组,然后它的元素还是一个int[]数组
通俗的理解:一维数组比喻成平房,一层楼,有几间房
二维数组比喻成楼房,有几层楼,每层楼各有几间房
二维数组的初始化
不管静态初始化还是动态初始化,第一维的长度必须确定
静态初始化
静态初始化:数组对象的创建和元素赋值操作同时进行
例如:
arr = new int[][]{{1,2,3},{4,5,6},{7,8,9,10}};
相当于:
如果把arr看成一个一维数组,那么它有三个元素,每个元素又分别是一个一维数组,分别为
arr[0]就是{1,2,3},对于arr[0]数组的元素是arr[0][0]、arr[0][1]、arr[0][2]
arr[1]就是{4,5,6},对于arr[1]数组的元素是arr[1][0]、arr[1][1]、arr[1][2]
arr[2]就是{7,8,9,10},对于arr[2]数组的元素是arr[2][0]、arr[2][1]、arr[2][2]、arr[2][3]
动态初始化
动态初始化:数组对象的创建与元素的赋值分开进行
(1)规则矩形
例如:
arr = new int[5][6]; //声明了5行6列的数组
//或者说声明了一个5个元素一维数组arr,然后每个元素又是一个长度为6的一维数组
此时所有的数组对象都创建完毕,而且所有的元素都有默认值。
arr[0]就是{0,0,0,0,0,0},对于arr[0]数组的元素是arr[0][0]、arr[0][1]、arr[0][2]、arr[0][3]、arr[0][4]、arr[0][5]
arr[1]就是{0,0,0,0,0,0},对于arr[1]数组的元素是arr[1][0]、arr[1][1]、arr[1][2]、arr[1][3]、arr[1][4]、arr[1][5]
arr[2]就是{0,0,0,0,0,0},对于arr[2]数组的元素是arr[2][0]、arr[2][1]、arr[2][2]、arr[2][3]、arr[2][4]、arr[2][5]
arr[3]就是{0,0,0,0,0,0},对于arr[3]数组的元素是arr[3][0]、arr[3][1]、arr[3][2]、arr[3][3]、arr[3][4]、arr[3][5]
arr[4]就是{0,0,0,0,0,0},对于arr[4]数组的元素是arr[4][0]、arr[4][1]、arr[4][2]、arr[4][3]、arr[4][4]、arr[4][5]
(2)不规则矩阵
例如:
arr = new int[][]{{1,2,3,4},null,null,null};
此时有三个子数组对象未创建。
arr[0]就是{1,2,3,4},对于arr[0]数组的元素是arr[0][0]、arr[0][1]、arr[0][2]、arr[0][3]
arr[1]就是null,此时arr[1],想要存储数据,必须先new int[长度],然后再为元素赋值。
arr[2]就是null,同上
arr[3]就是null,同上
例如:
arr = new int[5][];
此时arr的5个子数组都未创建。
arr[0]为null,此时arr[0],想要存储数据,必须先new int[长度],然后再为元素赋值。
arr[1]为null,同上
arr[2]为null,同上
arr[3]为null,同上
arr[4]为null,同上
应用 杨辉三角的使用
使用二维数组打印一个 10 行杨辉三角.
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1. 第一行有 1 个元素, 第 n 行有 n 个元素
2. 每一行的第一个元素和最后一个元素都是 1
3. 从第三行开始, 对于非第一个元素和最后一个元素的元素.
yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];
yanghui[2][1] = yanghui[1][0] + yanghui[1][1] = 1+1 =2
yanghui[3][1] = yanghui[2][0] + yanghui[2][1] = 1+2 =3
yanghui[3][2] = yanghui[2][1] + yanghui[2][2] = 2+1 =3
public class TestYangHui {
public static void main(String[] args) { //第一步:声明一个二维数组,并确定行数 int[][] yanghui = new int[10][]; //第二步:确定每行的列数 for(int i=0;i<yanghui.length;i++){ yanghui[i] = new int[i+1]; } //第三步:为每一个元素赋值 /* //第一行: yanghui[0][0]=1; //第二行: yanghui[1][0]=1; yanghui[1][1]=1;*/ for(int i=0;i<yanghui.length;i++){ //从第三行开始, 对于非第一个元素和最后一个元素的元素. for(int j=0;j<yanghui[i].length;j++){ if(j==0 || j==yanghui[i].length-1){ yanghui[i][j]=1; }else{ yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j]; } } } //第四步:打印 for(int[] a : yanghui){ for(int num : a){ System.out.print(num+"\t"); } System.out.println(); } } } |
(1)对于public修饰符,它具有最大的访问权限,可以访问任何一个在CLASSPATH下的类、接口、异常等。它往往用于对外的情况,也就是对象或类对外的一种接口的形式。
(2)对于protected修饰符,它主要的作用就是用来保护子类的。它的含义在于子类可以用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西。
(3)对于default来说,有点的时候也成为friendly(友员),它是针对本包访问而设计的,任何处于本包下的类、接口、异常等,都可以相互访问,即使是父类没有用protected修饰的成员也可以。
(4)对于private来说,它的访问权限仅限于类的内部,是一种封装的体现,例如,大多数的成员变量都是修饰符为private的,它们不希望被其他任何外部的类访问。
总结:重载(Overload)与重写(Override)什么区别
(1)重载是在同一个类中,重写是子类与父类中
(2)重载与重写都要求方法的名称完全一致
(3)重载要求参数列表必须不同,重写要求参数列表必须相同
(4)重载和返回值类型无关,重写有很严格的要求
继承后属性、构造器、方法的特点?
(1)属性
A、子类会继承父类的所有的属性,包括私有的,但是私有的在子类中不可见
B、当子类与父类的属性重名时,如果父类的属性没有私有化,那么想要区分父类的属性还是子类自己声明的属性,
使用super.属性表示从父类继承的属性
(2)构造器
A、子类不会继承父类的构造器
B、但是子类一定会调用父类的构造器,默认调用父类的空参/无参构造器,使用super();调用父类的空参构造
C、如果父类没有无参构造器,那么子类必须手动编写构造器,并在构造器中手动调用父类的有参构造
使用super(实参列表)调用父类的有参构造
(3)方法
A、子类会继承父类的所有方法,包括私有的,但是私有的方法在子类中不可见
B、当父类的某个方法的实现不适用于子类,子类可以重写父类的方法
重写的要求:方法名和形参列表必须完全相同
返回值类型:如果是基本数据类型和void,那么必须完全相同
如果是引用数据类型,那么<=即可
权限修饰符:必须>=
如下方法不能被子类重写:private、static、final
public class Example {
String str = new String("Good");
char[] ch = new char[] { 'a', 'b', 'c' };
public static void main(String[] args) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
// String是引用数据类型 将地址值传递过去后重新赋值了 ,
//这时候是在堆内存中重新分配一块区域 那么原有的引用没有改变
System.out.println(ex.str); //good
// 引用没有改变
System.out.println(ex.ch); //dbc
}
public void change(String str, char ch[]) {
str = "bad";
ch[0] = 'd';
}
}
abstract 修饰方法和类
abstract只能修饰类与方法
* (1)abstract和final
* (2)abstract和static
* (3)abstract和private
abstract不能修饰属性、构造器、局部变量、代码块
抽象类的应用:模板方式设计模式
它定义了一个操作的算法骨架,将某些步骤延迟到子类中实现。这样,新的子类可以在不改变一个算法结构的前提下重写定义该算法的某些特定步骤。
抽象类必须被继承 抽象方法必须被实现
//抽象类
abstract class CalTime {
public final void getTime() {
// 先获取开始时间
long start = System.currentTimeMillis();
method();
// 先获取结束时间
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "毫秒");
}
//抽象方法
protected abstract void method();
}
接口和类是平级,或者可以理解为一种特殊的类
从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
JDK1.8接口的新特性
默认方法
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
interface A{ default void methodA(){ //此处public可以省略 System.out.println("A的默认实现"); } } |
class SubA implements A{ } |
new SubA().methodA(); |
静态方法
interface A{ public static void testA(){//public可以省略 System.out.println("A的静态方法"); } } |
A.testA(); |
为什么要定义接口?
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面的专业的实现了:规范和具体实现的分离。
接口就是规范,定义的是一组契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
特殊情况:接口中的默认方法“类优先”原则
(1)当一个子类在继承某个父类,同时实现一个父接口时,如果父类中出现了和接口中的默认方法相同(方法名与形参列表相同)时,那么子类选择父类中的方法,而忽略接口中的默认方法
(2)当一个实现类实现了多个接口,而多个接口中出现了相同的默认方法(方法名与形参列表相同)时,实现类必须重写该默认方法,以解决冲突问题。即放弃接口中的默认实现,改为实现类自己提供实现,如果想要保留其中一个父接口中的方法实现,那么只能这么做
interface A{ default void method(){ System.out.println("A的默认实现方法,lalala"); } } interface B{ default void method(){ System.out.println("B的默认实现方法,lalala"); } default void method(int a){ System.out.println("B的默认实现方法,lalala"); } } |
class Sub implements A,B{
@Override public void method() { A.super.method();//如果想要保留其中一个父接口中的方法实现,那么只能这么做 } } |
1、类与接口的关系:
(1)类与类之间是继承,而且是单继承
(2)类与接口之间是实现,可以多实现
(3)接口与接口之间是继承,可以多继承
2、抽象类与接口的区别?
接口就是比“抽象类”还“抽象”的“抽象类”
1.语法层面上的区别
1)抽象类使用abstract class声明,接口使用interface声明
2)抽象类可以提供成员方法的实现细节,而JDK1.8之前接口中只能存在public abstract 方法,JDK1.8之后可以有默认实现;
3)接口中的成员变量只能是public static final类型的;而抽象类中的成员变量没有这个要求
-
接口中不能含有静态代码块,而抽象类可以有静态代码块
-
JDK1.8之前接口中不能有静态方法,JDK1.8之后可以,但抽象类一直可以有
5)抽象类必须有构造方法,而接口没有构造方法
6)一个类只能继承extends一个抽象类,而一个类却可以实现implements多个接口。
2.设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2)抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
接口的应用:工厂模式
工厂模式:实现了创建者与调用者的分离
面向对象的设计原则(总共六个):这里说几个和工厂模式相关的
-
OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。
当我们写完的代码,不能因为需求变化就修改。我们可以通过新增代码的方式来解决变化的需求。如果每次需求变动都去修改原有的代码,那原有的代码就存在被修改错误的风险,当然这其中存在有意和无意的修改,都会导致原有正常运行的功能失效的风险,这样很有可能会展开可怕的蝴蝶效应,使维护工作剧增。
说到底,开闭原则除了表面上的可扩展性强以外,在企业中更看重的是维护成本。
所以,开闭原则是设计模式的第一大原则,它的潜台词是:控制需求变动风险,缩小维护成本。
-
DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程。
如果A中关联B,那么尽量使得B实现某个接口,然后A与接口发生关系,不与B实现类发生关联关系。
依赖倒置的潜台词是:面向抽象编程,解耦调用和被调用者。
-
LOD(迪米特法则,Law Of Demeter):只与你直接的朋友通信,而避免和陌生人通信。
要求尽量的封装,尽量的独立,尽量的使用低级别的访问修饰符。这是封装特性的典型体现。
一个类如果暴露太多私用的方法和字段,会让调用者很茫然。并且会给类造成不必要的判断代码。所以,我们使用尽量低的访问修饰符,让外界不知道我们的内部。这也是面向对象的基本思路。这是迪米特原则的一个特性,无法了解类更多的私有信息。
另外,迪米特原则要求类之间的直接联系尽量的少,两个类的访问,通过第三个中介类来实现。
迪米特原则的潜台词是:不和陌生人说话,有事去中介。
工厂模式的分类:
-
简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
-
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
-
抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
核心本质:
实例化对象,用工厂方法代替new操作。
将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
1、无工厂模式
package com.atguigu.pattern.factory.nofactory;
public class TestNoFactory {
public static void main(String[] args) { Car a = new Audi(); Car b = new BYD(); a.run(); b.run(); }
} interface Car{ void run(); } class Audi implements Car{
public void run() { System.out.println("奥迪在跑"); } } class BYD implements Car{ public void run() { System.out.println("比亚迪在跑"); } } |
1、简单工厂模式
package com.atguigu.pattern.factory.simple;
public class TestSimple {
public static void main(String[] args) { Car a = CarFactory.getAudi(); Car b = CarFactory.getByd(); a.run(); b.run(); }
} interface Car{ void run(); } class Audi implements Car{
public void run() { System.out.println("奥迪在跑"); } } class BYD implements Car{ public void run() { System.out.println("比亚迪在跑"); } } class CarFactory{ /*public Car getCar(String type){ if("奥迪".equals(type)){ return new Audi(); }else if("比亚迪".equals(type)){ return new BYD(); }else{ return null; } }*/ public static Car getAudi(){ return new Audi(); } public static Car getByd(){ return new BYD(); } } |
调用者只要知道他要什么,从哪里拿,如何创建,不需要知道。分工,多出了一个专门生产Car的实现类对象的工厂类。把调用者与创建者分离。
小结:
简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的实例对象,或者用不同的方法返回不同的实例对象。
对于增加新产品,不修改代码的话,是无法扩展的。
2、工厂方法模式
为了避免简单工厂模式的缺点,不完全满足OCP(对扩展开放,对修改关闭)。
工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立的模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。
package com.atguigu.pattern.factory.method;
public class TestFactoryMethod {
public static void main(String[] args) { Car a = new AudiFactory().getCar(); Car b = new BydFactory().getCar(); a.run(); b.run(); }
} interface Car{ void run(); } class Audi implements Car{
public void run() { System.out.println("奥迪在跑"); } } class BYD implements Car{ public void run() { System.out.println("比亚迪在跑"); } } interface Factory{ Car getCar(); } class AudiFactory implements Factory{ public Audi getCar(){ return new Audi(); } } class BydFactory implements Factory{ public BYD getCar(){ return new BYD(); } } |
根据设计理论建议,选择工厂方法设计模式,但是根据复杂度来说,实际工作更多选择简单工厂模式。实际上就是如果添加功能话 简单工厂模式就是将一些操作封装起来,当修改需求的时候,往往修改修改代码。而工厂方法模式一般使用了interface或是abstract等,就直接添加实现类就可以了。