Java面向对象

static

静态,可以修饰成员变量、成员方法。

成员变量

成员变量按照有无static修饰,分为两种:

类变量:有static修饰,属于类,在计算机里只有一份,被类的全部对象共享。

访问方式:类名.类变量 (推荐),对象.类变量(不推荐)。

实例变量(对象的变量):无static修饰,属于每个对象的。

访问方式:对象.实例变量。

成员变量的执行原理

类变量的应用场景

如果某个数据只需要一份,且希望能够被共享(访问、修改),则将该数据定义成类变量。

1
2
3
4
5
6
7
8
9
public class User {
//类变量
public static int count;

//构造器
public User(){
count++;//每new一个User对象,用户类可以记住自己创建了多少个用户对象。
}
}
1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
new User();
new User();

System.out.println(User.count);//2
}
}

成员方法

类方法(静态方法):有static修饰的成员方法,属于类。类方法只能访问类变量,不能访问实例变量。

访问方式:类名.类方法 (推荐),对象名.类方法(不推荐)

实例方法(对象的方法):无static修饰的成员方法,属于对象。

访问方式:对象.实例方法

成员方法的执行原理

注意:

main方法是类方法。调用java Test执行程序时,实际执行的是Test.main(),所以main方法能直接跑起来。

类方法的应用场景:设计工具类

工具类优点:

工具类中的方法都是类方法,每个类方法都是用来完成一个功能的,提高代码的复用性。

实例方法需要创建对象来调用,此时对象只是为了调用方法,对象占内存,这样会浪费内存。使用类方法,直接用类名调用即可,调用方便,节省内存。

注意:工具类不需要创建对象, 建议将工具类的构造器私有化

注意:

1.类方法中可以直接访问类的成员,不可以直接访问实例成员。

2.实例方法中既可以直接访问类成员,也可以直接访问实例成员。

3.实例方法中可以出现this关键字,类方法中不可以出现this关键字的。

代码块

代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)。

静态代码块

格式:static { }

特点:类加载时自动执行,由于类只会加载一次,所以静态代码块只会执行一次

作用:完成类的初始化,例如:对类变量的初始化赋值

实例代码块

格式:{ }

特点:每次创建对象时执行实例代码块,并在构造器前执行

作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值

设计模式

设计模式就是具体问题的最优解决方案

单例设计模式:确保一个类只有一个对象。

分类:

饿汉式单例:拿对象时,对象早就创建好了。

写法:

1
2
3
1.把类的构造器私有。
2.定义一个类变量记住类的一个对象。
3.定义一个类方法,返回对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//单例类(饿汉式单例)
public class A {

//2.定义一个类变量记住类的一个对象。
private static A a = new A();

//1.私有类的构造器。
private A(){}

//3.定义一个类方法返回对象。
public static A getInstance(){
return a;
}
}
1
2
3
4
//使用枚举enum创建单例
public enum A{
X;//单例
}

应用场景:任务管理器对象获取运行时对象。可以避免浪费内存。

懒汉式单例:拿对象时,才开始创建对象。(延迟加载对象)

写法:

1
2
3
1.把类的构造器私有。
2.定义一个类变量用于存储对象。
3.提供一个类方法,保证返回的是同一个对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//单例类(懒汉式单例)
public class B {
//2.定义一个类变量用于存储对象。
public static B b;

//1.把类的构造器私有。
private B(){}

//3.提供一个类方法,保证返回的是同一个对象。
public static B getInstance(){
if(b == null){
b = new B();
}
return b;
}
}

继承extends

继承就是用extends关键字,让一个类和另一个类建立起一种父子关系。

子类能继承父类的非私有成员(成员变量、成员方法)。

子类的对象是由子类、父类共同完成的。

1
public class B extends A{}

继承的执行原理

权限修饰符

权限修饰符:限制类中的成员(成员变量、成员方法、构造器、代码块…)能够被访问的范围。

单继承、Object类

Java是单继承的:一个类只能继承一个直接父类;Java中的类不支持多继承,但是支持多层继承。

Object类是Java中所有类的祖宗。

方法重写

子类重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写

重写后方法的访问,Java会遵循就近原则

注意事项

1.使用@Override注解可以指定java编译器,检查方法重写的格式是否正确。

