面向对象
- java实体类中包含什么呢?
属性
,设值器(构造器,get/set方法)
,eauals()方法和hashcode()方法
,目前只能想到这么多,然后规划一下:属性(面向对象部分说)
,属性(类之间的关系(面向对象部分说))
,设值器(封装部分说)
,eauals()方法和hashcode()方法(面向对象部分说)
- 都在流传万物皆对象,那么怎么理解呢? 比如说 拿你的基友说这个问题
- 类中属性 : 拿你的基友说这个问题,那么你的基友就可以理解为这里所说的对象,我问你基友的信息(年龄啊之类的),那么就相当于对象中的信息,所以不管你能想到的任何事情,比如书,电脑,杯子等等物品,都有它的"参数",那么一个物品的物品名字就可以理解为对象名,对应到java中Class Name,那么这个物品名的实体就可以成为一个对象了,对应到java中就是你
new ClassName()
了,请注意的是这是两个不同的概念,你对别人说我的杯子怎么怎么样,他只能想到一个大概的杯子形状,这个时候就只是java class的层面,而你将你喝水的杯子拿给一个人的时候,那么这个人就非常清楚明白的看到这个杯子的"参数了",比如说颜色大小,这个时候就对应到了java中的new ClassName()
了,这个时候,你在跟他说你的杯子,他就已经知道你的杯子的具体样子了,他就已经get到了杯子中的属性并将这个杯子具体化了
//这就是你所说的杯子,你把这个拿给你朋友,你朋友只知道你的杯子有颜色重量高度等一些信息,
//但是啥颜色,多重多高是不知道的
public class Cup {
public String color;
public float weight;
public float height;
//....
}
//******************************
//如果你将这个cup对象拿给你朋友,你朋友就很清楚了,是红色的杯子,多高多重
Cup cup = new Cup();
cup.color = "red";
cup.weight = 20.6F;
cup.height = 100.55F;
- 类之间的引用(关系): 首先明确一点,你和你基友都是属于人,所以你们自然是一个"类",你问你基友他朋友的事,这就是一种关联,你基友是一个对象,你基友的朋友自然也就是一个对象,你问你基友,然后你基友再问他的朋友,所以这里形成的关联就是
你->你基友->你基友的朋友
,你们之间有一种关系纽带,就是你们之间的关系,(再比如说你跟你老爸,是父子关系),对应到java中也就是类之间的引用
//大家都是人...
public class Person {
//这些属性就是每个人都有的属性,你叫啥,多大,年龄等等
public String name;
public String gender ;
public int age;
//这就是代表人和人之间的关系
Person yourFriend;
}
public static void main(String[] args) {
//你自己
Person own = new Person();
own.name = "wangziqiang";
own.gender = "男";
own.age = 20;
//你基友
Person yourFriend = new Person();
yourFriend.name = "xiaoer";
yourFriend.gender = "男";
yourFriend.age = 20;
//你和你基友之间建立关系引用
own.yourFriend = yourFriend;
//你基友的朋友
Person other = new Person();
other.name = "zhangfei";
other.gender = "男";
other.age = 50;
//你基友和你基友的朋友建立关系引用
yourFriend.yourFriend = other;
//你通过你基友问他朋友的年龄,发现你基友有个忘年交
int age = own.yourFriend.yourFriend.age;
}
- 人和人区分:人和人咋区分呢?可以通过长相,身份证,关系都可以,那么对应到类中,我们比如说上面的
Person
,我们可以通过name
来区分,但是单单用name
可不行,天底下多少叫张三的啊,那不就疯了?? ,所以我们区分一个人要将很多信息汇总在一起来区分他,比如说有两个名字一样的,但是可以通过身份证区分啊等等,在类中我们就可以用eauals()方法和hashcode()方法
来综合区分,但是还是需要注意两个方法不得不同的,比如
@Override
public boolean equals(Object o) {
//根据你的三个参数区分你
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name) &&
Objects.equals(gender, person.gender);
}
@Override
public int hashCode() {
//根据你的三个参数区分你
return Objects.hash(name, gender, age);
}
- 如上是我们自己实现的,但是在Object中实现的equals是直接用
==
来判断两个对象是否相等的,==
操作是直接以两个对象的地址作为判断条件的,而hashCode也是返回一个对象的地址,当你用Object中的hashCode方法比较两个对象的时候其实也是比较的两个对象的地址,在这也可以反映出来,java实现的这两个方法不管使用哪一个都可以返回相同的比较结果,所以我们也应该遵守这个规则,如果我们在重写equals时就一定要重写hashcode,以保证比较状态的一致性,如果不这样,就会造成程序的错误,以至于HashMap和HashSet工作不正常,我们也可以使用AutoValue框架和lombok框架可以用来自动生成类的toString和hashCode的一类的方法
- 到这里我把能想到的类中的概念都解释了一下,下面就是面向对象思想中涉及到的封装,继承,多态了
封装
- 封装意思很明确就是将一个东西包起来,如上的
Person
和Cup
类,只要某个类一动手,直接把人家性别的给改了,这是不允许的,所以封装的意图就在于为了隐藏类中的部分属性,以避免可以直接被其他类随意访问,包装起来后更利于一个类的内聚
- 上面个图片自己感觉就很生动形象了,只给其他人留一个小窗口,通过窗口给我的我可以接着,但是你要送个炸弹,我还能给你扔出去,而不是如下这样,任冰雨在脸上胡乱的拍~,如果你这时候在房子里,就不会这样
- 那么对应到类中我们就需要改变一下上面的类,拿
Person
开刀
public class Person {
//加private
private String name;
private String gender ;
private int age;
private Person yourFriend;
//get/set方法
}
- 但是不是所有情况都是加private就可以了,如果存在继承的情况,那么就需要按情况来了,这个之后再说,当修改完了
Person
类之后,我们就应该这样使用了
public static void main(String[] args) {
Person own = new Person();
own.setName("wangziqiang");
own.setGender("男");
own.setAge(20);
Person yourFriend = new Person();
yourFriend.setName("xiaoer");
yourFriend.setGender("男");
yourFriend.setAge(20);
own.setYourFriend(yourFriend);
Person other = new Person();
other.setName("zhangfei");
other.setGender("男");
other.setAge(50);
yourFriend.setYourFriend(other);
int age = own.getYourFriend().getYourFriend().getAge();
}
- 代码是多了,但是保护类的效果是非常显著的,上面是都实现了每个属性的set/get方法,但是好像还是可以随意更改啊,如果你要控制一个属性,对属性对应的get/set方法下手就了,比如我们的性别一生都不变,那么我们可以直接在构造器中初始化好,然后将对应的
setGender()
方法取消掉就好了,具体的代码就不展示了 - 对于构造器代码块,比如如下,假如我们只生产红杯子
public class Cup {
public String color;
public float weight;
public float height;
//构造代码块
{
this.color = "red";
}
public Cup(float weight, float height) {
this.weight = weight;
this.height = height;
}
}
- 我们知道构造代码块是优先于构造器的,其实现原理无非就是将构造代码块中的代码在编译完后直接赋值给变量,我们可以使用相关工具查看编译好的class类
public class Cups {
public String color;
public float weight;
public float height;
//构造代码块
{
this.color = "red";
}
public Cups(float weight, float height) {
this.weight = weight;
this.height = height;
}
}
public class Cup
{
public String color = "red";
public float weight;
public float height;
public Cup(float weight, float height)
{
this.weight = weight;
this.height = height;
}
}
- 但是注意像IO初始化有异常的这种,会被提到构造器中
public class Person {
//加private
private String name;
private String gender ;
private int age;
private FileInputStream in;
{
try {
this.in = new FileInputStream("x");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
}
public class Person
{
private String name;
private String gender;
private int age;
private FileInputStream in;
public Person(String name, String gender, int age)
{
try
{
this.in = new FileInputStream("x");
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
this.name = name;
this.gender = gender;
this.age = age;
}
}
public class Person {
private String name;
private String gender;
private int age;
public String getGender() {
return gender;
}
public void setGender(String gender) {
if ("nan".equals(gender)){
//我只接受我是男的
this.gender = gender;
}else {
//你要改我的性别,你去死吧,不操作代表不接受
}
}
//other getter/setter
}
- 当然上面的逻辑判断可能不太好,但是只是用来说明问题的,这里面可以加逻辑判断
- 至此,我能想到的东西就完全的叙述完了
继承
public class Father {
private String name;
private int age;
protected long money;
@Override
public String toString() {
return "Father{" +
"name='" + name + '\'' +
", age=" + age +
", money=" + money +
'}';
}
}
public class Son extends Father {
private String name;
private int age;
public Son(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Son{" +
"name='" + name + '\'' +
", age=" + age +
", money=" + money +
'}';
}
}
- 如上就可以反映出,在
Son
中我们并没有money的属性,完全来自于父类,所以继承可以使子类获取父类的特定的属性和方法,所以继承就可以实现类的可复用即减少可重复代码的作用 - 但是需要注意的是
final
修饰的类是不允许被继承的,private
修饰的属性和方法也是只有父类拥有的,而对于final
修饰的父类方法,子类是不可重写的 - 上面注意到
money
被子类继承下来了,注意他的修饰符是protected
,所以现在我们需要说一下修饰关键字
-
private
:私有的,修饰方法和属性都属于父类本身,子类是不可见的 -
protected
:只允许一个类的子类继承实现自己的方法和属性 -
default
:这个不是关键字,只是代表一种访问级别,即一个类或方法不加任何修饰的时候,是包内的类可以访问的 -
public
:公开的,即谁都可以访问
- 上面说的访问控制修饰符都是针对外部类来说的,对于类自己来说,类中可以随意访问,排除私有内部类
- 实例化的时候就会涉及到类的初始化,那么java中是从一个类的顶层类开始初始化,直到自己初始化完成才可以,我们可以看一下,
public class Father {
String other = "other";
private String name = "a";
private static int age = 2;
private static String gender = "n";
public Father() {
System.out.println("father");
}
}
public class Son extends Father {
private String name = "s";
private static int age = 2;
public Son() {
System.out.println("son");
}
public static void main(String[] args) {
Son son = new Son();
}
}
/**
* father
* son
*/
- 如上图是在
main
方法中断点时看到的,当son初始化完毕,控制台会输出如上注释内容,也证实了是先初始化父类,然后才是自己,当然Father
是继承Object
的,只是Object没输出,然后我们注意到图片上的内容
- 静态区:父类的
private static
都被继承了下来 - son对象内:父类的
private
也被继承了下来,当然其他比private
访问权限大的也会被继承下来
- 所以到这我们就可以总结一下,子类拥对父类的私有变量具有拥有权,但是不具有使用权,如果想有使用权,可以提供get/set
- 对于静态区,static修饰的变量都是类本身的,所以如片中的并不是son中拥有的,即子类不会继承父类的static变量,这是我自己的认为的.如有不对请指正
- 所以到这,可以说如果子类的静态变量和父类中的静态变量重名了,这样是不属于重写的,只是各自拥有而已
- 好了到这,我还没想起来其他需要注意的,下面要说的就是父子之间的转换问题
- 首先记住,不存在父子关系是不能够转换的
- 如果父类中有三个变量,而子类中比父类多好多变量,那么在强转为父类的时候,这些变量是不能够使用了,就好像是一个大货车过限高杆,会被削掉一部分,那么这样的操作在java中不允许的,这可以看做是向下转型,比如这样的不允许
Father father = new Father();
System.out.println(father);
Son castSon = (Son) father; //ClassCastException
System.out.println(castSon);
- 但是这样的向下转型是可以的,因为虽然是父类的引用,但依旧是子类的类型
Father father = new Son();
System.out.println(father);
System.out.println(father.getClass()); //Son
Son castSon = (Son) father;
System.out.println(castSon);
Son son = new Son();
System.out.println(son);
Father castSon = son;
System.out.println(castSon);
- 使用继承虽然好,但是还是需要注意一些问题,最大坏处就是封装性的破坏,这里涉及到一个词:组合
- 组合的意思很明了,就像是拼积木,所以我们是该用哪一种呢 ?继承的父子类之间的关系是is-a,而组合是has-a,继承即cat是animal,组合就是leg,eyes组成animal,所以如果你实现的逻辑是什么是什么,那么就用继承,如果是某些东西组成一个什么,就使用组合
多态
- 其实上面的转型就是一种多态,但是放到继承也好像合理点,不管放哪里,现在你已经知道了的是多态可以进行转型
- 上面提到的限高杆问题,只是针对真实类型的父类不可以转型为其子类,因为会丢掉东西,但是如下这样因为存在继承关系,会自动的向上转型,但依然会丢掉一些东西,即father虽然真实类型是Son,但是只能使用Father内的东西,而Son中对Father扩展的其他类就不可以了,
Father father = new Son();
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
output:
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
- 上面这道题还是有点意思的,首先我们做这道题要知道的是具体的实现看子类就可以了,下面我们具体来看一下怎么会输出这些东西
- 首先方法调用的优先级为
this.show(O)
super.show(O)
this.show((super)O)
super.show((super)O)
- 好了按照上面这个顺序,我们开始做一个
a1.show(b)
,首先a1类型为A,所以this代表A,其实现也为A,然后在类A中寻找参数为B的方法,发现没有,然后去找A的父类中的show方法,因为A没有父类,排除Object,所以进行第三个判断,(super)O
代表的是(super)B
,所以这里在A中寻找参数为A的方法,发现有此方法,,然后判断a1对象有没有子类实现,没有,所以直接就输出1--A and A
,可以这样表示
- 在A类中搜->
A.show(B)
,未发现 - 在Object类中搜->
Object.show(B)
,未发现 - 在A类中搜->
A.show((super) B) -> A.show(A)
,发现有此方法,然后判断真实类型,发现是A,然后A类的实现决定最后实现
- 我们再来看5的过程
a2.show(c)
,a2的this为A,即定义变量的类型
- 在A类中搜 ->
A.show(C)
,未发现 - 在Object类中搜->
Object.show(C)
,未发现 - 在A类中搜->
A.show(super C) -> A.show(B)
,未发现,此时参数B类型还有父类,往上找,即搜->A.show(B super) -> A.show(A)
,发现此方法,然后查看具体实现类,发现是B类,在B类中找到重写的方法,此方法决定了最终实现,输出5--B and A
- 所以到这通过一个案例基本说明了多态是如何使用的,记住一个对象的具体实现还要看其真实实现类~
- 多态机制遵循的原则概括为:当父类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,自我理解:不对请多指正,就是用谁定义的此变量,那么就决定了调用谁中的成员方法,但是前提是被调用的方法是在父类中定义过的,当执行代码的时候,如果实现类中实现了父类中的方法,那么会执行实现类中重写后的方法