Java基础知识

一、JDK、JRE基本介绍

JDK的全称(Java Development Kit Java开发工具包);JDK = JRE + Java 开发工具包 [Java,Javac,Javadoc,Javap等]
JDK是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。
JRE(Java Runtime Environment Java运行环境)JRE = JVM + Java 的核心类库 [类]。
包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,如果想运行一个开发好的Java程序,计算机中只需要安装JRE即可。


二、JDK、JRE 和 JVM 包含关系

JDK = JRE + 开发工具集 (例如Javac,Java编译工具等)
JRE = JVM + Java SE 标准类库 (Java核心类库)


三、Java基础知识

        基础概念/语法:面向对象(继承、封装、多态)基础、包、类、接口、方法、对象、属性、第一个 Java 程序。

        标识符: 用来标识类名、对象名、变量名、方法名、类型名、数组名、文件名的有效字符序列。

        合法的标识符:由字母、数字、下划线“_”、美元符号“$”或者“¥”组成,并且首字符不能是数字;不能把java关键字和保留字作为标识符;标识符对大小写敏感。

变量类型:局部变量、实例变量(成员变量)、类变量(静态变量)
变量的声明:数据类型 变量名;
变量的赋值:变量名 = 数据;
变量的操作:放入打印语句进行输出或者进行运算
各种数据类型的默认值:

运算符

1)算术运算符:+、-、*、/、%、++、--;

2)关系运算符:==、!=、>、<、>=、<=;

3)逻辑运算符:&&、||、!;

4)赋值运算符:=、+=、-=、*=、/=;

5)条件运算符:Object x = (expression) ? value if true : value if false;

权限控制修饰符:


面向对象三大特性

继承:一般类只能单继承,内部类实现多继承,接口可以多继承

封装:访问权限控制public > protected > 包 > private 内部类也是一种封装

多态:编译时多态,体现在向上转型和向下转型,通过引用类型判断调用哪个方法(静态分派)。

 

1.类和对象


类与对象时整个面向对象中最基础的组成单元。

:是抽象的概念集合,表示的是一个共性的产物,类之中定义的是属性和行为(方法);
对象:对象是一种个性的表示,表示一个独立的个体,每个对象拥有自己独立的属性,依靠属性来区分不同对象。

类与对象的区别:类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。

定义一个类的基本格式
[修饰符] class 类名{
0到多个构造器
0到多个成员变量
0到多个方法
0到多给初始化块
}修饰符可以写public final abstract或者不写,java类名要用大驼峰写法。一个java源文件(也就是文件后缀名为.java的文件)可以写多个类,但是里面只能有一个用public修饰的class,
 

final关键字

1.用来修饰一个引用

如果引用为基本数据类型,则该引用为常量,该值无法修改;

如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。

如果引用时类的成员变量,则必须当场赋值,否则编译会报错。

2.用来修饰一个方法

当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。

使用final方法的原因主要有两个:

(1)把方法锁定,以防止继承类对其进行更改。

(2)效率,在早期的java版本中,会将final方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要final方法进行这些优化了。

final方法意味着“最后的、最终的”含义,即此方法不能被重写。

注意:若父类中final方法的访问权限为private,将导致子类中不能直接继承该方法,因此,此时可以在子类中定义相同方法名的函数,此时不会与重写final的矛盾,而是在子类中重新地定义了新方法。

3.用来修饰一个类

当用final修饰类时,该类成为最终类,无法被继承,该类就不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰,但要注意:

final类中所有的成员方法都会隐式的定义为final方法。

2.构造器


什么是构造器

构造器通常也叫构造方法、构造函数,构造器在每个项目中几乎无处不在。当你new一个对象时,就会调用构造器。构造器格式如下:

