Java面向对象有三大特点:继承(只能单一继承,不能多继承)封装(对象数据和操作该对象的指令都是对象自身的一部分,能够实现尽可能对外部隐藏数据。) 多态。
一、对象和类
什么是类?类是构造对象的模板或蓝图。什么是对象?是由类创建的实例,即通过类模板来创建具体的对象实例。对象有三个主要特征:对象的行为(可以对该对象施加那些操作或者说对象中包含哪些方法)对象的状态(施加方法后对象如何响应,体现在对象数据内容的变换)对象的标识(辨别具有相同行为与状态的不同对象,通常使用不同的对象变量)
/*
* 对象和类的基础演示
*/
//类的创建
class Student{
//定义数据域
private String name;
private int age;
private final int number;//由于被final修饰值确定后无法改变,只能被赋值一次。
public static final String SCHOOLNAME = "HNUST"; //定义静态常量,一般很少使用静态变量,静态常量是属于类的数据,可在外部通过类名访问,由于被final修饰值确定后无法改变
//对于final关键词修饰对象只是表示存储在变量中的对象引用不会再指示其他对象。不过对象可以改变。
/*
* 创建构造函数或者说是构造器
* 要求:构造器与类同名,每个类有一个以上的构造起,构造器可以有0个以上的参数,构造器没有返回值,构造器总是伴随着new操作一起调用
* */
public Student(String name, int age, int number){
this.name = name; //this表示隐式参数,this用途:使用this关键字引用成员变量,使用this关键字在自身构造方法内部引用其它构造方法,使用this关键字代表自身类的对象,使用this关键字引用成员方法
this.age = age;
this.number = number;
}
//构造器重载:下面的是无参数构造器,会设置对象的初始值。
public Student(){
this.name = "";
this.age = 0;
this.number = 0;
}
//在一个构造器中调用另一个构造器,需要this()来进行调用。并且放在当前构造器的第一行。
//定义方法
//更改器方法
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
//访问器方法:只访问对象不改变对象
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public int getNumber(){
return this.number;
}
//静态方法:静态方法属于类,不属于某个对象。静态方法只能操作静态域。
public static String getSchool(){
return SCHOOLNAME;
}
}
//java中总是采用按值调用。方法的到的是所有参数值的一个拷贝,特别是方法不能改变传递给他的任何参数变量的内容。一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不同了,就相当于指针。
//Java中方法参数使用:方法不能改变一个基本数据类型的参数,方法可以改变一个对象参数的状态,方法不能让对象参数引用一个新的对象。
public class Main {
public static void main(String[] args) {
Student student = new Student("hank", 20, 6);
System.out.println(student.getName() + " " + student.getAge() + " " + student.getNumber() + " " + Student.getSchool());
student.setAge(21);
student.setName("hkh");
System.out.println(student.getName() + " " + student.getAge() + " " + student.getNumber() + " " + Student.getSchool());
}
}
类的设计技巧:1、一定要保证数据私有。2、一定要对数据初始化。3、不要再类中使用过多的基本数据类型。4、不是所有域都需要独立的域访问器和域更改器。5、将职责过多的类进行分解。6、类名和类方法要能够体现他们的职责。7、优先使用不可变类。
二、继承
类的继承,可以实现复用已有类的方法和域。关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类,基类,或父类。新类称为子类,派生类或孩子类。
/* * 类的继承 */ class Animal{ private String name = ""; private int age = 0; public Animal(String name, int age){ this.name = name; this.age = age; } public String getName(){ return this.name; } public int getAge(){ return this.age; } public void act(){ System.out.println("我能动···"); } } class Bird extends Animal{ private int weight; public Bird(String name, int age, int weight){ super(name, age); //通过super关键词对父类构造器的调用 this.weight = weight; } public int getWeight() { return weight; } //对父类方法的覆盖重写 public void act(){ System.out.println("我能飞···"); } } public class Main { public static void main(String[] args) { Bird bird = new Bird("hank", 2, 10); System.out.println(bird.getName());//Bird可以继承Animal的方法。 System.out.println(bird.getAge()); System.out.println(bird.getWeight()); bird.act();//Bird可以覆盖掉Animal的方法。 } }
父类对象与子类对象之间的相互转换问题:1、从子类转换为父类就比较直观。因为在继承关系中“is-a”,很明确的指出子类肯定是父类,例如:Bird肯定是Animal一样。那么就可以直接将子类对象赋值给父类对象变量。2、从父类转换为子类则需要进行强制类型转换,再将父类对象赋值给子类变量。(只能在继承层次中进行类型转换。将父类转换为子类之前,应当使用instanceof进行检查,尽量少使用)
理解方法调用:1、编译器查看对象的声明类型和方法名。2、编译器将查看调用方法时提供的参数类型。(次二步用来确定调用哪一个方法)3、如果是private方法static方法final方法或者构造器,那么编译器将可以准确的知道调用哪个方法,这种调用方式称为静态绑定(在程序运行前就已经知道方法是属于那个类的,在编译的时候就可以连接到类的中,定位到这个方法。在Java中,final、private、static修饰的方法以及构造函数都是静态绑定的,不需程序运行,不需具体的实例对象就可以知道这个方法的具体内容。)。与此对应的便是动态绑定(在程序运行过程中,根据具体的实例对象才能具体确定是哪个方法。)动态绑定是多态性得以实现的重要因素,它通过方法表来实现:每个类被加载到虚拟机时,在方法区保存元数据,其中,包括一个叫做 方法表(method table)的东西,表中记录了这个类定义的方法的指针,每个表项指向一个具体的方法代码。如果这个类重写了父类中的某个方法,则对应表项指向新的代码实现处。从父类继承来的方法位于子类定义的方法的前面。(绑定:把一个方法与其所在的类/对象 关联起来叫做方法的绑定。绑定分为静态绑定(前期绑定)和动态绑定(后期绑定))。
注意:
子类继承父类的结果:
1.子类继承父类后,继承到了父类所有的属性和方法。 注:是所有
2.子类可调用的方法也要看情况而定:
子类和父类在同一个包下时 “子类和子类的对象”可以调用父类的默认的,受保护的,公有的属性以及方法
子类和父类在不同的包下时,在子类中可以调用受保护的,公有的属性以及方法,而子类的对象可以调用受保护的,公有的属性以及方法。
方法的重写:
1.当子类和父类都有某种方法,而子类的方法更加要求细致,或者实现功能不同,就需要方法的重写
2.重写条件:
1.必须要存在继承关系;
只有继承之间的关系才能有方法的重写
2.方法的返回值类型,方法名,参数个数,参数类型,参数顺序
抽象类:
抽象类是具有普遍通用性的类。包含一个或多个抽象方法(用abstract修饰的方法)的类为抽象类,并且必须用abstract修饰该类。抽象方法充当着占位的角色,他们的具体实现在子类中。扩展抽象类可以有两种选择:1、在抽象类中定义部分抽象方法或不定义抽象方法,这样子类就必须标记为抽象类。2、定义全部的抽象方法,这样子类就不是抽象类了。
注意:抽象类不能被实例化,但是它的子类可以创建对象。
Java访问权限:
private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
default:即不加任何访问修饰符,通常称为“默认访问权限“或者“包访问权限”。该模式下,只允许在同一个包中进行访问。
protected: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问。
继承的设计技巧:
1、将公共操作和域放在超类
2、不要使用受保护的类
3、使用继承实现“is-a”关系
4、除非所有继承的方法都有意义,否则不要使用继承
5、在覆盖方法时,不要改变预期的行为
6、尽量使用多态
7、不要过多的使用反射(关于反射日后再做详细讨论)
三、接口、lambda表达式与内部类
接口
接口:在Java中接口不是类是对类的一组需求描述。在接口中所有的方法自动地属于public,并且在接口声明时不必提供关键字public。实现接口的步骤:1、将类声明为实现给定的接口。2、对接口中所有的方法进行实现。(实现接口时必须将方法声明为public)
接口的特性:1、接口不是类,尤其不能使用new实例化一个接口。但是接口能声明接口的变量。接口的变量必须引用实现了接口类的对象(这不是接口变量,而是一个接口类型的引用指向了一个实现给接口的对 象,这是java中的一种多态现象。java中的接口不能被实例化,但是可以通过接口引用指向一个对象,这样通过接口来调用方法可以屏蔽掉具体的方法的实现,这是在JAVA编程中经常用到的接口回调,也就是经常说的面向接口的编程)。
2、接口中不能包含实例域或静态方法,但是可以包含常量。与接口方法一样,接口中的域将被自动的设为public static final。按照Java语言规范在程序中不要书写多余的关键字。
3、一个类只能拥有一个父类,但可以实现多个接口(这解决了Java多继承的一部分问题)。
接口中的默认方法:在声明方法时使用defaul声明默认方法。在实现接口时就只需要考虑需要的方法就行了不必实现所有的接口方法。
超类与接口同名方法的冲突处理:1、超类优先。2、对于不同接口的同名方法,超接口将会被覆盖。
lambda表达式
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。lambda表达式的语法:(参数)->{表达式}(如果没有参数也要加上括号)。lambda的表达式的返回类型不需要指定,其返回类型总是会由上下文推导得出。对于只有一个抽象方法的接口,当需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。lambda表达式所能做的也只是能转换为函数式接口。lambda表达式只能被需要函数式接口的方法使用。在lambda表达式中不能声明一个与局部变量同名的参数或局部变量。
方法引用:有时可能已经有现成的方法可以完成你想要传递到其他代码的某个动作,就可以通过方法引用来实现,相当于提供了方法参数的lambda表达式。用法:object::instanceMethod、Class::staticMethod、Class::instanceMethod。要用::双冒号操作符分隔方法名与对象名或类名。
处理lambda表达式:使用lambda表达式的重点是延迟执行。毕竟,如果axing立即执行代码,完全可以直接执行,而无需把它包装在lambda表达式中。原因在于:
1、在一个单独线程中运行代码。2、在算法的适当位置运行代码。(如排序中的比较操作)3、发生某种情况时执行代码。(如按钮点击)4、只在必要时才运行代码。
@FunctionalInterface
interface Calculate {
void applay(int i);
}
public class Main {
public static void main(String[] args) {
//通过匿名内部类使用函数式接口
/*
Calculate calculate = new Calculate() {
@Override
public void applay(int i) {
System.out.println(i);
}
};
*/
//通过lambda使用函数式接口
Calculate calculate = param -> System.out.println(param);
calculate.applay(5);
}
}
内部类(参考大佬)
内部类是定义在另一个类中的类。为什么需要内部类呢?主要原因:1、每个内部类都能独立地继承自一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类都没有影响(内部类可以有效的实现“多重继承”)2、内部类方法可以访问该类定义所在的作用域的数据,包括私有的数据。3、内部类可以对同一个包中的其他类隐藏起来。4、当要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
成员内部类
定义在另一个类(外部类)的内部,而且与成员方法和属性平级叫成员内部类,相当于外部类的非静态方法,如果被static修饰,就变成静态内部类了。
/* * 成员内部类中不能存在static关键字,即,不能声明静态属性、静态方法、静态代码块等。 * 成员内部类可以调用外部类的所有成员,但只有在创建了外部类的对象后,才能调用外部的成员。 * 创建成员内部类的实例使用:外部类名.内部类名 实例名 = 外部类实例名.new 内部类构造方法(参数),可以理解为隐式地保存了一个引用,指向创建它的外部类对象。 * 在成员内部类中访问外部类的成员方法和属性时使用:外部类名.this.成员方法/属性(同名的情况下使用,若不同名,则直接使用即可)。 * 内部类在编译之后生成一个单独的class文件,里面包含该类的定义,所以内部类中定义的方法和变量可以跟父类的方法和变量相同。 * 外部类无法直接访问成员内部类的方法和属性,需要通过内部类的一个实例来访问。 * 与外部类平级的类继承内部类时,其构造方法中需要传入父类的实例对象。且在构造方法的第一句调用“外部类实例名.super(内部类参数)”。 */ class Outer{ private String OutName = "Outer"; public void outPrint(){ System.out.println("I'm " + OutName); } class Inner{ private String inName = "Inner"; public void inPrint(){ System.out.println("I'm " + inName); System.out.println("Outer is " + OutName); outPrint(); } } } public class Main { public static void main(String[] args) { Outer out = new Outer(); Outer.Inner in = out.new Inner(); in.inPrint(); } }
静态内部类(嵌套类)
使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。称为静态内部类(也可称为类内部类),这样的内部类是类级别的,static关键字的作用是把类的成员变成类相关,而不是实例相关。注意:1. 要创建嵌套类的对象,并不需要其外围类的对象。2. 不能从嵌套类的对象中访问非静态的外围类对象。(通常在不需要访问外部类的时候使用静态内部类)
/*
* 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
* 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
* 外部类访问内部类的静态成员:内部类.静态成员
* 外部类访问内部类的非静态成员:实例化内部类即可
*/
class Outer{ private String OutName = "Outer"; static final String CONST = "static"; public void outPrint(){ System.out.println("I'm " + OutName); } static class Inner{ private String inName = "Inner"; public void inPrint(){ System.out.println("I'm " + inName); System.out.println(Outer.CONST); } } } public class Main { public static void main(String[] args) { Outer.Inner in = new Outer.Inner(); //外部类名.内部类名 实例名 = new 外部类实例名.内部类构造方法(参数)
in.inPrint(); } }
局部内部类(方法内部类)
在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,和此外围类所有的成员。可以对外部完全地隐藏。
需要注意的是:
(1)、局部内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
(2)、局部内部类对象不能使用该内部类所在方法的非final局部变量。
(3)、局部类不能加访问修饰符,因为它们不是类成员
class Outer{
public void print(){
class Inner{
public void inprint(){
System.out.println("I'm Inner");
}
}
//局部内部类对于外界是隐藏的,因此需要在内部类里面完成对象的定义和引用
new Inner().inprint();
}
}
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
out.print();
}
}
匿名内部类
- 只用到类的一个实例。
- 类在定义后马上用到。
- 类非常小(SUN推荐是在4行代码以下)
- 给类命名并不会导致你的代码更容易被理解。
-
匿名内部类不能有构造方法。
-
匿名内部类不能定义任何静态成员、方法和类。
-
匿名内部类不能是public,protected,private,static。
-
只能创建匿名内部类的一个实例。
-
一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
-
因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
- **由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法.或者说要扩展实现的类。
abstract class Outer{ abstract void printOut(); } public class Main { public static void main(String[] args) { Outer out = new Outer(){ public void printOut(){ System.out.println("I'm Inner"); } }; out.printOut(); } }