2.子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限( public > protected > 缺省 )。

3.重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小

4.私有方法、静态方法不能被重写,如果重写会报错的。

5.子类重写Object类的toString()方法,以便返回对象的内容。

子类访问其他成员(成员变量、成员方法)依照就近原则

1.先子类局部范围找。

2.然后子类成员范围找。

3.然后父类成员范围找,如果父类范围还没有找到则报错。

注意:

如果子父类中,出现了重名的成员,会优先使用子类的。可以通过super关键字,指定访问父类的成员:super.父类成员变量/父类成员方法

1
2
3
4
5
6
7
8
//父类
public class A {
String name = "父类name";

public void print() {
System.out.println("父类的print方法");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//子类
public class B extends A{
String name = "子类name";

public void showName(String name){
System.out.println(name);//局部变量
System.out.println(this.name);//子类成员变量
System.out.println(super.name);//父类成员变量
}

@Override
public void print(){
System.out.println("子类的print方法");
}

public void showMethod(){
print();//子类成员方法
super.print();//父类成员方法
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
B b = new B();
b.showName("外部");
b.showMethod();
}
}
//输出结果:
//外部
//子类name
//父类name
//子类的print方法
//父类的print方法

子类构造器

子类的全部构造器,都会先调用父类的构造器,再执行自己。

注意:

1.默认情况下,子类全部构造器的第一行代码都是super()(写不写都有) ,它先会调用父类的无参数构造器。

2.如果父类没有无参数构造器,则必须在子类构造器的第一行手写super(….),指定去调用父类的有参数构造器。

this()调用兄弟构造器

任意类的构造器中,是可以通过this(…) 去调用该类的其他构造器的。

注意:

this(…) 、super(…) 都只能放在构造器的第一行,因此,有了this(…)就不能写super(…)了,反之亦然。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Student {
String name;
int age;
String gender;

public Student() {
this("no one", 0, "no gender");
}

public Student(String name, int age){//只传入两个参数,可以使用this去调用该类的其他构造器
this(name,age,"女");
}

public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}

@Override
public String toString() {
return "姓名:"+name+", 年龄:"+age+", 性别:"+gender;
}
}

多态

多态是在继承/实现情况下的一种现象,表现为:对象多态行为多态

前提

继承/实现关系;存在父类引用子类对象;存在方法重写

注意:

多态是对象、行为的多态,Java中的属性(成员变量)没有多态

好处

1.在多态形式下,右边对象是解耦合的,即右边对象可以随时切换

2.定义方法时,使用父类类型的形参,可以接收一切子类对象

注意:

多态下不能使用子类的独有功能

类型转换

自动类型转换:父类 变量名 = new 子类(); People p1 = new Student();

强制类型转换:子类 变量名 = (子类) 父类变量; Student s1 = (Student) p1;

注意:

1.存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错。

1
2
People p1 = new Student(); //自动类型转换
Student s1 = (Student) p1;//强制类型转换

2.运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来。

3.强制类型转换之前,使用instanceof关键字,判断当前对象的真实类型,再进行强转。

1
2
3
4
5
6
7
8
//父类People
public class People {
public String name = "People";

public void run(){
System.out.println("人跑步");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//子类Student
public class Student extends People{
public String name = "Student";

@Override
public void run(){
System.out.println("学生跑步");
}

public void test(){
System.out.println("学生考试");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//子类Teacher
public class Teacher extends People{
public String name = "Teacher";

@Override
public void run(){
System.out.println("老师跑步");
}

public void teach(){
System.out.println("老师上课");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//测试
public class Test {
public static void main(String[] args) {
//1.在多态形式下,右边对象是解耦合的,即右边对象可以随时切换。
People p1 = new Student(); //自动类型转换
p1.run();//运行结果:学生跑步
// 编译看左边,运行看右边:编译时看左边p1是People类,People类有run方法可以编译通过,运行时执行右边Student类的run方法

System.out.println(p1.name);//运行结果:People
// 对于成员变量,编译看左边,运行看左边:编译时看左边p1是People类,运行时使用左边People类的成员变量

//p1.test(); //会报错,多态下无法直接调用子类的独有功能。

//Student s1 = p1;//会报错,需要强制类型转换
Student s1 = (Student) p1;//强制类型转换
s1.run();//运行结果:学生跑步

//Teacher t1 = (Teacher) p1;//运行时出现错误:java.lang.ClassCastException

People p2 = new Teacher();
p2.run();
System.out.println(p2.name);

//2.定义方法时,使用父类类型的形参,可以接收一切子类对象。
Student s = new Student();
go(s);
Teacher t = new Teacher();
go(t);
}
//2.定义方法时,使用父类类型的形参,可以接收一切子类对象。
public static void go(People p){
p.run();
if(p instanceof Student){
Student s = (Student) p;
s.test();
}else if(p instanceof Teacher){
Teacher t = (Teacher) p;
t.teach();
}
}
}

final

final关键字是最终的意思,可以修饰(类、方法、变量)

1.修饰类:该类被称为最终类,特点是不能被继承

2.修饰方法:该方法被称为最终方法,特点是不能被重写

3.修饰变量:该变量只能被赋值一次

1
2
3
4
5
6
7
final修饰变量:
一、局部变量:final int a; a = 12;
二、成员变量
1.静态成员变量:public static final double PI = "3.1415926";
//常量:public static final修饰的成员变量
2.实例成员变量:private final String name = "surourou";
//这种用法没有意义

注意:

1.final修饰基本类型的变量,变量存储的数据不能被改变

2.final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容可以改变

常量

static final修饰的成员变量就被称为常量。

执行原理

程序编译后,常量会被宏替换:出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能一样

抽象类

abstract修饰类,这个类就是抽象类

abstract修饰方法,这个方法就是抽象方法。抽象方法只有方法签名,不能有方法体。

1
2
3
4
public abstract class A {
// 抽象方法:必须abstract修饰,只有方法签名,不能有方法体。
public abstract void test();
}

注意:

1.抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。

2.类该有的成员(成员变量、方法、构造器)抽象类都可以有。

3.抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。

4.一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。

模板方法设计模式

解决方法中存在重复代码的问题。

实现方法:

1
2
3
4
1、定义一个抽象类。
2、在里面定义2个方法:
模板方法:把相同代码放里面去。
抽象方法:具体实现交给子类完成。

注意:

使用final关键字修饰模板方法。模板方法是给对象直接使用的,不能被子类重写。一旦子类重写了模板方法,模板方法就失效。

接口

关键字interface定义接口。

1
2
3
4
public interface A{
// 成员变量(常量)
// 成员方法(抽象方法)
}

注意:

1.接口里面的成员变量是常量,成员方法是抽象方法,只有这两种东西。

2.接口内不能有构造器和代码块static{},所以接口不能创造对象。接口是用来被类实现implements的,实现接口的类称为实现类

3.一个类可以实现多个接口(接口可以理解成干爹),实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。接口弥补了类单继承的不足。

1
2
//实现接口
public class A implements A1, A2, A3{}
1
2
//使用接口
A1 a = new A();

JDK8接口新增的三种方法

1.默认方法(实例方法):使用用default修饰,默认会被加上public修饰。只能使用接口的实现类对象调用。

2.私有方法:必须用private修饰(JDK 9开始才支持)。只能在接口内部调用。

3.类方法(静态方法):使用static修饰,默认会被加上public修饰。只能用接口名来调用。

1
2
3
4
5
6
7
8
9
10
11
12
public interface A{
// 1、默认方法(实例方法):使用用default修饰,默认会被加上public修饰
// 注意:只能使用接口的实现类对象调用
public default void test1(){}

//2、私有方法:必须用private修饰(JDK 9开始才支持)
private void test2(){}

//3、类方法(静态方法):使用static修饰,默认会被加上public修饰。
//注意:只能用接口名来调用。
public static void test3(){}
}

接口是多继承的

1
2
3
4
5
6
7
interface A{}
interface B{}
interface C{}

interface D extends A,B,C{}//接口是多继承

class E implements D{}//便于实现类去实现

注意:
1.一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。

1
2
3
4
5
6
7
8
interface I{
void test();
}
interface J{
String test();
}

//interface K extends I,J{}//I和J的test方法存在冲突,不支持多继承。

2.一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。

1
2
3
4
5
6
7
8
9
10
interface I{
void test();
}
interface J{
String test();
}E

//class E implements I,J{}//I和J的test方法存在冲突,不支持多实现。
//两个test函数名和参数一致,但是返回值不一致,会导致冲突。
//若是一个是void test();一个是void test(String s);则两个参数不一致,可以重写,则可以支持多实现。

3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Fu{
public void run(){
System.out.println("父类的run方法");
}
}

interface IT{
default void run(){
System.out.println("接口IT的run方法");
}
}

class Zi extends Fu implements IT{}//调用Zi的run方法时,会优先使用父类Fu的run方法
//class Zi implements IT extends Fu {}//错误,只能先继承父类再继承接口

4.一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface IT1{
default void test(){
System.out.println("IT1");
}
}

interface IT2{
default void test(){
System.out.println("IT2");
}
}

class N implements IT1, IT2{
@Override
public void test(){//重写test方法则不会发生冲突
System.out.println("class N");
}
}

内部类

成员内部类

1.成员内部类:类中的普通成员。成员内部类可以定义成员变量、构造器和成员方法。JDK16开始可以定义静态成员。

2.和实例方法一样,成员内部类的实例方法中,可以直接访问外部类的实例成员、静态成员

3.成员内部类可以在成员内部类的实例方法中,拿到当前外部类对象,格式是:外部类名.this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Outer {
private int age = 99;
public static String name;

//成员内部类
public class Inner{
private String name;
public static String gender;//JDK16之后才支持定义静态成员。
private int age = 88;

public void test(){
int age = 66;
System.out.println(age);//66
System.out.println(this.age);//88
System.out.println(Outer.this.age);//99
}
}
}
1
2
3
//创建对象的格式
Outer.Inner inner = new Outer().new Inner();
inner.test();//调用成员内部类的方法

静态内部类

静态内部类:有static修饰的内部类,属于外部类自己持有。

注意:静态内部类可以直接访问外部类的静态成员不可以直接访问外部类的实例成员。(和静态方法类似)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Outer {
private int age = 99;
public static String name = "Outer";

//静态内部类
public static class Inner{
public static String gender;//JDK16之后才支持定义静态成员。
private int a;

public void test(){
System.out.println(name);
//System.out.println(age);//错误,静态内部类不可以访问外部类的实例成员
}
}
}
1
2
3
//创建对象的格式
Outer.Inner inner = new Outer.Inner();
inner.test();//调用成员内部类的方法

局部内部类

局部内部类是定义在在方法中、代码块中、构造器等执行体中。

鸡肋语法。

匿名内部类

匿名内部类:一种特殊的局部内部类。匿名:指的是程序员不需要为这个类声明名字。

特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象

作用

1.用于更方便的创建一个子类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
//这个匿名内部类会编译成一个子类,然后立即创建出一个子类对象
Animal animal = new Animal() {
@Override
public void cry() {
System.out.println("猫在哭");
}
};
animal.cry();
}
}

abstract class Animal{
public abstract void cry();
}
1
2
3
4
5
6
7
8
9
//Java会编译出以下匿名内部类
class Test$1 extends Animal {
Test$1() {
}

public void cry() {
System.out.println("猫在哭");
}
}

2.作为一个参数传输给方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args) {
//匿名内部类作为一个参数传输给方法
//注意,这里new Swimming并不是new一个接口对象,而是一个匿名内部类
go(new Swimming() {
@Override
public void swim() {
System.out.println("狗在游泳");
}
});
}

public static void go(Swimming s){
s.swim();
}
}

interface Swimming{
void swim();
}

枚举

枚举是一种特殊类

枚举类中的第一行,只能写一些合法的标识符(名称),多个名称用逗号隔开。这些名称本质是常量,每个常量都会记住枚举类的一个对象

1
2
3
4
5
//枚举类
public enum A {
//枚举类的第一行必须罗列一些名称,这些名称都是常量,每个常量是一个枚举对象
X,Y,Z;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
A a1 = A.X;
System.out.println(a1);//X

//A a = new A();//报错,枚举类的构造器是私有的,不能对外创造对象

A[] as = A.values();//拿到枚举类的所有对象
for (int i = 0; i < as.length; i++) {
System.out.println(as[i]);//X Y Z
}

A a2 = A.valueOf("Y");//
System.out.println(a2.name());//枚举类对象的名称 'Y'
System.out.println(a2.ordinal());//枚举类对象的索引 1

下面是枚举类反编译的代码:

抽象枚举

在枚举类中定义抽象方法就是抽象枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//抽象枚举
public enum B {
//枚举类第一行枚举类抽象对象,而具有抽象方法无法new一个对象,所以需要重写
X {//这里用X{}或者X(){}进行重写都可以
@Override
public void go() {

}
},Y("surourou") {
@Override
public void go() {
System.out.println(this.getName()+"你好");
}
};
//在枚举类中定义抽象方法就是抽象枚举
public abstract void go();

private String name;
private B(){}
private B(String name){this.name=name;}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
1
2
B by = B.Y;
by.go();//运行结果:surourou你好

枚举类实现单例

1
2
3
public enum C {
X;//单例即一个类只有一个对象。
}

应用

枚举类用来表示一组信息,然后作为参数进行传输。

1
2
3
public enum Constant {
BOY, GIRL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
check(Constant.BOY);
check(Constant.GIRL);
}
public static void check(Constant sex){
switch (sex){
case Constant.BOY://或case BOY: 带不带Constant前缀都行
System.out.println("男孩");
break;
case Constant.GIRL:
System.out.println("女孩");
break;
}
}

泛型

定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>) ,称为泛型类、泛型接口,泛型方法,统称为泛型。如:ArrayList是泛型类。

本质:把具体的数据类型作为参数传给类型变量。

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//自定义泛型类
public class MyArrayList <E>{
private Object[] arr = new Object[10];
private int size = 0;

public boolean add (E e){
arr[size++] = e;
return true;
}

public E get(int index){
return (E) arr[index];
//return arr[index];//会报错,因为arr数组是Object类型,需要强转为E类型
}
}
1
2
3
4
5
6
7
//使用自定义泛型类
MyArrayList<String> list = new MyArrayList<>();
list.add("srr");
list.add("girl");
list.add("18");
String s = list.get(0);
System.out.println(s);

其他类型的泛型类定义:

1
2
class MyClass1<E, T>{}//可以放多个类型变量
class MyClass2<E extends Animal>{}//限制类型变量,要求E必须继承Animal类,或者为Animal类

泛型接口

1
2
3
4
5
6
//泛型接口
public interface Data<T> {
//public interface Data<T extends Animal> //要求T是Animal类或其子类
void add(T t);
ArrayList<T> getByName(String name);
}
1
2
3
4
5
6
7
8
9
10
11
12
//实现泛型接口
public class StudentData implements Data<Student>{
@Override
public void add(Student student) {

}

@Override
public ArrayList<Student> getByName(String name) {
return null;
}
}

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
String s1 = test("surourou");
System.out.println(s1);

Student st = test(new Student());
System.out.println(st);
}

public static <T> T test(T t){//泛型方法
//public static <T extends Student> T test(T t)//限定T是Student类或其子类
return t;
}

通配符?

通配符?:在使用泛型时可以代表一切类型

? extends Car代表上限,?只能是Car或者Car的子类。

? super Car代表下限,?只能是Car或者Car的父类。

1
2
public static void test2(ArrayList<? extends Student> t){}
public static void test2(ArrayList<?> t){}

泛型擦除

泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除

1
2
3
4
5
//源文件.java
ArrayList<String> a = new ArrayList<>();
a.add("srr");
a.add("girl");
String str = a.get(0);
1
2
3
4
5
6
//编译后的class文件
ArrayList a = new ArrayList();//a是Object类型
a.add("srr");
a.add("girl");
String str = (String)a.get(0);//编译器强转成String类型
//编译时进行了泛型擦除,底层是基于Object类型操作的

注意:泛型不支持基本数据类型,只能支持对象类型(引用数据类型)。

1
2
3
4
5
6
//ArrayList<int> a1 = new ArrayList<>();//错误,泛型不支持基本类型
//ArrayList<double> a2 = new ArrayList<>();//错误,泛型不支持基本类型
ArrayList<Integer> a3 = new ArrayList<>();
a3.add(1);
ArrayList<Double> a4 = new ArrayList<>();
a4.add(1.0);

Java面向对象
http://surourou8.github.io/2024/09/20/Java面向对象/
作者
Su Rourou
发布于
2024年9月20日
许可协议