[修饰符,比如public] 类名 (参数列表,可以没有参数){ //这里不能有return}

构造器的注意事项:

1.构造器的名称必须和类名一致
2.一个类中可以定义多个构造器,但是构造器的参数列表必须不同(重载)
3.如果我们没有手动定义构造器,则Java系统会提供一个默认的构造器给我们使用,一旦我们定义了构造器,则系统会把默认的构造器收回
4.构造器的作用:实例化对象,给对象赋初始值
5.代码游离块优先执行


构造器的定义

构造方法的名字必须和所在类的名字一致,没有返回值,但不能声明void,访问权限可以为任意,但是一般情况下使用public方法权限,构造方法中的参数可以根据需要自行定义,参数的不同的构造方法构成重载

构造器的使用

Java中构造方法的使用有两个地方,一个是跟在关键字new后面,类名加上一个小括号(),小括号内根据实际加上实参,另外一个是跟在关键字super或this后加上一个小括号(),小括号内根据实际添加实参

构造器的继承

1)子类构造器会默认调用父类无参构造器,如果父类没有无参构造器,则必须在子类构造器的第一行通过 super关键字指定调用父类的哪个构造器

2)final类是不允许被继承的,编译器会报错。很好理解,由于final修饰符指的是不允许被修改,而继承中,子类是可以修改父类的,这里就产生冲突了,所以final类是不允许被继承的。

构造器、静态代码块、构造代码块的执行顺序

1.无继承的情况下的执行顺序静态代码块:只在程序启动后执行一次,优先级最高构造代码块:任何一个构造器被调用的时候,都会先执行构造代码块,优先级低于静态代码块构造器:优先级低于构造代码块总结一下优先级:静态代码块 > 构造代码块 > 构造器


2.有继承的情况下的执行顺序:父类静态代码块:只在程序启动后执行一次,优先级最高 子类静态代码块:只在程序启动后执行一次,优先级低于父类静态代码块 父类构造代码块:父类任何一个构造器被调用的时候,都会执行一次,优先级低于子类静态代码块父类构造器:优先级低于父类构造代码子类构造代码块:子类任何一个构造器被调用的时候,都会执行一次,优先级低于父类构造器子类构造器:优先级低于子类构造代码块总结一下优先级:父类静态代码块 > 子类静态代码块 > 父类构造代码块 > 父类构造器 > 子类构造代码块 > 子类构造器

3.成员变量和局部变量
 
     Java 语言中若变量按声明的位置分类,可以分为:成员变量、局部变量,而成员变量又分为:实例变量,全局变量。实例变量是指不使用static修饰的变量,全局变量是指使用static修饰的变量。 局部变量包括:方法内声明的变量、方法的形参、构造器的形参、代码块内声明的变量。

相同点:
      1.声明格式相同
          格式: 数据类型  变量名  =  变量值
      2.变量,必须先声明后使用
      3.变量,都有其作用域

不同点:
      1.声明的位置不同:
          成员变量:直接声明在类的内部
          局部变量:方法内声明的变量
                           方法的形参、构造器的形参
                           代码块内声明的变量。


       2.权限修饰符的使用:
                   成员变量:可以在声明的类型前,指明权限修饰符。
                           权限修饰符有:private、public、protected、缺省
                   局部变量:不可以声明权限修饰符
                  
        3.默认初始化值:
            成员变量:在声明时,如果没有显示赋值。则其有默认初始化值。
                             byte/short/int/long :  0
                             float/double : 0.0
                              char : 0 或 '\u0000'
                             boolean : false
                            引用类型: null
             局部变量:在使用变量前,一定要进行显示初始化。即:局部变量没有初始化值
                            对于方法的形参(局部变量的一种)而言,是在调用方法时,给形参赋值。


          4.在内存中加载的位置不同:
              成员变量:声明在堆空间中
               局部变量:声明在栈空间中

4.赋值过程
java的赋值语句是“=”号,比如把1赋值给a可以写:int a = 1;,如果是String类型可以写:String a = "1";,对象可以写成User u1 = new User();。

● 变量之间的赋值,可以说没有所谓的值赋值和地址赋值。

● 简而言之,将一个变量a赋值给另一个变量b,是将这个变量a的值,拷贝一份给变量b(如果a是引用类型,就拷贝引用,如果是基本类型,就拷贝原始值)

所赋的值

1.默认值
2.显示赋值
3.构造器赋值
4.对象名.属性名/对象名.方法名()
5.代码块赋值
  {}
  成员代码块/构造代码块
  静态代码块
构造代码块/ 成员代码块:
   位置: 类中方法外
   作用: 给成员变量赋值
   声明方式: {}
   执行顺序: 先从上到下依次执行代码块 执行完毕再执行 构造器
              一个类中可以存在多个代码块(先代码块后构造器)
   注意:
    创建几次对象 构造代码块就会执行几次

