一、Static关键字
- 使用范围:
- 在Java类中,可用static修饰属性、方法、代码块、内部类
- 被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
1.静态变量的特点
非static的成员变量是实例变量、非静态变量
使用static修饰的成员变量就是静态变量(或类变量、类属性),存放在堆空间内。
- 静态变量的默认值规则和实例变量一样。
- 静态变量值是所有对象共享。
- 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
- 如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。
- 静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。
2. 对比静态变量和实例变量
① 个数
>静态变量:在内存空间中只有一份,被类的多个对象所共享。
>实例变量:类的每一个实例(或对象)都保存着一份实例变量。
②内存位置
>静态变量:jdk6及之前,存放在方法区(永久代)。jdk7及之后,存放在堆空间。方法区叫元空间。
>实例变量:存放在堆空间的对象实体中。
③加载时机
>静态变量:随着类的加载而加载,由于类只会加载一次,所以静态变量也只有一份。
>实例变量:随着对象的创建而加载。
④调用者
>静态变量:可以被类直接调用,也可以使用对象调用。
>实例变量:只能使用对象进行调用。
⑤判断是否可以调用 --->从生命周期的角度解释
类变量 实例变量
类 yes no
对象 yes yes
类变量是和类一起加载的,也就是说类和类变量是先构造完成的,对象和实例变量是后构造的,如果实例对象还没创建,类就不能调用实例变量。
⑥消亡时机
>静态变量:随着类的卸载而消亡。
>实例变量:随着对象的消亡而消亡
3.静态变量的内存解析
4.静态方法的特点
用static修饰的成员方法就是静态方法(类方法)。
- 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
- 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
- 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。也不能使用this和super。
- 静态方法可以被子类继承,但不能被子类重写。不存在多态性。可以重载。
- 静态方法的调用都只看编译时类型。
- 因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。
-
类方法 实例方法
类 yes no
对象 yes yes
补充:在类的非静态方法中,可以调用当前类中的静态结构(属性,方法)或非静态结构(属性,方法)。即:静态只能调用静态,非静态都能调
5.开发中,什么时候需要将属性声明为静态的?
>判断当前类的多个实例是否能共享此成员,且此成员变量的值是相同的。
>开发中,常将一些常量声明是静态的。比如:Math类中的PI.
6.开发中,什么时候需要将方法声明为静态的?
>方法内操作的变量如果都是静态变量(而非实例变量)的话,则此方法建议声明为静态方法。
>开发中,常常将工具类中的方法,声明为静态方法。比如:Arrays类、Math类。
是否可以从一个static方法内部发出对非static方法的调用?
只能通过对象来对非静态方法的调用。如main方法中调用非static方法需要创建对象。
被static修饰的成员(类、方法、成员变量)能否再使用private进行修饰?
完全可以。除了代码块,代码块本身不能用权限修饰符修饰。
二、单例(Singleton)设计模式
设计模式是在大量的实践中总结
和理论化
之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。"套路"
经典的设计模式共有23种。每个设计模式均是特定环境下特定问题的处理方法。
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
★ 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
。
①单例模式的两种实现方式
饿汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
懒汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
饿汉式 vs 懒汉式
饿汉式:
-
特点:
立即加载
,即在使用类的时候已经将对象创建完毕。 -
优点:实现起来
简单
;没有多线程安全问题。 -
缺点:当类被加载的时候,会初始化static的实例,静态对象被创建并分配内存空间,从这以后,这个static的实例便一直占着这块内存,直到类被卸载时,静态变量被摧毁,并释放所占有的内存。因此在某些特定条件下会
耗费内存
。
懒汉式:
-
特点:
延迟加载
,即在调用静态方法时实例才被创建。 -
优点:实现起来比较简单;当类被加载的时候,static的实例未被创建并分配内存空间,当静态方法第一次被调用时,初始化实例变量,并分配内存,因此在某些特定条件下会
节约内存
。 -
缺点:在多线程环境中,这种实现方法是完全错误的,
线程不安全
,根本不能保证单例的唯一性。
三、代码块
作用:用来初始化类或对象的信息(即初始化类或对象的成员变量)
静态代码块
-
可以有输出语句。
-
可以对类的属性、类的声明进行初始化操作。
-
不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
-
若有多个静态的代码块,那么按照从上到下的顺序依次执行。
-
静态代码块的执行要先于非静态代码块。
-
静态代码块随着类的加载而加载,且只执行一次。
非静态代码块
-
可以有输出语句。
-
可以对类的属性、类的声明进行初始化操作。
-
除了调用非静态的结构外,还可以调用静态的变量或方法。
-
若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
-
每次创建对象的时候,都会执行一次。且先于构造器执行。
实例变量赋值顺序
① 默认初始化
② 显式初始化 或 ⑤代码块中初始化
③ 构造器中初始化
④ 通过"对象.属性"或"对象.方法"的方式,给属性赋值
顺序:① - ②/⑤ - ③ - ④
静态代码块,普通代码块,构造方法,从类加载开始的执行顺序?
静态代码块 --> 普通代码块 --> 构造器
四、final关键字
final修饰类
表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性。
final修饰方法
表示这个方法不能被子类重写。
final修饰变量
final修饰某个变量(成员变量或局部变量),一旦赋值,它的值就不能被修改,即常量,常量名建议使用大写字母。
可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值
排错
public class Something {
public int addOne(final int x) {
return ++x; //错误
// return x + 1; //正确,x的值没变
}
}
排错
public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
// o = new Other(); //错误,o被final修饰
o.i++; //正确
}
}
class Other {
public int i;
}
final不能用于修饰构造方法
没有意义
五、抽象类与抽象方法---abstract关键字
Java语法规定,包含抽象方法的类必须是抽象类。
抽象类:被abstract修饰的类。
抽象方法:被abstract修饰没有方法体的方法。其功能是确定的(通过方法的声明即可确定),只是不知道如何具体实现(体现为没有方法体),如下方代码findArea的功能就是求面积,只是没有具体实现细节。
注意:抽象方法没有方法体
说明:
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- 抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
- 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
-
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
- 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
注意
- 不能用abstract修饰变量、代码块、构造器;
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类。
>私有方法不能被重写,而abstarct修饰的方法必须要被重写
>避免静态方法使用类进行调用,因为abstarct修饰的方法不能被调用。
>final的方法不能被重写,抽象方法一定要重写
>final的类不能有子类,而抽象类必须有子类来造对象。
六、接口
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。
接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大家都要遵守。
接口使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,枚举,接口,注解,记录。
1.接口内部结构的说明:
>可以声明:
属性:必须使用public static final修饰
方法:jdk8之前,声明抽象方法,修饰为public abstract
jdk8:声明静态方法、默认方法
jdk9:声明私有方法
>不可以声明:构造器、代码块等
2. 接口的使用规则
class A extends SuperA implements B,C{}
A相较于SuperA来讲,叫做子类。
A相较B,C来讲,叫做实现类。
①接口不能创建对象,但是可以被类实现(implements ,类似于被继承)。
②类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类。
③对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
格式:【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。
④接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
⑤一个接口能继承另一个或者多个接口,接口的继承也使用 extends 关键字,子接口继承父接口的方法。
⑥接口中声明的静态方法只能被接口来调用,不能使用其实现类进行调用。
⑦接口中声明的默认方法可以被实现类继承,实现类在没有重写此方法的情况下,默认调用接口中声明的默认方法。如果实现类重写了此方法,则调用的是自己重写的方法。
⑧类实现了两个接口,而两个接口中定义了同名同参数的默认方法,则实现类在没有重写此两个接口默认方法的情况下,会报错 ------>接口冲突
要求:此时实现类必须要重写接口中定义的同名同参数的方法。
⑨子类(或实现类)继承了父类并实现了接口,父类和接口中声明了同名同参数的方法。(其中,接口中的方法是默认方法default)。默认情况下,子类(或实现类)在没有重写此方法的情况下调用的是父类中的方法。 ------>类优先原则
3.接口与实现类对象构成多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
接口名 变量名 = new 实现类的对象
4.接口与抽象类之间的对比
访问接口的默认方法如何使用
使用实现类的对象进行调用。而且实现还可以重写此默认方法。
七、内部类(难)
>成员内部类:直接声明在外部类的里面。
>使用static修饰的,静态的成员内部类
>不使用static修饰的,非静态的成员内部类
>局部内部类:声明在方法内、构造器内、代码块内的内部类
>匿名的局部内部类
>非匿名的局部内部类
八、枚举类
枚举类型本质上也是一种类,只不过是这个类的对象是有限的、固定的几个,不能让用户随意创建。
1.enum方式定义的要求和特点
- 枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
- 列出的实例系统会自动添加 public static final 修饰。
- 如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
- 编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数
- 如果枚举类需要的是有参构造,需要手动定义,有参构造的private可以省略,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
- 枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
- JDK5.0 之后switch,提供支持枚举类型,case后面可以写枚举常量名,无需添加枚举类作为限定。
举例:
public enum SeasonEnum {
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
2.enum中常用方法
String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
static 枚举类型[ ] values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值,是一个静态方法
static 枚举类型 valueOf(String name):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
String name():得到当前枚举常量的名称。建议优先使用toString()。
int ordinal():返回当前枚举常量的次序号,默认从0开始
3.实现接口的枚举类
- 和普通 Java 类一样,枚举类可以实现一个或多个接口
- 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
- 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法
举例:
interface Info{
void show();
}
//使用enum关键字定义枚举类
enum Season1 implements Info{
//1. 创建枚举类中的对象,声明在enum枚举类的首位
SPRING("春天","春暖花开"){
public void show(){
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
public void show(){
System.out.println("宁静的夏天");
}
},
AUTUMN("秋天","秋高气爽"){
public void show(){
System.out.println("秋天是用来分手的季节");
}
},
WINTER("冬天","白雪皑皑"){
public void show(){
System.out.println("2002年的第一场雪");
}
};
//2. 声明每个对象拥有的属性:private final修饰
private final String SEASON_NAME;
private final String SEASON_DESC;
//3. 私有化类的构造器
private Season1(String seasonName,String seasonDesc){
this.SEASON_NAME = seasonName;
this.SEASON_DESC = seasonDesc;
}
public String getSEASON_NAME() {
return SEASON_NAME;
}
public String getSEASON_DESC() {
return SEASON_DESC;
}
}
九、注解(Annotation)
注解(Annotation)是从JDK5.0开始引入,以“@注解名”在代码中存在。例如:
@Override
@Deprecated
@SuppressWarnings(value=”unchecked”)
Annotation 可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。还可以添加一些参数值,这些信息被保存在 Annotation 的 “name=value” 对中。
注解可以在类编译、运行时进行加载,体现不同的功能。
1.注解与注释
对于单行注释和多行注释是给程序员看的。
而注解是可以被编译器或其他程序读取的。程序还可以根据注解的不同,做出相应的处理。
2.三个最基本的注解
@Override
- 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误!
- 只能标记在方法上。
- 它会被编译器程序读取。
@Deprecated
- 用于表示被标记的数据已经过时,不推荐使用。
- 可以用于修饰 属性、方法、构造、类、包、局部变量、参数。
- 它会被编译器程序读取。
@SuppressWarnings
- 抑制编译警告。当我们不希望看到警告信息的时候,可以使用 SuppressWarnings 注解来抑制警告信息
- 可以用于修饰类、属性、方法、构造、局部变量、参数
- 它会被编译器程序读取。
- 可以指定的警告类型有(了解)
- all,抑制所有警告
- unchecked,抑制与未检查的作业相关的警告
- unused,抑制与未用的程式码及停用的程式码相关的警告
- deprecation,抑制与淘汰的相关警告
- nls,抑制与非 nls 字串文字相关的警告
- null,抑制与空值分析相关的警告
- rawtypes,抑制与使用 raw 类型相关的警告
- static-access,抑制与静态存取不正确相关的警告
- static-method,抑制与可能宣告为 static 的方法相关的警告
- super,抑制与置换方法相关但不含 super 呼叫的警告
- ...
3.元注解
对现有的注解进行解释说明。
-
@Target:表明可以用来修饰的结构
-
@Retation:表明生命周期
十、包装类
为了使得基本数据类型的变量具备引用数据类型变量的相关特征(比如:封装性,继承性,多态性),我们给各个基本数据类型的变量都提供了对应的包装类。
Java针对八种基本数据类型定义了相应的引用类型:包装类(封装类)。有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
1.封装以后的,内存结构对比:
public static void main(String[] args){
int num = 520;
Integer obj = new Integer(520);
}
2.包装类与基本数据类型间的转换
装箱:把基本数据类型转为包装类对象
调用valueof(xxx)方法
Integer obj1 = new Integer(4);//使用构造函数函数
Float f = new Float(“4.56”);
Long l = new Long(“asdf”); //NumberFormatException
Integer obj2 = Integer.valueOf(4);//使用包装类中的valueOf方法
拆箱:把包装类对象拆为基本数据类型
调用xxxValue()方法
Integer obj = new Integer(4);
int num1 = obj.intValue();
自动装箱与拆箱:
由于我们经常要做基本类型与包装类之间的转换,从JDK5.0开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。
注意:只能与自己对应的类型之间才能实现自动装箱与拆箱。
Integer i = 1;
Double d = 1;//错误的,1是int类型
3.基本数据类型、包装类与字符串间的转换
(1)基本数据类型转为字符串
方式1:调用字符串重载的valueOf()方法
int a = 10;
//String str = a;//错误的
String str = String.valueOf(a);
方式2:更直接的方式
int a = 10;
String str = a + "";
(2)字符串转为基本数据类型
调用包装类的静态方法:parseXxx()
String s1 = "123";
int i1 =Integer.parseInt(s1);
sout.(i1 + 10);
面试题:
public void method1() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //false
//底层都会调用Integer的valueOf方法
Integer m = 1;
Integer n = 1;
System.out.println(m == n); //true
-128~127
Integer x = 128;
Integer y = 128;
System.out.println(x == y); //false
}
包装类 | 缓存对象 |
Byte | -128~127 |
Short | -128~127 |
Integer | -128~127 |
Long | -128~127 |
Float | 没有 |
Double | 没有 |
Character | 0~127 |
Boolean | true和false |