类变量和类方法
类变量
也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
JDK8
之前,静态变量存储在常量池中;JDK8
以后,保存在class
实例的尾部,而class
对象存在于堆中。
public class ChildGame {
public static void main(String [] args) {
int count = 0;
Child child1 = new Child("白骨精");
child1.join();
child1.count++;
Child child2 = new Child("狐狸精");
child2.join();
child2.count++;
Child child3 = new Child("老鼠精");
child3.join();
child3.count++;
//类变量可以通过类名来访问
System.out.println("共有" + Child.count + "个小孩加入了游戏!");
//白骨精加入了游戏!
//狐狸精加入了游戏!
//老鼠精加入了游戏!
//共有3个小孩加入了游戏!
System.out.println("==========================================");
System.out.println("child1.count=" + child1.count);
System.out.println("child2.count=" + child2.count);
System.out.println("child3.count=" + child3.count);
//child1.count=3
//child2.count=3
//child3.count=3
}
}
class Child {
private String name;
//定义一个变量count,是一个类变量(静态变量)static
//该变量最大的特点是会被child类的所有对象实例共享。
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + "加入了游戏!");
}
}
- 语法:访问修饰符
static
数据类型 变量名 - 如何访问?
- 对象名.类变量名【静态变量的访问修饰符的访问权限和范围和普通属性是一样的】
- 类名.类变量名(推荐使用)
public class VisitStatic {
public static void main(String [] args) {
//类名.类变量名
//说明:类变量是随着类的加载而创建的,所以即使没有创建对象实例也可以访问。
System.out.println(A.name);
//对象名.类变量名
A a = new A();
System.out.println(a.name);
}
}
class A {
//类变量
//类变量的访问必须遵守相关的访问权限,这里是public
public static String name = "郭泳妍";
}
============================================================================
public class VisitStatic {
public static void main(String [] args) {
//类名.类变量名
//说明:类变量是随着类的加载而创建的,所以即使没有创建对象实例也可以访问。
System.out.println(A.getName());
//对象名.类变量名
A a = new A();
System.out.println(a.getName());
}
}
class A {
//类变量
//类变量的访问必须遵守相关的访问权限,这里是public
private static String name = "郭泳妍";
public static String getName() {
return name;
}
public static void setName(String name) {
A.name = name;
}
}
- 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量。
- 类变量与实例变量的区别:类变量是该类的所有对象共享的,实例变量是每个对象独享的。
- 加上
static
称为类变量或者静态变量,否则称为实例变量/普通变量/非静态变量。 - 类变量在加载时就已经被初始化,意思是即使没有创建对象,只要类加载了就可以使用类变量。
- 类变量的生命周期是随着类的加载开始的,随着类的消亡而销毁。
类方法/静态方法
- 语法:访问修饰符
static
数据返回类型 方法名() { } - 如何调用?
- 类名.类方法.
- 对象名.类方法名【前提是满足访问修饰符的访问权限和范围】
public class StaicMethod {
public static void main(String [] args) {
//对象名.类方法名
Stu tom = new Stu("tom");
//tom.pay(1000);
Stu.pay(1000); //两种方法均可以
Stu mary = new Stu("mary");
mary.pay(2000);
//类名.类方法
//输出当前收到的总学费
Stu.show(); //总学费有3000.0
}
}
class Stu {
private String name;
//定义一个静态变量来累计学生学费
private static double fee = 0;
public Stu() {
super();
}
public Stu(String name) {
super();
this.name = name;
}
//当方法使用了static修饰后,该方法为静态方法
public static void pay(double fee) {
Stu.fee += fee; //累计到静态变量的fee
}
public static void show() {
System.out.println("总学费有" + Stu.fee);
}
}
- 经典使用场景:当方法中不涉及到任何和对象有关的成员,即不创建实例也可以调用某个方法(也就是说当成工具使用),则可以将方法设计成静态方法提升开发效率。
System.out.println("9开方的结果是:" + Math.sqrt(9));
- 开发自己的工具时,可以将方法做成静态的,方便进行调用。
- 类方法和普通方法都是随着类的加载而加载的,将结构信息存储在方法区中。
- 类方法中无
this
这个参数,而普通方法中隐含着this
这个参数。 - 类方法可以通过类名和对象名来调用。
- 普通方法和对象有关,需要通过对象名来调用,比如对象名.方法名(参数),不能通过类名调用。
- 类方法中不允许使用和对象有关的关键字,如
this、super
,而普通方法可以。 - 类方法中只能访问静态变量或者静态方法。
- 普通成员既可以访问普通变量,也可以访问静态变量。
- 静态方法只能访问静态的成员;非静态的方法,可以访问静态成员和非静态成员。
class D {
private int n1 = 100;
private static int n2 = 200;
public void say() {
}
public static void hi() {
//类方法中不允许使用和对象有关的关键字,如`this、super
//System.out.println(this.n1);
}
public static void hello() {
//System.out.println(D.n1); ---->报错
System.out.println(n2);
System.out.println(D.n2);
//System.out.println(this.n2); ---->报错
hi();
//say(); ---->报错
}
//静态方法只能访问静态的成员;非静态的方法,可以访问静态成员和非静态成员。
public void OK() {
//非静态成员
System.out.println(n1);
say();
//静态成员
System.out.println(n2);
hi();
hello();
}
}
main方法
- 语法:
public static void main(String [] args)
Java
虚拟机需要调用类的main
方法,所以该方法的访问权限必须要是public
,否则运行不了。java
虚拟机在执行main
方法时不必创建对象,作为程序入口,所以该方法必须是static
。- 该方法接收
Sting
类型的数组参数,该数组中保存执行java
命令时传递给所运行的类的参数。
- 在
main()
方法中,可以直接调用main
方法所在类的静态方法和静态属性。 - 但是,不能直接访问该类中的非静态成员,必须创建一个实例对象后才可以访问。
public class Main01 {
//静态变量/属性
private static String name = "郭泳妍";
//静态方法
public static void hi() {
System.out.println("hi!");
}
//非静态属性
private int n1 = 100;
//非静态方法
public void cry() {
System.out.println("喵喵喵……");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//可以直接使用name,静态方法可以访问本类的静态成员
System.out.println(name); //郭泳妍
hi();
//静态方法不可以访问本类的非静态成员和非静态方法
//System.out.println(n1); ----->报错
//cry(); ----->报错
//要访问需要先创建实例对象再调用
Main01 main01 = new Main01();
main01.hi();
main01.cry();
}
}
main动态传值
不同的编译器和不同的版本在网上查询教程。
代码块/初始化块
语法+概念
- 属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过
{ }
包围起来。 - 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是记载类时,或者创建对象时隐式调用。
- 基本语法:
[修饰符] {
代码
};
注意:
- 修饰符可选,要写的话只能写
static
。 - 代码块分两类,使用
static
修饰的叫静态代码块,没有static
修饰的叫做普通代码块/非静态代码块。 - 逻辑语句可以分为任意逻辑语句。
;
可以写上,也可以省略。
代码块的好处:
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作。
- 应用场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码重用性。
- 三个构造器中都要有相同的输出语句,这时可以把相同的语句放入一个代码块中即可。
- 这样当我们不管调用哪个构造器,创建对象都会先调用代码块内容。
- 代码块调用的顺序优先于构造器。
public class CodeBlock01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Movie movie1 = new Movie("你好李焕英");
Movie movie2 = new Movie("你好李焕英","贾玲");
}
}
class Movie {
private String name;
private String director;
private double price;
//3个构造器 ------>重载
//三个构造器中都要有相同的输出语句,这时可以把相同的语句放入一个代码块中即可。
//这样当我们不管调用哪个构造器,创建对象都会先调用代码块内容。
//代码块调用的顺序优先于构造器。
{
System.out.println("电影屏幕打开");
System.out.println("广告开始");
System.out.println("电影正式开始");
}
public Movie(String name) {
super();
// System.out.println("电影屏幕打开");
// System.out.println("广告开始");
// System.out.println("电影正式开始");
System.out.println("public Movie(String name)被调用");
this.name = name;
}
public Movie(String name, String director) {
super();
this.name = name;
this.director = director;
System.out.println("public Movie(String name, String director)被调用");
}
public Movie(String name, String director, double price) {
super();
this.name = name;
this.director = director;
this.price = price;
}
}
============================================================================
电影屏幕打开
广告开始
电影正式开始
public Movie(String name)被调用
电影屏幕打开
广告开始
电影正式开始
public Movie(String name, String director)被调用
代码块细节
static
代码块也叫静态代码块,作用就是对类进行初始化,而且他随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象就执行一次。
类什么时候被加载?*
- 创建对象时实例化(new)。
- 创建子类对象实例,父类也会被加载。
- 使用类的静态成员时(静态属性,静态方法)
普通的代码块,在创建对象实例时被隐式调用,对象被创建一次,就会被调用一次;而如果只是使用类的静态成员时,普通代码块并不会执行。
public class CodeBlock02 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建对象实例时(new)
AA a1 = new AA(); //AA的静态代码块被执行!
//创建子类对象实例,父类也会被加载。
//先加载父类,再加载子类
AA a2 = new AA(); //BB的静态代码块被执行! AA的静态代码块被执行!
//使用类的静态成员时(静态属性,静态方法)
//调用静态成员时,只有static代码块会被执行,普通代码块不会被执行
System.out.println(Cat.n1); //Cat的第一次静态代码块被执行! Cat的第三次静态代码块被执行! 100
//即使创建对象,static静态代码块只会被执行一次
DD dd1 = new DD();
DD dd2 = new DD(); //DD的静态代码块被执行!
}
}
class DD {
static {
System.out.println("DD的静态代码块被执行!");
}
}
class Cat {
public static int n1 = 100;
static {
System.out.println("Cat的第一次静态代码块被执行!");
}
{
System.out.println("Cat的第二次静态代码块被执行!");
}
static {
System.out.println("Cat的第三次静态代码块被执行!");
}
}
class BB {
static {
System.out.println("BB的静态代码块被执行!");
}
}
class AA extends BB{
//静态代码块
static {
System.out.println("AA的静态代码块被执行!");
}
}
创建一个对象时,在一个类调用顺序(重点、难点)
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化块调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们的定义顺序调用)。
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和普通属性初始化则按他们的定义顺序调用)
- 调用构造方法
public class CodeBlock03 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//1. 静态代码块和静态属性初始化块调用的优先级一样,
//如果有多个静态代码块和多个静态变量初始化,则按他们的定义顺序调用
//2. 普通代码块和普通属性初始化调用的优先级一样,
//如果有多个普通代码块和普通属性初始化则按他们的定义顺序调用
A a = new A(); //1.getN1()被调用…… 2. A 静态代码块被调用……
//3.getN2()被调用…… 4.A 普通代码块被调用……
//5.A构造器被调用…
}
}
class A {
public A() {
System.out.println("A构造器被调用……");
}
//普通属性初始化
private int n2 = getN2();
//普通代码块
{
System.out.println("A 普通代码块被调用……");
}
//静态属性初始化
private static int n1 = getN1();
//静态代码块
static {
System.out.println("A 静态代码块被调用……");
}
public static int getN1() {
System.out.println("getN1()被调用……");
return 100;
}
public int getN2() {
System.out.println("getN2()被调用……");
return 200;
}
}
============================================================================
getN1()被调用……
A 静态代码块被调用……
getN2()被调用……
A 普通代码块被调用……
A构造器被调用……
构造器的最前面其实隐含了super()
和调用普通代码块,静态相关的代码块、属性初始化,在类加载时就执行完毕,因此是优先于构造器和普通代码块执行的。
public class CodeBlock04 {
public static void main(String[] args) {
// TODO Auto-generated method stub
AAA a = new B(); //AAA普通代码块被调用 AAAA()构造器被调用、
//BBB普通代码块被调用、 B()构造器被调用
}
}
class AAA {
public AAA() {
//隐藏的执行要求:
//1.super(); ---->继承讲解
//2. 调用普通代码块
System.out.println("AAA()构造器被调用");
}
{
System.out.println("AAA普通代码块被调用");
}
}
class B extends AAA{
public B () {
System.out.println("B()构造器被调用");
}
{
System.out.println("BBB普通代码块被调用");
}
}
===================================================================================
AAA普通代码块被调用
AAA()构造器被调用
BBB普通代码块被调用
B()构造器被调用
创建一个子类时(继承关系),他们的静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造方法的调用顺序(难点、面试题)
- 父类的静态代码块和静态属性(优先级相同,按定义顺序调用)。
- 子类的静态代码块和静态属性(优先级相同,按定义顺序调用)。
- 父类的普通代码块和普通属性初始化(优先级相同,按定义顺序调用)。
- 父类的构造方法。
- 子类的普通代码块和普通属性初始化(优先级相同,按定义顺序调用)。
- 子类的构造方法。
练习
静态代码块可以只能调用静态成员,普通代码块可以调用任意成员。
单例设计模式
设计模式的概念
- 静态方法和属性的经典使用。
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。
单例模式
就是采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式饿汉类
类被加载后即全部对象都创建【创建后未被利用,可能造成资源浪费】
- 构造器私有化(防止直接
new
对象) - 类的内部创建对象。
- 向外暴露一个静态的公共方法。
- 代码实现。
/*
* 构造器私有化(防止直接`new`对象)
* 类的内部创建对象。
* 向外暴露一个静态的公共方法。
* 代码实现。
*/
//单例模式保证一个类只能创建一个对象
public class TestSingleton {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); //true
}
}
class Singleton {
//本类类型的静态私有变量
//为了能够在静态方法中将私有的inatance返回,需将其修饰为static
private static Singleton instance;
//构造器私有化
private Singleton() {
System.out.println("Singleton()无参构造器被调用……");
}
//静态的公共方法
public static Singleton getInstance() {
if(instance == null) {
//类的内部创建对象。
instance = new Singleton();
}
return instance;
}
}
===================================================================================
Singleton()无参构造器被调用……
true
单例模式懒汉类
即类中的对象被使用后才会被创建
//懒汉式
public class TestSingleton02 {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(Cat.n1); // 100【构造器未被调用】
Cat instance1 = Cat.getInstance();
System.out.println(instance1); //构造器被调用 Cat [name=小可爱]
Cat instance2 = Cat.getInstance();
System.out.println(instance2); //Cat [name=小可爱],cat已经不是空
System.out.println(instance1 == instance2); //true
}
}
//希望程序在运行过程中,只能创建一个cat对象
//使用单例模式
class Cat {
private String name;
public static int n1 = 100;
private static Cat cat; //默认是null
private Cat(String name) {
System.out.println("构造器被调用");
this.name = name;
}
//懒汉式中,只有当用户使用getInstance() 时才会返回cat对象,
//后面再次调用时返回上次创建的cat对象
//从而保证了单例
public static Cat getInstance() {
if(cat == null) {
cat = new Cat("小可爱");
}
return cat;
}
@Override
public String toString() {
return "Cat [name=" + name + "]";
}
}
===================================================================================
100
构造器被调用
Cat [name=小可爱]
Cat [name=小可爱]
true
饿汉式与懒汉式区别
- 最主要的区别在于创建对象的时机不同:饿汉式是类加载时就创建了对象实例,而懒汉式是在使用时才创建。
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
- 饿汉式存在浪费资源的可能。
final关键字
final可以修饰类、属性、方法、局部变量。
应用场景
- 当不希望类被继承时,可以用
final
修饰。 - 但不希望父类的某个方法被子类覆盖或者重写时,可以用
final
关键字修饰。
public final void hi(){}
- 当不希望类的某个属性的值被修改但是仍然可以调用。
public final double TAX = 0.09;
- 当不希望某个局部变量被修改。
public class Final01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
//当不希望A类被继承时,可以用final修饰A类。
final class A {}
//class B extends A {}
class C {
// 但不希望hi方法被子类覆盖或者重写时,可以用final关键字修饰hi方法。
public void hi() {};
//public final void hi(){}
}
class D extends C {
@Override
public void hi() {
// TODO Auto-generated method stub
super.hi();
System.out.println("重写了C类的hi方法");
}
}
使用细节
final
修饰的属性又叫常量,一般字母全部是大写,且字母用下划线隔开。如TAX_NUM
final
修饰的属性在定义时必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:
- 定义时:
public final double TAX_RATE = 0.08;
- 在构造器中。
- 在代码块中。
class AA {
public final double TAX_RATE1 = 0.09; //定义时
public final double TAX_RATE2;
public final double TAX_RATE3;
public AA () {
TAX_RATE2 = 0.02; //在构造器中
}
{
TAX_RATE3 = 0.01; //在代码块中
}
}
- 如果
final
修饰的属性是静态的,则初始化的位置只能是:
- 定义时
- 在静态代码块中,不能再构造器中赋值
class BB {
public static final double TAX_RATE1 = 0.09; //定义时
public static final double TAX_RATE2;
public final double TAX_RATE3;
// public static final double TAX_RATE3;
public BB () {
TAX_RATE3 = 0.01; //不能在构造器中
}
static {
TAX_RATE2 = 0.02; //在静态代码块中
}
}
final
类不能继承,但是可以实例化对象。
CC cc = new CC()
- 如果类不是
final
类,但是含有final
方法,则该方法虽然不能重写,但是可以被继承,可以在主类中创建对象时调用。 - 一般来说,如果一个已经是
final
类,就没有必要将方法修饰成final
方法。 - final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理。
public class Final02 {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(DD.num1); //100
System.out.println(DD.num2); //DD静态代码块被执行 200
}
}
class DD {
public final static int num1 = 100;
public static int num2 = 200;
static {
System.out.println("DD静态代码块被执行");
}
}
- 包装类(
Interger、Double、Float、Boolean
等都是final
),String
类也是final
类,这些都布不能被继承。
抽象类
概念
- 当父类的某个方法需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。
- 当一个类中存在抽象方法时,需要将该类声明为
abstract
类。 - 一般来说抽象类会被继承,由其子类实现抽象方法。
介绍
- 用
abstract
修饰的类就是抽象类。
访问修饰符 abstract 类名 {
}
- 用
abstract
修饰的方法就是抽象方法。
访问修饰符
abstract
返回类型 方法名 (参数列表); //没有方法体
- 抽象类的价值更多在于设计,是设计师设计好后,让子类继承并实现抽象类。
- 抽象类在面试中常见。
使用细节
- 抽象类不能实例化。
- 抽象类不一定包含抽象方法,还可以有实现方法。
- 一旦类包含了
abstract
方法,则这个类必须声明为abstract
。 abstract
只能修饰类和方法,不能修饰属性和其他的。- 抽象类还是类,可以有任意成员,比如:非抽象方法、构造器、静态属性等等。
- 抽象方法不能有主体,即不能实现。【没有方法体】
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非他自己也声明为
abstract
类。【所谓实现方法,就是要有方法体】。 - 抽象方法不能使用
private、final、static
修饰,因为这些关键字都是和重写相违背的。
abstract class A {
public abstract void hi();
}
//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非他自己也声明为abstract类。
abstract class B extends A {
}
class C extends A {
@Override
public void hi() {
// TODO Auto-generated method stub
System.out.println("你好!");
}
}
抽象——模板设计模式
常见需求:
- 有多个类,完成不同的任务job
- 要求统计得到各自完成任务的时间。
public class T2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
AA a = new AA();
a.job();
}
}
class AA {
//计算任务:1到100的和
public void job() {
//得到开始时间
long start = System.currentTimeMillis();
long sum = 0;
for(long i = 1; i<=900000;i++) {
sum += i;
}
//得到结束时间
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start));
}
}
class BB {
public void job2() {
//得到开始时间
long start = System.currentTimeMillis();
long sum = 0;
for(long i = 1; i<=700000;i++) {
sum += i;
}
//得到结束时间
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start));
}
}
===================================================================================
public class T3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
AAA a = new AAA();
a.calculateTime();
BBB b = new BBB();
b.calculateTime();
}
}
abstract class Template {
public abstract void job(); //抽象方法
public void calculateTime() {
long start = System.currentTimeMillis();
job(); //动态机制绑定
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start));
}
}
class AAA extends Template {
@Override
public void job() { //实现抽象方法
long sum = 0;
for(long i = 1; i<=900000;i++) {
sum += i;
}
}
}
class BBB extends Template {
@Override
public void job() { //实现抽象方法
long sum = 0;
for(long i = 1; i<=800000;i++) {
sum += i;
}
}
}
==================================
运行时间:3
运行时间:2
接口
USB
插槽就是现实中的接口。
快速入门
public class Interface01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Camera camera = new Camera();
Phone phone = new Phone();
Computer1 computer1 = new Computer1();
computer1.work(phone); //把手机接入计算机
computer1.work(camera);
}
}
interface UsbInterface {
//规定接口的相关方法
public void start();
public void stop();
}
//phone类实现UsbInterface
class Phone implements UsbInterface {
@Override
public void start() {
// TODO Auto-generated method stub
System.out.println("手机开始工作");
}
@Override
public void stop() {
// TODO Auto-generated method stub
System.out.println("手机停止工作");
}
}
//camera类实现UsbInterface
class Camera implements UsbInterface {
@Override
public void start() {
// TODO Auto-generated method stub
System.out.println("相机开始工作");
}
@Override
public void stop() {
// TODO Auto-generated method stub
System.out.println("相机停止工作");
}
}
class Computer1 {
public void work(UsbInterface usbInterface) {
//通过接口调用方法
usbInterface.start();
usbInterface.stop();
}
}
==================================
手机开始工作
手机停止工作
相机开始工作
相机停止工作
基本介绍
- 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
- 语法:
interface 接口名 {
//属性
//方法
}
class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法
}
JDK7
之前接口的所有方法都没有方法体,即都是抽象方法。JDK8
之后接口类可以有静态方法和默认方法,也就是说接口中可以有方法的具体实现。
interface AInterface {
//属性
public int n1 = 10;
//方法
public abstract void hello(); //抽象方法
//接口中抽象方法可以省略abstract
public void hello1();
//JDK8之后接口类可以有静态方法和默认方法,也就是说接口中可以有方法的具体实现。
default public void OK() { //默认实现静态方法
System.out.println("OK!");
}
public static void cry { //静态方法
System.out.println("喵喵喵……");
}
}
class D implements AInterface {
@Override
public void hello() {
// TODO Auto-generated method stub
}
@Override
public void hello1() {
// TODO Auto-generated method stub
}
}
应用场景
- 现在要制作战斗机,专家只需要把飞机需要的功能、规格定下来即可让别的人具体实现。
使用细节
- 接口不能被实例化。
- 接口中所有的方法是
public
方法,接口中的抽象方法可以不用abstract
修饰。
void aaa(); = abstract void aaa();
- 一个普通类实现接口,就必须将接口中的所有方法都实现。
- 抽象类实现接口,可以不用实现接口的方法。
- 一个类同时可以实现多个接口。【单一继承】
class Pig implements IB,IC {
}
- 接口中的属性只能是
final
,而且是public static final
修饰符。
int a =1 等价于
public static final int a = 1
- 接口中属性的访问形式:接口名.属性名。
- 一个接口不能继承其他的类,但是可以继承多个别的接口。
interface A
extends
B,C { //A、B、C均为接口
}
- 接口的修饰符只能是
public
和默认,这点和类相同。
接口VS继承
- 继承:当子类继承父类,就自动拥有了父类的功能
- 接口——补充机制:如果子类需要扩展功能,可以通过实现接口的方式扩展,可以理解为接口实际上是对
java
单继承机制的补充。 - 接口的价值在于设计规范好的方法,让其他类去实现;而继承的价值在于解决代码的复用性和可维护性。
- 接口比继承更加灵活,继承满足
is-a
的关系,接口满足like-a
的关系。 - 接口在一定程度上实现了代码解耦(即接口规范性+动态绑定)
高内聚低耦合
public class ExtendVSInterface {
public static void main(String[] args) {
// TODO Auto-generated method stub
LittleMonkey wukong = new LittleMonkey("悟空");
wukong.climbing();
wukong.swimming();
}
}
class Monkey {
private String name;
public void climbing() {
System.out.println( name + "猴子会爬树……");
}
public Monkey(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//接口——补充机制:如果子类需要扩展功能,可以通过实现接口的方式扩展
//可以理解为接口实际上是对java单继承机制的补充
interface Fish {
void swimming();
}
//继承:当子类继承父类,就自动拥有了父类的功能
class LittleMonkey extends Monkey implements Fish{
public LittleMonkey(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void swimming() {
// TODO Auto-generated method stub
System.out.println( getName() + "猴子通过学习,向小鱼学会了游泳……");
}
}
接口多态特性
多态参数
前面的
USB
接口中,形参UsbInterface usbInterface
既可以接收手机对象,也可以接收相机对象,就体现了接口多态。(接口引用可以指向实现了接口的类的对象)
```python
public class Interface01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Camera camera = new Camera();
Phone phone = new Phone();
Computer1 computer1 = new Computer1();
computer1.work(phone); //把手机接入计算机
computer1.work(camera);
}
}
interface UsbInterface {
//规定接口的相关方法
public void start();
public void stop();
}
//phone类实现UsbInterface
class Phone implements UsbInterface {
@Override
public void start() {
// TODO Auto-generated method stub
System.out.println("手机开始工作");
}
@Override
public void stop() {
// TODO Auto-generated method stub
System.out.println("手机停止工作");
}
}
//camera类实现UsbInterface
class Camera implements UsbInterface {
@Override
public void start() {
// TODO Auto-generated method stub
System.out.println("相机开始工作");
}
@Override
public void stop() {
// TODO Auto-generated method stub
System.out.println("相机停止工作");
}
}
//UsbInterface usbInterface形参是接口类型
//这个参数实现了接口的类的对象实例
class Computer1 {
public void work(UsbInterface usbInterface) {
//通过接口调用方法
usbInterface.start();
usbInterface.stop();
}
}
多态数组
多态传递
public class Interface04 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//接口类型的变量可以指向实现了该接口的类的对象实例
IG ig = new Teacher();
IH ih = new Teacher(); //Teacher并未实现IH接口
//但如果IH继承IG,即Teacher可实现IH接口,体现了多态传递
}
}
interface IH {
void hi();
}
interface IG extends IH{}
class Teacher implements IG {
@Override
public void hi() {
// TODO Auto-generated method stub
}
}
练习
public class Interface05 {
public static void main(String[] args) {
// TODO Auto-generated method stub
new C().px();
}
}
interface A {
int x = 0; //等价于 public static final int x = 0
}
class B {
int x = 1; //普通数学
}
class C extends B implements A {
public void px() {
//System.out.println(x); //报错,不清楚哪个x
System.out.println(A.x+""+super.x); //访问父类x不可使用B.x
}
}
内部类(非常重要、难点)
- 一个类的内部嵌套了另一个类结构,被嵌套的类称为内部类,嵌套其他类的类称为外部类。
- 类的五大成员:属性、方法、构造器、代码块、内部类。
- 内部类最大的特点是可以直接访问私有属性,并且体现类与类之间的包含关系。
- 内部类的分类:
- 局部内部类(有类名)-------->定义在外部类的局部位置(比如方法内);
- 匿名内部类(没有类名,重点)-------->定义在外部类的局部位置;
- 成员内部类(没有
static
修饰)------->定义在外部类的成员位置; - 静态内部类(有
static
修饰)------->定义在外部类的成员位置;
局部内部类
1. 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
2. 可以直接访问外部类的所有成员,包括私有的。
3. 不能添加访问修饰符,因为他的地位就是一个局部变量,局部变量不能使用修饰符。但是可以使用final
修饰。 final
修饰意味着局部内部类不能被继承,不加final
意味着局部内部类可以被继承。
4. 局部内部类的作用域仅仅在定义它的方法或代码块中。
4. 局部内部类可以直接访问外部类的成员。
5. 外部类方法中,可以创建局部内部类对象然后调用方法即可。
6. 本质上还是一个类,即类的五大成员都可以有。
6. 外部其他类不能访问局部内部类(因为局部内部类的地位就是一个局部变量)。
7. 如果外部类和局部内部类的成员重名时,默认.就近原则,如果想访问外部类的成员,则可以使用外部类名.this.成员
去访问。
//演示局部内部类的使用
public class LocalInnerClass {
public static void main(String[] args) {
// TODO Auto-generated method stub
Outer02 outer02 = new Outer02();
outer02.m1();
}
}
class Outer02 { //外部类
private int n1 =100;
private void m2() {
System.out.println("outer02 m2()");
} //私有方法
public void m1() { //方法
//1. 局部内部类是定义在外部类的局部位置,通常在方法
//3. 不能添加修饰符,但是可以使用final修饰。
//4. 局部内部类的作用域仅仅在定义它的方法或代码块【本质是没有方法名的方法】中。
//即该内部类的作用域为m1()方法中。
final class Inner02 { //局部内部类,本质仍然是类,即五大成员都可以有。
//可以直接访问外部类的所有成员,包括私有的
private int n1 = 800;
public void f1() {
//5. 局部内部类可以直接访问外部类的成员,比如下面的外部类n1和m2()。
//7. 如果外部类和局部内部类的成员重名时,默认.就近原则,如果想访问外部类的成员,
//则可以使用`外部类名.this.成员`去访问
//outer02.this本质上就是外部类的对象即哪个对象调用了m1方法,outer02.this就是哪个对象。
System.out.println("内部类的n1=" + n1 + "外部类的n1=" + Outer02.this.n1);
m2(); //也可直接访问私有方法
}
}
//6. 外部类方法中,可以创建Inner02对象然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名内部类(重点、难点)
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名。
本质上是类。
内部类。
该类没有名字。
同时还是一个对象。
- 基本语法:
new 类或接口(参数列表){
类体
};
演示基于接口匿名内部类的使用
//演示基于接口匿名内部类的使用
public class Inner01 { //外部其他类
public static void main(String[] args) {
// TODO Auto-generated method stub
Outer01 outer01 = new Outer01();
outer01.method();
}
}
class Outer01 { //外部类
private int n1 = 10;
public void method() {
//1. 基于接口的匿名内部类
//2. 需求1:想使用接口A,并创建对象。
//3. 传统方法:写一个类,实现该接口并创建对象
// A tiger = new Tiger();
// tiger.cry();
//4. 需求2:tiger只是使用一次,后面不再使用。
//5. 可以使用匿名内部类来简化开发
//6. tiger的编译类型A ,运行类型是匿名内部类,底层分布随机类名Outer01$1,底层是实现关系
//7. JDK底层在创建匿名内部类Outer01$1,立马就创建了Outer01$1实例,并且把地址返回给tiger
//8. 匿名内部类使用一次就不能再使用了,但对象tiger可以反复调用。
A tiger = new A() {
@Override
public void cry() {
System.out.println("老虎叫唤");
}
};
//9. getClass()获取对象的运行类型
System.out.println("tiger的运行类型:" + tiger.getClass() );
tiger.cry();
tiger.cry();
tiger.cry();
}
}
interface A { //接口
public void cry();
}
演示基于类匿名内部类的使用
//演示基于类的匿名内部类的使用
public class Inner02 { //外部其他类
public static void main(String[] args) {
// TODO Auto-generated method stub
Outer02 outer02 = new Outer02();
outer02.method();
}
}
class Outer02 { //外部类
private int n1 = 10;
public void method() {
//基于类的匿名内部类的使用
//1. father的编译类型Father
//2. father的运行类型Outer02$2
//3. 底层会创建匿名内部类 ,底层是继承关系,Outer02$2继承了father类
//4. jack形参列表会自动传递给构造器
Father father = new Father("jack") { //输出名字是jack
@Override
public void test() {
// TODO Auto-generated method stub
System.out.println("匿名内部类重写了test方法");
}
};
//getClass()获取对象的运行类型
System.out.println("father对象的运行类型:" + father.getClass()); //输出Outer02$2
father.test(); //输出匿名内部类重写了test方法
//基于抽象类的匿名内部类的使用
//抽象类中必须重写eat方法
Animal animal = new Animal() {
@Override
void eat() {
// TODO Auto-generated method stub
System.out.println("吃东西");
}
};
}
}
class Father {
public Father(String name) { //构造器
System.out.println("名字是" + name);
}
public void test() { //方法
}
}
abstract class Animal {
abstract void eat();
}
==============================================
名字是jack
father对象的运行类型:class 类变量和类方法.Outer02$1
匿名内部类重写了test方法
使用细节
- 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象。因此,从语法上看,它既有定义类的特征,也有创建对象的特征。
- 可以直接访问外部类的所有成员,包括私有的。
- 不能添加访问修饰符,因为它的低位就是一个局部变量。
- 匿名内部类的作用域仅仅是在它的方法或代码块中。
- 匿名内部类----访问------->外部类成员 【访问方式:直接访问】
- 外部其他类----不能访问------->匿名内部类(因为匿名内部类的地位就是一个局部变量)。
- 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则。
- 如果想访问外部类的成员,则可以使用
外部类名.this.成员
去访问。
public class Inner03 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Outer03 outer03 = new Outer03();
outer03.f1(); //输出第一种调用方法匿名内部类重写了hi方法
//如果匿名内部类中未重写hi方法,则输出 Person类中的hi方法
}
}
class Outer03 { //外部类
private int n1 = 99;
//6. 外部其他类----不能访问------->匿名内部类
//System.out.println(n2);
public void f1() {
//创建一个基于内部类的匿名
//1. 第一种调用方法
//3. 不能添加访问修饰符,因为它的低位就是一个局部变量。
//Person person = new public Person()
Person person = new Person() {
private int n2 = 1000;
@Override
public void hi() {
//2. 可以直接访问外部类的所有成员,包括私有的。
System.out.println(n1);
System.out.println("第一种调用方法匿名内部类重写了hi方法");
}
};
//输出匿名内部类重写了hi方法
person.hi(); //运行时存在动态绑定 ,运行类型是Outer03$3
//2. 第二种调用方法
//也可以直接调用,匿名内部类本身也是返回对象
new Person() {
private int n2 = 1000;
@Override
public void hi() {
// TODO Auto-generated method stub
System.out.println("第二种调用方法匿名内部类重写了hi方法");
}
}.hi();
}
}
class Person {
public void hi() {
System.out.println("Person类中的hi方法");
}
}
//抽象类、接口……都可以写
匿名内部类的最佳实践
练习一
public class Inner04 {
public static void main(String[] args) {
//匿名内部类可以当作实参去传递
//f2()方法里传递的IL
f2(new IL(){
public void show() {
System.out.println("这是一副名画");
}
});
//传统方式
//写一个类实现IL,将picture传给接口IL------硬编码
f2(new Picture());
}
//静态方法,形参是接口类型
public static void f2(IL il) {
il.show();
}
}
interface IL {
void show();
}
class Picture implements IL {
@Override
public void show() {
// TODO Auto-generated method stub
System.out.println("这是一副名画");
}
}
练习二
- 有一个铃声接口Bell,里面有一个ring方法
- 有一个手机类cellphone,具有闹钟功能alarmblock,参数是Bell类型
- 测试手机的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
- 再传入另一个匿名内部类(对象),打印:小伙伴上课了
public class Inner05 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Cellphone cellphone = new Cellphone();
cellphone.alarmblock(new Bell() {
@Override
public void ring() {
// TODO Auto-generated method stub
System.out.println("懒猪起床了");
}
});
cellphone.alarmblock(new Bell() {
@Override
public void ring() {
// TODO Auto-generated method stub
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell {
void ring();
}
class Cellphone {
public void alarmblock(Bell bell) {
bell.ring();
}
}
成员内部类
成员内部类是定义在外部类的成员位置,并且没有
static
修饰
- 可以直接访问外部类的所有成员,包含私有的。
- 可以添加任意访问符,因为它的地位是一个成员。
- 内部类编译后也会产生一个
.class
文件,其命名规则是:外部类名字$内部类名字.class
- 内部类可以使用所有访问限制修饰符,但外部类只能是
public
或缺省访问限制修饰符。
public class MemberInner01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Outer01 outer01 = new Outer01();
outer01.t1();
}
}
class Outer01 { //外部类
private int n1 = 99;
public String name = "张三";
class Inner01 { //成员内部类
public void say() {
//可以直接访问外部类的所有成员,包含私有的。
System.out.println("Outer01的n1=" + n1 + "Outer01的name=" + name);
}
}
public void t1() {
Inner01 inner01 = new Inner01();
inner01.say();
}
}
- 作用域:和外部类成员一样,为整个类体。
- 成员内部类----->访问----->外部类成员(比如:属性)【访问方式:直接访问】。
- 外部类----->访问成员内部类【访问方式:创建对象再访问】。
- 外部其他类------>访问------->成员内部类【三种方法】
public class MemberInner01 { //外部其他类
public static void main(String[] args) {
// TODO Auto-generated method stub
Outer01 outer01 = new Outer01();
outer01.t1();
//外部其他类使用成员内部类的两种方式
//第一种
Outer01.Inner01 inner01 = outer01.new Inner01();
inner01.say();
//第二种;在外部类中编写一个方法可以返回Inner08对象
Outer01.Inner01 inner01Instance = outer01.getInner01Instance();
inner01Instance.say();
}
}
class Outer01 { //外部类
private int n1 = 99;
public String name = "张三";
class Inner01 { //成员内部类
public void say() {
//可以直接访问外部类的所有成员,包含私有的。
System.out.println("Outer01的n1=" + n1 + "Outer01的name=" + name);
}
}
public void t1() {
Inner01 inner01 = new Inner01();
inner01.say();
}
//该方法返回Inner01的实例
public Inner01 getInner01Instance() {
return new Inner01();
}
}
- 如果外部类和内部类重名时,内部类访问的话,默认就近原则,如果想访问外部类的成员时,可以使用
外部类名.this.成员
去访问。
class A02 { //外部类
private int n1 = 10;
private static String name = "jack";
//定义一个成员内部类
class B02 {
private int n1 = 20;
//可以直接访问外部类的所有成员,包括私有的
public void say() {
System.out.println("B02 n1 = " + n1 + "A02 n1 = " + A02.this.n1);
//B02 n1 = 20
//A02 n1 = 10
}
}
}
静态内部类
是定义在外部类的成员位置,并且有
static
修饰。
- 可以直接访问外部类的所有静态成员,包括私有的,但不能访问非静态成员。
- 可以添加任何访问修饰符,因为它的地位就是一个成员。
- 作用域:同其他的成员,为整个类体。
- 静态内部类------->访问------->外部类(比如:静态属性)【访问方式:直接访问所有静态成员】。
- 外部类------->访问------->静态内部类【访问方式:创建对象再访问】。
- 外部其他类------->访问---------->静态内部类。
- 如果外部类和静态内部类重名时,静态内部类访问默认遵循就近原则,如果想访问外部类成员,则可以使用
外部类名.成员
去访问。
public class StaticInner01 {
public static void main(String[] args) { //外部其他类
// TODO Auto-generated method stub
Outer01 outer01 = new Outer01();
outer01.hi();
//外部其他类访问静态内部类
//方式一:
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)。
Outer01.Inner01 inner01 = new Outer01.Inner01();
inner01.say();
System.out.println("=====================");
//方式二:
//编写一个方法可以返回静态内部类的实例
outer01.getInner01();
inner01.say();
}
}
class Outer01 { //外部类
private int n1 = 10;
private static int n2 = 10;
private static String name = "张三";
//静态内部类
public static class Inner01 {
public void say() {
//1.可以直接访问外部类的所有静态成员,包括私有的,但不能访问非静态成员。
//System.out.println(n1);----->报错
//2.静态内部类------->访问------->外部类(比如:静态属性)【访问方式:直接访问所有静态成员】。
System.out.println(n2);
}
}
//3. 作用域:同其他的成员,为整个类体。
public void hi() {
//4.外部类------->访问------->静态内部类【访问方式:创建对象再访问】。
Inner01 inner01 = new Inner01();
inner01.say();
}
public Inner01 getInner01() { //静态方法、非静态方法的也可以
return new Inner01();
}
}