5.重载(Override)和重写(Overload)
1)重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。

即外壳不变,核心重写

2)重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

3)重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。

       例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,抛出 IOException 异常或者 IOException 的子类异常。

在面向对象原则里,重写意味着可以重写任何现有方法。

方法重写规则

参数列表与被重写方法的参数列表必须完全相同。

返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

父类的成员方法只能被它的子类重写。

声明为 final 的方法不能被重写。

声明为 static 的方法不能被重写,但是能够被再次声明。

子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

构造方法不能被重写。

如果不能继承一个类,则不能重写该类的方法。

重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

重载规则

被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。


6.内部类


         内部类,就是在一个类中在定义一个类,包含内部类的类称为外部类。Java中一共有四种内部类:成员内部类,静态内部类,方法内部类,匿名内部类。内部类的意义是内部类和外部类可以方便访问彼此的私有域,内部类也是一种封装的表现,且内部类可以使用private关键字,内部类还能变相实现多继承。

注意:

内部类和外部类可以方便的互相访问彼此的private域。
内部类可以直接访问外部类的私有属性,但外部类要访问内部类的私有属性,必须创建内部类对象,再通过创建的内部类对象访问。
内部类是一个相对独立的个体,与外部类不是is a关系(人体和心脏)
1.成员内部类:
成员内部类:直接定义在类中不加任何修饰符(static)定义的类
成员内部类可以类比成员方法/属性,成员方法能访问静态域,不能拥有静态域。
注意:

成员内部类必须依赖于外部类。
成员内部类能否定义一个静态变量?不能,成员内部类必须依赖于外部类,若成员内部类有静态属性,没有外部类也能访问。
成员内部类对象的创建有两种:

1.外部类的内部创建
和使用其他普通类没什么区别
2.外部类的外部创建
在外部类的外部创建内部类对象-内部类要对外部可见(访问权限问题)
2.静态内部类:
静态内部类:定义在类中,用static修饰的内部类
注意

与成员内部类的区别:静态内部类不需要依赖外部类对象!
静态内部类不可以使用任何外部类的非static类(包括属性和方法),但可以存在自己的成员变量。
静态内部类就是普通类,只是套在了类的内部而已
成员内部类可以访问外部类的成员域和静态域,但不能拥有静态域;静态内部类可以拥有成员域,但不能直接访问外部类的成员域,静态域,随便访问。静态方法能访问静态域不能访问成员域
创建静态内部类对象有两种方式

1.外部类的内部
2.外部类的外部
外部类.内部类 引用 = new 外部类.内部类();
3.方法内部类:

方法内部类: 直接定义在方法内部的类,不允许使用任何访问修饰符,对外部完全隐藏,出了方法就没有了。
注意:

方法内部类无法定义static域,除此之外和成员内部类的使用相同
方法内部类若使用了方法的形参,该形参为隐式的final声明
4.匿名内部类:

匿名内部类是方法内部类的特殊,不写类名称。
匿名内部类遵从方法内部类的所有要求
匿名内部类默认会继承一个类或实现一个接口。

面向对象(三大特征和接口、抽象类)


OOP 语言:也就是面向对象编程。
面向对象的语言有三大特性:封装、继承、多态。三大特性是面向对象编程的核心。

一.封装
1.封装的概念:在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者

封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的,
把属性和动作隐藏,只提供相应的方法来调用即可,只要知道如何使用类就行了.
当类的实现者把内部的逻辑发生变化时,类的调用者根本不用因此而修改方法。
这样就降低了类使用者的学习和使用成本,从而降低了复杂程度,也保证了代码的安全性

2. private实现封装

private 访问限制修饰符,被它修饰的字段或者方法就只能在当前类中使用。

3. getter和setter方法
当我们用private修饰字段后,这个字段就无法被直接使用了。
这个时候就用到了 get 和 set 方法了

4.封装的好处
1.提高了数据的安全性
别人不能够通过 变量名来修改某个私有的成员属性
2.操作简单
封装后,类的调用者在使用的时候,只需调用方法即可。
3.隐藏了实现
实现过程对类的调用者是不可见的,类的调用者只需调用方法即可,不知道具体实现。

二.继承


1. 继承的概念:
继承的意义:代码的重复使用

