【部分内容来自网络,侵删】
类和面向对象
面向对象的程序核心是由对象组成的,每个对象包含着对用户公开的特定功能和隐藏的实现部分。程序中的很多对象来自 JDK 标准库,而更多的类需要我们程序员自定义。
面向对象有以下特点:
- 面向对象是一种常见的思想,比较符合人们的思考习惯;
- 面向对象可以将复杂的业务逻辑简单化,增强代码复用性;
- 面向对象具有抽象、封装、继承、多态等特性
定义
一个类可以包含以下类型变量:
局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。
一个类还可以定义多个方法。
public class Dog{
String breed;
int age;
String color;
void barking(){
}
void hungry(){
}
void sleeping(){
}
}
类的实例化
类想要使用必须实例化,生成对象Dog dog = new Dog();
类的属性具有默认值,若不赋初值则JAVA会按下表为其添加默认值,按照如下的规则:
数据类型 | 默认值 |
---|---|
boolean | false |
char | '/u0000' |
整形(byte,short,int,long) | 0 |
浮点型(float, double) | +0.0f或+0.0d |
引用型 | null |
java内存结构图
基本数据类型包装类
基本类型 | 包装类型 |
---|---|
byte | Byte |
int | Integer |
short | Short |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
装箱:将基本数据类型封装为包装类对象,利用每一个包装类提供的构造方法实现装箱操作。
拆箱:将包装类中包装的基本数据类型数据取出。
// 自动装箱
Integer integer1 = 1;
// 自动拆箱
int integer2 = integer1;
封装
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装的优点:
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
权限修饰符
访问控制修饰符 | 同类 | 同包 | 子类 | 不同的包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | - |
default | √ | √ | - | - |
private | √ | - | - | - |
this
this关键字主要有三个应用:
this首先是一个对象,它代表调用这个函数的对象。根据面向对象的基本语法,每当调用变量或者函数的时候,都要按照类名.变量(函数)的格式来调用,意即每个变量或函数都必须属于某一个实际的对象而不是一个类(static的除外).在不会产生混淆的地方, this是可以省略的.
this有如下三个作用:
- 当成员变量和局部变量重名时,在方法中使用this时,表示的是该方法所在类中的成员变量。(this是当前对象自己)
- 把自己当作参数传递时,也可以用this.(this作当前参数进行传递)
- 我们会用到一些内部类和匿名类,如事件处理。当在匿名类中用this时,这个this则指的是匿名类或内部类本身。这时如果我们要使用外部类的方法和变量的话,则应该加上外部类的类名。
- 在构造函数中,通过this可以调用同一类中别的构造函数。(必须置于最起始的位置,不能在构造函数以外的任何函数内调用构造函数,在一个构造函数内只能调用一个构造函数)
- this同时传递多个参数。
构造方法
构造方法构造方法就是与类同名的那个方法,作用就是对类进行初始化。 如果你没有定议任何构造方法的形式,程式会为你加上一个无参数的构造方法,但是如果已经有了一个有参数的构造方法,那么无参数的构造方法就不会默认被加上。构造方法前面没有返回值类型的声明,在方法中不能使用return语句返回一个值。
注意:构造方法也可以是私有的,此时在外部将无法实例化该类。
继承
继承是所有OOP语言不可缺少的部分,在java中使用extends关键字来表示继承关系。当创建一个类时,总是在继承,如果没有明确指出要继承的类,就总是隐式地从根类Object进行继承。如果两个类存在继承关系,则子类会自动继承父类的方法和变量,在子类中可以调用父类的方法和变量。在java中,只允许单继承,也就是说 一个类最多只能显示地继承于一个父类。但是一个类却可以被多个类继承,也就是说一个类可以拥有多个子类。
子类继承父类的成员变量
当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:
1、能够继承父类的public和protected成员变量;不能够继承父类的private成员变量;
2、对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;
3、对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。子类继承父类的方法
同样地,子类也并不是完全继承父类的所有方法:
1、能够继承父类的public和protected成员方法;不能够继承父类的private成员方法;
2、对于父类的包访问权限成员方法,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;
3、对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。构造器
子类是不能够继承父类的构造器,但是要注意的是,如果父类的构造器都是带有参数的,则必须在子类的构造器中显示地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
重写
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
方法的重写规则:
- 参数列表必须完全与被重写方法的相同;
- 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 父类的成员方法只能被它的子类重写。
- 声明为final的方法不能被重写。
- 声明为static的方法不能被重写,但是能够被再次声明。
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。 - 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
super
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
- 普通的直接引用
与this类似,super相当于是指向当前对象的父类,这样就可以用super.xxx来引用父类的成员。 - 子类中的成员变量或方法与父类中的成员变量或方法同名
- 引用构造函数
super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
注意:this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
final
- 用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值;
- 用来修饰方法参数,表示在变量的生存期中它的值不能被改变;
- 修饰方法,表示该方法无法被重写;
- 修饰类,表示该类无法被继承。
static
用于方便在没有创建对象的情况下来进行调用(方法/变量)。被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
- static方法 static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
- static变量 static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。 static成员变量的初始化顺序按照定义的顺序进行初始化。
静态代码段和构造代码段
class Demo {
static {
System.out.println("静态代码段");
}
{
System.out.println("构造代码段");
}
public Demo() {
System.out.println("构造器");
}
}
//打印顺序
//静态代码段
//构造代码段
//构造器
java中的初始化顺序(不考虑继承):类加载--> 静态代码段--> 构造代码段--> 构造器
初始化顺序(考虑继承):父类的静态代码段--->子类的静态代码段-->
父类的构造代码段--->父类构造函数--->子类非静态代码段--->子类构造函数
abstract
用abstract修饰的类,即抽象类;用abstract修饰的方法,即抽象方法。
- 抽象方法不能有方法主体。抽象方法不能用private修饰,因为抽象方法必须被子类实现(覆写)
- 抽象类不能实例化,抽象类虽然不能被实例化,但有自己的构造方法,抽象类不能使用finally关键字修饰
- 抽象类中不一定要包含abstrace方法。也就是说,抽象中可以没有abstract方法。
- 一旦类中包含了abstract方法,那类该类必须声明为abstract类。
- 如果一个子类继承自一个抽象类,则必须实现该类的抽象方法,或者继续声明为抽象类。
接口
一种特殊的类,有全局常量和公共方法组成。一个接口中,所有方法为公开、抽象方法;所有的属性都是公开、静态、常量。
一个类实现一个接口的格式: class IAImpl implements IA{ };
一个类实现接口,相当于它继承一个抽象类。
类必须实现接口中的方法,否则其为一抽象类。 实现中接口和类相同。
interface IplayGame{
void PlayGame();
}
interface IplayVideo{
void PlayVideo();
}
class Phone implements IplayGame,IplayVideo{
public void PlayVideo() {
System.out.println("播放视频");
}
public void PlayGame() {
System.out.println("玩游戏");
}
}
- 接口中的属性是常量,即使定义时不添加public static final修饰符,系统也会自动加上。
- 接口的方法只能是抽象方法,总是使用,即使定义时不添加public abstract修饰符,系统也会自动加上。
- jdk1.8后,接口中也可以定义普通成员方法,但不能直接使用,接口中也可以定义静态方法。
interface MyInterface{
String myNewName(String newName);
default String myOldName(){
return "chao";
}
static void test(){
System.out.println("hello");
}
}
- 接口之间支持多继承,类与接口之间支持多实现
- 抽象类是一种模板设计模式,而接口是一种行为规范。
- 接口是like-a关系,而抽象类是is-a关系
- 优先选用接口,尽量少使用抽象类,需要定义子类的行为,同时又要为子类提供共性行为时使用抽象类。
多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。多态即通过动态绑定实现。
Java实现多态有三个必要条件:继承、重写、向上转型。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {//多态
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a; //向下转型
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
- 对于子类和父类同名的成员变量,编译看左,运行看左
- 对于子类和父类同名的成员方法,编译看左,运行看右
在java中,多态大致可以分为以下几种情况:
- person为父类,student为子类。那么:
person p=new student()
- fliable为接口,bird为实现接口的类,那么:
fliable f=new bird()
- fliable为抽象类,bird为继承fliable的类,那么:
fliable f=new bird()
instanceof
instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
result = object instanceof class
#class如果是object对象的父类,自身类,结果返回为True,否则返回false。
多态的优势和弊端:
减少重复代码,使代码变得简洁,提高系统扩展性。只能只用父类方法,无法使用子类特有的方法。
向上转型和向下转型
向上转型:子类引用的对象转换为父类类型称为向上转型。通俗地说就是是将子类对象转为父类对象。此处父类对象可以是接口。
向下转型:与向上转型相对应的就是向下转型了。向下转型是把父类对象转为子类对象。向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型之前,它得先向上转型),向下转型只能转型为本类对象,此外,向下类型 转换必须使用强制类型转换的形式。
方法参数和返回值
- 类作为方法参数和返回值
class User{
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
class UserService {
/*
* 登录
*/
User login(String username, String password){
if(username.equals("sjl") && password.equals("123")){
return new User(username, password);
}
return null;
}
void logout(User user){
}
}
- 抽象类作为方法参数和返回值
abstract class Animal {
abstract void introduct();
}
class Ant extends Animal {
void introduct() {
System.out.println("我是一只小蚂蚁");
}
}
class AnimalService {
private ArrayList<Animal> data = new ArrayList<Animal>();
void add(Animal animal){
data.add(animal);
}
Animal getAnimal(int index){
return data.get(index);
}
}
- 接口作为方法参数和返回值
//接口
interface Smoke{
public abstract void smoking();
}
class Student implements Smoke{
@Override
public void smoking() {
System.out.println("课下吸口烟,赛过活神仙");
}
}
//测试类
public class Test {
public static void main(String[] args) {
//通过多态的方式,创建一个Smoke类型的变量,而这个对象实际是Student
Smoke s = new Student();
//调用method方法
method(s);
}
//定义一个方法method,用来接收一个Smoke类型对象,在方法中调用Smoke对象的show方法
public static void method(Smoke sm){//接口作为参数
//通过sm变量调用smoking方法,这时实际调用的是Student对象中的smoking方法
sm.smoking();
}
}
内部类
内部类是指在一个外部类的内部再定义一个类。
内部类可以直接访问外部类的成员属性,包括私有的,而外部类想要访问内部类的属性,必须创建对象。
广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类
- 成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,如下
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw {
//内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}
类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以这种形式:外部类.this.成员变量;外部类.this.成员方法
。
成员内部类可以无条件地访问外部类的成员,而在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如:Outter.Inner inner = new Outter().new Inner();
- 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
局部内部类访问局部变量时,只能访问被final修饰的局部变量。局部变量随着方法的调用结束而消失,但对象不会立即消失,为了使用局部变量,必须将局部变量用final修饰。
- 匿名内部类
匿名内部类本质上是一个实现该类/接口的子类的匿名对象。使用匿名内部类我们必须要继承一个父类或者实现一个接口。
public abstract class Bird {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract int fly();
}
public class Test {
public void test(Bird bird){
System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
}
public static void main(String[] args) {
Test test = new Test();
test.test(new Bird() {
public int fly() {
return 10000;
}
public String getName() {
return "大雁";
}
});
}
}
匿名内部类本质上是一个实现该类/接口的子类的匿名对象。
- 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
匿名对象
匿名对象:常见一个对象但不会将对象的地址赋值给某个变量,匿名对象可以作为方法的接受参数和返回值。
使用匿名对象的特点:
- 创建匿名对象直接使用,没有变量名
- 匿名对象没有指定其引用变量时,只能使用一次
- 匿名对象可以作为方法的参数和返回值使用
包
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
包的作用:
1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
包的语法为package pkg1[.pkg2[.pkg3…]]
,为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 "import" 语句可完成此功能,import package1[.package2…].(classname|*);
;