面向对象(下)
继续学习剩下的面向对象知识
java8的包装类
在java中8种数据类型不支持面向对象的编程机制,不具备对象的特征,没有成员变量和方法的调用,为了解决这个问题,java提供了包装类概念。
为8种基本数据类型分别定义了相应的引用类型,并且称为基本数据类型的包装类。
JDK1.5提供了自动装箱和自动拆箱的功能
自动装箱:把一个基本类型变量直接赋值给对应的包装类变量或者Object变量
自动拆箱:与自动装箱相反,允许包装类变量直接赋值给对应的基本类型变量
public class AutoBoxingUnboxing {
public static void main(String[] args) {
//直接把基本类型赋值给Integer包装类对象
Integer inObj=5;
//直接把一个布尔类型赋值给Object类型对象
Object boolObj=true;
//把Integer包装类对象赋值给一个int类型变量
int it=inObj;
//判断bolObj是否属于布尔类型包装类
if(boolObj instanceof Boolean){
//将boolObj强转成布尔类型,在赋值给b
boolean b=(boolean)boolObj;
System.out.println(b);
}
}
}
另外包装类还实现了字符串和基本类型之间的转换。有两种方式:
- 利用包装类提供的parseXxx(String s)静态方法(出了Character不提供这个方法)
- 利用包装类提供的Xxx(String s)构造器
基本来写转换字符串方式:
- String类还提供了多个重载valueOf()方法,用于将基本数据类型转换成字符串。
- 将基本类型变量和""进行连接运算,系统会自动的把基本数据类型转换成字符串类型。
有关其他的java8包装类方法可以查看API文档研究
处理对象
java对象都是Object类的实例,都可以直接调用该类中定义的方法,这些方法提供了处理java对象的通用方法。
打印对象和toString方法
- 使用System.out.println();方法输出内容到控制台
- toString方法是一个特殊的方法,它是一个自我描述的方法,这个方法通常实现这个功能,直接打印该对象时,系统会输出这个对象的自我描述信息,用于告诉外界具有的状态信息
==和equals方法
测试两个变量是否相等有两个方式:
- "" 的作用是判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。(基本数据类型比较的是值,引用数据类型==比较的是内存地址)
- equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
- 类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
类成员
使用static关键字修饰的 成员就是类成员。
理解类成员
在Java中只能包含成员变量,方法,构造器,初始化块,内部类(接口,枚举)等5种成员。用static修饰的成员属于类成员,类变量既可以通过类来访问,也可以通过类的对象来访问。当通过对象来访问类属性时,系统会在底层转化为通过该类来访问类属性。
类成员规则
- 类成员并不是属于实例,它是属于类本身的。只要类存在,类成员就存在。
- 即使通过null对象访问类成员,程序也不会引发NullPointException。
- 类成员不能访问实例对象
public class NullAccessStatic
{
private static void test()
{
System.out.println("hello");
}
public static void main(String[] args)
{
// 定义一个NullAccessStatic变量,其值为null
NullAccessStatic nas = null;
// 使用null对象调用所属类的静态方法
nas.test();
}
}
//结果为输出hello
表明null对象可以访问它所属类成员
单例类
一个类始终只能创建一个实例,这个类就是单例类。
class Singleton {
//使用一个类变量缓存曾经创建的实例
private static Singleton instance;
//对构造器使用private修饰,隐藏构造器
private Singleton() {}
//提供一个静态方法,返回Singleton类型
//该方法加入自定义控制,保证只能产生一个实例
public static Singleton getInstance() {
/*
如果instance为null,则表明还不曾创建Singleton对象
如果instance不为null,则表明已经创建Singleton对象,将不会重新创建新实例
*/
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class SingletonTest{
public static void main(String[] args) {
/*
创建Singleton对象不能通过构造器创建
只能通过getInstance方法得到实例
*/
Singleton a=Singleton.getInstance();
Singleton a2=Singleton.getInstance();
System.out.println(a==a2);
}
}
final修饰符
final用于修饰,类,变量,和方法,一旦获取了初始值就不能改变。
final成员变量
- final修饰的成员变量必须由程序员显式的指定初始化值
- 一旦有了初始值,就不能重新赋值。
final修饰的类变量,实例变量能指定初始值的地方:
- 类变量:必须在静态初始化块中指定初始值或者声明该类变量指定初始值,而且只能在两个地方的其中之一指定
- 实例变量:必须在非静态代码块,声明该实例变量或构造器中指定初始值,只能在三个地方其中之一指定。
final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化,因此使用final修饰局部变量时,既可以指定默认值,也可以不指定默认值。
final修饰的局部变量在定义时没有指定默认值,则可以在后面代码对final变量赋初始值,只能赋值一次,不可以重复;如果已经赋值,则不能对变量再次赋值
public class FinalLocalVariableTest {
public static void main(String[] args) {
//定义一个final的String类型str,并制定值hello
final String str="hello";
// str="cc";会报错
//定义一个final的double类型的d ,不赋初始值
final double d;
//第一次赋值
d=3.14;
//第二次赋值
//d=5.22;报错
}
}
final修饰基本类型变量和引用类型变量的区别
- 当final 修饰 基本类型变量 时,不能对基本类型变量重新赋值.即基本类型变量不能被改变.
- 当final 修饰 引用类型变量 时, 引用类型变量保存的仅仅是一个引用, final只保证这个引用类型变量所引用的地址不能改变, 即一直引用同一个对象, 但是这个对象完全可以改变.
可执行 “宏替换”的final变量
当定义final变量时就指定了初始值,而却该初始值可以在编译时就确定下来,那么final变量本质就是一个"宏变量",编译器会把程序中所有应用该变量的地方直接替换成该变量的值
final方法
final修饰的方法不可被重写,如果不希望子类重写父类方法,则可以使用final修饰符。
public class FinalMethodTest {
public final void test(){}
}
class Sub extends FinalMethodTest{
//下面代码会编译错误,不能重写父类的final方法
public void test(){};
}
final类
final修饰的类不可以有子类。
public final class FinalClass {}
//下面继承父类会编译错误,
class Sub extends FinalClass{}
不可变类
不可变的类是创建类的实例后,该实例的变量是不可改变的。java提供的八个包装类和String类都是不可以变类。
自定义不可变类遵守的规则:
- 使用private和final修饰符来修饰 该类的成员变量
- 提供带参数构造器,用于根据传入参数来初始化类里的成员变量
- 仅为该类的成员变量提供getter方法,不能为该类成员变量提成setter方法,普通方法无法修改final修饰的成员变量
- 如果有必要,修改Object类的hashCode和equals方法
缓存实例的不可变类
1、不可变类的实例状态不可改变,可以被多个对象很方便的共享
2、如果程序经常使用某个不可变类的实例,那么把该实例保存近缓存是一个好的选择,不用每次都生成新的实例对象,消耗系统开销
class CacheImmutale{
private static int MAX_SIZE=10;
//使用数组缓存已有的实例
private static CacheImmutale [] cache=new CacheImmutale[MAX_SIZE];
//记录缓存实例缓存中的位置,cache[pos-1]是最新缓存实例
private static int pos=0;
private final String name;
//构造器
CacheImmutale(String name) {
this.name = name;
}
//getName方法
public String getName() {
return name;
}
public static CacheImmutale valueOf(String name){
//遍历已缓存的对象
for(int i =0;i<MAX_SIZE;i++){
//如果有相同实例,则直接返回缓存的实例
if(cache[i]!=null && cache[i].getName().equals(name)){
return cache[i];
}
}
//如果缓存吃已满
if (pos==MAX_SIZE) {
//把缓存的第一个对象覆盖,即刚刚生成的对象放在缓冲池的最开始位置
cache[0]=new CacheImmutale(name);
//把pos设置为1
pos=1;
}
else{
//把新创建的对象缓存起来,pos+1
cache[pos++]=new CacheImmutale(name);
}
return cache[pos-1];
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj!=null&&obj.getClass()==CacheImmutale.class){
CacheImmutale ci= (CacheImmutale) obj;
return name.equals(ci.getName());
}
return false;
}
public int hashCode(){
return name.hashCode();
}
}
public class CacheImmutaleTest {
public static void main(String[] args) {
CacheImmutale c1=CacheImmutale.valueOf("hello");
CacheImmutale c2=CacheImmutale.valueOf("hello");
System.out.println(c1==c2);
}
}
总结:
1、是否需要隐藏类的构造器,完全取决于系统需求
2、盲目乱用缓存可能导致系统性能下降
3、缓存的对象会占用内存,如果对象只用一次,重复使用的概率不大,缓存该实例就弊大于利
4、如果某个对象需要频繁的调用,缓存该实例就利大于弊
抽象类
抽象方法是只有方法签名,没有方法实现
抽象方法和抽象类
抽象类和抽象方法的规则:
- 抽象类必须使用abstract修饰符来修饰,抽象方法也必须用abstract修饰符修饰,抽象方法不能有方法体
- 抽象类不能被实例化,无法用new关键字来调用抽象类的构造器创建抽象类实例。即抽象类不包含抽象方法也不能被实例化
- 抽象类可以包括变量,方法,构造器,初始化块,内部类5个部分。抽象类的构造器不能创建实例,主要用于子类调用
- 含义抽象方法的类,只能定义为抽象类
//定义一个抽象类颜色
abstract class Color{
abstract public void colorName();
}
class red extends Color{
//通过继承Color类,重写了抽象类方法colorName
@Override
public void colorName() {
System.out.println("红色");
}
}
抽象类的作用
抽象类体现的就是一模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展,改造,但子类总体上会大致保留抽象类的行为方式。
使用模板的简单规则:
- 抽象父类可以 只定义使用某些方法,把不能实现的部分抽象成抽象方法,留给其子类实现。
- 父类中可能包含需要调用其他系列方法的方法,这些调用的方法即可以由父类实现,也可以是其子类实现。父类里提供的方法只是定义一个通用算法,其实现也并不完全由自身实现,而必须依赖其子类的辅助。
java8改进的接口
接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。
Java 8 引入了新的语言特性——默认方法,默认方法是在接口中的方法签名前加上了 default
关键字的实现方法。
public interface InterfaceTest {
//默认方法
default public void a(){
System.out.println("aaaa");
}
}
接口的概念
- 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”。
- 接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成。这样将功能的定义与实现分离,优化了程序设计
注意:一切事物均有功能,即一切事物均有接口。
java8中接口的定义
定义接口使用interface关键字。
[修饰符] interface 接口名 extends 父接口1,父接口2,....
{
零到多个常量定义..
零到多个抽象方法定义...
零到多个内部类,接口,枚举定义...
零到多个默认方法或者类方法定义...
}
上面语法的说明
- 修饰符可以使public或者省略,省略public的话,则默认才用包权限访问,即只能在相同的包结构下才能访问接口
- 接口的命名应该才用与类命名相同的规则。
- 一个接口可以有多个直接父类接口,但接口只能继承接口,不能继承类
注意:
-
接口是一种规范,所以不能定义构造器和初始化块
-
接口可以包含成员变量(只能是静态常量),方法(只能是抽象实例方法,类方法,默认方法),内部类(内部接口,枚举)定义
-
接口里定义了多个类共同的公共行为规范,因此接口里的所有成员,包括常量,方法,内部类,内部枚举都是public访问权限
-
接口定义常量时,不管是否使用public static final 修饰符,接口的成员变量总是使用这个三修饰符来修饰。
//系统自动为接口的成员变量增加public static final修饰符 //所以下面两条语句在接口中是相同的 int MAX_SIZE=50; public static final MAX_SIZE=50;
-
接口定义的方法只能是,抽象方法,类方法,默认方法,如果不是默认方法,系统会自动在普通方法添加public abstract修饰符。接口里普通方法不能有方法实现,但是类方法和默认方法都必须有方法实现
public interface Output {
//接口里的成员变量只能是常量
int MAX_SIZE=50;
//接口定义的普通方法只能是public的抽象方法
void out();
void getData(String msg);
//接口中定义的默认方法,需要使用default修饰
default void print(String... msgs){
for (String msg : msgs) {
System.out.println(msg);
}
}
//接口中定义类方法使用static
static String staticTest(){
return "接口类方法";
}
}
接口的继承
接口的继承和类继承不一样,接口的继承支持多继承,一个接口可以有多个父类接口。使用extends关键字实现继承,父类接口之间可以使用逗号隔开。
interface A{
int PROR_A=5;
void testA();
}
interface B{
int PROR_B=15;
void testB();
}
//继承接口A B
interface C extends A,B{
int PROR_C=25;
void testC();
}
public interface InterfaceExtendsTest {
public static void main(String[] args) {
//通过C接口调用A B接口常量和自身常量
System.out.println(C.PROR_A);
System.out.println(C.PROR_B);
System.out.println(C.PROR_C);
}
}
使用接口
接口不能用于创建实例,但接口可以用于引用类型变量,当使用接口来引用类型变量时,这个引用类型变量必须引用到其实现类的对象。
接口的主要用途:
- 定义变量,也可以用于强制类型转换
- 调用接口中定义的常量
- 被其他类实现
一个类可以实现一个或者多个接口,继承使用extends关键字,实现接口使用implements关键字。
[修饰符] class 类名 extends 父类 implements 接口1,接口2...{
类体部分
}
注意:一个类只能有一个继承类,但是可以有多个接口。
interface A{
int PROR_A=5;
void testA();
}
interface B{
int PROR_B=15;
void testB();
}
//实现接口A B
public class InterfaceExtendsTest implements A,B
{
public static void main(String[] args) {
InterfaceExtendsTest test=new InterfaceExtendsTest();
test.testB();
test.testA();
}
//重写接口A的抽象方法
@Override
public void testA() {
System.out.println("实现A接口");
}
//重写接口B的抽象方法
@Override
public void testB() {
System.out.println("实现B接口");
}
}
接口和抽象类
相同点:
- 都不能被实例化
- 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不相同点:
- 接口只有定义,不能有方法的实现,
java 1.8中可以定义default方法体
,而抽象类可以有定义与实现,方法可在抽象类中实现。- 实现接口的关键字为
implements
,继承抽象类的关键字为extends
。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。- 接口强调特定功能的实现,而抽象类强调所属关系。
- 接口成员变量默认为
public static final
,必须赋初值,不能被修改;其所有的成员方法都是public
、abstract
的。抽象类中成员变量默认default
,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract
修饰,不能被private
、static
、synchronized
和native
等修饰,必须以分号结尾,不带花括号。- 接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。功能需要累积时用抽象类,不需要累积时用接口。
面向接口编程
- 降低程序的耦合性。其能够最大限度的解耦,所谓解耦既是解耦合的意思,它和耦合相对。耦合就是联系,耦合越强,联系越紧密。在程序中紧密的联系并不是一件好的事情,因为两种事物之间联系越紧密,你更换 其中之一的难度就越大,扩展功能和debug的难度也就越大。
- 易于程序的扩展;
- 有利于程序的维护;
内部类
一个类放入另一个类的内部定义,这个定义在其他类内部的类被称为内部类(也可以称嵌套类)。
内部类主要的作用:
- 内部类有更好的封装性,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
- 内部类成员可以直接访问外部类的私有数据,因为内部类也是外部类的成员,同一个类的成员之间可以互相访问。但是外部类不能访问内部类的实现细节
- 匿名内部类适合用于创建那些仅使用一次的类。
注意:内部类出了定义在其他类里面之外,还有两个区别
- 内部类比外部类可以多使用三个修饰符:private, protected,static-----外部类不可以使用三个修饰符
- 非静态内部类不能拥有静态成员
非静态内部类
没有使用static修饰的成员内部类是非静态内部类
注意:非静态内部类里不可以有静态初始化,静态方法,静态变量,但可以包含普通的初始化块。
静态内部类
使用static修饰一个内部类,则这个内部类属于外部类本身,而不属于 外部类的某个对象。(可称为类内部类,静态内部类)
注意:静态内部类不能访问外部类的实例变量,只能访问外部类的类变量,方法也同理。
public class StaticInnerClassTest {
private int prop1=5;
private static int prop2=10;
static class StaticInnerClass{
//静态内部类里定义静态成员
private static int age;
public void accssOuterProp(){
//编译报错,静态内部类不能访问外部类的成员变量
System.out.println(prop1);
//访问外部类的静态变量
System.out.println(prop2);
}
}
}
外部类不能直接访问静态内部类的成员,但是可以使用静态内部类的类名作为调用者访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
public class AccessStaticInnerTest {
//静态内部类StaticInerClass
static class StaticInerClass{
private static int prop1=5;
private int prop2=10;
}
public void accessInnerProp(){
//通过类名访问静态内部类的类成员
System.out.println(StaticInerClass.prop1);
//通过实例访问静态内部类的实例成员
System.out.println(new StaticInerClass().prop2);
}
}
java还允许在接口里定义内部类,接口里的内部类默认使用public static修饰,接口的内部类只能是静态内部类。
使用内部类
内部类定义变量和创建实例与外部类存在一些小差异,分三种情况讨论内部类用法:
-
在外部类内部使用内部类
- 通过new调用内部类构造器创建实例
- 不要再外部类的静态成员中使用非静态内部类,静态成员不能访问非静态内部类
-
在外部类以外使用非静态内部类
控制权限:
- 省略访问控制符的内部类,只能被与外部类处于同一个包中的其他类访问
- 使用protected修饰的内部类,只能被与外部类处于同一个包中的其他类和外部类子类访问
- 使用public修饰的内部类,可以在任何地方被访问
访问语法格式:
-
在外部类以外访问定义内部类变量:OuterClass.InnerClass.varName
-
在外部类以外的地方创建非静态内部类实例:OuterInstance.new InnerConstructor()
class Out{ //定义一个内部类。不使用任何修饰符 //只有同个包中的其他类可以访问 class In{ public In(String msg){ System.out.println(msg); } } } public class CreateInnerInstance { public static void main(String[] args) { Out.In in=new Out().new In("测试"); /*上面代码可以修改成如下 *1.使用OutterClass.InnerClass的形式定义内部类 * Out.In in; *2.创建外部类实例,非静态内部类实例将寄生在该实例中 *Out out=new Out(); *3.通过外部类实例和new来调用内部类构造器创建非静态内部类实例 * in=out.new In("测试"); * */ } }
创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类构造器时,必须存在一个外部类对象
public class SubClass extends Out.In { //显式定义SubClass构造器 public SubClass(Out out) { //通过传入Out对象显式调用In的构造器 out.super("hello"); } }
-
在外部类以外使用静态内部类
-
静态内部类是外部类类相关的,因此创建静态内部类对象时无需创建外部类对象。
-
使用语法:new OuterClass.InnerConstructor()
class StaticOut{ //定义一个静态内部类,不使用任何访问权限修饰符 //一个包中其他类可以访问该内部类 static class StaticIn{ public StaticIn(){ System.out.println("静态内部类构造器"); } } } public class CreateStaticInnerInstance { public static void main(String[] args) { StaticOut.StaticIn in=new StaticOut.StaticIn(); /* 上面代码可以通过两个分解 使用OuterClass.InnerClass的形式定义内部类变量 StaicOut.StaticIn in; 通过new来调用内部类构造器创建静态内部类实例 in=new StaticOut.StaticIn(); */ } }
静态内部类的子类创建
public class StaticSubClass extends StaticOut.StaticIn{}
局部内部类
把一个内部类放进方法里定义,则这个内部类就是一个局部内部类,局部内部类只在该方法有效。
注意:
- 所有局部内部类都不能使用static修饰
- 局部成员的作用域是所在方法,其他程序单元也不可能访问另外一个方法中的局部成员,所以都不使用访问控制权限修饰。
- 局部内部类定义变量,创建实例,派生子类都只能在局部内部类所在的方法内进行
public class LocalInnerClass { public static void main(String[] args) { //局部内部类 class InnerBase{ int a; } //定义局部内部类子类 class InnerSub extends InnerBase{ int b; } //创建局部内部类对象 InnerSub is=new InnerSub(); is.a=5; is.b=6; } }
-
java8改进的匿名内部类
匿名内部类适合创建只需要一次使用的类。
语法:
new 实现接口()|父类构造器(实参列表)
{
//匿名内部类的类体部分
}
匿名内部类必须继承一个父类,或者实现一个接口,做多只能继承一个父类,或者实现一个接口。
规则:
- 匿名内部类不能是抽象类,系统中创建匿名内部类时,会立即创建匿名内部类的对象。因此不能定义成抽象类。
- 匿名内部类不能定义构造器。因为他没有类名,无法定义构造器,但是可以定义初始化块,可以通过实例初始化块完成构造器需要完成的内容
最常用的匿名内部类方式,是需要创建接口类型的对象:
interface Product{
public double getPrice();
public String getName();
}
public class AnonymousTest {
public void test(Product p){
System.out.println(p.getName()+p.getPrice());
}
public static void main(String[] args) {
AnonymousTest ta=new AnonymousTest();
//调用test()方法时,需要传入一个Product参数
//传入匿名内部类实现类实例
ta.test(new Product(){
//重写接口方法
@Override
public double getPrice() {
return 5000.2;
}
//重写接口方法
@Override
public String getName() {
return "ccccc显卡";
}
});
}
}
匿名内部类不是抽象类,所以匿名内部类必须实现它抽象父类或者接口里的所有抽象方法。有需要还可以重写父类的方法
java8新增的Lambda表达式
Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的方式创建接一个抽象方法的接口的实例
Lambda表达式入门
//匿名内部类写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("内部类写法");
}
}).start();
//lambda 写法
new Thread(() -> System.out.println("lambda写法")).start();
从上面例子中看出,Lambda表达式主要用于代替匿名内部类的繁琐语法。由三个部分组成:
- 形参列表。形参列表允许省略形参类型。如果形参类型只有一个参数,甚至连形参列表的圆括号都可以省略不写
- 箭头(->)。必须通过英文中画线号和大于号组成
- 代码块。如果代码块包含一条语句,Lambda表达式允许省略代码块的花括号,那么这条语句就不用花括号表示结束了。Lambda代码块只有一个return语句,甚至可以省略关键字。Lambda表达式需要返回值,而他的代码块中仅有一条省略了return语句,Lambad表达式会自动返回这条语句的值。
interface Eatable{
void taste();
//int abc(int a);
}
interface Flyable{
void fly(String weather);
}
interface Addable{
int add(int a,int b);
}
public class LambdaQs {
//调用该方法需要的Eatable对象
public void eat(Eatable e){
System.out.println("SS"+e);
e.taste();
//System.out.println(e.abc(10));
}
//调用该方法需要的Flyable对象
public void drive(Flyable fly){
System.out.println(fly);
fly.fly("碧空如洗的天空");
}
//调用该方法需要的Addable对象
public void test(Addable add){
System.out.println(add.add(3,5));
}
public static void main(String[] args) {
LambdaQs lq=new LambdaQs();
//Lambda表达式只有一条语句可以省略花括号
lq.eat(()-> System.out.println("CCCC"));
//Lambda表达式形参只有一个时可以省略圆括号
lq.drive(weather->{
System.out.println("今天天气"+weather.toString());
System.out.println("飞行平稳");
});
//Lambda表达式代码只有一条语句时,可以省略花括号
//代码中只有一条语句,即使该表达式需要返回值,可以省略return关键字
lq.test((a,b)-> a+b);
}
}
Lambda表达式与函数式接口
Lambda表达式的类型是“目标类型”,必须是函数式接口。
函数式接口:包含一个抽象方法的接口。但可以包含多个默认方法,类方法,不过只能有声明一个抽象方法
注意:Lambda表达式有两个限制:
- Lambda表达式的目标类型必须是明确函数式接口
- Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此只能为只有一个抽象方法的接口(函数式接口)创建对象
为了保证Lambda表达式的目标类型是一个明确的函数式接口,可以有如下三种方式:
- 将Lambda表达式赋值给函数式接口的变量
- 将Lambda表达式作为函数式接口类型的参数传递某个方法
- 使用函数式表达式对Lambda表达式进行强制类型转换
方法引用和构造器引用
方法引用和构造器引用可以让Lambda表达式更加简洁。
1.引用类型方法:
用代码说明:
//01
//定义一个函数式接口
@FunctionalInterface
public interface Converter {
//功能是将String类型转换成Integer类型
Integer convert(String from);
}
//02
//使用Lambda表达式创建Converter对象
//因为只有一条语句所以可以省略代码块花括号,由于表达式实现了方法需要的返回值,所以Lambda表达式将这条代码的值作为返回值
Converter converter=from -> Integer.valueOf(from);
//03
//调用converter的方法convert()实现转换
Integer convert = converter.convert("50");
System.out.println(convert);
//04
//当Lambda表达式代码块只有一行调用类方法的代码,可以用如下方法替换
//*方法引用代替Lambda表达式:引用类方法
//函数式接口中被实现方法的全部参数传给类方法作为参数
Converter converter=Integer::valueOf;
上面调用Integer类的valueOf()类方法实现Converter函数式接口中惟一的抽象方法,当调用Converter接口中唯一的抽象方法时,调用参数会传给Integer类的valueOf类方法
2.引用特定对象的实例方法
代码说明:
//01
//indexOf返回指定字符第一次出现的字符串内的索引
//使用Lambda表达式创建Converter对象
Converter converter=from -> "fkit.org".indexOf(from);
//02
//调用converter对象的convert方法
Integer tt = converter.convert("f");
System.out.println(tt);//结果为0
//03
//*方法引用代替Lambda表达式:引用特殊对象的实例方法
//函数式接口中被实现方法的全部参数传递给方法作为参数
//上面Lambda表达式只有一行代码调用"fkit.org"的indexOf方法,因此可以使用如下方法替换
Converter converter="fkit.org"::indexOf;
3.引用某类对象的实例方法
代码说明:
//01
//定义函数式接口,该接口有一个test方法,根据三个参数生成一个String返回值
@FunctionalInterface
public interface MyTest {
String test(String a,int b,int c);
}
//02
//使用Lambda表达式生成MyTest对象
MyTest test=((a, b, c) -> a.substring(b,c));
//03
//调用方法,test对象是由Lambda表达式创建,test()方法执行体就是Lambda表达式部分代码
String hello = test.test("hello mm bb cc", 2, 5);
System.out.println(hello);
//04
//*方法引用代替Lambda表达式:引用某类对象的实例方法
//函数式接口中被实现方法的第一个参数作为调用者
//后面的参数全部传给该方法作参数
MyTest test=String::substring;
4.引用构造器
代码说明:
//01
//创建一个函数式接口,包含win()方法,该方法根据String参数生成JFrame返回值
@FunctionalInterface
public interface YourTest {
JFrame win(String title);
}
//02
//通过yt调用win()方法,ty是Lambda表达式创建,win()执行体就是Lambda表达式的代码块,即执行体执行的是new JFrame(a);语句
JFrame jf = yt.win("我的窗口");
System.out.println(jf);
//03
//构造器引用代替Lambda表达式
//函数式接口中实现方法的全部参数传给构造器作为参数
YourTest yt=JFrame::new;
Lambda表达式与匿名内部类的区别和联系
相同点:
- 都可以直接访问 "effectively final"的局部变量 (java8开始,可以不加final修饰符,由系统默认添加。java将这个功能称为:Effectively final 功能.),以及外部类的成员变量(包括实例变量,类变量)
- Lambda表达式创建对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法
@FunctionalInterface
interface Displayble{
//定义一个抽象方法和默认方法
void display();
default int add(int a,int b){
return a+b;
}
}
public class LambdaAndinner {
private int age=12;
private static String name="软件中心";
public void test(){
String book="疯狂java";
Displayble dis=()->{
//访问"Effectively final"的局部变量
System.out.println("Book变量"+book);
//访问外部类的实例变量和类变量
System.out.println("外部age"+age);
System.out.println("外部name"+name);
};
dis.display();
//调用dis对象从接口中继承的add方法
System.out.println(dis.add(3, 5));
}
public static void main(String[] args) {
LambdaAndinner lambda=new LambdaAndinner();
lambda.test();
}
}
不同点:
- 匿名内部类可以为任意接口创建实例,不管接口包含多少个抽象方法,只要匿名内部类实现所有抽象方法即可,但是Lambda表达式只能为函数式接口创建实例
- 匿名内部类可以为抽象类甚至普通类创建实例,但是Lambda表达式只能为函数式接口创建实例
- 匿名内部类实现抽象方法的方法体允许调用接口中定义的默认方法,但Lambda表达式的代码块不允许调用接口中定义的默认方法。
使用Lambda表达式调用Arrays的类方法
略。。
枚举类
一个类的对象是有限固定的,这实例有限而且固定的类,在java中称为枚举类
手动实现枚举类
public static final int SEASON_SPRING = 1;
public static final int SEASON_SUMMER = 2;
public static final int SEASON_FALL = 3;
public static final int SEASON_WINTER = 14;
上面简单定义的手动枚举类有以下几个问题:
(1).类型不安全:因为上面的每个季节实际上是一个
int
整数,因此完全可以把一个季节当成一个int
整数使用,例如进行加法运算SEASON_SPRING+SEASON_SUMMER
,这样的代码完全正常。(2).没有命名空间:当需要使用季节时,必须在
SPRING
前使用SEASON_
前缀,否则程序可能与其他类中的静态常量混淆。(3).打印输出的意义不明确:当我们打印输出某个季节时,例如打印
SEASON_SPRING
,实际上输出的是1
,这个1很难猜测它代表了春天。
从这个意义上面来看,枚举类的存在的确很有意义,但是手动定义枚举类的代码量比较大,实现起来也比较麻烦,所以Java
从JDK1.5
后就增加了对枚举类的支持
枚举类入门
java5新增了enum关键字,用于定义枚举类。
枚举类与普通类的区别:
- 枚举类可以实现一个或者多个接口,使用enum定义枚举类默认继承java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显式继承其他父类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口
- 使用enum定义,非抽象的 枚举类默认使用final修饰,因此枚举类不能派生子类
- 枚举类的构造器只能使用private访问权限,如果省略构造器访问控制符,则默认使用private修饰,如果强制指定访问控制符,也只能是private
- 枚举类的所有实例必须在枚举类的第一行显式列出,否则枚举类永远都不能产生实例,列出实例时,系统会自动添加public static final修饰,无须程序员自己添加
枚举类提供一个方法values(),用于方便遍历所有枚举类
//定义一个枚举类
public enum SeasonEnum{
//在第一行列出4个枚举实例
A,B,C,D
}
使用枚举类的方式:
//格式EnumClass.variable
public class TTT {
public static void main(String[] args) {
//通过枚举类名.枚举实例,调用并输出
System.out.println(SeasonEnum.A);
//使用枚举类的values方法,通过foreach循环输出
for (SeasonEnum value : SeasonEnum.values()) {
System.out.println(value);
}
}
}
还有更多的枚举类方法可以查看java API。
枚举类的成员变量,方法,构造器
枚举类也是一种类,是一种比较特殊的类,他一样可以用于成员变量,方法,构造器。
成员变量:
//定义一个枚举类,且包含一个成员变量name
public enum Gender {
MALE,FEMALE;
//定义一个public修饰符的成员变量name
public String name;
}
//调用上面的枚举类
public class GenderTest {
public static void main(String[] args) {
//通过Enum的valueOf方法获取指定枚举类的枚举值
Gender g=Enum.valueOf(Gender.class,"FEMALE");
//直接为枚举值的name实例变量赋值
g.name="女";
//直接访问枚举值的name实例变量
System.out.println(g.name);
}
}
枚举类的实例只能是枚举值,不能随缘通过new来创建枚举类对象
方法:
枚举类定义方法与普通类类似。
//修改上面代码Gender
public enum Gender {
MALE,FEMALE;
//定义一个private修饰符的成员变量name
private String name;
//创建一个setName方法
public void setName(String name){
switch (this){
case MALE:
if(name.equals("男")){
this.name=name;
}else{
System.out.println("参数错误");
return;
}
break;
case FEMALE:
if(name.equals("女")){
this.name=name;
}else{
System.out.println("参数错误");
return;
}
break;
}
}
//创建getName方法
public String getName(){
return this.name;
}
}
//上面枚举方法调用
public class GenderTest {
public static void main(String[] args) {
//调用Gender的valueOf获取对象
Gender g=Gender.valueOf("FEMALE");
g.setName("女");
System.out.println(g+"代表:"+g.getName());
//下面设置name值会提示参数错误
g.setName("男");
System.out.println(g+"代表:"+g.getName());
}
}
枚举类通常设计成不可改变类,因此建议枚举类成员都是用private final修饰
构造器:
如果枚举类成员变量都为final修饰符修饰,就必须在构造器里初始化这些成员变量(或者在定义成员变量指定默认值,或在初始化块吃定初始值,这两种情况不常见),因此应该在枚举类里显式指定带参数的构造器
//一旦枚举类显式定义了带参构造器,列出枚举值时就必须对应的传入参数
public enum Gender {
MALE("男"),FEMALE("女");
//创建private的String类型name
private final String name;
//创建带参构造器
private Gender(String name){
this.name=name;
}
// 创建getName方法
public String getName(){
return this.name;
}
}
当Gender创建了带参构造器之后,列出枚举值时要对应构造器的参数格式。也就是说枚举类在列出枚举值时,实际上调用了构造器创建枚举对象,只是无需使用new关键字,也无需调用构造器。
在前面列出枚举值时无须传入参数,无须使用括号,因为前面的枚举类包含无参构造器
//下面两行代码实际上等同于 MALE("男"),FEMALE("女");
public static final Gender MALE=new Gender("男");
public static final Gender FEMALE=new Gender("女");
实现接口的枚举类
枚举类实现接口与普通类一样,枚举类实现一个或者多个接口时,需要实现该接口所包含的方法。
//定义一个接口
public interface GenderDesc {
void info();
}
//枚举类继承一个接口并且实现接口的方法
public enum Gender implements GenderDesc {
//枚举值
;
@Override
public void info() {
}
}
包含抽象方法的枚举类
public enum Operation {
PLUS{
public double eval(double x,double y){
return x+y;
}
},
MINUS{
public double eval(double x,double y){
return x-y;
}
},
TIMES{
public double eval(double x,double y){
return x*y;
}
},
DIVIDE{
public double eval(double x,double y){
return x/y;
}
};
//为枚举类定义抽象方法
//这个 抽象方法由不同的枚举值提供不同的实现
public abstract double eval(double x,double y);
public static void main(String[] args) {
System.out.println(Operation.DIVIDE.eval(3,5));
System.out.println(Operation.MINUS.eval(3,5));
System.out.println(Operation.PLUS.eval(3,5));
System.out.println(Operation.TIMES.eval(3,5));
}
}
枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动添加abstract关键字),但因为枚举类需要显式的创建枚举值,而不是作为父类,所以定义枚举值时需要为抽象方法提供实现,否则报错
对象与垃圾回收
垃圾回收机制特征:
- 垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(如数据库链接,网络IO等)
- 程序无法精确控制垃圾回收的运行,垃圾回收会在适当的时候运行。当对象永久失去引用后,系统就会在适当的时候回收它所占的内存。
- 在垃圾回收机制回收任何对象之前,送会先调用它的finalize()方法,该方法可能使对象重新复活(让一个引用变量重新引用该变量),从而导致垃圾回收机制取消回收
对象在内存中的状态
当一个对象在堆内存运行,根据他被引用变量所引用的状态,可以把所处的状态分成如下三种:
- 可达状态:当对象创建后,有一个以上的引用变量引用它,则这个对象处于可达状态,程序可以通过引用变量来调用该对象的实例变量和方法
- 可恢复状态:当程序中某个对象不在有任何的引用变量引用它,它就会进入可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象占用的内存,在回收该对象之前,系统会调用所有可恢复对象的finalize()方法进行资源清理。如果系统在调用finalize()方法后重新获取了一个引用变量引用该对象,则进入可达状态,反之进入不可达状态
- 不可达状态:当对象与所有引用变量的关联切断,且系统已经调用所有对象的finalize()方法后依旧没有使该对象变成可达状态,那么该对象永久失去引用,最后变成不可达状态。只有一个对象处于不可达状态,系统才会真正的回收该对象所占的资源。
强制垃圾回收
java的强制垃圾回收,这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定。
强制回收的两种方式:
- 调用System类的gc()静态方法:System.gc();
- 调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc();
finalize方法
特点:
- 用于不要主动调用某个对象的finalize()方法,该方法应该交给垃圾回收机制调用。
- finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会执行方法
- 当JVM执行可恢复对象的finalize()方法时,可能会使该对象或者系统中其他对象重写变成可达状态
- 当JVM执行finalize()方法时出现异常,垃圾回收机制不会报告异常,程序继续执行
对象的软,弱和虚引用
java对象引用的四种方式:
- 强引用:是指创建一个对象并把这个对象赋给一个引用变量。
- 软引用(SoftReference):如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
- 弱引用(WeakReference):弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
- 虚引用(PhantomReference):虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
提供四种方式的主要目的:
- 可以让程序员通过代码的方式决定某些对象的生命周期;
- 有利于JVM进行垃圾回收。
修饰符的适用范围
适用JAR文件
JAR文件其实是java文档文件,他是一种压缩文件,通常被称为JAR包,在文件中包含了一个名为META-INF/MANIFEST.MF的清单文件,这个清单文件是生成JAR文件时系统自动创建的。
使用JAR文件的好处:
-
安全。能够对JAR文件进行数字签名,只能够识别数字签名的用户使用里面的东西
-
加快下载速度。在网上下载applet时,如果存在多个文件而不打包,为了把每个文件都下载到客户端,需要为每个文件单独建立一个HTTP链接,这是非常耗时的工作。将这些 文件压缩成一个jar包,只需要建立一次http连接就能一次性下载所有文件
-
压缩。使文件变小
-
包装性。能够让jar包里面的包依赖于统一版本的类文件
-
可移植性。jar包作为嵌在java平台内部处理的标准,能够在各个平台直接使用
命令详解
详细可以看百度。
创建可执行JAR包
一个应用程序开发成功 后,大致有三种发布方式。
- 使用平台相关的编译器整个应用编译成平台相关的可执行性文件。这种方式常常需要第三方编译器的支持,而且编译生成的可执行文件丧失了跨平台的特性,甚至可能有一定的性能下降
- 为应用编译一个批处理的文件
- 将一个应用程序制作成可执行的jar包,通过普通的jar包来发布应用程序。
运行jar包的两种方式
- 使用java命令,运行时语法:java -jar xxx.jar
- 使用javax命令,运行时语法:javax xxx.jar
VM执行可恢复对象的finalize()方法时,可能会使该对象或者系统中其他对象重写变成可达状态
4. 当JVM执行finalize()方法时出现异常,垃圾回收机制不会报告异常,程序继续执行
对象的软,弱和虚引用
java对象引用的四种方式:
- 强引用:是指创建一个对象并把这个对象赋给一个引用变量。
- 软引用(SoftReference):如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
- 弱引用(WeakReference):弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
- 虚引用(PhantomReference):虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
提供四种方式的主要目的:
- 可以让程序员通过代码的方式决定某些对象的生命周期;
- 有利于JVM进行垃圾回收。
修饰符的适用范围
[外链图片转存中…(img-3Sy7foo4-1596782993719)]
适用JAR文件
JAR文件其实是java文档文件,他是一种压缩文件,通常被称为JAR包,在文件中包含了一个名为META-INF/MANIFEST.MF的清单文件,这个清单文件是生成JAR文件时系统自动创建的。
使用JAR文件的好处:
-
安全。能够对JAR文件进行数字签名,只能够识别数字签名的用户使用里面的东西
-
加快下载速度。在网上下载applet时,如果存在多个文件而不打包,为了把每个文件都下载到客户端,需要为每个文件单独建立一个HTTP链接,这是非常耗时的工作。将这些 文件压缩成一个jar包,只需要建立一次http连接就能一次性下载所有文件
-
压缩。使文件变小
-
包装性。能够让jar包里面的包依赖于统一版本的类文件
-
可移植性。jar包作为嵌在java平台内部处理的标准,能够在各个平台直接使用
[外链图片转存中…(img-YjX1CTxL-1596782993722)]
命令详解
[外链图片转存中…(img-p9NjtQoe-1596782993723)]
详细可以看百度。
创建可执行JAR包
一个应用程序开发成功 后,大致有三种发布方式。
- 使用平台相关的编译器整个应用编译成平台相关的可执行性文件。这种方式常常需要第三方编译器的支持,而且编译生成的可执行文件丧失了跨平台的特性,甚至可能有一定的性能下降
- 为应用编译一个批处理的文件
- 将一个应用程序制作成可执行的jar包,通过普通的jar包来发布应用程序。
运行jar包的两种方式
- 使用java命令,运行时语法:java -jar xxx.jar
- 使用javax命令,运行时语法:javax xxx.jar