Java深入理解——面向对象
类和对象
-
类:类是一个模板,它描述一类对象的行为和状态
-
对象:对象是类的一个实例,有状态和行为
-
类的5大成分(有且仅有):
- 成员变量Field:描述类或者对象的属性信息的
- 成员方法Method:描述类或者对象的行为的
- 构造器Constructor:初始化类的一个对象返回
- 代码块:局部代码块是定义在方法或语句中。控制变量的生命周期(作用域)
- 内部类:就是将一个类的定义放在另一个类的内部
eg:
public class ClassDemo1{ System.out.println("hello world"); } //报错原因:不是类的5大成分
-
定义类的格式:
修饰符 class 类名{ }
注意:
- 类名的首字母应该大写,满足”驼峰式写法“
- 一个Java代码文件中可以定义多个类 。但是只能有一个类是用public修饰的,而且public修饰的类名必须成为代码的文件名称。
-
构造器的格式:
修饰符 类名(形参){ }
注意:一个类默认会自带一个无参构造器,即使不写它也存在,但是如果一个类它写了一个构造器,那么默认的无参数构造器就被覆盖了
this关键字
this关键字的作用:
- this代表了当前对象的引用。
- this关键字可以用在实例方法和构造器中
- this用在方法中,谁调用这个方法,this就代表谁
- this用在构造器,代表了构造器正在对初始化的那个对象的调用
注意:当不会出现冲突时,this关键字可以省略,例如在getter方法中,就省略了this关键字
public Integer getAge() {
return age;
}
封装
面向对象三大特征:封装,继承,多态
封装的作用:
- 可以提高安全性
- 可以使代码组件化
封装的规范:
- 建议成员变量都私有
- 提供成套的getter/setter方法暴露成员变量的取值和赋值。
核心思想:合理隐藏,合理暴露
static
Java通过成员变量是否有static修饰来区分是属于类的还是属于对象的
static(静态)修饰的成员(方法和成员变量)属于类本身的
成员变量:
-
静态成员变量(类变量)
有static修饰的成员变量称为静态成员变量,属于类本身的,直接用类名访问即可,与类一起加载一次
-
实例成员变量:
无static修饰的成员变量称为实例成员变量,属于类的每个对象的,必须用类的对象来访问
成员方法:
-
静态方法
有static修饰的成员方法称为静态方法也叫类方法,属于类本身的,直接用类名访问即可
-
实例成员变量:
无static修饰的成员变量称为实例方法,属于类的每个对象的,必须用类的对象来访问
成员对象的访问语法:
- 静态成员变量访问:
- 类名.静态成员变量
- 对象。静态成员变量(不推荐)
- 实例成员变量的访问:
- 对象.实例成员变量
eg:
/**
* @author YJL
*/
public class StaticDemo {
//静态成员变量,直接使用类本身访问即可
public static String str = "hello";
//实例成员变量无static修饰,属于类的对象,必须要调用对象
private String name = "lalala";
private int age;
public static void main(String[] args){
//类名.静态成员变量
System.out.println(StaticDemo.str);
//同一个类访问静态成员变量可以省略不写
System.out.println(str);
StaticDemo sd = new StaticDemo();
//对象.实例成员变量
System.out.println(sd.name);
//对象.静态成员变量(不推荐)
System.out.println(sd.str);
}
}
内存图:
成员方法的访问机制也是类似的
a.静态方法是否可以直接访问实例变量?
不可以的,实例变量必须用对象访问!!
b.静态方法是否可以直接访问静态变量?
可以的,静态成员变量可以被共享访问。
c,静态方法是否可以直接访问实例方法?
不可以的,实例方法必须用对象访问!!
d.静态方法是否可以直接访问静态方法?
可以的,静态方法可以被共享访问!
a.实例方法是否可以直接访问实例成员变量?
可以的,因为它们都属于对象
b.实例方法是否可以直接访问静态成员变量?
可以的,静态成员变量可以被共享访问
c.实例方法是否可以直接访问实例方法?
可以的,实例方法和实例方法都属于对象
d.实例方法是否可以直接访问静态方法?
可以的,静态方法可以被共享访问!
代码块和内部类
静态代码块
必须用static
修饰,属于类,会与类一起优先(比main方法还要优先)加载,而且自动地触发执行一次
格式:
static {
}
作用:静态代码块可以用于再执行类的方法之前进行静态资源的初始化操作
实例代码块
无static
修饰,属于类的每个对象,与类的每个对象一起加载
格式:
{
}
作用:实例代码块可以用于初始化实例化资源
实例代码块中的代码实际上是提取到每个构造器中去执行的
内部类
定义在一个类里面的类就是内部类
作用:可以提供更好的封装性,内部类有更多权限修饰符,封装性有更多的控制,可以体现出组件的思想
静态内部类
有static
修饰,属于外部类本身,会加载一次
类有的成分他都有,静态内部类属于外部类本身,只会加载一次,所以它的特点与外部类是完全一样的,只是位置在别人里面而已
外部类=宿主
内部类=寄主
静态内部类访问格式:
外部类名称.内部类名称
静态内部类创建对象的格式:
外部类名称.内部类名称 对象名称 = new 外部类名称.内部类构造器;
成员内部类(实例内部类)
无static
修饰,属于外部类的对象,所以实例内部类可以访问外部类的实例变量,需要与外部类一起加载
注意:不能在实例内部类中定义静态成员,但是允许定义常量
实例内部类的访问格式:
外部类.内部类名称
创建对象的格式:
外部类名称.内部类名称 对象名称 = new 外部构造器.new 内部构造器
匿名内部类
就是一个没有名字的局部内部类
匿名内部类可以简化代码,也是开发中常用的形式
匿名内部类的格式:
new 类名|抽象类|接口(形参){
方法重写
}
- 匿名内部类是 一个没有名字的内部类
- 匿名内部类一旦写出类,就会立即创建一个匿名内部类的对象返回
- 匿名内部类的对象的类型相当于是当前new的那个的类型的子类0类型
public class AnonymousDemo1 {
public static void main(String[] args) {
Animal a = new Animal() {
@Override
public void eat() {
System.out.println("eat apple");
}
};
a.eat();
}
}
abstract class Animal{
abstract public void eat();
}
继承
被继承的类称为:父类/超类/基类
继承父类的类称为:子类/派生类/孩子类
继承的作用?
“可以提高代码的复用性”,相同代码可以定义在父类中,然后子类直接继承父类,就可以直接使用父类的这些代码
子类更加强大:子类不仅得到了父类的功能,它还有自己的功能
继承的特点:子类继承了一个父类,子类就可以直接得到父类的属性(成员变量)和行为(方法),"is a " 是父类的一个明显的特征
继承的格式:
子类 extends 父类{
}
子类和父类的构造器
特点:子类构造器默(无论有无参数)认一定会先访问父类的无参数构造器,再执行子类自己的构造器
原因:
- 子类的构造器的第一行默认会有一个
super ()
调用父类的午餐构造器,写不写都存在; - 子类继承自父类,子类就得到 了父类的属性和行为,必须点调用父类的构造器初始化父类才能继承父类的属性和行为(先有爸爸,再有儿子)
/*******父类********/
public class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public Employee() {
System.out.println("父类构造");
}
}
/******子类********/
public class Manager extends Employee{
private String execution;
public Manager(String name, int age, String execution) {
super(name, age);
this.execution = execution;
}
public Manager() {
super();//这里不写也会默认访问父类的构造器
System.out.println("子类无参构造");
}
public static void main(String[] args) {
Manager manager1 = new Manager();
manager1.equals("1");
}
}
覆盖方法/方法重写(override)
子类重写一个与父类申明一样的方法来覆盖父类的方法,子类的这个方法就进行了重写
方法重写的校验注解:@override
方法一旦加上这个注解那就必须重写父类的方法,否则报错
方法重写的要求:
- 子类重写方法的名称和形参列表必须与父类重写方法一致
- 子类重写方法的返回值类型范围小于等于父类
- 子类重写方法的修饰符权限应该与父类的修饰符权限相同或者更大
- 子类重写方法声明抛出的异常应该与父类被重写的方法申明抛出的异常一样或者范围更小
“申明不变,重新实现”
但是注意:静态方法和私有方法不能被重写
阻止继承:final类和方法
不允许扩展的类被称为final类。
如果在定义类的时候使用了final修饰符就表明这个类是final类。
public final class Executive extends Manager{
}
类中的特定方法也可以被声明为final,如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动的成为final方法)
public class Employee{
public final String getName(){
return name;
}
}
Object类:所有类的超类
以为Object类是所有类的超类,所以可以使用Object类型的变量引用任何类型的对象:
Object obj = new Employee(“Harry Hacker”,3500);//假设已存在Employee这个类,下同
在Java中,只有基本类型(primitive types)不是对象,例如:数值,字符和布尔类型的之都不是对象
而所有的数组类型,不管是对象数组还是基本类型的数字都扩展了Object类
Employee[] staff = new employee[10];
obj = staff;//OK
obj = new int[10]//OK
equals方法
在Object类中,这个方法将判断两个对象是否具有相同的引用。如果这两个对象具有相同的引用,那么他们一定是相等的。
在重写equals方法时,最好把hasCode方法也一起重写,原因是为了保持哈希结构中key的一致性
重写equals方法要遵循的原则:
- 自反性:对于任何非空引用 x,
x.equals(x)
应该返回 true - 对称性: 对于任何引用 x 和 y, 当且仅当
y.equals(x)
返回 true ,x.equals(y)
也应该返 回 true。 - 传递性: 对于任何引用 x、 y 和 z, 如果
x.equals(y)
返 N true,y.equals(z)
返回 true,x.equals(z)
也应该返回 true。 - 一致性: 如果 x 和 y 引用的对象没有发生变化,反复调用
x.eqimIS(y)
应该返回同样 的结果。 - 对于任意非空引用 x,
x.equals(null)
应该返回 false
equals方法与==的区别:
基本类型的比较只能用==,用equals无法通过编译,因为基本类型不是对象没有继承Object类。
在引用类型的比较中,==比较的只是对象的地址,而equals方法经过重写override后可以用于比较对象中包含的值
附:
String的两种初始化的方式:
-
直接变量赋值:
public class Test { public static void main(String[] args) { String str1 = "Mike"; String str2 = "Milk"; } }
在内存中如下:
给str1赋值时,Java在栈中开辟一个str1的空间,先在常量池中寻找是否已经有了“Mike”,若没有,开辟一个“Mike”空间,若有了“Mike”,则不开辟空间,str2也使用“Mike” 的地址
-
第二种方式是主动创建String类的对象
String str3 = new String("Mikey"); String str4 = new String("Mikey");
创建String类str3变量,在栈中开辟一个空间,创建一个String对象,在堆中新开辟一个空间
str4同理
==比较的是地址值
String s = "111";
String ss = "111";
String sss = "222";
String s1 = new String("333");
String s2 = new String("333");
String s3 = new String("444");
System.out.println(s==ss);//true
System.out.println(s==sss);//false
System.out.println(s1==s2);//false
System.out.println(s1==s3);//false
抽象类与接口
抽象类
抽象类是指在普通类的结构里面增加抽象方法的组成部分。
抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。
格式:
abstract class 类名{
public abstract 方法标签(参数);
}
注意:抽象类是无法直接被实例化的,他需要一个类来实现它的抽象方法
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
- 抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理;
- 抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类;
- 子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。)
接口
接口是更加彻底的抽象,在JDK1.8之前接口只能是抽象方法和常量。
接口体现的是规范思想,实现接口的子类必须重写完接口的全部抽象方法
定义格式:
修饰符 interface 接口名称{
//JDK1.8之前接口只能是抽象方法和常量。
}
接口中的抽象方法可以省略public
abstract
不写,默认会加上
常量:变量值只有一个,而且在程序运行的过程中不可更改
常量的一般修饰符是:public static final
,在接口中常量可以省略public static final
不写,默认会加上
常量的变量名称建议字母全部大写,多个单词用“_”连接
实现接口的类被称为:实现类
类实现接口的格式:
修饰符 class 实现类名称 implements 接口1, 接口2,接口3...{
}//实现类可以多实现
接口与接口之间可以多继承与类的单继承不一样 :
eg:
interface Sportman extends Law, Go {//接口与接口的多继承,用一个接口合并多个接口
void run();
void competition();
}
interface Law{
void rule();
}
interface Go{
void abroad();
}
JDK1.8开始之后接口新增的三个方法:
- 默认方法:就是之前写的实例方法,但是必须用default修饰
- 静态方法:用
static
修饰,默认会加public
修饰,接口的静态方法只能用接口的类名称调用 - 私有方法:就是私有的实例方法,用
private
修饰,只能在本接口中被访问,私有方法通常是给其他私有方法或者默认方法调用的
注意:
- 当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法
- 当一个类实现多个方法时,多个接口中存在同名的默认方法,实现类必须重写这个方法
- 接口中,没有构造器,不能创建对象
多态
同一个类型的对象,执行同一个行为,在不同的状态下会表现出不同的行为特征
多态的方法的调用:”编译看左边,运行看右边“
对于变量的调用:”编译看左边,运行也看左边“
要存在对父类方法的重写多态才有意义
多态的形式:
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;
抽象类 对象名称 = new 实现类构造器;
package stage01.polymorphicDemo;
/**
* @author YJL
*/
public class PolymorphicDemo1 {
public static void main(String[] args) {
Animal a = new Dog();
a.eat();//执行dog里重写的方法
a.run();//报错,编译不通过
System.out.println(a.age);//结果输出10
}
}
class Animal{
public void eat(){
public int age = 10;
System.out.println("觅食");
}
}
class Dog extends Animal{
public int age = 20;
public void run(){
System.out.println("im running!");
}
@Override
public void eat() {
System.out.println("吃肉");
}
}
多态的优势:
- 在多态形势下,右边对象可以实现组件化切换,业务功能也随之改变,便于维护和拓展,可以实现类与类之间的解耦
- 实际开发过程中,父类类型作为方法形式参数,传弟子了对象给方法,可以传入一切子类对象进行方法的调用,更能体现出多态的扩展性与便利
多态的劣势:
- 多态形势下,不能直接调用子类特有的功能
为了解决多态的劣势,可以采用强制类型转换的方法把对象转成子类对象:(以上面的代码为例)
if(a instanceof Dog){
Dog d = (Dog) a;
}