了解面向对象
面向过程&面向对象
面向过程思想(线性思维)
- 步骤清晰简单,第一步做什么,第二步做什么。
- 面对过程适合处理一些较为简单的问题。
面向对象思想(分类思维)
- 物以类聚。分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题。
总结:对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
什么是面向对象
面向对象编程(Object-Oriented Programming,OOP)
- 本质:以类的方式组织代码,以对象的方式组织(封装)数据。
- 面向对象的操作本身是抽象 的,即依靠某些数据中的共同点将他们整合到一起去,用"抽像"可能更好去理解。抽象是一种编程思想。
- 三大特性:
- 封装
- 继承
- 多态
- 从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。
- 从代码运行角度考虑是先有类后有对象。类是对象的模版。
代入对象思想,重新打开方法
方法的调用:
- 静态方法
写入方法时,用static标识,例如:
java
public class Order{
public static void sit(){
System.out.println("坐下!");
}
}
这是一个Order类中的sit方法,若我们要调用它,可以输入:
java
Order.sit();
调用方法为:类名.方法名();
- 非静态方法
顾名思义,就是没有用static标识的方法,这样的方法不能够用上面的操作来调用。
我们需要将它实例化,也就是用new。
java
//实例化
//对象类型 对象名 = 对象值;
Order order = new Order();
- 值传递和引用传递
java
public class Demo1 {
//值传递 因为只是方法之间的调用,无法更改原值
//值传递使用的是赋值,引用传递使用的是地址
public static void main(String[] args) {
int a =1;
System.out.println(a);
Demo1.change(a);
System.out.println(a);
System.out.println("==========================");
Person person = new Person();
System.out.println(person.name);
change2(person);
System.out.println(person.name);
}
public static void change2(Person person){
person.name = "陈";
}
public static void change(int a){
a=10;
}
}
class Person{
String name;
}
类与对象的关系
- 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。
它只能去描述/定义某一类具体事物应该具备的特点和行为。
- 对象是抽象概念的具体事例。
能够体现出特点,展现出功能的事具体的实例,而不是一个抽象的概念。
创建与初始化对象
使用new关键字创建对象。
创建时,出了飞陪内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
- 必须和类的名字相同。
- 必须没有返回类型,也不能写void。
创建一个项目,其中最好只有一个main方法去运行其他方法,这样能够保证精简性。
java
public class Student {
//属性:字段
String name;
int age;
//方法
//this关键字用于本类中的所有方法和属性都能够使用
public void study(){
System.out.println(this.name+"好好学习,天天向上!");
}
}
这是一个功能类,里面的一些功能我们能用另外的main方法来实现。
java
public class Application {
//一个项目保证只有一个main方法
//用来检测运行的
//类:抽象的,实例化
//类实例化后会返回一个自己的对象
//对象就是一个Student类的具体实例
public static void main(String[] args) {
Student xiaoming= new Student();
Student xiaohong= new Student();
//就像上面两个例子,就可以理解为Student的两个对象,
// 属于不同对象,但是拥有的一些特性是相同的
//他们都有名字和年龄
xiaoming.name = "小明";
xiaoming.age = 10;
System.out.println(xiaoming.name);
System.out.println(xiaoming.age);
xiaohong.name = "小红";
xiaohong.age = 9;
System.out.println(xiaohong.name);
System.out.println(xiaohong.age);
}
}
这个就是这个项目的main方法,可以在这个方法中对上面那个类进行创建对象以及封装数据。
构造器
一个类即使什么都不写,它也会存在一个方法。 (可以利用java文件转class文件查看)
- 无参构造
- 使用new关键字,本质是在调用构造器。
- 实际用于初始化值。
java
public Person(){
this.name = "小飞";
}
- 有参构造
java
public Person(String name){
this.name = name;
}
值得注意的是,一旦定义了有参构造,无参构造就必须显示定义。 例如:
java
public Person(){
}
public Person(String name){
this.name = name;
}
即便任何东西不写,都要有一个放在那,这里运用的是重载的思想。
还有个快捷生成构造器的方法:alt+Insert.
- 进入后点击"Constructor"
- 选择想要加入的参数
- 点"OK"则生成有参构造,点"select none"则生成无参构造。
三大特性
封装
"该露的露,该藏得藏。"
就像电视一样,一些复杂的连接设备和机械被封装到我们看不到的地方,我们所能看见的都是一些便于操作的装置。
我们程序设计要追求 "高内聚,低耦合" 。
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉。
- 低耦合:仅暴露少量的方法给外部使用。
封装(数据的隐藏): 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
使用方法:属性私有,get/set。
- 属性私有,使用于属性上,也就是那些字段,在前面加一个private。这样的话就不像之前那样谁都可以访问这个类了。
- get:让其他类能够调用这些私有属性。
- set:让其他类能够设置这些私有属性。
java
//属性私有
private String name;
private int age;
private char sex;
//get让其他类可以得到这个属性
public char getSex() {
return sex;
}
//set可以设置这个属性
public void setSex(char sex) {
this.sex = sex;
}
这就是get/set的用法,不仅如此,我们还可以利用set方法去限制某些不合法的设置,利用if,while这些结构都是可行的。
其他类想要调用就可以如下操作。
java
Animal animal=new Animal();
animal.getName();
animal.setName("Lion");
System.out.println(animal.getName());
还有更快捷的创建方法,像创建构造器一样:
- Alt + Insert
- 选择"getter and setter"就能直接创建了。
作用
- 提高程序的安全性
- 隐藏代码的实际细节
- 统一接口
- 系统可维护性增加了
继承
本质: 是对某一批类的抽象,从而实现对现实世界更好的建模。
extends的意思是"扩展"。子类是父类的扩展。
JAVA中类只有单继承,没有多继承。
注意点:
- 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,用关键字extends来表示。
- 子类和父类之间,从意义上讲应该具有"is a"的关系。例如,学生是人,学生是子类,人是父类。
- 子类继承了父类,就可以拥有父类的全部方法。
- 一般使用继承,父类使用的都是public作为修饰符,如果使用private就不能够直接被子类继承,依然需要封装来转换。
Object类
使用"Ctrl + H"打开,可以看到所有的类都继承在Object类的下面。
在Java中,所有的类,都默认直接或者间接继承Object类。
Super
super关键字用法和this关键字类似,都是用来指定某个范围内的数据。
- this指定的是当前类中的方法或属性。
- super指定的是父类中的方法或属性。
如果父类和子类中出现了名字相同的方法,这个时候super的作用至关重要,可以用来指定需要的到底是哪个方法。
不仅如此,继承中的构造器也有super的存在:
子类如果构造了构造器,里面会有个隐藏代码super(),来调用父类的构造器,所以子类也拥有父类的构造器。
注意点:
- 而super()如果显示了,必须放在构造器中的第一行,否则报错。 this()同理,也必须在构造器的第一行。(所以,它们两个不能同时调用)
- super()中如果不写入变量,则默认调用无参构造,如果写入对应的变量,调用的则是对应变量的有参构造。
举例:
- 子类:
java
public class Son extends Father{
public Son() {
//隐藏代码:调用了父类的无参构造
super();
System.out.println("Son无参构造了");
}
private String name= "儿子";
public void print(){
System.out.println("Son");
}
public void test2(){
print();//Son
this.print();//Son
super.print();//Father
}
public void test1(String name){
System.out.println(name);//张三
System.out.println(this.name);//儿子
System.out.println(super.name);//父亲
}
}
- 父类:
java
public class Father {
public Father(){
System.out.println("Father无参构造了");
}
protected String name = "父亲";
public void print(){
System.out.println("Father");
}
}
- main方法:
java
public class Application {
public static void main(String[] args) {
Son son = new Son();
son.test1("张三");
son.test2();
}
}
方法重写
顾名思义,就是方法的重写,和属性无关。
快捷打开方式:Alt + Insert → Override
方法重写的条件:
- 使用的是非静态的方法。
如果使用的是静态方法,方法的调用只和左边,定义的数据类型有关。
main方法:
java
public static void main(String[] args) {
A a = new A();
B b = new A();
a.test();//A
b.test();//B
}
A类:
java
public class A extends B{
public static void test(){
System.out.println("A");
}
}
B类:
java
public class B {
public static void test(){
System.out.println("B");
}
}
如果使用的是非静态方法,子类重写了父类的方法。此时的打印结果都是A。
- 修饰符使用的是public。
- 存在继承关系。
- 方法名和参数列表必须相同,否则就成重载了。
- 修饰符:范围可以扩大,但不能缩小;
大小排序:public > protected > default >private.
- 方法体可以不同。
为什么需要重写: 父类的功能,子类不一定需要,或者不一定满足。
多态
定义: 同一个方法可以根据发送对象的不同而采用多种不同的行为方式。说白了,就是可以通过改变引用对象来执行同一个方法。
通过new调用时,一个对象的实际类型是确定的。
例如:
java
//new Student();
//new Person();
它们是不同对象的实例类型。
但是,可以指向的引用类型就不确定了:父类的引用指向子类。
例如:
java
//Student能调用的方法都是自己的或者继承父亲的
Student s1 = new Student();
//父亲类,可以指向子类,但是不能调用子类独有的方法
Person s2 = new Student();
Object s3 = new Student();
//对象能执行哪些方法,主要看左边,和右边的关系不大
s1可以执行三个类中的所有方法,s2只能执行Person类和Object类的方法,s3只能执行Object类的方法。
这种情况下,想要让父类调用子类的方法,只能通过类型转换来达成。
例如:
java
((Student)s2).test();
test方法只存在于Student类里面,所以可以将s2的引用类型转换为Student去使用这个方法。
注意事项
- 多态是方法的多态,属性没有多态
- 父类和子类转换要有联系,如果没有联系还要强转会报类型转换异常。
- 存在条件:
- 存在继承关系
- 方法需要重写。若发生了重写(方法名相同),则执行的都是子类的方法;若没有发生重写,各执行各的。
- 父类引用指向子类对象。
instanceof
作用:用于判断一个对象是什么类型。(判断两个类之间是否存在继承关系)
用法:对象名 instanceof 类名.
例如:
现在创建了三个类,Student类、Person类、Teacher类。其中,Student类和Teacher类是Person类的子类。
java
Object xiaoming = new Student();
System.out.println(xiaoming instanceof Student);//T
System.out.println(xiaoming instanceof Person);//T
System.out.println(xiaoming instanceof Object);//T
System.out.println(xiaoming instanceof Teacher);//F
System.out.println(xiaoming instanceof String);//F
xiaoming这个对象是Student类中的,因此与它有"血缘关系"的只有Student类、Person类、Object类。其他类都和它没有关系。
这里我们可以知道,如果有关系,则为true,没关系,则为false。
我们再降个级看看:
java
Person xiaoming = new Student();
System.out.println(xiaoming instanceof Student);//T
System.out.println(xiaoming instanceof Person);//T
System.out.println(xiaoming instanceof Object);//T
System.out.println(xiaoming instanceof Teacher);//F
System.out.println(xiaoming instanceof String);//报错
此时最下面那行会显示编译报错,这是为什么呢?
xiaoming的实例类型是Student类,但是在之前它是被Object类引用的,所以有资格去比较。
就像是祖父带着孙辈的其中一个来比较,嗯,Person是你的父亲,Student是你的亲兄弟,String是你的叔叔,Teacher是你的表兄弟。
但是,现在换成了Person作为父亲带着儿子中的一个来比较,祖父自然是希望的,但是同辈的就不乐意了,"我和你是平辈,你说比就比,那我岂不是很没面子。"然后就选择不赏脸,跑开了。
再再再举个例子:
java
Student xiaoming = new Student();
System.out.println(xiaoming instanceof Student);//T
System.out.println(xiaoming instanceof Person);//T
System.out.println(xiaoming instanceof Object);//T
System.out.println(xiaoming instanceof Teacher);
//报错
System.out.println(xiaoming instanceof String);//报错
用我们上面那个思路,自己领着自己来认亲戚了,爹和爷肯定是乐意的,叔叔和表兄弟就不高兴了,"给脸给多了是吧。"然后两个人就屁颠屁颠地跑远了。
笼统一点,就是编译看左边,运行看右边。
类型转换
- 父 → 子:强制转换 (相当于大 → 小)
就像数据类型一样,需要改变它的引用类型。
java
Person xiaoming = new Student();
//这种情况下,xiaoming是无法去调用Student的独有方法的
//假设它有个独有方法run();
((Student) xiaoming).run();
//这样改造就可以去使用了
//也可以将它分为两步
Student xiaoming = new Student();
xiaoming.run();
//是不是感觉有点low
- 子 → 父
也是直接改就完事儿了。
java
Person person = xiaoming;
但是需要注意的是,如果向上转了,就不能够运用自身原本的一些独有方法了。
static关键字
static一般用于定义静态方法或者静态属性。
java
public class Student {
private static int age;
private double score;
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(s1.score);
System.out.println(s1.age);
System.out.println(Student.age);
System.out.println(Student.score);//编码错误
}
}
从上面代码中可以看出,不仅对象可以调用属性,类也同样可以。但是Student.score却直接报错了,但是Student.age没有报错。
它们之间的区别在于,一个使用了static,一个没有使用。
static标示的静态方法或者属性,是和类一起加载的,也就是说,类存在就可以直接使用,但是非静态不行,需要创建一个对象才能够去使用。
并且优先级也是有区别的。
java
{
System.out.println("匿名代码块");//2
}
static {
System.out.println("静态代码块");//1
}
public Person(){
System.out.println("构造方法");//3
}
public static void main(String[] args) {
Person person = new Person();
}
由此可见,静态 >匿名>构造方法.
注意事项:
- 静态方法只执行一次。
- 匿名代码用来赋初始值。
- static还能用于导入功能包中,即用import引用的类或者方法。这样的话,使用这些方法就不需要在前面加上类名了。
- final关键字相当于"绝育",如果在类上使用,就不能被继承。毕竟常量不可以被改变。
java
public final class animal{}
抽象类
使用abstract来实现。
使用结构:
- 抽象类
java
public abstract class Action {
}
- 抽象方法
java
public abstract void Student();
注意点:
- 抽象方法不能够有方法体,只能像上面代码中一样用;结束,相当于是一种约束作用。
- 抽象类被某个类继承了,那么这个子类必须要实现它的所有方法,也就是需要重写。除非这个子类也是抽象类。
- 不能去 new 这个抽象类,只能让子类去实现它:相当于被约束。
- 抽象类中可以写普通的方法,但是抽象方法必须在抽象类中。
作用
- 定义通用接口和规范
- 包含具体的方法和属性,这些方法和属性可以被子类继承和复用,避免了在每个子类中重复编写相同的代码。
- 抽象类为多态性提供了支持,通过抽象类的引用可以指向不同的子类对象,从而实现不同的行为。
- 提高代码的可维护性和可扩展性。
接口
对比:
- 从类上:
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有
接口:只有规范。自己无法写方法------专业的约束。
- 从关键字上:
声明类的关键字是class,接口的关键字是interface.
定义: 接口就是规范,定义的是一组规则,体现了现实世界中"如果你是···则不许能···"的思想。例如,如果你是汽车,则必须能跑。如果你是飞机,则必须能飞。
本质: 接口的本质就是契约,就像我们的法律一样,制定好后都得遵守。
一个接口的例子:
java
public interface UserService {
//接口中的所有定义其实都是抽象的 public abstract,即便不写也会隐性的表达
void add(String name);
void delete(String name);
void update(String name);
}
类可以实现接口,也就是implements 接口
java
public class UerServiceTest implements UserService{
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
@Override
public void update(String name) {
}
}
使用结构如上,但是需要注意的是:
- 实现了接口的类,就需要重写接口中的方法。
不仅如此,接口还解决了继承中单继承的缺陷,可以让我们在感觉上使用了多继承。
java
public class UerServiceTest implements UserService,TimeService{
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
@Override
public void update(String name) {
}
@Override
public void Timer() {
}
}
我们又添加了一个TimeService的接口,只要重写了它的方法,同样不会报错。
除去方法外,我们再来说说属性。
在接口中定义的属性都是常量而不是变量,所以也同样是不能在接口内改变的。但是一般来说我们不会在接口中写入属性,因为扩展性不强。
java
public interface UserService {
int A = 999;
}
跟上面的隐性抽象方法一样,这里也隐性了一些字:public static final。
java
public interface UserService {
public static final int A = 999;
}
这才是完全体。
作用
- 约束。
- 定义一些方法,让不同的人实现。
- 接口不能被实例化,接口中没有构造方法。
- 类可以实现多个接口,显现出多继承的作用。
- 必须要重写接口中的方法。
内部类
定义:内部类就是在一个类的内部再定义一个类,比如,A类中定义一个B类,那么B类就是A类的内部类,相对而言A类是B类的外部类。
内部类又分为下面几种:
- 成员内部类
java
public class Outer {
public void out(){
System.out.println("out");
}
public class Inner{
public void in(){
System.out.println("in");
}
}
此时Inner就是个典型的内部类。
java
public class Application {
Outer outer = new Outer();
//通过外部类来实例化内部类
Outer.Inner inner = outer.new Inner();
}
想要调用内部类中的方法,我们需要先实例化外部类,再通过外部类来实例化内部类,代码如上。
而内部类也是可以调用外部类的私有属性的,使用get/set就能操作。但是有个前提,就是这个类不是一个静态内部类。因为静态类的优先级高于私有属性,所以创建出这个类的时候,私有属性还不知道在哪呢。
- 静态内部类
- 局部内部类
局部内部类是存在于方法中的内部类。举例如下:
java
public class Outer{
public void method(){
//局部内部类
class Inner{
public void in(){
}
}
}
}
- 匿名内部类
存在在一个class类中的类,可以不用去实例化这个类,也能去使用类中的功能。如下,A类就可以在方法中去调用B类中的方法了。
java
public class A{
public static void main(String[] args){
new B().eat();
}
}
class B{
public void eat(){
}
}
顺带一提,在一个类文件中,要存在新的类,不一定非得是内部类,也可以并存,例如:
java
public class A{
}
class B{
}
这样是能够允许的,一个jJva类中可以有很多class类,但是只能有一个public class。