代码中创建的类, 主要是为了抽象现实中的一些事物.

注意:

使用 extends 指定父类.
Java不同于C++/Python,JAVA中一个子类只能继承一个父类(单继承)
子类会继承父类的所有public 的字段和方法.
对于父类的 private 的字段和方法, 子类中是无法访问的.
子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
2. super 关键字
1.当一个类没有写构造方法的时候,系统默认会有一个没有参数且没有任何内容的构造方法。

当子类继承父类后,在构造子类之前,就必须先帮父类进行构造。

3.super 表示获取到父类实例的引用.,和this类似共有三种用法:
1).super.父类的成员变量
2).super.父类的成员方法
3).super():调用父类的构造方法

注意:super 和 this一样不能在静态方法里使用 !

如果一个类没有指定父类的时候,默认继承的就是Object类。

4.访问权限


5.final关键字
1.final修饰变量(常量,这个常量不能再被修改)
2.final修饰类,密封类:当前类不能再继承
3.final修饰方法,密封方法:该方法不能进行重写

三.多态
 1. 向上转型
(1) 概念
向上转型就是把一个子类引用给一个父类引用,也就是父类引用 引用了子类的对象

(2) 向上转型发生的几种时机
1.直接赋值

2.方法传参

3.方法返回

(3) 注意事项
注意:当发生向上转型的时候,通过父类引用只能调用父类的自己的方法和成员变量

 2. 向下转型
(1) 概念
向下转型就是父类对象转成子类对象。

(2) instanceof关键字
向下转型一般不建议使用,因为它非常不安全。

如果要让它安全就要加上一个关键字instanceof 来判断一下。

instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了

3.动态绑定(运行时绑定)
(1) 动态绑定概念
动态绑定发生的前提

1.先向上转型
2.通过父类引用来调用父类和子类同名的覆盖方法

    动态绑定也就叫运行时绑定,因为程序在编译的时候调用的其实是父类的方法,但是程序在运行时运行的则是子类的方法,运行期间发生了绑定。

(2) 重写(Override)
重写发生的条件

1.方法名相同
2.方法的参数列表相同(返回类型和数据类型)
3.方法的返回值相同

返回值构成父子类关系也是可以发生重写的,此时叫做:协变类型

注意:

1.子类的重写的这个方法,他的访问修饰符,一定要大于等于父类方法的访问修饰符
2.被final和static修饰的方法是不能发生重写的

(3) @Override注解
被@Override注解修饰的方法代表是要重写的方法,一但方法被这个注解修饰,只要方法的方法名、放回值、参数列表有一个地方不满足重写的要求,编译器就会报错。

4.多态
(1) 理解多态
多态其实就是一种思想,一个事物表现出不同的形态,就是多态。

(2) 多态的好处
1.类调用者对类的使用成本进一步降低.
封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可
2. 可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低
 

总结:
1.封装:安全性
2.继承:为了代码的复用(java是单继承)
3.多态:一个事物表现出不同的形态
4.注意重载和重写的区别
5.注意this和super的区别

四.抽象类


抽象类
1.抽象类就是Java中没有方法体的方法。类中如果有抽象方法,该类就定义为抽象类。
2.抽象类和抽象方法必须有关键字abstract(抽象的)修饰。
3.抽象类的格式为:abstract class 类名 {}
4.抽象方法的格式为:public abstract void eat();
5.抽象类可以没有抽象方法,有抽象方法的一定是抽象类。
6.抽象类中可以有构造方法,是为了子类访问父类数据时初始化父类的。
7.如果想让抽象类实例化,就要以多态的方式由具体的子类实例化。
8.抽象类的子类必须是抽象类,或者重写所有方法的子类。

抽象类的成员特点
1.抽象类的成员变量既可以是变量,也可以是常量。
2.抽象类有用于子类访问父类数据初始化用的构造方法。
3.抽象类的成员方法既可以是抽象的,也可以是非抽象的。
4.关键字abstract不能和private、final、static关键字共存。
5.如果一个抽象类没有抽象方法,那就不能创建对象。

五.接口


接口
对象与生俱来的功能定义在类中很合理,但是局部对象的特性定义在类中就不合理了。不是天生具备的就不合适。如果有扩展功能,就要用接口来定义扩展功能。

