抽象方法
使用abstract修饰的方法,没有方法体,只有声明。它定义的是一种规范,告诉子类必须要给抽象方法提供具体的实现
抽象类
包含抽象方法的类就是抽象类。抽象类通过abstract方法定义规范,要求子类必须定义具体实现。
抽象类使用要点:
- 有抽象方法的类只能定义成抽象类
- 抽象类不能实例化,不能用new来实例化抽象类
- 抽象类可以包含属性、方法和构造器,但构造器不能用new来实例化,只能被子类调用
- 抽象类只能用来被继承
- 抽象方法必须被子类实现
具体用法给出示例:
abstract class A{
int num;
abstract public void say();
}
class B extends A{
//这里必须实现say方法,否则编译错误
public void say(){
System.out.println("我实现了父类(抽象类)的方法!");
}
public void end(){
System.out.println("就到这里结束啦");
}
}
public class Study{
public static void main(String[] args) {
//A a = new A();这里会编译错误,显示A是抽象的,不可被实例化
B b = new B();
b.num = 5;
System.out.println(b.num);
b.say();b.end();
}
}
接口就是一种规范,他定义了一组规则。就比如说,你是一个适龄儿童、少年,那么你就必须接受九年制义务教育,这是国家统一的规则是吧。
接口从本质上来讲还是类,他像是一种契约,如同法律,一旦制定,大家都要做个遵纪守法的好公民。
Ps.面向对象的精髓是对对象的抽象,最能体现这一点的就是接口。
接口是比抽象类还抽象的抽象类,更加规范地对子类进行约束,全面、专业地实现了规范和具体实现的分离。接口不提供任何实现,接口中所有方法都是抽象方法,接口完全面向规范,规定了一批类具有的公共方法规范。(想要更好的理解的话还是要去主动实现,这样更加深刻和直观)
关于普通类、抽象类和接口的区别:
- 普通类:具体实现
- 抽象类:具体实现、规范(抽象方法)
- 接口:规范
接口声明格式:
[访问修饰符] interface 接口名 [extends 父接口1,父接口2···]{
//在看到这里的时候是不是发现了多继承?这在后面会写到
常量定义;
方法定义;
}
- 访问修饰符只能是public或默认设置
- 接口可以多继承
- 接口中的属性只能是常量,总以(默认)public static final修饰,不写就默认为是
- 接口中的方法只能是public abstract,不写就默认为是
- 子类通过implements来实现接口中的规范
- 接口不能创建实例,但是可用于声明引用变量类型
- 一个类实现了接口,必须实现接口中的所有方法,并且这些方法只能是public的
具体的使用如下:
public class Study{
public static void main(String[] args) {
new LittleOne().study();
new LittleOne().help();
new Programmer().work();
new Programmer().help();
//student a = new student();错错错
student a = new LittleOne();//对对对,这是向上自动转型
((LittleOne)a).studyagain();//复习复习,这是向下强制转型
adult b = new Programmer();
((Programmer)b).workagain();
}
}
interface student{
int age = 10;//public static final常量
void study();//public abstract抽象方法
}
interface adult{
int age = 30;
void work();
}
interface Characteristic{
String ch = "善良";
void help();
}
class LittleOne implements student, Characteristic{
//如果方法不是全部都实现了就会编译错误
public void study(){
System.out.println("好好学习天天向上!");
}
public void help(){
System.out.println("助人为乐!");
}
public void studyagain(){
System.out.println("我又来学习了!");
}
}
class Programmer implements adult, Characteristic{
public void work(){
System.out.println("我变秃了,也变强了!");
}
public void help(){
System.out.println("助人为乐!");
}
public void workagain(){
System.out.println("我又来变强了!");
}
}
接口完全支持多继承。子接口扩展某个父接口,会获得父接口中所定义的一切。
实现如下:
public class Study{
public static void main(String[] args) {
f ff = new f();
ff.s1();ff.s2();ff.s3();
}
}
interface A{
int a = 5;
void s1();
}
interface B{
int b = 10;
void s2();
}
interface C extends A, B{
int c = 20;
void s3();
}
class f implements C{
public void s1(){
System.out.println(a);
}
public void s2(){
System.out.println(b);
}
public void s3(){
System.out.println(c);
}
}
内部类是一种特殊的类,它是定义在一个类的内部的类。
一般情况下,类是一个独立的单元,在有些情况下,则把一个类放在另一个类的内部来定义,称作内部类。内部类可以用public、default、protected和private修饰,而外部的顶级只能用public和default来修饰。下面给出内部类的演示:
class Outer{
private int num = 99;
public void print(){
System.out.println(num);
}
public class Inner{
private int num = 999;//内部类中可以声明与外部类同名的属性和方法
public void print(){
System.out.println(num);
}
}
}
内部类只是一个编译时的概念,编译成功后会生成两个字节码文件Outer.class和Outer$Inner.class。
- 提供更好的封装,只能由外部类直接访问,而不允许同一个包中的其他类访问
- 内部类可以直接访问外部类的私有属性,内部类被当做其外部类的成员。但是外部类不能访问内部类的内部属性和方法
- 接口只是解决了多重继承的部分问题,内部类使得多重继承的解决方案更完整
在只为外部类提供服务的情况下可以优先考虑使用内部类
使用内部类间接实现多继承
成员内部类可以使用public、defaule、protected和private修饰。
①非静态内部类
1°非静态内部类必须寄存在一个外部类的对象里。非静态内部类对象单独属于外部类的某个对象。
2°非静态内部类可以直接访问外部类的成员(属性和方法),但是外部类不能直接访问非静态内部类成员。
3°非静态内部类不能有静态方法、静态属性和静态初始化块。
4°外部类的静态方法、静态代码不能访问非静态内部类,包括不能使用非静态内部类定义变量,创建实例。
5°成员变量访问:
- 内部类里的方法的局部变量:变量名
- 内部类属性:this.变量名
- 外部类属性/方法:外部类名.this.变量名/方法
6°内部类的访问:
- 在外部类中定义内部类:new 内部类名()
- 在外部类以外的地方使用非静态内部类:外部类名.内部类名 引用变量名 = new 外部类名().new 内部类名()
示例如下:
public class Study{
public static void main(String[] args) {
Outer a = new Outer();
a.print();//外部类的方法
Outer.Inner b = new Outer().new Inner();
b.print();//内部类方法
Outer.Inner c = a.new Inner();
c.print();//内部类方法
}
}
class Outer{
private int num = 3;
//Inner i = new Inner();//在外部类中定义内部类
public void print(){
System.out.println("外部类的成员变量->" + num);
}
public class Inner{
int num = 36;
public void print(){
int num = 369;
Outer.this.print();
System.out.println("内部类方法里的局部变量->" + num);
System.out.println("内部类的成员变量->" + this.num);
System.out.println("外部类的成员变量->" + Outer.this.num);
}
}
}
Static class ClassName{
//类体
}
2°使用要点:
- 一个静态内部类对象的存在,并不一定存在对应的外部类对象,因此,静态内部类的实例方法不能直接访问外部类的实例方法
- 静态内部类可以看做是外部类的一个静态成员,因此,外部类的方法中可以通过“静态内部类.名字”的方法访问静态内部类的静态成员,通过new 静态内部类()访问静态内部类的实例
示例如下
public class Study{
public static void main(String[] args) {
Outer a = new Outer();
a.print();//外部类的方法
//通过new 外部类名.内部类名()来创建内部类对象
Outer.Inner b = new Outer.Inner();
b.print();
}
}
class Outer{
private int num = 3;
public void print(){
Inner i = new Inner();//可以访问内部类的成员和方法
System.out.println("外部类的成员变量->" + num);
System.out.println("内部类成员变量->" + i.num);
i.print();
}
//相对于外部类的一个静态成员
static public class Inner{
int num = 36;
public void print(){
int num = 369;
System.out.println("内部类方法里的局部变量->" + num);
System.out.println("内部类的成员变量->" + this.num);
//System.out.println("外部类的成员变量->" + Outer.this.num);//不能直接访问外部类成员
}
}
}
匿名内部类适合哪种只需要使用一次的类,例如键盘监听等操作。
匿名内部类的语法格式如下:
new 父类构造器(实参类表)/实现接口(){
//匿名内部类类体
}
利用键盘监听器操作的演示如下
this.addWindowListener(new WindowAdapter(){
@override
public void windowClosing(WindowEvent e){
System.exit(0);
}
});//看不懂也没关系,只是个演示而已
this.addKeyListener(new KeyAdapter(){
@override
public void keyPressed(KeyEvent e){
myTank.keyPressed(e);
}
@override
public void keyReleased(KeyEvent e){
myTank.keyReleased(e);
}
});//这两个大括号里就是匿名内部类的写法
匿名匿名,连名字都没有就没有构造器了。还有就是匿名内部类没有访问修饰符。
局部内部类定义在方法的内部,作用于只限于本方法,离开该方法就会失效。局部内部类主要是用来解决比较复杂的问题。(局部内部类在开发中很少应用)
示例如下
public class Study{
public void func(){
class Inner{
public void fun(){
System.out.println("6666666");
}
}
new Inner().fun();
}
public static void main(String[] args) {
new Study().func();
}
}
输出如下
String类又称为不可变字符序列。什么事不可变呢?如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。给出下面的示例:
public class Study{
public static void main(String[] args) {
String s = "abcd";
System.out.println(s);
s = "1234";
System.out.println(s);
}
}
结果如下
这里的s改变了那为什么又叫做不可改变呢?
我们来看图
s是一个引用,一开始s指向的是对象abcd的地址,而后又指向新的对象1234的地址,在这个过程中abcd还存在于内存中,并没有被改变,只不过是s指向了新对象而已。
String位于java.lang包中,Java程序默认导入该包下的所有类。
Java字符串是Unicode字符序列,而非C/C++中的ASCII码。Java中没有内置的字符串类型,而是在标准的Java库中提供了预定义的String类,每个用双引号括起来的字符都是String类的一个实例。
字符串的连接
String s1 = "Hello";
String s2 = "World";
String s = s1 + s2; // s = "HelloWorld";
string s3 = "This " + 18;// 输出的s3是This 18
(我觉得这一块部分要深入了解的话等学完java的基础后再去学习深入理解JVM,这里先做简单的介绍和了解)
常量池有三种:
1.全局字符串常量池(String Pool)
全局字符串常量池中放的内容是在类加载完成后存到Stirng Pool中的,在每个VM中只有一份,存放的是字符串常量的引用值(在堆中生成字符串对象实例)。
2.class文件常量池(Class Constant Pool)
class常量池是在编译时每个class都有的,在编译阶段,它存放的是常量(文本字符串、final常量等)和符号引用。
3.运行时常量池(Runtime Constant Pool)
运行时常量池是在类加载完成后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用保持一致。
常量池演示和分析:
String s1 = "abc";
string s2 = new String("def");
String s3 = "abc";
String s4 = str2.intern();//返回String Pool中的"def"引用值
String s5 = "def";
System.out.println(s1 == s3);//true
System.out.println(s2 == s4);//false
System.out.println(s4 == s5);//true
上面的代码在编译后,会在这个类的class常量池中存放一些字符;当类加载后,将class常量池中存放的字符引用转存到运行时常量池中;然后经过验证、准备阶段后,在堆中生成驻留字符串的实例对象(即s1所指向"abc"的实例对象);再将这个对象的引用存放全局字符串常量池中;最后在解析阶段,把运行时常量池中的字符引用替换成直接引用,于是直接查询String Pool,保证String Pool里地引用值与运行时常量池中的引用值一致。
关于上面的代码,首先,在堆中会有一个"abc" 实例,全局String PooI中存放着"abc" 的一个引用值。在运行第二句的时候会生成两个实例,一个是"def"的实例对象,并且在String Pool中存储一个"def" 的引用值,还有一个是new出来的一个 “def” 的实例对象,与上面那个是不同的实例。在解析s3时查找String Pool,里面有"abc"的全局驻留字符串引用,所以s3的引用地址与之前的那个已经存在的相同。s4是在运行的时候调用intern()函数,返回String Pool中"def" 的引用值,如果没有就将s2的引用值添加进去。在这里,String Pool中已经有"def"的引用值了,所以返回上面在new s2的时候添加到String Pool中的"def" 引用值。最后,s5在解析时就也是指向存在于String Pool中的"def" 的引用值。
这是我自己画的图,乱七八糟的,了解的不全面所以应该是有地方不对的吧(欢迎批评指正)
关于常用的方法可以自行百度吧,用着用着就会了(滑稽)。
开闭原则
指的是让设计的系统对扩展开放,对修改封闭
- 对扩展开放是指应对需求变化要灵活,在新增功能时不需要修改已有的代码,增加新代码即可
- 对修改封闭是指核心部分经过精心设计后,不再因为需求变化而变化
在实际开发中,虽然无法完全做到,但要尽量遵守开闭原则。