面向对象2
1.静态 static关键字
- 使用范围:在Java类中, 可用static修饰属性、 方法、 代码块、 内部类
- 被修饰后的成员具备以下特点:
-
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
- 类方法,类中使用static修饰的方法
-
- 没有对象的实例时,可以用**类名.方法名()**的形式访问由static修饰的类方法。
- 在static方法内部只能访问类的static修饰的属性或方法, 不能访问类的非static的结构
- 因为不需要实例就可以访问static方法,因此static方法内部不能有this。 (也不能有super ? YES!)
- static修饰的方法不能被重写
案例:
Chinese类
package com.example.java.day6;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-10:14
* @PACKAGE_NAME com.example.java.day6
*/
/**
*@ClassName:Chinese
*@author weixu
*@date 2023/7/24 10:14
*/
public class Chinese {
private String name;
private int age;
public static String nation = "大中国";
public void f1() {
System.out.println("f1");
}
public static void f2() {
System.out.println("f2");
}
//自我介绍
public static void show() {
//在静态方法中,不能使用this,super
//静态方法中,只能使用静态变量
System.out.println("自我介绍" + nation);
f2();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
StaticTest类:
public class StaticTest {
public static void main(String[] args) {
Chinese malong = new Chinese();
Chinese yaoming = new Chinese();
malong.setName("马龙");
yaoming.setName("姚明");
malong.setAge(30);
System.out.println(Chinese.nation);//大中国 类中原来的静态变量为大中国
malong.nation = "中国";
System.out.println(Chinese.nation);//中国 //
yaoming.nation = "美国";
System.out.println(Chinese.nation);//美国
yaoming.setAge(40);
System.out.println(malong.getName()+" "+malong.getAge()+" "+Chinese.nation);//马龙 30 美国
//调用静态方法
Chinese.show();
}
}
2.单例模式
- 设计模式:程序在某一个场景的,设计和开发套路,比如围棋和象棋套路
- 单例模式:要求程序中某一个组件,在程序运行的整个生命周期中,只有一个实例
-
- **构造函数私有,**外面的组件不能主动创建这个对象
- **提供静态方法返回对象实例,**返回的是静态实例(静态的特性),所以只有一个实例
1.饿汉式单例模式
- 好处:线程安全的
- 坏处:加载时,时间比较长 (实例在类加载时就被创建。如果实例的初始化过程较为复杂,需要执行大量的初始化操作或者依赖其他资源,就会导致类加载的时间较长。)
public class EagerSingleton {
private EagerSingleton()
{
System.out.println("饿汉式单例模式");
}
private static EagerSingleton instance = new EagerSingleton();//因为静态的特性只有一份存在方法区,所以这个对象一定只有一个
public static EagerSingleton getInstance()
{
return instance;
}
}
2.懒汉式
存在线程安全问题,没有静态初始化
public class LazySingleton {
private LazySingleton()
{
System.out.println("懒汉式单例模式");
}
private static LazySingleton instance = null;
public static LazySingleton getInstance()
{
if (instance == null)
{
instance = new LazySingleton();
}
return instance;
}
}
测试:
public class SingletonTest {
//单例模式
public static void main(String[] args) {
//1.饿汉式 好处:线程安全的 坏处:加载时,时间比较长
EagerSingleton s1 = EagerSingleton.getInstance();
EagerSingleton s2 = EagerSingleton.getInstance();
EagerSingleton s3 = EagerSingleton.getInstance();
//2.懒汉式 存在线程安全问题,没有静态初始化
LazySingleton s4 = LazySingleton.getInstance();
LazySingleton s5 = LazySingleton.getInstance();
}
}
懒汉式线程不安全的原因:
- 多线程竞争:在懒汉式单例模式中,当多个线程同时调用
getInstance()
方法,而此时实例尚未创建(instance
为 null),它们会同时通过了创建实例的条件判断,然后各自创建一个实例,导致多个实例存在。 - 未加同步控制:在最简单的懒汉式实现中,
getInstance()
方法没有加上同步控制,这意味着多个线程可以同时进入该方法,从而同时创建多个实例。例如:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
// 私有构造函数,防止其他类直接实例化
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton(); // 多个线程可能同时执行到这里,创建多个实例
}
return instance;
}
}
解决方案:
为了解决懒汉式的线程安全问题,可以在 getInstance()
方法上加上同步关键字 synchronized
,确保在同一时刻只能有一个线程进入该方法,从而避免了多线程并发创建多个实例的情况。例如:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
// 私有构造函数,防止其他类直接实例化
}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
3.代码块和初始化顺序
对象初始化顺序:
父类静态代码块->子类静态代码块->父类初始化代码块->父类构造函数->子类初始化代码块->子类构造函数
Parent类:
package com.example.java.day6.Initialize;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-14:11
* @PACKAGE_NAME com.example.java.day6
*/
/**
*@ClassName:Parent
*@author weixu
*@date 2023/7/24 14:11
*/
public class Parent {
int age = 10;//初始化
//初始化代码块
{
System.out.println("父类的初始化代码块1");
}
{
System.out.println("父类的初始化代码块2");
}
//静态初始化代码块
static
{
System.out.println("父类的静态初始化代码块1");
}
static
{
System.out.println("父类的静态初始化代码块2");
}
//构造函数初始化
public Parent()
{
System.out.println("父类的构造函数初始化");
}
}
Children类:
package com.example.java.day6.Initialize;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-14:11
* @PACKAGE_NAME com.example.java.day6
*/
/**
*@ClassName:Children
*@author weixu
*@date 2023/7/24 14:11
*/
public class Children extends Parent{
int age = 20;
//初始化代码块
{
System.out.println("子类的初始化代码块1");
}
{
System.out.println("子类的初始化代码块2");
}
static
{
System.out.println("子类的静态初始化代码块1");
}
static
{
System.out.println("子类的静态初始化代码块2");
}
public Children()
{
System.out.println("子类的构造函数初始化");
}
}
InitializeTest类:
package com.example.java.day6.Initialize;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-14:10
* @PACKAGE_NAME com.example.java.day6
*/
/**
*@ClassName:InitializeTest
*@author weixu
*@date 2023/7/24 14:10
*/
//代码初始化 以及初始化顺序 父类静态代码块->子类静态代码块->父类初始化代码块->父类构造函数->子类初始化代码块->子类构造函数
public class InitializeTest {
public static void main(String[] args) {
Children children = new Children();
}
}
4.final修饰词
- 修饰类,表示类不能被继承
- 修饰方法:表示不能被重写
- 修饰成员变量,表示变量不能被改变
//public final class ATest //final修饰的类无法被继承
public class ATest {
final int age = 10;//final修饰的变量,只能赋值一次 无法再改变
// final void test()
// {
// System.out.println("A");
// }
void test()
{
System.out.println("A");
}
}
class BTest extends ATest
{
//无法重写父类被final修饰的方法
@Override
void test() {
System.out.println("B");
}
void f1()
{
super.test();//final修饰的方法无法被重写,但是可以被调用
this.test();
//super.age = 10;//final修饰的变量,只能赋值一次 无法再改变
}
}
5.抽象类
- 用abstract关键字来修饰一个类, 这个类叫做抽象类。
- 用abstract来修饰一个方法, 该方法叫做抽象方法。抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如: public abstract void talk();
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
ATest类(抽象类):
package com.example.java.day6.abstract_test;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-15:26
* @PACKAGE_NAME com.example.java.day6.abstract_test
*/
/**
*@ClassName:ATest
*@author weixu
*@date 2023/7/24 15:26
*/
public abstract class ATest {
//抽象类中没有方法体的方法就是抽象方法
public abstract void test();
//与接口不同的是抽象类中可以有非抽象方法
public void f1()
{
System.out.println("A");
}
}
BTest类(ATest类的子类):
package com.example.java.day6.abstract_test;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-15:26
* @PACKAGE_NAME com.example.java.day6.abstract_test
*/
/**
*@ClassName:BTest
*@author weixu
*@date 2023/7/24 15:26
*/
public class BTest extends ATest {
//void BTest默认为public abstract void BTest();
void BTest()
{
System.out.println("B");
}
//父类为抽象类,子类必须重写父类的抽象方法
@Override
public void test() {
System.out.println("B");
}
}
Test类(测试类):
package com.example.java.day6.abstract_test;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-15:43
* @PACKAGE_NAME com.example.java.day6.abstract_test
*/
/**
*@ClassName:Test
*@author weixu
*@date 2023/7/24 15:43
*/
public class Test {
public static void main(String[] args) {
//new ATest(); //抽象类不能直接实例化
new BTest();
}
}
6.接口
概念:
- 一方面, 有时必须从几个类中派生出一个子类, 继承它们所有的属性和方法。 但是, 由于Java不支持多重继承。 有了接口, 就可以得到多重继承的效果。
- 另一方面, 有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、 MP3机、手机、数码相机、移动硬盘等都支持USB连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。 继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
Usb接口:
//接口中的方法都是抽象方法 不能有方法体
public interface Usb {
void start();
void stop();
}
Typc接口:
public interface Typc {
void transfer();
}
Phone类(接口实现类):
//1.实现类可以实现多个接口 2.实现类需要实现接口中的所有方法
public class Phone implements Usb,Typc{
public static void main(String[] args) {
System.out.println("手机");
}
@Override
public void transfer() {
System.out.println("手机可以传输数据");
}
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机停止工作");
}
}
小节:接口和抽象的区别
- 抽象类可以有非抽象方法,接口只能有抽象方法
- 接口中方法默认
public abstract
- 接口可以实现多继承,抽象类不可以
7. 内部类和匿名内部类
7.1 内部类
java中可以把一个java类放到另一java类的内部,这个类成为内部类
内部类的分类:
- 成员变量位置,成员内部类
-
- static成员内部类
- 非静态的成员内部类
- 方法中,局部内部类
语法:
- 修饰成员变量的所有的修饰符都可以修饰成员内部类
- 内部类可以使用继承
案例:
Person类:
package com.example.java.day6.inner_class_test;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-16:38
* @PACKAGE_NAME com.example.java.day6.inner_class_test
*/
/**
*@ClassName:Person
*@author weixu
*@date 2023/7/24 16:38
*/
public class Person {
private String name = "张三";
private int age = 20;
//1.静态内部类
//内部类,如果一个类只给当前外部类使用,别的组件不用,比如Dog和Cat类只是在Person中使用
//所有的成员变量的修饰符,都可以修饰内部类
public static class Dog
{
public void eat()
{
System.out.println("狗吃骨头");
}
}
//2.内部类
public class Cat
{
public void eat()
{
System.out.println("猫吃鱼");
}
}
//3.成员方法内部类
public void f1()
{
class Bird
{
public void eat()
{
System.out.println("鸟吃虫子");
}
}
}
}
7.2 匿名内部类
- 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
- 匿名内部类的特点
-
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
- 语法new 接口(类){实现};
案例:
Usb接口:
package com.example.java.day6.inner_class_test;
/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-15:22
* @PACKAGE_NAME com.example.java.day6.interface_test
*/
//接口中的方法都是抽象方法 不能有方法体
public interface Usb {
//接口方法默认是public abstract
void start();
void stop();
}
Typc接口:
package com.example.java.day6.inner_class_test;
/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-15:22
* @PACKAGE_NAME com.example.java.day6.interface_test
*/
public interface Typc {
//接口方法默认是public abstract
void transfer();
}
Test1类:
package com.example.java.day6.inner_class_test;/**
* @User HASEE
* @Author WeiXu
* @Createtime 2023/7/24-24-16:35
* @PACKAGE_NAME com.example.java.day6.inner_class_test
*/
/**
*@ClassName:Test1
*@author weixu
*@date 2023/7/24 16:35
*/
public class Test1
{
public static void main(String[] args) {
//创建静态成员内部类的方法
Person.Dog dog = new Person.Dog();//静态内部类的创建方式 静态的是属于类本身的而不是对象的
dog.eat();
//创建非静态成员内部类的方法
Person person = new Person();
Person.Cat cat = person.new Cat();//非静态内部类的创建方式 非静态的是属于对象的而不是类本身的 因此需要先创建对象 再创建内部类
cat.eat();
/**
* 1:多态性:编译类型是父类
* 2:new 接口(类),后面跟一个实现
* 3:有一个对象usb1,实现了Usb接口的一个没有名字的类的实例
* Test$1->实现了Usb接口->用这个类产生一个对象usb1
*/
//方式1:匿名内部类 格式:Usb usb1 = new Usb()[...]
Usb usb1 = new Usb() {
@Override
public void start() {
System.out.println("手机启动了");
}
@Override
public void stop() {
System.out.println("手机关机了");
}
};
f1(usb1);
//方式2:匿名内部类直接作为参数 格式f2(new 接口[...])
f2(new Typc() {
@Override
public void transfer() {
System.out.println("TypeC接口的传输功能");
}
});
}
public static void f1(Usb usb) {
usb.start();
usb.stop();
}
public static void f2(Typc typeC) {
typeC.transfer();
}
}