接口的概念
1.接口:用来定义一些扩展功能,那些事物将来要用接个功能,就可以实现这个借口然后做出具体实现。
2.接口就是用来定义规范和规则的。
3.用关键字interface来定义接口。将class改成interface。格式: interface 接口名 {}。类实现接口用implements表示,格式:class 类名 implements 接口名{}。
4.接口中的功能没有具体实现。接口不能实例化,不能new对象。


接口的特点
1.接口中成员变量的特点:全是静态的公共变量。没有变量一说。接口中成员变量前面有默认的修饰符。
2.接口中没有构造方法,不能new对象。
3.接口中的方法全是抽象方法,内有非抽象方法。方法前存在默认修饰符。


接口和接口。类和类的关系
1.类和类的关系:单继承,支持多层继承。
2.类和接口的关系:实现关系,既可以单实现,也可以多实现。实现接口必须重写接口中所有方法。
3.接口和接口的关系:继承关系,并且可以多继承。单继承一个类的同时可以多继承多个接口。


抽象类和接口的区别
1.设计思想:抽取所有子类的共性,并强制子类对共性功能重写。接口定义的是整个继承体系当中的一些额外扩展功能,那些类想要具备扩展功能就可以具体实现某个接口。
2.抽象类中有构造方法,接口中没有。
3.抽象类中中既可以有抽象方法也可以由非抽象方法。接口中全是静态的公共变量。
 

数组,String类


一.数组


        数组是最为常见的一种数据结构,是相同类型的、用一个标识符封装到一起的基本类型数据序列或对象序列。

        数组是具有相同数据类型的一组数据的集合,根据维数不同可以分为一维数组、二维数组和多维数组。大家可以将一维看作直线,二维看作平面、三维看成立体空间。

一、一维数组

1. 创建一维数组

数组作为对象允许使用new关键字进行内存分配。在使用数组之前,必须首先定义数组变量所属的类型,即声明数组。

声明数组有两种形式,在之前的篇章中也有所提及,语法格式如下:

数组元素类型 数组名[];

数据元素类型[] 数组名;

程序员在编写代码时习惯使用第二种方法,需要注意的声明时是[]中不能添加任何数据。下面是一个声明数组的例子,两种声明方式都正确,不同数据要声明不同类型的数组:

int arr[]; //声明int型数组,数组中每个元素都是int型数值

String[] str; //声明String数组,数组中每个元素都是String数组

声明数组后还不能访问它的任何元素,要想真正使用数组还要为其分配内存空间,且分配内存空间时必须指明数组的长度。语法格式如下:

数组名 = new 数组元素类型[数组元素个数];

下面举一个例子,为数组分配一个大小为5的内存空间:

arr = new int[5];

一维数组arr的存储状态如下图:

arr[0]

arr[1]

arr[2]

arr[3]

arr[4]

括号中的0、1、2、3、4表示数组的下标。需要注意的是,下标从0开始,到数组长度-1为止。

当然也可以直接声明并分配内存,如下:

int[] week = new int[7];

上述代码创建了一个一维数组week,并指定了数组长度为7。

还有一点需要注意的是,使用new关键字为数组分配内存时,数组中各个元素的初始化值都为0。比如上述代码使用new关键字创建了长度为7的week数组,那么数组中的元素可以表示为[0, 0, 0, 0, 0, 0, 0],这就是一个一维数组,数组中的每一个元素都初始化为0。

2. 初始化一维数组

前面说的初始化是使用new关键字自动初始化,数组也可以与基本数据类型一样由程序员进行初始化操作,可以分别初始化数组中的每个元素。

数组初始化的方式有两种,下面用一个例子来说明:

int arr1[] = new int[]{1,2,3,4,5,6}; //第一种

int arr2[] = {10,11,12,13,14}; //第二种

数组初始化的方式是:把数据包括在大括号之内,中间用逗号分开数组中的元素的值,系统自动为数组分配一定的空间。上述第一种创建了6个元素的数组,其值依次为1、2、3、4、5、6,第二种创建了5个元素的数组,其值依次为10、11、12、13、14。

下面我们结合上篇的流程控制来举一个一维数组数组的例子。

eg:求一维数组中各元素的和

