开发环境
- JDK:JAVA 编译环境
- JRE:JAVA 运行环境
基本语法
数据类型(四种八类型)
- 整数型:byte, short, int, long;
- 浮点型:float, double;
- 字符型:char;
- 布尔型:boolean.
书写规范
类名保持首字母大写,包名保持小写,方法名使用驼峰写法。
面向对象
在 JAVA 中,万事万物都是对象。尽管如此,实际的代码编译中操作的确实对象的引用(reference)
。
类也是对象
对象引用是相对于对象独立的存在,也就是说有一个对象应用,但是不需要一个对象与之对应。
Car carKey;
以上创建的只是引用,而并非对象。如果想在代码之中使用这个引用时,会返回一个没有对象关联的异常。安全的做法是,在创建对象引用时把一个对象赋给它。此时我们需要使用new
方法。
new一个对象
使用new
进行对象的创建:
Sheep sheep1 = new Sheep();
Sheep sheep2 = new Sheep("codesheep", 18, 65.0f);
通过new
方法,通过调用类的无参或有参构造的方法来实例化了两个对象。
在创建对象的过程中,JVM
处理了如下步骤
- 首先,
new
一个对象时,以Sheep sheep = new Sheep()
为例,JVM
会先来检查Sheep
这个符号引用的类是否已经被加载过,如果没有就要执行对应类的加载过程。在这个过程中,对象占用的内存就已经定下来了。 - 声明类型引用。
Sheep sheep = new Sheep()
中就声明了一个Sheep
类型的引用sheep
。 - 按照第一步中规划的内存分配计划,
JVM
会在堆上给对象分配内存。 - 初始化0值。例int的初始化0值就是0,对象化的初始化0值就是null。
- 接下来
JVM
会进行对象头的设置,这里面就主要包括对象的运行时数据(比如Hash码、分代年龄、锁状态标志、锁指针、偏向线程ID、偏向时间戳等)以及类型指针(JVM通过该类型指针来确定该对象是哪个类的实例); - 属性的显示初始化也好理解,比如定义一个类的时候,针对某个属性字段手动的赋值,如:
private String name = "codesheep";
就在这时候给初始化上; - 最后是调用类的构造方法来进行进行构造方法内描述的初始化动作。
经过以上的步骤一个对象才得以诞生。
属性和方法
类的一个最基本的要素就是属性和方法。
属性也被称为字段,是类的重要组成部分。属性可以使任意类型的对象,也可以是最基本的数据类型。
class A{
int a;
Apple apple;
}
类中还应该包括方法,方法表示的是做某些事情的方式。方法其实就是函数,只不过Java习惯把函数成为方法,这种叫法也体现了面向对象的概念。
方法的基本构成包括:
- 方法名称;
- 参数;
- 返回值;
- 方法体。
e.g.
public int getResult(){
// ...
return 1;
}
其中getResult
就是方法名,()
表示接受的参数,return
表示方法的返回值,{}
中的为方法体。
构造方法
在Java中,有一种特殊的方法被称为构造方法(或构造函数、构造器)。在Java中,通过提供这个构造器,来确保每个对象都被初始化。构造方法只能在对象创建时期调用一次,保证了对象初始化的进行。构造方法比较特殊,它没有参数类型和返回值,它的名称要和类名称保持一致,并且构造方法可以有多个。
e.g.
//一堆初始化方法的初始化类
class Apple{
int sum;
String color;
public Apple(){}
public Apple(int sum){}
public Apple(String color){}
public Apple(int sum, String color){}
}
//开始
class createApple{
public static void main(String[] args){
Apple apple1 = new Apple();
Apple apple2 = new Apple(1);
Apple apple3 = new Apple("red");
Apple apple4 = new Apple(2,"color");
}
}
以上,在Apple
类中,定义了四个构造方法。其中,不加任何参数的构造方法被称为默认的构造方法,即:
Apple apple1 = new Apple();
如果类中没有定义任何构造方法,JVM会自动生成一个默认的构造方法。默认的构造方法以被称为默认构造器或者无参构造器。但是如果定义了任何一个构造方法,JVM将不再提供默认构造器。
方法重载
在 Java 中一个很重要的概念是方法的重载,它是类名的不同表现形式。之前说的构造函数,实际上也是重载的一种。
每个重载的方法都有独一无二的参数列表
(其中包括参数的类型,顺序,参数数量等),此为 Java 来区分重载目标方法的标准。
重载的条件
- 方法名称必须相同;
- 参数列表必须不同 (个数不同、类型不同、参数类型排列顺序不同等);
- 方法的返回类型可以相同也可以不同;
- 仅仅返回类型不同不足以成为方法的重载;
- 重载是发生在编译时的,因为编译器可以根据参数的类型来选择使用哪个方法。
public class Apple{
int sum;
String color;
public Apple(){}
public Apple(int sum){} //重载
public int getApple(int num){
return 1;
}
//重载
public String getApple(String color){
return "color";
}
}
方法的重写
方法重写虽然与重载名字很相似,但是完全不同。方法重写的描述是对子类和父类之间的。而重载指的是同一类中。
重写的原则:
- 重写的方法必须要和父类保持一致,包括返回值的类型,方法名,参数列表也都一样;
- 重写的方法可以使用
@Override
注解来进行标注; - 子类中重写方法的访问权限不能低于父类中方法的访问权限。
class Fruit{
public void eat(){
System.out.println('eat fruit');
}
}
class Apple extends Fruit{
@Override
public void eat(){
System.out.println('eat apple');
}
}
初始化
类初始化
创建对象引用时,如果没有相应的类,则实际上是调用了这个对象的无参构造方法来进行的初始化,如下:
class Car{
public Car(){}
}
实际上就是初始化了一个类,默认进行,由 JVM 自动添加。
成员的初始化
Java会尽量保证每个变量在使用前都会获得初始化,初始化涉及两种初始化。
一种是编译器默认指定的字段初始化,基本数据类型的初始化。
| 类型 | 初始值 |
| :-----: | :------: |
| boolean | false |
| char | /u0000 |
| byte | (byte)0 |
| short | (short)0 |
| int | 0 |
| long | 0L |
| float | 0.0f |
| double | 0.0d |其他对象类型的初始化,String也是一种对象,对象的初始值都是null,其中也包括基本类型的包装类。
一种是指定数值的初始化,e.g.
int a = 11
也就是说,指定a的初始化值不是0,而是11.其他基本类型和对象类型也是一样的。
构造器初始化
可以利用构造器来对某些方法和某些动作进行初始化,确定初始值,e.g.
public class Counter{
int i;
public Counter(){
i = 11; //构造器对i的初始化值处理,对i初始化为11.
}
}
初始化顺序
需要探讨的初始化顺序:
- 静态属性:
static
开头定义的属性; - 静态方法块:
static{}
; - 普通属性:非
static
定义的属性; - 普通方法块:
{}
包起来的代码块; - 构造函数:类名相同的方法;
- 方法:普通方法。
public class LifeCycle{
//静态属性
private static String staticField = getStaticField();
//静态方法块
static {
System.out.println(staticField);
System.out.println("静态方法块初始化");
}
//普通属性
private String field = getField();
//普通方法块
{
System.out.println(field);
}
//构造函数
public LifeCycle(){
System.out.println("构造函数初始化");
}
public static String getStaticField(){
String statiFiled = "Static Field Intial";
return statiFiled;
}
public static String getField(){
String filed = "Field Initial";
return filed;
}
//主函数
public static void main(String[] args){
new LifeCycle();
}
}
执行顺序:
- 静态属性初始化;
- 静态方法块初始化;
- 普通属性初始化;
- 普通方法块初始化;
- 构造函数初始化。
数组初始化
数组是相同类型的、用一个标识符名称风撞到一起的一个对象序列或基本类型数据序列。数组是通过方括号下表操作符[]
来定义使用的。e.g.
//以下两段含义相同
int[] a1;
int a1[];
//赋值操作
int arrary[4] = {1, 2, 3, 4}; //直接给每个元素赋值
int arrary[4] = {1, 2}; //给一部分赋值,后面的都是0
int arrary[] = {1,2}; //由参数的各述决定数组的个数
可变参数列表
Java 中对数组的一种比较冷门的用法就是可变参数
,可变参数的定义如下:
public int add(int... numbers){
int sum = 0;
for(int num : numbers){
sum += num;
}
return sum;
}
然后,可以使用以下方法进行可变参数的调用:
add(); //不传参数
add(1); //传递一个参数
add(2,1); //传递多个参数
add(new Integer[] {1, 3, 2}); //传递数组
对象销毁
Java 与 C 语言不同的一个重要特征就是 Java 不需要自己手动管理对象的销毁工作,而是由 JVM
来管理和销毁。虽然不需要手动销毁,但是仍需要知道对象作用域
这个概念。
对象作用域
绝大多数语言都有作用域(scope)
这个概念。作用域决定了其内部定义的变量名的可见性和生命周期。在Java 中,作用域通常由{}
的位置来决定,e.g.
{
int a = 11;
{
int b =12;
}
}
a 变量会在两个{}
作用域有效,而 b 变量的值只能在它自己的{}
内有效。
虽然存在作用域,但是不允许以下写法:
{
int x = 11;
{
int x = 12;
}
}
这种写法在 C 中是可以的,但是在 Java 中不允许,因为老头认为这样写会导致程序混乱。
this 和 super
this
和 super
都是 Java 中的关键字
this
标识的当前对象, this
可以调用方法、调用属性和指向对象本身。this
在 Java 中的使用一般有三种。
e.g.
public class Apple{
int i = 0;
Apple eatApple(){
i++;
return this; //加入this,调用eatApple()总会返回对象自身
}
public static void main(String[] args){
Apple apple = new Apple();
apple.eatApple().eatApple();
}
}
//this修饰属性
public class Banana{
private int num;
public Banana(int num){
this.num = num;
}
public static void main(String[] args){
new Banana(10); //传递"10"给全局变量num
}
}
//this充当全局关键字,this()必须放在构造方法的第一行,否则编译不通过
public class Apple{
private int num;
private String color;
public Apple(int num){
this(num,"红色");
}
public Apple(String color){
this(1,color);
}
public Apple(int num, String color){
this.num = num;
this.color = color;
}
}
如果 this 是指向自身的一个引用,那么 super
就是指向父类的一个引用。super
关键字和 this
一样,可以使用 super.object
来引用父类成员。
e.g.
public class Fruit{
int num;
String color;
public class Apple extends Fruit{
@Override
public void eat(){
super.num = 10;
System.out.println("eat" + num + "Apples");
}
}
}
使用 super
来调用父类的构造函数同this
。
两个方法都有的特征:
- 调用位置都是构造函数第一行,非构造函数位置自定;
- 调用次数:一个构造函数只可调用一次。
访问控制权限
访问控制权限又称为封装
,它是面向对象三大特征中的一种。访问控制类的核心是只对需要的类可见。
Java 中成员的访问权限总共有四种:public
, protected
, default
, private
.
private | default | protected | public | |
---|---|---|---|---|
同一类 | ||||
同一包中的类 | ||||
子类 | ||||
其他包中的类 |
标识可以访问。
继承
继承是所有oop 语言都不可缺少的一部分。只要一个类被创建,那么它就隐式继承自 Object 父类,只不过没有指定。如果你显示指定了父类,那么你继承于父类,而你的父类继承于object 类。
//继承关键字:extends
class Father{}
class Son extends Father{}
Son
类会保留有Father
的属性,如果Son
类没有实现自己的方法的话,那么默认就是用的父类Father
的方法。如果子类实现了自己的 feature 方法,那么就相当于是重写了父类的 feature 方法。
多态
多态指的是同一个行为具有不同的表现形式。是指一个类实例(object )的相同方法在不同情形下具有不同表现形式。封装和继承是多态的基础,也就是说多态只是一种表现形式而已。
多态实现的三个充要条件
- 继承
- 重写父类方法
- 父类引用指向子类对象
e.g.
public class Fruit{
int num;
public void eat(){
System.out.println("eat Fruit");
}
}
public class Apple extends Fruit{
@Override
public void eat(){
super.num = 10;
System.out.println("eat " + num + " Apple");
}
public static void main(String[] args){
Fruit fruit = new Apple();
fruit.eat();
}
}
如代码所示,Fruit fruit = new Apple()
Fruit类型的对象指向了Apple对象的引用,此处便是父类引用指向子类对象,因为Apple继承于Fruit,并且重写了eat方法,所以能够表现出多种形态的形式。
组合
指:将对相引用置于新类中。组合也是提高类复用性的一种方法。如果想让类具有更多的拓展功能,要做到多用组合,少用继承。
public class SoccerPlayer{
private String name;
private Soccer Soccer;
}
public class Soccer{
private String soccerName;
}
如上 SoccerPlayer 中引用了 Soccer 类,通过引用 Soccer 类,调用其中的属性和方法。组合和继承石油区别的,如下:
特征 | 组合 | 继承 |
---|---|---|
关系 | 组合是一种has - a 的关系,可以理解为有一个 | 集成式一种 is - a 的关系,可以理解为是一个 |
耦合性 | 组合的双方是一种松耦合的关系 | 继承双方紧耦合 |
是否存在多态 | 组合不具备多态和向上转型 | 继承是多态的基础,可以实现向上转型 |
时期 | 组合是运行期绑定 | 继承是编译期绑定 |
代理
大致描述:A想要调用B类的方法,A不直接调用,A会在自己的类中创建一个B对象的代理,再由代理调用B的方法。
e.g.
public class Destination {
public void todo(){
System.out.println("control...");
}
public class Device{
private String name;
private Destination desitination;
private DeviceController deviceController;
public void control(Destination destination){
destination.todo();
}
}
public class DeviceController{
private Device name;
private Destination destination;
public void control(Destination destination){
destination.todo();
}
}
}
向上转型
向上转型代表了父类和子类之间的关系,其实父类和自磊之间不仅仅有向上转型,还有向下转型,他们的转型后范围不同。
- 向上转型:通过子类对象(小范围)转化为父类对象(大范围),这种转换是自动完成的,不用强制。
- 向下转型:通过父类对象(大范围)转化为子类对象(小范围),这种转换不是自动完成的,需要强制指定。
static
static 是 Java 中的关键字,它的意思是静态的
,static 可以用来修饰成员变量和方法, static 用在没有创建对象的情况下调用 方法/变量。
用static声明的成员变量为静态策划能够员变量,也就是类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。
static String name = "aaa";
使用 static 修饰的方法称为静态方法,静态方法能够直接适用
类名.方法名
进行调用。由于静态方法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有this关键字的,实例变量都会有this关键字的效果。在静态方法中不能访问类的非静态成员变量和非静态方法。static void printMessage(){ System.out.println("aaa is aaa"); }
static 除了修饰副属性和方法外,还有静态代码块的功能,可用于类的初始化操作。进而提升程序的性能。
public class StaicBlock{
static{
System.out.println("I am sb");
}
}
由于静态代码块随着类的加载而执行,所以很多时候只需要进行一次初始化操作放在static代码块中进行。
final
- final 修饰类时,表示这个类不能被继承,final 类中的成员变量可以根据需要设为 final ,但是要注意 final 类中的所有成员方法都被隐式地指定为 final 方法。
- final 修式方法时,表明这个方法不能被任何子类重写,因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才能将方法设置为final。
- final 修饰变量分为两种情况,一种是修饰基本数据类型,表示数据类型的值不能被修改;一种是修饰引用类型,表示对其初始化之后便不能再让其指向另一个对象。
接口和抽象类
接口
技术黑箱与统一标准。
在 Java 中,接口是由 interface
关键字来表示的,比如我们可以向下面定义一个接口
public interface 123(){
void writeWell();
}
接口的特征:
interface
接口是一个完全抽象的类,不会提供任何方法的实现,只是会进行方法的定义。- 接口中只能是用来那个中访问修饰符,一种是
public
,它对整个项目可见;一种是default
缺省值,它只具有包访问权限。 - 接口只提供方法的定义,接口没有实现,但是接口可以被其他类实现。也就是说,实现接口的类需要提供方法的实现,实现接口使用
implements
关键字来显示,一个接口可以有多个实现。
class 123 implements 123Job{
@Override
public void writeWell(){
Sysyem.out.println("123");
}
}
- 接口不能被实例化,所以接口中不能有任何构造方法,定义构造方法编译会出错。
- 接口的实现比如实现接口的全部方法,否则必须定义为抽象类。
抽象类
抽象类是一种抽象能力弱于接口的类,在Java中,抽象类使用abstract
关键字来表示。如果把接口形容为为狗这个动物,那么抽象类可以说是毛色是白色、小型的品种,而实现类可以是具体的类,比如说是泰迪,博美。可以如下图定义抽象类
public interface Dog {
void FurColor();
}
abstract class WhiteDog implements Dog {
public void Fur Color(){
System.out.println("Fur is white.");
}
abstract void SmallBody();
}
在抽象类中,具有如下特征
- 如果一个类中有抽象方法,那么这个类一定是抽象类。使用关键字
abstract
修饰的方法一定是抽象方法,具有抽象方法的类一定是抽象类。实现类方法中只有方法具体的实现。 - 抽象类中不一定只有充想方法,抽象类中也可以有具体的方法。可以选择是否实现这些方法。
- 抽象类中的约束不想接口那么严格,可以在抽象类中定义 构造方法、抽象方法、普通属性、方法、静态属性、和静态方法。
- 抽象类和接口一样不能被实例化,实例化只能实例化具体的类。
异常!
异常是程序经常会出现的,发现错误的最佳时机实在编译阶段,也就是在试图运行程序之前。但是在编译期间并不能找到所有的错误,有一些NullPointerException
和ClassNotFoundException
异常在编译期间是找不到的,这些异常是RuntimeExcption
运行时异常,这些异常往往在运行时才能被发现。
我们写 Java 程序经常会出现两种问题,一种是 java.lang.Exception ,一种是 java.lang.Error,都用来表示出现异常情况,下面针对这两种概念进行理解。
认识 Exception
Exception
位于java.lang
包下,它是一种顶级接口,继承于Throwable
类,Exception类及其子类都是 Throwable 的组成条件,是程序出现的合理情况。
在认识 Exception 之前,有必要先了解一下什么是 Throwable
。
什么是 Throwable
Throwable 类是 Java 语言中所有errors
和exceptions
的父类。只有继承于 Throwable 的类活着其子类才能够被抛出,还有一种方式是带有 Java 中的 @throw 注解也可以抛出。
在java规范
中,对非受查一场合受查异常的定义是这样的
The unchecked exception classes are the run-time exception classes and the error classes.
The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are
Throwable
and all its subclasses other thanRuntimeException
and its subclasses andError
and its subclasses.
也就是说,除了RuntimeException
和其子类,以及Error
和其子类,其他所有异常都是checkedException
。
那么按照这种逻辑,我们可以对 Throwable 及其子类进行归纳分析。
由此可见,Throwable 位于异常和错误的最顶层,我们查看 Throwable 类中发现它的方法和属性有很多,现在只总结其中比较常用的几个:
//返回抛出异常的详细信息
public String getMesage();
public String getLocalizedMessage();
//返回异常发生的简要描述
public String toString();
//打印异常到标准输出流上
public void printStackTrace();
public void printStackTrace(PrintStream s);
public void printStackTrace(PrintWriter s);
//记录栈帧的当前状态
public synchronized Throwable fillInStackTrace();
此外,因为 Throwable 的父类也是 Object,所以常用的方法还有继承父类的getClass()
和getName()
方法。
常见的Exception
现在知道Exception
的父类是Throwable
,并且Exception
有两种异常,一种是RuntimeException
;一种是CheckedException
,这两种异常都应该去捕获
。
下面列出一些 Java 中常见的异常及其分类。
RuntimeException
序号 | 异常名称 | 异常描述 |
---|---|---|
1 | ArrayIndexOutOfBoundsException | 数组越界异常 |
2 | NullPointerException | 空指针异常 |
3 | IllegalArgumentException | 非法参数异常 |
4 | NegativeArraySizeException | 数组长度为负 |
5 | IllegalStateException | 非法状态 |
6 | ClassCastException | 类型转换异常 |
UncheckedException
序号 | 异常名称 | 异常描述 |
---|---|---|
1 | NoSuchFieldException | 表示该类没有指定名称 |
2 | NoSuchMethodException | 表示该类没有指定方法 |
3 | IllegalAccessException | 不允许访问某个类 |
4 | ClassNotFoundException | 类没有找到 |
与Exception有关的Java关键字
Java中会怎么处理这些异常?有如下关键字:throws, throw, try, finally, catch
throws和throw
在Java
中异常也是一个对象。若要被自定义抛出或者应用程序抛出,必须借助throws
和throw
语句来定义异常。
throws 与 throw通常是成对出现的,如
static void catchException() throws Exception{
throw new Exception();
}
throw 语句用在方法体内部,表述抛出异常,由方法体内的语句处理。throws 语句用在方法声明后,表示再抛出异常,由该方法的调用者来处理。
throws主要是声明这个方法会抛出这个类型的异常,使它的使用者知道要捕获这个异常。throw 是具体向外抛异常的动作,所以它是抛出一个异常实例。
try finally catch
这三个关键字主要由一下组合:
try...catch
try...finally
try...catch...finally
//try...catch 表示对某一段代码可能抛出异常进行的捕获
static void cacheException() throws Exception{
try {
System.out.println("1");
}catch (Exception e){
e.printStackTrace();
}
}
//try...finally表示对一段代码不管执行情况如何,都会走到finally中的代码
static void cacheException() throws Exception{
for (int i=0, i<5, i++){
System.out.println("enter: i=" + i);
try{
System.out.println("execute: i=" + i);
continue;
}finally{
System.out.println("leave: i =" + i);
}
}
}
Error
Error是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM 出现的问题。这些错误大多数是不可检查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的情况。e.g. OutOfMeoryError
和StackOverFlowError
异常的出现会有基种情况。
内部类
以上的所有都是普通类的定义。而内部类与其不同。
内部类的定义:可以将一个类的定义放在另一个类的内部,这就是内部类。
创建内部类
public class OuterClass{
private String name;
private int age;
class InnerClass{
public InnerClass(){
name = "abc";
age = 15;
}
}
}
在这段代码中,InnerClass就是OuterClass的一个内部类。每个内部类都能独立地继承一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影像。内部类拥有外部类的访问权
。
内部类不仅仅能够定义在类的内部,还可以定义在方法和作用域内部,这种被称为局部内部类
,除此之外,还有匿名内部类、内部类以及实现Java中的多重继承
。下面是定义内部类的方法:
- 一个在方法中定义的类(局部内部类)
- 一个定义在作用域中的类,这个作用域在方法的内部(成员内部类)
- 一个实现了接口的匿名类(匿名内部类)
- 一个匿名类,它拓展了非默认构造器的类
- 一个匿名类,执行字段初始化操作
- 一个匿名类,它通过实例初始化实现构造
由于每个类都会产生一个.class
文件,其中包含了如何创建该类型的对象的全部信息。使用$
来表示内部类的信息,比如OuterClass$InnerClass.class。
集合
Iterable接口
实现此接口允许对象称为 for-each 循环的目标,也就是增强 for 循环,也是 Java 中的一种语法糖
。
List
除了实现此接口的对象外,数组也可以用 for-each 循环遍历,如下:
Object[] list = new Object[10];
for (Object obj: list){}
其他遍历方式
jdk 1.8之前Iterator
只有 iterator 一个方法,就是
Iterator iterator();
实现此接口的方法能够创建一个轻量级的迭代器,用于安全地遍历元素,移除元素,添加元素。这里面涉及一个fail-fast
机制。
也可以使用迭代器的方法进行遍历:
for(Iterator it = coll.iterator(); it.hasNext();){
System.out.println(it.next());
}
顶级接口
Collection
是一个顶级接口,它主要用来定义集合的约定。
List
接口是一个顶级接口,它继承了Collection
接口,同时也是ArrayList
、LinkedList
等集合元素的父类。
Set
接口位于与List
接口同级的层次上,它同时也继承了Collection
接口。Set
扣扣提供了额外的规定。它对add
,equals
,hashCode
方法提供了额外的标准。
Queue
适合List
,Set
接口并列的Collection
的三大接口之一。Queue
的设计用来在处理之前保持元素的访问次序。除了Collection
基础的操作之外,队列提供了额外的插入,读取,检查操作。
SortedSet
接口直接继承于Set
接口,使用Comparable
对元素进行自然排序或者使用Comparator
在创建时对元素提供定制的排序规则。set
的迭代器将按升序元素顺序遍历集合。
Map
是一个支持key-value
存储的对象,Map
不能包含重复的key
,每一个key
最多映射一个value
。这个接口代表了Dictionary
类。Dictionary
类是一个抽象类而不是接口。
ArraryList
ArraryList
是实现了List
接口的可扩容数组(动态数组)
,它的内部是给予数组实现的。它的具体定义如下:
public class ArraryList extends AbstractList implements List,
RandomAccess, Cloneable, java.io.Serializable {...}
ArraryList
可以实现所有可选择的列表操作,允许所有的元素,包括空值。ArraryList
还提供了内部存储list
的方法,它能够完全替代Vector
,只有一点例外,ArraryList
不是线程安全的容器。ArraryList
有一个容量的概念,这个数组的容量就是List
用来存储元素的容量。ArraryList
不是线程安全的容器,如果多个线程中至少有两个线程修改了ArraryList
的结构的话就会导致线程安全问题,作为替代条件可以使用线程安全的List
,应使用Collections.synchronizedList
。List list = Collections.synchronizedList(new ArraryList(...))
ArraryList
具有fail-fast
快速失败机制,能够对ArraryList
作出失败检测。当在跌待集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast
,即抛出ConcurrentModificationException
异常。
Vector
Vector
同ArraryList
一样,都是基于数组来实现的,只不过Vector
是一个线程安全的容器,它对内部的每个方法都简单粗暴的上锁,避免多线程引发的安全性问题,但通常这种同步方式需要的开销比较大,因此,访问元素的效率远远低于ArraryList
。
扩容上,ArraryList
扩容后的数组长度会增加50%,而Vector
的扩容长度后数组会增加一倍。
LinkedList类
LinkedList
是一个双向链表,允许存储任何元素。特征如下
LinkedList
所有的操作都可以表现为双向性的,索引到链表的操作将遍历从头到尾,视哪个距离近为遍历顺序。注意这各市县不是线程安全的,如果多个线程并发访问链表,并且至少其中的一个线程修改了链表结构,那么这个链表必须进行外部枷锁。或者使用
List list = Collection.synchronizedList(new LinkedList(...))
Stack
栈堆是我们常说的后入先出
容器。它继承了Vector
类,提供了常用的push
和pop
操作,以及在栈顶的peek
方法,测试stack
是否为空的empty
方法,喝一个寻找与栈顶距离的search
方法。
第一次创建栈,不包含任何元素。一个更完善,可靠性更强的LIFO
栈操作由Deque
接口和他的实现提供,应该优先使用这个类。
Deque stack = new ArraryDeque()
HashSet
HashSet
是Set
接口的实现类,由HashMap
支持(实际上HashSet
是HashMap
的一个实例)。它不能保证集合的迭代顺序。这个类允许null
元素。
- 注意这个实现不是线程安全的,如果多线程并发访问
HashSet
,并且至少一个线程修改了set
,必须进行外部枷锁。或者使用Collections.synchronizedSet()
方法重写。 - 这个实现支持
fail-fast
机制。
TreeSet
TreeSet
是一个基于TreeMap
的NavigableSet
实现。这些元素使用它们的自然排序或者在创建时提供的Comparator
进行排序,具体取决于使用的构造函数。
- 此实现为基本操作,
add
,remove
和contains
提供了log(n)
的时间成本。 注意这个实现不是线程安全的。如果多线程并发访问
TreeSet
,并且至少一个线程改变了Set
,必须进行外部加锁。或者使用SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...))
LinkedHashMap类
LinkedHashMap
是Map
接口的Hash表和链表的实现。这个实现与HashMap
不同之处在于它维护了一个贯穿其所有条目的双向链表。这个链表定义了遍历顺序,通常是插入map
中的顺序。
- 它提供了一个特殊的
LinkedHashMap(int, float, bloolean)
构造器来创建LinkedHashMap
,其遍历顺序是其最后一次访问的顺序。 - 可以重写
removeEldestEntry(Map.Entry)
方法以便在将新映射添加到map
时强制删除过期映射的策略。 - 这个类提供了所有可选择的
map
操作,并且允许null
元素。由于维护链表的额外开销,性能可能会低于HashMap
,有一条除外:遍历LinkedHashMap
中的collection-views
需要与map.size
成正比,无论其容量如何。
Hashtable类
IdentityHashMap类
WeakHashMap类
Collection类
Collection
不属于Java
框架继承树上的内容,它属于单独的分支,Collection
是一个包装类,它的作用就是为了集合框架提供某些功能实现,此类只包括静态方法操作或者返回collections
。
同步包装
同步包装讲自动同步(线程安全性)添加到任意集合。六个核心集合接口(Collection
,Set
,List
,Map
,SortedSet
,SortedMap
)中的每一个都有一个静态工厂方法。
public static Collection synchronizedCollection(Collection c);
public static Set synchronizedSet(Set s);
public static List synchronizedList(List list);
public static Map synchronizedMap(Map m);
public static SortedSet synchronizedSortedSet(SortedSet s);
public static SortedMap synchronizedSortedMap(SortedMap m);
不可修改的包装
不可修改包装的包装器通过拦截修改集合的操作并抛出UnSupportedOperationException
,主要用在下面两个情景:
- 构建集合后使其不可改变。在这种情况下,最好不要去获取返回
collection
的引用,这样有利于保证不变性 - 允许某些客户端以只读方式访问你的数据结构。你保留对返回的
collecton
的引用,但分发对包装器的引用。通过这种方式,客户可以查看但不能修改,同时保持完全访问权限。
这些方法是:
public static Collection unmodifiableCollection(Collection extends T> c);
public static Set unmodifiableSet(Set extends T> s);
public static List unmodifiableList(List extends T> list);
public static Map unmodifiableMap(Map extends K, ? extends V> m);
public static SortedSet unmodifiableSortedSet(SortedSet extends T> s);
public static SortedMap unmodifiableSortedMap(SortedMap m);
线程安全的Collections
泛型
在JDK1.5
中,提出一个新的概念,那就是泛型:
泛型其实就是一种参数化的集合,它限制了太你家进集合的类型。泛型的本质就是一种参数化的类型。多态可以看作是泛型的机制。一个类继承了父类,那么就能通过它的父类找到对应的子类,但是不能通过其他类来找到具体要找的这个类。泛型的设计之初就是希望对象或方法具有最广泛的表达能力。
下面是一个没有泛型的栗子
List arrayList = new ArrayList();
arrayList.add("cxuan");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("test = ", item);
}
因为Integer
类型不能强行转换为String
类型所以会报错误:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
用泛型改写:
List arrayList = new ArrayList();
arrayList.add(100);
泛型的使用
泛型很多用法,用好泛型很牛
用泛型表示类
泛型可以加在类上面,来表示这个类的类型
//此处T可以随意写成任何标识
public class GenericDemo{
//value 这个成员变量的类型为T,T的类型由外部指定
private T value;
public GenericDemo(T value) {
this.value = value;
}
public T getValue(){ //泛型方法getKey的返回类型为T,T的类型由外部制定
return value;
}
public void setValue(T value){
this.value = value
}
}
用泛型标识接口
泛型接口与泛型类的定义及使用基本相同。
//定义一个泛型接口
public interface Generator{
public T next();
}
一般泛型接口常用于生成器(generator)
中,生成器相当于对象公擦汗难过,是一种专门用来创建对象的类。
泛型方法
可以使用泛型来表示方法
public class GenericMethods{
public void f(T x){
System.out.println(x.getClass().getName());
}
}
泛型通用符
List
是泛型类,为了表示各种泛型List
的父类,可以使用类型通配符,类型通配符使用问号(?)
表示,它的元素类型可以匹配任何类型。例如:
public static void main(String[] args){
List name = new ArraryList();
List age = new ArraryList();
List number = new ArraryList();
name.add("cxuan");
age.add(18);
number.add(314);
generic(name);
generic(age);
generic(number);
}
public static void generic(List> data){
System.out.println("Test cxuan :" + data.get(0));
}
上界通配符: extends ClassType>
该通配符为ClassType
的所有子类型。它表示的是任何类型都是ClassType
类型的子类。
下届通配符: super ClassType>
改通配符为ClassType
的所有超类型,它表示的是任何类型的父类都是ClassType
。
反射
反射是Java中一个非常重要同时也是一个高级特征,基本上 Spring 等一系列框架都是基于反射的思想写成的。
Java 反射机制是在程序的运行过程中,对于任何一个类,都能知道它的所有属性和方法;对于任何一个对象,都能够知道调用它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能被称为java语言的反射机制。
反射机制主要提供了一下几个功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所有的成员变量和方法
- 在运行时调用任意一个对象的方法
由此,反射就像是一个掌控全局的角色,不管你程序怎么运行,我都能够知道你这个类有哪些属性和方法,你这个对象由谁调用。
在java中,使用java.lang.reflect
包实现了反射机制。
下面是一个简单的反射类:
public class Person{
public String name;
public int age;
public Person(){
super();
}
public Person(String name, int age){
super();
this.name = name;
this.age = age;
}
public String showInfo(){
return "name=" + name + ", age=" + age;
}
}
public class Student extends Person implements Study{
public String className;
private String address;
public Student(){
super();
}
public Student(String name, int age, String className, String address){
super(name, age);
this.className = className;
this.address = address;
}
public Student(String className){
this.className = className;
}
public String toString(){
return "姓名:" + name + ",年龄" + age + ",班级" + className + ",住址:" + address;
}
public String getAddress(){
return address;
}
public void setAddress(String address){
this.address = address;
}
}
public class TestRelect {
public static void main(String[] args){
Class student = null;
try{
student = Class.forName("com.cxuan.reflection.Student");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
//获取对象的所有公有属性
Field[] fields = student.getFields();
for (Fidld f : fields){
System.out.println(f);
}
System.out.println("----------------");
//获取对象所有属性,但不包含继承的
Field[] declaredFields = student.getDeclaredFields();
for (Field df : declareFields) {
System.out.println(df);
}
//获取对象的所有公共方法
Method[] methods = student.getmethods();
for (Method m : methods){
System.out.println(m);
}
//获取对象所有方法,但不包含继承的。
Method[] declaredMethods = student.getDeclaredMethods();
for (Method dm : declaredMethods){
System.out.println(dm);
}
//获取对象所有的公共构造方法
Constructor[] constructors = student.getConstructors();
for (Contructor c : constructors){
System.out.println(c);
}
//获取对象所有的构造方法
Constructor[] declaredConstructors = student.getDeclaredConstructors();
for (Constructor dc : declaredConstructors){
System.out.println(dc);
}
Class c = Class.forName("com.cxuan.reflection.Student");
Student stu1 = (Student)c.newInstance();
//方法1:实例化默认构造方法,调用set赋值
stu1.setAddress("山东");
System.out.println(stu1);
//方法2:取得全部的构造函数,使用构造函数赋值
Constructor constructor = c.getConstructor(String.class, int.class, String.class, String.class);
Student stu2 = (Strudent) constructor.newInstance("cxuan", 24, "1班", "山东");
System.out.println(stu2);
/**
*获取方法并执行方法
*/
Method show = c.getMethod("showInfo"); //获取showInfo()方法
Object object = show.invoke(stu2); //调用showInfo()方法
}
}
Class类
Field类
Method类
ClassLoader类
枚举
枚举可能是我们使用次数比较少的特性,在Java
中,枚举使用enum
关键字来表示,枚举其实是一项非常有用的特性,可以理解为具有特定性质的类。enum
不仅仅java
有,C
和C++
也有枚举的概念。
public enum Family{
FATHER,
MOTHER,
SOM,
DAUGHTER;
}
如上创建了一个Family
的枚举类,它具有4个值,由于枚举类型都是常量,所以都用大写字母来表示。那么enum
创建出来了,如何引用?
public class EnumUse{
public static void main(String[] args){
Family s = Family.FATHER;
}
}
枚举特性
enum
类当创建完成后,编译器会自动为enum
添加toString()
方法,能够方便显示enum
实例的具体名字是什么。除了toString()
方法,编译器还会添加ordinal()
方法,这个方法用来表示enum
常量的声明顺序,以及values()
方法显示顺序的值。
public static void main(String[] args){
for(Family family : Family.values()){
System.out.println(family + ", ordinal" + fmamily.ordinal());
}
}
enum
可以进行静态导入包,静态导入包可以做到不用输入枚举类名.常量
,可以直接使用常量,使用enum
和static
关键字可以做到静态导入包。
枚举和普通类一样
枚举和普通类一样,除了枚举中能够方百年快捷的定义常量
,我们日常开发使用的public static final xxxx
其实都可以用枚举来定义。在枚举中能够定义属性和方法。
public enum OrdinalEnum{
WEST("live in west"),
EAST("live in east"),
SOUTH("live in south"),
NORTH("live in north");
String description;
OrdinalEnum(String description){
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public static void main(String[] args) {
for(OrdinalEnum ordinalEnum : OrdinalEnum.values()){
System.out.println(ordinalEnum.getDescription());
}
}
}