概述
12.面向对象(高级)
类变量/类方法
关于静态变量在内存中的存储,主要和jdk的版本有关,jdk8以前放在方法区的静态域中,jdk8之后放在堆中(class对象)
注意:关于这一点会在后面的反射中再详细的学习,现在先了解一下即可
只要记住不管static变量在哪里:(1)static变量是同一个类所有对象共享(2)static类变量在类加载的时候就生成了
如何访问类变量/类方法
类名.类变量名(推荐) 或者 对象名.类变量名
修饰符 static 返回类型 类方法名 (形参) (推荐)或者 static 修饰符 返回类型 类方法名 (形参)
类变量/类方法的访问修饰符的访问权限和普通变量一样
小结:静态方法只能访问静态变量和静态方法,普通成员方法既可以访问静态成员也可以访问非静态成员
在IDEA中如何给main方法传递参数?
在命令行界面如何传递参数在第一章笔记Java概述讲过了,现在来讲一下在IDEA如何传递参数
第一步:点击Edit Configurations…
第二步:在Program arguments里填入参数即可,注意事项和命令行界面一样
对于访问修饰符,什么情况下该使用什么修饰符呢?
- 属性通常使用private封装起来
- 方法一般使用public用于被调用
- 会被子类继承的方法,通常使用protected
- 再就是作用范围最小原则,简单说,能用private就用private,不行就放大一级,用默认,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了。
注意:只是一般情况,最终还是要根据实际情况而定
代码块
基本语法:[修饰符] { 代码 };
- 修饰符可以不写,写的话只能写static
- 代码块分两种:静态代码块和非静态代码块
- 最后的;号可以省略(建议不省略)
代码块的调用顺序优先于构造器
相当于另一种形式的构造器(对构造器的补充机制),可以做初始化的操作,如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
使用细节:
-
静态代码块随着类的加载而执行并且只会执行一次(因为类也只会加载一次),如果是普通代码块,每创建一个对象,就执行一次
-
类什么时候被加载(重点):(1)创建对象实例时(2)创建子类对象实例,父类也会被加载(3)使用类的静态成员时
-
普通的代码块,在创建对象实例时,会被隐式的调用,被创建一次就会调用一次,如果只是使用类的静态成员时,普通代码块并不会执行
-
创建一个对象时,在一个类中的调用顺序是(重点):
(1)调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级是一样的,如果有多个则按它们的顺序调用)
(2)调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化调用的优先级是一样的,如果有多个则按它们的顺序调用)
(3)调用构造方法
-
构造器的最前面其实隐含了super()和调用普通代码块
-
创建一个子类对象时(继承关系),它们的调用顺序是(重点):
(1)父类的静态代码块和静态属性
(2)子类的静态代码块和静态属性
(3)父类的普通代码块和普通属性初始化
(4)父类的构造方法
(5)子类的普通代码块和普通属性初始化
(6)子类的构造方法
本质上就是先加载类信息(1和2),然后再创建父类对象(3和4),最后创建子类对象(5和6)
-
静态代码块只能调用静态成员,普通代码块可以调用任意成员
单例设计模式
饿汉式/懒汉式
实现步骤:
- 构造器私有化=>防止用户直接new
- 类的内部创建对象
- 向外暴露一个静态的公共方法 getInstance
两种模式的比较:
- 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(后面学习线程时会完善)
- 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式。
//饿汉式
class Cat {
private String name;
private static Cat cat = new Cat("小可爱");
private Cat(String name) {
this.name = name;
}
public static Cat getInstance() {
return cat;
}
}
//懒汉式
class Cat {
private String name;
private static Cat cat;
private Cat(String name) {
this.name = name;
}
public static Cat getInstance() {
if(cat == null){
cat = new Cat("小可爱");
}
return cat;
}
}
final关键字
一般在下面的情况可以使用final关键字:
- 当不希望类被继承时,可以用final修饰
- 当不希望父类的某个方法被子类覆盖/重写时,可以用final修饰
- 当不希望类的某个属性的值被修改时,可以用final修饰
- 当不希望某个局部变量被修改时,可以使用final修饰
使用细节:
- final修饰的属性又叫常量,一般用XX_XX_XX来命名
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:(1)定义时(2)在构造器中(3)在代码块中
- 如果final修饰的属性是静态的,则初始化的位置只能是:(1)定义时(2)在静态代码块中(不能在构造器中赋值)
- final类不能继承,但是可以实例化对象
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
- 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final
- final不能修饰构造方法(构造器)
- final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
- 包装类(Integer,Double,Float,Boolean等)都是final类,String类也是final类
//final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
class AA {
public final static int num = 100;
static {
System.out.println("AA 静态代码块");
}
}
//在main方法中
System.out.println("结果为:" + AA.num);
//输出结果为:100,不会输出静态代码块中的内容
结果为:100
抽象类
基本介绍:
- 原因:父类方法不确定性,即该方法在父类中实现没什么实际意义
- 抽象方法就是没有实现的方法,即没有方法体
- 当一个类中存在抽象方法,需要将该类声明为抽象类
- 一般来说,抽象方法会被继承,由其子类来实现抽象方法
使用细节:
- 抽象类不能被实例化
- 抽象类不一定要包含抽象方法,也就是说,抽象类可以没有抽象方法
- 一旦类包含了抽象方法,则这个类必须声明为abstract
- abstract只能修饰类和方法,不能修饰属性和其他的
- 抽象类可以有任意成员(抽象类的本质还是类),比如非抽象方法,构造器,静态属性等等
- 抽象方法不能有主体,即不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
- 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的
抽象模板模式
视频地址:https://www.bilibili.com/video/BV1fh411y7R8?p=402
接口
注意:
- 在jdk7.0前接口里的所有方法都没有方法体,即都是抽象方法。jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现,需要使用default关键字修饰
- 在接口中,抽象方法可以省略abstract关键字
public interface A {
public static void a() {
System.out.println("静态方法");
}
default public void b() {
System.out.println("默认方法");
}
}
使用细节:
- 接口不能被实例化
- 接口中所有方法是public方法,接口中抽象方法可以不用abstract修饰
- 一个普通类实现接口,就必须将该接口的所有方法都实现
- 抽象类实现接口,可以不用实现接口的方法
- 一个类同时可以实现多个接口
- 接口中的属性只能是final的,而且是public static final 修饰符,比如 int a = 1;实际上是 public static final int a = 1;
- 接口中属性的访问形式:接口名.属性名
- 接口不能继承其他类,但可以继承多个其他接口
- 接口的修饰符只能是public和默认,这点和类的修饰符是一样的
实现接口VS继承类
- 当子类继承了父类,就自动的拥有父类的功能,如果子类需要扩展功能,可以通过实现接口的方式扩展
- 继承的价值主要在于:解决代码的复用性和可维护性
- 接口的价值主要在于:设计好各种规范(方法),让其它类去实现
- 接口比继承更加灵活,继承是满足 is-a 的关系,接口需要满足 like-a 的关系
- 接口在一定程度上实现代码解耦
小结:实现接口机制 是对 Java单继承机制 的补充
接口的多态特性
- 多态参数:接口类型的引用变量可以指向实现了接口的类的对象
- 多态数组:接口类型数组
- 接口多态传递
interface IF {}
class A implements IF{}
class B implements IF{}
//多态参数:接口类型的引用变量可以指向实现了接口的类的对象
IF if01 = new A();
//多态数组:接口类型数组
IF[] arr = new IF[2];
arr[0] = new A();
arr[1] = new B();
//接口多态传递
//接口IQ继承了IG,类A实现了IQ接口
interface IG {}
interface IQ extends IG {}
class A implements IQ {}
//所以下面的是对的,相当于A类也实现了IG接口
IG ig = new A();
//验证方法:假如IG里有一个hi方法
interface IG {
public void hi();
}
//那么当A继承IQ时会提示你需要实现hi方法
内部类
类的五大成员:属性,构造器,方法,代码块,内部类
内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
注意:内部类是学习的难点和重点,看底层源码时,有大量的内部类
内部类的分类:四种
定义在外部类局部位置上(比如方法内):(1)局部内部类(有类名) (2)匿名内部类(没有类名,重点!!!)
定义在外部类的成员位置上:(1)成员内部类(没有用static修饰) (2)静态内部类(使用static修饰)
局部内部类
-
可以直接访问外部类的所有成员,包括私有的
-
不能添加访问修饰符,因为它的地位就是一个局部变量(本质还是一个类),但是可以使用final修饰
-
作用域:仅仅在定义它的方法或代码块中
-
访问方式:外部类在方法中,可以创建内部类对象,然后再访问
-
外部其他类不能访问局部内部类
-
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
public class Test {
public static void main(String[] args) {
Outer outer = new Outer(100);
outer.m1();
System.out.println("outer的哈希值为:" + outer);
}
}
class Outer {
private int n = 0;
public static int k = 10;
public Outer(int n) {
this.n = n;
}
public void m1() {
final class Inner {
private int n = 200;
public void f1() {
//Outer.this 的本质就是外部类的一个对象,哪个对象调用了m1()方法,就指向哪个对象
System.out.println("n = " + n);
System.out.println("n = " + Outer.this.n);//这里指向outer这个对象
System.out.println("Outer.this的哈希值为:" + Outer.this);
}
}
Inner inner = new Inner();
inner.f1();
}
{
System.out.println("hahahaha");
}
}
匿名内部类
核心:本质是类,也是内部类,该类没有名字(有系统取的名字),同时还是个对象
基本语法:new 类或接口(参数列表){ 类体 };
原因:某个类只使用一次,后面不再使用,可以使用匿名内部类来简化开发
视频地址:https://www.bilibili.com/video/BV1fh411y7R8?p=416
- 可以直接访问外部类的所有成员,包括私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量(本质还是一个类),但是可以使用final修饰
- 作用域:仅仅在定义它的方法或代码块中
- 访问方式:外部类在方法中,可以创建内部类对象,然后再访问
- 外部其他类不能访问匿名内部类
- 如果外部类和匿名内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.m1();
}
}
class Outer {
private int n = 100;
public void m1() {
//先以接口为例
//ie的编译类型是什么?IE
//ie的运行类型是什么?就是匿名内部类 Outer$1
/*
* 我们看底层 会分配类名 Outer$1
* class Outer$1 implement IE {
@Override
public void cry() {
System.out.println("呜呜呜呜呜");
}
* }
* */
IE ie = new IE(){
@Override
public void cry() {
System.out.println("呜呜呜呜呜");
}
};
ie.cry();
//可以验证(.getClass()方法可以返回对象的类型)
System.out.println("ie的运行类型为:" + ie.getClass());
//现在以类为例
//son的编译类型是什么?Son
//son的运行类型是什么?就是匿名内部类 Outer$2
/*
* 我们看底层 会分配类名 Outer$2
* class Outer$2 extends Son {
@Override
public void eat() {
System.out.println("你还吃,就知道吃");
}
* }
* */
//============================================
//注意和Son son = new Son("小胖");的区别!!!!!!
//============================================
//另外,假如Son是抽象类,则必须重写eat()方法
Son son = new Son("小胖"){
@Override
public void eat() {
System.out.println("你还吃,就知道吃");
}
};
son.eat();
//可以验证
System.out.println("son的运行类型为:" + son.getClass());
//还可以这种写法
Son son = new Son("小胖"){
@Override
public void eat() {
System.out.println("你还吃,就知道吃");
}
}.eat();//注意这里!!!
}
}
interface IE{
public void cry();
}
class Son {
private String name = "";
public Son(String name) {
this.name = name;
System.out.println("name = " + this.name);
}
public void eat(){
System.out.println("吃吃吃");
}
}
注:所以以后看见一个类的运行类型是XXX$X,大概率就是匿名内部类
成员内部类
-
可以直接访问外部类的所有成员,包括私有的
-
可以添加任意访问修饰符(四种都行),因为它的地位就是一个成员
-
作用域:和外部类的其他成员一样,为整个类体
-
访问方式:外部类在方法中,可以创建内部类对象,然后再访问
-
外部其他类可以访问成员内部类,有两种方式
方式一:外部类对象名.成员内部类名 引用名 = 外部类对象名.new 成员内部类名();
方式二:在外部类中编写一个方法,可以返回成员内部类对象(类似于单例模式的getInstance()方法)
-
如果外部类和成员内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
静态内部类
-
可以直接访问外部类的所有静态成员,包括私有的,但不能访问非静态成员
-
可以添加任意访问修饰符(四种都行),因为它的地位就是一个成员
-
作用域:和外部类的其他成员一样,为整个类体
-
访问方式:外部类在方法中,可以创建内部类对象,然后再访问
-
外部其他类可以访问静态内部类,有两种方式
方式一:外部类名.静态内部类名 引用名 = new 外部类名.静态内部类名();
方式二:在外部类中编写一个方法,可以返回静态内部类对象(可以将返回的方法也写成静态的)
-
如果外部类和匿名内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
最后
以上就是落后香氛为你收集整理的Java笔记——12.面向对象(高级)12.面向对象(高级)的全部内容,希望文章能够帮你解决Java笔记——12.面向对象(高级)12.面向对象(高级)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复