1 public classSumNum {2

3 public static voidmain(String[] args) {4 int[] num = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //创建并初始化数组

5 int sum = 0; //存储累加和

6

7 for(int i=0; i

9 System.out.print(num[i] + "="); //输出等号

10 } else{11 System.out.print(num[i] + "+"); //输出加号

12 }13 sum +=num[i];14 }15

16 System.out.print(sum);17 }18

19 }

其中,num是数组名,这个数组总长是10,但一般在编程中,可能会出现需要修改数组长度的时候,此时假设for循环中写的是i<10,那么我们在修改代码时可能会忽略这个地方导致出现意想不到的错误,所以我们在编写代码时,要习惯性使用数组名.length来获取数组的长度,如上述代码中的num.length其实就是10。而下一句中的num.length-1是因为数组下标是从0开始的,当我们获取最后一个元素时,数组下标为总长减一,故num.length-1就是最后一个元素10的下标为9。最后的运行结果为:

二、多维数组

1. 二维数组

如果一维数组中的各个元素仍是一维数组,那么它就是一个二维数组。二维数组常用于表示表,表中的信息以行和列的形式组织,第一个下标代表元素所在的行,第二个下标代码元素所在的列。或用线性代数的知识来说,二维数组就是一个矩阵,第一个下标代表矩阵的行,第二个下标代表矩阵的列。

二维数组的声明也有两种方式,语法格式如下:

数组元素类型 数组名[][];

数组元素类型[][] 数组名;

与一维数组一样,如果声明时没有分配内存空间,同样也要使用关键字new来分配内存。

二维数组可以看作由多个一维数组组成,在给二维数组分配内存时,可以为这些一维数组同时分配相同的内存。第一个中括号中的数组是一维数组的个数,第二个中括号中是一维数组的长度。

int arr = new int[2][4];

上述代码就是一个典型的声明一个二维数组arr并为其分配内存空间,分配后arr拥有两个长度为4的一维数组,内存分配如下图:

arr[0][0]

arr[0][1]

arr[0][2]

arr[0][3]

arr[1][0]

arr[1][1]

arr[1][2]

arr[1][3]

我们也可以为每一维单独分配内存。如下:

int[][] a = new int[2][];

a[0] = new int[2];

a[1] = new int[3];

这样我们就可以得到一个二维数组,第一个一维数组长度为2,第二个一维数组长度为3。

二维数组的初始化与一维数组类似,同样可以使用大括号完成二维数组的初始化。如下:

int arr[][] = {{3,0},{5,7},{6,9}};

这就是一个arr[3][2]的数组,有三个一维数组,每个一维数组的长度都为2。但要明确下标是从开始的,比如arr[1][0]=5,指的是第二个一维数组的第一个元素为5。故我们也可以直接给arr[x][y]赋值,如给arr[1]的第二个元素赋值:

arr[1][1] = 50;

那么上述数组就变成了如下形式:

int arr[][] = {{3,0},{5,50},{6,9}};

2. 三维数组

对于三维数组,想必各位已经能推算出来了,一维用一个括号,二维用两个括号,那么三维就用三个括号。

int arr[][][] = new int[][][]{
{{1,2,3},{4,5,6}},

{{7,8,9},{10,11,12}},

{{13,14,15},{16,17,18}}

};

三、数组的基本操作

1. 遍历数组

遍历数组就是获取数组中的每个元素,通常遍历数组都是用for循环实现的。

遍历一维数组只需用一个for循环即可实现,如下例:

int[] week = {1,2,3,4,5,6,7}for(int i=0; i<7; i++) {
System.out.println(week[i]);

}

还有另一种遍历方法就是使用foreach语句,这个在上一篇最后已经提到了,这里就不再过多赘述了,直接上代码:

for(intarr : week) {
System.out.println(arr);

}

遍历二维数组比一维稍微麻烦一点,需使用双层循环,如下例:

int a[][] = {{1,2},{3,4,5},{6,7,8,9}};for(int i=0; i

System.out.print(a[i][j]+ " "); //输出

}

System.out.println();//换行

}

需要注意的是,第一个for循环是遍历行的,所以是长度是a.length,而第二个for循环是遍历列的,长度是a[i].length。

再强调一次print和println的区别,print是输出但不换行,println是输出并换行,所以输出结果如下:

2. 对数组进行排序

