类和对象的基础知识
类的定义
-
类是事物的属性(外在特征)和行为(具备的功能)的集合
-
把具有相同特征和行为的一组对象抽象为类,类是抽象概念,如人类、车类等,无法具体到每个实体。
对象的定义
-
对象是类的具体的体现
-
某个类的一个实体,当有了对象后,这些特征便有了相应的值,行为也就有了相应的意义。
类是描述某一对象的统称,对象是这个类的一个实例而已。有类之后就能根据这个类来产生具体的对象。一类对象所具备的共同特征和行为(方法)都在类中定义。
类的格式
格式:
class 类名{
类的属性 — 成员变量;
类的行为 — 成员方法;
}
案例:
class Person{
//类的属性
String name;
int age;
String sex;
//类的方法
public void sayHello(){
System.out.println(“Hello”);
}
}
类的属性
-
属性是对一个类的描述,属性的值是这个类中不同对象的区分
-
属性是直接定义在类中的变量,它还有一个名字叫做成员变量,它的定义和“普通变量”的定义基本一致。
-
格式:[修饰符] 属性类型 属性名 [ = 默认值];
-
修饰符可以省略,可以是 public、protected、private、static、final等,也可以相互组合起来修饰属性。
-
-
属性类型:可以是 Java 语言允许的任何数据类型,包括基本数据类型和引用数据类型。
-
属性名:和普通变量的命名原则一致,但若可读性角度来说:属性名应该是由一个或多个有意义的单词连缀而成,第一个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词之间不需使用任何分隔符。
-
默认值:定义属性还可以定义一个默认值,如果未赋值,系统会赋予合适的初值。基本数据类型是 0,引用数据类型是 null。
public class Person{
private int id;
public String name;
char gender;
}
类的方法
-
格式:
- 修饰符可以省略,也可以是 public、protected、private、static、final、abstract等, 其中 public、protected、private 三个最多只能出现一个,abstract 和 final 最多只能出现其中之一,它们可以与 static 组合起来修饰属性。
-
方法返回值类型:返回值类型可以是 Java 语言允许的任何数据类型,包括基本类型和引用类型。若声明了返回值类型,则方法体内必须有一个有效的 return 语句,该语句返回一个变量或一个表达式(变量或表达式类型必须与此处声明的类型匹配)。若方法没有返回值,必须使用 void 来声明。
-
方法名:与属性名规则基本相同,通常建议方法名以英文字的动词开头。
-
形参列表:用于定义该方法可以接受的参数,由零组或多组“参数类型 形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间英文空格隔开。
注意:方法体里多条可执行性语句之间有严格的执行顺序,排在方法体前面的语句总是先执行,排在方法体后面的语句总是后执行。
public class Student{
private String name;
private int age = 33;
public int getAge(){
System.out.println("获取当前对象的年龄");
return age;
}
public void readBook(String bookName){
System.out.println(name + "正在读:" + bookName);
}
}
创建对象
格式:类名 对象名 = new 类名();
Person me = new Person();
使用对象中的属性
- 在本类中的方法内,可以直接调用本类定义的属性,无需额外操作。
- 对象使用:对象名.属性名 = 属性值
public class Student{
String name;
int age = 33;
public int getAge(){
System.out.println("获取当前对象的年龄");
return age;
}
public void readBook(String bookName){
System.out.println(name + "正在读:" + bookName);
}
}
Student me = new Student();
//赋值
me.name = “water”;
me.age = 25;
//输出
System.out.println(me.name + “…” + me.age + “…” + me.sex);
使用对象中的行为
- 在本类中的方法内,可以直接调用本类定义的其他,无需额外操作。
- 对象名.方法名();
eg: me.sayHello();
如果一个类包含了属性与方法,那么该类的每一个对象都具有自己的属性,但无论一个类有多少个对象,所有对象共享一个方法
对象的内存模型分析:
- javac Demo04.java —— 生成Demo04.class 和 Dog.class两个字节码文件
- Demo04.class 和 Dog.class 被放入方法区
- 永远第一个执行main方法,main方法被放进栈区
- new Dog() – 在堆内存里分配内存将Dog.class里的内容复制后放入,并将内存的地址存给dog
- 更改dog对象的属性值
- 执行dog对象的方法,执行新的方法时会将新方法压入栈中,直到执行到return时才会被释放出去,前提是此方法前面没有其他的方法在执行(栈的特性,先进后出)。
- 最后main方法执行完毕,被释放,堆内存中的内容失去被指的指针,等待垃圾回收器回收内存被释放
说明:eat()执行完成后,遇到return(由于方法类型是void,return被隐藏),eat()被移除栈,sleep()方法亦如此。
构造方法
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时由编译器自动调用,并且在整个对象的生命周期内只调用一次。
定义
- 方法名称与类名称完全相同。
- 构造方法没有返回值声明(不是void)。
- 一个类中至少存在一个构造方法,若没有显示定义,编译器会生成一个默认的无参构造。
public class Person{
// 构造方法
public Person(){ // 无参构造函数
// 构造方法的实现
}
public Person(int a){ // 有参构造函数
// 构造方法的实现
}
public Person(int a,String b){ // 有参构造函数
// 构造方法的实现
}
public Person(String a){ // 有参构造函数
// 构造方法的实现
}
// ....
}
分类
-
显式构造器
显式构造器就是程序员自己书写的构造器,这些构造器的名称必须和类名一致。通过形式参数列表的不同来区分这些构造器。
-
无参数构造器
-
有参数构造器
-
-
隐式构造器
当一个类中没有提供任何构造方法,系统默认提供一个无参数的构造方法。这个无参数的构造方法叫做隐式构造器,也叫做缺省构造器。
作用
构造方法的作用只有一个:初始化对象,会在产生对象时,给对象未赋值的属性赋值。
调用
使用 new 运算符来调用构造方法,构造器会在产生对象时被调用。
new 构造方法名(实际参数列表);
案例:
public class Person{
int id;
String name;
char gender;
public Person(){
id = 1;
name = "张三";
gender = '男';
}
public Person(int id1,String name1,char gender1){
id = id1;
name = name1;
gender = gender1;
}
}
// 其他类的main方法
// 调用了 Person 类的无参数构造器给属性赋值
Person p1 = new Person();
// 调用了 Person 类的有参数构造器给属性赋值
Person p2 = new Person(3,"关为",'男');
注意事项
this 关键字表示对当前对象的引用,一般在重名时使用
public class Person{
int id;
String name;
char gender;
public Person(){
id = 1;
name = "张三";
gender = '男';
}
// ❌的写法
public Person(int id,String name,char gender){
// 这里定义的形式参数id、name和gender和成员变量重名了,
// 如果还是这样赋值是错误的
id = id; // ❌
name = name; // ❌
gender = gender; // ❌
}
// ✔的写法
public Person(int id,String name,char gender){
// 这里我们要使用到 this 关键字,
this.id = id; // 将参数id值赋予成员变量id
this.name = name;
this.gender = gender;
}
}
隐式构造器会给未赋值的属性赋予合适的初值(原始类型都是 0,引用类型是 null)。
public class Person{
int id;
String name;
char gender;
public void show(){
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("gender:" + gender);
}
}
// 其他类的main方法
Person person = new Person();
person.show(); // id:0 name:NULL gender:NULL
当书写了显式构造器后,系统的无参数隐式构造器就无法被程序员调用了。
public class Person{
int id;
String name;
char gender;
public Person(int id){
// ...
}
}
// 其他类的main方法
Person person = new Person(); // ❌ 没有定义无参数构造器
如果定义的构造器并没有给所有属性赋值,那这些属性的值就是默认值
public class Person{
int id;
String name;
char gender;
public Person(int id){
this.id = id;
}
public void show(){
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("gender:" + gender);
}
}
// 其他类的main方法
Person person = new Person(2);
person.show();// id:2 name:NULL gender:NULL
匿名对象
-
定义:没有名字的对象就是匿名对象
-
格式:new 类名();
-
使用方式:
new 类名().属性名;
new 类名().方法名();
- 匿名对象的特点
一次性,只能使用一次
- 使用范围
(1)只需要使用一次对象的属性或者方法的时候,就可以选择使用匿名对象
(2)匿名对象可以作为实际参数进行传递
class Person{
public void eat(Pig pig){
System.out.println(“我喜欢吃” + pig.name);
}
}
class Pig{
String name = “烤乳猪”;
}
class Demo{
public static void main(String[] args){
/*
Person person = new Person();
Pig pig = new Pig();
person.eat(pig)
*/
new Person().eat(new Pig());
}
}
抽象类
定义
抽象类:被abstract关键字所修饰的类就是抽象类
抽象方法:没有方法体的方法就是抽象方法
有抽象方法的类就是抽象类
特点
- 抽象类和抽象方法都要由abstract关键字来修饰
// class Person{
// public void eat(){
// System.out.println("吃"); // 在后续中并不会使用该方法,所以可以省略
// }
// }
// 省略了执行语句的eat就没有内容,{}也可以省略,所以eat也就没有了方法体,
// 也就成为了抽象方法,而拥有抽象方法的类也就成为了抽象类
abstract class Person{
public abstract void eat();
}
class Student extends Person{
public void eat(){
System.out.println("吃肉");
}
}
class Teacher extends Person{
public void eat(){
System.out.println("吃米饭");
}
}
class Demo{
public static void main(String[] args){
Student s = new Student();
Teacher t = new Teacher();
}
}
-
抽象类可以没有抽象方法,但是有抽象方法的类一定是抽象类
-
抽象类不能创建对象
个人理解:由于抽象类里有抽象方法,抽象方法没有方法特是不能调用的,为了防止调用抽象方法的情况,所以规定抽象类不能拥有对象,也就没有了会调用到抽象方法的情况。
- 一个类想要继承一个抽象类,要么该类也是一个抽象类,要么该类是一个普通类,但是必须重写所有的抽象方法
个人理解:必须重写抽象方法是为了防止抽象方法被调用
abstract class Person{
public abstract void eat();
public abstract void play();
public void sleep(){
}
public void drink(){
}
}
// 普通类
class Student extends Person{
public abstract void eat(){
}
public abstract void play(){
}
}
// 抽象类
abstract class Student extends Person{
// 可以不用重写抽象方法
}
抽象类的成员特点
- 成员变量
- 里面可以有静态的成员变量
- 里面可以有非静态的成员变量
- 里面可以自定义常量
- 成员方法
- 里面可以有抽象方法
- 里面可以有非抽象方法
- 构造方法
- 有构造方法,即使不写,也有默认无参构造方法
- 抽象类的构造方法是用来让子类调用的,然后给抽象类中的成变量进行初始化,方便提供给子类使用
abstract关键字不能哪些关键字共存
- abstract不和private关键字共存:
因为被abstract关键字修饰的方法是抽象方法,子类是一定要重写的,如果该方法同时又被private修饰的话,子类还是重写不了,所以冲突
- abstract不能和static关键字共存
因为被static关键字所修饰的方法可以通过类名直接调用,但是同时被abstract关键字修饰的方法,没有方法体的,调用一个没有方法的方法是没有意义的
- abstract不能和final关键字共存
因为被final关键字所修饰的方法是不能被重写的,但是同时被abstract关键字修饰的方法,没有方法体的,所以冲突
内部类
定义在另一个类内部的类,它允许在一个类的内部定义另一个类。内部类可以访问外部类的成员变量和方法,并且可以用来实现一些特定的功能。
分类
• 成员内部类:定义在外部类的成员变量位置,并且可以访问外部类的成员变量和方法。
• 静态内部类:定义在外部类的静态变量位置,并且可以拥有自己的静态变量和方法。
• 局部内部类:定义在外部类的方法体内,作用域通常被限制在该方法中。
• 匿名内部类:没有名称的内部类,通常用来简化代码,在创建线程、事件处理等方面被广泛使用。
注意:在创建成员内部类对象时,必须先创建外部类对象,再使用外部类对象来创建成员内部类对象。因为成员内部类的存在依赖于外部类对象。
成员内部类
成员内部类是定义在另一个类中的类。它可以访问该外部类的所有成员变量和方法,即使它们被声明为私有。可通过以下步骤创建成员内部类:
-
在外部类中使用关键字 class 创建一个成员内部类。
-
调用外部类的构造函数或方法来创建成员内部类对象。
-
可以使用 new 运算符创建成员内部类对象。
public class Demo{
public static void main(String[] args){
Outer.Inner oi = new Outer().new Inner();
System.out.println(oi.i);
oi.print();
Outer1 outer1 = new Outer1();
outer1.show();
}
}
class Outer{
class Inner{ // 内部类
int i = 1;
public void print(){
System.out.println("约吗");
}
}
}
class Outer1{
private class Inner1{ // 内部类
int i = 1;
public void print(){
System.out.println("约吗");
}
}
public void shoe(){
Inner inner = new Inner();
System.out.println(inner.i);
inner.println();
}
}
注意事项:
• 内部类可以使用外部类的 private 成员(包括私有方法和变量)。
• 外部类不能直接访问内部类的成员。在外部类中,必须先创建内部类对象,然后才能访问内部类的成员。
• 使用内部类时应仔细考虑垃圾回收问题。如果内部类对象在外部类对象之外引用,则可能会导致内存泄漏。
静态内部类(Static Nested Classes)
静态内部类是静态声明的类,与外部类实例无关。它与其他静态成员类似,可以通过类名引用。
可通过以下步骤创建静态内部类:
-
在外部类中使用关键字 static 和 class 创建一个静态内部类。
-
使用外部类的名称和点运算符来访问静态内部类。
public class Demo{
public static void main(String[] args){
Outer.Inner oi = new Outer.Inner();
System.out.println(oi.i);
oi.print();
/*
以下方法需要导包: import com.inner,Outer,Inner;
*/
Inner inner = new Inner();
System.out.println(inner.i);
inner.print();
}
}
class Outer{
static class Inner{ // 内部类
int i = 1;
public void print(){
System.out.println("约吗");
}
}
}
注意事项:
• 静态内部类不能直接访问外部类的非静态成员。要访问外部类的非静态成员,需要首先创建外部类对象。
• 可以将静态内部类用作外部类的辅助类,但是,如果静态内部类不依赖于外部类实例,则最好将其声明为静态成员。
• 因为静态内部类不引用外部类实例,因此它可以防止内存泄漏等问题。
局部内部类(Local Inner Classes)
局部内部类是在代码块、方法或构造函数中定义的类。它只在该块中可见,并且通常用于解决特定问题。
可通过以下步骤创建局部内部类:
-
在代码块、方法或构造函数中使用关键字 class 创建一个局部内部类。
-
调用该块的方法或构造函数来创建局部内部类对象。
class Outer{
public void print(){
class Inner{
int i = 2;
public void show(){
System.out.println("再见");
}
}
Inner inner = new Inner();
System.out.println(inner.i);
inner.show();
}
}
public class Demo{
public static void main(String[] args){
Outer outer = new Outer();
outer.print();
}
}
注意事项:
• 局部内部类不能用 public、protected 或 private 修饰。
• 局部内部类只能在声明它的块中实例化。
• 可以访问外部类的所有成员,包括私有成员。
匿名内部类(Anonymous Inner Classes)
匿名内部类是没有名称的局部内部类。它通常在创建对象时使用,并且只能实例化一次。
可通过以下步骤创建匿名内部类:
-
使用关键字 new 和要实现的接口或继承的基类来创建一个对象。
-
在大括号中编写代码块实现接口或继承的基类的方法。
interface Inter{
public abstract void print();
}
class InterImpl implements Inter{
public void print(){
System.out.println("约吗");
}
public void show(){
System.out.println("在吗");
}
}
public Demo{
public static void main(String[] args){
InterImpl interImpl = new InterImpl();
interImpl.print();
interImpl.show();
Inter inter = new InterImpl();
inter.print();
// inter.show();
InterImpl interImpl = (InterImpl)inter;// 多态向下转型
InterImpl.show();
}
}
interface Inter{
public abstract void print();
}
public Demo{
public static void main(String[] args){
//way-1
new Inter(){
public void print(){
System.out.println("约吗");
}
}.print();
//way-2
Inter inter = new Inter(){
public void print(){
System.out.println("约吗");
}
public void show(){
System.out.println("在吗");
}
}
inter.print();
// inter.show(); // 报错,因为多态的低端的不能使用子类特有的
}
}
abstract class Father {
int num;
public Father(int num) {
this.num = num;
}
public abstract void method();
}
class Niming {
public void show() {
Father father = new Father(10) { // 定义 Father 类的匿名子类
@Override
public void method() { // 重写 Father 类的 method() 方法
System.out.println("Father 类的匿名子类重写了 method() 方法");
System.out.println("num 的值为:" + num);
}
};
father.method(); // 在合适的位置调用 method() 方法
}
}
public class InnerClasssDemo5 {
public static void main(String[] args) {
Niming niming = new Niming();
niming.show();
}
}
interface InterFace1 {
int num = 100;
void method();
}
class Niming2 {
public void show() {
InterFace1 interFace1 = new InterFace1() { // 定义实现了 InterFace1 接口的匿名内部类
@Override
public void method() { // 重写 InterFace1 接口中的 method() 方法
System.out.println("InterFace1 接口的匿名内部类重写了 method() 方法");
System.out.println("num 的值为:" + num);
}
};
interFace1.method(); // 在合适的位置调用 method() 方法
}
}
public class InnerClassDemo6 {
public static void main(String[] args) {
Niming2 niming2 = new Niming2();
niming2.show();
}
}
注意事项:
• 匿名内部类通常用于事件处理程序和回调函数中。
• 不能为匿名内部类定义构造函数。
• 可以访问外部类的成员变量和方法,但只能访问最终变量(也就是值不会改变的变量)。
• 匿名内部类可以实现接口或继承一个类,但不能同时实现接口和继承一个类。
• 如果要在匿名内部类中使用一个局部变量,则该变量必须声明为 final。
类的初始化顺序
class Fu{
static {
System.out.println("fu...静态代码块");
}
{
System.out.println("fu...构造方法代码块");
}
public Fu(){
System.out.println("fu...构造方法");
}
}
class Zi extends Fu{
static {
System.out.println("zi...静态代码块");
}
{
System.out.println("zi...构造方法代码块");
}
public Zi(){
// 1.super();
// 2.执行构造方法体中构造代码块
// 3.执行构造方法体中的内容
System.out.println("zi...构造方法");
}
}
成员变量和局部变量
局部变量:定义在方法中,或者方法声明上(形参)的变量就是局部变量
成员变量:定义在类中,方法外的变量就是成员变量(即类的属性)
局部变量和成员变量的区别:
- 定义外置不同
局部变量:定义在方法中或方法声明上
成员变量:定义在类中方法外
- 内存位置不同
局部变量:存储在栈中方法中
成员变量:存储在堆中的对象中
- 初始值不同
局部变量:没有默认值初始值,想要使用一定要先赋值在使用
成员变量:有默认初始化值,如果不赋值也可以使用
String – null、int – 0、boolean — false、double – 0.0、char – ‘\u0000’
- 生命周期
局部变量:因为是存储在方法中,所以是随着方法的存在而存在,随着方法的消失而消失
成员变量:因为是存储在对象中,所以是随着对象的存在而存在,随着对象的消失而消失
- 作用域范围不同
局部变量:出了方法就用不了了
成员变量:在本类中的方法都可以使用