static
static 常用来修饰类的成员:成员变量、方法、嵌套类
成员变量
- 被
static
修饰:类变量、成员变量、静态字段- 在程序中只占用一段固定的内存(存储在方法区),所有对象共享
- 可以通过实例、类访问 (一般用类名访问和修改,不建议用实例修改),但是不能通过类名访问实例变量和实例方法
- 没有被
static
修饰:实例变量- 每个实例都有一份内存
package com.hxw.demo4;public class Person {public static int count = 0;public int age;
}
package com.hxw.demo4;
public class Main {public static void main(String[] args) {Person.count = 20;Person p1 = new Person();p1.age = 20;System.out.println(p1.count);Person p2 = new Person();p2.age = 30;System.out.println(p2.count);}
}
控制台输出
20
20
方法
- 被
static
修饰:类方法、静态方法- 可以通过实例、类访问
- 内部不可以使用
this
- 可以直接访问类变量、类方法
- 不可以直接访问实例变量和实例方法
- 没有被
static
修饰:实例方法- 只能通过实例访问,不可以通过类访问
- 内部可以使用
this
- 可以直接访问实例变量和实例方法
package com.hxw.demo4;public class Person {public static int count = 0;public int age;// 实例方法public void run1(){// 实例方法中可以使用this调用其他实例方法this.run2();//实例方法可以直接访问类变量和类方法System.out.println(count);test1();System.out.println(this.age);}public void run2(){System.out.println("void run2");}public static void test1(){System.out.println("static void test1");test2();System.out.println(count);}public static void test2(){System.out.println("static void test2");}
}
package com.hxw.demo4;public class Main {public static void main(String[] args) {Person person = new Person();person.age = 18;person.run1();Person.test1();}
}
总结:
静态导入
使用了静态导入之后,就可以省略类名来访问静态成员(成员变量、方法、嵌套类)
静态导入的经典应用
为了避免每一次都是用Math.PI
,可以考虑使用静态导入,其他的Math方法也是一样的道理。
成员变量的初始化
- 编译器会自动为未初始化的成员变量设置初始值
- 如何手动给实例变量提供初始值
- 在声明中:定义变量的时候直接给其设置初始值
- 在构造方法中:在构造方法内部利用
this
进行初始化 - 在初始化中 :这个情况看下面代码,了解一下,
package com.hxw.dmeo6;public class Person {public static int count;public int age = 1; //申明中public Person(){age = 20;// 在构造方法中,但是先执行初始化中的代码}//在初始化中{age = 18;}
}
一般很少使用初始化代码,方法如下 公共初始化代码一般放到参数最多的构造方法中
package com.hxw.dmeo6;public class Person {public static int count;public int age = 1; //申明中public Person(){this(0, 0);}public Person(int age){this(age, 0);}public Person(int age, int no){// 公共初始化代码一般放到参数最多的构造方法中}// //在初始化中
// {
// age = 18;
// }
}
- 如何手动给类变量提供初始值
- 在声明中
- 在静态初始化代码块中:当一个类初始化的时候(也就是一个类第一次被主动使用时,JVM会对类进行初始化),就会执行静态初始化代码块,且只会执行一次。
- 可以有多个静态初始化块,按照在源码中出现的顺序被执行
初始化块和静态初始化块
这里实际的执行顺序和自己想的不一样,要先初始化完父类,再初始化子类
单例模式
如果一个类设计为单例模式,那么程序运行过程,这个类只能创建一个实例
- 饿汉单例模式:一开始就创建了对象
- 懒汉单例模式:等到该创建对象的时候创建对象,懒汉单例模式会有线程安全问题如果三个对象同时构建实例,那么会创建多个对象,但是最终创建的才会赋值给instance,所以总的说来还是单例模式
Rocket.class
package com.hxw.demo7;/*** 饿汉式单例模式:一上来就new了一个对象*/
//public class Rocket {
// //私有的静态(唯一内存)实例变量
// private static Rocket instance = new Rocket();
//
// // 构造方法私有化 在Main中就不能new Rocket()
// private Rocket(){
//
// }
//
// //公共的静态方法,返回唯一的静态实例
// public static Rocket getInstance(){
// return instance;
// }
//}/*** 懒汉式单例模式*/public class Rocket {public static Rocket instace = null;private Rocket(){}public static Rocket getInstance(){//用到的时候再new对象if(instace == null){instace = new Rocket();}return instace;}
}
Main.class
package com.hxw.demo7;public class Main {public static void main(String[] args) {Rocket r1 = Rocket.getInstance();Rocket r2 = Rocket.getInstance();Rocket r3 = Rocket.getInstance();//r1 r2 r3 都是同一个instanceSystem.out.println(r1 == r2);System.out.println(r2 == r3);}
}
控制台输出
true
true
final和常量
- 被
final
修饰的类不能被继承 - 被
final
修饰的成员方法不能被重写 - 被
final
修饰的变量,只能进行一次赋值,而且需要初始化,如果定义的时候没有初始化,那么在构造函数中一定要初始化
常量(Content)
- 常量的写法:
public static final double PI = 3.14159265358979323846;
,一般名字全部用大写 - 如果将基本类型或字符串定义为常量,并且在编译时就能确定值,编译器就会使用常量值代替各处的常量名(类似于C语言的宏替换),这样可以减少访问内存的次数,提高代码运行的效率
嵌套类
内部类(Inner Class,非静态嵌套类)
- 嵌套类:定义在另一个类中的类
- 外部类:在嵌套类外层的类
- 顶级类:最外层的外部类
- 内部类:没有被
static
修饰的嵌套类,非静态嵌套类。- 跟实例变量、实例方法一样,内部类与外部类的实例相关联**,必须先创建外部类实例,然后再用外部类实例创建内部类实例**。
- 内部类不能定义除了除编译时常量以外的任何
static
成员 - 内部类可以直接访问外部类中的所有成员(即使被申明为static)因为先创建了外部类实例,再创建内部类实例。
- 外部类可以直接访问内部类实例的成员变量、方法
外部类、内部类如下
package com.hxw.demo9;public class OuterClass {// 静态嵌套类static class StaticNestedClass {}// 非静态嵌套类(内部类)class InnerClass{}class A{class B{class C{}}}}
对应的内存图如下
静态嵌套类
- 静态嵌套类:被
static
修饰的嵌套类 - 静态嵌套类在行为上就是一个顶级类,只是定义的代码写在了另一个类中
- 对比一般的顶级类,静态嵌套类多了一些特殊权限
- 可以访问外部类中的成员(即使被申明为static)
什么情况使用嵌套类
情况一:如果类A只用在类C内部,可以考虑将类A嵌套到类C内部
- 封装性更好
- 程序包更加简化
- 增强可读性、维护性
情况二:如果类A需要经常访问类C的非公共成员,可以考虑将类A嵌套到类C中
- 另外也可以根据需要将类A隐藏起来,不对外暴露
情况三:如果需要经常访问非公共的实例成员(而且需要现有外层,后有内层的关系,例如现有公司Company
后有员工Employee
),设计成内部类(非静态嵌套类),否则设计成静态嵌套类
- 如果必须现有C实例,才能创建A实例,那么可以将A设计为C的内部类
情况一案例:
局部类
- 局部类:定义在代码块(包含
{}
,可以在里面写代码的块)中的类(可以定义在方法中,for循环中,if语句中) - 局部类不能定除编译时常量以外的任何
static
成员 - 局部类只能访问final或者有效final(只赋值一次,没有被第二次赋值)的局部变量
- 局部类可以直接访问外部类的所有成员
- 局部类只有定义在实例相关的代码块中,才能直接访问外部类中的实例成员(实例变量、实例方法)
由于局部类是定义在代码块中的,所以其作用域也只局限于代码块中,因此,什么时候会用到局部类呢?当一个类是在一个代码块中使用的时候
package com.hxw.demo10;public class Person {static {class A{}}{int age = 10;class A{}}public void test(){for (int i = 0; i < 10; i++) {class A{}}if(true){class A{}}else{class A{}}{class A{}}}
}
抽象类
抽象方法(Abstract Method)
- 抽象方法:被
abstract
修饰的方法- 只有方法申明,没有方法实现
- 不能是
private
权限(因为定义抽象方法的目的就是为了让子类去实现) - 只能是实例方法,而不能是类方法
- 只能定义在抽象类,接口中
package com.hxw.demo11;public abstract class Main {public static void main(String[] args) {}public abstract void test();
}
抽象类
- 抽象类:被
abstract
修饰的类- 可以定义抽象方法
- 不能实例化,但是可以自定义构造方法
- 子类必须实现抽象父类中的所有抽象方法(除非子类也是一个抽象类)
- 可以向非抽象类一样定义成员变量、常量、嵌套类型、初始化块、非抽象方法等,也就是,抽象类也可以完全不定义抽象方法
常见使用场景
- 抽取子类的公共实现到抽象父类中,要求子类必须要单独实现的去定义成抽象方法
接口(Interface)
联想到日常笔记本电脑的接口,不同厂商的USB长得都是一样的,这是他们遵守相同的标准
-
API(Application Programming Interface)
- 应用编程接口,提供给开发者调用的一组功能(无需提供源码)
-
java中的接口
- 一系列方法申明的集合
- 用来定义规范、标准
抽象类(abstract)是通过子类继承来实现其抽象方法,接口(Interface)是通过其他类实现该接口来实现其抽象方法
家教案例引入接口概念
有一个小孩,需要应聘家教,应聘要求:
- 会教授编程
- 会教踢足球
涉及的人群可能有:在校的学生,在校的老师、出租车司机
首先看没有使用接口:
- 定义一个孩子类
package com.hxw.demo12;public class Child {private String name;private Student jiajiao;public Child(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Student getJiajiao() {return jiajiao;}public void setJiajiao(Student jiajiao) {this.jiajiao = jiajiao;}public void study(){jiajiao.jiaoBianCheng(this);jiajiao.jiaoZuQiu(this);}}
- 定义一个学生类
package com.hxw.demo12;public class Student {public void jiaoBianCheng(Child child){System.out.println("Student教" + child.getName() + "编程");}public void jiaoZuQiu(Child child){System.out.println("Student教" + child.getName() + "足球");}}
- 主函数
package com.hxw.demo12;public class Main {public static void main(String[] args) {Child child = new Child("Jack");child.setJiajiao(new Student());child.study();}
}
如果希望老师教学生的话,按照这种方式就需要再创建一个Teacher类…教课的人身份众多,都需要单独修改Child类中的jiajiao属性,以及本类中的方法,这样很繁琐,但是这些类本质上都是实现了两个方法,招人的要求是满足这两个要求,但是对于这个人其他的特征,其实不用了解。此时,就可以将这两个行为定义到接口当中,各种身份的家教去实现这个接口即可。
- 创建JiaJiaoable接口,将原来Student中的方法复制过去,并改写为抽象方法。
package com.hxw.demo12;public interface JiaJiaoable {public abstract void jiaoBianCheng(Child child);public abstract void jiaoZuQiu(Child child);
}
Student类、Teacher类分别实现JiaJiaoable接口中的方法
package com.hxw.demo12;public class Student implements JiaJiaoable{@Overridepublic void jiaoBianCheng(Child child) {System.out.println("Student教" + child.getName() + "biancheng");}@Overridepublic void jiaoZuQiu(Child child) {System.out.println("Student教" + child.getName() + "zuqiu");}
}
package com.hxw.demo12;public class Teacher implements JiaJiaoable{@Overridepublic void jiaoBianCheng(Child child) {System.out.println("Teacher教" + child.getName() + "biancheng");}@Overridepublic void jiaoZuQiu(Child child) {System.out.println("Teacher教" + child.getName() + "zuqiu");}
}
修改孩子类的家教属性为借口类型
package com.hxw.demo12;public class Child {private String name;private JiaJiaoable jiajiao;public Child(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public JiaJiaoable getJiajiao() {return jiajiao;}public void setJiajiao(JiaJiaoable jiajiao) {this.jiajiao = jiajiao;}public void study(){jiajiao.jiaoBianCheng(this);jiajiao.jiaoZuQiu(this);}}
调用Main方法:
package com.hxw.demo12;public class Main {public static void main(String[] args) {Child child = new Child("Jack");child.setJiajiao(new Student());child.study();child.setJiajiao(new Teacher());child.study();}
}
控制台输出
Student教Jackbiancheng
Student教Jackzuqiu
Teacher教Jackbiancheng
Teacher教Jackzuqiu
接口中可以定义的内容
- 可以定义:抽象方法、常量、嵌套类型,从java8开始定义
- 上述可以定义的内容都是隐式
public
的,因此可以省略public
关键字 - 从java9开始可以定义
private
方法
- 上述可以定义的内容都是隐式
- 常量可以省略
static
、final
- 接口中没有成员变量,写出来就是常量
- 抽象方法可以省略
abstract
- 不能自定义构造方法、不能定义初始化块、不能实例化
接口的细节
- 接口名称可以在任何使用类型的地方使用
- 一个类可以通过
implement
关键字实现一个或者多个接口- 实现接口的类必须接口中定义的所有抽象方法,除非它也是一个抽象类
- 如果一个类实现的多个接口中有相同的抽象方法,只需要实现此方法一次
extends
和implement
可以同时使用,但是implements
必须写在extends
后面- 当父类、接口中的方法签名(方法名称和参数列表)一样,那么返回值类型也必须一样
一个接口可以通过extends
关键字继承一个或者多个接口
- 当多个父接口方法签名一样的时候,那么返回值类型也必须一样
定义保姆类
package com.hxw.demo12;public interface BaoMuable {void cook(Child child);
}
学生不仅想应聘家教,也想应聘保姆
package com.hxw.demo12;public class Student implements JiaJiaoable, BaoMuable{@Overridepublic void jiaoBianCheng(Child child) {System.out.println("Student教" + child.getName() + "biancheng");}@Overridepublic void jiaoZuQiu(Child child) {System.out.println("Student教" + child.getName() + "zuqiu");}@Overridepublic void cook(Child child) {}
}
由上述案例,可以看出 一个类可以通过implement
关键字实现一个或者多个接口
定义一个Animal类
package com.hxw.demo12.test;public class Animal {public void eat(String name){}
}
定义一个吃东西的接口
package com.hxw.demo12.test;public interface Eatable {void eat(String name);
}
定义了一个小狗类,这个类继承了Animal.class
并且实现了Eatable
接口
package com.hxw.demo12.test;public class Dog extends Animal implements Eatable{@Overridepublic void eat(String name) {}public static void main(String[] args) {Dog dog = new Dog();dog.eat("bone");}}
抽象类和接口的对比
抽象类和接口的用途有些类似,该如何选择?
抽象类:
1. 继承:A extends D (A 是 D)接口:
2.实现:A implement D(A会D中的行为)
- 何时选择抽象类
- 在紧密相关的类之间共享代码(例如人、老师都吃东西)
- 需要除
public
以外的访问权限 - 需要定义实例变量、非
finial
的静态变量(接口中只能定义常量)
- 何时选择接口
- 不相关的类实现相同的方法 (例如人、狗都吃东西)
class Student{public void eat(){// 代码1// 代码2}
}
class Teacher{public void eat(){// 代码1// 代码2}
}
Student和Teacher中的eat()代码一样,就可以将其共享出来,选择抽象类
接口的升级问题
- 如果接口需要升级,比如增加新的抽象方法
- 会导致大幅度的代码改动,以前实现的接口类都得改动
- 若想在不改动以前实现类的前提下进行接口升级,从java8开始有两种方案
- 默认方法(Default Method):将接口中新增的方法定义为默认方法,默认方法是允许有实现的
- 静态方法(Static Method)
Lambda
Lambda Expression
- Lambda 表达式是从java8才开始有的语法。
- 函数式接口(Funxtional Interface):只包含一个抽象方法的接口
- 可以在接口上加上
@FunctionalInterface
注解,表示其是一个函数式接口
- 可以在接口上加上
- 当匿名类实现的是函数式接口时, 可以使用Lambda表达式进行简化
函数式接口
Lambda注意事项
- Lambda只能访问
finial
或者有效finial
的局部变量 - Lambda没有引入新的作用域
定义一个OuterClass
package com.hxw.demo13;public class OuterClass {private int age = 1;public class InnerClass{private int age = 2;void inner(){Testable t = v -> {System.out.println(v);System.out.println(age);System.out.println(this.age);System.out.println(InnerClass.this.age);System.out.println(OuterClass.this.age);};t.test(3);}}public static void main(String[] args) {new OuterClass().new InnerClass().inner();}
}
方法引用
- 如果Lambda中的内容仅仅是调用这个方法,可以使用方法引用(method Reference)来简化