数组可以通过使用java.util包中的Arrays类来实现排序,sort()方法提供了许多重载形式,可对任意类型数组进行升序排序。语法格式为 Arrays.sort(object);

eg:创建一数组,排序后输出。

1 public classTaxis {2

3 public static voidmain(String[] args) {4 int[] arr = new int[] {17, 21, 6, 59, 31, 13, 3};5

6 System.out.println("原始数组:");7 for(int i=0; i

11 Arrays.sort(arr); //按字典顺序排序

12

13 System.out.println("\n排序后的数组:");14 for(int i=0 ; i

19 }

Java语言中的String类型数组排序算法是根据字典编排顺序排序的,因此数字排在字母前面,大写字母排在小写字母前面。运行结果如下:

3. 其它操作

数组还有许多操作,比如填充替换数组元素的fill方法、复制数组的copyOf方法等等,包括Arrays类也还提供了其它操作数组的方法,大家可以通过查阅资料或API来学习它们的使用方法。

还有一个很重要的知识点就是排序算法,上面已经出现了多重循环,那么就可以由此引出诸如冒泡排序、选择排序等等的许多排序算法,如果以后有时间我可能会总结一下各类排序算法,常见的有冒泡、选择、直接插入、快速、归并、希尔、堆排序等等
 

二。String类


常量池:
静态常量池:指的是编译时常量池,我们不关注
字符串常量池:在java8之后放在了堆里
运行时常量池:放在了元空间里,元空间放在了主存里(不在堆里了,不占有jvm内存空间)

使用字符串常量池(也就是当我们用类似String a = “xiaoming”;)。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串!

但当我们使用new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后放池里一份(所以new一般是new两个对象),然后返回堆中的地址。

举个例子来理解String字符串:

String a = “xiaoming”;
final String m = “xiao”;
final String n = “ming”;
String b = “xiao”;
String c = “ming”;
String d = “xiao” + “ming”;
String e= b + c;
String f = m+n;
String g = new String(“xiaoming”);
System.out.println((a == d));
System.out.println((a == e));
System.out.println((a == f));
System.out.println((a == g));

输出结果:
true
false
true
false

首先,==比的都是地址,第一种情况下,由于编译期会优化,优化后的 d = “xiaoming”,由于字符串常量池的原因,d会指向已经创建好的a,所以地址不变返回true。

第二种情况,JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,所以会新建一个变量,a和e的地址就不相同了,返回true。

第三种情况,被final修饰后的变量不可再赋值,所以会返回true。

第四种情况,最常见,直接新建一个变量在堆里,二者地址显然不同,返回false。

String、StringBuffer、StringBuilder的区别

(1)可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。

(2)是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。

(3)String、StringBuilder、StringBuffer三者的执行效率:
StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。比如String str = “hello”+ "world"的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高。因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:
当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;
当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。
 

集合,迭代器


一.集合
Java中所有的类都位于java.util包下,主要由两个接口派生出来。分别是Collection(集合)和Map.Collection(映射集合),包含了List和Set两大分支。Map是一个映射接口。Set、Map、List(集,映射,列表)可以看做集合的三大类。
而遍历集合的工具有Iterator和Enumeration(迭代器和枚举);
Arrays(数组)和Collection(集合)是操作数组集合的两个工具类。

一、Java中的集合主要分为四类:
1、List列表:有序的,可重复的;
2、Queue队列:有序,可重复的;
3、Set集合:不可重复;
4、Map映射:无序,键唯一,值不唯一。

二、集合类下包含的主要的实现类:

① List列表:有序,可重复

ArrayList数组列表,有序,可重复,内部是通过Array实现。对数据列表进行插入、删除操作时都需要对数组进行拷贝并重排序。因此在知道存储数据量时,尽量初始化初始容量,提升性能。
LinkedList双向链表,每个元素都有指向前后元素的指针。顺序读取的效率较高,随机读取的效率较低。
Vector向量,线程安全的列表,与ArrayList一样也是通过数组实现的,不同的是Vector是线程安全的,也即同一时间下只能有一个线程访问Vector,线程安全的同时带来了性能的耗损,所以一般都使用ArrayList。
Stack栈,后进先出(LIFO),继承自Vector,也是数组,线程安全的栈。但作为栈数据类型,不建议使用Vector中与栈无关的方法,尽量只用Stack中的定义的栈相关方法,这样不会破坏栈数据类型。
ArrayQueue数组队列,先进先出(FIFO)
② Queue队列,有序、可重复

ArrayDeque数组实现的双端队列,可以在队列两端插入和删除元素
LinkedList也是双向链表
PriorityQueue优先队列,数组实现的二叉树,完全二叉树实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)
③ Map映射 /字典,无序,键值对,键唯一


HashMap哈希映射/字典,无序字典,键值对数据,key是唯一的,Key和Value都可以为null
TreeMap红黑树实现的key->value融合,可排序,红黑树是一种自平衡二叉查找树。
LinkedHashMap链表映射/字典,继承了hashmap的所有特性,同时又实现了双向链表的特性,保留了元素插入顺序。
④ Set集合,不可重复

HashSet基于HashMap实现的集合,对HashMap做了一些封装。与HaspMap不同的是元素的保存为链表形式,插入数据时遍历链表查看是否有相同数据,有则返回false,没有则返回true.
LinkedHashSet链表集合,继承自HashSet与LinkedHashMap相似,是对LinkedHashMap的封装。
TreeSet红黑树集合,与TreeMap相似,是对TreeMap的封装。 


二.迭代器(Iterator)


一.迭代器作用:
   通用的取出集合中元素的方法(对集合进行遍历)

二.迭代器概念:
1)迭代: 即collection集合元素的通用获取方式,在取出元素之前先要判断集合中是否有没有元素,如果有,就把这个元素取出来,接下来继续判断,如果还有,就继续取出来,直到把集合中所有元素全部取出

这种取出方式专业术语称为迭代

2)使用java.util.Iterator接口对集合进行遍历

