面向对象
对象
- 属性
- 行为
好处
-
模块性
-
信息隐藏
-
代码复用
类
继承
Inheritance
关键字:extents
接口
interface
关键字:implements
实现该接口的类必须重写接口的所有方法
包
package
Java包(package)详解 (biancheng.net)
相当于一个文件夹,里面有很多文件或子文件夹
导入关键字 import 建议用那个类就导入那个类,也可以导入*
package语句要放在第一行,且只能有一个,前面可以有注释
import语句在package和类之间,可以有多个,且没有顺序要求
命名:全部小写
公司名.项目名.模块名
类和对象
访问修饰符
access modifiers
访问权限 | 修饰符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开的 | public | yes | yes | yes | yes |
受保护的 | protected | yes | yes | yes | no |
默认的 | 不写(default) | yes | yes | no | no |
私有的 | private | yes | no | no | no |
用于修饰类的 方法 和属性,以及类的访问权限
- 外部类只能是public 或default
- default是默认的,不能写出来,否则会报错
命名
- 类名的第一个字母应该是大写的
- 方法的第一个单词应该是动词,小驼峰
属性
初始化
可以在生命属性的时候赋予初始值,但是在逻辑处理方面具有局限性,可以在构造函数中赋值
方法
重载
方法名相同,参数列表不同
不能仅仅依据修饰符或者返回类型的不同来重载方法。
重载方法应该少用,因为它们会使代码的可读性大大降低
构造函数
constructors
-
构造函数没有返回值,有访问修饰符
-
构造函数可以有多个
-
如果没有编写构造函数,编译器会自动创建一个无参构造函数
这个缺省的无参构造函数会调用父类的无参构造函数
如果没有显式指定父类,则都有一个隐式的父类Object类
参数
可变参数
可变长参数
如果不确定有多少个参数
可以在最后一个参数的类型后面添加一个省略号(三个点,…) ,然后添加一个空格和参数名。然后可以使用该参数的任意数量(包括无)调用该方法。
可变参数可以保证无法传入null,因为传入0个参数时,接收到的实际值是一个空数组而不是null。
public class Test {
public static void main(String[] args) {
int[] a={1,2,3,4,5};
testVarargs(1,2,3);//123
testVarargs(a);//12345
}
public static void testVarargs(int... varargs){
for (int i = 0; i < varargs.length; i++) {
System.out.print(varargs[i]);
}
}
}
不用提前创建数组了
如果与重载方法冲突,会优先匹配参数固定的方法
值传递和引用传递
- 值调用(call by value): 在参数传递过程中,形参和实参占用了两个完全不同的内存空间。形参所存储的内容是实参存储内容的一份拷贝。实际上,Java对象的传递就符合这个定义,只不过形参和实参所储存的内容并不是常规意义上的变量值,而是变量的地址。
Java 只有值传递
- 对于基本数据类型,就是值的拷贝,在方法内修改,并不影响方法外面
- 对于引用类型的参数,也是值的拷贝,不过这个值是对象的地址,在方法内修改,会影响到方法外面
- 引用调用(call by reference) : 在参数传递的过程中,形参和实参完全是同一块内存空间,两者不分彼此。 实际上,形参名和实参名只是编程中的不同符号,在程序运行过程中,内存中存储的空间才是最重要的。不同的变量名并不能说明占用的内存存储空间不同。
实例化
Point originOne = new Point(23, 94);
必须使用new来创建对象
new运算符通过为新对象分配内存并返回对该内存的引用来实例化类。new运算符还调用对象构造函数。
新运算符返回的引用不必分配给变量。它也可以直接用在表达式中。例如:
int height = new Rectangle().height;
请注意,在执行这个语句之后,程序不再有对创建的 Rectangle 的引用,因为程序从来不在任何地方存储引用。该对象未被引用,其资源可以由 Java 虚拟机自由回收。
使用对象
在类内可以直接使用属性
在类外需要跟上一个点
垃圾收集器
一些面向对象的语言要求您跟踪您创建的所有对象,并在不再需要它们时显式地销毁它们。显式地管理内存是冗长而且容易出错的。
Java 平台允许您创建任意多的对象(当然,受到系统可以处理的内容的限制) ,而且您不必担心破坏它们。当确定对象不再被使用时,JRE 就会删除它们。这个过程称为垃圾收集。
如果没有对该对象的更多引用,则该对象有资格进行垃圾收集。当变量超出作用域时,保存在变量中的引用通常会被删除。或者,可以通过将变量设置为特殊值 null 来显式删除对象引用。请记住,一个程序可以对同一个对象有多个引用; 在该对象符合垃圾收集条件之前,必须删除对该对象的所有引用。
this
在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。
因此,通过this.field就可以访问当前实例的字段。
如果没有命名冲突,可以省略this。
class Person{
public String name;
public int age;
Person(String name,int age){
this.name=name;//这里的this指向当前的对象
this.age=age;
}
Person(){
//在一个构造函数中调用另一个构造函数,形式:this(参数列表),而且必须在第一行
this("张三",18);
}
public void run(){
System.out.println("跑");
}
public void eat(){
this.run();//可以通过this调用类的其他方法
System.out.println("吃");
}
}
Static
-
关键字:static
-
静态变量又称类变量;静态方法又称类方法
-
静态变量不属于具体的某个对象,是所有的对象共有
-
内存:类的静态变量,jdk8(包含jdk8)之后,存在堆中;之前存在方法区的静态域中
-
产生:类变量和类方法在类加载的时候就已经生成了
-
访问:类名.静态变量名(推荐);对象名.静态变量名
类名.静态方法名(推荐);对象名.静态方法名
- 静态方法只能访问静态变量,类方法中无this的参数
- 非静态方法可以访问所有的成员
- 静态方法的应用场景:当方法中不涉及任何和对象相关的成员,可以设计成静态方法来提高开发效率。比如工具类中的utils
常量
1.常量的值不可以改变
2.定义变量的时候,如果加上final修饰符,这个变量就变成了常量:
经常和static搭配 static final double PI = 3.141592653589793;
3.根据习惯,常量名通常全部大写,多个单词之间用下滑线分割
可以修饰类,属性,方法,局部变量
-
修饰类,类不能被继承
该类不能继承,但是可以实例化对象
也没必再将方法修饰成final
-
修饰父类的方法,则子类不能重写父类的该方法
子类不能重写,但是可以使用
-
修饰属性,则不能被修改,又称常量
一般用大写字母 和 _ 来命名:;例如 TOTAL_NUMBER
必须在定义时,或者构造器中,或者代码块中,赋初值
如果final修饰的属性是静态的,则只能在定义时,或着在静态代码块中赋初值,不能在构造器中
-
修饰局部变量,则不能被修改,称为局部常量
注意事项
-
final 不能修饰构造器
-
final和static搭配使用效率更高,编译器底层做了优化
两者顺序可以颠倒
-
包装类(Integetr,Doube ,Float ,Blooean)都是final类型,String也是final类型
不是基本数据类型
代码块
代码块的基本介绍
-
代码化块,又称初始化块
-
没有名字,参数,返回值
-
基本格式 [static] { 代码语句};
-
最后的分号可写可不写
代码块的分类
- 代码块只有一个可以选择的修饰符:static
- 带有static的是静态代码块;不带的是普通代码块
代码块的执行
- 代码块不通过类或对象显式调用,而是隐式调用
- 静态代码块实在类被加载的时候执行,且只被执行一次
- 普通代码块是在每创建一次对象的时候,就被执行一次
静态代码块,普通代码块,构造器的执行顺序
- 创建对象需要先加载类的信息,因此静态代码块会先执行,同时静态属性初始化
静态属性和静态代码块属于同一等级,同时存在时,执行顺序根据定义时的顺序来
- 然后是执行构造器,
但是构造器的第一行是调用父类的构造器,
然后默认隐含执行父类普通代码块,和普通属性的初始化
普通属性和普通代码块属于同一等级,同时存在时,执行顺序根据定义时的顺序来最后才是构造器的代码
类的加载
- 当创建该类的对象的时候,类被加载
- 子类创建对象时候,父类会被先加载,子类后被加载
- 当使用静态的成员或方法时,类会被加载
代码块的用处
- 相当于是对构造器的补充
- 提高代码的复用
- Java 编译器将初始化器块复制到每个构造函数中。因此,可以使用此方法在多个构造函数之间共享一个代码块。
当存在继承关系的时候
- 程序的执行是先加载类的信息:
先加载父类的信息,因此先执行父类的静态代码块和静态属性的初始化
在加载子类的信息,因此再执行子类的静态代码块和静态属性的初始化
- 然后执行子类的构造器
但是第一行是调用父类的构造器,
因此接着执行父类的执行父类的普通代码块和普通属性的初始化,再执行父类的构造器
最后执行子类的普通代码块和普通属性的初始化,子类的构造器
注意事项
- 静态代码块只能调用静态属性和静态方法
- 普通代码块可以调用任意属性和任意方法
静态初始化块
static{
}
一个类可以有任意数量的静态初始化块,它们可以出现在类体的任何位置。
运行时系统保证按照静态初始化块在源代码中出现的顺序调用它们。
嵌套类
嵌套类分为两类: 非静态类和静态类
非静态的嵌套类又被称为内部类
内部类又分为
- 在一个类(外部类)中直接定义的内部类;
- 在一个方法(外部类的方法)中定义的内部类;
- 匿名内部类。
静态嵌套类
- 用static修饰
- 可以访问外部类的所有静态成员,不可以访问非静态成员
- 当外部类和把内部类重名时,默认遵守就近原则;调用外部类属性的方法:外部类名.外部类属性名//不用写this,是因为static可以访问的都是静态属性,可以通过类名直接访问
public class Test {
public static void main(String[] args) {
//外部其他类访问静态内部类
//通过类名直接创建,前提是可以访问
Outer.Inner inner = new Outer.Inner();
//在外部类中编写一个方法,返回静态内部类实例
Outer.Inner inner2 = Outer.getInner();
}
}
class Outer{
static class Inner{
public void funcInner(){
System.out.println("成员内部类的方法");
}
}
public static Inner getInner(){//将其声明为静态,可以通过类名直接访问
Inner inner = new Inner();
return inner;
}
}
内部类
不能定义或声明静态成员
成员内部类
-
可以访问外部类的所有成员
-
可以添加任意访问修饰符
-
作用域在类内,在成员方法中创建实例
-
当外部类和把内部类重名时,默认遵守就近原则;调用外部类属性的方法:外部类名.this.外部类属性名//谁调用该方法或代码块,则this指向谁
-
访问方式
public class Test { public static void main(String[] args) { //通过外部类的方法,来创建调用成员内部类 Outer outer1 = new Outer(); outer1.func(); //直接创建成员内部类,通过外部类的对象 Outer.Inner inner = outer1.new Inner(); //通过外部类的方法返回一个内部类 Outer.Inner inner2 = outer1.getInner(); Outer.Inner inner3 = new Outer().getInner(); } } class Outer{ private int age; class Inner{ public void funcInner(){ System.out.println("成员内部类的方法"); } } public void func(){ Inner in=new Inner(); in.funcInner(); } public Inner getInner(){ Inner inner=new Inner(); return inner; } }
局部类
可以在方法体中定义类
局部内部类(有名字)
-
可以访问外部类的所有成员
-
不能添加访问修饰符,但是可以使用final 修饰符
-
作用域仅在定义它的方法或代码块内
-
当外部类和把内部类重名时,默认遵守就近原则;调用外部类属性的方法:外部类名.this.外部类属性名//谁调用该方法或代码块,则this指向谁
-
public class Main { public void func(){ class Inner{//内部类 } } }
匿名内部类(没有名字)
- 匿名内部类是在方法或代码块中编写一个内部类并创建一个实例
- 且只能创建一个对象.匿名内部类使用一次,就不能再使用
- 匿名并不是没有名字,系统会自动分配名字。
- 不能添加访问修饰符,因为它的地位就是一个局部变量
- 作用域:仅仅在定义它的方法或代码块中
public class Test {
public static void main(String[] args) {
}
}
class Outer{//外部类
private int n1 = 10;//属性
public void method() {//方法
//1. 匿名内部类是在方法或代码块中编写一个内部类并创建一个对象,
// 且只能创建一个对象.匿名内部类使用一次,就不能再使用
//2. 匿名并不是没有名字,系统会自动分配名字。
//3. 我们要根据它所实现的接口 或者 继承的类的名字来创建对象
// ( 这里并不是根据接口或者抽象类来实例化对象)
//<1>基于实现接口的匿名内部类
/*
底层 会分配 类名 Outer04$1
class Outer$1 implements Interface1 {
@Override
public void fun() {
System.out.println("fun");
}
}
*/
Interface1 inner = new Interface1() {
@Override
public void fun() {
System.out.println("fun");
}
};
//调用1
inner.fun();
//调用2
new Interface1() {
@Override
public void fun() {
System.out.println("fun");
}
}.fun();
//查看对象的运行类型: 对象名.getClass()
System.out.println("inner的运行类型=" + inner.getClass());
//<2>基于父类的匿名内部类
/*
class Outer$2 extends A{
@Override
void fun1(){
System.out.println("匿名内部类重写了父类");
}
}
*/
A a =new A(){
@Override
public void fun1(){
System.out.println("匿名内部类重写了父类");
}
};//这是创建对象的语句,因此最后要有 ;
//基于抽象类的匿名内部类
B b =new B(){
public void fun2(){
System.out.println("匿名内部类定义了fun2");
}
};
}
}
interface Interface1{//接口
void fun();
}
class A {//类
public void fun1(){
System.out.println("父类的fun");
}
}
abstract class B{//抽象类
abstract void fun2();
}
好处
- 如果一个类只对另一个类有用,那么将它嵌入到该类中并将两个类放在一起是合乎逻辑的。
- 更易读和维护
- 提高了封装性
对象在内存中的形式
【零基础 快速学Java】韩顺平 零基础30天学会Java_哔哩哔哩_bilibili
- 加载类的信息,在方法区中,执行静态代码块,静态属性(只会执行一次)
- 在堆中分配空间,进行默认初始化,
- 执行普通属性,普通代码块,给属性显式初始化(创建一次,调用一次)
- 把地址赋给对象cat
- 执行构造器
- 进行指定初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8n7dvB7z-1676812627809)(imgs/image-20220513213213157.png)]
枚举
自定义枚举
public class Main2 {
public static void main(String[] args) {
//调用,这样不能自己new对象
Season autumn = Season.AUTUMN;
}
}
class Season{
private String name;
private String desc;
//自定义枚举
//枚举一般大写,static与final连用,优化底层机制
public final static Season SPRING =new Season("春天","温暖的");
public final static Season WINTER =new Season("冬天","寒冷的");
public final static Season AUTUMN =new Season("秋天","凉爽的");
public final static Season SUMMER =new Season("夏天","炎热的");
//先将构造器私有化
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
}
enum
public class Main2 {
public static void main(String[] args) {
//调用,这样不能自己new对象
Season autumn = Season.AUTUMN;
}
}
enum Season{//把class 换成enum
/*
1. 实例化常量放在第一行,多个常量中间用逗号间隔
2. 具体格式如下所示 对象名(参数列表)
*/
SPRING("春天","温暖的"),SUMMER("夏天","炎热的"),
AUTUMN("秋天","凉爽的"),WINTER("冬天","寒冷的");
//上面的权限和这句话是一样的,
//public final static Season SPRING =new Season("春天","温暖的");
private String name;
private String desc;
//先将构造器私有化,enum默认的控制权限时 default
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
}