跟着刚哥梳理java知识点——面向对象(八)
面向对象的核心概念:类和对象。
类:对一类事物描述,是抽象的、概念上的定义。
对象:实际存在的该类事物的每个个体,因而也成为实例(Instance)。
Java类及类的成员:属性(成员变量Field)和方法(method)。
类对象的内存结构:
Animal a1 = new Animal(); a1.name = "花花"; a1.age = 3;
之前梳理数组的时候说过,栈中的对象存放对象的引用及Animal a1的a1这个引用。堆空间都是new出的东西包括成员变量。
① 在栈中声明一个a1的引用变量
② 在堆中申请一块区域存储new Animal并为成员变量赋默认值(name=null,age=0)。
③ a1的引用变量指向堆的new Animal的首地址(16进制数字)。
④ a1.name="花花":修改成员变量name值为花花。
⑤ a1.age = 3:修改成员变量age值为3。
Animal a2 = new Animal();
每次new出来的对象都是在堆中重新申请的一个新空间
Animal a3 = a1;
a3.name = "草草"
① 在栈中声明一个a3的引用变量
② a3的引用变量指向a1的指向的堆空间的首地址
③ a3.name = "草草":修改成员变量name值为"草草"。
最终,a1和a3的成员变量name都修改成了草草。
类的== 和equals
== 表示对象引用的地址是否相等,即16位的首地址码是否一致。
类没有重写equals,所以用的是Object的equals的方法,看源码就能看出来,和==一样。
想让比较的两个类所有属性值都一样的情况下就相等的话,需要自己重写equals方法。
类的成员之一:属性
成员变量(属性)和局部变量
1、修饰符
① 成员变量:public、private、protected、default、final、static
② 局部变量:final
笔试题:
public class aClass { void run() { static int i = 0; i++; System.out.println(i); } public static void main(String args[]) { aClass obj = new aClass(); obj.run(); obj.run(); } }
结果为:A:0 B:1 C:2 D:编译错误
很明显是选D,因为局部变量只能用修饰符final修饰。
2、默认初始化值:
① 局部变量:类的局部变量和数组元素的默认初始化值一致的。
② 成员变量:没有默认值。
3、内存中存放的位置:
① 局部变量:堆
② 成员变量:栈
① 在栈中声明一个p1的引用变量
② 在堆中申请一块区域存储new Person并为成员变量赋默认值(name=null,age=0,sex=false)。
③ p1的引用变量指向堆的new Person的首地址(16进制数字)。
④ 在栈中申请n的变量并赋值为付昊(n为形参的参数名字)
⑤ 将n这个局部变量的值赋值给刚才new Person的成员变量name
⑥ setName执行完毕后,n这个局部变量销毁。
4、类对象的属性赋值顺序:
① 属性的默认值:private int age;
② 属性的显式赋值:private int age = 1;
③ 构造器给属性赋值:public Person(int a){age = a;}
④ 通过setter方法赋值:public void setAge(int n){age = n;}
类的成员之二:方法
1、修饰符:private、protect、public、static、final、synchronize。
2、重载:方法名字相同,参数列表不同(参数个数不同和类型不同)。
3、重写:继承中子类重新实现了父类的方法。
① 要求子类方法名字和参数列表完全和父类一样。
② 子类方法的修饰符不能小于父类方法的修饰符。
4、形参:
java中的参数传递机制:值传递
① 基本数据类型传递(传递的值是具体的实参的基本数据类型的值)
public class TestArgsTransfer { public void swap(int i,int j){ int temp = i; i = j; j = temp; } public static void main(String[] args){ TestArgsTransfer tt = new TestArgsTransfer(); int i = 10; int j = 5; tt.swap(i,j); } }
内存结构
② 引用数据类型传递(传递的值是引用对象的16位的首地址值)
public class TestArgsTransfer { public void swap(DataSwap d){ int temp = d.i; d.i = d.j; d.j = temp; } public static void main(String[] args){ TestArgsTransfer tt = new TestArgsTransfer(); DataSwap ds = new DataSwap(); tt.swap(ds); } } class DataSwap{ int i = 10; int j = 5; }
类的成员之三:构造器:
特点:
① 和类名字一致
② 没有返回值
③ 只能被4个访问权限修饰符修饰
作用:
① 创建对象
② 给创建的对象的属性赋值,通过构造器的形参实现。
【知识点】:
① 若不显式的声明类的构造器的话,程序会默认的提供一个空参的构造器。
② 若显式的声明的类的构造器的话,那么默认的构造器就不再提供。
③ 类的多个构造器之间形成重载。
java封装性:
1、将属性私有化,然后通过调用getter和setter方法来获取或者设置属性。
为什么不直接使用对象.属性直接使用属性呢?
设想,你有一个Person类代表一个人,Person有一个char类型的sex字段表示性别,理论上,sex只接受两个值, 'M '和 'F ',但如果你把sex字段设为public,
你很难限制你的用户只给它赋 'M '或 'F '值。将sex设为private,再用setSex()来设置性别,你就完全可以控制这种行为了。而且你还可以控制只能get不能set,
或相反,但如果是public就不行了。另外我们可能并不想对属性进行写操作,这个时候,可以直接不写set方法。这就是只读属性了。
2、通过4种访问权限修饰符隐藏了类或者方法
java继承性:extends
1、子类继承父类后,父类中声明的属性、方法,子类就可以获取到。
2、子类除了通过继承,获取父类的结构之外,还可以定义自己特有的成分。
3、java中的类只支持单继承,不支持多继承。
4、由于Object是所有类的超类,那么你在最顶层的父类其实还会调用Object的无参构造函数。
子类对象实例化的全过程
1 public class Main { 2 public static void main(String[] args) { 3 Dog dog = new Dog(); 4 dog.setAge(10); 5 dog.setName("花花"); 6 dog.setHostName("小明"); 7 System.out.println("age:" + dog.getAge() + "\nname:" + dog.getName() + "\nhostname:" + dog.getHostName()); 8 } 9 } 10 //生物 11 class Creator{ 12 private int age; 13 public int getAge() { 14 return age; 15 } 16 public void setAge(int age) { 17 this.age = age; 18 } 19 } 20 //动物 21 class Animal extends Creator{ 22 private String name; 23 public String getName() { 24 return name; 25 } 26 public void setName(String name) { 27 this.name = name; 28 } 29 } 30 //狗 31 class Dog extends Animal{ 32 private String hostName; 33 public String getHostName() { 34 return hostName; 35 } 36 public void setHostName(String hostName) { 37 this.hostName = hostName; 38 } 39 }
子类对象的实例化并没有在堆中创建了任何它的父类对象,只是把父类对象的属性带了过来。
java多态性
多态性:可以理解为一个事物的多种表现形态。
1、方法的重载与重写
同一类中方法名相同,但是调用子类和父类的表现却是不同的。
2、子类对象的多态性
Person p = new Student(); //子类对象的多态性,父类的引用指向子类对象 //虚拟方法调用:实际调用子类重写父类的方法,但是父类必须有此方法声明 p.eat(); p.walk();
3、向下转型和向上转型
Person p = new Student();//向上转型 Student s = (Student)p;//向下转型,强制类型转换
但是这种转型是有一些风险的,那么最好用instanceof来判断
4、子类对象的多态性,并不适用属性。加入父类和子类的属性值相同,左边是谁,就使用谁的属性。
匿名类对象:
1、创建类的对象是没有名字的。
2、使用场景:当我们只需要调用一次类的对象时。
3、特点:创建的对象调用完一次这个对象就销毁了。
new Person().getName;
关键字:
1、this
this表示当前对象,用来修饰属性、方法、构造器
在构造器内部,this必须声明在首行。
class Cake{ private String name; public Cake(){ System.out.println("cake"); } public Cake(String name){ this();//this调用无参的构造器 this.name = name;//this调用当前对象 } }
2、super
this表示父类对象,用来修饰属性、方法、构造器
在构造器内部,supper必须声明在首行。
在构造器内部,子类构造器都默认第一步调用父类无参的构造器,除非你加了this,因为只要加了this的话super就不起作用了。
1 class Person{ 2 public Person(){ 3 System.out.println("p1"); 4 } 5 public Person(int id,String name){ 6 this(); 7 System.out.println("p2"); 8 } 9 } 10 class Student extends Person{ 11 public Student(){ 12 System.out.println("s1"); 13 } 14 public Student(String n){ 15 System.out.println("s2"); 16 } 17 public Student(int g){ 18 this("s"); 19 System.out.println("s3"); 20 } 21 } 22 public class Main { 23 public static void main(String[] args){ 24 Student student = new Student(0); 25 } 26 }
输出结果:p1 s1 s2
总结:
① 在构造器内要么用this要么用super,用了this,super就会失效。
② 在构造器内如果子类不显式的调用父类的构造器,那么默认调用无参的super();
③ 设计父类的时候,如果不显式的声明一个无参的构造函数,那么有可能造成程序编译错误。
3、static
静态的,可以修饰属性、方法、代码块、内部类
1)属性(类变量):
class SportsMan { String name; int age; static String nation; public SportsMan(String name, int age) { super(); this.name = name; this.age = age; } }
总结:
① SportsMan类中的nation属性是static修饰的,由这个类创建的对象都共用这个属性。
② 类变量的加载早于对象,所以既可以用“sp1.nation”,也可以用“SportsMan.nation”调。
③ 当其中一个对象对此类变量进行修改,会影响其他对象的类变量的一个调用。
2)方法:
① 随着类的加载而加载,在内存中也是独一份。
② 可以直接通过“类.类方法”
③ 静态方法内部可以调用静态的属性或者方法,但是不能调用非静态的属性和方法,因为静态的生命周期要早于非静态对象。
④ 非静态的方法可以调用静态的属性和方法,因为非静态的方法晚于静态的方法和属性。
4、final
① 修饰类 :不能被继承
② 修饰方法:不能被重写
③ 修饰属性:此属性就是一个常量。此常量不能使用默认初始化(除非在构造器中或者非静态代码块初始化),可以显式的赋值。
1 final int I;// 编译错误 2 final int I = 12;//正确 3 public void m(){ 4 I = 15; // 编译错误 5 }
但是如果放在构造器中,就没问题。
1 final int I;//正确 2 public A(){ 3 I = 15;//正确 4 System.out.println("3"); 5 }
笔试题一:
1 public class Something{ 2 public int addOne(final int x){ 3 return ++x; 4 } 5 }
肯定编译错误,final一旦修饰的时候赋了值,那么对不起,我的值就是最终的值,以后谁都不能改,谁改谁错。
笔试题二:
1 public class Something{ 2 public static void main(String[] args){ 3 Other o = new Other(); 4 new Something.addOne(o); 5 } 6 public void addOne(final Ohter o){ 7 o.i++ 8 } 9 } 10 class Other{ 11 public int i; 12 }
肯定不出错,final修饰的是o(即o的堆地址),o里的属性没关系,想怎么变就怎么变。
类的成员之四:初始化块
1)非静态块
1 class Person{ 2 int id = 0; 3 String name = "n"; 4 { 5 id = 10; 6 name = "ni"; 7 System.out.println("你好:你"); 8 } 9 { 10 id = 11; 11 name = "hao"; 12 System.out.println("你好:好"); 13 } 14 public Person(){ 15 id = 12; 16 name = "ni hao"; 17 } 18 } 19 public class Main { 20 public static void main(String[] args) { 21 Person p1 = new Person(); 22 System.out.println(p1.id + "\n" + p1.name); 23 } 24 }
输出结果:
你好:你
你好:好
12
ni hao
① 可有对类的属性(静态和非静态)进行初始化操作
② 里面可以有输出语句
③ 属性赋值的顺序:默认初始化值--->显式初始化或者代码块初始化(此处看顺序位置)--->构造器--->通过setter方法
2)静态块
① 随着类的加载而加载,而且只被加载一次。
② 静态的代码块在非静态代码块之前运行
③ 非静态的属性不能放在静态代码块
笔试题:
1 class A{ 2 static { 3 System.out.println("1"); 4 } 5 { 6 System.out.println("2"); 7 } 8 public A(){ 9 System.out.println("3"); 10 } 11 } 12 class B extends A{ 13 static { 14 System.out.println("a"); 15 } 16 { 17 System.out.println("b"); 18 } 19 public B(){ 20 System.out.println("c"); 21 } 22 } 23 public class Main { 24 public static void main(String[] args) { 25 A a = new B(); 26 a = new A(); 27 } 28 }
输出结果:1 a 2 3 b c 2 3
总结:
① 不管是静态代码块还是非静态代码块都是从父类开始执行的。
② 静态代码块只执行一次,非静态代码块new一次执行一次。
抽象类:
1、抽象类
① 抽象类不能被实例化
② 抽象类可以有构造函数
③ 抽象方法所在的类一定是抽象类
④ 抽象类中可以没有抽象方法
⑤ 不能和final共同修饰类
2、抽象方法
① 抽象方法必须需要子类进行重写
② 抽象类中可以有具体的实现方法。
③ 若子类是一个具体的“实体类”,那么就必须实现继承抽象类中的所有的抽象方法
④ 若子类继承抽象类,没有重写完所有的抽象方法,那么此类必须也是一个抽象类
3、其他:abstract不能修饰属性、构造器、private、final、static
① abstract 不能修饰属性,因为子类的属性是无法重写父类属性的
② abstract 不能修饰构造器,因为构造器不能被重写
③ abctract 不能和private修饰,因为你本来就是想让子类进行重写的,private的话你还让子类怎么重写。
④ final就不用说了,它修饰的方法不能被重写。它两个本身就是矛盾的。
⑤ static的话,直接用类.方法调用时不合规的。
1 abstract class Animal { 2 public Animal(){ 3 // 只要实例化子类,抽象类的构造器会被执行。 4 System.out.println("我是一个抽象动物"); 5 } 6 public void breath(){ 7 //抽象类中可以有具体实现方法。可以通过实例化子类,再调用抽象类的方法。 8 System.out.println("动物都会呼吸"); 9 } 10 public abstract void cry(); 11 } 12 13 class Cat extends Animal{ 14 15 @Override 16 public void cry() { 17 System.out.println("猫叫:喵喵..."); 18 } 19 } 20 21 class Dog extends Animal{ 22 23 @Override 24 public void cry() { 25 System.out.println("狗叫:汪汪..."); 26 } 27 28 } 29 public class Main { 30 public static void main(String[] args) { 31 Animal cat = new Cat(); 32 cat.cry(); 33 } 34 }
执行结果:
我是一个抽象动物
猫叫:喵喵...
接口
接口是一个特殊的抽象类,只有两个元素①常量 ②抽象方法
1、接口不需要写权限修饰符,肯定是public。
2、常量默认使用public static final修饰,方法默认使用public abstract修饰
3、接口既不能实例化,也不能有构造器
4、如果实现接口,那么就需要实现接口的所有抽象方法
5、接口之间可以多继承
6、类可以实现多个接口,但是需要实现这些接口所有的抽象方法。
1 public class DayStudy { 2 public static void main(String[] args) { 3 //1、第一个多态性。格式:接口 参数 = new 实现类() 4 Runner d = new Duck(); 5 test1(d); 6 //2、第二个多态性。参数用接口类型,传入实现类,解耦 7 Duck d1 = new Duck(); 8 test1(d1); 9 test2(d1); 10 test3(d1); 11 } 12 public static void test1(Runner r ){ 13 r.run(); 14 } 15 public static void test2(Swiner s ) { 16 s.swin(); 17 } 18 public static void test3(Flier f ) { 19 f.fly(); 20 } 21 } 22 interface Runner{ 23 void run(); 24 } 25 interface Swiner{ 26 void swin(); 27 } 28 interface Flier{ 29 void fly(); 30 } 31 class Duck implements Runner,Swiner,Flier{ 32 @Override 33 public void run() { 34 System.out.println("会跑"); 35 } 36 @Override 37 public void swin() { 38 System.out.println("会下水游泳"); 39 } 40 @Override 41 public void fly() { 42 System.out.println("会飞"); 43 } 44 }
Runner r = new Runner(){ @override public void run(){ system.out.printLn("匿名类"); } }