核心思想就是“隐藏细节”、“数据安全”,将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。所有的内容对外部不可见。
子类可以继承父类的属性和方法,并对其进行拓展。将其他的功能继承下来继续发展 。
同一种类型的对象执行同一个方法时可以表现出不同的行为特征。通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态。方法的重载本身就是一个多态性的体现。
类表示一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征。 (类似生活中的图纸与实物的概念。)
类必须通过对象才可以使用,对象的所有操作都在类中定义。
类由属性和方法组成:
属性:就相当于人的一个个的特征
方法:就相当于人的一个个的行为,例如:说话、吃饭、唱歌、睡觉
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
类名称 对象名称 = new 类名称() ;
如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式:
访问类中的属性: 对象.属性 ;
调用类中的方法: 对象.方法(实际参数列表) ;
类必须编写在.java文件中;
一个.java文件中,可以存在N个类,但是只能存在一个public修饰的类;
.java文件的文件名必须与public修饰的类名完全一直;
同一个包中不能有重名的类;在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:
1、成员内部类
2、局部内部类
3、匿名内部类
4、静态内部类
Person p = new Person();
在右侧Person后面出现的小括号, 其实就是在调用构造方法
作用:用于对象初始化。
执行时机:在创建对象时,自动调用
特点:所有的Java类中都会至少存在一个构造方法如果一个类中没有明确的编写构造方法, 则编译器会自动生成一个无参的构造方法, 构造方法中没有任何的代码。如果自行编写了任意一个构造器, 则编译器不会再自动生成无参的构造方法。
定义的格式 :
与普通方法基本相同, 区别在于: 方法名称必须与类名相同, 没有返回值类型的声明 ;
建议自定义无参构造方法,不要对编译器形成依赖,避免错误发生。
当类中有非常量成员变量时,建议提供两个版本的构造方法,一个是无参构造方法,一个是全属性做参数的构造方法。
当类中所有成员变量都是常量或者没有成员变量时,建议不提供任何版本的构造。
在Java基础中,this关键字是一个最重要的概念。使用this关键字可以完成以下的操作:
1、调用类中的属性
2、调用类中的方法或构造方法 ,注意:在一个构造方法中,调用另一个构造方法时,调用的代码必须编写在构造方法的第一行。
3、表示当前对象
class Person{
private String name;
private int age;
Person(){
//调用下面的构造方法,如果下面还有代码,必须写在第一行
this("nianphi",18);
}
Person(String name,int age){
//调用类中的属性
this.name=name;
this.age=age;
}
}
概述
static表示“静态”的意思,可以用来修饰成员变量和成员方法。 static的主要作用在于创建独立于具体对象的域变量或者方法 。
简单理解:
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。 并且不会因为对象的多次创建 而在内存中建立多份数据 。
重点 :
静态成员 在类加载时加载并初始化 ;
无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份( 可以理解为所有对象公用 ) ;
在访问时: 静态不能访问非静态 , 非静态可以访问静态 ;
静态修饰的方法,被调用时,有可能对象还未创建 。
class Demo{
public static void main(String[] args){
/*
Emp e1 = new Emp("nianphi","北京");
Emp e2 = new Emp("火炬","北京");
Emp e3 = new Emp("ERROR","北京");
Emp e4 = new Emp("麻子","北京");
假设公司迁址到天津
e1.setRegion("天津");
e2.setRegion("天津");
e3.setRegion("天津");
e4.setRegion("天津");
*/
//上述代码替换地址工作量非常大,所以可以把地址定义成静态变量
Emp.region="北京";
Emp e1 = new Emp("nianphi");
Emp e2 = new Emp("火炬");
Emp e3 = new Emp("ERROR");
Emp e4 = new Emp("麻子");
Emp.region="天津";
}
}
class Emp{
private String name;
//private String region;
static String region;
Emp(String name,String region){
this.name=name;
this.region=region;
}
Emp(String name){
this.name=name;
}
Emp(){}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public String getRegion(){
return region;
}
public void setRegion(String region){
this.region=region;
}
}
//示例二
public class Demo {
public static void main(String[] args) {
Clothes clothes1 = new Clothes();
Clothes clothes2 = new Clothes();
Clothes clothes3 = new Clothes();
}
}
class Clothes{
static int count;
Clothes(){
count++;
System.out.println("序号:"+count);
}
}
//输出:
//序号:1
//序号:2
//序号:3
final用于修饰属性(类里定义的标识符称为属性)和变量(方法体里定义的标识符成为变量:
通过final修饰的属性和变量都是常量,就是不能再次赋值的变量或属性 ;
final修饰的局部变量,只能赋值一次(可以先声明后赋值);
final修饰的成员属性,必须在声明时赋值 ;
全局常量(public static final)可以在任何位置被访问 ;
final修饰的类,不能被继承。
final修饰的方法,不能被子类重写。
通过supper可以访问父类的构造方法、属性、方法。
通过supper调用父类构造方法的代码,必须写在第一行。
supper和this调用构造函数时都需要放在第一行,但是两者不会同时使用,因为不可能调用自身构造函数的同时还调用父类的构造方法
成员内部类是最普通的内部类,它的定义为位于另一个类的内部
public class Demo{
public static void main(String[] args){
//外部使用成员内部类
Outer outter = new Outer(100);
Outer.Inner inner = outter.new Inner();
inner.say(); //输出:200
// 100
}
}
class Outer {
private double x = 0;
public Outer(double x) {
this.x = x;
}
class Inner {
private double x=200;
//内部类
public void say() {
System.out.println(x);
System.out.println(Outer.this.x);
}
}
}
特点: 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。 不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
例如:
interface Person{
public void say();
}
public class Demo{
public static void main(String[] args){
//局部内部类
class PersonImp implements Person{
@Override
public void say(){
System.out.prinln("新编写的局部内部类的say方法内容");
}
}
PersonImp p=new PersonImp();
//这里像调用haha()方法,但是需要一个Person类,为此专门创建一个class文件类很浪费时间,所以使用局部内部类
haha(p);
}
public static void haha(Person p){ }
}
//窗口关闭
public static void main(String[] args){
Frame f=new Frame("QQ登陆器");
f.setVisible(true);
f.setSize(300,200);
class MyWindowListener implements WindowListener{
@Override
public void windowClosing(WindowEvent e){
System.out.println("哈哈哈");
}
}
MyWindowListener l=new MyWindowListener();
//想要添加一个窗口关闭的事件,可以使用局部类
f.addWindowListener(l);
}
匿名内部类没有名字,匿名内部类创建出来只能使用一次
/*new 父类构造器(参数列表)|实现接口() {
匿名内部类的类体部分
}*/
interface Person{
public void say();
}
public class Demo{
public static void main(String[] args){
//匿名内部类
Person p=new Person(){
public void say(){
System.out.println("nianphi我喜欢你");
}
}
haha(p);
}
public static void haha(Person p){ }
}
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
在使用匿名内部类的过程中,我们需要注意如下几点:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或 者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
6、只能访问final型的局部变量。JDK1.8之后变量默认为final类型,但是只要第二次赋值,就不再是final类型的了。
只能访问final类型的局部变量的原因,因为局部类编译的时候是单独编译成一个文件,所以在文件中有final变量的备份。
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。
格式:
public class Demo {
public static void main(String[] args) {
Book.Info info = new Book.Info();
info.say();
}
}
class Book {
static class Info {
public void say(){
System.out.println("这是一本书");
}
}
}
在Java中有一个设计的原则“一切皆对象”,那么这样一来Java中的一些基本的数据类型,就完全不符合于这种设计思想,因为Java中的八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,引入了八种基本数据类型的包装类。
基本数据类型:英文单词全写+首字母大写:如int -> Integer,char -> Character
其中 Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个数字。
Object:Character、Boolean都是Object的直接子类。
基本数据类型和包装类型的区别
1、包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址,基本类型不是
2、包装类型是引用的传递,基本类型是值的传递
3、声明方式不同,基本数据类型不需要new关键字,而包装类型需要new在堆内存中进行new来分配内存空间
4、存储位置不同,基本数据类型直接将值保存在值栈中,而包装类型是把对象放在堆中,然后通过对象的引用来调用他们
5、初始值不同,eg: int的初始值为 0 、 boolean的初始值为false 而包装类型的初始值为null
抽象类必须使用abstract class声明
一个抽象类中可以没有抽象方法。抽象方法必须写在抽象类或者接口中。
abstract class 类名{ // 抽象类
}
只声明而未实现的方法称为抽象方法(未实现指的是:没有“{}”方法体),抽象方法必须使用abstract关键字声明。
格式:
abstract class 类名{
public abstract void 方法名() ; // 抽象方法,只声明而未实现
}
在抽象类的使用中有几个原则:
抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键字new完成。 不能被我们创建,但是jvm虚拟器可以创建。
一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须覆写(重写)抽象类中的全部抽象方法。
常见问题
1、抽象类不能使用final声明,因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能。
2、 抽象类能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法。
抽象类和普通类的区别
1、抽象类必须用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法)。 默认缺省为 public ;
2、抽象类不可以使用new关键字创建对象, 但是在子类创建对象时, 抽象父类也会被JVM实例化 ;
3、如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为 abstract类 ;
接口
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
interface 接口名称{
全局常量 ;
抽象方法 ;
}
接口的继承 extends
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实力域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承的限制:Java中只有单继承,多重继承,没有多继承(即一个子类只能有一个父类)。多重继承通俗来讲就是爷爷、爸爸、孙子。
接口因为都是抽象部分, 不存在具体的实现, 所以允许多继承,例如:
interface C extends A,B{ }
接口与抽象类的区别
1、抽象类要被子类继承,接口要被类实现。
2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
3、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
4、抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
6、接口不能有构造方法,但是抽象类可以有
多态
多态:就是对象的多种表现形式,(多种体现形态)
多态的体现
对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性就从此而来。
ps: 方法的重载 和 重写 也是多态的一种, 不过是方法的多态(相同方法名的多种形态)。
重载: 一个类中方法的多态性体现 。
重写: 子父类中方法的多态性体现。
多态的使用:对象的类型转换
类似于基本数据类型的转换:
向上转型:将子类实例变为父类实例 |- 格式:父类 父类对象 = 子类实例 ;
向下转型:将父类实例变为子类实例 |- 格式:子类 子类对象 = (子类)父类实例 ;
public class Demo{
public static void main(String[] args){
Student student1=new Student();
Nurse nurse1=new Nurse();
//向上转型,父类引用指向子类对象
Person person1=student1;
person1.say(); //输出:我是学生
Person person2=nurse1;
person2.say(); //输出:我是护士
//向下转型
Student student2=(Student)person1;
student2.say(); //输出:我是学生
//向下转型需要注意的是不能把原来是护士的张三转成学生 例如:
Student student3=(Student)person2;
student3.say(); //此处会报错
//向上转型比较高级的用法
Student student4=new Student();
say(student4); //输出:我是学生
}
public static void say(Person person){
person.say();
}
}
abstract class Person{
public abstract void say();
}
class Student extends Person{
@Override
public void say(){
System.out.println("我是学生");
}
}
class Nurse extends Person{
@Override
public void say(){
System.out.println("我是护士")
}
}
方法的重载
方法名称相同, 参数类型或参数长度不同或顺序不同, 可以完成方法的重载 ;
方法的重载与返回值无关;
方法的重载 ,可以让我们在不同的需求下, 通过传递不同的参数调用方法来完成具体的功能。
构造方法的重载
一个类, 可以存在多个构造方法 ;
参数列表的长度或类型不同即可完成构造方法的重载 ;
构造方法的重载 ,可以让我们在不同的创建对象的需求下, 调用不同的方法来完成对象的初始化 。
参数列表必须完全与被重写的方法相同;
返回类型必须完全与被重写的返回类型相同;
访问权限不能比父类被重写的方法的访问权限更低。例如父类方法为public,子类就不能为protected;
父类的成员方法只能被它的子类继承;
声明为static和private的方法不能被重写,但是能够被再次声明;
public class Demo{
public static void main(String[] args){
Student student=new Student();
student.say();
}
}
class Person{
public void say(){
System.out.println("哈吉米哈吉米哈吉米");
}
}
class Student extends Person{
public void say(){
System.out.println("哈吉米弄他北路多");
}
}
//哈吉米弄他北路多
1、重写方法名返回值相同参数相同;
2、重载方法名相同返回值相同参数可以不同,个数可以不同;
3、重写发生在父子类中,重载发生在一个类中;
4、重载与访问权限无关 。
存取的速度特别快
栈存储的特点是:先进后出
存储速度快的原因:
栈内存, 通过 ‘栈指针’ 来创建空间与释放空间 ,指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 。
但是这种移动的方式, 必须要明确移动的大小与范围 ,
明确大小与范围是为了方便指针的移动 , 这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序 的灵活性 ~
所以我们把更大部分的数据存储到了堆内存中。
存放的是类的对象 ;
基本数据类型的数据以及引用数据类型的引用
int a =10;
Person p = new Person();
Java是一个纯面向对象语言, 限制了对象的创建方式 :
所有类的对象都是通过new关键字创建
new关键字, 是指告诉JVM , 需要明确的去创建一个新的对象 , 去开辟一块新的堆内存空间:
堆内存与栈内存不同, 优点在于我们创建对象时 , 不必关注堆内存中需要开辟多少存储空间 , 也不需要关注内存占用
时长 。
堆内存中内存的释放是由GC(垃圾回收器)完成的
垃圾回收器回收堆内存的规则 :当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收 。
所有定义的方法的信息都保存在该区域,此区属于共享区间。
这些区域储存的是:静态变量+常量+类信息(构造方法/接口定义)+运行时常量池。
实例变量存在堆内存中,和方法区无关。
存放的是:类信息,静态的变量,常量,成员方法
方法区中包含了一个特殊的区域常量池,存储的是使用static修饰的成员
实机演示
public class Student{
String name;
int age;
public void study(){
System.out.println("good good study")
}
}
public class TestStudent{
public static void main(String[] args){
Student s = new Student();
System.out.println(s);
System.out.println(s.name + s.age);
s.name = nianphi;
s.age = 18;
System.out.println(s.name + s. age);
s.study()
}
}
/*1.加载clss
2.申明局部变量
3.在堆中开辟空间
4.默认初始化
5.显示初始化
6.构造方法初始化
7.堆内存的地址给局部变量*/