面向对象
类与对象的关系
类是一种抽象的数据类型,它是对某一类事物整体描述。即抽象的概念。
例如:人、宠物、车,程序中即是Person类、Pet类、Car类
对象是抽象概念的具体实例。即一个体。
例如:张三、狗、奥迪
代码与现实相反,先有类后有对象。
类实例化后会返回一个自己的对象,通过new实例化。
package Demo;
public class Hello {
public static void main(String[] args) {
Student student1 = new Student();//创建对象
// 对象类型 对象名 对象值
Student student2 = new Student();
}
static class Student{//类最多就两点,属性和方法。
String name;
int age;
}
}
对象与引用的关系
引用 "指向" 对象
使用类类型、数组类型、接口类型声明出的变量,都可以指向对象,这种变量就是引用类型变量,简称引用。对象是通过引用来操作的。
在程序中,创建出对象后,直接使用并不方便,所以一般会用一个引用类型的变量去接收这个对象,这个就是所说的引用指向对象。
对象与方法的关系
静态方法
使用static修饰符修饰的方法,就是静态方法。
调用这种方法的时候,"可以"使用对象调用,也"可以"使用类来调用,但是推荐使用类进行调用,因为静态 方法是属于类的。(静态属性也是一样的)
package Demo;
public class Hello {
public static void main(String[] args) {
say();//使用static修饰的方法可以直接被调用,如果say方法在同一个包下的其他类中如在Student类中,则Student.say();
}
public static void say(){
System.out.println("Good game");//结果为Good game
}
}
非静态方法
没有使用static修饰符修饰的方法,就是非静态方法。
调用这种方法的时候,是"一定"要使用对象的。因为非静态方法是属于对象的。(非静态属性也是一样的)
在项目中方法更多的是非静态的,为了更好的封装,避免直接被外部调用。
package Demo;
public class Hello {
public static void main(String[] args) {
Hello s = new Hello();//创建对象,实例化方法所在的类
s.say();//调用对象中的方法
//new Hello().say(); 也可以,但更推荐上面那种写法
}
public void say(){
System.out.println("Good game");//结果为Good game
}
}
同一类中方法调用
假设同一个类中有a、b两个方法
-
a和b都是非静态方法,相互之间可以直接调用。
public void a(){ b(); } public void b(){ } -
a和b都是静态方法,相互之间可以直接调用。
public static void a(){ b(); } public static void b(){ } -
a静态方法,b是非静态方法,a方法中不能直接调用b方法,但是b方法中可以直接调用a方法. 静态方法不能调用非静态方法!
public static void a(){ b();//报错 } public void b(){ a(); }
值传递和引用传递
调用方法进行传参时,分为值传递和引用传递两种。
如果参数的类型是基本数据类型,那么就是值传递。
如果参数的类型是引用数据类型,那么就是引用传递。
值传递是实参把自己变量本身存的简单数值赋值给形参。
引用传递是实参把自己变量本身存的对象内存地址值赋值给形参。
所以值传递和引用传递本质上是一回事,只不过传递的东西的意义不同而已。
- 值传递
package Demo;
public class Hello {
public static void main(String[] args) {
int a=1;
System.out.println(a);//1
change(a);
System.out.println(a);//1
}//并没有改变a的值
public static void change(int x){//往深的说,a和x的内存地址不一样,改变后不影响各自的取值,与void无关
x=2;
}
}
- 引用传递
package Demo;
public class Hello {
public static void main(String[] args) {
Student a = new Student();
System.out.println(a.age);//0
change(a);
System.out.println(a.age);//2
}
public static void change(Student a) {
a.age = 2;
}
}
class Student{
int age;//初始值为0
}
构造器
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。
-
构造器的特点:
1.必须和类的名字相同
2.必须没有返回类型,也不能写void -
构造器的作用:
1.创建对象本质是调用构造器,当调用完构造器后才算new了一个对象。
2.给对象中的属性初始化赋值。
public class Student{
String name;
//构造器
public Student(){
name = "tom";
}
}
- 默认构造器
初学对象的时候代码中为什么没有构造器?因为在java中,即使我们在编写类的时候没有写构造器,编译之后也会自动添加一个无参构造器,这个无参构造器也被称为默认的构造器。
public class Student{
}//不会在代码中显示,但编译后查看class文件能在Student类中找到
- 构造器重载
除了无参构造器之外,很多时候我们还会使用有参构造器,在创建对象时候可以给属性赋值。
Person.java
package Demo;
public class Person {
String name;
public Person(){//定义有参构造器必须要把无参构造器也写出来,原理是方法重载
name="benson";
}
public Person(String name){
this.name=name;//this.name的name指的是Person的属性name,=右边的name指的是参数的name
}//this关键字指代当前所在类,这里是Person类
}
demo01.java
package Demo;
public class demo01 {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name);//benson
Person person1 = new Person("LJS");//输入参数创建对象时会指向有参构造器
System.out.println(person1.name);//LJS
}
}
内存分析
JDK8后方法区从堆中改成存到本地内存中
封装
制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。
需要让用户知道的暴露出来,不需要让用户了解的全部隐藏起来。这就是封装。
封装的步骤
-
- 使用private修饰需要封装的成员变量。
-
- 提供一个公开的方法设置或者访问私有的属性
设置 通过set方法,命名格式: set属性名(); 属性的首字母要大写
访问 通过get方法,命名格式: get属性名(); 属性的首字母要大写
- 提供一个公开的方法设置或者访问私有的属性
//对象能在类的外部"直接"访问
public class Student{
? public String name;
? public void println(){
? System.out.println(this.name);
? }
}
public class Test{
? public static void main(String[] args){
? Student s = new Student();
? s.name = "tom";
? }
}
在类中一般不会把数据直接暴露在外部的,而使用private(私有)关键字把数据隐藏起来
public class Student{
private String name;
}
public class Test{
public static void main(String[] args){
Student s = new Student(); //编译报错,在类的外部不能直接访问类中的私有成员
s.name = "tom";
}
}
如果在类的外部需要访问这些私有属性,那么可以在类中提供对于的get和set方法,以便让用户在类的外部可以间接的访问到私有属性
//set负责给属性赋值
//get负责返回属性的值
public class Student{
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.setName("tom");
System.out.println(s.getName());
}
}
继承
- 继承的本质在于抽象。类是对对象的抽象,继承是对某一批类的抽象。
- 继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。
public class Student extends Person{ //Student是子类,Person是父类,student is a Person
}
- JAVA中类只有单继承,没有多继承,即除了object类,一个子类只能继承一个父类。ps:接口可以多继承
- 父类中的属性和方法可以被子类继承
父类中的属性和方法使用public修饰,在子类中继承后"可以直接"使用
父类中的属性和方法使用private修饰,在子类中继承后"不可以直接"使用,父类要提供get/set方法
父类中的构造器是不能被子类继承的,但是子类的构造器中,会隐式的调用父类中的无参构造器(默认使用super关键字)
用final修饰的类不能被继承,没有子类。final断子绝孙!
Object类
java中的每一个类都是"直接" 或者 "间接"的继承了Object类.所以每一个对象都和Object类有"is a"的关系。
从API文档中,可以看到任何一个类最上层的父类都是Object。(Object类本身除外)AnyClass is aObject。
Super关键字
子类继承父类之后,在子类中可以使用this来表示访问或调用子类中的属性或方法,使用super就表示访问或调用父类中的属性和方法。
- 访问父类中的属性
- 调用父类中的方法
- 调用父类的构造器
-
- 父类无构造器
- 父类无构造器
-
- 显式地调用父类的有参构造器
- 显式地调用父类的有参构造器
super使用的注意的地方
- 用super调用父类构造方法,必须是构造方法中的第一个语句。
- super只能出现在子类的方法或者构造方法中。
- super 和 this 不能够同时调用构造方法。(因为this也是在构造方法的第一个语句)
方法重写override
涉及多态,理解就好
静态方法是类的方法,有static时因为b是B类定义的,所以b调用B类的方法
非静态方法是对象的方法,没有static时b调用的是对象的方法,因为b是A类new出来的对象,所以b调用A的方法
重写的前提是继承,子类重写父类的方法。只针对类的方法,与属性无关。非静态方法才算重写。private方法不能重写
而重载是在同一个类中,方法名相同,参数列表不同。
重写语法
- 方法名必须相同
- 参数列表必须相同
- 访问控制修饰符可以被扩大,但是不能被缩小: public>protected>default>private,如protected->public
- 抛出异常类型的范围可以被缩小,但是不能被扩大(本来欠1k,创建子类后不能欠1w,可以欠100)
ClassNotFoundException- 返回类型可以相同,也可以不同,如果不同的话,子类重写后的方法返回类型必须是父类方法返回类型的子类型
例如:父类方法的返回类型是Person,子类重写后的返回类可以是Person也可以是Person的子类型 - 返回类型可以相同,也可以不同,如果不同的话,子类重写后的方法返回类型必须是父类方法返回类型的子类型
为什么要重写?
子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需要把方法进行重写。
重写也可以理解成子类的方法覆盖了父类同名的方法
多态
一个对象的实际类型是确定的,如 new Student(); new Person();等
但是这个对象所属的类型(指向对象的引用)可能有很多种,可以是这对象实际类型的任意父类型。
- 虽然new的是Student,但依旧走的是父类的方法,原理是子类继承了父类的非private全部的方法。
- 子类重写父类的方法之后,父类的引用会执行子类的方法
- 父类不能引用子类独有的方法,如eat
总结: - 多态也是针对方法的,属性没有多态。
- 多态的前提是继承关系和方法重写
- 对象能执行哪些方法主要看对象左边的类型,如Student s1的Student,Person s2的Person。与右边new Student();的关系不大
简单来说就是哪个类型的就执行谁的,调用的方法子类父类都有的时候,就执行子类的。 - 子类Student能调用的只有自己的或者从父类中继承的方法
- 父类Person可以指向子类,但不能调用子类独有的方法,除非强制转换类型
- static、final、private修饰的方法没有多态
instanceof
a instanceof A:判断对象a是否为类A的实例或者子类,如果是则true,不是则false。
a只与=右边有关,看是谁的对象,不用管对象的类型。
引用类型的强制转换
父类不能引用子类独有的方法,如图
因为对象s是Person类型的,要转换成Student类型,就是高转低,所以要强制转换(向下转型)
低转高能自动转换,(向上转型)子类转父类,但是会丢失独有的方法。如图,s2调用不了go方法了
static
在类中,使用static修饰的成员变量,就是静态变量,反之为非静态变量。使用static修饰的方法同理
静态变量属于类的,"可以"使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问。
public class Student{
private static int age;
private double score;
public static void main(String[] args) {
Student s = new Student(); //推荐使用类名访问静态成员
System.out.println(Student.age);
System.out.println(s.age);
System.out.println(s.score);
}
}
代码块和静态代码块
public class Person {
//没有名字的俩大括号就叫(匿名)代码块
{
}
//加个static就叫静态代码块
static{
}
}
因为没有名字,在程序并不能主动调用这些代码块。
匿名代码块是在创建对象的时候自动执行的,并且在构造器执行之前。同时匿名代码块在每次创建对象的时候都会自动执行。
静态代码块是在类加载完成之后就自动执行,并且只执行一次。ps:每个类在第一次被使用的时候就会被加载,并且一般只会加载一次
匿名代码块和静态代码块的作用:
匿名代码块的作用是给对象的成员变量初始化赋值,但是因为构造器也能完成这项工作,所以匿名代码块使用的并不多。
静态代码块的作用是给类中的静态成员变量初始化赋值。
静态导入包
静态导包就是java包的静态导入,用import static代替import,导入这个类里的静态方法。
好处:这种方法的好处就是可以简化一些操作
例如打印操作System.out.println(…);就可以将其写入一个静态方法print(…),在使用时直接print(…)就可以了。
但是这种方法建议在有很多重复调用的时候使用,如果仅有一到两次调用,不如直接写来的方便。
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
//之前是需要Math.random()调用的,即System.out.println(Math.random());
System.out.println(random());
System.out.println(PI);
}
}
抽象类abstract
abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
抽象类中可以没有抽象方法,但是有抽象方法一定在抽象类中。
语法
public abstract class Action{ //抽象类,大括号内可以为空(毕竟连普通类都可以为空)
public abstract void doSomething(); //抽象方法,只有方法名,没有参数列表以及方法体。
}
特点和作用
抽象类,不能使用new关键字来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
ps:子类继承抽象类后,需要实现抽象类中没有实现的抽象方法,否则这个子类也要声明为抽象类。
抽象类不能被实例化,那它有构造器吗?
抽象类的目的就是为实现多态中的共同点,抽象类的构造器会在子类实例化时调用,因此它也是用来实现多态中的共同点构造,不建议这样使用!
为什么要编写抽象类、抽象方法?
打个比方,要做一个游戏。如果要创建一个角色,如果反复创建类和方法会很繁琐和麻烦。
建一个抽象类后。若要创建角色可直接继承抽象类中的字段和方法,而抽象类中又有抽象方法。
如果一个角色有很多种职业,每个职业又有很多技能,要是依次实例这些技能方法会显得想当笨拙。
定义抽象方法,在需要时继承后重写调用,可以省去很多代码。
总之抽象类和抽象方法起到一个框架作用。很方便后期的调用和重写抽象方法是为了程序的可扩展性。重写抽象方法时即可实现同名方法但又非同目的的要求。
接口interface
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法) 都有
接口:只有规范
为什么需要接口?接口和抽象类的区别?
- 接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了规范和具体实现的分离。
- 抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类具有的公共方法规范。
- 从接口的实现者角度看,接口定义了可以向外部提供的服务。
- 从接口的调用者角度看,接口定义了实现者能提供那些服务。
- 接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的系统之间模块之间的接口定义好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。
语法:
声明类的关键字是class,声明接口的关键字是interface
- 接口中只能包含public,static,final类型的成员变量和public,abstract类型的成员方法
- 接口中可以不写任何方法,但如果写方法了,该方法必须是抽象方法。默认就是public abstract修饰的,所以可以直接写方法。
- 接口中可以不写任何属性,但如果写属性了,该属性必须是静态常量。默认就是public static final修饰的。ps:可以直接使用接口名访问其属性。因为是public static修饰的
public interface Action{
void run();
int AGE=99;
}
main:
System.out.println(Action.NAME);
System.out.println(Action.AGE);
接口的实现
接口接口必须通过类来实现它的抽象方法,通过implements实现。
实现接口的类取名规范是接口名+Impl
实现接口的类就必须重写接口中所有的方法
一个类可以实现多个接口
public class Student implements A,B,C,D{
//Student需要实现接口A B C D中所有的抽象方法
//否则Student类就要声明为抽象类,因为有抽象方法没实现
}
main:
A s1 = new Student();
B s2 = new Student();
C s3 = new Student();
D s4 = new Student();
ps:
s1只能调用接口A中声明的方法以及Object中的方法
s2只能调用接口B中声明的方法以及Object中的方法
s3只能调用接口C中声明的方法以及Object中的方法
s4只能调用接口D中声明的方法以及Object中的方法
必要时可以类型强制转换
例如:接口A 中有test() , 接口B 中有run()
A s1 = new Student();
s1.test();
B s2 = new Student();
s2.run();
if(s1 instanceof B){
((B)s1).run();//接口A转换成B
}
接口的继承
一个接口可以继承多个父接口
public interface A{
public void testA();
}
public interface B{
public void testB();
}
//接口C把接口A B中的方法都继承过来了
public interface C extends A,B{
public void testC();
}
//Student相当于实现了A B C三个接口,需要实现所有的抽象方法
//Student的对象也就同时属于A类型 B类型 C类型
public class Student implements C{
public viod testA(){
}
public viod testB(){
}
public viod testC(){
}
}
main:
C o = new Student();
System.out.println(o instanceof A);//true
System.out.println(o instanceof B);//true
System.out.println(o instanceof C);//true
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//编译报错
System.out.println(o instanceof X);
如果o是一个接口类型声明的变量,那么只要X不是一个final修饰的类,该代码就能通过编译
至于其结果是不是true,就要看变量o指向的对象的实际类型,是不是X的子类或者实现类了。
一个类只能继承一个直接的父类,但可以实现多个接口,间接的实现了多继承。
实例:(总览)
interface SwimInterface{
void swim();
}
class Fish{
int fins=4;
}
class Duck {
int leg=2;
void egg(){};
}
class Goldfish extends Fish implements SwimInterface {
@Override
public void swim() {
System.out.println("Goldfish can swim ");
}
}
class SmallDuck extends Duck implements SwimInterface {
public void egg(){
System.out.println("SmallDuck can lay eggs ");
}
@Override
public void swim() {
System.out.println("SmallDuck can swim ");
}
}
public class InterfaceDemo {
public static void main(String[] args) {
Goldfish goldfish=new Goldfish();
goldfish.swim();
SmallDuck smallDuck= new SmallDuck();
smallDuck.swim();
smallDuck.egg();
}
}
内部类
内部类不是在一个java源文件中编写俩个平行的俩个类,而是在一个类的内部再定义另外一个类。
例如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。
内部类分为四种:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
ps:一个class文件中可以有多个类class,但只能有一个公共类public class
成员内部类(实例内部类、非静态内部类)
//在A类中申明了一个B类,此B类就在A的内部,并且在成员变量的位置上,所以就称为成员内部类
public class Outer {
private int id;
public void out(){
System.out.println("这是外部类方法");
}
//ps:成员内部类不能有静态属性和方法
class Inner{
public void in(){
System.out.println("这是内部类方法");
}
}
}
实例化内部类
public class Test{
public static void main(String[] args) {
//实例化成员内部类分两步
//1、实例化外部类
Outer outObject = new Outer();
//2、通过外部类调用内部类
Outer.Inner inObject = outObject.new Inner();
//测试,调用内部类中的方法
inObject.in();
//打印:这是内部类方法
}
}
内部类能干什么?(通用的)
1.访问外部类的所有属性(这里的属性包括私有的成员变量,方法)
2. 如果内部类中的变量名和外部类的成员变量名一样,在内部类中,通过外部类对象.this.属性名来访问外部类属性。
如何调用外部类的成员变量呢?通过Outer.this.成员变量名
原理:
想实例化内部类对象,就必须通过外部类对象,当外部类对象来new出内部类对象时,会把自己(外部类对象)的引用传到了内部类中,所以内部类就可以通过 Outer.this来访问外部类的属性和方法。到这里,你也就可以知道为什么内部类可以访问外部类的属性和方法,这里由于有两个相同的属性名称,所以需要显示的用Outer.this来调用外部类的属性。
平常如果属性名不重复,那么我们在内部类中调用外部类的属性和方法时,前面就隐式的调用了Outer.this。
静态内部类
使用static修饰的内部类就叫静态内部类
static回顾:
一般只修饰变量和方法,平常不可以修饰类,但是内部类却可以被static修饰。
1)static修饰成员变量:整个类的实例共享静态变量
2)static修饰方法:静态方法,只能够访问用static修饰的属性或方法,而非静态方法可以访问static修
饰的方法或属性
3)被static修饰了的成员变量和方法能直接被类名调用。
4)static不能修饰局部变量,切记,不要搞混淆了,static平常就用来修饰成员变量和方法。
public class StaticInnerClassTest {
private String name;
private static int age;
public void run(){
}
public static void go(){
}
//外部类访问静态内部类
public void test(){
StaticInnerClass sic = new StaticInnerClass();
//静态的内部类不需要依赖 外部类,所以不用this
sic.name = "tom";
sic.test1("jack");
StaticInnerClass.age=10;
StaticInnerClass.test2("xixi");
}
private static class StaticInnerClass{
private String name;
private static int age;
public void test1(String name){
System.out.println(name);
System.out.println(this.name);
System.out.println(StaticInnerClass.age);
System.out.println(StaticInnerClassTest.age); //System.out.println(StaticInnerClassTest.this.name);静态类不能访问非静态属性
StaticInnerClassTest.go();
//StaticInnerClassTest.this.run();静态类不能访问非静态方法
}
public static void test2(String name){
//只能访问自己和外部类的静态属性和方法
System.out.println(name);
//System.out.println(this.name);静态方法里面连自己类的非静态属性都不能访问
System.out.println(StaticInnerClass.age);
System.out.println(StaticInnerClassTest.age); //System.out.println(StaticInnerClassTest.this.name);静态方法不能访问非静态属性
StaticInnerClassTest.go();
//StaticInnerClassTest.this.run();静态方法不能访问非静态方法
}
}
}
局部内部类
局部内部类是在一个方法内部声明的一个类
局部内部类中可以访问外部类的成员变量及方法
局部内部类中如果要访问该内部类所在方法中的局部变量,那么这个局部变量就必须是final修饰的
public class Outer {
private int id;
//在method01方法中有一个Inner内部类,这个内部类就称为局部内部类
public void method01(){
class Inner{
public void in(){
System.out.println("这是局部内部类");
}
}
}
}
匿名内部类
在这四种内部类中,以后的工作可能遇到最多的是匿名内部类。
讲匿名内部类要先了解什么是匿名对象?如果一个对象只要使用一次,那么我们就是需要new Object().method()就可以了,而不需要给这个实例保存到该类型变量中去。
public class Test {
public static void main(String[] args) {
//讲new出来的Apple实例赋给apple变量保存起来,但是我们只需要用一次,就可以这样写
Apple apple = new Apple();
apple.eat();
//这种就叫做匿名对象的使用,不把实例保存到变量中。
new Apple().eat();
}
}
class Apple{
public void eat(){
System.out.println("我要被吃了");
}
}