继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
子类可以用自己的方式实现父类的方法
提高了类之间的耦合性,注意这是一个缺点,代码独立性变差
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。
final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写
子类不继承父类的构造器,而是调用
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
重写的好处在于子类可以根据需要,定义特定于自己的行为
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常
在子类中调用父类的被重写方法时,要使用 super 关键字
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
public class Main{ public static void main(String[] args) { show(new Cat()); show(new Dog()); Animal a = new Cat(); a.eat(); Cat c = (Cat)a; c.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的普通函数就相当于 C++ 的虚函数
如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法
类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体
public abstract class Main { private String name; private int age; private String address; public abstract double a(); // 抽象方法必须在抽象类中 }
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段
属性私有,方法公有
一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类 类描述对象的属性和方法。接口则包含类要实现的方法
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法
[可见度] interface 接口名称 [extends 其他的接口名] { // 声明变量 // 抽象方法 }
一个接口能继承另一个接口,和类之间的继承方式比较相似
和类不同,接口支持多继承
最常用的继承接口是没有包含任何方法的接口
标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情
功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用,如同文件夹一样
包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类
在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同
包没有父子关系
使用某一个包的成员
用static
修饰的字段,称为静态字段
class Person { public String name; public int age; // 定义静态字段number: public static int number; }
可以在实例中直接访问静态字段
用static
修饰的方法称为静态方法
public class Main { public static void main(String[] args) { Person.setNumber(99); System.out.println(Person.number); } } class Person { public static int number; public static void setNumber(int value) { number = value; } }
静态方法属于class
而不属于实例,因此,静态方法内部,无法访问this
变量,也无法访问实例字段,它只能访问静态字段。
注意main方法就是静态方法
接口中的字段自动静态常量,static final
定义在另一个类的内部,所以称为内部类
class Outer { class Inner { // 定义了一个Inner Class } }
void asyncHello() { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello, " + Outer.this.name); } }; new Thread(r).start(); }
class A{ static class B{ } }
classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索
class
classpath
就是一组目录的集合,它设置的搜索路径与操作系统相关
不要把任何Java核心库添加到classpath中!JVM根本不依赖classpath加载核心库!
把package
组织的目录层级,以及各个目录下的所有文件(包括.class
文件和其他文件)都打成一个jar文件,方便发送和备份
为了统一全球所有语言的编码,全球统一码联盟发布了Unicode
编码
Java的String
和char
在内存中总是以Unicode编码表示
用分隔符拼接数组的需求
public static void main(String[] args) { String[] names = {"Bob", "Alice", "Grace"}; StringJoiner sj = new StringJoiner(", "); for (String name : names) { sj.add(name); } System.out.println(sj.toString()); }
添加开头结尾
public static void main(String[] args) { String[] names = {"Bob", "Alice", "Grace"}; StringJoiner sj = new StringJoiner(", ", "Hello ", "!"); for (String name : names) { sj.add(name); } System.out.println(sj.toString()); }
枚举一个JavaBean的所有属性
public static void main(String[] args) throws Exception { BeanInfo info = Introspector.getBeanInfo(Person.class); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { System.out.println(pd.getName()); System.out.println(" " + pd.getReadMethod()); System.out.println(" " + pd.getWriteMethod()); } }
一个不变类(记录类)具有以下特点:
final
,无法派生子类;final
,保证创建实例后无法修改任何字段。final class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int x() { return this.x; } public int y() { return this.y; } }
表示范围大的数
BigInteger bi = new BigInteger("1235436565346456667"); System.out.println(bi.pow(34)); BigDecimal bd = new BigDecimal("3434.434654674567545"); System.out.println(bd.plus());
Java提供的常用工具类有:
反复使用System.out.println()
非常麻烦
解决方法是使用日志。
public static void main(String[] args) { Logger logger = Logger.getGlobal(); logger.info("start process..."); logger.warning("memory is running out..."); logger.fine("ignored."); logger.severe("process will be terminated..."); }
输出结果:
Java标准库内置的Logging使用并不是非常广泛
使用Java标准库内置的Logging有以下局限:
Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()
方法,就无法修改配置;
配置不太方便,需要在JVM启动时传递参数
和Java标准库提供的日志不同,Commons Logging是一个第三方日志库
可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统
默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging
需要引入对应jar包
public static void main(String[] args) { Log log = LogFactory.getLog(Main.class); log.info("start..."); log.warn("end."); }
将日志加密写入数据库的功能,需要自己开发
<?xml version="1.0" encoding="UTF-8"?> <Configuration> <Properties> <!-- 定义日志格式 --> <Property name="log.pattern">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n</Property> <!-- 定义文件名变量 --> <Property name="file.err.filename">log/err.log</Property> <Property name="file.err.pattern">log/err.%i.log.gz</Property> </Properties> <!-- 定义Appender,即目的地 --> <Appenders> <!-- 定义输出到屏幕 --> <Console name="console" target="SYSTEM_OUT"> <!-- 日志格式引用上面定义的log.pattern --> <PatternLayout pattern="${log.pattern}" /> </Console> <!-- 定义输出到文件,文件名引用上面定义的file.err.filename --> <RollingFile name="err" bufferedIO="true" fileName="${file.err.filename}" filePattern="${file.err.pattern}"> <PatternLayout pattern="${log.pattern}" /> <Policies> <!-- 根据文件大小自动切割日志 --> <SizeBasedTriggeringPolicy size="1 MB" /> </Policies> <!-- 保留最近10份 --> <DefaultRolloverStrategy max="10" /> </RollingFile> </Appenders> <Loggers> <Root level="info"> <!-- 对info级别的日志,输出到console --> <AppenderRef ref="console" level="info" /> <!-- 对error级别的日志,输出到err,即上面定义的RollingFile --> <AppenderRef ref="err" level="error" /> </Root> </Loggers> </Configuration>
所有被标记为静态的内容,会在类刚加载的时候就分配,而不是在对象创建的时候分配,所以说静态内容一定会在第一个对象初始化之前完成加载。
代码块在对象创建时执行,也是属于类的内容,但是它在构造方法执行之前执行
静态代码块和上面的静态方法和静态变量一样,在类刚加载时就会调用
public class Student { static int a; static { a = 10; } public static void main(String[] args) { System.out.println(Student.a); }
冒泡排序就是冒泡,其实就是不断使得我们无序数组中的最大数向前移动,经历n轮循环逐渐将每一个数推向最前。
插入排序其实就跟我们打牌是一样的,我们在摸牌的时候,牌堆是乱序的,但是我们一张一张摸到手中进行排序,使得它变成了有序的!
选择排序其实就是每次都选择当前数组中最大的数排到最前面!
比抽象类更抽象,代表某个确切的功能,只包含方法的定义。
通过声明default
关键字来给抽象方法一个默认实现:
public interface Eat { default void eat(){ //do something... } }
对,你没猜错,就是和局部变量一样哒~
public class Test { public void test(){ class Inner{ } Inner inner = new Inner(); } }
反正我是没用过!内部类 -> 累不累 -> 反正我累了!
读作λ
表达式,它其实就是我们接口匿名实现的简化
public class Main { public static void main(String[] args) { Eat e = new Eat() { @Override public void eat() { System.out.println("eat"); } }; } } interface Eat{ void eat(); }
等价于:
public class Main { public static void main(String[] args) { Eat e = () -> { }; } } interface Eat{ void eat(); }
求1到100所有数的和
暴力求解
public static void main(String[] args) { int sum = 0; for (int i = 1; i <= 100; i++) { //for循环暴力求解,简单,但是效率似乎低了一些 sum += i; } System.out.println(sum); } public static void main(String[] args) { System.out.println((1 + 100) * 50); //高斯求和公式,利用数学,瞬间计算结果! }
说到最后,其实数学和逻辑思维才是解决问题的最终办法!