static(静态)
静态变量
在JavaBean中,不加static只能供一个对象使用
- 被 static 修饰的变量属于类,所有实例共享同一个变量值
- 在内存中只存在一份,当类被加载时初始化
- 通常用于表示所有对象共有的属性
public class Student {
// 静态变量(类变量)
public static String schoolName = "阳光中学";
// 实例变量
private String name;
public Student(String name) {
this.name = name;
}
public static void main(String[] args) {
// 直接通过类名访问静态变量
System.out.println(Student.schoolName); // 输出:阳光中学
Student s1 = new Student("张三");
Student s2 = new Student("李四");
// 也可以通过实例访问(不推荐)
System.out.println(s1.schoolName); // 输出:阳光中学
// 修改静态变量,所有实例都会受到影响
Student.schoolName = "星光中学";
System.out.println(s2.schoolName); // 输出:星光中学
}
}
[!NOTE] JDK8之前,静态区处于方法区里面。 JDK7之后,静态区处于堆空间之中。
静态方法
- 多用在测试类和工具类当中
- JavaBean中很少用
[!NOTE] JavaBean类:用来描述一类事物的类 测试类:用来检查其他类是否书写正确,带有main方法的类,是程序的入口 工具类:不是用来描述一类事物的,而是帮我们做一些事情的类
工具类
1.类名见名知意 2.私有化构造方法
public class ArrUtil{
private ArrUtil(){}
}
private ArrayUtil() {} 是一个私有构造方法
由于构造方法被声明为 private(私有),这意味着在类的外部无法调 用这个构造方法,也就无法创建 ArrayUtil 类的对象实例。
防止这个工具类被意外地实例化
[!NOTE] 如果不写这个私有构造方法,Java 会默认提供一个公共的无参构造方法 这样别人就可能误将其当作普通 JavaBean,写出new ArrayUtil()这样的代码(虽然创建了对象也没用,但不符合设计意图)
3.方法定义/为静态
static的注意事项
- 静态方法只能访问静态变量和静态方法
- 非静态方法可以访问静态变量或静态方法,也可以访问非静态的成员变量和非静态的成员方法
- 静态方法中没有this关键字
public class Student{
String name;
int age;
static String teachername;
//this:表示当前方法调用者的地址值
//这个this:是由虚拟机赋值的
public void show1(Student this){//此处的this是虚拟机暗加的
sout("this:" + this);//地址值
sout(name+","+age+","+teachername);
//实际上是this.name和this.age和this.teachername
show2();
//实际上是this.show2();
//意思是当前show1调用的对象继续调用show2
}
public void show2(){}
poublic static void method(){//此处的静态方法却不会有this关键字
//不可调用非静态的成员变量和成员方法
sout("静态方法");
}
}
public class StudentTest(){
public static void main(String[] args){
Student.teachername = "老师姓名";
Student s1 = new Student();
sout("s1:" + s1);//地址值
s1.name = "张三";
s1.age = 23;
s1.show1(); //此处的s1对应this
Student s2 = new Stduent();
sout("s2:" + s2);//地址值
s2.name = "李四";
s2.age = 24;
s2.show1();//此处的s2对应this
}
}
打印后,s1与this的地址值相同,s2与this的地址相同。
内存的加载
静态:随着类的加载而加载
非静态:与对象有关
main方法
public class HelloWorld{
public static void main(String[] args){
//[]:数组
//String:数据类型
//args:数组名
sout("HelloWorld");
}
}
- public:被JVM调用,访问权限足够大
- static:被JVM调用,不用创建对象,直接类名访问。
- 因为main方法是静态的,所以测试类中的其他方法也需要是静态的
- void:被JVM调用,不需要给JVM返回值
- main:一个通用的名称,不是关键字,但是被JVM识别
- String[] args:以前用于接受键盘录入数据,现在没用
继承
- Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起 继承关系
public class Student extends Person{} - Studet成为子类(派生类),Person称为父类(基类或超类)。
使用继承的好处:
- 可以把多个子类中重复的代码抽取到父类中了,提高代码的复用性
- 子类可以在父类的基础上,增加其他的功能,使子类更强大。
?什么时候用继承: 当类与类之间,存在相同(共性)的内容,并满足子类是父类的一种,就可以考虑用继承,来优化代码。
继承特点
Java只支持单继承,不支持多继承,但支持多层继承。
- 单继承:一个子类只能继承一个父类
- 不支持多继承:子类不能继承多个父类
[!NOTE] 每一个类都直接或间接继承于Object
[!WARNING]
- 构造方法不可被继承
- 成员变量私有和非私有都能被继承(私有不可直接使用)
- 成员方法只有非私有非static非final(能被加入虚方法表)能被继承
在继承中 成员变量的访问特点
就近原则
public class Fu{
String name = "Fu";
}
public class Zi extends Fu{
String name = "Zi";//若没有此语句会打印Fu
public void ziShow(){
String name = "ziShow";//若没有此语句会打印Zi
sout(name); //ziShow...依次向上找name
}
}
public class Fu{
String name = "Fu";
}
public class Zi extends Fu{
String name = "Zi";
public void ziShow(){
String name = "ziShow";
//局部->本类->父类
sout(name); //ziShow
sout(this.name);//Zi
//this:本类
sout(super.name);//Fu
//super:父类
}
}
[!IMPORTANT] name:从局部位置开始往上找… 局部 –> 本类 –> 父类 this.name:从本类成员位置开始往上找… 本类 –> 父类 super.name:从父类成员位置开始往上找…父类
在继承中成员方法的访问特点
直接调用满足就近原则,super调用直接访问父类 方法的重写: 当父类的方法不能满足子类现在的需求时,需要进行方法重写
书写格式: 在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。 @Override重写注解 @Override是放在重写后的方法上,校验子类重写时语法是否正确。
方法重写的本质
A类——-继承——>B类—继承—>C类
子类覆盖了从父类继承下来的虚方法表里的方法
| 写入方法 | A(method2) | B(method2) | C(method1,method2) |
|---|---|---|---|
| 虚方法表 | C:method1 | C:method1 | C:method1 |
| A:method2()重写 | B:method2(重写) | C:method2 |
[!NOTE] 1.重写方法的名称、形参列表必须与父类中的一致 2.子类重写父方法时.,访问权限子类必须大于等于父类(空 < protected < public) 3.子类重写父类方法时,返回值类型必须小于等于父类 4.重写的方法尽量和父类保持一致 5.只有被添加到虚方法表中的方法才能被重写
在继承中构造方法的访问特点
- 父类中的构造方法不会被子类继承,但是可以使用super调用。
- 子类中所有的构造方法默认先访问父类中的无参构造,在执行自己。(子类构造方法第一句默认是super(),不写也存在,若想调用父类有参构造,则必须使用super)
- 如果想要访问父类的有参构造,必须手动书写
this 与 super
this:理解为一个变量,表示当前方法调用者的地址值。 super:代表父类存储空间。
| 关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
|---|---|---|---|
| this | this.成员变量—访问本类成员变量 | this.成员方法—访问本类成员方法 | this(…)—访问本类构造方法 |
| super | super.成员变量—访问父类成员变量 | super.成员方法—访问父类成员方法 | super(…)—访问父类构造方法 |
this():
[!NOTE] 在子类中可以直接快捷使用构造函数
class Student{
String name;
int age;
String school;
Student(){
//表示调用本类的其他构造方法
//虚拟机不会再添加super();
this(null,0,"默认大学");
}
public Student(String name, int age, String school) {
//因为调用之后此处有一个super();
this.name = name;
this.age = age;
this.school = school;
}
}
多态
同类型的对象,表现出不同的形态
表现形式:父类类型 对象名称 = 子类对象;
前提:
1.有继承/实现关系
2.有父类引用指向子类对象
3.有方法重写
好处:使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
public class Test {
public static void main(String[] args) {
Student s = new Student("张三",18);
Teacher t = new Teacher("王建国",22);
Admin a = new Admin("管理员",20);
register(s);
register(t);
register(a);
}
//接收老师,学生,管理员
public static void register(Person p){
//在每一个子类中都重写了show方法
//p.show(),会直接指向子类的方法
p.show();
}
}
多态调用成员变量的特点
`父类类型 对象名称 = 子类对象;`
- 变量调用:编译看左边,运行也看左边
//Animal 默认name为"动物"
//Dog 默认name为"狗"
Animal a = new Dog();
//编译看左边:Javac编译代码的时候,会看左边的父类有没有这个变量,如果有,编译成功,反之失败
//运行也看左边:Java运行代码的时候,实际获取的就是左边父类中成员变量的值
sout(a.name);//动物
- 方法调用:编译看左边,运行看右边
Animal a = new Dog();
//编译看左边:Javac编译代码的时候,会看左边的父类中有没有这个方法,如果有,编译成功,反之失败
//运行看右边:Java运行代码的时候,实际上运行的是子类中的方法
a.show();//Dog---show方法
[!NOTE] 成员变量:在子类的对象中,会把父类的成员变量也继承下来。父:name,子:name(本质上时在内存中开辟了一个地址空间存储两个name) 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖。
优点:使用父类型作为参数,可以接收所有子类对象
弊端:不能调用子类的特有功能(方法),但是可以通过强制转换解决
Animal a = new Dog();
Dog d = (Dog)a;//大范围 -> 小范围
//判断对象是不是某一个类中的 instanceof
if(a instanceof Dog){
Dog d = (Dog)a;
d.lookHome();
}else if{
Cat c = (Dog)a;
c.catchMouse();
}else{
sout("没有这个类型,无法转换");
}
//新特性
if(a instanceof Dog d){
// Dog d = (Dog)a;
d.lookHome();
}else if(a instanceof Cat c){
// Cat c = (Dog)a;
c.catchMouse();
}else{
sout("没有这个类型,无法转换");
}
包
包就是文件夹,用来管理不同类的Java类,方便后期代码维护
包名一般都用小写
使用其他类的规则:
1.使用同一个包中的类时,不需要导包
2.使用Java.lang包中的类时,不需要导包
3.其他情况都需要导报
4.如果同时使用两个包的同名类,需要用 全类名
全类名:包名 + 类名
final
方法:表明该方法时最终方法,不能被重写
类:表明该类是最终类,不能被继承
变量:叫做常量,只能被赋值一次 (c/c++中的const)
[!TIP] 常量的命名规范 单个单词:全部大写 多个单词:全部大写,单词之间用下划线隔开
[!NOTE] final修饰的变量是基本类型:那么变量存储的数据值不能发生改变
final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,对象内部的可以改变(静态指针)
//记录的地址值不能发生改变
final Student S = new Student("张三",23);
// S = new Student(); !报错
S.setName("李四"); // 对象内部可以改变
补充:字符串不可变的原因是String是由final和private存储的数组
权限修饰符
- 控制一个成员能够被访问的范围
- 可以修饰成员变量,方法,构造方法,内部类。
| 修饰符 | 同一个类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
|---|---|---|---|---|
| private | √ | |||
| 空着不写(默认) | √ | √ | ||
| protected | √ | √ | √ | |
| public | √ | √ | √ | √ |
代码块
构造代码块:从多个构造方法中抽取重复代码,而且会先于构造方法的执行
静态代码块
格式:static{}
特点:需要static关键字修饰,随着类的加载而加载,并且自动触发,只执行一次。
static{
sout("执行静态代码块");
//随着类的加载而加载,并且只执行一次。
}
应用:数据初始化
//sattic里面只能用static
static ArrayList<Student>list = new ArrayList<>();
static{
sout("初始化");
}
public staic void main(String[] args){
...
}
抽象类和抽象方法
抽象类: 是一种不能被实例化的类,它主要用于定义其他类(子类)的共同接口和部分实现.
- 抽象方法一定在抽象类中
- 抽象类不一定含有抽象方法.
抽象类的主要特点:
1.不能实例化:抽象类本身不能创建对象,只能作为父类被继承
2.可以包含抽象方法:抽象方法只有声明,没有具体实现,必须由子类实现
3.可以包含具体方法:抽象类也可以有已实现的方法,供子类直接使用或重写
4.子类必须实现所有抽象方法:如果子类没有实现父类的所有抽象方法,那么子类也必须是抽象类
接口
- 接口用关键字**
interface**来定义public interface 接口名{} - 接口不能实例化
- 接口和类之间是实现关系,通过**
implements**关键字表示public class 类名 implement 接口名{} - 接口的子类(实现类) 要么重写接口中的所有抽象方法 要么是抽象类
[!NOTE] 1.接口和类的实现关系,可以单实现,也可以多实现。
public class 类名 implements 接口名1,接口名2{}2.实现类还可以在继承一个类的同时实现多个接口。public class 类名 extends 父类 inplements 接口名1,接口名2{}
成员特点和接口中的各种关系
接口中成员的特点
成员变量
只能是常量 默认修饰符:public static final
构造方法:没有
成员方法 只能是抽象方法 默认修饰符:public abstract
JDK7以前:接口中只能定义抽象方法
JDK8的新特性:接口中可以定义有方法体的方法
JDK9的新特性:接口中可以定义私有方法
接口和类的关系
- 类和类的关系
继承关系,只能单继承,不能多继承,但是可以多层继承
- 类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类同时实现多个接口
- 接口和接口的关系
继承关系,可以单继承,也可以多继承
接口中的默认方法
格式:
public default 返回值类型 方法名(参数列表){ }
接口中默认方法的注意事项
1.默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
2.public可以省略,default不能省略
3.如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。
接口中的静态方法
- JDK8以后 允许在接口中国定义静态方法,需要用static修饰
接口中静态方法的额定义格式 :
- 格式: public static 返回值类型 方法名(参数列表){ }
- 范例: public static void show(){ }
注意事项:
静态方法只能通过接口名调用,不能同故宫实现类名或者对象名调用
public可以省略,static 不可以省略
JDK9以后 接口中出现了私有方法
- privbate 返回值类型 方法名(参数列表){} private void show(){ }
- private static 返回值类型 方法名(参数列表){} private static void method(){ }
public interface InterA {
public static void show1(){
System.out.println("show1方法开始执行了");
show3();
}
public static void show2(){
System.out.println("show2方法开始执行了");
show3();
}
public static void show3(){
//必须是static
//若上述show1,show2是default,则不需要static
System.out.println("记录程序运行时的各种细节");
}
}
[!NOTE] 静态的私有方法,为静态方法服务 普通的私有方法,为默认方法服务
接口的应用(接口多态)
接口类型 j = new 实现类对象();
编译看左,运行看右
适配器设计模式
- 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
- 使用设计模式是为了可重用代码、让代码更容易被他人理解,保证代码的可靠性、程序的重用性。(设计模式就是多种套路)
- 适配器设计模式:解决接口与接口实现类之间的矛盾问题
public interface Inter {
public abstract void method1();
public abstract void method2();
...
public abstract void method10();
}
//在此创建父类来使InterImpl更加简洁
public abstract class InterAdapter implements Inter {
//abstract 不让外界创建对象
@Override
public void method1() {}
public void method2();
...
@Override
public void method10() {}
}
public class InterImpl extends InterAdapter{
//需要用到哪个方法就重写哪个方法
@Override
public void method5() {
System.out.println("第五个方法");
}
}
Inter定义了多个抽象方法,但是测试类只需要调用其中的一个方法method5(),于是添加一个类(作为测试类的父类)去重写所有方法,这样测试类就可以按需重写方法了。其中添加的这个类就是适配器
内部类
- 类的五大成员:属性,方法,构造方法,代码块,内部类
- 内部类分为: 成员内部类,静态内部类,局部内部类,匿名内部类
- 在A类的内部定义B类,B类就被成为内部类
- 内部类表示的事物是外部类的一部分,内部类单独出现没有任何意义
访问特点
- 内部类可以直接访问外部类的成员,包括私有private
- 外部类要访问内部类的成员,必须创建对象。
成员内部类
- 写在成员位置的,属于外部内部类
- 成员内部类可以被一些修饰符修饰,private、默认、protected、public、static等(用static修饰就是静态内部类了)
获取成员内部类对象的两种方式:
方式一:外部类编写方法,对外提供内部类对象
方式二:直接创建
格式 Outer.Inner oi = new Outer().new Inner();
成员内部类获取外部类的成员变量
public class Outer {
private int a = 10;//<----sout(Outer.this.a)
class Inner {
private int a = 20;//<----sout(this.a)
private void show() {
int a = 30;//<----sout(a)
System.out.println(a);
System.out.println(this.a);
System.out.println(Outer.this.a);//此处的Outer是类名
}
}
}
静态内部类
- 是用static修饰的成员内部类
- 静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象
创建静态内部类对象的格式:
外部类名.内部类名. 对象名 = new 外部类名.内部类名 ();Outer.Inner oi = new Outer.Inner();
调用非静态方法的格式:先创建对象,再对象调用
调用静态方法的格式:外部类名.内部类名.方法名();
局部内部类(了解)
- 1.将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量
- 2.外界是无法直接使用,需要在方法内部创建对象使用
- 3.该类可以直接访问外部类的成员,也可以访问方法内的局部变量
匿名内部类**
- 匿名内部类本质上是隐藏了名字的内部类
new 类名或接口名(){
重写方法;
};
//注意有分号
接口(实现关系)
public class Student implements Swim(){
@Override
public void swim(){
sout("重写的swim方法");
}
}
//1.把前面的class去掉,剩余的内容变成了一个没有名字的类
//2.这个没有名字的类想要实现Swim接口
//把Swim写在了大括号的前面,表示这个没有名字的类实现了Swim接口,所以需要在类中重写接口里面的所有抽象方法。
new Swim(){
@Override
public void swim(){
sout("重写的swim方法")
}
//这一部分(匿名内部类的对象)是没有名字的类(Student),他来实现Swim接口
};
类名(继承关系)
new Animal(){
@Override
public void eat(){
sout()
}
}
应用/拓展
public class Test {
public static void main(String[] args) {
//整体理解为Swim接口的实现类对象
//接口多态:接口类型 j = new 实现类对象();
Swim s = new Swim(){
@Override
public void swim() {
System.out.println("重写之后的swim方法");
}
};
//编译看左边,运行看右边
s.swim();
new Swim(){
@Override
public void swim() {
System.out.println("重写之后的swim方法");
}
}.swim();//自己调用自己
}
}
格式的细节:
包含了继承或实现,方法重写,创建对象。整体就是一个类的子类对象或者接口的实现类对象
使用场景:
当方法的参数是接口或者类时。 以接口为例,可以传递这个接口的实现类对象 如果实现类只要使用一次,就可以用匿名内部类简化代码