三.迭代器的定义:
    提供一种方法访问一个容器(container)对象中各个元素,而又不需要暴露该对象的内部细节

迭代器模式,就是为了容器而生

1)Collection接口继承了java.lang.Interable接口,该接口有一个interator()方法,那么实现Collection接口的类都有interator()方法,用以返回一个实现了Interator接口的对象

2)Interator仅用于集合的遍历,本身并不提供继承对象的能力,如果需要创建Interator对象,则必须有一个被迭代的集合

3)集合对象每次调用interator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
 

四.常用方法:


boolean hasNext();
//判断集合中是否还有下一个元素,如果仍有元素可以迭代,则返回true;没有则返回false

E next();
//返回迭代的下一个元素

   Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊 Collection中接口中有一个iterator()方法,这个方法返回的就是迭代器实现类的对象

default remove();
//移除操作,不同于集合直接调用remove()

五、Iterator与泛型(Genetic)搭配:
         Iterator 对集合类中的任何一个实现类,都可以返回这样一个 Iterator 对象。可以适用于任何一个类。因为集合类(List和Set等)可以装入的对象的类型是不确定的,从集合中取出时都是 Object 类型,用时都需要进行强制转化,这样会很麻烦,用上泛型,就是提前告诉集合确定要装入集合的类型,这样就可以直接使用而不用显示类型转换。

六、foreach 和 Iterator 的关系
       foreach 以用来处理集合中的每个元素而不用考虑集合定下标。就是为了让用 Iterator 简单。但是删除的时候,区别就是在 remove,循环中调用集合remove会导致原集合变化导致错误,而应该用迭代器的 remove 方法。

for 循环和迭代器 Iterator 对比:
①采用 ArrayList 对随机访问比较快,而 for 循环中的 get(),采用的即是随机访问的方法,因此在 ArrayList 里,for 循环较快
②采用 LinkedList 则是顺序访问比较快,Iterator 中的 next(),采用的即是顺序访问的方法,因此在 LinkedList 里,使用 Iterator 较快。
③从数据结构角度分析,for 循环适合访问顺序结构,可以根据下标快速获取指定元素。而 Iterator 适合访问链式结构,因为迭代器是通过 next() 和 Pre() 来定位的,可以访问没有顺序的集合。
④使用 Iterator 的好处在于可以用相同方式去遍历集合中元素,而无需考虑集合类的内部实现(只要它实现了java.lang.Iterable接口)。如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改;如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整。因为 List 有序,Set 无序,结构不同,它们的访问算法也不一样。(由此也说明遍历和集合本身分离了)
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值