文章目录
Java 类和对象
区分面向过程与面向对象
- 面向过程:将一个问题按步骤列举,然后一个人从第一步做到最后一步(从始至终一个人)
- 面向对象:将一个问题分解成若干个问题,每个对象负责一个或多个问题,最后也达到解决问题的效果(多个人共同完成)
类的定义与使用
一个类就像是根据图纸造房子(一张图纸可以造成多个房子,一个类也可以实例化多个对象,每个对象都有自己的空间)
定义
类前面的修饰符只能是
默认
或public
,且一个文件中只能有一个public修饰的类
// 创建类:类名一般采用大驼峰命名
public class ClassName{
field; // 字段(属性)
method; // 行为
}
使用
public class PetDog {
// 属性
public String name;//名字
public String color;//颜色
// 行为
public void barks() {
System.out.println(name + ": 旺旺旺~~~");
}
public void wag() {
System.out.println(name + ": 摇尾巴~~~");
}
}
对象实例化
通过 new 实例化对象(会在堆上给对象分配空间)
public Main {
public void static main(String[] args) {
PetDog dog1 = new PetDog(); // 对象实例化
PetDog dog2 = new PetDog();
// 对象.方法名/属性名 进行访问对象中的方法和属性
dog1.barks();
dog1.name;
}
}
一个简略图,实例化时,引用变量会引用堆上开辟好的一块空间地址(像成员变量就存储在堆上,每个对象的成员变量是自己独有的;而方法和类的某些属性,是共享的,存在方法区,static处详细讲)
this 引用
当类中方法接收参数的时候,若形参名字和成员变量名字相同,会优先使用形参,这个时候就需要用this来区分了,this其实就是类方法形参中的第一个参数(默认可以不写:类名 this),所以建议使用成员变量的时候都加 this
this总结
- this 就是当前对象的引用 ---- 谁调用方法或字段就是谁
- 当方法参数和字段名字相同时,可以以此来区分
- this 是一个隐藏参数(第一个参数) – 看上面的图
- 访问成员变量时,建议加上 this
- this 只能在成员方法中使用,不能在静态方法中,static中会讲为什么
- this(); 调用构造方法,只能放在构造方法中
实例化过程
- 加载 类对应的字节码文件
- 在堆上分配内存空间
- 若有默认初始化,进行默认初始化(
定义类的时候进行的赋值
) - 调用合适的构造方法(构造方法可以重载)
- 返回堆上初始化好的空间的地址
构造方法
构造方法的名字与类名相同,但没有返回值
若自己不写,系统会自动提供一个 不带参数的空构造方法
若自己实现了一个或多个构造方法时,系统就不会自动提供了
// 空构造方法(自己没有实现。系统会提供一个空的构造方法)
public PetDog() {
this("小黑"); // 可以在构造方法中,调用其他构造方法(1.必须第一行;2. 不能形成环)
}
// 带一个参数的构造方法
public PetDog(String name) {
this.name = name; // this后面有讲解
}
方法的重载
上面两个构造方法就可以同时存在代码中,这就构成了方法的重载
- 方法名相同
- 参数列表不同
- 返回值不做要求
方法签名
经过编译器编译修改过之后方法最终的名字。具体方式:
方法全路径名+参数列表+返回值类型
,构成方法完整的名字(就是为了区分重载的方法)
四大访问权限
不同的只是访问权限不同(限制使用范围)
- privte(私有权限)
- 默认权限(也叫做:包访问权限)
- protected:一般建立在继承的基础上使用(继承中讲解)
- public(公共权限)
作用范围
public
public的访问权限最大,在哪里都可以访问,因此及其不安全,在后面的开发过程中还是需要合理的使用
访问修饰符
private
Java面向对象的封装机制,主要体现在private 关键字上
运用private关键字对类的实现细节进行隐藏,类中提供公开的接口来间接访问 private修饰的属性,这种机制就叫做 “封装”,降低了代码的耦合度,数据更加的安全
默认权限(包权限)
包
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。类似于 我们常见的 “文件夹”
包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可
如何导入包
packet + 路径(以点进行分隔) packet com.bit.demo 声明包(不是
系统自带
的,一般都会需要进行声明包 (使用自己创建包里面的类))
- 系统提供的
- import java.util.Arrays — 导入java.util 包里面的Array类
- import java.util.* ---- 导入java.util包里面的所有类(后面程序在用到里面类的时候才加载包里面的对应的类,不会直接全部加载)
- 不导入包:java.util.Data 也可以表示 Data类(但是若同时 需要 util包 和 sql包时,就只能使用 java.util.Data来区分了,因为util 和 sql 中都包含 Data类)
- 自定义的:import 路径. import 路径.方法名*
- 静态导入:类是个静态方法,就可以静态导入包,直接使用字段和方法了 (了解即可,不推荐)
- 例如:import static java.lang.Math.* 直接sqrt(x)即可;若不导入需要Math.sqrt(x)
- import static java.lang.System.* 导入后使用: out.println() ;若没导入需要:System.out,println()
static
static 修饰的变量 或 方法 称之为 静态变量 / 方法 , 他们都是存放在方法区的(方法区的东西只有一份,也就是同一个类创建出来的多个对象会 共用 方法区 上分配的资源,一个对象修改了静态变量的值,另一个对象访问的时候也会随之更改)
注意点
- 普通方法中是可以访问静态成员变量的
- 静态成员方法中不可以访问普通成员变量
- 普通成员变量依赖于对象,而静态变量是用类进行调用的 - 不依赖对象
- 静态变量可以通过类访问,也可以通过对象访问;但推荐用类访问
- 生命周期随类的创建而创建,销毁而销毁
- 静态方法没有 隐藏this 参数 this指代当前对象的引用,而静态方法不依赖对象
示例
public static void main(String[] args) {
// classes是静态属性
Student student1 = null; // student1不指向任何对象
student1.classes = "104班"; // classes 不依赖任何对象
System.out.println(student1.classes); // 并不会报 空指针异常
}
代码块执行顺序
见下图,我们可以发现,在字节码文件中,实例代码块被默认的加载到了构造方法里面,并放在开头;且当我们创建多个对象时,静态代码块只会执行一次
顺序:先 静态代码块,再 实例代码块,最后 构造方法
内部类
- 之所以被叫做 内部类,是因为其 定义包含在某个类当中(类的一个成员)
- 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
实例内部类
- 如何实例化内部类对象:
- 部类名.内部类名 变量 = 外部类对象的引用.new InnerClass()
- 实例内部类当中 不能定义静态的成员变量 - 如果要定义 必须是编译的时候确定的值(必须是static final的)
- 静态变量不依赖于对象,但
实例内部类
依赖于外部类的对象 - public static final int data6 = 60; 常量–》编译的时候,就确定了data6的值
- 静态变量不依赖于对象,但
- 实例内部类当中,若内部类的成员变量与外部类成员变量重名了,优先使用自己的
- 使用外部类的:OuterClass.this.变量名
- 使用内部类的:this.变量名 / 直接变量名
- 实例内部类中不能包含静态方法
- 外部类不能直接访问内部类对象,必须创建内部类对象
class OuterClass {
// 实例成员变量/普通成员变量
public int data1 = 10;
public int data2 = 20;
public static int data3 = 30;
// 实例内部类
class InnerClass {
public int data4 = 40;
public int data5 = 50;
public int data3 = 1;
public static final int data6 = 60;
/**
* 构造方法
*/
public InnerClass() {
System.out.println("InnerClass的构造方法");
}
public void method() {
System.out.println(data3);
System.out.println(OuterClass.data3);
System.out.println("InnerClass的一个method方法");
System.out.println(OuterClass.this.data1); // 访问外部类的成员(固定写法)
System.out.println(this.data4); // 当前类的成员,this--内部类的对象引用
System.out.println(data6); // 内部类里面的static final常量
}
}
public void methodOut() {
InnerClass innerClass = new InnerClass();
System.out.println(innerClass.data4); // 访问内部类的成员 - 必须要有内部类对象
System.out.println(this.data1); // 访问自己的成员
}
}
public class TestDemo {
public static void main(String[] args) {
// 构造内部类对象
OuterClass outerClass = new OuterClass();
System.out.println(outerClass.data1);
// 外部类名.内部类名 变量 = 外部类对象的引用.new InnerClass()
OuterClass.InnerClass innerClass1 = outerClass.new InnerClass();
// new OuterClass():匿名对象
OuterClass.InnerClass innerClass2 = new OuterClass().new InnerClass();
System.out.println(innerClass1.data4);
System.out.println(innerClass2.data4);
innerClass1.method();
}
}
静态内部类
比
实例内部类
更加的常用,因为它的创建不需要借助外部类对象
- 如何实例静态内部类
- OuterClass1.InnerClass innerClass = new OuterClass1.InnerClass();
- 在静态内部类当中,只能访问自己的成员 或 外部类的静态成员
- 如何访问外部类的其他成员?(获取外部类对象)
- 手动在静态内部类中创建(new OuterClass)
- 通过构造方法 外部 传入
- 如何访问外部类的其他成员?(获取外部类对象)
- 静态内部类里面可以定义静态方法和静态变量
class OuterClass1 {
public int data1 = 10;
private int data2 = 20;
public static int data3 = 30;
public void method() {
System.out.println("OuterClass2::method()");
}
// 静态内部类
static class InnerClass {
public int data4 = 40;
private int data5 = 50;
public static int data6 = 60;
// 1. 创建
// OuterClass1 out = new OuterClass1();
// 2. 外部传入
OuterClass1 out;
public InnerClass(OuterClass1 out) {
this.out = out;
System.out.println("InnerClass()");
}
public InnerClass() {
System.out.println("InnerClass()");
}
public void method() {
System.out.println(out.data1);
System.out.println(data4);
System.out.println("innerclass的method方法");
}
public static void fun() {
System.out.println("haha");
}
}
}
public class TestDemo1 {
public static void main1(String[] args) {
// 不需要创建外部类对象,所以静态内部类比实例内部类使用频繁(链表的节点就可以使用静态内部类)
OuterClass1.InnerClass innerClass = new OuterClass1.InnerClass();
OuterClass1.InnerClass innerClass1 = new OuterClass1.InnerClass(new OuterClass());
innerClass.method();
}
}
匿名内部类
在线程的创建中会用到
class Test {
public int a = 10;
public void test() {
System.out.println("123");
}
}
public class TestDemo2 {
public static void main(String[] args) {
// 匿名内部类
new Test(){
public void test1() {
System.out.println("hehe");
}
@Override
public void test() {
System.out.println("haha");
}
}.test();
}
}
继承
Java不支持多继承,因此在后面出现了 接口的概念,来解决多继承问题
class Animal {
String name;
String hair;
public void eat() {
System.out.println("吃饭");
}
}
class Cat extends Animal {
public void miu() {
System.out.println("Cat:miumiu");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Dog:bark");
}
}
super
- super 关键字的作用:在子类中访问父类的成员
- 只能在非静态方法中使用,见子类构造方法(静态方法不依赖对象,也就不执行构造方法)
子类构造方法
若自己没有写,编译器也会提供一个无参数的构造方法,并在构造子类之前,先调用 super来构造父类(若自己在子类中定义了带参数的构造方法时,1. 调用super(),2.实现super的构造方法)
注意:子类构造方法中,super()也是必须放在第一行的,因此super() 与 this() 不能一起使用
super与this
相同点
- 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点
- this 是当前对象的引用;super 是由子类对象 从 父类对象 中继承过来的那部分的"引用"
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- this是非静态成员方法的一个隐藏参数,super不是隐藏的参数
- 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
带继承的代码块执行顺序
根据下图得出结论:
- 最早执行的是静态代码块,且父类 静态代码块 早于 子类
- 父类实例代码块 和 构造方法 紧接着执行
- 子类实例代码块 和 构造方法在执行
- 第二次实例化子类时,父子类的静态代码块 都不会执行了
class Animal {
String name;
{
System.out.println("Animal 实例代码块");
}
static {
System.out.println("Animal 静态代码块");
}
public Animal(String name) {
this.name = name;
System.out.println("Animal 构造方法");
}
}
class Cat extends Animal {
{
System.out.println("Cat 实例代码块");
}
static {
System.out.println("Cat 静态代码块");
}
public Cat(String name) {
super(name);
System.out.println("Cat 构造方法");
}
}
public class Demo2 {
public static void main(String[] args) {
Cat cat1 = new Cat("咬人猫");
System.out.println("===================");
Cat cat2 = new Cat("食人花");
}
}
protected
注意看:两个类处于不同包地下
final
final关键可以用来修饰变量、成员方法以及类
- 修饰变量或字段,表示常量(即不能修改)
- 修饰类:表示此类不能被继承
- 修饰方法:表示该方法不能被重写(重写在多态中介绍)
多态
是一种思想,
去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
前提条件
必须发生在继承体系下
- 向上转型:父类引用 引用子类对象
- 发生重写:父类和子类当中有同名的覆盖方法
- 通过父类引用,调用这个同名的方法。此时在程序执行时,会发生动态绑定
向上转型与向下转型
向上转型
使用 父类对象 来 引用子类对象(后面发生动态绑定)
- 向上转型的优点:让代码实现更简单灵活。
- 向上转型的缺陷:
不能调用到子类特有的方法。
- 直接赋值
Animal animal = new Cat();
- 参数列表
public static void eatFood(Animal a){
a.eat();
}
eatFood(new Cat());
- 返回值
public static Animal buyAnimal(String var){
if("狗" == var){
return new Dog();
} else if("猫" == var){
return new Cat();
} else{
return null;
}
}
向下转型
向下转型不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换
Java操作数据库的JDBC标准中,会用到向下转型
Cat cat = new Cat();
Animal animal = cat;
if(animal instanceof Cat) {
cat = (Cat)animal; // 向下转型
cat.mew();
}
重写
基本要求
- 方法名相同
- 参数列表相同
- 返回值相同(JDK7以后,返回值是 父子类 关系,也构成重写)
注意点
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
- 父类中被 static,private,final 修饰的方法不能被重写
- 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解编辑器能帮我们进行一些合法性校验.
重写与重载区别
区别点 | 重载(override) | 重写(override) |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
访问限定符 | 可以修改 | 大部分不能(子类的 >= 父类的就行) |
多态优缺点
- 降低 “圈复杂度”,也就是 避免大量的 if–else
class Shape {
public void draw() {
System.out.println("画图形");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("♦");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("✿");
}
}
// 不使用多态
public static void drawMaps1() {
Cycle cycle = new Cycle();
Rect rect = new Rect();
Flower flower = new Flower();
String[] shapes = {"cycle","rect","cycle","rect","flower"};
for (String shape: shapes) {
if(shape.equals("cycle")) {
cycle.draw();
} else if(shape.equals("rect")) {
rect.draw();
} else if(shape.equals("flower")) {
flower.draw();
}
}
}
// 使用多态
public static void drawMaps() {
Cycle cycle = new Cycle();
Rect rect = new Rect();
Flower flower = new Flower();
Shape[] shapes = {cycle,rect,cycle,rect,flower};
for (Shape shape : shapes) {
shape.draw();
}
}
- 提高可扩展性
直接继承 Shape 类,就可以画 三角形 了
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("▲");
}
}
- 代码的运行效率降低。
注意:
运行结果:D.func() 0
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Demo4 {
public static void main(String[] args) {
D d = new D();
}
}
分析
虽然发生了动态绑定,但是它并不属于 “多态”
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0
抽象类
当一个类里面有了抽象方法时,此时这个类就要变成抽象类:只是这个类不能被实例化而已,里面还是可以定义成员变量和成员方法的
- 抽象类一般都是用来被继承的
- 抽象类不能进行实例化
- 当一个普通类继承抽象类时,必须重写抽象类中全部抽象方法
- 抽象类存在的最大意义 就是为了继承
- 抽象类也可以发生向上转型,这样就可以发生多态
- 当一个抽象类A 继承 抽象类B时,此时抽象类A可以不重写抽象类B中的抽象方法
- 当一个普通类C 继承了 6 中的抽象类A 时,就得重写以往类中所有的抽象方法(A,B)中的
- final 和 abstract 两个关键字不能一起使用
- 抽象方法也不能是 private
- 抽象类当中不一定有抽象方法,但若有抽象方法,这个类必然是抽象类
- 抽象方法一般都不需要实现内容
接口
- 接口当中的成员变量,默认都是 public static final的,必须被赋值
- 接口当中的成员方法,若需要实现,需要用default来修饰
- 接口中的静态方法可以直接使用的
- 接口不能进行实例化,只能被继承
- 继承接口的方法中,必须重写抽象方法,默认方法可以重写,也可以不重写
- 接口 . 静态方法() – 调用静态方法
- 和抽象类中的 6,7一样
- 一个普通类可以 implements 多个接口,接口间用 逗号隔开
- A接口 extends B接口 ----> A接口扩展了B接口的功能
三大常用接口
使用Comparable 和 Comparator 两个接口来排序时,必须时引用类型数组,int[ ] 不可以,Integer[ ] 可以
Comparable
缺点:一个类里面只能有一个compareTo方法,当你第一次排序 name 字段,但是接下来想看看通过 score 比较的结果,这个时候就需要改变 Student 类中的实现了;这时候 Comparator 就可以解决
Comparator
创建一个类,实现 Comparator 比较器接口,直接传入 Arrays.sort()中即可
Cloneable
浅拷贝
深拷贝
当在一个对象中存在引用对象的成员属性事,除了拷贝外面一层对象外,里面引用的对象也需要被拷贝